From d36cf7a3c25444d806a7f52a3af4abea34e61261 Mon Sep 17 00:00:00 2001
From: soorq
Date: Fri, 10 Apr 2026 19:07:00 +0300
Subject: [PATCH 01/30] feat(config): implement centralized configuration
module
---
.env.example | 2 ++
libs/config/src/config.module.ts | 45 +++++++++++++++++++++++++++++++
libs/config/src/config.schema.ts | 10 +++++++
libs/config/src/config.types.d.ts | 15 +++++++++++
libs/config/src/index.ts | 2 ++
libs/config/tsconfig.lib.json | 9 +++++++
nest-cli.json | 14 +++++++++-
package.json | 21 ++++++++++-----
pnpm-lock.yaml | 45 +++++++++++++++++++++++++++++++
src/app.module.ts | 3 ++-
src/main.ts | 8 +++++-
test/jest-e2e.json | 4 +++
tsconfig.json | 12 +++++++--
13 files changed, 179 insertions(+), 11 deletions(-)
create mode 100644 .env.example
create mode 100644 libs/config/src/config.module.ts
create mode 100644 libs/config/src/config.schema.ts
create mode 100644 libs/config/src/config.types.d.ts
create mode 100644 libs/config/src/index.ts
create mode 100644 libs/config/tsconfig.lib.json
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..a29a613
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,2 @@
+PORT=2000
+NODE_ENV=development
\ No newline at end of file
diff --git a/libs/config/src/config.module.ts b/libs/config/src/config.module.ts
new file mode 100644
index 0000000..3a2a306
--- /dev/null
+++ b/libs/config/src/config.module.ts
@@ -0,0 +1,45 @@
+import { Module } from '@nestjs/common';
+import { ConfigModule as NestConfigModule } from '@nestjs/config';
+import * as path from 'path';
+import { ConfigSchema } from './config.schema';
+import { ZodError } from 'zod/v4';
+
+const validateConfig = (config: Record) => {
+ try {
+ return ConfigSchema.parse(config);
+ } catch (error) {
+ if (error instanceof ZodError) {
+ console.group('\nENVIRONMENT_VALIDATION_ERROR\n');
+
+ error.issues.forEach((issue) => {
+ const field = issue.path.join('.') || 'ROOT';
+
+ console.group(`Field: ${field}`);
+ console.error(`Message: ${issue.message}`);
+ console.error(`Code: ${issue.code.toUpperCase()}`);
+ console.groupEnd();
+ console.error('\n');
+ });
+
+ console.groupEnd();
+
+ throw new Error('Invalid environment configuration');
+ }
+ throw error;
+ }
+};
+
+@Module({
+ imports: [
+ NestConfigModule.forRoot({
+ isGlobal: true,
+ envFilePath: path.resolve(process.cwd(), '.env'),
+ validate: validateConfig,
+ validationOptions: {
+ abortEarly: true,
+ },
+ }),
+ ],
+ exports: [NestConfigModule],
+})
+export class ConfigModule {}
diff --git a/libs/config/src/config.schema.ts b/libs/config/src/config.schema.ts
new file mode 100644
index 0000000..c7e8350
--- /dev/null
+++ b/libs/config/src/config.schema.ts
@@ -0,0 +1,10 @@
+import { z } from 'zod/v4';
+
+export const ConfigSchema = z.object({
+ PORT: z.coerce.number().default(3000),
+ NODE_ENV: z
+ .enum(['development', 'production', 'test'])
+ .default('development'),
+});
+
+export type Config = z.infer;
diff --git a/libs/config/src/config.types.d.ts b/libs/config/src/config.types.d.ts
new file mode 100644
index 0000000..09709b0
--- /dev/null
+++ b/libs/config/src/config.types.d.ts
@@ -0,0 +1,15 @@
+import '@nestjs/config';
+import { Config } from './config.schema';
+
+declare module '@nestjs/config' {
+ interface ConfigService<_K = unknown, _WasValidated extends boolean = false> {
+ /**
+ * Переопределяем метод get, чтобы он предлагал ключи из нашей схемы
+ */
+ get(key: T): Config[T];
+ /**
+ * Переопределяем метод getOrThrow, чтобы он предлагал ключи из нашей схемы
+ */
+ getOrThrow(key: T): Config[T];
+ }
+}
diff --git a/libs/config/src/index.ts b/libs/config/src/index.ts
new file mode 100644
index 0000000..0c71077
--- /dev/null
+++ b/libs/config/src/index.ts
@@ -0,0 +1,2 @@
+export * from './config.module';
+export * from './config.schema';
diff --git a/libs/config/tsconfig.lib.json b/libs/config/tsconfig.lib.json
new file mode 100644
index 0000000..ec3efa6
--- /dev/null
+++ b/libs/config/tsconfig.lib.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "declaration": true,
+ "outDir": "../../dist/libs/config"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
+}
diff --git a/nest-cli.json b/nest-cli.json
index f9aa683..f0481e6 100644
--- a/nest-cli.json
+++ b/nest-cli.json
@@ -3,6 +3,18 @@
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
- "deleteOutDir": true
+ "deleteOutDir": true,
+ "webpack": true
+ },
+ "projects": {
+ "config": {
+ "type": "library",
+ "root": "libs/config",
+ "entryFile": "index",
+ "sourceRoot": "libs/config/src",
+ "compilerOptions": {
+ "tsConfigPath": "libs/config/tsconfig.lib.json"
+ }
+ }
}
}
diff --git a/package.json b/package.json
index 1b859d3..e6ddd83 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
- "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"libs/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
@@ -21,10 +21,12 @@
},
"dependencies": {
"@nestjs/common": "^10.0.0",
+ "@nestjs/config": "^4.0.4",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"reflect-metadata": "^0.2.0",
- "rxjs": "^7.8.1"
+ "rxjs": "^7.8.1",
+ "zod": "^4.3.6"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
@@ -55,7 +57,7 @@
"json",
"ts"
],
- "rootDir": "src",
+ "rootDir": ".",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
@@ -63,8 +65,15 @@
"collectCoverageFrom": [
"**/*.(t|j)s"
],
- "coverageDirectory": "../coverage",
- "testEnvironment": "node"
+ "coverageDirectory": "./coverage",
+ "testEnvironment": "node",
+ "roots": [
+ "/src/",
+ "/libs/"
+ ],
+ "moduleNameMapper": {
+ "^@libs/config(|/.*)$": "/libs/config/src/$1"
+ }
},
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
-}
+}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 39f752a..638e084 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -11,6 +11,9 @@ importers:
'@nestjs/common':
specifier: ^10.0.0
version: 10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/config':
+ specifier: ^4.0.4
+ version: 4.0.4(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)
'@nestjs/core':
specifier: ^10.0.0
version: 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2)
@@ -23,6 +26,9 @@ importers:
rxjs:
specifier: ^7.8.1
version: 7.8.2
+ zod:
+ specifier: ^4.3.6
+ version: 4.3.6
devDependencies:
'@nestjs/cli':
specifier: ^10.0.0
@@ -449,6 +455,12 @@ packages:
class-validator:
optional: true
+ '@nestjs/config@4.0.4':
+ resolution: {integrity: sha512-CJPjNitr0bAufSEnRe2N+JbnVmMmDoo6hvKCPzXgZoGwJSmp/dZPk9f/RMbuD/+Q1ZJPjwsRpq0vxna++Knwow==}
+ peerDependencies:
+ '@nestjs/common': ^10.0.0 || ^11.0.0
+ rxjs: ^7.1.0
+
'@nestjs/core@10.4.22':
resolution: {integrity: sha512-6IX9+VwjiKtCjx+mXVPncpkQ5ZjKfmssOZPFexmT+6T9H9wZ3svpYACAo7+9e7Nr9DZSoRZw3pffkJP7Z0UjaA==}
peerDependencies:
@@ -1199,6 +1211,18 @@ packages:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'}
+ dotenv-expand@12.0.3:
+ resolution: {integrity: sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==}
+ engines: {node: '>=12'}
+
+ dotenv@16.6.1:
+ resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
+ engines: {node: '>=12'}
+
+ dotenv@17.4.1:
+ resolution: {integrity: sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==}
+ engines: {node: '>=12'}
+
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -2806,6 +2830,9 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
+ zod@4.3.6:
+ resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
+
snapshots:
'@angular-devkit/core@17.3.11(chokidar@3.6.0)':
@@ -3326,6 +3353,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@nestjs/config@4.0.4(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)':
+ dependencies:
+ '@nestjs/common': 10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ dotenv: 17.4.1
+ dotenv-expand: 12.0.3
+ lodash: 4.18.1
+ rxjs: 7.8.2
+
'@nestjs/core@10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2)':
dependencies:
'@nestjs/common': 10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2)
@@ -4184,6 +4219,14 @@ snapshots:
dependencies:
esutils: 2.0.3
+ dotenv-expand@12.0.3:
+ dependencies:
+ dotenv: 16.6.1
+
+ dotenv@16.6.1: {}
+
+ dotenv@17.4.1: {}
+
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -6004,3 +6047,5 @@ snapshots:
yn@3.1.1: {}
yocto-queue@0.1.0: {}
+
+ zod@4.3.6: {}
diff --git a/src/app.module.ts b/src/app.module.ts
index 8662803..b4044b1 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -1,9 +1,10 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
+import { ConfigModule } from '@libs/config';
@Module({
- imports: [],
+ imports: [ConfigModule],
controllers: [AppController],
providers: [AppService],
})
diff --git a/src/main.ts b/src/main.ts
index 13cad38..67e6de2 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,8 +1,14 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
+import { ConfigService } from '@nestjs/config';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
- await app.listen(3000);
+
+ const config = app.get(ConfigService);
+ const port = config.getOrThrow('PORT');
+
+ await app.listen(port);
}
+
bootstrap();
diff --git a/test/jest-e2e.json b/test/jest-e2e.json
index e9d912f..164de3e 100644
--- a/test/jest-e2e.json
+++ b/test/jest-e2e.json
@@ -5,5 +5,9 @@
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
+ },
+ "moduleNameMapper": {
+ "@libs/config/(.*)": "/../libs/config/src/$1",
+ "@libs/config": "/../libs/config/src"
}
}
diff --git a/tsconfig.json b/tsconfig.json
index 95f5641..2ba2906 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -16,6 +16,14 @@
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
- "noFallthroughCasesInSwitch": false
+ "noFallthroughCasesInSwitch": false,
+ "paths": {
+ "@libs/config": [
+ "libs/config/src"
+ ],
+ "@libs/config/*": [
+ "libs/config/src/*"
+ ]
+ }
}
-}
+}
\ No newline at end of file
From e96728b28ed170618ecccac0daa02e607b6dfa9f Mon Sep 17 00:00:00 2001
From: Danil <123034340+soorq@users.noreply.github.com>
Date: Fri, 10 Apr 2026 19:43:46 +0300
Subject: [PATCH 02/30] feat(db): integrate drizzle orm, pg driver and initial
migrations (#3)
* chore(drizzle):feat(database): setup database lib / configure drizzle
* chore(db): generate initial migration
---
drizzle.config.ts | 10 +
libs/database/src/database.constants.ts | 2 +
libs/database/src/database.module.ts | 145 +++
libs/database/src/index.ts | 3 +
.../interfaces/database-module.interface.ts | 26 +
libs/database/src/interfaces/index.ts | 1 +
libs/database/tsconfig.lib.json | 9 +
migrations/0000_stale_sunspot.sql | 1 +
migrations/meta/0000_snapshot.json | 20 +
migrations/meta/_journal.json | 13 +
nest-cli.json | 9 +
package.json | 15 +-
pnpm-lock.yaml | 1055 +++++++++++++++++
src/shared/entities/index.ts | 1 +
src/shared/entities/schema.ts | 3 +
test/jest-e2e.json | 4 +-
tsconfig.json | 19 +-
17 files changed, 1322 insertions(+), 14 deletions(-)
create mode 100644 drizzle.config.ts
create mode 100644 libs/database/src/database.constants.ts
create mode 100644 libs/database/src/database.module.ts
create mode 100644 libs/database/src/index.ts
create mode 100644 libs/database/src/interfaces/database-module.interface.ts
create mode 100644 libs/database/src/interfaces/index.ts
create mode 100644 libs/database/tsconfig.lib.json
create mode 100644 migrations/0000_stale_sunspot.sql
create mode 100644 migrations/meta/0000_snapshot.json
create mode 100644 migrations/meta/_journal.json
create mode 100644 src/shared/entities/index.ts
create mode 100644 src/shared/entities/schema.ts
diff --git a/drizzle.config.ts b/drizzle.config.ts
new file mode 100644
index 0000000..4bb294b
--- /dev/null
+++ b/drizzle.config.ts
@@ -0,0 +1,10 @@
+import { defineConfig } from 'drizzle-kit';
+
+export default defineConfig({
+ schema: './src/shared/entities/index.ts',
+ out: './migrations',
+ dialect: 'postgresql',
+ dbCredentials: {
+ url: process.env.DATABASE_URL!,
+ },
+});
diff --git a/libs/database/src/database.constants.ts b/libs/database/src/database.constants.ts
new file mode 100644
index 0000000..a6109aa
--- /dev/null
+++ b/libs/database/src/database.constants.ts
@@ -0,0 +1,2 @@
+export const DATABASE_OPTIONS = 'DATABASE_OPTIONS';
+export const DATABASE_SERVICE = 'DATABASE_SERVICE';
diff --git a/libs/database/src/database.module.ts b/libs/database/src/database.module.ts
new file mode 100644
index 0000000..0c9cf2c
--- /dev/null
+++ b/libs/database/src/database.module.ts
@@ -0,0 +1,145 @@
+import {
+ type DynamicModule,
+ Logger,
+ Module,
+ OnApplicationShutdown,
+ type Provider,
+ type Type,
+} from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { drizzle } from 'drizzle-orm/node-postgres';
+import { Pool } from 'pg';
+import { DATABASE_OPTIONS, DATABASE_SERVICE } from './database.constants';
+import type {
+ DatabaseModuleAsyncOptions,
+ DatabaseModuleOptions,
+ DatabaseModuleOptionsFactory,
+} from './interfaces';
+
+@Module({
+ providers: [],
+})
+export class DatabaseModule implements OnApplicationShutdown {
+ private static logger = new Logger(DatabaseModule.name);
+
+ private static pool: Pool;
+
+ static register(config: DatabaseModuleOptions): DynamicModule {
+ return {
+ module: DatabaseModule,
+ global: config.global ?? false,
+ providers: [this.createOptionsProvider(config), this.createDatabaseProvider()],
+ exports: [DATABASE_SERVICE],
+ };
+ }
+
+ static registerAsync(config: DatabaseModuleAsyncOptions): DynamicModule {
+ return {
+ module: DatabaseModule,
+ global: config.global ?? false,
+ imports: config.imports ?? [],
+ providers: [...this.createAsyncProviders(config), this.createDatabaseProvider()],
+ exports: [DATABASE_SERVICE],
+ };
+ }
+
+ private static createOptionsProvider(options: DatabaseModuleOptions): Provider {
+ return {
+ provide: DATABASE_OPTIONS,
+ useValue: options,
+ };
+ }
+
+ private static createDatabaseProvider(): Provider {
+ return {
+ provide: DATABASE_SERVICE,
+ useFactory: async (cfg: ConfigService, opts: DatabaseModuleOptions) => {
+ const baseUrl = cfg.get('DATABASE_URL');
+
+ const pool = new Pool({
+ connectionString: baseUrl,
+ max: 20,
+ idleTimeoutMillis: 30000,
+ });
+
+ pool.on('connect', (client) => {
+ client.query(`SET search_path TO ${opts.schemaName || 'public'}`);
+ });
+
+ this.pool = pool;
+
+ return drizzle(pool, {
+ schema: opts.schema,
+ logger: opts.logging
+ ? {
+ logQuery(query, params) {
+ const start = Date.now();
+ DatabaseModule.logger.debug(`SQL: ${query}`);
+
+ if (params?.length) {
+ DatabaseModule.logger.debug(`Params: ${JSON.stringify(params)}`);
+ }
+
+ const duration = Date.now() - start;
+ DatabaseModule.logger.debug(`Execution time: ${duration}ms`);
+ },
+ }
+ : false,
+ });
+ },
+ inject: [ConfigService, DATABASE_OPTIONS],
+ };
+ }
+
+ private static createAsyncProviders(options: DatabaseModuleAsyncOptions): Provider[] {
+ if (options.useFactory) {
+ return [
+ {
+ provide: DATABASE_OPTIONS,
+ useFactory: options.useFactory,
+ inject: options.inject || [],
+ },
+ ...(options.extraProviders || []),
+ ];
+ }
+
+ const providers: Provider[] = [];
+
+ const useClass = options.useClass || options.useExisting;
+ if (!useClass) {
+ throw new Error(
+ 'You must provide either useClass, useExisting or useFactory in DatabaseModuleAsyncOptions'
+ );
+ }
+
+ providers.push(this.createAsyncOptionsProvider(useClass));
+
+ if (options.useClass) {
+ providers.push({ provide: useClass, useClass });
+ }
+
+ if (options.extraProviders) {
+ providers.push(...options.extraProviders);
+ }
+
+ return providers;
+ }
+
+ private static createAsyncOptionsProvider(
+ useClass: Type
+ ): Provider {
+ return {
+ provide: DATABASE_OPTIONS,
+ useFactory: async (optionsFactory: DatabaseModuleOptionsFactory) =>
+ optionsFactory.createDatabaseOptions(),
+ inject: [useClass],
+ };
+ }
+
+ async onApplicationShutdown(_signal?: string) {
+ if (DatabaseModule.pool) {
+ DatabaseModule.logger.log('Closing database connections...');
+ await DatabaseModule.pool.end();
+ }
+ }
+}
diff --git a/libs/database/src/index.ts b/libs/database/src/index.ts
new file mode 100644
index 0000000..f6e9f8f
--- /dev/null
+++ b/libs/database/src/index.ts
@@ -0,0 +1,3 @@
+export * from './database.module';
+export * from './database.constants';
+export type { DatabaseService } from './interfaces/database-module.interface';
diff --git a/libs/database/src/interfaces/database-module.interface.ts b/libs/database/src/interfaces/database-module.interface.ts
new file mode 100644
index 0000000..26acdb1
--- /dev/null
+++ b/libs/database/src/interfaces/database-module.interface.ts
@@ -0,0 +1,26 @@
+import type { FactoryProvider, ModuleMetadata, Provider, Type } from '@nestjs/common';
+import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
+
+export interface DatabaseModuleOptions {
+ schemaName: string;
+ schema: Record;
+ logging?: boolean;
+ global?: boolean;
+}
+
+export interface DatabaseModuleOptionsFactory {
+ createDatabaseOptions(): Promise | DatabaseModuleOptions;
+}
+
+export interface DatabaseModuleAsyncOptions extends Pick {
+ useExisting?: Type;
+ useClass?: Type;
+ useFactory?: (
+ ...args: unknown[]
+ ) => Promise> | Omit;
+ inject?: FactoryProvider['inject'];
+ global?: boolean;
+ extraProviders?: Provider[];
+}
+
+export type DatabaseService> = NodePgDatabase;
diff --git a/libs/database/src/interfaces/index.ts b/libs/database/src/interfaces/index.ts
new file mode 100644
index 0000000..9d0a95e
--- /dev/null
+++ b/libs/database/src/interfaces/index.ts
@@ -0,0 +1 @@
+export * from './database-module.interface';
diff --git a/libs/database/tsconfig.lib.json b/libs/database/tsconfig.lib.json
new file mode 100644
index 0000000..21c8d58
--- /dev/null
+++ b/libs/database/tsconfig.lib.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "declaration": true,
+ "outDir": "../../dist/libs/database"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
+}
diff --git a/migrations/0000_stale_sunspot.sql b/migrations/0000_stale_sunspot.sql
new file mode 100644
index 0000000..a615183
--- /dev/null
+++ b/migrations/0000_stale_sunspot.sql
@@ -0,0 +1 @@
+CREATE SCHEMA "base";
diff --git a/migrations/meta/0000_snapshot.json b/migrations/meta/0000_snapshot.json
new file mode 100644
index 0000000..f99863e
--- /dev/null
+++ b/migrations/meta/0000_snapshot.json
@@ -0,0 +1,20 @@
+{
+ "id": "a40dfb7f-7d44-4721-bf37-a197b5f1e479",
+ "prevId": "00000000-0000-0000-0000-000000000000",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {},
+ "enums": {},
+ "schemas": {
+ "base": "base"
+ },
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json
new file mode 100644
index 0000000..17d6d2b
--- /dev/null
+++ b/migrations/meta/_journal.json
@@ -0,0 +1,13 @@
+{
+ "version": "7",
+ "dialect": "postgresql",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "7",
+ "when": 1775839169154,
+ "tag": "0000_stale_sunspot",
+ "breakpoints": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/nest-cli.json b/nest-cli.json
index f0481e6..1079603 100644
--- a/nest-cli.json
+++ b/nest-cli.json
@@ -15,6 +15,15 @@
"compilerOptions": {
"tsConfigPath": "libs/config/tsconfig.lib.json"
}
+ },
+ "database": {
+ "type": "library",
+ "root": "libs/database",
+ "entryFile": "index",
+ "sourceRoot": "libs/database/src",
+ "compilerOptions": {
+ "tsConfigPath": "libs/database/tsconfig.lib.json"
+ }
}
}
}
diff --git a/package.json b/package.json
index e6ddd83..f473ae6 100644
--- a/package.json
+++ b/package.json
@@ -17,13 +17,19 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
- "test:e2e": "jest --config ./test/jest-e2e.json"
+ "test:e2e": "jest --config ./test/jest-e2e.json",
+ "db:generate": "drizzle-kit generate",
+ "db:migrate": "drizzle-kit migrate",
+ "db:studio": "drizzle-kit studio"
},
"dependencies": {
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^4.0.4",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
+ "drizzle-orm": "^0.45.2",
+ "drizzle-zod": "^0.8.3",
+ "pg": "^8.20.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"zod": "^4.3.6"
@@ -35,9 +41,11 @@
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
+ "@types/pg": "^8.20.0",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
+ "drizzle-kit": "^0.31.10",
"eslint": "^8.42.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
@@ -72,8 +80,9 @@
"/libs/"
],
"moduleNameMapper": {
- "^@libs/config(|/.*)$": "/libs/config/src/$1"
+ "^@libs/config(|/.*)$": "/libs/config/src/$1",
+ "^@libs/database(|/.*)$": "/libs/database/src/$1"
}
},
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
-}
\ No newline at end of file
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 638e084..b220b73 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,6 +20,15 @@ importers:
'@nestjs/platform-express':
specifier: ^10.0.0
version: 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)
+ drizzle-orm:
+ specifier: ^0.45.2
+ version: 0.45.2(@types/pg@8.20.0)(pg@8.20.0)
+ drizzle-zod:
+ specifier: ^0.8.3
+ version: 0.8.3(drizzle-orm@0.45.2(@types/pg@8.20.0)(pg@8.20.0))(zod@4.3.6)
+ pg:
+ specifier: ^8.20.0
+ version: 8.20.0
reflect-metadata:
specifier: ^0.2.0
version: 0.2.2
@@ -48,6 +57,9 @@ importers:
'@types/node':
specifier: ^20.3.1
version: 20.19.39
+ '@types/pg':
+ specifier: ^8.20.0
+ version: 8.20.0
'@types/supertest':
specifier: ^6.0.0
version: 6.0.3
@@ -57,6 +69,9 @@ importers:
'@typescript-eslint/parser':
specifier: ^6.0.0
version: 6.21.0(eslint@8.57.1)(typescript@5.9.3)
+ drizzle-kit:
+ specifier: ^0.31.10
+ version: 0.31.10
eslint:
specifier: ^8.42.0
version: 8.57.1
@@ -290,6 +305,461 @@ packages:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
+ '@drizzle-team/brocli@0.10.2':
+ resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
+
+ '@esbuild-kit/core-utils@3.3.2':
+ resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
+ deprecated: 'Merged into tsx: https://tsx.is'
+
+ '@esbuild-kit/esm-loader@2.6.5':
+ resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==}
+ deprecated: 'Merged into tsx: https://tsx.is'
+
+ '@esbuild/aix-ppc64@0.25.12':
+ resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/aix-ppc64@0.27.7':
+ resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.18.20':
+ resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm64@0.25.12':
+ resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm64@0.27.7':
+ resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.18.20':
+ resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-arm@0.25.12':
+ resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-arm@0.27.7':
+ resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.18.20':
+ resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/android-x64@0.25.12':
+ resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/android-x64@0.27.7':
+ resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.18.20':
+ resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-arm64@0.25.12':
+ resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-arm64@0.27.7':
+ resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.18.20':
+ resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.25.12':
+ resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.27.7':
+ resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.18.20':
+ resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-arm64@0.27.7':
+ resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.18.20':
+ resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.25.12':
+ resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.27.7':
+ resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.18.20':
+ resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm64@0.25.12':
+ resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm64@0.27.7':
+ resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.18.20':
+ resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.25.12':
+ resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.27.7':
+ resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.18.20':
+ resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.25.12':
+ resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.27.7':
+ resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.18.20':
+ resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.25.12':
+ resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.27.7':
+ resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.18.20':
+ resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.25.12':
+ resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.27.7':
+ resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.18.20':
+ resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.25.12':
+ resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.27.7':
+ resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.18.20':
+ resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.25.12':
+ resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.27.7':
+ resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.18.20':
+ resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.25.12':
+ resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.27.7':
+ resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.18.20':
+ resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.25.12':
+ resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.27.7':
+ resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-arm64@0.27.7':
+ resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.18.20':
+ resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.25.12':
+ resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.27.7':
+ resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-arm64@0.27.7':
+ resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.18.20':
+ resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.25.12':
+ resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.27.7':
+ resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/openharmony-arm64@0.27.7':
+ resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.18.20':
+ resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/sunos-x64@0.25.12':
+ resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/sunos-x64@0.27.7':
+ resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.18.20':
+ resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-arm64@0.25.12':
+ resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-arm64@0.27.7':
+ resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.18.20':
+ resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.12':
+ resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.27.7':
+ resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.18.20':
+ resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.25.12':
+ resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.27.7':
+ resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
'@eslint-community/eslint-utils@4.9.1':
resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -628,6 +1098,9 @@ packages:
'@types/node@20.19.39':
resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==}
+ '@types/pg@8.20.0':
+ resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==}
+
'@types/qs@6.15.0':
resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==}
@@ -1223,6 +1696,108 @@ packages:
resolution: {integrity: sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==}
engines: {node: '>=12'}
+ drizzle-kit@0.31.10:
+ resolution: {integrity: sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==}
+ hasBin: true
+
+ drizzle-orm@0.45.2:
+ resolution: {integrity: sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==}
+ peerDependencies:
+ '@aws-sdk/client-rds-data': '>=3'
+ '@cloudflare/workers-types': '>=4'
+ '@electric-sql/pglite': '>=0.2.0'
+ '@libsql/client': '>=0.10.0'
+ '@libsql/client-wasm': '>=0.10.0'
+ '@neondatabase/serverless': '>=0.10.0'
+ '@op-engineering/op-sqlite': '>=2'
+ '@opentelemetry/api': ^1.4.1
+ '@planetscale/database': '>=1.13'
+ '@prisma/client': '*'
+ '@tidbcloud/serverless': '*'
+ '@types/better-sqlite3': '*'
+ '@types/pg': '*'
+ '@types/sql.js': '*'
+ '@upstash/redis': '>=1.34.7'
+ '@vercel/postgres': '>=0.8.0'
+ '@xata.io/client': '*'
+ better-sqlite3: '>=7'
+ bun-types: '*'
+ expo-sqlite: '>=14.0.0'
+ gel: '>=2'
+ knex: '*'
+ kysely: '*'
+ mysql2: '>=2'
+ pg: '>=8'
+ postgres: '>=3'
+ prisma: '*'
+ sql.js: '>=1'
+ sqlite3: '>=5'
+ peerDependenciesMeta:
+ '@aws-sdk/client-rds-data':
+ optional: true
+ '@cloudflare/workers-types':
+ optional: true
+ '@electric-sql/pglite':
+ optional: true
+ '@libsql/client':
+ optional: true
+ '@libsql/client-wasm':
+ optional: true
+ '@neondatabase/serverless':
+ optional: true
+ '@op-engineering/op-sqlite':
+ optional: true
+ '@opentelemetry/api':
+ optional: true
+ '@planetscale/database':
+ optional: true
+ '@prisma/client':
+ optional: true
+ '@tidbcloud/serverless':
+ optional: true
+ '@types/better-sqlite3':
+ optional: true
+ '@types/pg':
+ optional: true
+ '@types/sql.js':
+ optional: true
+ '@upstash/redis':
+ optional: true
+ '@vercel/postgres':
+ optional: true
+ '@xata.io/client':
+ optional: true
+ better-sqlite3:
+ optional: true
+ bun-types:
+ optional: true
+ expo-sqlite:
+ optional: true
+ gel:
+ optional: true
+ knex:
+ optional: true
+ kysely:
+ optional: true
+ mysql2:
+ optional: true
+ pg:
+ optional: true
+ postgres:
+ optional: true
+ prisma:
+ optional: true
+ sql.js:
+ optional: true
+ sqlite3:
+ optional: true
+
+ drizzle-zod@0.8.3:
+ resolution: {integrity: sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww==}
+ peerDependencies:
+ drizzle-orm: '>=0.36.0'
+ zod: ^3.25.0 || ^4.0.0
+
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -1276,6 +1851,21 @@ packages:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
+ esbuild@0.18.20:
+ resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ esbuild@0.25.12:
+ resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ esbuild@0.27.7:
+ resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
+ engines: {node: '>=18'}
+ hasBin: true
+
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
@@ -1524,6 +2114,9 @@ packages:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
engines: {node: '>=10'}
+ get-tsconfig@4.13.7:
+ resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==}
+
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -2191,6 +2784,40 @@ packages:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
+ pg-cloudflare@1.3.0:
+ resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==}
+
+ pg-connection-string@2.12.0:
+ resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==}
+
+ pg-int8@1.0.1:
+ resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
+ engines: {node: '>=4.0.0'}
+
+ pg-pool@3.13.0:
+ resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==}
+ peerDependencies:
+ pg: '>=8.0'
+
+ pg-protocol@1.13.0:
+ resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==}
+
+ pg-types@2.2.0:
+ resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
+ engines: {node: '>=4'}
+
+ pg@8.20.0:
+ resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==}
+ engines: {node: '>= 16.0.0'}
+ peerDependencies:
+ pg-native: '>=3.0.1'
+ peerDependenciesMeta:
+ pg-native:
+ optional: true
+
+ pgpass@1.0.5:
+ resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
+
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -2214,6 +2841,22 @@ packages:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
+ postgres-array@2.0.0:
+ resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
+ engines: {node: '>=4'}
+
+ postgres-bytea@1.0.1:
+ resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==}
+ engines: {node: '>=0.10.0'}
+
+ postgres-date@1.0.7:
+ resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
+ engines: {node: '>=0.10.0'}
+
+ postgres-interval@1.2.0:
+ resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
+ engines: {node: '>=0.10.0'}
+
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -2303,6 +2946,9 @@ packages:
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
engines: {node: '>=8'}
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
resolve.exports@2.0.3:
resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==}
engines: {node: '>=10'}
@@ -2436,6 +3082,10 @@ packages:
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
engines: {node: '>= 12'}
+ split2@4.2.0:
+ resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
+ engines: {node: '>= 10.x'}
+
sprintf-js@1.0.3:
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
@@ -2650,6 +3300,11 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+ tsx@4.21.0:
+ resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@@ -3065,6 +3720,240 @@ snapshots:
dependencies:
'@jridgewell/trace-mapping': 0.3.9
+ '@drizzle-team/brocli@0.10.2': {}
+
+ '@esbuild-kit/core-utils@3.3.2':
+ dependencies:
+ esbuild: 0.18.20
+ source-map-support: 0.5.21
+
+ '@esbuild-kit/esm-loader@2.6.5':
+ dependencies:
+ '@esbuild-kit/core-utils': 3.3.2
+ get-tsconfig: 4.13.7
+
+ '@esbuild/aix-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/aix-ppc64@0.27.7':
+ optional: true
+
+ '@esbuild/android-arm64@0.18.20':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm64@0.27.7':
+ optional: true
+
+ '@esbuild/android-arm@0.18.20':
+ optional: true
+
+ '@esbuild/android-arm@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm@0.27.7':
+ optional: true
+
+ '@esbuild/android-x64@0.18.20':
+ optional: true
+
+ '@esbuild/android-x64@0.25.12':
+ optional: true
+
+ '@esbuild/android-x64@0.27.7':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.18.20':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.27.7':
+ optional: true
+
+ '@esbuild/darwin-x64@0.18.20':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-x64@0.27.7':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.18.20':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.27.7':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.18.20':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.27.7':
+ optional: true
+
+ '@esbuild/linux-arm64@0.18.20':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm64@0.27.7':
+ optional: true
+
+ '@esbuild/linux-arm@0.18.20':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm@0.27.7':
+ optional: true
+
+ '@esbuild/linux-ia32@0.18.20':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ia32@0.27.7':
+ optional: true
+
+ '@esbuild/linux-loong64@0.18.20':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-loong64@0.27.7':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.18.20':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.12':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.27.7':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.18.20':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.27.7':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.18.20':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.27.7':
+ optional: true
+
+ '@esbuild/linux-s390x@0.18.20':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.12':
+ optional: true
+
+ '@esbuild/linux-s390x@0.27.7':
+ optional: true
+
+ '@esbuild/linux-x64@0.18.20':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-x64@0.27.7':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.27.7':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.18.20':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.27.7':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.27.7':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.18.20':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.27.7':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.27.7':
+ optional: true
+
+ '@esbuild/sunos-x64@0.18.20':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.12':
+ optional: true
+
+ '@esbuild/sunos-x64@0.27.7':
+ optional: true
+
+ '@esbuild/win32-arm64@0.18.20':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-arm64@0.27.7':
+ optional: true
+
+ '@esbuild/win32-ia32@0.18.20':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/win32-ia32@0.27.7':
+ optional: true
+
+ '@esbuild/win32-x64@0.18.20':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-x64@0.27.7':
+ optional: true
+
'@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)':
dependencies:
eslint: 8.57.1
@@ -3567,6 +4456,12 @@ snapshots:
dependencies:
undici-types: 6.21.0
+ '@types/pg@8.20.0':
+ dependencies:
+ '@types/node': 20.19.39
+ pg-protocol: 1.13.0
+ pg-types: 2.2.0
+
'@types/qs@6.15.0': {}
'@types/range-parser@1.2.7': {}
@@ -4227,6 +5122,23 @@ snapshots:
dotenv@17.4.1: {}
+ drizzle-kit@0.31.10:
+ dependencies:
+ '@drizzle-team/brocli': 0.10.2
+ '@esbuild-kit/esm-loader': 2.6.5
+ esbuild: 0.25.12
+ tsx: 4.21.0
+
+ drizzle-orm@0.45.2(@types/pg@8.20.0)(pg@8.20.0):
+ optionalDependencies:
+ '@types/pg': 8.20.0
+ pg: 8.20.0
+
+ drizzle-zod@0.8.3(drizzle-orm@0.45.2(@types/pg@8.20.0)(pg@8.20.0))(zod@4.3.6):
+ dependencies:
+ drizzle-orm: 0.45.2(@types/pg@8.20.0)(pg@8.20.0)
+ zod: 4.3.6
+
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -4273,6 +5185,89 @@ snapshots:
has-tostringtag: 1.0.2
hasown: 2.0.2
+ esbuild@0.18.20:
+ optionalDependencies:
+ '@esbuild/android-arm': 0.18.20
+ '@esbuild/android-arm64': 0.18.20
+ '@esbuild/android-x64': 0.18.20
+ '@esbuild/darwin-arm64': 0.18.20
+ '@esbuild/darwin-x64': 0.18.20
+ '@esbuild/freebsd-arm64': 0.18.20
+ '@esbuild/freebsd-x64': 0.18.20
+ '@esbuild/linux-arm': 0.18.20
+ '@esbuild/linux-arm64': 0.18.20
+ '@esbuild/linux-ia32': 0.18.20
+ '@esbuild/linux-loong64': 0.18.20
+ '@esbuild/linux-mips64el': 0.18.20
+ '@esbuild/linux-ppc64': 0.18.20
+ '@esbuild/linux-riscv64': 0.18.20
+ '@esbuild/linux-s390x': 0.18.20
+ '@esbuild/linux-x64': 0.18.20
+ '@esbuild/netbsd-x64': 0.18.20
+ '@esbuild/openbsd-x64': 0.18.20
+ '@esbuild/sunos-x64': 0.18.20
+ '@esbuild/win32-arm64': 0.18.20
+ '@esbuild/win32-ia32': 0.18.20
+ '@esbuild/win32-x64': 0.18.20
+
+ esbuild@0.25.12:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.12
+ '@esbuild/android-arm': 0.25.12
+ '@esbuild/android-arm64': 0.25.12
+ '@esbuild/android-x64': 0.25.12
+ '@esbuild/darwin-arm64': 0.25.12
+ '@esbuild/darwin-x64': 0.25.12
+ '@esbuild/freebsd-arm64': 0.25.12
+ '@esbuild/freebsd-x64': 0.25.12
+ '@esbuild/linux-arm': 0.25.12
+ '@esbuild/linux-arm64': 0.25.12
+ '@esbuild/linux-ia32': 0.25.12
+ '@esbuild/linux-loong64': 0.25.12
+ '@esbuild/linux-mips64el': 0.25.12
+ '@esbuild/linux-ppc64': 0.25.12
+ '@esbuild/linux-riscv64': 0.25.12
+ '@esbuild/linux-s390x': 0.25.12
+ '@esbuild/linux-x64': 0.25.12
+ '@esbuild/netbsd-arm64': 0.25.12
+ '@esbuild/netbsd-x64': 0.25.12
+ '@esbuild/openbsd-arm64': 0.25.12
+ '@esbuild/openbsd-x64': 0.25.12
+ '@esbuild/openharmony-arm64': 0.25.12
+ '@esbuild/sunos-x64': 0.25.12
+ '@esbuild/win32-arm64': 0.25.12
+ '@esbuild/win32-ia32': 0.25.12
+ '@esbuild/win32-x64': 0.25.12
+
+ esbuild@0.27.7:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.27.7
+ '@esbuild/android-arm': 0.27.7
+ '@esbuild/android-arm64': 0.27.7
+ '@esbuild/android-x64': 0.27.7
+ '@esbuild/darwin-arm64': 0.27.7
+ '@esbuild/darwin-x64': 0.27.7
+ '@esbuild/freebsd-arm64': 0.27.7
+ '@esbuild/freebsd-x64': 0.27.7
+ '@esbuild/linux-arm': 0.27.7
+ '@esbuild/linux-arm64': 0.27.7
+ '@esbuild/linux-ia32': 0.27.7
+ '@esbuild/linux-loong64': 0.27.7
+ '@esbuild/linux-mips64el': 0.27.7
+ '@esbuild/linux-ppc64': 0.27.7
+ '@esbuild/linux-riscv64': 0.27.7
+ '@esbuild/linux-s390x': 0.27.7
+ '@esbuild/linux-x64': 0.27.7
+ '@esbuild/netbsd-arm64': 0.27.7
+ '@esbuild/netbsd-x64': 0.27.7
+ '@esbuild/openbsd-arm64': 0.27.7
+ '@esbuild/openbsd-x64': 0.27.7
+ '@esbuild/openharmony-arm64': 0.27.7
+ '@esbuild/sunos-x64': 0.27.7
+ '@esbuild/win32-arm64': 0.27.7
+ '@esbuild/win32-ia32': 0.27.7
+ '@esbuild/win32-x64': 0.27.7
+
escalade@3.2.0: {}
escape-html@1.0.3: {}
@@ -4605,6 +5600,10 @@ snapshots:
get-stream@6.0.1: {}
+ get-tsconfig@4.13.7:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
@@ -5431,6 +6430,41 @@ snapshots:
path-type@4.0.0: {}
+ pg-cloudflare@1.3.0:
+ optional: true
+
+ pg-connection-string@2.12.0: {}
+
+ pg-int8@1.0.1: {}
+
+ pg-pool@3.13.0(pg@8.20.0):
+ dependencies:
+ pg: 8.20.0
+
+ pg-protocol@1.13.0: {}
+
+ pg-types@2.2.0:
+ dependencies:
+ pg-int8: 1.0.1
+ postgres-array: 2.0.0
+ postgres-bytea: 1.0.1
+ postgres-date: 1.0.7
+ postgres-interval: 1.2.0
+
+ pg@8.20.0:
+ dependencies:
+ pg-connection-string: 2.12.0
+ pg-pool: 3.13.0(pg@8.20.0)
+ pg-protocol: 1.13.0
+ pg-types: 2.2.0
+ pgpass: 1.0.5
+ optionalDependencies:
+ pg-cloudflare: 1.3.0
+
+ pgpass@1.0.5:
+ dependencies:
+ split2: 4.2.0
+
picocolors@1.1.1: {}
picomatch@2.3.2: {}
@@ -5445,6 +6479,16 @@ snapshots:
pluralize@8.0.0: {}
+ postgres-array@2.0.0: {}
+
+ postgres-bytea@1.0.1: {}
+
+ postgres-date@1.0.7: {}
+
+ postgres-interval@1.2.0:
+ dependencies:
+ xtend: 4.0.2
+
prelude-ls@1.2.1: {}
prettier-linter-helpers@1.0.1:
@@ -5520,6 +6564,8 @@ snapshots:
resolve-from@5.0.0: {}
+ resolve-pkg-maps@1.0.0: {}
+
resolve.exports@2.0.3: {}
resolve@1.22.11:
@@ -5672,6 +6718,8 @@ snapshots:
source-map@0.7.6: {}
+ split2@4.2.0: {}
+
sprintf-js@1.0.3: {}
stack-utils@2.0.6:
@@ -5877,6 +6925,13 @@ snapshots:
tslib@2.8.1: {}
+ tsx@4.21.0:
+ dependencies:
+ esbuild: 0.27.7
+ get-tsconfig: 4.13.7
+ optionalDependencies:
+ fsevents: 2.3.3
+
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
diff --git a/src/shared/entities/index.ts b/src/shared/entities/index.ts
new file mode 100644
index 0000000..9d8074b
--- /dev/null
+++ b/src/shared/entities/index.ts
@@ -0,0 +1 @@
+export { baseSchema } from './schema';
diff --git a/src/shared/entities/schema.ts b/src/shared/entities/schema.ts
new file mode 100644
index 0000000..8a9bcc9
--- /dev/null
+++ b/src/shared/entities/schema.ts
@@ -0,0 +1,3 @@
+import { pgSchema, type PgSchema } from 'drizzle-orm/pg-core';
+
+export const baseSchema: PgSchema = pgSchema('base');
diff --git a/test/jest-e2e.json b/test/jest-e2e.json
index 164de3e..f9730e0 100644
--- a/test/jest-e2e.json
+++ b/test/jest-e2e.json
@@ -8,6 +8,8 @@
},
"moduleNameMapper": {
"@libs/config/(.*)": "/../libs/config/src/$1",
- "@libs/config": "/../libs/config/src"
+ "@libs/config": "/../libs/config/src",
+ "@libs/database/(.*)": "/../libs/database/src/$1",
+ "@libs/database": "/../libs/database/src"
}
}
diff --git a/tsconfig.json b/tsconfig.json
index 2ba2906..32ea749 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"module": "commonjs",
- "declaration": true,
+ "declaration": false,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
@@ -9,7 +9,6 @@
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
- "baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
@@ -18,12 +17,12 @@
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false,
"paths": {
- "@libs/config": [
- "libs/config/src"
- ],
- "@libs/config/*": [
- "libs/config/src/*"
- ]
+ "@libs/config": ["./libs/config/src"],
+ "@libs/config/*": ["./libs/config/src/*"],
+ "@libs/database": ["./libs/database/src"],
+ "@libs/database/*": ["./libs/database/src/*"]
}
- }
-}
\ No newline at end of file
+ },
+ "include": ["src/**/*", "libs/**/*", "drizzle.config.ts"],
+ "exclude": ["dist", "node_modules"]
+}
From 193f273203e7329e6d50af678772bf34deebfb24 Mon Sep 17 00:00:00 2001
From: Maksym Berehovyi <108676512+maksberegovoi@users.noreply.github.com>
Date: Fri, 10 Apr 2026 20:17:55 +0300
Subject: [PATCH 03/30] chore(repo): setup code quality tools and development
environment
* chore: update eslint configuration
* chore: add husky and lint-staged configuration
* chore: add prettier configuration
* chore: add CI workflow and .env.example
* chore: bump per reviewer requests
* chore: remove workflow
---
.env.example | 5 +-
.eslintrc.js | 51 ++++++-----
.husky/pre-commit | 1 +
.lintstagedrc.mjs | 4 +
.prettierrc | 7 +-
pnpm-lock.yaml | 218 ++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 259 insertions(+), 27 deletions(-)
create mode 100644 .husky/pre-commit
create mode 100644 .lintstagedrc.mjs
diff --git a/.env.example b/.env.example
index a29a613..826b5f6 100644
--- a/.env.example
+++ b/.env.example
@@ -1,2 +1,3 @@
-PORT=2000
-NODE_ENV=development
\ No newline at end of file
+PORT=3005
+DATABASE_URL="postgresql://postgres:root@localhost:5432/task-tracker"
+NODE_ENV=development
diff --git a/.eslintrc.js b/.eslintrc.js
index 259de13..ed4ab51 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,25 +1,30 @@
module.exports = {
- parser: '@typescript-eslint/parser',
- parserOptions: {
- project: 'tsconfig.json',
- tsconfigRootDir: __dirname,
- sourceType: 'module',
- },
- plugins: ['@typescript-eslint/eslint-plugin'],
- extends: [
- 'plugin:@typescript-eslint/recommended',
- 'plugin:prettier/recommended',
- ],
- root: true,
- env: {
- node: true,
- jest: true,
- },
- ignorePatterns: ['.eslintrc.js'],
- rules: {
- '@typescript-eslint/interface-name-prefix': 'off',
- '@typescript-eslint/explicit-function-return-type': 'off',
- '@typescript-eslint/explicit-module-boundary-types': 'off',
- '@typescript-eslint/no-explicit-any': 'off',
- },
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ project: 'tsconfig.json',
+ tsconfigRootDir: __dirname,
+ sourceType: 'module',
+ },
+ plugins: ['@typescript-eslint/eslint-plugin'],
+ extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
+ root: true,
+ env: {
+ node: true,
+ jest: true,
+ },
+ ignorePatterns: ['.eslintrc.js'],
+ rules: {
+ '@typescript-eslint/interface-name-prefix': 'off',
+ '@typescript-eslint/explicit-function-return-type': 'off',
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-unused-vars': [
+ 'error',
+ {
+ argsIgnorePattern: '^_',
+ varsIgnorePattern: '^_',
+ caughtErrorsIgnorePattern: '^_',
+ },
+ ],
+ },
};
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..e02c24e
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1 @@
+pnpm lint-staged
\ No newline at end of file
diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs
new file mode 100644
index 0000000..68caded
--- /dev/null
+++ b/.lintstagedrc.mjs
@@ -0,0 +1,4 @@
+export default {
+ '*.{ts,js}': ['eslint --fix', 'prettier --write'],
+ '*.{json,css,md}': ['prettier --write'],
+};
diff --git a/.prettierrc b/.prettierrc
index dcb7279..c5c6203 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,4 +1,7 @@
{
"singleQuote": true,
- "trailingComma": "all"
-}
\ No newline at end of file
+ "trailingComma": "all",
+ "printWidth": 100,
+ "tabWidth": 4,
+ "semi": true
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b220b73..13d9673 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -81,9 +81,15 @@ importers:
eslint-plugin-prettier:
specifier: ^5.0.0
version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.8.2)
+ husky:
+ specifier: ^9.1.7
+ version: 9.1.7
jest:
specifier: ^29.5.0
version: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))
+ lint-staged:
+ specifier: ^16.4.0
+ version: 16.4.0
prettier:
specifier: ^3.0.0
version: 3.8.2
@@ -1299,6 +1305,10 @@ packages:
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
engines: {node: '>=8'}
+ ansi-escapes@7.3.0:
+ resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==}
+ engines: {node: '>=18'}
+
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@@ -1495,6 +1505,10 @@ packages:
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
engines: {node: '>=8'}
+ cli-cursor@5.0.0:
+ resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
+ engines: {node: '>=18'}
+
cli-spinners@2.9.2:
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
engines: {node: '>=6'}
@@ -1503,6 +1517,10 @@ packages:
resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==}
engines: {node: 10.* || >= 12.*}
+ cli-truncate@5.2.0:
+ resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
+ engines: {node: '>=20'}
+
cli-width@3.0.0:
resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
engines: {node: '>= 10'}
@@ -1533,10 +1551,17 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+ colorette@2.0.20:
+ resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
+
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
+ commander@14.0.3:
+ resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
+ engines: {node: '>=20'}
+
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -1815,6 +1840,9 @@ packages:
resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
engines: {node: '>=12'}
+ emoji-regex@10.6.0:
+ resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
+
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -1829,6 +1857,10 @@ packages:
resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
engines: {node: '>=10.13.0'}
+ environment@1.1.0:
+ resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
+ engines: {node: '>=18'}
+
error-ex@1.3.4:
resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
@@ -1956,6 +1988,9 @@ packages:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
+ eventemitter3@5.0.4:
+ resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
+
events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
@@ -2098,6 +2133,10 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
+ get-east-asian-width@1.5.0:
+ resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
+ engines: {node: '>=18'}
+
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@@ -2194,6 +2233,11 @@ packages:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'}
+ husky@9.1.7:
+ resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
+ engines: {node: '>=18'}
+ hasBin: true
+
iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
@@ -2256,6 +2300,10 @@ packages:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
+ is-fullwidth-code-point@5.1.0:
+ resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==}
+ engines: {node: '>=18'}
+
is-generator-fn@2.1.0:
resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==}
engines: {node: '>=6'}
@@ -2514,6 +2562,15 @@ packages:
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+ lint-staged@16.4.0:
+ resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==}
+ engines: {node: '>=20.17'}
+ hasBin: true
+
+ listr2@9.0.5:
+ resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==}
+ engines: {node: '>=20.0.0'}
+
loader-runner@4.3.1:
resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==}
engines: {node: '>=6.11.5'}
@@ -2539,6 +2596,10 @@ packages:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'}
+ log-update@6.1.0:
+ resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
+ engines: {node: '>=18'}
+
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@@ -2611,6 +2672,10 @@ packages:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
+ mimic-function@5.0.1:
+ resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
+ engines: {node: '>=18'}
+
minimatch@3.1.5:
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
@@ -2708,6 +2773,10 @@ packages:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
engines: {node: '>=6'}
+ onetime@7.0.0:
+ resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
+ engines: {node: '>=18'}
+
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -2829,6 +2898,10 @@ packages:
resolution: {integrity: sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==}
engines: {node: '>=12'}
+ picomatch@4.0.4:
+ resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
+ engines: {node: '>=12'}
+
pirates@4.0.7:
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
engines: {node: '>= 6'}
@@ -2962,10 +3035,17 @@ packages:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'}
+ restore-cursor@5.1.0:
+ resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
+ engines: {node: '>=18'}
+
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ rfdc@1.4.1:
+ resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
+
rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
deprecated: Rimraf versions prior to v4 are no longer supported
@@ -3064,6 +3144,14 @@ packages:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'}
+ slice-ansi@7.1.2:
+ resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==}
+ engines: {node: '>=18'}
+
+ slice-ansi@8.0.0:
+ resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
+ engines: {node: '>=20'}
+
source-map-support@0.5.13:
resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==}
@@ -3101,6 +3189,10 @@ packages:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
+ string-argv@0.3.2:
+ resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
+ engines: {node: '>=0.6.19'}
+
string-length@4.0.2:
resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==}
engines: {node: '>=10'}
@@ -3113,6 +3205,14 @@ packages:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
+ string-width@7.2.0:
+ resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
+ engines: {node: '>=18'}
+
+ string-width@8.2.0:
+ resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==}
+ engines: {node: '>=20'}
+
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
@@ -3209,6 +3309,10 @@ packages:
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
+ tinyexec@1.1.1:
+ resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==}
+ engines: {node: '>=18'}
+
tmp@0.0.33:
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
engines: {node: '>=0.6.0'}
@@ -3451,6 +3555,10 @@ packages:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
+ wrap-ansi@9.0.2:
+ resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
+ engines: {node: '>=18'}
+
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
@@ -3469,6 +3577,11 @@ packages:
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+ yaml@2.8.3:
+ resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==}
+ engines: {node: '>= 14.6'}
+ hasBin: true
+
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
@@ -4730,6 +4843,10 @@ snapshots:
dependencies:
type-fest: 0.21.3
+ ansi-escapes@7.3.0:
+ dependencies:
+ environment: 1.1.0
+
ansi-regex@5.0.1: {}
ansi-regex@6.2.2: {}
@@ -4953,6 +5070,10 @@ snapshots:
dependencies:
restore-cursor: 3.1.0
+ cli-cursor@5.0.0:
+ dependencies:
+ restore-cursor: 5.1.0
+
cli-spinners@2.9.2: {}
cli-table3@0.6.5:
@@ -4961,6 +5082,11 @@ snapshots:
optionalDependencies:
'@colors/colors': 1.5.0
+ cli-truncate@5.2.0:
+ dependencies:
+ slice-ansi: 8.0.0
+ string-width: 8.2.0
+
cli-width@3.0.0: {}
cli-width@4.1.0: {}
@@ -4983,10 +5109,14 @@ snapshots:
color-name@1.1.4: {}
+ colorette@2.0.20: {}
+
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
+ commander@14.0.3: {}
+
commander@2.20.3: {}
commander@4.1.1: {}
@@ -5153,6 +5283,8 @@ snapshots:
emittery@0.13.1: {}
+ emoji-regex@10.6.0: {}
+
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@@ -5164,6 +5296,8 @@ snapshots:
graceful-fs: 4.2.11
tapable: 2.3.2
+ environment@1.1.0: {}
+
error-ex@1.3.4:
dependencies:
is-arrayish: 0.2.1
@@ -5371,6 +5505,8 @@ snapshots:
etag@1.8.1: {}
+ eventemitter3@5.0.4: {}
+
events@3.3.0: {}
execa@5.1.1:
@@ -5578,6 +5714,8 @@ snapshots:
get-caller-file@2.0.5: {}
+ get-east-asian-width@1.5.0: {}
+
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -5690,6 +5828,8 @@ snapshots:
human-signals@2.1.0: {}
+ husky@9.1.7: {}
+
iconv-lite@0.4.24:
dependencies:
safer-buffer: 2.1.2
@@ -5769,6 +5909,10 @@ snapshots:
is-fullwidth-code-point@3.0.0: {}
+ is-fullwidth-code-point@5.1.0:
+ dependencies:
+ get-east-asian-width: 1.5.0
+
is-generator-fn@2.1.0: {}
is-glob@4.0.3:
@@ -6201,6 +6345,24 @@ snapshots:
lines-and-columns@1.2.4: {}
+ lint-staged@16.4.0:
+ dependencies:
+ commander: 14.0.3
+ listr2: 9.0.5
+ picomatch: 4.0.4
+ string-argv: 0.3.2
+ tinyexec: 1.1.1
+ yaml: 2.8.3
+
+ listr2@9.0.5:
+ dependencies:
+ cli-truncate: 5.2.0
+ colorette: 2.0.20
+ eventemitter3: 5.0.4
+ log-update: 6.1.0
+ rfdc: 1.4.1
+ wrap-ansi: 9.0.2
+
loader-runner@4.3.1: {}
locate-path@5.0.0:
@@ -6222,6 +6384,14 @@ snapshots:
chalk: 4.1.2
is-unicode-supported: 0.1.0
+ log-update@6.1.0:
+ dependencies:
+ ansi-escapes: 7.3.0
+ cli-cursor: 5.0.0
+ slice-ansi: 7.1.2
+ strip-ansi: 7.2.0
+ wrap-ansi: 9.0.2
+
lru-cache@10.4.3: {}
lru-cache@5.1.1:
@@ -6275,6 +6445,8 @@ snapshots:
mimic-fn@2.1.0: {}
+ mimic-function@5.0.1: {}
+
minimatch@3.1.5:
dependencies:
brace-expansion: 1.1.13
@@ -6355,6 +6527,10 @@ snapshots:
dependencies:
mimic-fn: 2.1.0
+ onetime@7.0.0:
+ dependencies:
+ mimic-function: 5.0.1
+
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -6471,6 +6647,8 @@ snapshots:
picomatch@4.0.1: {}
+ picomatch@4.0.4: {}
+
pirates@4.0.7: {}
pkg-dir@4.2.0:
@@ -6579,8 +6757,15 @@ snapshots:
onetime: 5.1.2
signal-exit: 3.0.7
+ restore-cursor@5.1.0:
+ dependencies:
+ onetime: 7.0.0
+ signal-exit: 4.1.0
+
reusify@1.1.0: {}
+ rfdc@1.4.1: {}
+
rimraf@3.0.2:
dependencies:
glob: 7.2.3
@@ -6702,6 +6887,16 @@ snapshots:
slash@3.0.0: {}
+ slice-ansi@7.1.2:
+ dependencies:
+ ansi-styles: 6.2.3
+ is-fullwidth-code-point: 5.1.0
+
+ slice-ansi@8.0.0:
+ dependencies:
+ ansi-styles: 6.2.3
+ is-fullwidth-code-point: 5.1.0
+
source-map-support@0.5.13:
dependencies:
buffer-from: 1.1.2
@@ -6730,6 +6925,8 @@ snapshots:
streamsearch@1.1.0: {}
+ string-argv@0.3.2: {}
+
string-length@4.0.2:
dependencies:
char-regex: 1.0.2
@@ -6747,6 +6944,17 @@ snapshots:
emoji-regex: 9.2.2
strip-ansi: 7.2.0
+ string-width@7.2.0:
+ dependencies:
+ emoji-regex: 10.6.0
+ get-east-asian-width: 1.5.0
+ strip-ansi: 7.2.0
+
+ string-width@8.2.0:
+ dependencies:
+ get-east-asian-width: 1.5.0
+ strip-ansi: 7.2.0
+
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
@@ -6836,6 +7044,8 @@ snapshots:
through@2.3.8: {}
+ tinyexec@1.1.1: {}
+
tmp@0.0.33:
dependencies:
os-tmpdir: 1.0.2
@@ -7074,6 +7284,12 @@ snapshots:
string-width: 5.1.2
strip-ansi: 7.2.0
+ wrap-ansi@9.0.2:
+ dependencies:
+ ansi-styles: 6.2.3
+ string-width: 7.2.0
+ strip-ansi: 7.2.0
+
wrappy@1.0.2: {}
write-file-atomic@4.0.2:
@@ -7087,6 +7303,8 @@ snapshots:
yallist@3.1.1: {}
+ yaml@2.8.3: {}
+
yargs-parser@21.1.1: {}
yargs@17.7.2:
From 4b790ab821b3df83d553b1174edf99a677c3e98c Mon Sep 17 00:00:00 2001
From: Maksym Berehovyi <108676512+maksberegovoi@users.noreply.github.com>
Date: Fri, 10 Apr 2026 21:05:54 +0300
Subject: [PATCH 04/30] chore(test): migrate from Jest to Vitest and setup
lint-staged
* chore: add lint staged
* chore: remove jest and add vitest deps
* chore: add vitest.config.ts
* chore: update scripts for testing
* chore: add vitest global types to tsconfig
* chore: migrate e2e tests configuration to vitest
* chore: fix eslint globals and include test folder in tsconfig
* chore: format code with prettier
---
.eslintrc.js | 11 +-
libs/config/src/config.module.ts | 56 +-
libs/config/src/config.schema.ts | 6 +-
libs/config/src/config.types.d.ts | 20 +-
libs/database/src/database.module.ts | 244 +-
.../interfaces/database-module.interface.ts | 26 +-
package.json | 146 +-
pnpm-lock.yaml | 2929 ++++++-----------
src/app.controller.spec.ts | 24 +-
src/app.controller.ts | 10 +-
src/app.module.ts | 6 +-
src/app.service.ts | 6 +-
src/main.ts | 8 +-
test/app.e2e-spec.ts | 25 +-
test/jest-e2e.json | 15 -
tsconfig.json | 60 +-
vitest.config.e2e.ts | 22 +
vitest.config.ts | 22 +
18 files changed, 1380 insertions(+), 2256 deletions(-)
delete mode 100644 test/jest-e2e.json
create mode 100644 vitest.config.e2e.ts
create mode 100644 vitest.config.ts
diff --git a/.eslintrc.js b/.eslintrc.js
index ed4ab51..f28531c 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -10,9 +10,16 @@ module.exports = {
root: true,
env: {
node: true,
- jest: true,
},
- ignorePatterns: ['.eslintrc.js'],
+ globals: {
+ describe: 'readonly',
+ it: 'readonly',
+ expect: 'readonly',
+ beforeEach: 'readonly',
+ afterEach: 'readonly',
+ vi: 'readonly',
+ },
+ ignorePatterns: ['.eslintrc.js', 'dist', 'node_modules'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
diff --git a/libs/config/src/config.module.ts b/libs/config/src/config.module.ts
index 3a2a306..48e55bf 100644
--- a/libs/config/src/config.module.ts
+++ b/libs/config/src/config.module.ts
@@ -5,41 +5,41 @@ import { ConfigSchema } from './config.schema';
import { ZodError } from 'zod/v4';
const validateConfig = (config: Record) => {
- try {
- return ConfigSchema.parse(config);
- } catch (error) {
- if (error instanceof ZodError) {
- console.group('\nENVIRONMENT_VALIDATION_ERROR\n');
+ try {
+ return ConfigSchema.parse(config);
+ } catch (error) {
+ if (error instanceof ZodError) {
+ console.group('\nENVIRONMENT_VALIDATION_ERROR\n');
- error.issues.forEach((issue) => {
- const field = issue.path.join('.') || 'ROOT';
+ error.issues.forEach((issue) => {
+ const field = issue.path.join('.') || 'ROOT';
- console.group(`Field: ${field}`);
- console.error(`Message: ${issue.message}`);
- console.error(`Code: ${issue.code.toUpperCase()}`);
- console.groupEnd();
- console.error('\n');
- });
+ console.group(`Field: ${field}`);
+ console.error(`Message: ${issue.message}`);
+ console.error(`Code: ${issue.code.toUpperCase()}`);
+ console.groupEnd();
+ console.error('\n');
+ });
- console.groupEnd();
+ console.groupEnd();
- throw new Error('Invalid environment configuration');
+ throw new Error('Invalid environment configuration');
+ }
+ throw error;
}
- throw error;
- }
};
@Module({
- imports: [
- NestConfigModule.forRoot({
- isGlobal: true,
- envFilePath: path.resolve(process.cwd(), '.env'),
- validate: validateConfig,
- validationOptions: {
- abortEarly: true,
- },
- }),
- ],
- exports: [NestConfigModule],
+ imports: [
+ NestConfigModule.forRoot({
+ isGlobal: true,
+ envFilePath: path.resolve(process.cwd(), '.env'),
+ validate: validateConfig,
+ validationOptions: {
+ abortEarly: true,
+ },
+ }),
+ ],
+ exports: [NestConfigModule],
})
export class ConfigModule {}
diff --git a/libs/config/src/config.schema.ts b/libs/config/src/config.schema.ts
index c7e8350..6042da3 100644
--- a/libs/config/src/config.schema.ts
+++ b/libs/config/src/config.schema.ts
@@ -1,10 +1,8 @@
import { z } from 'zod/v4';
export const ConfigSchema = z.object({
- PORT: z.coerce.number().default(3000),
- NODE_ENV: z
- .enum(['development', 'production', 'test'])
- .default('development'),
+ PORT: z.coerce.number().default(3000),
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
});
export type Config = z.infer;
diff --git a/libs/config/src/config.types.d.ts b/libs/config/src/config.types.d.ts
index 09709b0..c47f988 100644
--- a/libs/config/src/config.types.d.ts
+++ b/libs/config/src/config.types.d.ts
@@ -2,14 +2,14 @@ import '@nestjs/config';
import { Config } from './config.schema';
declare module '@nestjs/config' {
- interface ConfigService<_K = unknown, _WasValidated extends boolean = false> {
- /**
- * Переопределяем метод get, чтобы он предлагал ключи из нашей схемы
- */
- get(key: T): Config[T];
- /**
- * Переопределяем метод getOrThrow, чтобы он предлагал ключи из нашей схемы
- */
- getOrThrow(key: T): Config[T];
- }
+ interface ConfigService<_K = unknown, _WasValidated extends boolean = false> {
+ /**
+ * Переопределяем метод get, чтобы он предлагал ключи из нашей схемы
+ */
+ get(key: T): Config[T];
+ /**
+ * Переопределяем метод getOrThrow, чтобы он предлагал ключи из нашей схемы
+ */
+ getOrThrow(key: T): Config[T];
+ }
}
diff --git a/libs/database/src/database.module.ts b/libs/database/src/database.module.ts
index 0c9cf2c..7a89484 100644
--- a/libs/database/src/database.module.ts
+++ b/libs/database/src/database.module.ts
@@ -1,145 +1,147 @@
import {
- type DynamicModule,
- Logger,
- Module,
- OnApplicationShutdown,
- type Provider,
- type Type,
+ type DynamicModule,
+ Logger,
+ Module,
+ OnApplicationShutdown,
+ type Provider,
+ type Type,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import { DATABASE_OPTIONS, DATABASE_SERVICE } from './database.constants';
import type {
- DatabaseModuleAsyncOptions,
- DatabaseModuleOptions,
- DatabaseModuleOptionsFactory,
+ DatabaseModuleAsyncOptions,
+ DatabaseModuleOptions,
+ DatabaseModuleOptionsFactory,
} from './interfaces';
@Module({
- providers: [],
+ providers: [],
})
export class DatabaseModule implements OnApplicationShutdown {
- private static logger = new Logger(DatabaseModule.name);
-
- private static pool: Pool;
-
- static register(config: DatabaseModuleOptions): DynamicModule {
- return {
- module: DatabaseModule,
- global: config.global ?? false,
- providers: [this.createOptionsProvider(config), this.createDatabaseProvider()],
- exports: [DATABASE_SERVICE],
- };
- }
-
- static registerAsync(config: DatabaseModuleAsyncOptions): DynamicModule {
- return {
- module: DatabaseModule,
- global: config.global ?? false,
- imports: config.imports ?? [],
- providers: [...this.createAsyncProviders(config), this.createDatabaseProvider()],
- exports: [DATABASE_SERVICE],
- };
- }
-
- private static createOptionsProvider(options: DatabaseModuleOptions): Provider {
- return {
- provide: DATABASE_OPTIONS,
- useValue: options,
- };
- }
-
- private static createDatabaseProvider(): Provider {
- return {
- provide: DATABASE_SERVICE,
- useFactory: async (cfg: ConfigService, opts: DatabaseModuleOptions) => {
- const baseUrl = cfg.get('DATABASE_URL');
-
- const pool = new Pool({
- connectionString: baseUrl,
- max: 20,
- idleTimeoutMillis: 30000,
- });
-
- pool.on('connect', (client) => {
- client.query(`SET search_path TO ${opts.schemaName || 'public'}`);
- });
-
- this.pool = pool;
-
- return drizzle(pool, {
- schema: opts.schema,
- logger: opts.logging
- ? {
- logQuery(query, params) {
- const start = Date.now();
- DatabaseModule.logger.debug(`SQL: ${query}`);
-
- if (params?.length) {
- DatabaseModule.logger.debug(`Params: ${JSON.stringify(params)}`);
- }
-
- const duration = Date.now() - start;
- DatabaseModule.logger.debug(`Execution time: ${duration}ms`);
- },
- }
- : false,
- });
- },
- inject: [ConfigService, DATABASE_OPTIONS],
- };
- }
-
- private static createAsyncProviders(options: DatabaseModuleAsyncOptions): Provider[] {
- if (options.useFactory) {
- return [
- {
- provide: DATABASE_OPTIONS,
- useFactory: options.useFactory,
- inject: options.inject || [],
- },
- ...(options.extraProviders || []),
- ];
+ private static logger = new Logger(DatabaseModule.name);
+
+ private static pool: Pool;
+
+ static register(config: DatabaseModuleOptions): DynamicModule {
+ return {
+ module: DatabaseModule,
+ global: config.global ?? false,
+ providers: [this.createOptionsProvider(config), this.createDatabaseProvider()],
+ exports: [DATABASE_SERVICE],
+ };
}
- const providers: Provider[] = [];
+ static registerAsync(config: DatabaseModuleAsyncOptions): DynamicModule {
+ return {
+ module: DatabaseModule,
+ global: config.global ?? false,
+ imports: config.imports ?? [],
+ providers: [...this.createAsyncProviders(config), this.createDatabaseProvider()],
+ exports: [DATABASE_SERVICE],
+ };
+ }
+
+ private static createOptionsProvider(options: DatabaseModuleOptions): Provider {
+ return {
+ provide: DATABASE_OPTIONS,
+ useValue: options,
+ };
+ }
- const useClass = options.useClass || options.useExisting;
- if (!useClass) {
- throw new Error(
- 'You must provide either useClass, useExisting or useFactory in DatabaseModuleAsyncOptions'
- );
+ private static createDatabaseProvider(): Provider {
+ return {
+ provide: DATABASE_SERVICE,
+ useFactory: async (cfg: ConfigService, opts: DatabaseModuleOptions) => {
+ const baseUrl = cfg.get('DATABASE_URL');
+
+ const pool = new Pool({
+ connectionString: baseUrl,
+ max: 20,
+ idleTimeoutMillis: 30000,
+ });
+
+ pool.on('connect', (client) => {
+ client.query(`SET search_path TO ${opts.schemaName || 'public'}`);
+ });
+
+ this.pool = pool;
+
+ return drizzle(pool, {
+ schema: opts.schema,
+ logger: opts.logging
+ ? {
+ logQuery(query, params) {
+ const start = Date.now();
+ DatabaseModule.logger.debug(`SQL: ${query}`);
+
+ if (params?.length) {
+ DatabaseModule.logger.debug(
+ `Params: ${JSON.stringify(params)}`,
+ );
+ }
+
+ const duration = Date.now() - start;
+ DatabaseModule.logger.debug(`Execution time: ${duration}ms`);
+ },
+ }
+ : false,
+ });
+ },
+ inject: [ConfigService, DATABASE_OPTIONS],
+ };
}
- providers.push(this.createAsyncOptionsProvider(useClass));
+ private static createAsyncProviders(options: DatabaseModuleAsyncOptions): Provider[] {
+ if (options.useFactory) {
+ return [
+ {
+ provide: DATABASE_OPTIONS,
+ useFactory: options.useFactory,
+ inject: options.inject || [],
+ },
+ ...(options.extraProviders || []),
+ ];
+ }
+
+ const providers: Provider[] = [];
+
+ const useClass = options.useClass || options.useExisting;
+ if (!useClass) {
+ throw new Error(
+ 'You must provide either useClass, useExisting or useFactory in DatabaseModuleAsyncOptions',
+ );
+ }
+
+ providers.push(this.createAsyncOptionsProvider(useClass));
+
+ if (options.useClass) {
+ providers.push({ provide: useClass, useClass });
+ }
+
+ if (options.extraProviders) {
+ providers.push(...options.extraProviders);
+ }
- if (options.useClass) {
- providers.push({ provide: useClass, useClass });
+ return providers;
}
- if (options.extraProviders) {
- providers.push(...options.extraProviders);
+ private static createAsyncOptionsProvider(
+ useClass: Type,
+ ): Provider {
+ return {
+ provide: DATABASE_OPTIONS,
+ useFactory: async (optionsFactory: DatabaseModuleOptionsFactory) =>
+ optionsFactory.createDatabaseOptions(),
+ inject: [useClass],
+ };
}
- return providers;
- }
-
- private static createAsyncOptionsProvider(
- useClass: Type
- ): Provider {
- return {
- provide: DATABASE_OPTIONS,
- useFactory: async (optionsFactory: DatabaseModuleOptionsFactory) =>
- optionsFactory.createDatabaseOptions(),
- inject: [useClass],
- };
- }
-
- async onApplicationShutdown(_signal?: string) {
- if (DatabaseModule.pool) {
- DatabaseModule.logger.log('Closing database connections...');
- await DatabaseModule.pool.end();
+ async onApplicationShutdown(_signal?: string) {
+ if (DatabaseModule.pool) {
+ DatabaseModule.logger.log('Closing database connections...');
+ await DatabaseModule.pool.end();
+ }
}
- }
}
diff --git a/libs/database/src/interfaces/database-module.interface.ts b/libs/database/src/interfaces/database-module.interface.ts
index 26acdb1..55e114e 100644
--- a/libs/database/src/interfaces/database-module.interface.ts
+++ b/libs/database/src/interfaces/database-module.interface.ts
@@ -2,25 +2,25 @@ import type { FactoryProvider, ModuleMetadata, Provider, Type } from '@nestjs/co
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
export interface DatabaseModuleOptions {
- schemaName: string;
- schema: Record;
- logging?: boolean;
- global?: boolean;
+ schemaName: string;
+ schema: Record;
+ logging?: boolean;
+ global?: boolean;
}
export interface DatabaseModuleOptionsFactory {
- createDatabaseOptions(): Promise | DatabaseModuleOptions;
+ createDatabaseOptions(): Promise | DatabaseModuleOptions;
}
export interface DatabaseModuleAsyncOptions extends Pick {
- useExisting?: Type;
- useClass?: Type;
- useFactory?: (
- ...args: unknown[]
- ) => Promise> | Omit;
- inject?: FactoryProvider['inject'];
- global?: boolean;
- extraProviders?: Provider[];
+ useExisting?: Type;
+ useClass?: Type;
+ useFactory?: (
+ ...args: unknown[]
+ ) => Promise> | Omit;
+ inject?: FactoryProvider['inject'];
+ global?: boolean;
+ extraProviders?: Provider[];
}
export type DatabaseService> = NodePgDatabase;
diff --git a/package.json b/package.json
index f473ae6..f2c89ff 100644
--- a/package.json
+++ b/package.json
@@ -1,88 +1,64 @@
{
- "name": "task-backend",
- "version": "0.0.1",
- "description": "",
- "author": "",
- "private": true,
- "license": "UNLICENSED",
- "scripts": {
- "build": "nest build",
- "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"libs/**/*.ts\"",
- "start": "nest start",
- "start:dev": "nest start --watch",
- "start:debug": "nest start --debug --watch",
- "start:prod": "node dist/main",
- "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
- "test": "jest",
- "test:watch": "jest --watch",
- "test:cov": "jest --coverage",
- "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
- "test:e2e": "jest --config ./test/jest-e2e.json",
- "db:generate": "drizzle-kit generate",
- "db:migrate": "drizzle-kit migrate",
- "db:studio": "drizzle-kit studio"
- },
- "dependencies": {
- "@nestjs/common": "^10.0.0",
- "@nestjs/config": "^4.0.4",
- "@nestjs/core": "^10.0.0",
- "@nestjs/platform-express": "^10.0.0",
- "drizzle-orm": "^0.45.2",
- "drizzle-zod": "^0.8.3",
- "pg": "^8.20.0",
- "reflect-metadata": "^0.2.0",
- "rxjs": "^7.8.1",
- "zod": "^4.3.6"
- },
- "devDependencies": {
- "@nestjs/cli": "^10.0.0",
- "@nestjs/schematics": "^10.0.0",
- "@nestjs/testing": "^10.0.0",
- "@types/express": "^4.17.17",
- "@types/jest": "^29.5.2",
- "@types/node": "^20.3.1",
- "@types/pg": "^8.20.0",
- "@types/supertest": "^6.0.0",
- "@typescript-eslint/eslint-plugin": "^6.0.0",
- "@typescript-eslint/parser": "^6.0.0",
- "drizzle-kit": "^0.31.10",
- "eslint": "^8.42.0",
- "eslint-config-prettier": "^9.0.0",
- "eslint-plugin-prettier": "^5.0.0",
- "jest": "^29.5.0",
- "prettier": "^3.0.0",
- "source-map-support": "^0.5.21",
- "supertest": "^6.3.3",
- "ts-jest": "^29.1.0",
- "ts-loader": "^9.4.3",
- "ts-node": "^10.9.1",
- "tsconfig-paths": "^4.2.0",
- "typescript": "^5.1.3"
- },
- "jest": {
- "moduleFileExtensions": [
- "js",
- "json",
- "ts"
- ],
- "rootDir": ".",
- "testRegex": ".*\\.spec\\.ts$",
- "transform": {
- "^.+\\.(t|j)s$": "ts-jest"
+ "name": "task-backend",
+ "version": "0.0.1",
+ "description": "",
+ "author": "",
+ "private": true,
+ "license": "UNLICENSED",
+ "scripts": {
+ "build": "nest build",
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"libs/**/*.ts\"",
+ "start": "nest start",
+ "start:dev": "nest start --watch",
+ "start:debug": "nest start --debug --watch",
+ "start:prod": "node dist/main",
+ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "test:cov": "vitest run --coverage",
+ "test:debug": "vitest --inspect-brk --inspect --logHeapUsage --threads=false",
+ "test:e2e": "vitest run --config ./vitest.config.e2e.ts",
+ "db:generate": "drizzle-kit generate",
+ "db:migrate": "drizzle-kit migrate",
+ "db:studio": "drizzle-kit studio"
},
- "collectCoverageFrom": [
- "**/*.(t|j)s"
- ],
- "coverageDirectory": "./coverage",
- "testEnvironment": "node",
- "roots": [
- "/src/",
- "/libs/"
- ],
- "moduleNameMapper": {
- "^@libs/config(|/.*)$": "/libs/config/src/$1",
- "^@libs/database(|/.*)$": "/libs/database/src/$1"
- }
- },
- "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
+ "dependencies": {
+ "@nestjs/common": "^10.0.0",
+ "@nestjs/config": "^4.0.4",
+ "@nestjs/core": "^10.0.0",
+ "@nestjs/platform-express": "^10.0.0",
+ "drizzle-orm": "^0.45.2",
+ "drizzle-zod": "^0.8.3",
+ "pg": "^8.20.0",
+ "reflect-metadata": "^0.2.0",
+ "rxjs": "^7.8.1",
+ "zod": "^4.3.6"
+ },
+ "devDependencies": {
+ "@nestjs/cli": "^10.0.0",
+ "@nestjs/schematics": "^10.0.0",
+ "@nestjs/testing": "^10.0.0",
+ "@types/express": "^4.17.17",
+ "@types/node": "^20.3.1",
+ "@types/pg": "^8.20.0",
+ "@types/supertest": "^6.0.0",
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
+ "@typescript-eslint/parser": "^6.0.0",
+ "@vitest/coverage-v8": "^4.1.4",
+ "drizzle-kit": "^0.31.10",
+ "eslint": "^8.42.0",
+ "eslint-config-prettier": "^9.0.0",
+ "eslint-plugin-prettier": "^5.0.0",
+ "lint-staged": "^16.4.0",
+ "prettier": "^3.0.0",
+ "source-map-support": "^0.5.21",
+ "supertest": "^6.3.3",
+ "ts-loader": "^9.4.3",
+ "ts-node": "^10.9.1",
+ "tsconfig-paths": "^4.2.0",
+ "typescript": "^5.1.3",
+ "unplugin-swc": "^1.5.9",
+ "vitest": "^4.1.4"
+ },
+ "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 13d9673..21fa10b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -41,7 +41,7 @@ importers:
devDependencies:
'@nestjs/cli':
specifier: ^10.0.0
- version: 10.4.9
+ version: 10.4.9(@swc/core@1.15.24)(esbuild@0.27.7)
'@nestjs/schematics':
specifier: ^10.0.0
version: 10.2.3(chokidar@3.6.0)(typescript@5.9.3)
@@ -51,9 +51,6 @@ importers:
'@types/express':
specifier: ^4.17.17
version: 4.17.25
- '@types/jest':
- specifier: ^29.5.2
- version: 29.5.14
'@types/node':
specifier: ^20.3.1
version: 20.19.39
@@ -69,6 +66,9 @@ importers:
'@typescript-eslint/parser':
specifier: ^6.0.0
version: 6.21.0(eslint@8.57.1)(typescript@5.9.3)
+ '@vitest/coverage-v8':
+ specifier: ^4.1.4
+ version: 4.1.4(vitest@4.1.4)
drizzle-kit:
specifier: ^0.31.10
version: 0.31.10
@@ -81,12 +81,6 @@ importers:
eslint-plugin-prettier:
specifier: ^5.0.0
version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.8.2)
- husky:
- specifier: ^9.1.7
- version: 9.1.7
- jest:
- specifier: ^29.5.0
- version: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))
lint-staged:
specifier: ^16.4.0
version: 16.4.0
@@ -99,21 +93,24 @@ importers:
supertest:
specifier: ^6.3.3
version: 6.3.4
- ts-jest:
- specifier: ^29.1.0
- version: 29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))(typescript@5.9.3)
ts-loader:
specifier: ^9.4.3
- version: 9.5.7(typescript@5.9.3)(webpack@5.97.1)
+ version: 9.5.7(typescript@5.9.3)(webpack@5.97.1(@swc/core@1.15.24)(esbuild@0.27.7))
ts-node:
specifier: ^10.9.1
- version: 10.9.2(@types/node@20.19.39)(typescript@5.9.3)
+ version: 10.9.2(@swc/core@1.15.24)(@types/node@20.19.39)(typescript@5.9.3)
tsconfig-paths:
specifier: ^4.2.0
version: 4.2.0
typescript:
specifier: ^5.1.3
version: 5.9.3
+ unplugin-swc:
+ specifier: ^1.5.9
+ version: 1.5.9(@swc/core@1.15.24)
+ vitest:
+ specifier: ^4.1.4
+ version: 4.1.4(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
packages:
@@ -139,40 +136,6 @@ packages:
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
engines: {node: '>=6.9.0'}
- '@babel/compat-data@7.29.0':
- resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==}
- engines: {node: '>=6.9.0'}
-
- '@babel/core@7.29.0':
- resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==}
- engines: {node: '>=6.9.0'}
-
- '@babel/generator@7.29.1':
- resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-compilation-targets@7.28.6':
- resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-globals@7.28.0':
- resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-module-imports@7.28.6':
- resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-module-transforms@7.28.6':
- resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0
-
- '@babel/helper-plugin-utils@7.28.6':
- resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==}
- engines: {node: '>=6.9.0'}
-
'@babel/helper-string-parser@7.27.1':
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
@@ -181,124 +144,18 @@ packages:
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
- '@babel/helper-validator-option@7.27.1':
- resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helpers@7.29.2':
- resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==}
- engines: {node: '>=6.9.0'}
-
'@babel/parser@7.29.2':
resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==}
engines: {node: '>=6.0.0'}
hasBin: true
- '@babel/plugin-syntax-async-generators@7.8.4':
- resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-bigint@7.8.3':
- resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-class-properties@7.12.13':
- resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-class-static-block@7.14.5':
- resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-import-attributes@7.28.6':
- resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-import-meta@7.10.4':
- resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-json-strings@7.8.3':
- resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-jsx@7.28.6':
- resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-logical-assignment-operators@7.10.4':
- resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3':
- resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-numeric-separator@7.10.4':
- resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-object-rest-spread@7.8.3':
- resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-optional-catch-binding@7.8.3':
- resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-optional-chaining@7.8.3':
- resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-private-property-in-object@7.14.5':
- resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-top-level-await@7.14.5':
- resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-typescript@7.28.6':
- resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/template@7.28.6':
- resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
- engines: {node: '>=6.9.0'}
-
- '@babel/traverse@7.29.0':
- resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==}
- engines: {node: '>=6.9.0'}
-
'@babel/types@7.29.0':
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
engines: {node: '>=6.9.0'}
- '@bcoe/v8-coverage@0.2.3':
- resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
+ '@bcoe/v8-coverage@1.0.2':
+ resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
+ engines: {node: '>=18'}
'@borewit/text-codec@0.2.2':
resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==}
@@ -314,6 +171,15 @@ packages:
'@drizzle-team/brocli@0.10.2':
resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
+ '@emnapi/core@1.9.2':
+ resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==}
+
+ '@emnapi/runtime@1.9.2':
+ resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==}
+
+ '@emnapi/wasi-threads@1.2.1':
+ resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
+
'@esbuild-kit/core-utils@3.3.2':
resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
deprecated: 'Merged into tsx: https://tsx.is'
@@ -801,80 +667,6 @@ packages:
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
- '@istanbuljs/load-nyc-config@1.1.0':
- resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
- engines: {node: '>=8'}
-
- '@istanbuljs/schema@0.1.3':
- resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
- engines: {node: '>=8'}
-
- '@jest/console@29.7.0':
- resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- '@jest/core@29.7.0':
- resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- peerDependencies:
- node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
- peerDependenciesMeta:
- node-notifier:
- optional: true
-
- '@jest/environment@29.7.0':
- resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- '@jest/expect-utils@29.7.0':
- resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- '@jest/expect@29.7.0':
- resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- '@jest/fake-timers@29.7.0':
- resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- '@jest/globals@29.7.0':
- resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- '@jest/reporters@29.7.0':
- resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- peerDependencies:
- node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
- peerDependenciesMeta:
- node-notifier:
- optional: true
-
- '@jest/schemas@29.6.3':
- resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- '@jest/source-map@29.6.3':
- resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- '@jest/test-result@29.7.0':
- resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- '@jest/test-sequencer@29.7.0':
- resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- '@jest/transform@29.7.0':
- resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- '@jest/types@29.6.3':
- resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
@@ -905,6 +697,12 @@ packages:
resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==}
engines: {node: '>=8'}
+ '@napi-rs/wasm-runtime@1.1.3':
+ resolution: {integrity: sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==}
+ peerDependencies:
+ '@emnapi/core': ^1.7.1
+ '@emnapi/runtime': ^1.7.1
+
'@nestjs/cli@10.4.9':
resolution: {integrity: sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==}
engines: {node: '>= 16.14'}
@@ -999,6 +797,9 @@ packages:
engines: {node: '>=8.0.0', npm: '>=5.0.0'}
hasBin: true
+ '@oxc-project/types@0.124.0':
+ resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==}
+
'@paralleldrive/cuid2@2.3.1':
resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==}
@@ -1010,14 +811,208 @@ packages:
resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
- '@sinclair/typebox@0.27.10':
- resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==}
+ '@rolldown/binding-android-arm64@1.0.0-rc.15':
+ resolution: {integrity: sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [android]
+
+ '@rolldown/binding-darwin-arm64@1.0.0-rc.15':
+ resolution: {integrity: sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rolldown/binding-darwin-x64@1.0.0-rc.15':
+ resolution: {integrity: sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rolldown/binding-freebsd-x64@1.0.0-rc.15':
+ resolution: {integrity: sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15':
+ resolution: {integrity: sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15':
+ resolution: {integrity: sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15':
+ resolution: {integrity: sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15':
+ resolution: {integrity: sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15':
+ resolution: {integrity: sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15':
+ resolution: {integrity: sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rolldown/binding-linux-x64-musl@1.0.0-rc.15':
+ resolution: {integrity: sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@rolldown/binding-openharmony-arm64@1.0.0-rc.15':
+ resolution: {integrity: sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rolldown/binding-wasm32-wasi@1.0.0-rc.15':
+ resolution: {integrity: sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
+ '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15':
+ resolution: {integrity: sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15':
+ resolution: {integrity: sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@rolldown/pluginutils@1.0.0-rc.15':
+ resolution: {integrity: sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==}
+
+ '@rollup/pluginutils@5.3.0':
+ resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
+ '@standard-schema/spec@1.1.0':
+ resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+
+ '@swc/core-darwin-arm64@1.15.24':
+ resolution: {integrity: sha512-uM5ZGfFXjtvtJ+fe448PVBEbn/CSxS3UAyLj3O9xOqKIWy3S6hPTXSPbszxkSsGDYKi+YFhzAsR4r/eXLxEQ0g==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@swc/core-darwin-x64@1.15.24':
+ resolution: {integrity: sha512-fMIb/Zfn929pw25VMBhV7Ji2Dl+lCWtUPNdYJQYOke+00E5fcQ9ynxtP8+qhUo/HZc+mYQb1gJxwHM9vty+lXg==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@swc/core-linux-arm-gnueabihf@1.15.24':
+ resolution: {integrity: sha512-vOkjsyjjxnoYx3hMEWcGxQrMgnNrRm6WAegBXrN8foHtDAR+zpdhpGF5a4lj1bNPgXAvmysjui8cM1ov/Clkaw==}
+ engines: {node: '>=10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@swc/core-linux-arm64-gnu@1.15.24':
+ resolution: {integrity: sha512-h/oNu+upkXJ6Cicnq7YGVj9PkdfarLCdQa8l/FlHYvfv8CEiMaeeTnpLU7gSBH/rGxosM6Qkfa/J9mThGF9CLA==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@swc/core-linux-arm64-musl@1.15.24':
+ resolution: {integrity: sha512-ZpF/pRe1guk6sKzQI9D1jAORtjTdNlyeXn9GDz8ophof/w2WhojRblvSDJaGe7rJjcPN8AaOkhwdRUh7q8oYIg==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@swc/core-linux-ppc64-gnu@1.15.24':
+ resolution: {integrity: sha512-QZEsZfisHTSJlmyChgDFNmKPb3W6Lhbfo/O76HhIngfEdnQNmukS38/VSe1feho+xkV5A5hETyCbx3sALBZKAQ==}
+ engines: {node: '>=10'}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@swc/core-linux-s390x-gnu@1.15.24':
+ resolution: {integrity: sha512-DLdJKVsJgglqQrJBuoUYNmzm3leI7kUZhLbZGHv42onfKsGf6JDS3+bzCUQfte/XOqDjh/tmmn1DR/CF/tCJFw==}
+ engines: {node: '>=10'}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@swc/core-linux-x64-gnu@1.15.24':
+ resolution: {integrity: sha512-IpLYfposPA/XLxYOKpRfeccl1p5dDa3+okZDHHTchBkXEaVCnq5MADPmIWwIYj1tudt7hORsEHccG5no6IUQRw==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@swc/core-linux-x64-musl@1.15.24':
+ resolution: {integrity: sha512-JHy3fMSc0t/EPWgo74+OK5TGr51aElnzqfUPaiRf2qJ/BfX5CUCfMiWVBuhI7qmVMBnk1jTRnL/xZnOSHDPLYg==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@swc/core-win32-arm64-msvc@1.15.24':
+ resolution: {integrity: sha512-Txj+qUH1z2bUd1P3JvwByfjKFti3cptlAxhWgmunBUUxy/IW3CXLZ6l6Gk4liANadKkU71nIU1X30Z5vpMT3BA==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@swc/core-win32-ia32-msvc@1.15.24':
+ resolution: {integrity: sha512-15D/nl3XwrhFpMv+MADFOiVwv3FvH9j8c6Rf8EXBT3Q5LoMh8YnDnSgPYqw1JzPnksvsBX6QPXLiPqmcR/Z4qQ==}
+ engines: {node: '>=10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@swc/core-win32-x64-msvc@1.15.24':
+ resolution: {integrity: sha512-PR0PlTlPra2JbaDphrOAzm6s0v9rA0F17YzB+XbWD95B4g2cWcZY9LAeTa4xll70VLw9Jr7xBrlohqlQmelMFQ==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@swc/core@1.15.24':
+ resolution: {integrity: sha512-5Hj8aNasue7yusUt8LGCUe/AjM7RMAce8ZoyDyiFwx7Al+GbYKL+yE7g4sJk8vEr1dKIkTRARkNIJENc4CjkBQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@swc/helpers': '>=0.5.17'
+ peerDependenciesMeta:
+ '@swc/helpers':
+ optional: true
- '@sinonjs/commons@3.0.1':
- resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==}
+ '@swc/counter@0.1.3':
+ resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
- '@sinonjs/fake-timers@10.3.0':
- resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==}
+ '@swc/types@0.1.26':
+ resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==}
'@tokenizer/inflate@0.2.7':
resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==}
@@ -1038,27 +1033,24 @@ packages:
'@tsconfig/node16@1.0.4':
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
- '@types/babel__core@7.20.5':
- resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
-
- '@types/babel__generator@7.27.0':
- resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
-
- '@types/babel__template@7.4.4':
- resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
-
- '@types/babel__traverse@7.28.0':
- resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+ '@tybys/wasm-util@0.10.1':
+ resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
'@types/body-parser@1.19.6':
resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}
+ '@types/chai@5.2.3':
+ resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
'@types/cookiejar@2.1.5':
resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==}
+ '@types/deep-eql@4.0.2':
+ resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+
'@types/eslint-scope@3.7.7':
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
@@ -1074,24 +1066,9 @@ packages:
'@types/express@4.17.25':
resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==}
- '@types/graceful-fs@4.1.9':
- resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
-
'@types/http-errors@2.0.5':
resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
- '@types/istanbul-lib-coverage@2.0.6':
- resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
-
- '@types/istanbul-lib-report@3.0.3':
- resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==}
-
- '@types/istanbul-reports@3.0.4':
- resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==}
-
- '@types/jest@29.5.14':
- resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==}
-
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@@ -1125,21 +1102,12 @@ packages:
'@types/serve-static@1.15.10':
resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==}
- '@types/stack-utils@2.0.3':
- resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
-
'@types/superagent@8.1.9':
resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==}
'@types/supertest@6.0.3':
resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==}
- '@types/yargs-parser@21.0.3':
- resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
-
- '@types/yargs@17.0.35':
- resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==}
-
'@typescript-eslint/eslint-plugin@6.21.0':
resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -1201,6 +1169,44 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+ '@vitest/coverage-v8@4.1.4':
+ resolution: {integrity: sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==}
+ peerDependencies:
+ '@vitest/browser': 4.1.4
+ vitest: 4.1.4
+ peerDependenciesMeta:
+ '@vitest/browser':
+ optional: true
+
+ '@vitest/expect@4.1.4':
+ resolution: {integrity: sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==}
+
+ '@vitest/mocker@4.1.4':
+ resolution: {integrity: sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^6.0.0 || ^7.0.0 || ^8.0.0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+
+ '@vitest/pretty-format@4.1.4':
+ resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==}
+
+ '@vitest/runner@4.1.4':
+ resolution: {integrity: sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==}
+
+ '@vitest/snapshot@4.1.4':
+ resolution: {integrity: sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==}
+
+ '@vitest/spy@4.1.4':
+ resolution: {integrity: sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==}
+
+ '@vitest/utils@4.1.4':
+ resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==}
+
'@webassemblyjs/ast@1.14.1':
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
@@ -1321,10 +1327,6 @@ packages:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
- ansi-styles@5.2.0:
- resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
- engines: {node: '>=10'}
-
ansi-styles@6.2.3:
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
engines: {node: '>=12'}
@@ -1339,9 +1341,6 @@ packages:
arg@4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
- argparse@1.0.10:
- resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
-
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@@ -1358,33 +1357,15 @@ packages:
asap@2.0.6:
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
- asynckit@0.4.0:
- resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
-
- babel-jest@29.7.0:
- resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- peerDependencies:
- '@babel/core': ^7.8.0
-
- babel-plugin-istanbul@6.1.1:
- resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==}
- engines: {node: '>=8'}
-
- babel-plugin-jest-hoist@29.6.3:
- resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ assertion-error@2.0.1:
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
- babel-preset-current-node-syntax@1.2.0:
- resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==}
- peerDependencies:
- '@babel/core': ^7.0.0 || ^8.0.0-0
+ ast-v8-to-istanbul@1.0.0:
+ resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==}
- babel-preset-jest@29.6.3:
- resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- peerDependencies:
- '@babel/core': ^7.0.0
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -1423,13 +1404,6 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
- bs-logger@0.2.6:
- resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==}
- engines: {node: '>= 6'}
-
- bser@2.1.1:
- resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
-
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@@ -1460,17 +1434,13 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
- camelcase@5.3.1:
- resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
- engines: {node: '>=6'}
-
- camelcase@6.3.0:
- resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
- engines: {node: '>=10'}
-
caniuse-lite@1.0.30001787:
resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==}
+ chai@6.2.2:
+ resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
+ engines: {node: '>=18'}
+
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@@ -1479,10 +1449,6 @@ packages:
resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
- char-regex@1.0.2:
- resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
- engines: {node: '>=10'}
-
chardet@0.7.0:
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
@@ -1494,13 +1460,6 @@ packages:
resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==}
engines: {node: '>=6.0'}
- ci-info@3.9.0:
- resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
- engines: {node: '>=8'}
-
- cjs-module-lexer@1.4.3:
- resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==}
-
cli-cursor@3.1.0:
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
engines: {node: '>=8'}
@@ -1529,21 +1488,10 @@ packages:
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
engines: {node: '>= 12'}
- cliui@8.0.1:
- resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
- engines: {node: '>=12'}
-
clone@1.0.4:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
- co@4.6.0:
- resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
- engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
-
- collect-v8-coverage@1.0.3:
- resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==}
-
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -1623,11 +1571,6 @@ packages:
typescript:
optional: true
- create-jest@29.7.0:
- resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- hasBin: true
-
create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
@@ -1652,14 +1595,6 @@ packages:
supports-color:
optional: true
- dedent@1.7.2:
- resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==}
- peerDependencies:
- babel-plugin-macros: ^3.1.0
- peerDependenciesMeta:
- babel-plugin-macros:
- optional: true
-
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@@ -1686,17 +1621,13 @@ packages:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
- detect-newline@3.1.0:
- resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
dezalgo@1.0.4:
resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==}
- diff-sequences@29.6.3:
- resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
diff@4.0.4:
resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==}
engines: {node: '>=0.3.1'}
@@ -1836,10 +1767,6 @@ packages:
electron-to-chromium@1.5.334:
resolution: {integrity: sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==}
- emittery@0.13.1:
- resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
- engines: {node: '>=12'}
-
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
@@ -1875,6 +1802,9 @@ packages:
es-module-lexer@1.7.0:
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
+ es-module-lexer@2.0.0:
+ resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==}
+
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
@@ -1909,10 +1839,6 @@ packages:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
- escape-string-regexp@2.0.0:
- resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
- engines: {node: '>=8'}
-
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
@@ -1980,6 +1906,12 @@ packages:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
+ estree-walker@2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@@ -1995,17 +1927,9 @@ packages:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
- execa@5.1.1:
- resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
- engines: {node: '>=10'}
-
- exit@0.1.2:
- resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
- engines: {node: '>= 0.8.0'}
-
- expect@29.7.0:
- resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ expect-type@1.3.0:
+ resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
+ engines: {node: '>=12.0.0'}
express@4.22.1:
resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==}
@@ -2040,8 +1964,14 @@ packages:
fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
- fb-watchman@2.0.2:
- resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
@@ -2066,10 +1996,6 @@ packages:
resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==}
engines: {node: '>= 0.8'}
- find-up@4.1.0:
- resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
- engines: {node: '>=8'}
-
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
@@ -2125,14 +2051,6 @@ packages:
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
- gensync@1.0.0-beta.2:
- resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
- engines: {node: '>=6.9.0'}
-
- get-caller-file@2.0.5:
- resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
- engines: {node: 6.* || 8.* || >= 10.*}
-
get-east-asian-width@1.5.0:
resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
engines: {node: '>=18'}
@@ -2141,18 +2059,10 @@ packages:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
- get-package-type@0.1.0:
- resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
- engines: {node: '>=8.0.0'}
-
get-proto@1.0.1:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'}
- get-stream@6.0.1:
- resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
- engines: {node: '>=10'}
-
get-tsconfig@4.13.7:
resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==}
@@ -2194,11 +2104,6 @@ packages:
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
- handlebars@4.7.9:
- resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==}
- engines: {node: '>=0.4.7'}
- hasBin: true
-
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@@ -2229,15 +2134,6 @@ packages:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
- human-signals@2.1.0:
- resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
- engines: {node: '>=10.17.0'}
-
- husky@9.1.7:
- resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
- engines: {node: '>=18'}
- hasBin: true
-
iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
@@ -2253,11 +2149,6 @@ packages:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
- import-local@3.2.0:
- resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==}
- engines: {node: '>=8'}
- hasBin: true
-
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
@@ -2288,10 +2179,6 @@ packages:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
- is-core-module@2.16.1:
- resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
- engines: {node: '>= 0.4'}
-
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@@ -2304,10 +2191,6 @@ packages:
resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==}
engines: {node: '>=18'}
- is-generator-fn@2.1.0:
- resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==}
- engines: {node: '>=6'}
-
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
@@ -2324,10 +2207,6 @@ packages:
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
engines: {node: '>=8'}
- is-stream@2.0.1:
- resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
- engines: {node: '>=8'}
-
is-unicode-supported@0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
@@ -2339,22 +2218,10 @@ packages:
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
engines: {node: '>=8'}
- istanbul-lib-instrument@5.2.1:
- resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==}
- engines: {node: '>=8'}
-
- istanbul-lib-instrument@6.0.3:
- resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==}
- engines: {node: '>=10'}
-
istanbul-lib-report@3.0.1:
resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
engines: {node: '>=10'}
- istanbul-lib-source-maps@4.0.1:
- resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
- engines: {node: '>=10'}
-
istanbul-reports@3.2.0:
resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
engines: {node: '>=8'}
@@ -2366,155 +2233,20 @@ packages:
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
- jest-changed-files@29.7.0:
- resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-circus@29.7.0:
- resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-cli@29.7.0:
- resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- hasBin: true
- peerDependencies:
- node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
- peerDependenciesMeta:
- node-notifier:
- optional: true
-
- jest-config@29.7.0:
- resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- peerDependencies:
- '@types/node': '*'
- ts-node: '>=9.0.0'
- peerDependenciesMeta:
- '@types/node':
- optional: true
- ts-node:
- optional: true
-
- jest-diff@29.7.0:
- resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-docblock@29.7.0:
- resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-each@29.7.0:
- resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-environment-node@29.7.0:
- resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-get-type@29.6.3:
- resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-haste-map@29.7.0:
- resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-leak-detector@29.7.0:
- resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-matcher-utils@29.7.0:
- resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-message-util@29.7.0:
- resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-mock@29.7.0:
- resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-pnp-resolver@1.2.3:
- resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==}
- engines: {node: '>=6'}
- peerDependencies:
- jest-resolve: '*'
- peerDependenciesMeta:
- jest-resolve:
- optional: true
-
- jest-regex-util@29.6.3:
- resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-resolve-dependencies@29.7.0:
- resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-resolve@29.7.0:
- resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-runner@29.7.0:
- resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-runtime@29.7.0:
- resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-snapshot@29.7.0:
- resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-util@29.7.0:
- resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-validate@29.7.0:
- resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-watcher@29.7.0:
- resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
jest-worker@27.5.1:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
engines: {node: '>= 10.13.0'}
- jest-worker@29.7.0:
- resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest@29.7.0:
- resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- hasBin: true
- peerDependencies:
- node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
- peerDependenciesMeta:
- node-notifier:
- optional: true
+ js-tokens@10.0.0:
+ resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
- js-yaml@3.14.2:
- resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
- hasBin: true
-
js-yaml@4.1.1:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
- jsesc@3.1.0:
- resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
- engines: {node: '>=6'}
- hasBin: true
-
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
@@ -2547,18 +2279,84 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
- kleur@3.0.3:
- resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
- engines: {node: '>=6'}
-
- leven@3.1.0:
- resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
- engines: {node: '>=6'}
-
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
+ lightningcss-android-arm64@1.32.0:
+ resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.32.0:
+ resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.32.0:
+ resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.32.0:
+ resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.32.0:
+ resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.32.0:
+ resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ lightningcss-linux-arm64-musl@1.32.0:
+ resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ lightningcss-linux-x64-gnu@1.32.0:
+ resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ lightningcss-linux-x64-musl@1.32.0:
+ resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ lightningcss-win32-arm64-msvc@1.32.0:
+ resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.32.0:
+ resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.32.0:
+ resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
+ engines: {node: '>= 12.0.0'}
+
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
@@ -2571,21 +2369,18 @@ packages:
resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==}
engines: {node: '>=20.0.0'}
+ load-tsconfig@0.2.5:
+ resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
loader-runner@4.3.1:
resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==}
engines: {node: '>=6.11.5'}
- locate-path@5.0.0:
- resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
- engines: {node: '>=8'}
-
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
- lodash.memoize@4.1.2:
- resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
-
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
@@ -2603,13 +2398,16 @@ packages:
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
- lru-cache@5.1.1:
- resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
magic-string@0.30.8:
resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==}
engines: {node: '>=12'}
+ magicast@0.5.2:
+ resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==}
+
make-dir@4.0.0:
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
engines: {node: '>=10'}
@@ -2617,9 +2415,6 @@ packages:
make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
- makeerror@1.0.12:
- resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
-
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@@ -2715,6 +2510,11 @@ packages:
resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
@@ -2740,9 +2540,6 @@ packages:
encoding:
optional: true
- node-int64@0.4.0:
- resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
-
node-releases@2.0.37:
resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==}
@@ -2750,10 +2547,6 @@ packages:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
- npm-run-path@4.0.1:
- resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
- engines: {node: '>=8'}
-
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@@ -2762,6 +2555,9 @@ packages:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'}
+ obug@2.1.1:
+ resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
+
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
@@ -2789,26 +2585,14 @@ packages:
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
engines: {node: '>=0.10.0'}
- p-limit@2.3.0:
- resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
- engines: {node: '>=6'}
-
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
- p-locate@4.1.0:
- resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
- engines: {node: '>=8'}
-
p-locate@5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
- p-try@2.2.0:
- resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
- engines: {node: '>=6'}
-
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
@@ -2836,9 +2620,6 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
- path-parse@1.0.7:
- resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
-
path-scurry@1.11.1:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
@@ -2853,6 +2634,9 @@ packages:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
pg-cloudflare@1.3.0:
resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==}
@@ -2902,18 +2686,14 @@ packages:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
- pirates@4.0.7:
- resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
- engines: {node: '>= 6'}
-
- pkg-dir@4.2.0:
- resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
- engines: {node: '>=8'}
-
pluralize@8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
+ postcss@8.5.9:
+ resolution: {integrity: sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==}
+ engines: {node: ^10 || ^12 || >=14}
+
postgres-array@2.0.0:
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
engines: {node: '>=4'}
@@ -2943,14 +2723,6 @@ packages:
engines: {node: '>=14'}
hasBin: true
- pretty-format@29.7.0:
- resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- prompts@2.4.2:
- resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
- engines: {node: '>= 6'}
-
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
@@ -2959,9 +2731,6 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
- pure-rand@6.1.0:
- resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
-
qs@6.14.2:
resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==}
engines: {node: '>=0.6'}
@@ -2981,9 +2750,6 @@ packages:
resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==}
engines: {node: '>= 0.8'}
- react-is@18.3.1:
- resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
-
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
@@ -2999,38 +2765,17 @@ packages:
resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
engines: {node: '>=0.10'}
- require-directory@2.1.1:
- resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
- engines: {node: '>=0.10.0'}
-
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
- resolve-cwd@3.0.0:
- resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==}
- engines: {node: '>=8'}
-
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
- resolve-from@5.0.0:
- resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
- engines: {node: '>=8'}
-
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
- resolve.exports@2.0.3:
- resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==}
- engines: {node: '>=10'}
-
- resolve@1.22.11:
- resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
- engines: {node: '>= 0.4'}
- hasBin: true
-
restore-cursor@3.1.0:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'}
@@ -3051,6 +2796,11 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
+ rolldown@1.0.0-rc.15:
+ resolution: {integrity: sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+
run-async@2.4.1:
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
engines: {node: '>=0.12.0'}
@@ -3082,10 +2832,6 @@ packages:
resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==}
engines: {node: '>= 10.13.0'}
- semver@6.3.1:
- resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
- hasBin: true
-
semver@7.7.4:
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
engines: {node: '>=10'}
@@ -3130,6 +2876,9 @@ packages:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'}
+ siginfo@2.0.0:
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+
signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
@@ -3137,9 +2886,6 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
- sisteransi@1.0.5:
- resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
-
slash@3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'}
@@ -3152,8 +2898,9 @@ packages:
resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
engines: {node: '>=20'}
- source-map-support@0.5.13:
- resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==}
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
source-map-support@0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
@@ -3174,17 +2921,16 @@ packages:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
- sprintf-js@1.0.3:
- resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
-
- stack-utils@2.0.6:
- resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
- engines: {node: '>=10'}
+ stackback@0.0.2:
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
+ std-env@4.0.0:
+ resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==}
+
streamsearch@1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
@@ -3193,10 +2939,6 @@ packages:
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
engines: {node: '>=0.6.19'}
- string-length@4.0.2:
- resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==}
- engines: {node: '>=10'}
-
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -3228,14 +2970,6 @@ packages:
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
engines: {node: '>=4'}
- strip-bom@4.0.0:
- resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==}
- engines: {node: '>=8'}
-
- strip-final-newline@2.0.0:
- resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
- engines: {node: '>=6'}
-
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
@@ -3262,10 +2996,6 @@ packages:
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
engines: {node: '>=10'}
- supports-preserve-symlinks-flag@1.0.0:
- resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
- engines: {node: '>= 0.4'}
-
symbol-observable@4.0.0:
resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==}
engines: {node: '>=0.10'}
@@ -3299,27 +3029,31 @@ packages:
engines: {node: '>=10'}
hasBin: true
- test-exclude@6.0.0:
- resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
- engines: {node: '>=8'}
-
text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
+ tinybench@2.9.0:
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
tinyexec@1.1.1:
resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==}
engines: {node: '>=18'}
+ tinyglobby@0.2.16:
+ resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
+ engines: {node: '>=12.0.0'}
+
+ tinyrainbow@3.1.0:
+ resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
+ engines: {node: '>=14.0.0'}
+
tmp@0.0.33:
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
engines: {node: '>=0.6.0'}
- tmpl@1.0.5:
- resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
-
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@@ -3345,33 +3079,6 @@ packages:
peerDependencies:
typescript: '>=4.2.0'
- ts-jest@29.4.9:
- resolution: {integrity: sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0}
- hasBin: true
- peerDependencies:
- '@babel/core': '>=7.0.0-beta.0 <8'
- '@jest/transform': ^29.0.0 || ^30.0.0
- '@jest/types': ^29.0.0 || ^30.0.0
- babel-jest: ^29.0.0 || ^30.0.0
- esbuild: '*'
- jest: ^29.0.0 || ^30.0.0
- jest-util: ^29.0.0 || ^30.0.0
- typescript: '>=4.3 <7'
- peerDependenciesMeta:
- '@babel/core':
- optional: true
- '@jest/transform':
- optional: true
- '@jest/types':
- optional: true
- babel-jest:
- optional: true
- esbuild:
- optional: true
- jest-util:
- optional: true
-
ts-loader@9.5.7:
resolution: {integrity: sha512-/ZNrKgA3K3PtpMYOC71EeMWIloGw3IYEa5/t1cyz2r5/PyUwTXGzYJvcD3kfUvmhlfpz1rhV8B2O6IVTQ0avsg==}
engines: {node: '>=12.0.0'}
@@ -3413,10 +3120,6 @@ packages:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
- type-detect@4.0.8:
- resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
- engines: {node: '>=4'}
-
type-fest@0.20.2:
resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
engines: {node: '>=10'}
@@ -3425,10 +3128,6 @@ packages:
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
engines: {node: '>=10'}
- type-fest@4.41.0:
- resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
- engines: {node: '>=16'}
-
type-is@1.6.18:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
@@ -3446,11 +3145,6 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
- uglify-js@3.19.3:
- resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==}
- engines: {node: '>=0.8.0'}
- hasBin: true
-
uid@2.0.2:
resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==}
engines: {node: '>=8'}
@@ -3470,6 +3164,15 @@ packages:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
+ unplugin-swc@1.5.9:
+ resolution: {integrity: sha512-RKwK3yf0M+MN17xZfF14bdKqfx0zMXYdtOdxLiE6jHAoidupKq3jGdJYANyIM1X/VmABhh1WpdO+/f4+Ol89+g==}
+ peerDependencies:
+ '@swc/core': ^1.2.108
+
+ unplugin@2.3.11:
+ resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==}
+ engines: {node: '>=18.12.0'}
+
update-browserslist-db@1.2.3:
resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
hasBin: true
@@ -3489,43 +3192,123 @@ packages:
v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
- v8-to-istanbul@9.3.0:
- resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==}
- engines: {node: '>=10.12.0'}
-
vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
- walker@1.0.8:
- resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
-
- watchpack@2.5.1:
- resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==}
- engines: {node: '>=10.13.0'}
-
- wcwidth@1.0.1:
- resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
-
- webidl-conversions@3.0.1:
- resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
-
- webpack-node-externals@3.0.0:
- resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==}
- engines: {node: '>=6'}
-
- webpack-sources@3.3.4:
- resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==}
- engines: {node: '>=10.13.0'}
-
- webpack@5.97.1:
- resolution: {integrity: sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==}
- engines: {node: '>=10.13.0'}
+ vite@8.0.8:
+ resolution: {integrity: sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
- webpack-cli: '*'
+ '@types/node': ^20.19.0 || >=22.12.0
+ '@vitejs/devtools': ^0.1.0
+ esbuild: ^0.27.0 || ^0.28.0
+ jiti: '>=1.21.0'
+ less: ^4.0.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: '>=0.54.8'
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
peerDependenciesMeta:
- webpack-cli:
+ '@types/node':
+ optional: true
+ '@vitejs/devtools':
+ optional: true
+ esbuild:
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ vitest@4.1.4:
+ resolution: {integrity: sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==}
+ engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@opentelemetry/api': ^1.9.0
+ '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
+ '@vitest/browser-playwright': 4.1.4
+ '@vitest/browser-preview': 4.1.4
+ '@vitest/browser-webdriverio': 4.1.4
+ '@vitest/coverage-istanbul': 4.1.4
+ '@vitest/coverage-v8': 4.1.4
+ '@vitest/ui': 4.1.4
+ happy-dom: '*'
+ jsdom: '*'
+ vite: ^6.0.0 || ^7.0.0 || ^8.0.0
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@opentelemetry/api':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser-playwright':
+ optional: true
+ '@vitest/browser-preview':
+ optional: true
+ '@vitest/browser-webdriverio':
+ optional: true
+ '@vitest/coverage-istanbul':
+ optional: true
+ '@vitest/coverage-v8':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+
+ watchpack@2.5.1:
+ resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==}
+ engines: {node: '>=10.13.0'}
+
+ wcwidth@1.0.1:
+ resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
+
+ webidl-conversions@3.0.1:
+ resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+
+ webpack-node-externals@3.0.0:
+ resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==}
+ engines: {node: '>=6'}
+
+ webpack-sources@3.3.4:
+ resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==}
+ engines: {node: '>=10.13.0'}
+
+ webpack-virtual-modules@0.6.2:
+ resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
+
+ webpack@5.97.1:
+ resolution: {integrity: sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==}
+ engines: {node: '>=10.13.0'}
+ hasBin: true
+ peerDependencies:
+ webpack-cli: '*'
+ peerDependenciesMeta:
+ webpack-cli:
optional: true
whatwg-url@5.0.0:
@@ -3536,13 +3319,15 @@ packages:
engines: {node: '>= 8'}
hasBin: true
+ why-is-node-running@2.3.0:
+ resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
+ engines: {node: '>=8'}
+ hasBin: true
+
word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
- wordwrap@1.0.0:
- resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
-
wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
@@ -3562,21 +3347,10 @@ packages:
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
- write-file-atomic@4.0.2:
- resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==}
- engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
-
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
- y18n@5.0.8:
- resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
- engines: {node: '>=10'}
-
- yallist@3.1.1:
- resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
-
yaml@2.8.3:
resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==}
engines: {node: '>= 14.6'}
@@ -3586,10 +3360,6 @@ packages:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
- yargs@17.7.2:
- resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
- engines: {node: '>=12'}
-
yn@3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
@@ -3641,188 +3411,20 @@ snapshots:
js-tokens: 4.0.0
picocolors: 1.1.1
- '@babel/compat-data@7.29.0': {}
-
- '@babel/core@7.29.0':
- dependencies:
- '@babel/code-frame': 7.29.0
- '@babel/generator': 7.29.1
- '@babel/helper-compilation-targets': 7.28.6
- '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
- '@babel/helpers': 7.29.2
- '@babel/parser': 7.29.2
- '@babel/template': 7.28.6
- '@babel/traverse': 7.29.0
- '@babel/types': 7.29.0
- '@jridgewell/remapping': 2.3.5
- convert-source-map: 2.0.0
- debug: 4.4.3
- gensync: 1.0.0-beta.2
- json5: 2.2.3
- semver: 6.3.1
- transitivePeerDependencies:
- - supports-color
-
- '@babel/generator@7.29.1':
- dependencies:
- '@babel/parser': 7.29.2
- '@babel/types': 7.29.0
- '@jridgewell/gen-mapping': 0.3.13
- '@jridgewell/trace-mapping': 0.3.31
- jsesc: 3.1.0
-
- '@babel/helper-compilation-targets@7.28.6':
- dependencies:
- '@babel/compat-data': 7.29.0
- '@babel/helper-validator-option': 7.27.1
- browserslist: 4.28.2
- lru-cache: 5.1.1
- semver: 6.3.1
-
- '@babel/helper-globals@7.28.0': {}
-
- '@babel/helper-module-imports@7.28.6':
- dependencies:
- '@babel/traverse': 7.29.0
- '@babel/types': 7.29.0
- transitivePeerDependencies:
- - supports-color
-
- '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-module-imports': 7.28.6
- '@babel/helper-validator-identifier': 7.28.5
- '@babel/traverse': 7.29.0
- transitivePeerDependencies:
- - supports-color
-
- '@babel/helper-plugin-utils@7.28.6': {}
-
'@babel/helper-string-parser@7.27.1': {}
'@babel/helper-validator-identifier@7.28.5': {}
- '@babel/helper-validator-option@7.27.1': {}
-
- '@babel/helpers@7.29.2':
- dependencies:
- '@babel/template': 7.28.6
- '@babel/types': 7.29.0
-
'@babel/parser@7.29.2':
dependencies:
'@babel/types': 7.29.0
- '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
-
- '@babel/template@7.28.6':
- dependencies:
- '@babel/code-frame': 7.29.0
- '@babel/parser': 7.29.2
- '@babel/types': 7.29.0
-
- '@babel/traverse@7.29.0':
- dependencies:
- '@babel/code-frame': 7.29.0
- '@babel/generator': 7.29.1
- '@babel/helper-globals': 7.28.0
- '@babel/parser': 7.29.2
- '@babel/template': 7.28.6
- '@babel/types': 7.29.0
- debug: 4.4.3
- transitivePeerDependencies:
- - supports-color
-
'@babel/types@7.29.0':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
- '@bcoe/v8-coverage@0.2.3': {}
+ '@bcoe/v8-coverage@1.0.2': {}
'@borewit/text-codec@0.2.2': {}
@@ -3835,6 +3437,22 @@ snapshots:
'@drizzle-team/brocli@0.10.2': {}
+ '@emnapi/core@1.9.2':
+ dependencies:
+ '@emnapi/wasi-threads': 1.2.1
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/runtime@1.9.2':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/wasi-threads@1.2.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
'@esbuild-kit/core-utils@3.3.2':
dependencies:
esbuild: 0.18.20
@@ -4111,178 +3729,6 @@ snapshots:
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
- '@istanbuljs/load-nyc-config@1.1.0':
- dependencies:
- camelcase: 5.3.1
- find-up: 4.1.0
- get-package-type: 0.1.0
- js-yaml: 3.14.2
- resolve-from: 5.0.0
-
- '@istanbuljs/schema@0.1.3': {}
-
- '@jest/console@29.7.0':
- dependencies:
- '@jest/types': 29.6.3
- '@types/node': 20.19.39
- chalk: 4.1.2
- jest-message-util: 29.7.0
- jest-util: 29.7.0
- slash: 3.0.0
-
- '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))':
- dependencies:
- '@jest/console': 29.7.0
- '@jest/reporters': 29.7.0
- '@jest/test-result': 29.7.0
- '@jest/transform': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 20.19.39
- ansi-escapes: 4.3.2
- chalk: 4.1.2
- ci-info: 3.9.0
- exit: 0.1.2
- graceful-fs: 4.2.11
- jest-changed-files: 29.7.0
- jest-config: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))
- jest-haste-map: 29.7.0
- jest-message-util: 29.7.0
- jest-regex-util: 29.6.3
- jest-resolve: 29.7.0
- jest-resolve-dependencies: 29.7.0
- jest-runner: 29.7.0
- jest-runtime: 29.7.0
- jest-snapshot: 29.7.0
- jest-util: 29.7.0
- jest-validate: 29.7.0
- jest-watcher: 29.7.0
- micromatch: 4.0.8
- pretty-format: 29.7.0
- slash: 3.0.0
- strip-ansi: 6.0.1
- transitivePeerDependencies:
- - babel-plugin-macros
- - supports-color
- - ts-node
-
- '@jest/environment@29.7.0':
- dependencies:
- '@jest/fake-timers': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 20.19.39
- jest-mock: 29.7.0
-
- '@jest/expect-utils@29.7.0':
- dependencies:
- jest-get-type: 29.6.3
-
- '@jest/expect@29.7.0':
- dependencies:
- expect: 29.7.0
- jest-snapshot: 29.7.0
- transitivePeerDependencies:
- - supports-color
-
- '@jest/fake-timers@29.7.0':
- dependencies:
- '@jest/types': 29.6.3
- '@sinonjs/fake-timers': 10.3.0
- '@types/node': 20.19.39
- jest-message-util: 29.7.0
- jest-mock: 29.7.0
- jest-util: 29.7.0
-
- '@jest/globals@29.7.0':
- dependencies:
- '@jest/environment': 29.7.0
- '@jest/expect': 29.7.0
- '@jest/types': 29.6.3
- jest-mock: 29.7.0
- transitivePeerDependencies:
- - supports-color
-
- '@jest/reporters@29.7.0':
- dependencies:
- '@bcoe/v8-coverage': 0.2.3
- '@jest/console': 29.7.0
- '@jest/test-result': 29.7.0
- '@jest/transform': 29.7.0
- '@jest/types': 29.6.3
- '@jridgewell/trace-mapping': 0.3.31
- '@types/node': 20.19.39
- chalk: 4.1.2
- collect-v8-coverage: 1.0.3
- exit: 0.1.2
- glob: 7.2.3
- graceful-fs: 4.2.11
- istanbul-lib-coverage: 3.2.2
- istanbul-lib-instrument: 6.0.3
- istanbul-lib-report: 3.0.1
- istanbul-lib-source-maps: 4.0.1
- istanbul-reports: 3.2.0
- jest-message-util: 29.7.0
- jest-util: 29.7.0
- jest-worker: 29.7.0
- slash: 3.0.0
- string-length: 4.0.2
- strip-ansi: 6.0.1
- v8-to-istanbul: 9.3.0
- transitivePeerDependencies:
- - supports-color
-
- '@jest/schemas@29.6.3':
- dependencies:
- '@sinclair/typebox': 0.27.10
-
- '@jest/source-map@29.6.3':
- dependencies:
- '@jridgewell/trace-mapping': 0.3.31
- callsites: 3.1.0
- graceful-fs: 4.2.11
-
- '@jest/test-result@29.7.0':
- dependencies:
- '@jest/console': 29.7.0
- '@jest/types': 29.6.3
- '@types/istanbul-lib-coverage': 2.0.6
- collect-v8-coverage: 1.0.3
-
- '@jest/test-sequencer@29.7.0':
- dependencies:
- '@jest/test-result': 29.7.0
- graceful-fs: 4.2.11
- jest-haste-map: 29.7.0
- slash: 3.0.0
-
- '@jest/transform@29.7.0':
- dependencies:
- '@babel/core': 7.29.0
- '@jest/types': 29.6.3
- '@jridgewell/trace-mapping': 0.3.31
- babel-plugin-istanbul: 6.1.1
- chalk: 4.1.2
- convert-source-map: 2.0.0
- fast-json-stable-stringify: 2.1.0
- graceful-fs: 4.2.11
- jest-haste-map: 29.7.0
- jest-regex-util: 29.6.3
- jest-util: 29.7.0
- micromatch: 4.0.8
- pirates: 4.0.7
- slash: 3.0.0
- write-file-atomic: 4.0.2
- transitivePeerDependencies:
- - supports-color
-
- '@jest/types@29.6.3':
- dependencies:
- '@jest/schemas': 29.6.3
- '@types/istanbul-lib-coverage': 2.0.6
- '@types/istanbul-reports': 3.0.4
- '@types/node': 20.19.39
- '@types/yargs': 17.0.35
- chalk: 4.1.2
-
'@jridgewell/gen-mapping@0.3.13':
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -4318,7 +3764,14 @@ snapshots:
'@lukeed/csprng@1.1.0': {}
- '@nestjs/cli@10.4.9':
+ '@napi-rs/wasm-runtime@1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)':
+ dependencies:
+ '@emnapi/core': 1.9.2
+ '@emnapi/runtime': 1.9.2
+ '@tybys/wasm-util': 0.10.1
+ optional: true
+
+ '@nestjs/cli@10.4.9(@swc/core@1.15.24)(esbuild@0.27.7)':
dependencies:
'@angular-devkit/core': 17.3.11(chokidar@3.6.0)
'@angular-devkit/schematics': 17.3.11(chokidar@3.6.0)
@@ -4328,7 +3781,7 @@ snapshots:
chokidar: 3.6.0
cli-table3: 0.6.5
commander: 4.1.1
- fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.7.2)(webpack@5.97.1)
+ fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.15.24)(esbuild@0.27.7))
glob: 10.4.5
inquirer: 8.2.6
node-emoji: 1.11.0
@@ -4337,8 +3790,10 @@ snapshots:
tsconfig-paths: 4.2.0
tsconfig-paths-webpack-plugin: 4.2.0
typescript: 5.7.2
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.27.7)
webpack-node-externals: 3.0.0
+ optionalDependencies:
+ '@swc/core': 1.15.24
transitivePeerDependencies:
- esbuild
- uglify-js
@@ -4443,24 +3898,135 @@ snapshots:
transitivePeerDependencies:
- encoding
- '@paralleldrive/cuid2@2.3.1':
- dependencies:
- '@noble/hashes': 1.8.0
+ '@oxc-project/types@0.124.0': {}
+
+ '@paralleldrive/cuid2@2.3.1':
+ dependencies:
+ '@noble/hashes': 1.8.0
+
+ '@pkgjs/parseargs@0.11.0':
+ optional: true
+
+ '@pkgr/core@0.2.9': {}
+
+ '@rolldown/binding-android-arm64@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-darwin-arm64@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-darwin-x64@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-freebsd-x64@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-linux-x64-musl@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-openharmony-arm64@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-wasm32-wasi@1.0.0-rc.15':
+ dependencies:
+ '@emnapi/core': 1.9.2
+ '@emnapi/runtime': 1.9.2
+ '@napi-rs/wasm-runtime': 1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+ optional: true
+
+ '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/pluginutils@1.0.0-rc.15': {}
+
+ '@rollup/pluginutils@5.3.0':
+ dependencies:
+ '@types/estree': 1.0.8
+ estree-walker: 2.0.2
+ picomatch: 4.0.4
+
+ '@standard-schema/spec@1.1.0': {}
+
+ '@swc/core-darwin-arm64@1.15.24':
+ optional: true
+
+ '@swc/core-darwin-x64@1.15.24':
+ optional: true
+
+ '@swc/core-linux-arm-gnueabihf@1.15.24':
+ optional: true
+
+ '@swc/core-linux-arm64-gnu@1.15.24':
+ optional: true
+
+ '@swc/core-linux-arm64-musl@1.15.24':
+ optional: true
+
+ '@swc/core-linux-ppc64-gnu@1.15.24':
+ optional: true
+
+ '@swc/core-linux-s390x-gnu@1.15.24':
+ optional: true
+
+ '@swc/core-linux-x64-gnu@1.15.24':
+ optional: true
- '@pkgjs/parseargs@0.11.0':
+ '@swc/core-linux-x64-musl@1.15.24':
optional: true
- '@pkgr/core@0.2.9': {}
+ '@swc/core-win32-arm64-msvc@1.15.24':
+ optional: true
+
+ '@swc/core-win32-ia32-msvc@1.15.24':
+ optional: true
- '@sinclair/typebox@0.27.10': {}
+ '@swc/core-win32-x64-msvc@1.15.24':
+ optional: true
- '@sinonjs/commons@3.0.1':
+ '@swc/core@1.15.24':
dependencies:
- type-detect: 4.0.8
+ '@swc/counter': 0.1.3
+ '@swc/types': 0.1.26
+ optionalDependencies:
+ '@swc/core-darwin-arm64': 1.15.24
+ '@swc/core-darwin-x64': 1.15.24
+ '@swc/core-linux-arm-gnueabihf': 1.15.24
+ '@swc/core-linux-arm64-gnu': 1.15.24
+ '@swc/core-linux-arm64-musl': 1.15.24
+ '@swc/core-linux-ppc64-gnu': 1.15.24
+ '@swc/core-linux-s390x-gnu': 1.15.24
+ '@swc/core-linux-x64-gnu': 1.15.24
+ '@swc/core-linux-x64-musl': 1.15.24
+ '@swc/core-win32-arm64-msvc': 1.15.24
+ '@swc/core-win32-ia32-msvc': 1.15.24
+ '@swc/core-win32-x64-msvc': 1.15.24
+
+ '@swc/counter@0.1.3': {}
- '@sinonjs/fake-timers@10.3.0':
+ '@swc/types@0.1.26':
dependencies:
- '@sinonjs/commons': 3.0.1
+ '@swc/counter': 0.1.3
'@tokenizer/inflate@0.2.7':
dependencies:
@@ -4480,38 +4046,29 @@ snapshots:
'@tsconfig/node16@1.0.4': {}
- '@types/babel__core@7.20.5':
- dependencies:
- '@babel/parser': 7.29.2
- '@babel/types': 7.29.0
- '@types/babel__generator': 7.27.0
- '@types/babel__template': 7.4.4
- '@types/babel__traverse': 7.28.0
-
- '@types/babel__generator@7.27.0':
+ '@tybys/wasm-util@0.10.1':
dependencies:
- '@babel/types': 7.29.0
-
- '@types/babel__template@7.4.4':
- dependencies:
- '@babel/parser': 7.29.2
- '@babel/types': 7.29.0
-
- '@types/babel__traverse@7.28.0':
- dependencies:
- '@babel/types': 7.29.0
+ tslib: 2.8.1
+ optional: true
'@types/body-parser@1.19.6':
dependencies:
'@types/connect': 3.4.38
'@types/node': 20.19.39
+ '@types/chai@5.2.3':
+ dependencies:
+ '@types/deep-eql': 4.0.2
+ assertion-error: 2.0.1
+
'@types/connect@3.4.38':
dependencies:
'@types/node': 20.19.39
'@types/cookiejar@2.1.5': {}
+ '@types/deep-eql@4.0.2': {}
+
'@types/eslint-scope@3.7.7':
dependencies:
'@types/eslint': 9.6.1
@@ -4538,27 +4095,8 @@ snapshots:
'@types/qs': 6.15.0
'@types/serve-static': 1.15.10
- '@types/graceful-fs@4.1.9':
- dependencies:
- '@types/node': 20.19.39
-
'@types/http-errors@2.0.5': {}
- '@types/istanbul-lib-coverage@2.0.6': {}
-
- '@types/istanbul-lib-report@3.0.3':
- dependencies:
- '@types/istanbul-lib-coverage': 2.0.6
-
- '@types/istanbul-reports@3.0.4':
- dependencies:
- '@types/istanbul-lib-report': 3.0.3
-
- '@types/jest@29.5.14':
- dependencies:
- expect: 29.7.0
- pretty-format: 29.7.0
-
'@types/json-schema@7.0.15': {}
'@types/methods@1.1.4': {}
@@ -4596,8 +4134,6 @@ snapshots:
'@types/node': 20.19.39
'@types/send': 0.17.6
- '@types/stack-utils@2.0.3': {}
-
'@types/superagent@8.1.9':
dependencies:
'@types/cookiejar': 2.1.5
@@ -4610,12 +4146,6 @@ snapshots:
'@types/methods': 1.1.4
'@types/superagent': 8.1.9
- '@types/yargs-parser@21.0.3': {}
-
- '@types/yargs@17.0.35':
- dependencies:
- '@types/yargs-parser': 21.0.3
-
'@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
@@ -4704,6 +4234,61 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
+ '@vitest/coverage-v8@4.1.4(vitest@4.1.4)':
+ dependencies:
+ '@bcoe/v8-coverage': 1.0.2
+ '@vitest/utils': 4.1.4
+ ast-v8-to-istanbul: 1.0.0
+ istanbul-lib-coverage: 3.2.2
+ istanbul-lib-report: 3.0.1
+ istanbul-reports: 3.2.0
+ magicast: 0.5.2
+ obug: 2.1.1
+ std-env: 4.0.0
+ tinyrainbow: 3.1.0
+ vitest: 4.1.4(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
+
+ '@vitest/expect@4.1.4':
+ dependencies:
+ '@standard-schema/spec': 1.1.0
+ '@types/chai': 5.2.3
+ '@vitest/spy': 4.1.4
+ '@vitest/utils': 4.1.4
+ chai: 6.2.2
+ tinyrainbow: 3.1.0
+
+ '@vitest/mocker@4.1.4(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))':
+ dependencies:
+ '@vitest/spy': 4.1.4
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ optionalDependencies:
+ vite: 8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
+
+ '@vitest/pretty-format@4.1.4':
+ dependencies:
+ tinyrainbow: 3.1.0
+
+ '@vitest/runner@4.1.4':
+ dependencies:
+ '@vitest/utils': 4.1.4
+ pathe: 2.0.3
+
+ '@vitest/snapshot@4.1.4':
+ dependencies:
+ '@vitest/pretty-format': 4.1.4
+ '@vitest/utils': 4.1.4
+ magic-string: 0.30.21
+ pathe: 2.0.3
+
+ '@vitest/spy@4.1.4': {}
+
+ '@vitest/utils@4.1.4':
+ dependencies:
+ '@vitest/pretty-format': 4.1.4
+ convert-source-map: 2.0.0
+ tinyrainbow: 3.1.0
+
'@webassemblyjs/ast@1.14.1':
dependencies:
'@webassemblyjs/helper-numbers': 1.13.2
@@ -4855,8 +4440,6 @@ snapshots:
dependencies:
color-convert: 2.0.1
- ansi-styles@5.2.0: {}
-
ansi-styles@6.2.3: {}
anymatch@3.1.3:
@@ -4868,10 +4451,6 @@ snapshots:
arg@4.1.3: {}
- argparse@1.0.10:
- dependencies:
- sprintf-js: 1.0.3
-
argparse@2.0.1: {}
array-flatten@1.1.1: {}
@@ -4882,62 +4461,15 @@ snapshots:
asap@2.0.6: {}
- asynckit@0.4.0: {}
-
- babel-jest@29.7.0(@babel/core@7.29.0):
- dependencies:
- '@babel/core': 7.29.0
- '@jest/transform': 29.7.0
- '@types/babel__core': 7.20.5
- babel-plugin-istanbul: 6.1.1
- babel-preset-jest: 29.6.3(@babel/core@7.29.0)
- chalk: 4.1.2
- graceful-fs: 4.2.11
- slash: 3.0.0
- transitivePeerDependencies:
- - supports-color
+ assertion-error@2.0.1: {}
- babel-plugin-istanbul@6.1.1:
+ ast-v8-to-istanbul@1.0.0:
dependencies:
- '@babel/helper-plugin-utils': 7.28.6
- '@istanbuljs/load-nyc-config': 1.1.0
- '@istanbuljs/schema': 0.1.3
- istanbul-lib-instrument: 5.2.1
- test-exclude: 6.0.0
- transitivePeerDependencies:
- - supports-color
+ '@jridgewell/trace-mapping': 0.3.31
+ estree-walker: 3.0.3
+ js-tokens: 10.0.0
- babel-plugin-jest-hoist@29.6.3:
- dependencies:
- '@babel/template': 7.28.6
- '@babel/types': 7.29.0
- '@types/babel__core': 7.20.5
- '@types/babel__traverse': 7.28.0
-
- babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0):
- dependencies:
- '@babel/core': 7.29.0
- '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0)
- '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0)
- '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0)
- '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0)
- '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0)
- '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0)
- '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0)
- '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0)
- '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0)
- '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0)
- '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0)
- '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0)
- '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0)
- '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0)
-
- babel-preset-jest@29.6.3(@babel/core@7.29.0):
- dependencies:
- '@babel/core': 7.29.0
- babel-plugin-jest-hoist: 29.6.3
- babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0)
+ asynckit@0.4.0: {}
balanced-match@1.0.2: {}
@@ -4991,14 +4523,6 @@ snapshots:
node-releases: 2.0.37
update-browserslist-db: 1.2.3(browserslist@4.28.2)
- bs-logger@0.2.6:
- dependencies:
- fast-json-stable-stringify: 2.1.0
-
- bser@2.1.1:
- dependencies:
- node-int64: 0.4.0
-
buffer-from@1.1.2: {}
buffer@5.7.1:
@@ -5031,12 +4555,10 @@ snapshots:
callsites@3.1.0: {}
- camelcase@5.3.1: {}
-
- camelcase@6.3.0: {}
-
caniuse-lite@1.0.30001787: {}
+ chai@6.2.2: {}
+
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
@@ -5044,8 +4566,6 @@ snapshots:
chalk@5.6.2: {}
- char-regex@1.0.2: {}
-
chardet@0.7.0: {}
chokidar@3.6.0:
@@ -5062,10 +4582,6 @@ snapshots:
chrome-trace-event@1.0.4: {}
- ci-info@3.9.0: {}
-
- cjs-module-lexer@1.4.3: {}
-
cli-cursor@3.1.0:
dependencies:
restore-cursor: 3.1.0
@@ -5091,18 +4607,8 @@ snapshots:
cli-width@4.1.0: {}
- cliui@8.0.1:
- dependencies:
- string-width: 4.2.3
- strip-ansi: 6.0.1
- wrap-ansi: 7.0.0
-
clone@1.0.4: {}
- co@4.6.0: {}
-
- collect-v8-coverage@1.0.3: {}
-
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -5172,21 +4678,6 @@ snapshots:
optionalDependencies:
typescript: 5.7.2
- create-jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)):
- dependencies:
- '@jest/types': 29.6.3
- chalk: 4.1.2
- exit: 0.1.2
- graceful-fs: 4.2.11
- jest-config: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))
- jest-util: 29.7.0
- prompts: 2.4.2
- transitivePeerDependencies:
- - '@types/node'
- - babel-plugin-macros
- - supports-color
- - ts-node
-
create-require@1.1.1: {}
cross-spawn@7.0.6:
@@ -5203,8 +4694,6 @@ snapshots:
dependencies:
ms: 2.1.3
- dedent@1.7.2: {}
-
deep-is@0.1.4: {}
deepmerge@4.3.1: {}
@@ -5225,15 +4714,13 @@ snapshots:
destroy@1.2.0: {}
- detect-newline@3.1.0: {}
+ detect-libc@2.1.2: {}
dezalgo@1.0.4:
dependencies:
asap: 2.0.6
wrappy: 1.0.2
- diff-sequences@29.6.3: {}
-
diff@4.0.4: {}
dir-glob@3.0.1:
@@ -5281,8 +4768,6 @@ snapshots:
electron-to-chromium@1.5.334: {}
- emittery@0.13.1: {}
-
emoji-regex@10.6.0: {}
emoji-regex@8.0.0: {}
@@ -5308,6 +4793,8 @@ snapshots:
es-module-lexer@1.7.0: {}
+ es-module-lexer@2.0.0: {}
+
es-object-atoms@1.1.1:
dependencies:
es-errors: 1.3.0
@@ -5408,8 +4895,6 @@ snapshots:
escape-string-regexp@1.0.5: {}
- escape-string-regexp@2.0.0: {}
-
escape-string-regexp@4.0.0: {}
eslint-config-prettier@9.1.2(eslint@8.57.1):
@@ -5501,6 +4986,12 @@ snapshots:
estraverse@5.3.0: {}
+ estree-walker@2.0.2: {}
+
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.8
+
esutils@2.0.3: {}
etag@1.8.1: {}
@@ -5509,27 +5000,7 @@ snapshots:
events@3.3.0: {}
- execa@5.1.1:
- dependencies:
- cross-spawn: 7.0.6
- get-stream: 6.0.1
- human-signals: 2.1.0
- is-stream: 2.0.1
- merge-stream: 2.0.0
- npm-run-path: 4.0.1
- onetime: 5.1.2
- signal-exit: 3.0.7
- strip-final-newline: 2.0.0
-
- exit@0.1.2: {}
-
- expect@29.7.0:
- dependencies:
- '@jest/expect-utils': 29.7.0
- jest-get-type: 29.6.3
- jest-matcher-utils: 29.7.0
- jest-message-util: 29.7.0
- jest-util: 29.7.0
+ expect-type@1.3.0: {}
express@4.22.1:
dependencies:
@@ -5597,9 +5068,9 @@ snapshots:
dependencies:
reusify: 1.1.0
- fb-watchman@2.0.2:
- dependencies:
- bser: 2.1.1
+ fdir@6.5.0(picomatch@4.0.4):
+ optionalDependencies:
+ picomatch: 4.0.4
fflate@0.8.2: {}
@@ -5636,11 +5107,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- find-up@4.1.0:
- dependencies:
- locate-path: 5.0.0
- path-exists: 4.0.0
-
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
@@ -5659,7 +5125,7 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
- fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1):
+ fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.15.24)(esbuild@0.27.7)):
dependencies:
'@babel/code-frame': 7.29.0
chalk: 4.1.2
@@ -5674,7 +5140,7 @@ snapshots:
semver: 7.7.4
tapable: 2.3.2
typescript: 5.7.2
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.27.7)
form-data@4.0.5:
dependencies:
@@ -5710,10 +5176,6 @@ snapshots:
function-bind@1.1.2: {}
- gensync@1.0.0-beta.2: {}
-
- get-caller-file@2.0.5: {}
-
get-east-asian-width@1.5.0: {}
get-intrinsic@1.3.0:
@@ -5729,15 +5191,11 @@ snapshots:
hasown: 2.0.2
math-intrinsics: 1.1.0
- get-package-type@0.1.0: {}
-
get-proto@1.0.1:
dependencies:
dunder-proto: 1.0.1
es-object-atoms: 1.1.1
- get-stream@6.0.1: {}
-
get-tsconfig@4.13.7:
dependencies:
resolve-pkg-maps: 1.0.0
@@ -5789,15 +5247,6 @@ snapshots:
graphemer@1.4.0: {}
- handlebars@4.7.9:
- dependencies:
- minimist: 1.2.8
- neo-async: 2.6.2
- source-map: 0.6.1
- wordwrap: 1.0.0
- optionalDependencies:
- uglify-js: 3.19.3
-
has-flag@4.0.0: {}
has-own-prop@2.0.0: {}
@@ -5826,10 +5275,6 @@ snapshots:
statuses: 2.0.2
toidentifier: 1.0.1
- human-signals@2.1.0: {}
-
- husky@9.1.7: {}
-
iconv-lite@0.4.24:
dependencies:
safer-buffer: 2.1.2
@@ -5843,11 +5288,6 @@ snapshots:
parent-module: 1.0.1
resolve-from: 4.0.0
- import-local@3.2.0:
- dependencies:
- pkg-dir: 4.2.0
- resolve-cwd: 3.0.0
-
imurmurhash@0.1.4: {}
inflight@1.0.6:
@@ -5886,389 +5326,63 @@ snapshots:
figures: 3.2.0
lodash: 4.18.1
mute-stream: 1.0.0
- ora: 5.4.1
- run-async: 3.0.0
- rxjs: 7.8.2
- string-width: 4.2.3
- strip-ansi: 6.0.1
- wrap-ansi: 6.2.0
-
- ipaddr.js@1.9.1: {}
-
- is-arrayish@0.2.1: {}
-
- is-binary-path@2.1.0:
- dependencies:
- binary-extensions: 2.3.0
-
- is-core-module@2.16.1:
- dependencies:
- hasown: 2.0.2
-
- is-extglob@2.1.1: {}
-
- is-fullwidth-code-point@3.0.0: {}
-
- is-fullwidth-code-point@5.1.0:
- dependencies:
- get-east-asian-width: 1.5.0
-
- is-generator-fn@2.1.0: {}
-
- is-glob@4.0.3:
- dependencies:
- is-extglob: 2.1.1
-
- is-interactive@1.0.0: {}
-
- is-number@7.0.0: {}
-
- is-path-inside@3.0.3: {}
-
- is-stream@2.0.1: {}
-
- is-unicode-supported@0.1.0: {}
-
- isexe@2.0.0: {}
-
- istanbul-lib-coverage@3.2.2: {}
-
- istanbul-lib-instrument@5.2.1:
- dependencies:
- '@babel/core': 7.29.0
- '@babel/parser': 7.29.2
- '@istanbuljs/schema': 0.1.3
- istanbul-lib-coverage: 3.2.2
- semver: 6.3.1
- transitivePeerDependencies:
- - supports-color
-
- istanbul-lib-instrument@6.0.3:
- dependencies:
- '@babel/core': 7.29.0
- '@babel/parser': 7.29.2
- '@istanbuljs/schema': 0.1.3
- istanbul-lib-coverage: 3.2.2
- semver: 7.7.4
- transitivePeerDependencies:
- - supports-color
-
- istanbul-lib-report@3.0.1:
- dependencies:
- istanbul-lib-coverage: 3.2.2
- make-dir: 4.0.0
- supports-color: 7.2.0
-
- istanbul-lib-source-maps@4.0.1:
- dependencies:
- debug: 4.4.3
- istanbul-lib-coverage: 3.2.2
- source-map: 0.6.1
- transitivePeerDependencies:
- - supports-color
-
- istanbul-reports@3.2.0:
- dependencies:
- html-escaper: 2.0.2
- istanbul-lib-report: 3.0.1
-
- iterare@1.2.1: {}
-
- jackspeak@3.4.3:
- dependencies:
- '@isaacs/cliui': 8.0.2
- optionalDependencies:
- '@pkgjs/parseargs': 0.11.0
-
- jest-changed-files@29.7.0:
- dependencies:
- execa: 5.1.1
- jest-util: 29.7.0
- p-limit: 3.1.0
-
- jest-circus@29.7.0:
- dependencies:
- '@jest/environment': 29.7.0
- '@jest/expect': 29.7.0
- '@jest/test-result': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 20.19.39
- chalk: 4.1.2
- co: 4.6.0
- dedent: 1.7.2
- is-generator-fn: 2.1.0
- jest-each: 29.7.0
- jest-matcher-utils: 29.7.0
- jest-message-util: 29.7.0
- jest-runtime: 29.7.0
- jest-snapshot: 29.7.0
- jest-util: 29.7.0
- p-limit: 3.1.0
- pretty-format: 29.7.0
- pure-rand: 6.1.0
- slash: 3.0.0
- stack-utils: 2.0.6
- transitivePeerDependencies:
- - babel-plugin-macros
- - supports-color
-
- jest-cli@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)):
- dependencies:
- '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))
- '@jest/test-result': 29.7.0
- '@jest/types': 29.6.3
- chalk: 4.1.2
- create-jest: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))
- exit: 0.1.2
- import-local: 3.2.0
- jest-config: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))
- jest-util: 29.7.0
- jest-validate: 29.7.0
- yargs: 17.7.2
- transitivePeerDependencies:
- - '@types/node'
- - babel-plugin-macros
- - supports-color
- - ts-node
-
- jest-config@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)):
- dependencies:
- '@babel/core': 7.29.0
- '@jest/test-sequencer': 29.7.0
- '@jest/types': 29.6.3
- babel-jest: 29.7.0(@babel/core@7.29.0)
- chalk: 4.1.2
- ci-info: 3.9.0
- deepmerge: 4.3.1
- glob: 7.2.3
- graceful-fs: 4.2.11
- jest-circus: 29.7.0
- jest-environment-node: 29.7.0
- jest-get-type: 29.6.3
- jest-regex-util: 29.6.3
- jest-resolve: 29.7.0
- jest-runner: 29.7.0
- jest-util: 29.7.0
- jest-validate: 29.7.0
- micromatch: 4.0.8
- parse-json: 5.2.0
- pretty-format: 29.7.0
- slash: 3.0.0
- strip-json-comments: 3.1.1
- optionalDependencies:
- '@types/node': 20.19.39
- ts-node: 10.9.2(@types/node@20.19.39)(typescript@5.9.3)
- transitivePeerDependencies:
- - babel-plugin-macros
- - supports-color
-
- jest-diff@29.7.0:
- dependencies:
- chalk: 4.1.2
- diff-sequences: 29.6.3
- jest-get-type: 29.6.3
- pretty-format: 29.7.0
-
- jest-docblock@29.7.0:
- dependencies:
- detect-newline: 3.1.0
-
- jest-each@29.7.0:
- dependencies:
- '@jest/types': 29.6.3
- chalk: 4.1.2
- jest-get-type: 29.6.3
- jest-util: 29.7.0
- pretty-format: 29.7.0
+ ora: 5.4.1
+ run-async: 3.0.0
+ rxjs: 7.8.2
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 6.2.0
- jest-environment-node@29.7.0:
- dependencies:
- '@jest/environment': 29.7.0
- '@jest/fake-timers': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 20.19.39
- jest-mock: 29.7.0
- jest-util: 29.7.0
+ ipaddr.js@1.9.1: {}
- jest-get-type@29.6.3: {}
+ is-arrayish@0.2.1: {}
- jest-haste-map@29.7.0:
+ is-binary-path@2.1.0:
dependencies:
- '@jest/types': 29.6.3
- '@types/graceful-fs': 4.1.9
- '@types/node': 20.19.39
- anymatch: 3.1.3
- fb-watchman: 2.0.2
- graceful-fs: 4.2.11
- jest-regex-util: 29.6.3
- jest-util: 29.7.0
- jest-worker: 29.7.0
- micromatch: 4.0.8
- walker: 1.0.8
- optionalDependencies:
- fsevents: 2.3.3
+ binary-extensions: 2.3.0
- jest-leak-detector@29.7.0:
- dependencies:
- jest-get-type: 29.6.3
- pretty-format: 29.7.0
+ is-extglob@2.1.1: {}
- jest-matcher-utils@29.7.0:
- dependencies:
- chalk: 4.1.2
- jest-diff: 29.7.0
- jest-get-type: 29.6.3
- pretty-format: 29.7.0
+ is-fullwidth-code-point@3.0.0: {}
- jest-message-util@29.7.0:
+ is-fullwidth-code-point@5.1.0:
dependencies:
- '@babel/code-frame': 7.29.0
- '@jest/types': 29.6.3
- '@types/stack-utils': 2.0.3
- chalk: 4.1.2
- graceful-fs: 4.2.11
- micromatch: 4.0.8
- pretty-format: 29.7.0
- slash: 3.0.0
- stack-utils: 2.0.6
+ get-east-asian-width: 1.5.0
- jest-mock@29.7.0:
+ is-glob@4.0.3:
dependencies:
- '@jest/types': 29.6.3
- '@types/node': 20.19.39
- jest-util: 29.7.0
+ is-extglob: 2.1.1
- jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
- optionalDependencies:
- jest-resolve: 29.7.0
+ is-interactive@1.0.0: {}
- jest-regex-util@29.6.3: {}
+ is-number@7.0.0: {}
- jest-resolve-dependencies@29.7.0:
- dependencies:
- jest-regex-util: 29.6.3
- jest-snapshot: 29.7.0
- transitivePeerDependencies:
- - supports-color
+ is-path-inside@3.0.3: {}
- jest-resolve@29.7.0:
- dependencies:
- chalk: 4.1.2
- graceful-fs: 4.2.11
- jest-haste-map: 29.7.0
- jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0)
- jest-util: 29.7.0
- jest-validate: 29.7.0
- resolve: 1.22.11
- resolve.exports: 2.0.3
- slash: 3.0.0
+ is-unicode-supported@0.1.0: {}
- jest-runner@29.7.0:
- dependencies:
- '@jest/console': 29.7.0
- '@jest/environment': 29.7.0
- '@jest/test-result': 29.7.0
- '@jest/transform': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 20.19.39
- chalk: 4.1.2
- emittery: 0.13.1
- graceful-fs: 4.2.11
- jest-docblock: 29.7.0
- jest-environment-node: 29.7.0
- jest-haste-map: 29.7.0
- jest-leak-detector: 29.7.0
- jest-message-util: 29.7.0
- jest-resolve: 29.7.0
- jest-runtime: 29.7.0
- jest-util: 29.7.0
- jest-watcher: 29.7.0
- jest-worker: 29.7.0
- p-limit: 3.1.0
- source-map-support: 0.5.13
- transitivePeerDependencies:
- - supports-color
+ isexe@2.0.0: {}
- jest-runtime@29.7.0:
- dependencies:
- '@jest/environment': 29.7.0
- '@jest/fake-timers': 29.7.0
- '@jest/globals': 29.7.0
- '@jest/source-map': 29.6.3
- '@jest/test-result': 29.7.0
- '@jest/transform': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 20.19.39
- chalk: 4.1.2
- cjs-module-lexer: 1.4.3
- collect-v8-coverage: 1.0.3
- glob: 7.2.3
- graceful-fs: 4.2.11
- jest-haste-map: 29.7.0
- jest-message-util: 29.7.0
- jest-mock: 29.7.0
- jest-regex-util: 29.6.3
- jest-resolve: 29.7.0
- jest-snapshot: 29.7.0
- jest-util: 29.7.0
- slash: 3.0.0
- strip-bom: 4.0.0
- transitivePeerDependencies:
- - supports-color
+ istanbul-lib-coverage@3.2.2: {}
- jest-snapshot@29.7.0:
+ istanbul-lib-report@3.0.1:
dependencies:
- '@babel/core': 7.29.0
- '@babel/generator': 7.29.1
- '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
- '@babel/types': 7.29.0
- '@jest/expect-utils': 29.7.0
- '@jest/transform': 29.7.0
- '@jest/types': 29.6.3
- babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0)
- chalk: 4.1.2
- expect: 29.7.0
- graceful-fs: 4.2.11
- jest-diff: 29.7.0
- jest-get-type: 29.6.3
- jest-matcher-utils: 29.7.0
- jest-message-util: 29.7.0
- jest-util: 29.7.0
- natural-compare: 1.4.0
- pretty-format: 29.7.0
- semver: 7.7.4
- transitivePeerDependencies:
- - supports-color
+ istanbul-lib-coverage: 3.2.2
+ make-dir: 4.0.0
+ supports-color: 7.2.0
- jest-util@29.7.0:
+ istanbul-reports@3.2.0:
dependencies:
- '@jest/types': 29.6.3
- '@types/node': 20.19.39
- chalk: 4.1.2
- ci-info: 3.9.0
- graceful-fs: 4.2.11
- picomatch: 2.3.2
+ html-escaper: 2.0.2
+ istanbul-lib-report: 3.0.1
- jest-validate@29.7.0:
- dependencies:
- '@jest/types': 29.6.3
- camelcase: 6.3.0
- chalk: 4.1.2
- jest-get-type: 29.6.3
- leven: 3.1.0
- pretty-format: 29.7.0
+ iterare@1.2.1: {}
- jest-watcher@29.7.0:
+ jackspeak@3.4.3:
dependencies:
- '@jest/test-result': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 20.19.39
- ansi-escapes: 4.3.2
- chalk: 4.1.2
- emittery: 0.13.1
- jest-util: 29.7.0
- string-length: 4.0.2
+ '@isaacs/cliui': 8.0.2
+ optionalDependencies:
+ '@pkgjs/parseargs': 0.11.0
jest-worker@27.5.1:
dependencies:
@@ -6276,38 +5390,14 @@ snapshots:
merge-stream: 2.0.0
supports-color: 8.1.1
- jest-worker@29.7.0:
- dependencies:
- '@types/node': 20.19.39
- jest-util: 29.7.0
- merge-stream: 2.0.0
- supports-color: 8.1.1
-
- jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)):
- dependencies:
- '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))
- '@jest/types': 29.6.3
- import-local: 3.2.0
- jest-cli: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))
- transitivePeerDependencies:
- - '@types/node'
- - babel-plugin-macros
- - supports-color
- - ts-node
+ js-tokens@10.0.0: {}
js-tokens@4.0.0: {}
- js-yaml@3.14.2:
- dependencies:
- argparse: 1.0.10
- esprima: 4.0.1
-
js-yaml@4.1.1:
dependencies:
argparse: 2.0.1
- jsesc@3.1.0: {}
-
json-buffer@3.0.1: {}
json-parse-even-better-errors@2.3.1: {}
@@ -6334,15 +5424,60 @@ snapshots:
dependencies:
json-buffer: 3.0.1
- kleur@3.0.3: {}
-
- leven@3.1.0: {}
-
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
type-check: 0.4.0
+ lightningcss-android-arm64@1.32.0:
+ optional: true
+
+ lightningcss-darwin-arm64@1.32.0:
+ optional: true
+
+ lightningcss-darwin-x64@1.32.0:
+ optional: true
+
+ lightningcss-freebsd-x64@1.32.0:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.32.0:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.32.0:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.32.0:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.32.0:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.32.0:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.32.0:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.32.0:
+ optional: true
+
+ lightningcss@1.32.0:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.32.0
+ lightningcss-darwin-arm64: 1.32.0
+ lightningcss-darwin-x64: 1.32.0
+ lightningcss-freebsd-x64: 1.32.0
+ lightningcss-linux-arm-gnueabihf: 1.32.0
+ lightningcss-linux-arm64-gnu: 1.32.0
+ lightningcss-linux-arm64-musl: 1.32.0
+ lightningcss-linux-x64-gnu: 1.32.0
+ lightningcss-linux-x64-musl: 1.32.0
+ lightningcss-win32-arm64-msvc: 1.32.0
+ lightningcss-win32-x64-msvc: 1.32.0
+
lines-and-columns@1.2.4: {}
lint-staged@16.4.0:
@@ -6363,18 +5498,14 @@ snapshots:
rfdc: 1.4.1
wrap-ansi: 9.0.2
- loader-runner@4.3.1: {}
+ load-tsconfig@0.2.5: {}
- locate-path@5.0.0:
- dependencies:
- p-locate: 4.1.0
+ loader-runner@4.3.1: {}
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
- lodash.memoize@4.1.2: {}
-
lodash.merge@4.6.2: {}
lodash@4.18.1: {}
@@ -6394,24 +5525,26 @@ snapshots:
lru-cache@10.4.3: {}
- lru-cache@5.1.1:
+ magic-string@0.30.21:
dependencies:
- yallist: 3.1.1
+ '@jridgewell/sourcemap-codec': 1.5.5
magic-string@0.30.8:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
+ magicast@0.5.2:
+ dependencies:
+ '@babel/parser': 7.29.2
+ '@babel/types': 7.29.0
+ source-map-js: 1.2.1
+
make-dir@4.0.0:
dependencies:
semver: 7.7.4
make-error@1.3.6: {}
- makeerror@1.0.12:
- dependencies:
- tmpl: 1.0.5
-
math-intrinsics@1.1.0: {}
media-typer@0.3.0: {}
@@ -6485,6 +5618,8 @@ snapshots:
mute-stream@1.0.0: {}
+ nanoid@3.3.11: {}
+
natural-compare@1.4.0: {}
negotiator@0.6.3: {}
@@ -6501,20 +5636,16 @@ snapshots:
dependencies:
whatwg-url: 5.0.0
- node-int64@0.4.0: {}
-
node-releases@2.0.37: {}
normalize-path@3.0.0: {}
- npm-run-path@4.0.1:
- dependencies:
- path-key: 3.1.1
-
object-assign@4.1.1: {}
object-inspect@1.13.4: {}
+ obug@2.1.1: {}
+
on-finished@2.4.1:
dependencies:
ee-first: 1.1.1
@@ -6554,24 +5685,14 @@ snapshots:
os-tmpdir@1.0.2: {}
- p-limit@2.3.0:
- dependencies:
- p-try: 2.2.0
-
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
- p-locate@4.1.0:
- dependencies:
- p-limit: 2.3.0
-
p-locate@5.0.0:
dependencies:
p-limit: 3.1.0
- p-try@2.2.0: {}
-
package-json-from-dist@1.0.1: {}
parent-module@1.0.1:
@@ -6593,8 +5714,6 @@ snapshots:
path-key@3.1.1: {}
- path-parse@1.0.7: {}
-
path-scurry@1.11.1:
dependencies:
lru-cache: 10.4.3
@@ -6606,6 +5725,8 @@ snapshots:
path-type@4.0.0: {}
+ pathe@2.0.3: {}
+
pg-cloudflare@1.3.0:
optional: true
@@ -6649,13 +5770,13 @@ snapshots:
picomatch@4.0.4: {}
- pirates@4.0.7: {}
+ pluralize@8.0.0: {}
- pkg-dir@4.2.0:
+ postcss@8.5.9:
dependencies:
- find-up: 4.1.0
-
- pluralize@8.0.0: {}
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
postgres-array@2.0.0: {}
@@ -6675,17 +5796,6 @@ snapshots:
prettier@3.8.2: {}
- pretty-format@29.7.0:
- dependencies:
- '@jest/schemas': 29.6.3
- ansi-styles: 5.2.0
- react-is: 18.3.1
-
- prompts@2.4.2:
- dependencies:
- kleur: 3.0.3
- sisteransi: 1.0.5
-
proxy-addr@2.0.7:
dependencies:
forwarded: 0.2.0
@@ -6693,8 +5803,6 @@ snapshots:
punycode@2.3.1: {}
- pure-rand@6.1.0: {}
-
qs@6.14.2:
dependencies:
side-channel: 1.1.0
@@ -6714,8 +5822,6 @@ snapshots:
iconv-lite: 0.4.24
unpipe: 1.0.0
- react-is@18.3.1: {}
-
readable-stream@3.6.2:
dependencies:
inherits: 2.0.4
@@ -6730,28 +5836,12 @@ snapshots:
repeat-string@1.6.1: {}
- require-directory@2.1.1: {}
-
require-from-string@2.0.2: {}
- resolve-cwd@3.0.0:
- dependencies:
- resolve-from: 5.0.0
-
resolve-from@4.0.0: {}
- resolve-from@5.0.0: {}
-
resolve-pkg-maps@1.0.0: {}
- resolve.exports@2.0.3: {}
-
- resolve@1.22.11:
- dependencies:
- is-core-module: 2.16.1
- path-parse: 1.0.7
- supports-preserve-symlinks-flag: 1.0.0
-
restore-cursor@3.1.0:
dependencies:
onetime: 5.1.2
@@ -6770,6 +5860,27 @@ snapshots:
dependencies:
glob: 7.2.3
+ rolldown@1.0.0-rc.15:
+ dependencies:
+ '@oxc-project/types': 0.124.0
+ '@rolldown/pluginutils': 1.0.0-rc.15
+ optionalDependencies:
+ '@rolldown/binding-android-arm64': 1.0.0-rc.15
+ '@rolldown/binding-darwin-arm64': 1.0.0-rc.15
+ '@rolldown/binding-darwin-x64': 1.0.0-rc.15
+ '@rolldown/binding-freebsd-x64': 1.0.0-rc.15
+ '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.15
+ '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.15
+ '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.15
+ '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.15
+ '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.15
+ '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.15
+ '@rolldown/binding-linux-x64-musl': 1.0.0-rc.15
+ '@rolldown/binding-openharmony-arm64': 1.0.0-rc.15
+ '@rolldown/binding-wasm32-wasi': 1.0.0-rc.15
+ '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.15
+ '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.15
+
run-async@2.4.1: {}
run-async@3.0.0: {}
@@ -6803,8 +5914,6 @@ snapshots:
ajv-formats: 2.1.1(ajv@8.18.0)
ajv-keywords: 5.1.0(ajv@8.18.0)
- semver@6.3.1: {}
-
semver@7.7.4: {}
send@0.19.2:
@@ -6879,12 +5988,12 @@ snapshots:
side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2
+ siginfo@2.0.0: {}
+
signal-exit@3.0.7: {}
signal-exit@4.1.0: {}
- sisteransi@1.0.5: {}
-
slash@3.0.0: {}
slice-ansi@7.1.2:
@@ -6897,10 +6006,7 @@ snapshots:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
- source-map-support@0.5.13:
- dependencies:
- buffer-from: 1.1.2
- source-map: 0.6.1
+ source-map-js@1.2.1: {}
source-map-support@0.5.21:
dependencies:
@@ -6915,23 +6021,16 @@ snapshots:
split2@4.2.0: {}
- sprintf-js@1.0.3: {}
-
- stack-utils@2.0.6:
- dependencies:
- escape-string-regexp: 2.0.0
+ stackback@0.0.2: {}
statuses@2.0.2: {}
+ std-env@4.0.0: {}
+
streamsearch@1.1.0: {}
string-argv@0.3.2: {}
- string-length@4.0.2:
- dependencies:
- char-regex: 1.0.2
- strip-ansi: 6.0.1
-
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@@ -6969,10 +6068,6 @@ snapshots:
strip-bom@3.0.0: {}
- strip-bom@4.0.0: {}
-
- strip-final-newline@2.0.0: {}
-
strip-json-comments@3.1.1: {}
strtok3@10.3.5:
@@ -7009,8 +6104,6 @@ snapshots:
dependencies:
has-flag: 4.0.0
- supports-preserve-symlinks-flag@1.0.0: {}
-
symbol-observable@4.0.0: {}
synckit@0.11.12:
@@ -7019,13 +6112,16 @@ snapshots:
tapable@2.3.2: {}
- terser-webpack-plugin@5.4.0(webpack@5.97.1):
+ terser-webpack-plugin@5.4.0(@swc/core@1.15.24)(esbuild@0.27.7)(webpack@5.97.1(@swc/core@1.15.24)(esbuild@0.27.7)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
schema-utils: 4.3.3
terser: 5.46.1
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.27.7)
+ optionalDependencies:
+ '@swc/core': 1.15.24
+ esbuild: 0.27.7
terser@5.46.1:
dependencies:
@@ -7034,24 +6130,25 @@ snapshots:
commander: 2.20.3
source-map-support: 0.5.21
- test-exclude@6.0.0:
- dependencies:
- '@istanbuljs/schema': 0.1.3
- glob: 7.2.3
- minimatch: 3.1.5
-
text-table@0.2.0: {}
through@2.3.8: {}
+ tinybench@2.9.0: {}
+
tinyexec@1.1.1: {}
+ tinyglobby@0.2.16:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.4)
+ picomatch: 4.0.4
+
+ tinyrainbow@3.1.0: {}
+
tmp@0.0.33:
dependencies:
os-tmpdir: 1.0.2
- tmpl@1.0.5: {}
-
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
@@ -7072,27 +6169,7 @@ snapshots:
dependencies:
typescript: 5.9.3
- ts-jest@29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))(typescript@5.9.3):
- dependencies:
- bs-logger: 0.2.6
- fast-json-stable-stringify: 2.1.0
- handlebars: 4.7.9
- jest: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))
- json5: 2.2.3
- lodash.memoize: 4.1.2
- make-error: 1.3.6
- semver: 7.7.4
- type-fest: 4.41.0
- typescript: 5.9.3
- yargs-parser: 21.1.1
- optionalDependencies:
- '@babel/core': 7.29.0
- '@jest/transform': 29.7.0
- '@jest/types': 29.6.3
- babel-jest: 29.7.0(@babel/core@7.29.0)
- jest-util: 29.7.0
-
- ts-loader@9.5.7(typescript@5.9.3)(webpack@5.97.1):
+ ts-loader@9.5.7(typescript@5.9.3)(webpack@5.97.1(@swc/core@1.15.24)(esbuild@0.27.7)):
dependencies:
chalk: 4.1.2
enhanced-resolve: 5.20.1
@@ -7100,9 +6177,9 @@ snapshots:
semver: 7.7.4
source-map: 0.7.6
typescript: 5.9.3
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.27.7)
- ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3):
+ ts-node@10.9.2(@swc/core@1.15.24)(@types/node@20.19.39)(typescript@5.9.3):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.12
@@ -7119,6 +6196,8 @@ snapshots:
typescript: 5.9.3
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
+ optionalDependencies:
+ '@swc/core': 1.15.24
tsconfig-paths-webpack-plugin@4.2.0:
dependencies:
@@ -7146,14 +6225,10 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
- type-detect@4.0.8: {}
-
type-fest@0.20.2: {}
type-fest@0.21.3: {}
- type-fest@4.41.0: {}
-
type-is@1.6.18:
dependencies:
media-typer: 0.3.0
@@ -7165,9 +6240,6 @@ snapshots:
typescript@5.9.3: {}
- uglify-js@3.19.3:
- optional: true
-
uid@2.0.2:
dependencies:
'@lukeed/csprng': 1.1.0
@@ -7180,6 +6252,22 @@ snapshots:
unpipe@1.0.0: {}
+ unplugin-swc@1.5.9(@swc/core@1.15.24):
+ dependencies:
+ '@rollup/pluginutils': 5.3.0
+ '@swc/core': 1.15.24
+ load-tsconfig: 0.2.5
+ unplugin: 2.3.11
+ transitivePeerDependencies:
+ - rollup
+
+ unplugin@2.3.11:
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ acorn: 8.16.0
+ picomatch: 4.0.4
+ webpack-virtual-modules: 0.6.2
+
update-browserslist-db@1.2.3(browserslist@4.28.2):
dependencies:
browserslist: 4.28.2
@@ -7196,17 +6284,50 @@ snapshots:
v8-compile-cache-lib@3.0.1: {}
- v8-to-istanbul@9.3.0:
- dependencies:
- '@jridgewell/trace-mapping': 0.3.31
- '@types/istanbul-lib-coverage': 2.0.6
- convert-source-map: 2.0.0
-
vary@1.1.2: {}
- walker@1.0.8:
+ vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
dependencies:
- makeerror: 1.0.12
+ lightningcss: 1.32.0
+ picomatch: 4.0.4
+ postcss: 8.5.9
+ rolldown: 1.0.0-rc.15
+ tinyglobby: 0.2.16
+ optionalDependencies:
+ '@types/node': 20.19.39
+ esbuild: 0.27.7
+ fsevents: 2.3.3
+ terser: 5.46.1
+ tsx: 4.21.0
+ yaml: 2.8.3
+
+ vitest@4.1.4(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
+ dependencies:
+ '@vitest/expect': 4.1.4
+ '@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
+ '@vitest/pretty-format': 4.1.4
+ '@vitest/runner': 4.1.4
+ '@vitest/snapshot': 4.1.4
+ '@vitest/spy': 4.1.4
+ '@vitest/utils': 4.1.4
+ es-module-lexer: 2.0.0
+ expect-type: 1.3.0
+ magic-string: 0.30.21
+ obug: 2.1.1
+ pathe: 2.0.3
+ picomatch: 4.0.4
+ std-env: 4.0.0
+ tinybench: 2.9.0
+ tinyexec: 1.1.1
+ tinyglobby: 0.2.16
+ tinyrainbow: 3.1.0
+ vite: 8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/node': 20.19.39
+ '@vitest/coverage-v8': 4.1.4(vitest@4.1.4)
+ transitivePeerDependencies:
+ - msw
watchpack@2.5.1:
dependencies:
@@ -7223,7 +6344,9 @@ snapshots:
webpack-sources@3.3.4: {}
- webpack@5.97.1:
+ webpack-virtual-modules@0.6.2: {}
+
+ webpack@5.97.1(@swc/core@1.15.24)(esbuild@0.27.7):
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.8
@@ -7245,7 +6368,7 @@ snapshots:
neo-async: 2.6.2
schema-utils: 3.3.0
tapable: 2.3.2
- terser-webpack-plugin: 5.4.0(webpack@5.97.1)
+ terser-webpack-plugin: 5.4.0(@swc/core@1.15.24)(esbuild@0.27.7)(webpack@5.97.1(@swc/core@1.15.24)(esbuild@0.27.7))
watchpack: 2.5.1
webpack-sources: 3.3.4
transitivePeerDependencies:
@@ -7262,9 +6385,12 @@ snapshots:
dependencies:
isexe: 2.0.0
- word-wrap@1.2.5: {}
+ why-is-node-running@2.3.0:
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
- wordwrap@1.0.0: {}
+ word-wrap@1.2.5: {}
wrap-ansi@6.2.0:
dependencies:
@@ -7292,31 +6418,12 @@ snapshots:
wrappy@1.0.2: {}
- write-file-atomic@4.0.2:
- dependencies:
- imurmurhash: 0.1.4
- signal-exit: 3.0.7
-
xtend@4.0.2: {}
- y18n@5.0.8: {}
-
- yallist@3.1.1: {}
-
yaml@2.8.3: {}
yargs-parser@21.1.1: {}
- yargs@17.7.2:
- dependencies:
- cliui: 8.0.1
- escalade: 3.2.0
- get-caller-file: 2.0.5
- require-directory: 2.1.1
- string-width: 4.2.3
- y18n: 5.0.8
- yargs-parser: 21.1.1
-
yn@3.1.1: {}
yocto-queue@0.1.0: {}
diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts
index d22f389..2552ec5 100644
--- a/src/app.controller.spec.ts
+++ b/src/app.controller.spec.ts
@@ -3,20 +3,20 @@ import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
- let appController: AppController;
+ let appController: AppController;
- beforeEach(async () => {
- const app: TestingModule = await Test.createTestingModule({
- controllers: [AppController],
- providers: [AppService],
- }).compile();
+ beforeEach(async () => {
+ const app: TestingModule = await Test.createTestingModule({
+ controllers: [AppController],
+ providers: [AppService],
+ }).compile();
- appController = app.get(AppController);
- });
+ appController = app.get(AppController);
+ });
- describe('root', () => {
- it('should return "Hello World!"', () => {
- expect(appController.getHello()).toBe('Hello World!');
+ describe('root', () => {
+ it('should return "Hello World!"', () => {
+ expect(appController.getHello()).toBe('Hello World!');
+ });
});
- });
});
diff --git a/src/app.controller.ts b/src/app.controller.ts
index cce879e..a325e8b 100644
--- a/src/app.controller.ts
+++ b/src/app.controller.ts
@@ -3,10 +3,10 @@ import { AppService } from './app.service';
@Controller()
export class AppController {
- constructor(private readonly appService: AppService) {}
+ constructor(private readonly appService: AppService) {}
- @Get()
- getHello(): string {
- return this.appService.getHello();
- }
+ @Get()
+ getHello(): string {
+ return this.appService.getHello();
+ }
}
diff --git a/src/app.module.ts b/src/app.module.ts
index b4044b1..d4fff82 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -4,8 +4,8 @@ import { AppService } from './app.service';
import { ConfigModule } from '@libs/config';
@Module({
- imports: [ConfigModule],
- controllers: [AppController],
- providers: [AppService],
+ imports: [ConfigModule],
+ controllers: [AppController],
+ providers: [AppService],
})
export class AppModule {}
diff --git a/src/app.service.ts b/src/app.service.ts
index 927d7cc..61b7a5b 100644
--- a/src/app.service.ts
+++ b/src/app.service.ts
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
- getHello(): string {
- return 'Hello World!';
- }
+ getHello(): string {
+ return 'Hello World!';
+ }
}
diff --git a/src/main.ts b/src/main.ts
index 67e6de2..df101b2 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -3,12 +3,12 @@ import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config';
async function bootstrap() {
- const app = await NestFactory.create(AppModule);
+ const app = await NestFactory.create(AppModule);
- const config = app.get(ConfigService);
- const port = config.getOrThrow('PORT');
+ const config = app.get(ConfigService);
+ const port = config.getOrThrow('PORT');
- await app.listen(port);
+ await app.listen(port);
}
bootstrap();
diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts
index 50cda62..4d26f6b 100644
--- a/test/app.e2e-spec.ts
+++ b/test/app.e2e-spec.ts
@@ -4,21 +4,18 @@ import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
- let app: INestApplication;
+ let app: INestApplication;
- beforeEach(async () => {
- const moduleFixture: TestingModule = await Test.createTestingModule({
- imports: [AppModule],
- }).compile();
+ beforeEach(async () => {
+ const moduleFixture: TestingModule = await Test.createTestingModule({
+ imports: [AppModule],
+ }).compile();
- app = moduleFixture.createNestApplication();
- await app.init();
- });
+ app = moduleFixture.createNestApplication();
+ await app.init();
+ });
- it('/ (GET)', () => {
- return request(app.getHttpServer())
- .get('/')
- .expect(200)
- .expect('Hello World!');
- });
+ it('/ (GET)', () => {
+ return request(app.getHttpServer()).get('/').expect(200).expect('Hello World!');
+ });
});
diff --git a/test/jest-e2e.json b/test/jest-e2e.json
deleted file mode 100644
index f9730e0..0000000
--- a/test/jest-e2e.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "moduleFileExtensions": ["js", "json", "ts"],
- "rootDir": ".",
- "testEnvironment": "node",
- "testRegex": ".e2e-spec.ts$",
- "transform": {
- "^.+\\.(t|j)s$": "ts-jest"
- },
- "moduleNameMapper": {
- "@libs/config/(.*)": "/../libs/config/src/$1",
- "@libs/config": "/../libs/config/src",
- "@libs/database/(.*)": "/../libs/database/src/$1",
- "@libs/database": "/../libs/database/src"
- }
-}
diff --git a/tsconfig.json b/tsconfig.json
index 32ea749..d798e4f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,28 +1,36 @@
{
- "compilerOptions": {
- "module": "commonjs",
- "declaration": false,
- "removeComments": true,
- "emitDecoratorMetadata": true,
- "experimentalDecorators": true,
- "allowSyntheticDefaultImports": true,
- "target": "ES2021",
- "sourceMap": true,
- "outDir": "./dist",
- "incremental": true,
- "skipLibCheck": true,
- "strictNullChecks": false,
- "noImplicitAny": false,
- "strictBindCallApply": false,
- "forceConsistentCasingInFileNames": false,
- "noFallthroughCasesInSwitch": false,
- "paths": {
- "@libs/config": ["./libs/config/src"],
- "@libs/config/*": ["./libs/config/src/*"],
- "@libs/database": ["./libs/database/src"],
- "@libs/database/*": ["./libs/database/src/*"]
- }
- },
- "include": ["src/**/*", "libs/**/*", "drizzle.config.ts"],
- "exclude": ["dist", "node_modules"]
+ "compilerOptions": {
+ "module": "commonjs",
+ "declaration": false,
+ "removeComments": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "allowSyntheticDefaultImports": true,
+ "target": "ES2021",
+ "sourceMap": true,
+ "outDir": "./dist",
+ "incremental": true,
+ "skipLibCheck": true,
+ "strictNullChecks": false,
+ "noImplicitAny": false,
+ "strictBindCallApply": false,
+ "forceConsistentCasingInFileNames": false,
+ "noFallthroughCasesInSwitch": false,
+ "types": ["node", "vitest/globals"],
+ "paths": {
+ "@libs/config": ["./libs/config/src"],
+ "@libs/config/*": ["./libs/config/src/*"],
+ "@libs/database": ["./libs/database/src"],
+ "@libs/database/*": ["./libs/database/src/*"]
+ }
+ },
+ "include": [
+ "src/**/*",
+ "libs/**/*",
+ "test/**/*",
+ "drizzle.config.ts",
+ "vitest.config.ts",
+ "vitest.config.e2e.ts"
+ ],
+ "exclude": ["dist", "node_modules"]
}
diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts
new file mode 100644
index 0000000..62c7703
--- /dev/null
+++ b/vitest.config.e2e.ts
@@ -0,0 +1,22 @@
+import swc from 'unplugin-swc';
+import { defineConfig } from 'vitest/config';
+import path from 'path';
+
+export default defineConfig({
+ test: {
+ globals: true,
+ root: './',
+ environment: 'node',
+ include: ['test/**/*.e2e-spec.ts'],
+ alias: {
+ '@libs/config': path.resolve(__dirname, './libs/config/src'),
+ '@libs/database': path.resolve(__dirname, './libs/database/src'),
+ '@src': path.resolve(__dirname, './src'),
+ },
+ },
+ plugins: [
+ swc.vite({
+ module: { type: 'es6' },
+ }),
+ ],
+});
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..3fc6021
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,22 @@
+import swc from 'unplugin-swc';
+import { defineConfig } from 'vitest/config';
+import path from 'path';
+
+export default defineConfig({
+ test: {
+ globals: true,
+ root: './',
+ environment: 'node',
+ include: ['**/*.spec.ts'],
+ alias: {
+ '@libs/config': path.resolve(__dirname, './libs/config/src'),
+ '@libs/database': path.resolve(__dirname, './libs/database/src'),
+ '@src': path.resolve(__dirname, './src'),
+ },
+ },
+ plugins: [
+ swc.vite({
+ module: { type: 'es6' },
+ }),
+ ],
+});
From 89d7ba63a860498b66ae655c8cc6cd8de9364cb7 Mon Sep 17 00:00:00 2001
From: Danil <123034340+soorq@users.noreply.github.com>
Date: Fri, 10 Apr 2026 21:16:36 +0300
Subject: [PATCH 05/30] feat(infra): bootstrap docker orchestration and core
database module
* feat(infra): setup multi-stage dockerfiles and compose orchestration
* chore: resolve merge conflicts
---
.dockerignore | 41 ++++++++++++++
.env.example | 23 +++++++-
Dockerfile.dev | 14 +++++
Dockerfile.prod | 35 ++++++++++++
infra/compose.dev.yaml | 54 +++++++++++++++++++
libs/config/src/config.schema.ts | 7 +++
.../interfaces/database-module.interface.ts | 7 ++-
src/app.module.ts | 18 ++++++-
8 files changed, 194 insertions(+), 5 deletions(-)
create mode 100644 .dockerignore
create mode 100644 Dockerfile.dev
create mode 100644 Dockerfile.prod
create mode 100644 infra/compose.dev.yaml
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..dbafb72
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,41 @@
+# Dependencies
+node_modules
+npm-debug.log
+yarn-error.log
+pnpm-debug.log
+
+# Build output
+dist
+artifacts
+out
+
+# Environment variables
+.env
+.env.production
+.env.local
+!.env.example
+
+# Docker / Infrastructure
+docker-compose.yml
+docker-compose.*.yml
+Dockerfile
+Dockerfile.*
+.dockerignore
+
+# Git
+.git
+.gitignore
+.gitattributes
+
+# Editor / OS
+.vscode
+.idea
+.DS_Store
+*.swp
+*.log
+
+# Tests and Coverage
+coverage
+test-results
+*.spec.ts
+*.e2e-spec.ts
diff --git a/.env.example b/.env.example
index 826b5f6..f981c6e 100644
--- a/.env.example
+++ b/.env.example
@@ -1,3 +1,22 @@
-PORT=3005
-DATABASE_URL="postgresql://postgres:root@localhost:5432/task-tracker"
+# --- APP ---
+PORT=3000
NODE_ENV=development
+
+# --- POSTGRES ---
+DB_USERNAME=admin
+DB_PASSWORD=p@ssword123
+DB_DATABASE=task_tracker
+DB_PORT=6000
+DB_SCHEMA=base
+
+# ВАЖНО:
+# Для работы ВНУТРИ Docker: используй хост 'database' и порт 5432
+# DATABASE_URL=postgres://${DB_USERNAME}:${DB_PASSWORD}@database:5432/${DB_DATABASE}
+
+# Для работы ЛОКАЛЬНО (без докера): используй 'localhost' и порт 6000
+DATABASE_URL=postgres://${DB_USERNAME}:${DB_PASSWORD}@localhost:${DB_PORT}/${DB_DATABASE}
+
+# --- REDIS ---
+REDIS_HOST=redis
+REDIS_PORT=6379
+REDIS_EXTERNAL_PORT=6380
diff --git a/Dockerfile.dev b/Dockerfile.dev
new file mode 100644
index 0000000..55b2adf
--- /dev/null
+++ b/Dockerfile.dev
@@ -0,0 +1,14 @@
+FROM node:23-alpine
+
+RUN corepack enable && corepack prepare pnpm@latest --activate
+
+WORKDIR /app
+
+COPY package.json pnpm-lock.yaml ./
+COPY tsconfig* ./
+
+RUN pnpm install --no-frozen-lockfile
+
+COPY . .
+
+CMD ["pnpm", "run", "start:dev"]
\ No newline at end of file
diff --git a/Dockerfile.prod b/Dockerfile.prod
new file mode 100644
index 0000000..16325c9
--- /dev/null
+++ b/Dockerfile.prod
@@ -0,0 +1,35 @@
+FROM node:20-alpine AS base
+
+ENV PNPM_HOME="/pnpm"
+ENV PATH="$PNPM_HOME:$PATH"
+RUN corepack enable
+
+WORKDIR /app
+
+COPY package.json pnpm-lock.yaml ./
+
+FROM base AS build
+
+RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
+ pnpm install --frozen-lockfile
+
+COPY . .
+
+RUN pnpm run build
+
+RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
+ pnpm prune --prod
+
+FROM node:20-alpine AS runner
+WORKDIR /app
+
+ENV NODE_ENV=production
+ENV PORT=3000
+
+COPY --from=build /app/dist ./dist
+COPY --from=build /app/node_modules ./node_modules
+COPY --from=build /app/package.json ./
+
+EXPOSE 3000
+
+CMD ["node", "dist/main"]
\ No newline at end of file
diff --git a/infra/compose.dev.yaml b/infra/compose.dev.yaml
new file mode 100644
index 0000000..7c54f3a
--- /dev/null
+++ b/infra/compose.dev.yaml
@@ -0,0 +1,54 @@
+version: "3.9"
+
+name: task-tracker
+
+services:
+ api:
+ hostname: api
+ container_name: api
+ build:
+ context: ../
+ dockerfile: Dockerfile.dev
+ restart: always
+ env_file:
+ - ../.env
+ ports:
+ - "3000:3000"
+ depends_on:
+ database:
+ condition: service_healthy
+ redis:
+ condition: service_started
+ networks:
+ - backend
+
+ database:
+ hostname: database
+ container_name: database
+ image: postgres:16-alpine
+ restart: always
+ env_file:
+ - ../.env
+ environment:
+ POSTGRES_USER: ${DB_USERNAME:-admin}
+ POSTGRES_PASSWORD: ${DB_PASSWORD:-admin}
+ POSTGRES_DB: ${DB_DATABASE:-tracker}
+ ports:
+ - "6000:5432"
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ networks:
+ - backend
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE}"]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+ profiles: ["infra"]
+
+volumes:
+ postgres_data:
+ redis_data:
+
+networks:
+ backend:
diff --git a/libs/config/src/config.schema.ts b/libs/config/src/config.schema.ts
index 6042da3..172e6b3 100644
--- a/libs/config/src/config.schema.ts
+++ b/libs/config/src/config.schema.ts
@@ -3,6 +3,13 @@ import { z } from 'zod/v4';
export const ConfigSchema = z.object({
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
+ DB_USERNAME: z.string({ error: 'DB_USERNAME is missing' }),
+ DB_PASSWORD: z.string({ error: 'DB_PASSWORD is missing' }),
+ DB_DATABASE: z.string({ error: 'DB_DATABASE is missing' }),
+ DB_SCHEMA: z.string({ error: 'DB_SCHEMA is missing' }),
+ DATABASE_URL: z.string().url('DATABASE_URL must be a valid connection string'),
+ REDIS_HOST: z.string().default('redis'),
+ REDIS_PORT: z.coerce.number().default(6379),
});
export type Config = z.infer;
diff --git a/libs/database/src/interfaces/database-module.interface.ts b/libs/database/src/interfaces/database-module.interface.ts
index 55e114e..7926881 100644
--- a/libs/database/src/interfaces/database-module.interface.ts
+++ b/libs/database/src/interfaces/database-module.interface.ts
@@ -12,11 +12,14 @@ export interface DatabaseModuleOptionsFactory {
createDatabaseOptions(): Promise | DatabaseModuleOptions;
}
-export interface DatabaseModuleAsyncOptions extends Pick {
+export interface DatabaseModuleAsyncOptions extends Pick<
+ ModuleMetadata,
+ 'imports'
+> {
useExisting?: Type;
useClass?: Type;
useFactory?: (
- ...args: unknown[]
+ ...args: TArgs
) => Promise> | Omit;
inject?: FactoryProvider['inject'];
global?: boolean;
diff --git a/src/app.module.ts b/src/app.module.ts
index d4fff82..74ca97c 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -2,9 +2,25 @@ import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@libs/config';
+import { DatabaseModule } from '@libs/database';
+import { ConfigService } from '@nestjs/config';
+import * as schema from './shared/entities';
@Module({
- imports: [ConfigModule],
+ imports: [
+ ConfigModule,
+ DatabaseModule.registerAsync({
+ global: true,
+ inject: [ConfigService],
+ useFactory: (cfg: ConfigService) => {
+ return {
+ schema,
+ schemaName: cfg.getOrThrow('DB_SCHEMA'),
+ logging: true,
+ };
+ },
+ }),
+ ],
controllers: [AppController],
providers: [AppService],
})
From 6eb3ab5d15f617d8003997e1ad3ff8587b57d8f1 Mon Sep 17 00:00:00 2001
From: Danil <123034340+soorq@users.noreply.github.com>
Date: Fri, 10 Apr 2026 21:54:11 +0300
Subject: [PATCH 06/30] refactor: migrate from express to fastify and implement
unified bootstrap
* feat(infra): setup multi-stage dockerfiles and compose orchestration
* chore: resolve merge conflicts
* chore: add api boilerplate packages
* feat(bootstrap): implement unified bootstrap service and fix fastify issues
* feat(bootstrap): implement zod ecosystem contracts
---
.env.example | 1 +
libs/bootstrap/src/bootstrap.ts | 81 +
libs/bootstrap/src/configs/swagger.ts | 7 +
libs/bootstrap/src/configs/throttler.ts | 9 +
libs/bootstrap/src/index.ts | 1 +
libs/bootstrap/src/interfaces/index.ts | 1 +
.../src/interfaces/options.interface.ts | 35 +
libs/bootstrap/src/setups/cors.ts | 27 +
libs/bootstrap/src/setups/index.ts | 3 +
libs/bootstrap/src/setups/swagger.ts | 38 +
libs/bootstrap/src/setups/throttler.ts | 19 +
libs/bootstrap/tsconfig.lib.json | 9 +
libs/config/src/config.schema.ts | 19 +
nest-cli.json | 11 +-
package.json | 133 +-
pnpm-lock.yaml | 2177 +++++++++--------
src/app.module.ts | 14 +-
src/main.ts | 28 +-
test/app.e2e-spec.ts | 4 +-
tsconfig.json | 4 +-
20 files changed, 1508 insertions(+), 1113 deletions(-)
create mode 100644 libs/bootstrap/src/bootstrap.ts
create mode 100644 libs/bootstrap/src/configs/swagger.ts
create mode 100644 libs/bootstrap/src/configs/throttler.ts
create mode 100644 libs/bootstrap/src/index.ts
create mode 100644 libs/bootstrap/src/interfaces/index.ts
create mode 100644 libs/bootstrap/src/interfaces/options.interface.ts
create mode 100644 libs/bootstrap/src/setups/cors.ts
create mode 100644 libs/bootstrap/src/setups/index.ts
create mode 100644 libs/bootstrap/src/setups/swagger.ts
create mode 100644 libs/bootstrap/src/setups/throttler.ts
create mode 100644 libs/bootstrap/tsconfig.lib.json
diff --git a/.env.example b/.env.example
index f981c6e..d3f2c1a 100644
--- a/.env.example
+++ b/.env.example
@@ -1,6 +1,7 @@
# --- APP ---
PORT=3000
NODE_ENV=development
+CORS_ALLOWED_ORIGINS=http://localhost:3000
# --- POSTGRES ---
DB_USERNAME=admin
diff --git a/libs/bootstrap/src/bootstrap.ts b/libs/bootstrap/src/bootstrap.ts
new file mode 100644
index 0000000..80ec104
--- /dev/null
+++ b/libs/bootstrap/src/bootstrap.ts
@@ -0,0 +1,81 @@
+import { Logger } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { NestFactory } from '@nestjs/core';
+import { setupThrottler } from './setups/throttler';
+import { DEFAULT_THROTTLER_OPTIONS } from './configs/throttler';
+import { setupCors, setupSwagger } from './setups';
+import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify';
+import type { BootstrapOptions } from './interfaces/options.interface';
+import fastifyCookie from '@fastify/cookie';
+import fastifyCompress from '@fastify/compress';
+
+export async function bootstrapApp(options: BootstrapOptions) {
+ const adapter = new FastifyAdapter();
+
+ const {
+ appModule,
+ apiPrefix = 'api/v1',
+ serviceName = 'App',
+ portEnvKey = 'PORT',
+ defaultPort = 3000,
+ setupApp,
+ useCookieParser = true,
+ useCors = true,
+ throttlerOptions = DEFAULT_THROTTLER_OPTIONS,
+ swaggerOptions,
+ } = options;
+
+ let rootModule = appModule;
+
+ // TODO: Improve merging modules (in case of multiple features needed)
+ if (throttlerOptions) {
+ rootModule = setupThrottler(rootModule, throttlerOptions);
+ }
+
+ const app = await NestFactory.create(rootModule, adapter, {
+ rawBody: true,
+ });
+ const logger = new Logger(serviceName[0].toUpperCase() + serviceName.slice(1));
+ const configService = app.get(ConfigService);
+ const port = configService.getOrThrow(portEnvKey, defaultPort);
+ const origins = configService.getOrThrow('CORS_ALLOWED_ORIGINS');
+
+ app.enableShutdownHooks();
+
+ await app.register(fastifyCompress, {
+ global: true,
+ threshold: 1024,
+ });
+
+ if (apiPrefix) app.setGlobalPrefix(apiPrefix);
+ if (useCors) setupCors(app, origins);
+ if (swaggerOptions) {
+ const { path = 'docs', ...metadata } = swaggerOptions;
+
+ const domain = configService.get('DOMAIN');
+ const stage = configService.get('STAGE_DOMAIN');
+
+ const fullOptions = {
+ ...metadata,
+ path,
+ server: {
+ port,
+ domain,
+ stage,
+ },
+ };
+
+ await setupSwagger(app, fullOptions);
+ }
+ if (useCookieParser) app.register(fastifyCookie, { secret: 'SAME-SECRET' });
+ if (setupApp) setupApp(app);
+
+ await app.listen(port, '0.0.0.0', (_err, address) => {
+ if (_err) {
+ logger.error(_err);
+ process.exit(1);
+ }
+
+ logger.verbose(`Application is running on: ${address}${apiPrefix ? '/' + apiPrefix : ''}`);
+ });
+}
diff --git a/libs/bootstrap/src/configs/swagger.ts b/libs/bootstrap/src/configs/swagger.ts
new file mode 100644
index 0000000..918911d
--- /dev/null
+++ b/libs/bootstrap/src/configs/swagger.ts
@@ -0,0 +1,7 @@
+import type { SwaggerOptions } from '../interfaces/options.interface';
+
+export const SWAGGER_DEFAULTS: SwaggerOptions = {
+ title: 'API',
+ description: 'API Documentation',
+ version: '1.0.0',
+};
diff --git a/libs/bootstrap/src/configs/throttler.ts b/libs/bootstrap/src/configs/throttler.ts
new file mode 100644
index 0000000..135f264
--- /dev/null
+++ b/libs/bootstrap/src/configs/throttler.ts
@@ -0,0 +1,9 @@
+import type { ThrottlerModuleOptions } from '@nestjs/throttler';
+
+export const DEFAULT_THROTTLER_OPTIONS: ThrottlerModuleOptions = [
+ {
+ ttl: 60000,
+ limit: 100,
+ skipIf: (context) => context.getType() !== 'http',
+ },
+];
diff --git a/libs/bootstrap/src/index.ts b/libs/bootstrap/src/index.ts
new file mode 100644
index 0000000..cbc96b3
--- /dev/null
+++ b/libs/bootstrap/src/index.ts
@@ -0,0 +1 @@
+export { bootstrapApp } from './bootstrap';
diff --git a/libs/bootstrap/src/interfaces/index.ts b/libs/bootstrap/src/interfaces/index.ts
new file mode 100644
index 0000000..4d45ac8
--- /dev/null
+++ b/libs/bootstrap/src/interfaces/index.ts
@@ -0,0 +1 @@
+export type { BootstrapOptions, SwaggerOptions } from './options.interface';
diff --git a/libs/bootstrap/src/interfaces/options.interface.ts b/libs/bootstrap/src/interfaces/options.interface.ts
new file mode 100644
index 0000000..8b2f22c
--- /dev/null
+++ b/libs/bootstrap/src/interfaces/options.interface.ts
@@ -0,0 +1,35 @@
+import type { Config } from '@libs/config';
+import type { Type } from '@nestjs/common';
+import type { NestFastifyApplication } from '@nestjs/platform-fastify';
+import type { ThrottlerModuleOptions } from '@nestjs/throttler';
+
+export interface SwaggerMetadata {
+ title?: string;
+ description?: string;
+ version?: string;
+ path?: string;
+}
+
+export interface SwaggerInfrastructure {
+ server?: {
+ port?: string | number;
+ domain?: string;
+ stage?: string;
+ };
+ services?: { name: string; port: number }[];
+}
+
+export interface SwaggerOptions extends SwaggerMetadata, SwaggerInfrastructure {}
+
+export interface BootstrapOptions {
+ apiPrefix?: string;
+ appModule: Type;
+ defaultPort?: number;
+ portEnvKey?: keyof Config;
+ serviceName: string;
+ setupApp?: (app: NestFastifyApplication) => Promise | void;
+ swaggerOptions?: SwaggerMetadata;
+ throttlerOptions?: ThrottlerModuleOptions;
+ useCookieParser?: boolean;
+ useCors?: boolean;
+}
diff --git a/libs/bootstrap/src/setups/cors.ts b/libs/bootstrap/src/setups/cors.ts
new file mode 100644
index 0000000..73d2847
--- /dev/null
+++ b/libs/bootstrap/src/setups/cors.ts
@@ -0,0 +1,27 @@
+import fastifyCors from '@fastify/cors';
+import type { NestFastifyApplication } from '@nestjs/platform-fastify';
+
+export function setupCors(app: NestFastifyApplication, origins: string[]) {
+ app.getHttpAdapter()
+ .getInstance()
+ .register(fastifyCors, {
+ origin: (origin, callback) => {
+ // server-to-server / curl / healthcheck
+ if (!origin) {
+ return callback(null, true);
+ }
+
+ const { hostname } = new URL(origin);
+
+ if (origins.some((o) => hostname === o || hostname.endsWith(`.${o}`))) {
+ callback(null, origin);
+ }
+
+ callback(new Error('Not allowed by CORS'), false);
+ },
+ credentials: true,
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
+ preflightContinue: false,
+ optionsSuccessStatus: 204,
+ });
+}
diff --git a/libs/bootstrap/src/setups/index.ts b/libs/bootstrap/src/setups/index.ts
new file mode 100644
index 0000000..2cfe699
--- /dev/null
+++ b/libs/bootstrap/src/setups/index.ts
@@ -0,0 +1,3 @@
+export { setupCors } from './cors';
+export { setupThrottler } from './throttler';
+export { setupSwagger } from './swagger';
diff --git a/libs/bootstrap/src/setups/swagger.ts b/libs/bootstrap/src/setups/swagger.ts
new file mode 100644
index 0000000..90e938f
--- /dev/null
+++ b/libs/bootstrap/src/setups/swagger.ts
@@ -0,0 +1,38 @@
+import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
+import { cleanupOpenApiDoc } from 'nestjs-zod';
+import type { NestFastifyApplication } from '@nestjs/platform-fastify';
+import type { SwaggerOptions } from '../interfaces';
+import { SWAGGER_DEFAULTS } from '../configs/swagger';
+
+export async function setupSwagger(app: NestFastifyApplication, options: SwaggerOptions = {}) {
+ const { title, description, version, path, server } = {
+ ...SWAGGER_DEFAULTS,
+ ...options,
+ };
+
+ const { domain, port, stage } = server || {};
+
+ const builder = new DocumentBuilder()
+ .setTitle(title)
+ .setDescription(description)
+ .setVersion(version)
+ .addBearerAuth();
+
+ if (port) builder.addServer(`http://localhost:${port}`, 'Local');
+ if (stage) builder.addServer(`https://api.${stage}`, 'Staging');
+ if (domain) builder.addServer(`https://api.${domain}`, 'Production');
+
+ const document = SwaggerModule.createDocument(app, builder.build());
+
+ SwaggerModule.setup(path, app, cleanupOpenApiDoc(document), {
+ jsonDocumentUrl: `${path}/s/json`,
+ yamlDocumentUrl: `${path}/s/yaml`,
+ useGlobalPrefix: true,
+ ui: true,
+ swaggerOptions: {
+ persistAuthorization: true,
+ tagsSorter: 'alpha',
+ operationsSorter: 'alpha',
+ },
+ });
+}
diff --git a/libs/bootstrap/src/setups/throttler.ts b/libs/bootstrap/src/setups/throttler.ts
new file mode 100644
index 0000000..59ac61a
--- /dev/null
+++ b/libs/bootstrap/src/setups/throttler.ts
@@ -0,0 +1,19 @@
+import { Module, type Type } from '@nestjs/common';
+import type { ThrottlerModuleOptions } from '@nestjs/throttler';
+import { APP_GUARD } from '@nestjs/core';
+import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
+
+export function setupThrottler(module: Type, options: ThrottlerModuleOptions) {
+ @Module({
+ imports: [module, ThrottlerModule.forRoot(options)],
+ providers: [
+ {
+ provide: APP_GUARD,
+ useClass: ThrottlerGuard,
+ },
+ ],
+ })
+ class RootModule {}
+
+ return RootModule;
+}
diff --git a/libs/bootstrap/tsconfig.lib.json b/libs/bootstrap/tsconfig.lib.json
new file mode 100644
index 0000000..208ac7d
--- /dev/null
+++ b/libs/bootstrap/tsconfig.lib.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "declaration": true,
+ "outDir": "../../dist/libs/bootstrap"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
+}
diff --git a/libs/config/src/config.schema.ts b/libs/config/src/config.schema.ts
index 172e6b3..348a00f 100644
--- a/libs/config/src/config.schema.ts
+++ b/libs/config/src/config.schema.ts
@@ -10,6 +10,25 @@ export const ConfigSchema = z.object({
DATABASE_URL: z.string().url('DATABASE_URL must be a valid connection string'),
REDIS_HOST: z.string().default('redis'),
REDIS_PORT: z.coerce.number().default(6379),
+ DOMAIN: z
+ .string()
+ .toLowerCase()
+ .refine((val) => !val || /^[a-z0-9.-]+\.[a-z]{2,}$/.test(val), {
+ message: 'DOMAIN must be a valid hostname (e.g., example.com)',
+ })
+ .optional(),
+ STAGE_DOMAIN: z
+ .string()
+ .toLowerCase()
+ .refine((val) => !val || /^[a-z0-9.-]+\.[a-z]{2,}$/.test(val), {
+ message: 'STAGE_DOMAIN must be a valid hostname',
+ })
+ .optional(),
+ CORS_ALLOWED_ORIGINS: z
+ .string()
+ .min(1, "CORS_ALLOWED_ORIGINS can't be empty")
+ .transform((val) => val.split(',').map((s) => s.trim()))
+ .pipe(z.array(z.string().url('Each origin must be a valid URL'))),
});
export type Config = z.infer;
diff --git a/nest-cli.json b/nest-cli.json
index 1079603..8daf27c 100644
--- a/nest-cli.json
+++ b/nest-cli.json
@@ -24,6 +24,15 @@
"compilerOptions": {
"tsConfigPath": "libs/database/tsconfig.lib.json"
}
+ },
+ "bootstrap": {
+ "type": "library",
+ "root": "libs/bootstrap",
+ "entryFile": "index",
+ "sourceRoot": "libs/bootstrap/src",
+ "compilerOptions": {
+ "tsConfigPath": "libs/bootstrap/tsconfig.lib.json"
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index f2c89ff..1468528 100644
--- a/package.json
+++ b/package.json
@@ -1,64 +1,71 @@
{
- "name": "task-backend",
- "version": "0.0.1",
- "description": "",
- "author": "",
- "private": true,
- "license": "UNLICENSED",
- "scripts": {
- "build": "nest build",
- "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"libs/**/*.ts\"",
- "start": "nest start",
- "start:dev": "nest start --watch",
- "start:debug": "nest start --debug --watch",
- "start:prod": "node dist/main",
- "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
- "test": "vitest run",
- "test:watch": "vitest",
- "test:cov": "vitest run --coverage",
- "test:debug": "vitest --inspect-brk --inspect --logHeapUsage --threads=false",
- "test:e2e": "vitest run --config ./vitest.config.e2e.ts",
- "db:generate": "drizzle-kit generate",
- "db:migrate": "drizzle-kit migrate",
- "db:studio": "drizzle-kit studio"
- },
- "dependencies": {
- "@nestjs/common": "^10.0.0",
- "@nestjs/config": "^4.0.4",
- "@nestjs/core": "^10.0.0",
- "@nestjs/platform-express": "^10.0.0",
- "drizzle-orm": "^0.45.2",
- "drizzle-zod": "^0.8.3",
- "pg": "^8.20.0",
- "reflect-metadata": "^0.2.0",
- "rxjs": "^7.8.1",
- "zod": "^4.3.6"
- },
- "devDependencies": {
- "@nestjs/cli": "^10.0.0",
- "@nestjs/schematics": "^10.0.0",
- "@nestjs/testing": "^10.0.0",
- "@types/express": "^4.17.17",
- "@types/node": "^20.3.1",
- "@types/pg": "^8.20.0",
- "@types/supertest": "^6.0.0",
- "@typescript-eslint/eslint-plugin": "^6.0.0",
- "@typescript-eslint/parser": "^6.0.0",
- "@vitest/coverage-v8": "^4.1.4",
- "drizzle-kit": "^0.31.10",
- "eslint": "^8.42.0",
- "eslint-config-prettier": "^9.0.0",
- "eslint-plugin-prettier": "^5.0.0",
- "lint-staged": "^16.4.0",
- "prettier": "^3.0.0",
- "source-map-support": "^0.5.21",
- "supertest": "^6.3.3",
- "ts-loader": "^9.4.3",
- "ts-node": "^10.9.1",
- "tsconfig-paths": "^4.2.0",
- "typescript": "^5.1.3",
- "unplugin-swc": "^1.5.9",
- "vitest": "^4.1.4"
- },
- "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
-}
+ "name": "task-backend",
+ "version": "0.0.1",
+ "description": "",
+ "author": "",
+ "private": true,
+ "license": "UNLICENSED",
+ "scripts": {
+ "build": "nest build",
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"libs/**/*.ts\"",
+ "start": "nest start",
+ "start:dev": "nest start --watch",
+ "start:debug": "nest start --debug --watch",
+ "start:prod": "node dist/main",
+ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "test:cov": "vitest run --coverage",
+ "test:debug": "vitest --inspect-brk --inspect --logHeapUsage --threads=false",
+ "test:e2e": "vitest run --config ./vitest.config.e2e.ts",
+ "db:generate": "drizzle-kit generate",
+ "db:migrate": "drizzle-kit migrate",
+ "db:studio": "drizzle-kit studio"
+ },
+ "dependencies": {
+ "@fastify/compress": "^8.3.1",
+ "@fastify/cookie": "^11.0.2",
+ "@fastify/cors": "^11.2.0",
+ "@fastify/static": "^9.1.0",
+ "@nestjs/common": "^11.1.18",
+ "@nestjs/config": "^4.0.4",
+ "@nestjs/core": "^11.1.18",
+ "@nestjs/platform-fastify": "^11.1.18",
+ "@nestjs/swagger": "^11.2.7",
+ "@nestjs/throttler": "^6.5.0",
+ "drizzle-orm": "^0.45.2",
+ "drizzle-zod": "^0.8.3",
+ "fastify": "^5.8.4",
+ "nestjs-zod": "^5.3.0",
+ "pg": "^8.20.0",
+ "reflect-metadata": "^0.2.0",
+ "rxjs": "^7.8.1",
+ "zod": "^4.3.6"
+ },
+ "devDependencies": {
+ "@nestjs/cli": "^11.0.19",
+ "@nestjs/schematics": "^11.0.10",
+ "@nestjs/testing": "^11.1.18",
+ "@types/node": "^20.3.1",
+ "@types/pg": "^8.20.0",
+ "@types/supertest": "^6.0.0",
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
+ "@typescript-eslint/parser": "^6.0.0",
+ "@vitest/coverage-v8": "^4.1.4",
+ "drizzle-kit": "^0.31.10",
+ "eslint": "^8.42.0",
+ "eslint-config-prettier": "^9.0.0",
+ "eslint-plugin-prettier": "^5.0.0",
+ "lint-staged": "^16.4.0",
+ "prettier": "^3.0.0",
+ "source-map-support": "^0.5.21",
+ "supertest": "^6.3.3",
+ "ts-loader": "^9.4.3",
+ "ts-node": "^10.9.1",
+ "tsconfig-paths": "^4.2.0",
+ "typescript": "^5.1.3",
+ "unplugin-swc": "^1.5.9",
+ "vitest": "^4.1.4"
+ },
+ "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
+}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 21fa10b..3c4618f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,24 +8,48 @@ importers:
.:
dependencies:
+ '@fastify/compress':
+ specifier: ^8.3.1
+ version: 8.3.1
+ '@fastify/cookie':
+ specifier: ^11.0.2
+ version: 11.0.2
+ '@fastify/cors':
+ specifier: ^11.2.0
+ version: 11.2.0
+ '@fastify/static':
+ specifier: ^9.1.0
+ version: 9.1.0
'@nestjs/common':
- specifier: ^10.0.0
- version: 10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ specifier: ^11.1.18
+ version: 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/config':
specifier: ^4.0.4
- version: 4.0.4(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)
+ version: 4.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)
'@nestjs/core':
- specifier: ^10.0.0
- version: 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2)
- '@nestjs/platform-express':
- specifier: ^10.0.0
- version: 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)
+ specifier: ^11.1.18
+ version: 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/platform-fastify':
+ specifier: ^11.1.18
+ version: 11.1.18(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))
+ '@nestjs/swagger':
+ specifier: ^11.2.7
+ version: 11.2.7(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)
+ '@nestjs/throttler':
+ specifier: ^6.5.0
+ version: 6.5.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)
drizzle-orm:
specifier: ^0.45.2
version: 0.45.2(@types/pg@8.20.0)(pg@8.20.0)
drizzle-zod:
specifier: ^0.8.3
version: 0.8.3(drizzle-orm@0.45.2(@types/pg@8.20.0)(pg@8.20.0))(zod@4.3.6)
+ fastify:
+ specifier: ^5.8.4
+ version: 5.8.4
+ nestjs-zod:
+ specifier: ^5.3.0
+ version: 5.3.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.2.7(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6)
pg:
specifier: ^8.20.0
version: 8.20.0
@@ -40,17 +64,14 @@ importers:
version: 4.3.6
devDependencies:
'@nestjs/cli':
- specifier: ^10.0.0
- version: 10.4.9(@swc/core@1.15.24)(esbuild@0.27.7)
+ specifier: ^11.0.19
+ version: 11.0.19(@swc/core@1.15.24)(@types/node@20.19.39)(esbuild@0.27.7)
'@nestjs/schematics':
- specifier: ^10.0.0
- version: 10.2.3(chokidar@3.6.0)(typescript@5.9.3)
+ specifier: ^11.0.10
+ version: 11.0.10(chokidar@4.0.3)(typescript@5.9.3)
'@nestjs/testing':
- specifier: ^10.0.0
- version: 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(@nestjs/platform-express@10.4.22)
- '@types/express':
- specifier: ^4.17.17
- version: 4.17.25
+ specifier: ^11.1.18
+ version: 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))
'@types/node':
specifier: ^20.3.1
version: 20.19.39
@@ -95,7 +116,7 @@ importers:
version: 6.3.4
ts-loader:
specifier: ^9.4.3
- version: 9.5.7(typescript@5.9.3)(webpack@5.97.1(@swc/core@1.15.24)(esbuild@0.27.7))
+ version: 9.5.7(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.24)(esbuild@0.27.7))
ts-node:
specifier: ^10.9.1
version: 10.9.2(@swc/core@1.15.24)(@types/node@20.19.39)(typescript@5.9.3)
@@ -114,23 +135,36 @@ importers:
packages:
- '@angular-devkit/core@17.3.11':
- resolution: {integrity: sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==}
- engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
+ '@angular-devkit/core@19.2.23':
+ resolution: {integrity: sha512-RazHPQkUEsNU/OZ75w9UeHxGFMthRiuAW2B/uA7eXExBj/1meHrrBfoCA56ujW2GUxVjRtSrMjylKh4R4meiYA==}
+ engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
+ peerDependencies:
+ chokidar: ^4.0.0
+ peerDependenciesMeta:
+ chokidar:
+ optional: true
+
+ '@angular-devkit/core@19.2.24':
+ resolution: {integrity: sha512-Kd49warf6U/EyWe5BszF/eebN3zQ3bk7tgfEljAw8q/rX95UUtriJubWvp6pgzHfzBA4jwq8f+QiNZB8eBEXPA==}
+ engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
peerDependencies:
- chokidar: ^3.5.2
+ chokidar: ^4.0.0
peerDependenciesMeta:
chokidar:
optional: true
- '@angular-devkit/schematics-cli@17.3.11':
- resolution: {integrity: sha512-kcOMqp+PHAKkqRad7Zd7PbpqJ0LqLaNZdY1+k66lLWmkEBozgq8v4ASn/puPWf9Bo0HpCiK+EzLf0VHE8Z/y6Q==}
- engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
+ '@angular-devkit/schematics-cli@19.2.24':
+ resolution: {integrity: sha512-bsStZQG67J1HBqTmWxtIcobvgrn32L4UOdL7hGyOru5VxDWPNA8pRnDYavT3hnJeBkJYPoQIw8u7Dm0ecoQprw==}
+ engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
hasBin: true
- '@angular-devkit/schematics@17.3.11':
- resolution: {integrity: sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==}
- engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
+ '@angular-devkit/schematics@19.2.23':
+ resolution: {integrity: sha512-Jzs7YM4X6azmHU7Mw5tQSPMuvaqYS8SLnZOJbtiXCy1JyuW9bm/WBBecNHMiuZ8LHXKhvQ6AVX1tKrzF6uiDmw==}
+ engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
+
+ '@angular-devkit/schematics@19.2.24':
+ resolution: {integrity: sha512-lnw+ZM1Io+cJAkReC0NPDjqObL8NtKzKIkdgEEKC8CUmkhurYhedbicN8Y8NYHgG1uLd2GozW3+/QqPRZaN+Lw==}
+ engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
'@babel/code-frame@7.29.0':
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
@@ -650,6 +684,45 @@ packages:
resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ '@fastify/accept-negotiator@2.0.1':
+ resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==}
+
+ '@fastify/ajv-compiler@4.0.5':
+ resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==}
+
+ '@fastify/compress@8.3.1':
+ resolution: {integrity: sha512-BUpItLr6MUX9e9ukg5Y6xekyA/7pBFG8QWtFCrUDm9ctoBc3R2/nA16yOaOWtVoccpXGjdDEYA/MxAb5+8cxag==}
+
+ '@fastify/cookie@11.0.2':
+ resolution: {integrity: sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA==}
+
+ '@fastify/cors@11.2.0':
+ resolution: {integrity: sha512-LbLHBuSAdGdSFZYTLVA3+Ch2t+sA6nq3Ejc6XLAKiQ6ViS2qFnvicpj0htsx03FyYeLs04HfRNBsz/a8SvbcUw==}
+
+ '@fastify/error@4.2.0':
+ resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==}
+
+ '@fastify/fast-json-stringify-compiler@5.0.3':
+ resolution: {integrity: sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==}
+
+ '@fastify/formbody@8.0.2':
+ resolution: {integrity: sha512-84v5J2KrkXzjgBpYnaNRPqwgMsmY7ZDjuj0YVuMR3NXCJRCgKEZy/taSP1wUYGn0onfxJpLyRGDLa+NMaDJtnA==}
+
+ '@fastify/forwarded@3.0.1':
+ resolution: {integrity: sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==}
+
+ '@fastify/merge-json-schemas@0.2.1':
+ resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==}
+
+ '@fastify/proxy-addr@5.1.0':
+ resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==}
+
+ '@fastify/send@4.1.0':
+ resolution: {integrity: sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==}
+
+ '@fastify/static@9.1.0':
+ resolution: {integrity: sha512-EPRNQYqEYEYTK8yyGbcM0iHpyJaupb94bey5O6iCQfLTADr02kaZU+qeHSdd9H9TiMwTBVkrMa59V8CMbn3avQ==}
+
'@humanwhocodes/config-array@0.13.0':
resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==}
engines: {node: '>=10.10.0'}
@@ -663,9 +736,148 @@ packages:
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
deprecated: Use @eslint/object-schema instead
- '@isaacs/cliui@8.0.2':
- resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
- engines: {node: '>=12'}
+ '@inquirer/ansi@1.0.2':
+ resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==}
+ engines: {node: '>=18'}
+
+ '@inquirer/checkbox@4.3.2':
+ resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/confirm@5.1.21':
+ resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/core@10.3.2':
+ resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/editor@4.2.23':
+ resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/expand@4.0.23':
+ resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/external-editor@1.0.3':
+ resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/figures@1.0.15':
+ resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==}
+ engines: {node: '>=18'}
+
+ '@inquirer/input@4.3.1':
+ resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/number@3.0.23':
+ resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/password@4.0.23':
+ resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/prompts@7.10.1':
+ resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/prompts@7.3.2':
+ resolution: {integrity: sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/rawlist@4.1.11':
+ resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/search@3.2.2':
+ resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/select@4.4.2':
+ resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/type@3.0.10':
+ resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
@@ -689,26 +901,29 @@ packages:
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
- '@ljharb/through@2.3.14':
- resolution: {integrity: sha512-ajBvlKpWucBB17FuQYUShqpqy8GRgYEpJW0vWJbUu1CV9lWyrDCapy0lScU8T8Z6qn49sSwJB3+M+evYIdGg+A==}
- engines: {node: '>= 0.4'}
-
'@lukeed/csprng@1.1.0':
resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==}
engines: {node: '>=8'}
+ '@lukeed/ms@2.0.2':
+ resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==}
+ engines: {node: '>=8'}
+
+ '@microsoft/tsdoc@0.16.0':
+ resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==}
+
'@napi-rs/wasm-runtime@1.1.3':
resolution: {integrity: sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==}
peerDependencies:
'@emnapi/core': ^1.7.1
'@emnapi/runtime': ^1.7.1
- '@nestjs/cli@10.4.9':
- resolution: {integrity: sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==}
- engines: {node: '>= 16.14'}
+ '@nestjs/cli@11.0.19':
+ resolution: {integrity: sha512-9htODqTVVNH4lJqyeIotsAgfeaYngDi020cVCd6JhJRKuOT83c/t4JDSky6+xr0lhHyNTNMgZmulxqcMNZFfrw==}
+ engines: {node: '>= 20.11'}
hasBin: true
peerDependencies:
- '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0
+ '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0
'@swc/core': ^1.3.62
peerDependenciesMeta:
'@swc/cli':
@@ -716,11 +931,11 @@ packages:
'@swc/core':
optional: true
- '@nestjs/common@10.4.22':
- resolution: {integrity: sha512-fxJ4v85nDHaqT1PmfNCQ37b/jcv2OojtXTaK1P2uAXhzLf9qq6WNUOFvxBrV4fhQek1EQoT1o9oj5xAZmv3NRw==}
+ '@nestjs/common@11.1.18':
+ resolution: {integrity: sha512-0sLq8Z+TIjLnz1Tqp0C/x9BpLbqpt1qEu0VcH4/fkE0y3F5JxhfK1AdKQ/SPbKhKgwqVDoY4gS8GQr2G6ujaWg==}
peerDependencies:
- class-transformer: '*'
- class-validator: '*'
+ class-transformer: '>=0.4.1'
+ class-validator: '>=0.13.2'
reflect-metadata: ^0.1.12 || ^0.2.0
rxjs: ^7.1.0
peerDependenciesMeta:
@@ -735,13 +950,14 @@ packages:
'@nestjs/common': ^10.0.0 || ^11.0.0
rxjs: ^7.1.0
- '@nestjs/core@10.4.22':
- resolution: {integrity: sha512-6IX9+VwjiKtCjx+mXVPncpkQ5ZjKfmssOZPFexmT+6T9H9wZ3svpYACAo7+9e7Nr9DZSoRZw3pffkJP7Z0UjaA==}
+ '@nestjs/core@11.1.18':
+ resolution: {integrity: sha512-wR3DtGyk/LUAiPtbXDuWJJwVkWElKBY0sqnTzf9d4uM3+X18FRZhK7WFc47czsIGOdWuRsMeLYV+1Z9dO4zDEQ==}
+ engines: {node: '>= 20'}
peerDependencies:
- '@nestjs/common': ^10.0.0
- '@nestjs/microservices': ^10.0.0
- '@nestjs/platform-express': ^10.0.0
- '@nestjs/websockets': ^10.0.0
+ '@nestjs/common': ^11.0.0
+ '@nestjs/microservices': ^11.0.0
+ '@nestjs/platform-express': ^11.0.0
+ '@nestjs/websockets': ^11.0.0
reflect-metadata: ^0.1.12 || ^0.2.0
rxjs: ^7.1.0
peerDependenciesMeta:
@@ -752,30 +968,74 @@ packages:
'@nestjs/websockets':
optional: true
- '@nestjs/platform-express@10.4.22':
- resolution: {integrity: sha512-ySSq7Py/DFozzZdNDH67m/vHoeVdphDniWBnl6q5QVoXldDdrZIHLXLRMPayTDh5A95nt7jjJzmD4qpTbNQ6tA==}
+ '@nestjs/mapped-types@2.1.1':
+ resolution: {integrity: sha512-SCCoMEJ6jdeI5h/N+KCVF1+pmg/hmEkNA5nHTS8Gvww7T/LCl4o1gFLinw2iQ60w7slFkszHcGLKGdazVI4F8A==}
+ peerDependencies:
+ '@nestjs/common': ^10.0.0 || ^11.0.0
+ class-transformer: ^0.4.0 || ^0.5.0
+ class-validator: ^0.13.0 || ^0.14.0 || ^0.15.0
+ reflect-metadata: ^0.1.12 || ^0.2.0
+ peerDependenciesMeta:
+ class-transformer:
+ optional: true
+ class-validator:
+ optional: true
+
+ '@nestjs/platform-fastify@11.1.18':
+ resolution: {integrity: sha512-iJtbqQz51k7Z1vOTUEHO1mU8PsDO1WdgPSJ/6CuXBnazkrkePXoszhefFaPwJreBVn35GE3WTd/6ou7bFwnhmA==}
peerDependencies:
- '@nestjs/common': ^10.0.0
- '@nestjs/core': ^10.0.0
+ '@fastify/static': ^8.0.0 || ^9.0.0
+ '@fastify/view': ^10.0.0 || ^11.0.0
+ '@nestjs/common': ^11.0.0
+ '@nestjs/core': ^11.0.0
+ peerDependenciesMeta:
+ '@fastify/static':
+ optional: true
+ '@fastify/view':
+ optional: true
- '@nestjs/schematics@10.2.3':
- resolution: {integrity: sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==}
+ '@nestjs/schematics@11.0.10':
+ resolution: {integrity: sha512-q9lr0wGwgBHLarD4uno3XiW4JX60WPlg2VTgbqPHl/6bT4u1IEEzj+q9Tad3bVnqL5mlDF3vrZ2tj+x13CJpmw==}
peerDependencies:
typescript: '>=4.8.2'
- '@nestjs/testing@10.4.22':
- resolution: {integrity: sha512-HO9aPus3bAedAC+jKVAA8jTdaj4fs5M9fing4giHrcYV2txe9CvC1l1WAjwQ9RDhEHdugjY4y+FZA/U/YqPZrA==}
+ '@nestjs/swagger@11.2.7':
+ resolution: {integrity: sha512-+e1KWSyZMAQeyZ8nbQSvm3fhzqdxxBNQENvpjO2dVyD7KJmLTTQyXpRb1nM5O04oFdDTUtG3SHMl4+e+zgCK2A==}
+ peerDependencies:
+ '@fastify/static': ^8.0.0 || ^9.0.0
+ '@nestjs/common': ^11.0.1
+ '@nestjs/core': ^11.0.1
+ class-transformer: '*'
+ class-validator: '*'
+ reflect-metadata: ^0.1.12 || ^0.2.0
+ peerDependenciesMeta:
+ '@fastify/static':
+ optional: true
+ class-transformer:
+ optional: true
+ class-validator:
+ optional: true
+
+ '@nestjs/testing@11.1.18':
+ resolution: {integrity: sha512-frzwNlpBgtAzI3hp/qo57DZoRO4RMTH1wST3QUYEhRTHyfPkLpzkWz3jV/mhApXjD0yT56Ptlzn6zuYPLh87Lw==}
peerDependencies:
- '@nestjs/common': ^10.0.0
- '@nestjs/core': ^10.0.0
- '@nestjs/microservices': ^10.0.0
- '@nestjs/platform-express': ^10.0.0
+ '@nestjs/common': ^11.0.0
+ '@nestjs/core': ^11.0.0
+ '@nestjs/microservices': ^11.0.0
+ '@nestjs/platform-express': ^11.0.0
peerDependenciesMeta:
'@nestjs/microservices':
optional: true
'@nestjs/platform-express':
optional: true
+ '@nestjs/throttler@6.5.0':
+ resolution: {integrity: sha512-9j0ZRfH0QE1qyrj9JjIRDz5gQLPqq9yVC2nHsrosDVAfI5HHw08/aUAWx9DZLSdQf4HDkmhTTEGLrRFHENvchQ==}
+ peerDependencies:
+ '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
+ '@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
+ reflect-metadata: ^0.1.13 || ^0.2.0
+
'@noble/hashes@1.8.0':
resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
engines: {node: ^14.21.3 || >=16}
@@ -792,9 +1052,9 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
- '@nuxtjs/opencollective@0.3.2':
- resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==}
- engines: {node: '>=8.0.0', npm: '>=5.0.0'}
+ '@nuxt/opencollective@0.4.1':
+ resolution: {integrity: sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==}
+ engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'}
hasBin: true
'@oxc-project/types@0.124.0':
@@ -803,9 +1063,8 @@ packages:
'@paralleldrive/cuid2@2.3.1':
resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==}
- '@pkgjs/parseargs@0.11.0':
- resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
- engines: {node: '>=14'}
+ '@pinojs/redact@0.4.0':
+ resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
'@pkgr/core@0.2.9':
resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
@@ -918,6 +1177,9 @@ packages:
rollup:
optional: true
+ '@scarf/scarf@1.4.0':
+ resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==}
+
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
@@ -1014,8 +1276,8 @@ packages:
'@swc/types@0.1.26':
resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==}
- '@tokenizer/inflate@0.2.7':
- resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==}
+ '@tokenizer/inflate@0.4.1':
+ resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==}
engines: {node: '>=18'}
'@tokenizer/token@0.3.0':
@@ -1036,15 +1298,9 @@ packages:
'@tybys/wasm-util@0.10.1':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
- '@types/body-parser@1.19.6':
- resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}
-
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
- '@types/connect@3.4.38':
- resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
-
'@types/cookiejar@2.1.5':
resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==}
@@ -1060,48 +1316,21 @@ packages:
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
- '@types/express-serve-static-core@4.19.8':
- resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==}
-
- '@types/express@4.17.25':
- resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==}
-
- '@types/http-errors@2.0.5':
- resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
-
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@types/methods@1.1.4':
resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==}
- '@types/mime@1.3.5':
- resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
-
'@types/node@20.19.39':
resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==}
'@types/pg@8.20.0':
resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==}
- '@types/qs@6.15.0':
- resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==}
-
- '@types/range-parser@1.2.7':
- resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
-
'@types/semver@7.7.1':
resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==}
- '@types/send@0.17.6':
- resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==}
-
- '@types/send@1.2.1':
- resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==}
-
- '@types/serve-static@1.15.10':
- resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==}
-
'@types/superagent@8.1.9':
resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==}
@@ -1258,9 +1487,18 @@ packages:
'@xtuc/long@4.2.2':
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
- accepts@1.3.8:
- resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
- engines: {node: '>= 0.6'}
+ abort-controller@3.0.0:
+ resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
+ engines: {node: '>=6.5'}
+
+ abstract-logging@2.0.1:
+ resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
+
+ acorn-import-phases@1.0.4:
+ resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==}
+ engines: {node: '>=10.13.0'}
+ peerDependencies:
+ acorn: ^8.14.0
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
@@ -1284,6 +1522,14 @@ packages:
ajv:
optional: true
+ ajv-formats@3.0.1:
+ resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+
ajv-keywords@3.5.2:
resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
peerDependencies:
@@ -1297,9 +1543,6 @@ packages:
ajv@6.14.0:
resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
- ajv@8.12.0:
- resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
-
ajv@8.18.0:
resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
@@ -1307,10 +1550,6 @@ packages:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
- ansi-escapes@4.3.2:
- resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
- engines: {node: '>=8'}
-
ansi-escapes@7.3.0:
resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==}
engines: {node: '>=18'}
@@ -1331,12 +1570,9 @@ packages:
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
engines: {node: '>=12'}
- anymatch@3.1.3:
- resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
- engines: {node: '>= 8'}
-
- append-field@1.0.0:
- resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==}
+ ansis@4.2.0:
+ resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==}
+ engines: {node: '>=14'}
arg@4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
@@ -1344,9 +1580,6 @@ packages:
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
- array-flatten@1.1.1:
- resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
-
array-timsort@1.0.3:
resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==}
@@ -1367,9 +1600,20 @@ packages:
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+ atomic-sleep@1.0.0:
+ resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
+ engines: {node: '>=8.0.0'}
+
+ avvio@9.2.0:
+ resolution: {integrity: sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==}
+
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ balanced-match@4.0.4:
+ resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
+ engines: {node: 18 || 20 || >=22}
+
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
@@ -1378,23 +1622,19 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
- binary-extensions@2.3.0:
- resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
- engines: {node: '>=8'}
-
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
- body-parser@1.20.4:
- resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==}
- engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
-
brace-expansion@1.1.13:
resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==}
brace-expansion@2.0.3:
resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==}
+ brace-expansion@5.0.5:
+ resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==}
+ engines: {node: 18 || 20 || >=22}
+
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
@@ -1410,22 +1650,13 @@ packages:
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
- busboy@1.6.0:
- resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
- engines: {node: '>=10.16.0'}
-
- bytes@3.1.2:
- resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
- engines: {node: '>= 0.8'}
+ buffer@6.0.3:
+ resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
- call-bind@1.0.9:
- resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==}
- engines: {node: '>= 0.4'}
-
call-bound@1.0.4:
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
engines: {node: '>= 0.4'}
@@ -1445,16 +1676,12 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
- chalk@5.6.2:
- resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
- engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
-
- chardet@0.7.0:
- resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
+ chardet@2.1.1:
+ resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==}
- chokidar@3.6.0:
- resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
- engines: {node: '>= 8.10.0'}
+ chokidar@4.0.3:
+ resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
+ engines: {node: '>= 14.16.0'}
chrome-trace-event@1.0.4:
resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==}
@@ -1480,10 +1707,6 @@ packages:
resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
engines: {node: '>=20'}
- cli-width@3.0.0:
- resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
- engines: {node: '>= 10'}
-
cli-width@4.1.0:
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
engines: {node: '>= 12'}
@@ -1517,8 +1740,8 @@ packages:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
- comment-json@4.2.5:
- resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==}
+ comment-json@4.6.2:
+ resolution: {integrity: sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==}
engines: {node: '>= 6'}
component-emitter@1.3.1:
@@ -1527,30 +1750,20 @@ packages:
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
- concat-stream@2.0.0:
- resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
- engines: {'0': node >= 6.0}
+ consola@3.4.2:
+ resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
+ engines: {node: ^14.18.0 || >=16.10.0}
- consola@2.15.3:
- resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==}
-
- content-disposition@0.5.4:
- resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
- engines: {node: '>= 0.6'}
-
- content-type@1.0.5:
- resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
- engines: {node: '>= 0.6'}
+ content-disposition@1.1.0:
+ resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==}
+ engines: {node: '>=18'}
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
- cookie-signature@1.0.7:
- resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==}
-
- cookie@0.7.2:
- resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
- engines: {node: '>= 0.6'}
+ cookie@1.1.1:
+ resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
+ engines: {node: '>=18'}
cookiejar@2.1.4:
resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==}
@@ -1558,10 +1771,6 @@ packages:
core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
- cors@2.8.5:
- resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
- engines: {node: '>= 0.10'}
-
cosmiconfig@8.3.6:
resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
engines: {node: '>=14'}
@@ -1578,14 +1787,6 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
- debug@2.6.9:
- resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
- peerDependencies:
- supports-color: '*'
- peerDependenciesMeta:
- supports-color:
- optional: true
-
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@@ -1605,10 +1806,6 @@ packages:
defaults@1.0.4:
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
- define-data-property@1.1.4:
- resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
- engines: {node: '>= 0.4'}
-
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
@@ -1617,9 +1814,9 @@ packages:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
- destroy@1.2.0:
- resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
- engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
@@ -1758,11 +1955,11 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
- eastasianwidth@0.2.0:
- resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+ duplexify@3.7.1:
+ resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==}
- ee-first@1.1.1:
- resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
+ duplexify@4.1.3:
+ resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==}
electron-to-chromium@1.5.334:
resolution: {integrity: sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==}
@@ -1773,12 +1970,8 @@ packages:
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
- emoji-regex@9.2.2:
- resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
-
- encodeurl@2.0.0:
- resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
- engines: {node: '>= 0.8'}
+ end-of-stream@1.4.5:
+ resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
enhanced-resolve@5.20.1:
resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
@@ -1799,9 +1992,6 @@ packages:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
- es-module-lexer@1.7.0:
- resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
-
es-module-lexer@2.0.0:
resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==}
@@ -1835,10 +2025,6 @@ packages:
escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
- escape-string-regexp@1.0.5:
- resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
- engines: {node: '>=0.8.0'}
-
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
@@ -1916,9 +2102,9 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
- etag@1.8.1:
- resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
- engines: {node: '>= 0.6'}
+ event-target-shim@5.0.1:
+ resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
+ engines: {node: '>=6'}
eventemitter3@5.0.4:
resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
@@ -1931,13 +2117,8 @@ packages:
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
engines: {node: '>=12.0.0'}
- express@4.22.1:
- resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==}
- engines: {node: '>= 0.10.0'}
-
- external-editor@3.1.0:
- resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
- engines: {node: '>=4'}
+ fast-decode-uri-component@1.0.1:
+ resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -1952,15 +2133,27 @@ packages:
fast-json-stable-stringify@2.1.0:
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+ fast-json-stringify@6.3.0:
+ resolution: {integrity: sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA==}
+
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+ fast-querystring@1.1.2:
+ resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==}
+
fast-safe-stringify@2.1.1:
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
fast-uri@3.1.0:
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
+ fastify-plugin@5.1.0:
+ resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==}
+
+ fastify@5.8.4:
+ resolution: {integrity: sha512-sa42J1xylbBAYUWALSBoyXKPDUvM3OoNOibIefA+Oha57FryXKKCZarA1iDntOCWp3O35voZLuDg2mdODXtPzQ==}
+
fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
@@ -1973,28 +2166,21 @@ packages:
picomatch:
optional: true
- fflate@0.8.2:
- resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
-
- figures@3.2.0:
- resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==}
- engines: {node: '>=8'}
-
file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
- file-type@20.4.1:
- resolution: {integrity: sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==}
- engines: {node: '>=18'}
+ file-type@21.3.4:
+ resolution: {integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==}
+ engines: {node: '>=20'}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
- finalhandler@1.3.2:
- resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==}
- engines: {node: '>= 0.8'}
+ find-my-way@9.5.0:
+ resolution: {integrity: sha512-VW2RfnmscZO5KgBY5XVyKREMW5nMZcxDy+buTOsL+zIPnBlbKm+00sgzoQzq1EVh4aALZLfKdwv6atBGcjvjrQ==}
+ engines: {node: '>=20'}
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
@@ -2007,13 +2193,9 @@ packages:
flatted@3.4.2:
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
- foreground-child@3.3.1:
- resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
- engines: {node: '>=14'}
-
- fork-ts-checker-webpack-plugin@9.0.2:
- resolution: {integrity: sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==}
- engines: {node: '>=12.13.0', yarn: '>=1.0.0'}
+ fork-ts-checker-webpack-plugin@9.1.0:
+ resolution: {integrity: sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==}
+ engines: {node: '>=14.21.3'}
peerDependencies:
typescript: '>3.6.0'
webpack: ^5.11.0
@@ -2025,14 +2207,6 @@ packages:
formidable@2.1.5:
resolution: {integrity: sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==}
- forwarded@0.2.0:
- resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
- engines: {node: '>= 0.6'}
-
- fresh@0.5.2:
- resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
- engines: {node: '>= 0.6'}
-
fs-extra@10.1.0:
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
engines: {node: '>=12'}
@@ -2077,10 +2251,9 @@ packages:
glob-to-regexp@0.4.1:
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
- glob@10.4.5:
- resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
- deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
- hasBin: true
+ glob@13.0.6:
+ resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
+ engines: {node: 18 || 20 || >=22}
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
@@ -2108,13 +2281,6 @@ packages:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
- has-own-prop@2.0.0:
- resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==}
- engines: {node: '>=8'}
-
- has-property-descriptors@1.0.2:
- resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
-
has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
@@ -2134,8 +2300,8 @@ packages:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
- iconv-lite@0.4.24:
- resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
+ iconv-lite@0.7.2:
+ resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
engines: {node: '>=0.10.0'}
ieee754@1.2.1:
@@ -2160,25 +2326,13 @@ packages:
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
- inquirer@8.2.6:
- resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==}
- engines: {node: '>=12.0.0'}
-
- inquirer@9.2.15:
- resolution: {integrity: sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==}
- engines: {node: '>=18'}
-
- ipaddr.js@1.9.1:
- resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
- engines: {node: '>= 0.10'}
+ ipaddr.js@2.3.0:
+ resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==}
+ engines: {node: '>= 10'}
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
- is-binary-path@2.1.0:
- resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
- engines: {node: '>=8'}
-
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@@ -2211,6 +2365,9 @@ packages:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
+ isarray@1.0.0:
+ resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
+
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@@ -2230,9 +2387,6 @@ packages:
resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==}
engines: {node: '>=6'}
- jackspeak@3.4.3:
- resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
-
jest-worker@27.5.1:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
engines: {node: '>= 10.13.0'}
@@ -2253,6 +2407,9 @@ packages:
json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+ json-schema-ref-resolver@3.0.0:
+ resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==}
+
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
@@ -2267,9 +2424,6 @@ packages:
engines: {node: '>=6'}
hasBin: true
- jsonc-parser@3.2.1:
- resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==}
-
jsonc-parser@3.3.1:
resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
@@ -2283,6 +2437,9 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
+ light-my-request@6.6.0:
+ resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==}
+
lightningcss-android-arm64@1.32.0:
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
engines: {node: '>= 12.0.0'}
@@ -2369,6 +2526,10 @@ packages:
resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==}
engines: {node: '>=20.0.0'}
+ load-esm@1.0.3:
+ resolution: {integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==}
+ engines: {node: '>=13.2.0'}
+
load-tsconfig@0.2.5:
resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -2395,16 +2556,16 @@ packages:
resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
engines: {node: '>=18'}
- lru-cache@10.4.3:
- resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+ lru-cache@11.3.3:
+ resolution: {integrity: sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==}
+ engines: {node: 20 || >=22}
+
+ magic-string@0.30.17:
+ resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
- magic-string@0.30.8:
- resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==}
- engines: {node: '>=12'}
-
magicast@0.5.2:
resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==}
@@ -2419,17 +2580,10 @@ packages:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
- media-typer@0.3.0:
- resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
- engines: {node: '>= 0.6'}
-
memfs@3.5.3:
resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==}
engines: {node: '>= 4.0.0'}
- merge-descriptors@1.0.3:
- resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
-
merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
@@ -2453,16 +2607,16 @@ packages:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
- mime@1.6.0:
- resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
- engines: {node: '>=4'}
- hasBin: true
-
mime@2.6.0:
resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
engines: {node: '>=4.0.0'}
hasBin: true
+ mime@3.0.0:
+ resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+
mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
@@ -2471,6 +2625,10 @@ packages:
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
engines: {node: '>=18'}
+ minimatch@10.2.5:
+ resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
+ engines: {node: 18 || 20 || >=22}
+
minimatch@3.1.5:
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
@@ -2478,10 +2636,6 @@ packages:
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
engines: {node: '>=16 || 14 >=14.17'}
- minimatch@9.0.9:
- resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
- engines: {node: '>=16 || 14 >=14.17'}
-
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
@@ -2489,26 +2643,12 @@ packages:
resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
engines: {node: '>=16 || 14 >=14.17'}
- mkdirp@0.5.6:
- resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
- hasBin: true
-
- ms@2.0.0:
- resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
-
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
- multer@2.0.2:
- resolution: {integrity: sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==}
- engines: {node: '>= 10.16.0'}
-
- mute-stream@0.0.8:
- resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
-
- mute-stream@1.0.0:
- resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==}
- engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ mute-stream@2.0.0:
+ resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
+ engines: {node: ^18.17.0 || >=20.5.0}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
@@ -2518,39 +2658,29 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
- negotiator@0.6.3:
- resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
- engines: {node: '>= 0.6'}
-
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
+ nestjs-zod@5.3.0:
+ resolution: {integrity: sha512-QY6imXm9heMOpWigjFHgMWPvc1ZQHeNQ7pdogo9Q5xj5F8HpqZ972vKlVdkaTyzYlOXJP/yVy3wlF1EjubDQPg==}
+ peerDependencies:
+ '@nestjs/common': ^10.0.0 || ^11.0.0
+ '@nestjs/swagger': ^7.4.2 || ^8.0.0 || ^11.0.0
+ rxjs: ^7.0.0
+ zod: ^3.25.0 || ^4.0.0
+ peerDependenciesMeta:
+ '@nestjs/swagger':
+ optional: true
+
node-abort-controller@3.1.1:
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
node-emoji@1.11.0:
resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==}
- node-fetch@2.7.0:
- resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
- engines: {node: 4.x || >=6.0.0}
- peerDependencies:
- encoding: ^0.1.0
- peerDependenciesMeta:
- encoding:
- optional: true
-
node-releases@2.0.37:
resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==}
- normalize-path@3.0.0:
- resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
- engines: {node: '>=0.10.0'}
-
- object-assign@4.1.1:
- resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
- engines: {node: '>=0.10.0'}
-
object-inspect@1.13.4:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'}
@@ -2558,9 +2688,9 @@ packages:
obug@2.1.1:
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
- on-finished@2.4.1:
- resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
- engines: {node: '>= 0.8'}
+ on-exit-leak-free@2.1.2:
+ resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
+ engines: {node: '>=14.0.0'}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -2581,10 +2711,6 @@ packages:
resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==}
engines: {node: '>=10'}
- os-tmpdir@1.0.2:
- resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
- engines: {node: '>=0.10.0'}
-
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
@@ -2593,9 +2719,6 @@ packages:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
- package-json-from-dist@1.0.1:
- resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
-
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -2604,10 +2727,6 @@ packages:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
- parseurl@1.3.3:
- resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
- engines: {node: '>= 0.8'}
-
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -2620,15 +2739,12 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
- path-scurry@1.11.1:
- resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
- engines: {node: '>=16 || 14 >=14.18'}
+ path-scurry@2.0.2:
+ resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
+ engines: {node: 18 || 20 || >=22}
- path-to-regexp@0.1.13:
- resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==}
-
- path-to-regexp@3.3.0:
- resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==}
+ path-to-regexp@8.4.2:
+ resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
@@ -2637,6 +2753,9 @@ packages:
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+ peek-stream@1.1.3:
+ resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==}
+
pg-cloudflare@1.3.0:
resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==}
@@ -2678,14 +2797,20 @@ packages:
resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
engines: {node: '>=8.6'}
- picomatch@4.0.1:
- resolution: {integrity: sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==}
- engines: {node: '>=12'}
-
picomatch@4.0.4:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
+ pino-abstract-transport@3.0.0:
+ resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==}
+
+ pino-std-serializers@7.1.0:
+ resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==}
+
+ pino@10.3.1:
+ resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==}
+ hasBin: true
+
pluralize@8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
@@ -2723,18 +2848,29 @@ packages:
engines: {node: '>=14'}
hasBin: true
- proxy-addr@2.0.7:
- resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
- engines: {node: '>= 0.10'}
+ process-nextick-args@2.0.1:
+ resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
+
+ process-warning@4.0.1:
+ resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==}
+
+ process-warning@5.0.0:
+ resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
+
+ process@0.11.10:
+ resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
+ engines: {node: '>= 0.6.0'}
+
+ pump@3.0.4:
+ resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
+
+ pumpify@2.0.1:
+ resolution: {integrity: sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
- qs@6.14.2:
- resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==}
- engines: {node: '>=0.6'}
-
qs@6.15.1:
resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==}
engines: {node: '>=0.6'}
@@ -2742,29 +2878,31 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
- range-parser@1.2.1:
- resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
- engines: {node: '>= 0.6'}
+ quick-format-unescaped@4.0.4:
+ resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
- raw-body@2.5.3:
- resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==}
- engines: {node: '>= 0.8'}
+ readable-stream@2.3.8:
+ resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
- readdirp@3.6.0:
- resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
- engines: {node: '>=8.10.0'}
+ readable-stream@4.7.0:
+ resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ readdirp@4.1.2:
+ resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
+ engines: {node: '>= 14.18.0'}
+
+ real-require@0.2.0:
+ resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
+ engines: {node: '>= 12.13.0'}
reflect-metadata@0.2.2:
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
- repeat-string@1.6.1:
- resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
- engines: {node: '>=0.10'}
-
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
@@ -2784,6 +2922,10 @@ packages:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
+ ret@0.5.0:
+ resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==}
+ engines: {node: '>=10'}
+
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@@ -2801,14 +2943,6 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
- run-async@2.4.1:
- resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
- engines: {node: '>=0.12.0'}
-
- run-async@3.0.0:
- resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==}
- engines: {node: '>=0.12.0'}
-
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -2818,9 +2952,20 @@ packages:
rxjs@7.8.2:
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
+ safe-buffer@5.1.2:
+ resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
+
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+ safe-regex2@5.1.0:
+ resolution: {integrity: sha512-pNHAuBW7TrcleFHsxBr5QMi/Iyp0ENjUKz7GCcX1UO7cMh+NmVK6HxQckNL1tJp1XAJVjG6B8OKIPqodqj9rtw==}
+ hasBin: true
+
+ safe-stable-stringify@2.5.0:
+ resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
+ engines: {node: '>=10'}
+
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
@@ -2832,22 +2977,16 @@ packages:
resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==}
engines: {node: '>= 10.13.0'}
+ secure-json-parse@4.1.0:
+ resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==}
+
semver@7.7.4:
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
engines: {node: '>=10'}
hasBin: true
- send@0.19.2:
- resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==}
- engines: {node: '>= 0.8.0'}
-
- serve-static@1.16.3:
- resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==}
- engines: {node: '>= 0.8.0'}
-
- set-function-length@1.2.2:
- resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
- engines: {node: '>= 0.4'}
+ set-cookie-parser@2.7.2:
+ resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
@@ -2898,6 +3037,9 @@ packages:
resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
engines: {node: '>=20'}
+ sonic-boom@4.2.1:
+ resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==}
+
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -2931,9 +3073,8 @@ packages:
std-env@4.0.0:
resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==}
- streamsearch@1.1.0:
- resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
- engines: {node: '>=10.0.0'}
+ stream-shift@1.0.3:
+ resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
string-argv@0.3.2:
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
@@ -2943,10 +3084,6 @@ packages:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
- string-width@5.1.2:
- resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
- engines: {node: '>=12'}
-
string-width@7.2.0:
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
engines: {node: '>=18'}
@@ -2955,6 +3092,9 @@ packages:
resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==}
engines: {node: '>=20'}
+ string_decoder@1.1.1:
+ resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
+
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
@@ -2996,6 +3136,9 @@ packages:
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
engines: {node: '>=10'}
+ swagger-ui-dist@5.32.2:
+ resolution: {integrity: sha512-t6Ns52nS8LU2hqi0+rezMjFO1ZrCsCrnommXrU7Nfrg2va2dWahdvM6TuSwzdHpG29v6BHJyU1c/UWFhgVZzVQ==}
+
symbol-observable@4.0.0:
resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==}
engines: {node: '>=0.10'}
@@ -3032,8 +3175,12 @@ packages:
text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
- through@2.3.8:
- resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
+ thread-stream@4.0.0:
+ resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==}
+ engines: {node: '>=20'}
+
+ through2@2.0.5:
+ resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
@@ -3050,14 +3197,14 @@ packages:
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
engines: {node: '>=14.0.0'}
- tmp@0.0.33:
- resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
- engines: {node: '>=0.6.0'}
-
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
+ toad-cache@3.7.0:
+ resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==}
+ engines: {node: '>=12'}
+
toidentifier@1.0.1:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
@@ -3066,13 +3213,6 @@ packages:
resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==}
engines: {node: '>=14.16'}
- tr46@0.0.3:
- resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
-
- tree-kill@1.2.2:
- resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
- hasBin: true
-
ts-api-utils@1.4.3:
resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==}
engines: {node: '>=16'}
@@ -3124,22 +3264,6 @@ packages:
resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
engines: {node: '>=10'}
- type-fest@0.21.3:
- resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
- engines: {node: '>=10'}
-
- type-is@1.6.18:
- resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
- engines: {node: '>= 0.6'}
-
- typedarray@0.0.6:
- resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
-
- typescript@5.7.2:
- resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==}
- engines: {node: '>=14.17'}
- hasBin: true
-
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
@@ -3160,10 +3284,6 @@ packages:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
- unpipe@1.0.0:
- resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
- engines: {node: '>= 0.8'}
-
unplugin-swc@1.5.9:
resolution: {integrity: sha512-RKwK3yf0M+MN17xZfF14bdKqfx0zMXYdtOdxLiE6jHAoidupKq3jGdJYANyIM1X/VmABhh1WpdO+/f4+Ol89+g==}
peerDependencies:
@@ -3185,17 +3305,9 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
- utils-merge@1.0.1:
- resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
- engines: {node: '>= 0.4.0'}
-
v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
- vary@1.1.2:
- resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
- engines: {node: '>= 0.8'}
-
vite@8.0.8:
resolution: {integrity: sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -3287,9 +3399,6 @@ packages:
wcwidth@1.0.1:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
- webidl-conversions@3.0.1:
- resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
-
webpack-node-externals@3.0.0:
resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==}
engines: {node: '>=6'}
@@ -3301,8 +3410,8 @@ packages:
webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
- webpack@5.97.1:
- resolution: {integrity: sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==}
+ webpack@5.106.0:
+ resolution: {integrity: sha512-Pkx5joZ9RrdgO5LBkyX1L2ZAJeK/Taz3vqZ9CbcP0wS5LEMx5QkKsEwLl29QJfihZ+DKRBFldzy1O30pJ1MDpA==}
engines: {node: '>=10.13.0'}
hasBin: true
peerDependencies:
@@ -3311,9 +3420,6 @@ packages:
webpack-cli:
optional: true
- whatwg-url@5.0.0:
- resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
-
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@@ -3332,14 +3438,6 @@ packages:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
- wrap-ansi@7.0.0:
- resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
- engines: {node: '>=10'}
-
- wrap-ansi@8.1.0:
- resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
- engines: {node: '>=12'}
-
wrap-ansi@9.0.2:
resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
engines: {node: '>=18'}
@@ -3368,38 +3466,64 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
+ yoctocolors-cjs@2.1.3:
+ resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==}
+ engines: {node: '>=18'}
+
zod@4.3.6:
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
snapshots:
- '@angular-devkit/core@17.3.11(chokidar@3.6.0)':
+ '@angular-devkit/core@19.2.23(chokidar@4.0.3)':
dependencies:
- ajv: 8.12.0
- ajv-formats: 2.1.1(ajv@8.12.0)
- jsonc-parser: 3.2.1
- picomatch: 4.0.1
+ ajv: 8.18.0
+ ajv-formats: 3.0.1(ajv@8.18.0)
+ jsonc-parser: 3.3.1
+ picomatch: 4.0.4
rxjs: 7.8.1
source-map: 0.7.4
optionalDependencies:
- chokidar: 3.6.0
+ chokidar: 4.0.3
- '@angular-devkit/schematics-cli@17.3.11(chokidar@3.6.0)':
+ '@angular-devkit/core@19.2.24(chokidar@4.0.3)':
dependencies:
- '@angular-devkit/core': 17.3.11(chokidar@3.6.0)
- '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0)
+ ajv: 8.18.0
+ ajv-formats: 3.0.1(ajv@8.18.0)
+ jsonc-parser: 3.3.1
+ picomatch: 4.0.4
+ rxjs: 7.8.1
+ source-map: 0.7.4
+ optionalDependencies:
+ chokidar: 4.0.3
+
+ '@angular-devkit/schematics-cli@19.2.24(@types/node@20.19.39)(chokidar@4.0.3)':
+ dependencies:
+ '@angular-devkit/core': 19.2.24(chokidar@4.0.3)
+ '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3)
+ '@inquirer/prompts': 7.3.2(@types/node@20.19.39)
ansi-colors: 4.1.3
- inquirer: 9.2.15
symbol-observable: 4.0.0
yargs-parser: 21.1.1
+ transitivePeerDependencies:
+ - '@types/node'
+ - chokidar
+
+ '@angular-devkit/schematics@19.2.23(chokidar@4.0.3)':
+ dependencies:
+ '@angular-devkit/core': 19.2.23(chokidar@4.0.3)
+ jsonc-parser: 3.3.1
+ magic-string: 0.30.17
+ ora: 5.4.1
+ rxjs: 7.8.1
transitivePeerDependencies:
- chokidar
- '@angular-devkit/schematics@17.3.11(chokidar@3.6.0)':
+ '@angular-devkit/schematics@19.2.24(chokidar@4.0.3)':
dependencies:
- '@angular-devkit/core': 17.3.11(chokidar@3.6.0)
- jsonc-parser: 3.2.1
- magic-string: 0.30.8
+ '@angular-devkit/core': 19.2.24(chokidar@4.0.3)
+ jsonc-parser: 3.3.1
+ magic-string: 0.30.17
ora: 5.4.1
rxjs: 7.8.1
transitivePeerDependencies:
@@ -3708,6 +3832,74 @@ snapshots:
'@eslint/js@8.57.1': {}
+ '@fastify/accept-negotiator@2.0.1': {}
+
+ '@fastify/ajv-compiler@4.0.5':
+ dependencies:
+ ajv: 8.18.0
+ ajv-formats: 3.0.1(ajv@8.18.0)
+ fast-uri: 3.1.0
+
+ '@fastify/compress@8.3.1':
+ dependencies:
+ '@fastify/accept-negotiator': 2.0.1
+ fastify-plugin: 5.1.0
+ mime-db: 1.52.0
+ minipass: 7.1.3
+ peek-stream: 1.1.3
+ pump: 3.0.4
+ pumpify: 2.0.1
+ readable-stream: 4.7.0
+
+ '@fastify/cookie@11.0.2':
+ dependencies:
+ cookie: 1.1.1
+ fastify-plugin: 5.1.0
+
+ '@fastify/cors@11.2.0':
+ dependencies:
+ fastify-plugin: 5.1.0
+ toad-cache: 3.7.0
+
+ '@fastify/error@4.2.0': {}
+
+ '@fastify/fast-json-stringify-compiler@5.0.3':
+ dependencies:
+ fast-json-stringify: 6.3.0
+
+ '@fastify/formbody@8.0.2':
+ dependencies:
+ fast-querystring: 1.1.2
+ fastify-plugin: 5.1.0
+
+ '@fastify/forwarded@3.0.1': {}
+
+ '@fastify/merge-json-schemas@0.2.1':
+ dependencies:
+ dequal: 2.0.3
+
+ '@fastify/proxy-addr@5.1.0':
+ dependencies:
+ '@fastify/forwarded': 3.0.1
+ ipaddr.js: 2.3.0
+
+ '@fastify/send@4.1.0':
+ dependencies:
+ '@lukeed/ms': 2.0.2
+ escape-html: 1.0.3
+ fast-decode-uri-component: 1.0.1
+ http-errors: 2.0.1
+ mime: 3.0.0
+
+ '@fastify/static@9.1.0':
+ dependencies:
+ '@fastify/accept-negotiator': 2.0.1
+ '@fastify/send': 4.1.0
+ content-disposition: 1.1.0
+ fastify-plugin: 5.1.0
+ fastq: 1.20.1
+ glob: 13.0.6
+
'@humanwhocodes/config-array@0.13.0':
dependencies:
'@humanwhocodes/object-schema': 2.0.3
@@ -3720,14 +3912,145 @@ snapshots:
'@humanwhocodes/object-schema@2.0.3': {}
- '@isaacs/cliui@8.0.2':
+ '@inquirer/ansi@1.0.2': {}
+
+ '@inquirer/checkbox@4.3.2(@types/node@20.19.39)':
dependencies:
- string-width: 5.1.2
- string-width-cjs: string-width@4.2.3
- strip-ansi: 7.2.0
- strip-ansi-cjs: strip-ansi@6.0.1
- wrap-ansi: 8.1.0
- wrap-ansi-cjs: wrap-ansi@7.0.0
+ '@inquirer/ansi': 1.0.2
+ '@inquirer/core': 10.3.2(@types/node@20.19.39)
+ '@inquirer/figures': 1.0.15
+ '@inquirer/type': 3.0.10(@types/node@20.19.39)
+ yoctocolors-cjs: 2.1.3
+ optionalDependencies:
+ '@types/node': 20.19.39
+
+ '@inquirer/confirm@5.1.21(@types/node@20.19.39)':
+ dependencies:
+ '@inquirer/core': 10.3.2(@types/node@20.19.39)
+ '@inquirer/type': 3.0.10(@types/node@20.19.39)
+ optionalDependencies:
+ '@types/node': 20.19.39
+
+ '@inquirer/core@10.3.2(@types/node@20.19.39)':
+ dependencies:
+ '@inquirer/ansi': 1.0.2
+ '@inquirer/figures': 1.0.15
+ '@inquirer/type': 3.0.10(@types/node@20.19.39)
+ cli-width: 4.1.0
+ mute-stream: 2.0.0
+ signal-exit: 4.1.0
+ wrap-ansi: 6.2.0
+ yoctocolors-cjs: 2.1.3
+ optionalDependencies:
+ '@types/node': 20.19.39
+
+ '@inquirer/editor@4.2.23(@types/node@20.19.39)':
+ dependencies:
+ '@inquirer/core': 10.3.2(@types/node@20.19.39)
+ '@inquirer/external-editor': 1.0.3(@types/node@20.19.39)
+ '@inquirer/type': 3.0.10(@types/node@20.19.39)
+ optionalDependencies:
+ '@types/node': 20.19.39
+
+ '@inquirer/expand@4.0.23(@types/node@20.19.39)':
+ dependencies:
+ '@inquirer/core': 10.3.2(@types/node@20.19.39)
+ '@inquirer/type': 3.0.10(@types/node@20.19.39)
+ yoctocolors-cjs: 2.1.3
+ optionalDependencies:
+ '@types/node': 20.19.39
+
+ '@inquirer/external-editor@1.0.3(@types/node@20.19.39)':
+ dependencies:
+ chardet: 2.1.1
+ iconv-lite: 0.7.2
+ optionalDependencies:
+ '@types/node': 20.19.39
+
+ '@inquirer/figures@1.0.15': {}
+
+ '@inquirer/input@4.3.1(@types/node@20.19.39)':
+ dependencies:
+ '@inquirer/core': 10.3.2(@types/node@20.19.39)
+ '@inquirer/type': 3.0.10(@types/node@20.19.39)
+ optionalDependencies:
+ '@types/node': 20.19.39
+
+ '@inquirer/number@3.0.23(@types/node@20.19.39)':
+ dependencies:
+ '@inquirer/core': 10.3.2(@types/node@20.19.39)
+ '@inquirer/type': 3.0.10(@types/node@20.19.39)
+ optionalDependencies:
+ '@types/node': 20.19.39
+
+ '@inquirer/password@4.0.23(@types/node@20.19.39)':
+ dependencies:
+ '@inquirer/ansi': 1.0.2
+ '@inquirer/core': 10.3.2(@types/node@20.19.39)
+ '@inquirer/type': 3.0.10(@types/node@20.19.39)
+ optionalDependencies:
+ '@types/node': 20.19.39
+
+ '@inquirer/prompts@7.10.1(@types/node@20.19.39)':
+ dependencies:
+ '@inquirer/checkbox': 4.3.2(@types/node@20.19.39)
+ '@inquirer/confirm': 5.1.21(@types/node@20.19.39)
+ '@inquirer/editor': 4.2.23(@types/node@20.19.39)
+ '@inquirer/expand': 4.0.23(@types/node@20.19.39)
+ '@inquirer/input': 4.3.1(@types/node@20.19.39)
+ '@inquirer/number': 3.0.23(@types/node@20.19.39)
+ '@inquirer/password': 4.0.23(@types/node@20.19.39)
+ '@inquirer/rawlist': 4.1.11(@types/node@20.19.39)
+ '@inquirer/search': 3.2.2(@types/node@20.19.39)
+ '@inquirer/select': 4.4.2(@types/node@20.19.39)
+ optionalDependencies:
+ '@types/node': 20.19.39
+
+ '@inquirer/prompts@7.3.2(@types/node@20.19.39)':
+ dependencies:
+ '@inquirer/checkbox': 4.3.2(@types/node@20.19.39)
+ '@inquirer/confirm': 5.1.21(@types/node@20.19.39)
+ '@inquirer/editor': 4.2.23(@types/node@20.19.39)
+ '@inquirer/expand': 4.0.23(@types/node@20.19.39)
+ '@inquirer/input': 4.3.1(@types/node@20.19.39)
+ '@inquirer/number': 3.0.23(@types/node@20.19.39)
+ '@inquirer/password': 4.0.23(@types/node@20.19.39)
+ '@inquirer/rawlist': 4.1.11(@types/node@20.19.39)
+ '@inquirer/search': 3.2.2(@types/node@20.19.39)
+ '@inquirer/select': 4.4.2(@types/node@20.19.39)
+ optionalDependencies:
+ '@types/node': 20.19.39
+
+ '@inquirer/rawlist@4.1.11(@types/node@20.19.39)':
+ dependencies:
+ '@inquirer/core': 10.3.2(@types/node@20.19.39)
+ '@inquirer/type': 3.0.10(@types/node@20.19.39)
+ yoctocolors-cjs: 2.1.3
+ optionalDependencies:
+ '@types/node': 20.19.39
+
+ '@inquirer/search@3.2.2(@types/node@20.19.39)':
+ dependencies:
+ '@inquirer/core': 10.3.2(@types/node@20.19.39)
+ '@inquirer/figures': 1.0.15
+ '@inquirer/type': 3.0.10(@types/node@20.19.39)
+ yoctocolors-cjs: 2.1.3
+ optionalDependencies:
+ '@types/node': 20.19.39
+
+ '@inquirer/select@4.4.2(@types/node@20.19.39)':
+ dependencies:
+ '@inquirer/ansi': 1.0.2
+ '@inquirer/core': 10.3.2(@types/node@20.19.39)
+ '@inquirer/figures': 1.0.15
+ '@inquirer/type': 3.0.10(@types/node@20.19.39)
+ yoctocolors-cjs: 2.1.3
+ optionalDependencies:
+ '@types/node': 20.19.39
+
+ '@inquirer/type@3.0.10(@types/node@20.19.39)':
+ optionalDependencies:
+ '@types/node': 20.19.39
'@jridgewell/gen-mapping@0.3.13':
dependencies:
@@ -3758,12 +4081,12 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
- '@ljharb/through@2.3.14':
- dependencies:
- call-bind: 1.0.9
-
'@lukeed/csprng@1.1.0': {}
+ '@lukeed/ms@2.0.2': {}
+
+ '@microsoft/tsdoc@0.16.0': {}
+
'@napi-rs/wasm-runtime@1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)':
dependencies:
'@emnapi/core': 1.9.2
@@ -3771,38 +4094,39 @@ snapshots:
'@tybys/wasm-util': 0.10.1
optional: true
- '@nestjs/cli@10.4.9(@swc/core@1.15.24)(esbuild@0.27.7)':
+ '@nestjs/cli@11.0.19(@swc/core@1.15.24)(@types/node@20.19.39)(esbuild@0.27.7)':
dependencies:
- '@angular-devkit/core': 17.3.11(chokidar@3.6.0)
- '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0)
- '@angular-devkit/schematics-cli': 17.3.11(chokidar@3.6.0)
- '@nestjs/schematics': 10.2.3(chokidar@3.6.0)(typescript@5.7.2)
- chalk: 4.1.2
- chokidar: 3.6.0
+ '@angular-devkit/core': 19.2.24(chokidar@4.0.3)
+ '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3)
+ '@angular-devkit/schematics-cli': 19.2.24(@types/node@20.19.39)(chokidar@4.0.3)
+ '@inquirer/prompts': 7.10.1(@types/node@20.19.39)
+ '@nestjs/schematics': 11.0.10(chokidar@4.0.3)(typescript@5.9.3)
+ ansis: 4.2.0
+ chokidar: 4.0.3
cli-table3: 0.6.5
commander: 4.1.1
- fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.15.24)(esbuild@0.27.7))
- glob: 10.4.5
- inquirer: 8.2.6
+ fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.24)(esbuild@0.27.7))
+ glob: 13.0.6
node-emoji: 1.11.0
ora: 5.4.1
- tree-kill: 1.2.2
tsconfig-paths: 4.2.0
tsconfig-paths-webpack-plugin: 4.2.0
- typescript: 5.7.2
- webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.27.7)
+ typescript: 5.9.3
+ webpack: 5.106.0(@swc/core@1.15.24)(esbuild@0.27.7)
webpack-node-externals: 3.0.0
optionalDependencies:
'@swc/core': 1.15.24
transitivePeerDependencies:
+ - '@types/node'
- esbuild
- uglify-js
- webpack-cli
- '@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2)':
+ '@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)':
dependencies:
- file-type: 20.4.1
+ file-type: 21.3.4
iterare: 1.2.1
+ load-esm: 1.0.3
reflect-metadata: 0.2.2
rxjs: 7.8.2
tslib: 2.8.1
@@ -3810,71 +4134,84 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@nestjs/config@4.0.4(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)':
+ '@nestjs/config@4.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)':
dependencies:
- '@nestjs/common': 10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
dotenv: 17.4.1
dotenv-expand: 12.0.3
lodash: 4.18.1
rxjs: 7.8.2
- '@nestjs/core@10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2)':
+ '@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)':
dependencies:
- '@nestjs/common': 10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2)
- '@nuxtjs/opencollective': 0.3.2
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nuxt/opencollective': 0.4.1
fast-safe-stringify: 2.1.1
iterare: 1.2.1
- path-to-regexp: 3.3.0
+ path-to-regexp: 8.4.2
reflect-metadata: 0.2.2
rxjs: 7.8.2
tslib: 2.8.1
uid: 2.0.2
- optionalDependencies:
- '@nestjs/platform-express': 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)
- transitivePeerDependencies:
- - encoding
- '@nestjs/platform-express@10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)':
+ '@nestjs/mapped-types@2.1.1(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)':
dependencies:
- '@nestjs/common': 10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2)
- '@nestjs/core': 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2)
- body-parser: 1.20.4
- cors: 2.8.5
- express: 4.22.1
- multer: 2.0.2
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ reflect-metadata: 0.2.2
+
+ '@nestjs/platform-fastify@11.1.18(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))':
+ dependencies:
+ '@fastify/cors': 11.2.0
+ '@fastify/formbody': 8.0.2
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ fast-querystring: 1.1.2
+ fastify: 5.8.4
+ fastify-plugin: 5.1.0
+ find-my-way: 9.5.0
+ light-my-request: 6.6.0
+ path-to-regexp: 8.4.2
+ reusify: 1.1.0
tslib: 2.8.1
- transitivePeerDependencies:
- - supports-color
+ optionalDependencies:
+ '@fastify/static': 9.1.0
- '@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.7.2)':
+ '@nestjs/schematics@11.0.10(chokidar@4.0.3)(typescript@5.9.3)':
dependencies:
- '@angular-devkit/core': 17.3.11(chokidar@3.6.0)
- '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0)
- comment-json: 4.2.5
+ '@angular-devkit/core': 19.2.23(chokidar@4.0.3)
+ '@angular-devkit/schematics': 19.2.23(chokidar@4.0.3)
+ comment-json: 4.6.2
jsonc-parser: 3.3.1
pluralize: 8.0.0
- typescript: 5.7.2
+ typescript: 5.9.3
transitivePeerDependencies:
- chokidar
- '@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.9.3)':
+ '@nestjs/swagger@11.2.7(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)':
dependencies:
- '@angular-devkit/core': 17.3.11(chokidar@3.6.0)
- '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0)
- comment-json: 4.2.5
- jsonc-parser: 3.3.1
- pluralize: 8.0.0
- typescript: 5.9.3
- transitivePeerDependencies:
- - chokidar
+ '@microsoft/tsdoc': 0.16.0
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/mapped-types': 2.1.1(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)
+ js-yaml: 4.1.1
+ lodash: 4.18.1
+ path-to-regexp: 8.4.2
+ reflect-metadata: 0.2.2
+ swagger-ui-dist: 5.32.2
+ optionalDependencies:
+ '@fastify/static': 9.1.0
- '@nestjs/testing@10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(@nestjs/platform-express@10.4.22)':
+ '@nestjs/testing@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))':
dependencies:
- '@nestjs/common': 10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2)
- '@nestjs/core': 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
tslib: 2.8.1
- optionalDependencies:
- '@nestjs/platform-express': 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)
+
+ '@nestjs/throttler@6.5.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)':
+ dependencies:
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ reflect-metadata: 0.2.2
'@noble/hashes@1.8.0': {}
@@ -3890,13 +4227,9 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.20.1
- '@nuxtjs/opencollective@0.3.2':
+ '@nuxt/opencollective@0.4.1':
dependencies:
- chalk: 4.1.2
- consola: 2.15.3
- node-fetch: 2.7.0
- transitivePeerDependencies:
- - encoding
+ consola: 3.4.2
'@oxc-project/types@0.124.0': {}
@@ -3904,8 +4237,7 @@ snapshots:
dependencies:
'@noble/hashes': 1.8.0
- '@pkgjs/parseargs@0.11.0':
- optional: true
+ '@pinojs/redact@0.4.0': {}
'@pkgr/core@0.2.9': {}
@@ -3966,6 +4298,8 @@ snapshots:
estree-walker: 2.0.2
picomatch: 4.0.4
+ '@scarf/scarf@1.4.0': {}
+
'@standard-schema/spec@1.1.0': {}
'@swc/core-darwin-arm64@1.15.24':
@@ -4028,10 +4362,9 @@ snapshots:
dependencies:
'@swc/counter': 0.1.3
- '@tokenizer/inflate@0.2.7':
+ '@tokenizer/inflate@0.4.1':
dependencies:
debug: 4.4.3
- fflate: 0.8.2
token-types: 6.1.2
transitivePeerDependencies:
- supports-color
@@ -4051,20 +4384,11 @@ snapshots:
tslib: 2.8.1
optional: true
- '@types/body-parser@1.19.6':
- dependencies:
- '@types/connect': 3.4.38
- '@types/node': 20.19.39
-
'@types/chai@5.2.3':
dependencies:
'@types/deep-eql': 4.0.2
assertion-error: 2.0.1
- '@types/connect@3.4.38':
- dependencies:
- '@types/node': 20.19.39
-
'@types/cookiejar@2.1.5': {}
'@types/deep-eql@4.0.2': {}
@@ -4081,28 +4405,10 @@ snapshots:
'@types/estree@1.0.8': {}
- '@types/express-serve-static-core@4.19.8':
- dependencies:
- '@types/node': 20.19.39
- '@types/qs': 6.15.0
- '@types/range-parser': 1.2.7
- '@types/send': 1.2.1
-
- '@types/express@4.17.25':
- dependencies:
- '@types/body-parser': 1.19.6
- '@types/express-serve-static-core': 4.19.8
- '@types/qs': 6.15.0
- '@types/serve-static': 1.15.10
-
- '@types/http-errors@2.0.5': {}
-
'@types/json-schema@7.0.15': {}
'@types/methods@1.1.4': {}
- '@types/mime@1.3.5': {}
-
'@types/node@20.19.39':
dependencies:
undici-types: 6.21.0
@@ -4113,27 +4419,8 @@ snapshots:
pg-protocol: 1.13.0
pg-types: 2.2.0
- '@types/qs@6.15.0': {}
-
- '@types/range-parser@1.2.7': {}
-
'@types/semver@7.7.1': {}
- '@types/send@0.17.6':
- dependencies:
- '@types/mime': 1.3.5
- '@types/node': 20.19.39
-
- '@types/send@1.2.1':
- dependencies:
- '@types/node': 20.19.39
-
- '@types/serve-static@1.15.10':
- dependencies:
- '@types/http-errors': 2.0.5
- '@types/node': 20.19.39
- '@types/send': 0.17.6
-
'@types/superagent@8.1.9':
dependencies:
'@types/cookiejar': 2.1.5
@@ -4369,10 +4656,15 @@ snapshots:
'@xtuc/long@4.2.2': {}
- accepts@1.3.8:
+ abort-controller@3.0.0:
dependencies:
- mime-types: 2.1.35
- negotiator: 0.6.3
+ event-target-shim: 5.0.1
+
+ abstract-logging@2.0.1: {}
+
+ acorn-import-phases@1.0.4(acorn@8.16.0):
+ dependencies:
+ acorn: 8.16.0
acorn-jsx@5.3.2(acorn@8.16.0):
dependencies:
@@ -4384,11 +4676,11 @@ snapshots:
acorn@8.16.0: {}
- ajv-formats@2.1.1(ajv@8.12.0):
+ ajv-formats@2.1.1(ajv@8.18.0):
optionalDependencies:
- ajv: 8.12.0
+ ajv: 8.18.0
- ajv-formats@2.1.1(ajv@8.18.0):
+ ajv-formats@3.0.1(ajv@8.18.0):
optionalDependencies:
ajv: 8.18.0
@@ -4408,13 +4700,6 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
- ajv@8.12.0:
- dependencies:
- fast-deep-equal: 3.1.3
- json-schema-traverse: 1.0.0
- require-from-string: 2.0.2
- uri-js: 4.4.1
-
ajv@8.18.0:
dependencies:
fast-deep-equal: 3.1.3
@@ -4424,10 +4709,6 @@ snapshots:
ansi-colors@4.1.3: {}
- ansi-escapes@4.3.2:
- dependencies:
- type-fest: 0.21.3
-
ansi-escapes@7.3.0:
dependencies:
environment: 1.1.0
@@ -4442,19 +4723,12 @@ snapshots:
ansi-styles@6.2.3: {}
- anymatch@3.1.3:
- dependencies:
- normalize-path: 3.0.0
- picomatch: 2.3.2
-
- append-field@1.0.0: {}
+ ansis@4.2.0: {}
arg@4.1.3: {}
argparse@2.0.1: {}
- array-flatten@1.1.1: {}
-
array-timsort@1.0.3: {}
array-union@2.1.0: {}
@@ -4471,37 +4745,27 @@ snapshots:
asynckit@0.4.0: {}
+ atomic-sleep@1.0.0: {}
+
+ avvio@9.2.0:
+ dependencies:
+ '@fastify/error': 4.2.0
+ fastq: 1.20.1
+
balanced-match@1.0.2: {}
+ balanced-match@4.0.4: {}
+
base64-js@1.5.1: {}
baseline-browser-mapping@2.10.17: {}
- binary-extensions@2.3.0: {}
-
bl@4.1.0:
dependencies:
buffer: 5.7.1
inherits: 2.0.4
readable-stream: 3.6.2
- body-parser@1.20.4:
- dependencies:
- bytes: 3.1.2
- content-type: 1.0.5
- debug: 2.6.9
- depd: 2.0.0
- destroy: 1.2.0
- http-errors: 2.0.1
- iconv-lite: 0.4.24
- on-finished: 2.4.1
- qs: 6.14.2
- raw-body: 2.5.3
- type-is: 1.6.18
- unpipe: 1.0.0
- transitivePeerDependencies:
- - supports-color
-
brace-expansion@1.1.13:
dependencies:
balanced-match: 1.0.2
@@ -4509,7 +4773,11 @@ snapshots:
brace-expansion@2.0.3:
dependencies:
- balanced-match: 1.0.2
+ balanced-match: 1.0.2
+
+ brace-expansion@5.0.5:
+ dependencies:
+ balanced-match: 4.0.4
braces@3.0.3:
dependencies:
@@ -4530,24 +4798,16 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
- busboy@1.6.0:
+ buffer@6.0.3:
dependencies:
- streamsearch: 1.1.0
-
- bytes@3.1.2: {}
+ base64-js: 1.5.1
+ ieee754: 1.2.1
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
function-bind: 1.1.2
- call-bind@1.0.9:
- dependencies:
- call-bind-apply-helpers: 1.0.2
- es-define-property: 1.0.1
- get-intrinsic: 1.3.0
- set-function-length: 1.2.2
-
call-bound@1.0.4:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -4564,21 +4824,11 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
- chalk@5.6.2: {}
-
- chardet@0.7.0: {}
+ chardet@2.1.1: {}
- chokidar@3.6.0:
+ chokidar@4.0.3:
dependencies:
- anymatch: 3.1.3
- braces: 3.0.3
- glob-parent: 5.1.2
- is-binary-path: 2.1.0
- is-glob: 4.0.3
- normalize-path: 3.0.0
- readdirp: 3.6.0
- optionalDependencies:
- fsevents: 2.3.3
+ readdirp: 4.1.2
chrome-trace-event@1.0.4: {}
@@ -4603,8 +4853,6 @@ snapshots:
slice-ansi: 8.0.0
string-width: 8.2.0
- cli-width@3.0.0: {}
-
cli-width@4.1.0: {}
clone@1.0.4: {}
@@ -4627,56 +4875,35 @@ snapshots:
commander@4.1.1: {}
- comment-json@4.2.5:
+ comment-json@4.6.2:
dependencies:
array-timsort: 1.0.3
- core-util-is: 1.0.3
esprima: 4.0.1
- has-own-prop: 2.0.0
- repeat-string: 1.6.1
component-emitter@1.3.1: {}
concat-map@0.0.1: {}
- concat-stream@2.0.0:
- dependencies:
- buffer-from: 1.1.2
- inherits: 2.0.4
- readable-stream: 3.6.2
- typedarray: 0.0.6
-
- consola@2.15.3: {}
-
- content-disposition@0.5.4:
- dependencies:
- safe-buffer: 5.2.1
+ consola@3.4.2: {}
- content-type@1.0.5: {}
+ content-disposition@1.1.0: {}
convert-source-map@2.0.0: {}
- cookie-signature@1.0.7: {}
-
- cookie@0.7.2: {}
+ cookie@1.1.1: {}
cookiejar@2.1.4: {}
core-util-is@1.0.3: {}
- cors@2.8.5:
- dependencies:
- object-assign: 4.1.1
- vary: 1.1.2
-
- cosmiconfig@8.3.6(typescript@5.7.2):
+ cosmiconfig@8.3.6(typescript@5.9.3):
dependencies:
import-fresh: 3.3.1
js-yaml: 4.1.1
parse-json: 5.2.0
path-type: 4.0.0
optionalDependencies:
- typescript: 5.7.2
+ typescript: 5.9.3
create-require@1.1.1: {}
@@ -4686,10 +4913,6 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
- debug@2.6.9:
- dependencies:
- ms: 2.0.0
-
debug@4.4.3:
dependencies:
ms: 2.1.3
@@ -4702,17 +4925,11 @@ snapshots:
dependencies:
clone: 1.0.4
- define-data-property@1.1.4:
- dependencies:
- es-define-property: 1.0.1
- es-errors: 1.3.0
- gopd: 1.2.0
-
delayed-stream@1.0.0: {}
depd@2.0.0: {}
- destroy@1.2.0: {}
+ dequal@2.0.3: {}
detect-libc@2.1.2: {}
@@ -4762,9 +4979,19 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
- eastasianwidth@0.2.0: {}
+ duplexify@3.7.1:
+ dependencies:
+ end-of-stream: 1.4.5
+ inherits: 2.0.4
+ readable-stream: 2.3.8
+ stream-shift: 1.0.3
- ee-first@1.1.1: {}
+ duplexify@4.1.3:
+ dependencies:
+ end-of-stream: 1.4.5
+ inherits: 2.0.4
+ readable-stream: 3.6.2
+ stream-shift: 1.0.3
electron-to-chromium@1.5.334: {}
@@ -4772,9 +4999,9 @@ snapshots:
emoji-regex@8.0.0: {}
- emoji-regex@9.2.2: {}
-
- encodeurl@2.0.0: {}
+ end-of-stream@1.4.5:
+ dependencies:
+ once: 1.4.0
enhanced-resolve@5.20.1:
dependencies:
@@ -4791,8 +5018,6 @@ snapshots:
es-errors@1.3.0: {}
- es-module-lexer@1.7.0: {}
-
es-module-lexer@2.0.0: {}
es-object-atoms@1.1.1:
@@ -4893,8 +5118,6 @@ snapshots:
escape-html@1.0.3: {}
- escape-string-regexp@1.0.5: {}
-
escape-string-regexp@4.0.0: {}
eslint-config-prettier@9.1.2(eslint@8.57.1):
@@ -4994,7 +5217,7 @@ snapshots:
esutils@2.0.3: {}
- etag@1.8.1: {}
+ event-target-shim@5.0.1: {}
eventemitter3@5.0.4: {}
@@ -5002,47 +5225,7 @@ snapshots:
expect-type@1.3.0: {}
- express@4.22.1:
- dependencies:
- accepts: 1.3.8
- array-flatten: 1.1.1
- body-parser: 1.20.4
- content-disposition: 0.5.4
- content-type: 1.0.5
- cookie: 0.7.2
- cookie-signature: 1.0.7
- debug: 2.6.9
- depd: 2.0.0
- encodeurl: 2.0.0
- escape-html: 1.0.3
- etag: 1.8.1
- finalhandler: 1.3.2
- fresh: 0.5.2
- http-errors: 2.0.1
- merge-descriptors: 1.0.3
- methods: 1.1.2
- on-finished: 2.4.1
- parseurl: 1.3.3
- path-to-regexp: 0.1.13
- proxy-addr: 2.0.7
- qs: 6.14.2
- range-parser: 1.2.1
- safe-buffer: 5.2.1
- send: 0.19.2
- serve-static: 1.16.3
- setprototypeof: 1.2.0
- statuses: 2.0.2
- type-is: 1.6.18
- utils-merge: 1.0.1
- vary: 1.1.2
- transitivePeerDependencies:
- - supports-color
-
- external-editor@3.1.0:
- dependencies:
- chardet: 0.7.0
- iconv-lite: 0.4.24
- tmp: 0.0.33
+ fast-decode-uri-component@1.0.1: {}
fast-deep-equal@3.1.3: {}
@@ -5058,12 +5241,45 @@ snapshots:
fast-json-stable-stringify@2.1.0: {}
+ fast-json-stringify@6.3.0:
+ dependencies:
+ '@fastify/merge-json-schemas': 0.2.1
+ ajv: 8.18.0
+ ajv-formats: 3.0.1(ajv@8.18.0)
+ fast-uri: 3.1.0
+ json-schema-ref-resolver: 3.0.0
+ rfdc: 1.4.1
+
fast-levenshtein@2.0.6: {}
+ fast-querystring@1.1.2:
+ dependencies:
+ fast-decode-uri-component: 1.0.1
+
fast-safe-stringify@2.1.1: {}
fast-uri@3.1.0: {}
+ fastify-plugin@5.1.0: {}
+
+ fastify@5.8.4:
+ dependencies:
+ '@fastify/ajv-compiler': 4.0.5
+ '@fastify/error': 4.2.0
+ '@fastify/fast-json-stringify-compiler': 5.0.3
+ '@fastify/proxy-addr': 5.1.0
+ abstract-logging: 2.0.1
+ avvio: 9.2.0
+ fast-json-stringify: 6.3.0
+ find-my-way: 9.5.0
+ light-my-request: 6.6.0
+ pino: 10.3.1
+ process-warning: 5.0.0
+ rfdc: 1.4.1
+ secure-json-parse: 4.1.0
+ semver: 7.7.4
+ toad-cache: 3.7.0
+
fastq@1.20.1:
dependencies:
reusify: 1.1.0
@@ -5072,19 +5288,13 @@ snapshots:
optionalDependencies:
picomatch: 4.0.4
- fflate@0.8.2: {}
-
- figures@3.2.0:
- dependencies:
- escape-string-regexp: 1.0.5
-
file-entry-cache@6.0.1:
dependencies:
flat-cache: 3.2.0
- file-type@20.4.1:
+ file-type@21.3.4:
dependencies:
- '@tokenizer/inflate': 0.2.7
+ '@tokenizer/inflate': 0.4.1
strtok3: 10.3.5
token-types: 6.1.2
uint8array-extras: 1.5.0
@@ -5095,17 +5305,11 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
- finalhandler@1.3.2:
+ find-my-way@9.5.0:
dependencies:
- debug: 2.6.9
- encodeurl: 2.0.0
- escape-html: 1.0.3
- on-finished: 2.4.1
- parseurl: 1.3.3
- statuses: 2.0.2
- unpipe: 1.0.0
- transitivePeerDependencies:
- - supports-color
+ fast-deep-equal: 3.1.3
+ fast-querystring: 1.1.2
+ safe-regex2: 5.1.0
find-up@5.0.0:
dependencies:
@@ -5120,17 +5324,12 @@ snapshots:
flatted@3.4.2: {}
- foreground-child@3.3.1:
- dependencies:
- cross-spawn: 7.0.6
- signal-exit: 4.1.0
-
- fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.15.24)(esbuild@0.27.7)):
+ fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.24)(esbuild@0.27.7)):
dependencies:
'@babel/code-frame': 7.29.0
chalk: 4.1.2
- chokidar: 3.6.0
- cosmiconfig: 8.3.6(typescript@5.7.2)
+ chokidar: 4.0.3
+ cosmiconfig: 8.3.6(typescript@5.9.3)
deepmerge: 4.3.1
fs-extra: 10.1.0
memfs: 3.5.3
@@ -5139,8 +5338,8 @@ snapshots:
schema-utils: 3.3.0
semver: 7.7.4
tapable: 2.3.2
- typescript: 5.7.2
- webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.27.7)
+ typescript: 5.9.3
+ webpack: 5.106.0(@swc/core@1.15.24)(esbuild@0.27.7)
form-data@4.0.5:
dependencies:
@@ -5157,10 +5356,6 @@ snapshots:
once: 1.4.0
qs: 6.15.1
- forwarded@0.2.0: {}
-
- fresh@0.5.2: {}
-
fs-extra@10.1.0:
dependencies:
graceful-fs: 4.2.11
@@ -5210,14 +5405,11 @@ snapshots:
glob-to-regexp@0.4.1: {}
- glob@10.4.5:
+ glob@13.0.6:
dependencies:
- foreground-child: 3.3.1
- jackspeak: 3.4.3
- minimatch: 9.0.9
+ minimatch: 10.2.5
minipass: 7.1.3
- package-json-from-dist: 1.0.1
- path-scurry: 1.11.1
+ path-scurry: 2.0.2
glob@7.2.3:
dependencies:
@@ -5249,12 +5441,6 @@ snapshots:
has-flag@4.0.0: {}
- has-own-prop@2.0.0: {}
-
- has-property-descriptors@1.0.2:
- dependencies:
- es-define-property: 1.0.1
-
has-symbols@1.1.0: {}
has-tostringtag@1.0.2:
@@ -5275,7 +5461,7 @@ snapshots:
statuses: 2.0.2
toidentifier: 1.0.1
- iconv-lite@0.4.24:
+ iconv-lite@0.7.2:
dependencies:
safer-buffer: 2.1.2
@@ -5297,50 +5483,10 @@ snapshots:
inherits@2.0.4: {}
- inquirer@8.2.6:
- dependencies:
- ansi-escapes: 4.3.2
- chalk: 4.1.2
- cli-cursor: 3.1.0
- cli-width: 3.0.0
- external-editor: 3.1.0
- figures: 3.2.0
- lodash: 4.18.1
- mute-stream: 0.0.8
- ora: 5.4.1
- run-async: 2.4.1
- rxjs: 7.8.2
- string-width: 4.2.3
- strip-ansi: 6.0.1
- through: 2.3.8
- wrap-ansi: 6.2.0
-
- inquirer@9.2.15:
- dependencies:
- '@ljharb/through': 2.3.14
- ansi-escapes: 4.3.2
- chalk: 5.6.2
- cli-cursor: 3.1.0
- cli-width: 4.1.0
- external-editor: 3.1.0
- figures: 3.2.0
- lodash: 4.18.1
- mute-stream: 1.0.0
- ora: 5.4.1
- run-async: 3.0.0
- rxjs: 7.8.2
- string-width: 4.2.3
- strip-ansi: 6.0.1
- wrap-ansi: 6.2.0
-
- ipaddr.js@1.9.1: {}
+ ipaddr.js@2.3.0: {}
is-arrayish@0.2.1: {}
- is-binary-path@2.1.0:
- dependencies:
- binary-extensions: 2.3.0
-
is-extglob@2.1.1: {}
is-fullwidth-code-point@3.0.0: {}
@@ -5361,6 +5507,8 @@ snapshots:
is-unicode-supported@0.1.0: {}
+ isarray@1.0.0: {}
+
isexe@2.0.0: {}
istanbul-lib-coverage@3.2.2: {}
@@ -5378,12 +5526,6 @@ snapshots:
iterare@1.2.1: {}
- jackspeak@3.4.3:
- dependencies:
- '@isaacs/cliui': 8.0.2
- optionalDependencies:
- '@pkgjs/parseargs': 0.11.0
-
jest-worker@27.5.1:
dependencies:
'@types/node': 20.19.39
@@ -5402,6 +5544,10 @@ snapshots:
json-parse-even-better-errors@2.3.1: {}
+ json-schema-ref-resolver@3.0.0:
+ dependencies:
+ dequal: 2.0.3
+
json-schema-traverse@0.4.1: {}
json-schema-traverse@1.0.0: {}
@@ -5410,8 +5556,6 @@ snapshots:
json5@2.2.3: {}
- jsonc-parser@3.2.1: {}
-
jsonc-parser@3.3.1: {}
jsonfile@6.2.0:
@@ -5429,6 +5573,12 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
+ light-my-request@6.6.0:
+ dependencies:
+ cookie: 1.1.1
+ process-warning: 4.0.1
+ set-cookie-parser: 2.7.2
+
lightningcss-android-arm64@1.32.0:
optional: true
@@ -5498,6 +5648,8 @@ snapshots:
rfdc: 1.4.1
wrap-ansi: 9.0.2
+ load-esm@1.0.3: {}
+
load-tsconfig@0.2.5: {}
loader-runner@4.3.1: {}
@@ -5523,13 +5675,13 @@ snapshots:
strip-ansi: 7.2.0
wrap-ansi: 9.0.2
- lru-cache@10.4.3: {}
+ lru-cache@11.3.3: {}
- magic-string@0.30.21:
+ magic-string@0.30.17:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
- magic-string@0.30.8:
+ magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -5547,14 +5699,10 @@ snapshots:
math-intrinsics@1.1.0: {}
- media-typer@0.3.0: {}
-
memfs@3.5.3:
dependencies:
fs-monkey: 1.1.0
- merge-descriptors@1.0.3: {}
-
merge-stream@2.0.0: {}
merge2@1.4.1: {}
@@ -5572,14 +5720,18 @@ snapshots:
dependencies:
mime-db: 1.52.0
- mime@1.6.0: {}
-
mime@2.6.0: {}
+ mime@3.0.0: {}
+
mimic-fn@2.1.0: {}
mimic-function@5.0.1: {}
+ minimatch@10.2.5:
+ dependencies:
+ brace-expansion: 5.0.5
+
minimatch@3.1.5:
dependencies:
brace-expansion: 1.1.13
@@ -5588,67 +5740,42 @@ snapshots:
dependencies:
brace-expansion: 2.0.3
- minimatch@9.0.9:
- dependencies:
- brace-expansion: 2.0.3
-
minimist@1.2.8: {}
minipass@7.1.3: {}
- mkdirp@0.5.6:
- dependencies:
- minimist: 1.2.8
-
- ms@2.0.0: {}
-
ms@2.1.3: {}
- multer@2.0.2:
- dependencies:
- append-field: 1.0.0
- busboy: 1.6.0
- concat-stream: 2.0.0
- mkdirp: 0.5.6
- object-assign: 4.1.1
- type-is: 1.6.18
- xtend: 4.0.2
-
- mute-stream@0.0.8: {}
-
- mute-stream@1.0.0: {}
+ mute-stream@2.0.0: {}
nanoid@3.3.11: {}
natural-compare@1.4.0: {}
- negotiator@0.6.3: {}
-
neo-async@2.6.2: {}
+ nestjs-zod@5.3.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.2.7(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6):
+ dependencies:
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ deepmerge: 4.3.1
+ rxjs: 7.8.2
+ zod: 4.3.6
+ optionalDependencies:
+ '@nestjs/swagger': 11.2.7(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)
+
node-abort-controller@3.1.1: {}
node-emoji@1.11.0:
dependencies:
lodash: 4.18.1
- node-fetch@2.7.0:
- dependencies:
- whatwg-url: 5.0.0
-
node-releases@2.0.37: {}
- normalize-path@3.0.0: {}
-
- object-assign@4.1.1: {}
-
object-inspect@1.13.4: {}
obug@2.1.1: {}
- on-finished@2.4.1:
- dependencies:
- ee-first: 1.1.1
+ on-exit-leak-free@2.1.2: {}
once@1.4.0:
dependencies:
@@ -5683,8 +5810,6 @@ snapshots:
strip-ansi: 6.0.1
wcwidth: 1.0.1
- os-tmpdir@1.0.2: {}
-
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
@@ -5693,8 +5818,6 @@ snapshots:
dependencies:
p-limit: 3.1.0
- package-json-from-dist@1.0.1: {}
-
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@@ -5706,27 +5829,29 @@ snapshots:
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
- parseurl@1.3.3: {}
-
path-exists@4.0.0: {}
path-is-absolute@1.0.1: {}
path-key@3.1.1: {}
- path-scurry@1.11.1:
+ path-scurry@2.0.2:
dependencies:
- lru-cache: 10.4.3
+ lru-cache: 11.3.3
minipass: 7.1.3
- path-to-regexp@0.1.13: {}
-
- path-to-regexp@3.3.0: {}
+ path-to-regexp@8.4.2: {}
path-type@4.0.0: {}
pathe@2.0.3: {}
+ peek-stream@1.1.3:
+ dependencies:
+ buffer-from: 1.1.2
+ duplexify: 3.7.1
+ through2: 2.0.5
+
pg-cloudflare@1.3.0:
optional: true
@@ -5766,10 +5891,28 @@ snapshots:
picomatch@2.3.2: {}
- picomatch@4.0.1: {}
-
picomatch@4.0.4: {}
+ pino-abstract-transport@3.0.0:
+ dependencies:
+ split2: 4.2.0
+
+ pino-std-serializers@7.1.0: {}
+
+ pino@10.3.1:
+ dependencies:
+ '@pinojs/redact': 0.4.0
+ atomic-sleep: 1.0.0
+ on-exit-leak-free: 2.1.2
+ pino-abstract-transport: 3.0.0
+ pino-std-serializers: 7.1.0
+ process-warning: 5.0.0
+ quick-format-unescaped: 4.0.4
+ real-require: 0.2.0
+ safe-stable-stringify: 2.5.0
+ sonic-boom: 4.2.1
+ thread-stream: 4.0.0
+
pluralize@8.0.0: {}
postcss@8.5.9:
@@ -5796,16 +5939,26 @@ snapshots:
prettier@3.8.2: {}
- proxy-addr@2.0.7:
- dependencies:
- forwarded: 0.2.0
- ipaddr.js: 1.9.1
+ process-nextick-args@2.0.1: {}
- punycode@2.3.1: {}
+ process-warning@4.0.1: {}
+
+ process-warning@5.0.0: {}
+
+ process@0.11.10: {}
- qs@6.14.2:
+ pump@3.0.4:
dependencies:
- side-channel: 1.1.0
+ end-of-stream: 1.4.5
+ once: 1.4.0
+
+ pumpify@2.0.1:
+ dependencies:
+ duplexify: 4.1.3
+ inherits: 2.0.4
+ pump: 3.0.4
+
+ punycode@2.3.1: {}
qs@6.15.1:
dependencies:
@@ -5813,14 +5966,17 @@ snapshots:
queue-microtask@1.2.3: {}
- range-parser@1.2.1: {}
+ quick-format-unescaped@4.0.4: {}
- raw-body@2.5.3:
+ readable-stream@2.3.8:
dependencies:
- bytes: 3.1.2
- http-errors: 2.0.1
- iconv-lite: 0.4.24
- unpipe: 1.0.0
+ core-util-is: 1.0.3
+ inherits: 2.0.4
+ isarray: 1.0.0
+ process-nextick-args: 2.0.1
+ safe-buffer: 5.1.2
+ string_decoder: 1.1.1
+ util-deprecate: 1.0.2
readable-stream@3.6.2:
dependencies:
@@ -5828,13 +5984,19 @@ snapshots:
string_decoder: 1.3.0
util-deprecate: 1.0.2
- readdirp@3.6.0:
+ readable-stream@4.7.0:
dependencies:
- picomatch: 2.3.2
+ abort-controller: 3.0.0
+ buffer: 6.0.3
+ events: 3.3.0
+ process: 0.11.10
+ string_decoder: 1.3.0
- reflect-metadata@0.2.2: {}
+ readdirp@4.1.2: {}
+
+ real-require@0.2.0: {}
- repeat-string@1.6.1: {}
+ reflect-metadata@0.2.2: {}
require-from-string@2.0.2: {}
@@ -5852,6 +6014,8 @@ snapshots:
onetime: 7.0.0
signal-exit: 4.1.0
+ ret@0.5.0: {}
+
reusify@1.1.0: {}
rfdc@1.4.1: {}
@@ -5881,10 +6045,6 @@ snapshots:
'@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.15
'@rolldown/binding-win32-x64-msvc': 1.0.0-rc.15
- run-async@2.4.1: {}
-
- run-async@3.0.0: {}
-
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@@ -5897,8 +6057,16 @@ snapshots:
dependencies:
tslib: 2.8.1
+ safe-buffer@5.1.2: {}
+
safe-buffer@5.2.1: {}
+ safe-regex2@5.1.0:
+ dependencies:
+ ret: 0.5.0
+
+ safe-stable-stringify@2.5.0: {}
+
safer-buffer@2.1.2: {}
schema-utils@3.3.0:
@@ -5914,43 +6082,11 @@ snapshots:
ajv-formats: 2.1.1(ajv@8.18.0)
ajv-keywords: 5.1.0(ajv@8.18.0)
- semver@7.7.4: {}
-
- send@0.19.2:
- dependencies:
- debug: 2.6.9
- depd: 2.0.0
- destroy: 1.2.0
- encodeurl: 2.0.0
- escape-html: 1.0.3
- etag: 1.8.1
- fresh: 0.5.2
- http-errors: 2.0.1
- mime: 1.6.0
- ms: 2.1.3
- on-finished: 2.4.1
- range-parser: 1.2.1
- statuses: 2.0.2
- transitivePeerDependencies:
- - supports-color
+ secure-json-parse@4.1.0: {}
- serve-static@1.16.3:
- dependencies:
- encodeurl: 2.0.0
- escape-html: 1.0.3
- parseurl: 1.3.3
- send: 0.19.2
- transitivePeerDependencies:
- - supports-color
+ semver@7.7.4: {}
- set-function-length@1.2.2:
- dependencies:
- define-data-property: 1.1.4
- es-errors: 1.3.0
- function-bind: 1.1.2
- get-intrinsic: 1.3.0
- gopd: 1.2.0
- has-property-descriptors: 1.0.2
+ set-cookie-parser@2.7.2: {}
setprototypeof@1.2.0: {}
@@ -6006,6 +6142,10 @@ snapshots:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
+ sonic-boom@4.2.1:
+ dependencies:
+ atomic-sleep: 1.0.0
+
source-map-js@1.2.1: {}
source-map-support@0.5.21:
@@ -6027,7 +6167,7 @@ snapshots:
std-env@4.0.0: {}
- streamsearch@1.1.0: {}
+ stream-shift@1.0.3: {}
string-argv@0.3.2: {}
@@ -6037,12 +6177,6 @@ snapshots:
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
- string-width@5.1.2:
- dependencies:
- eastasianwidth: 0.2.0
- emoji-regex: 9.2.2
- strip-ansi: 7.2.0
-
string-width@7.2.0:
dependencies:
emoji-regex: 10.6.0
@@ -6054,6 +6188,10 @@ snapshots:
get-east-asian-width: 1.5.0
strip-ansi: 7.2.0
+ string_decoder@1.1.1:
+ dependencies:
+ safe-buffer: 5.1.2
+
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
@@ -6104,6 +6242,10 @@ snapshots:
dependencies:
has-flag: 4.0.0
+ swagger-ui-dist@5.32.2:
+ dependencies:
+ '@scarf/scarf': 1.4.0
+
symbol-observable@4.0.0: {}
synckit@0.11.12:
@@ -6112,13 +6254,13 @@ snapshots:
tapable@2.3.2: {}
- terser-webpack-plugin@5.4.0(@swc/core@1.15.24)(esbuild@0.27.7)(webpack@5.97.1(@swc/core@1.15.24)(esbuild@0.27.7)):
+ terser-webpack-plugin@5.4.0(@swc/core@1.15.24)(esbuild@0.27.7)(webpack@5.106.0(@swc/core@1.15.24)(esbuild@0.27.7)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
schema-utils: 4.3.3
terser: 5.46.1
- webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.27.7)
+ webpack: 5.106.0(@swc/core@1.15.24)(esbuild@0.27.7)
optionalDependencies:
'@swc/core': 1.15.24
esbuild: 0.27.7
@@ -6132,7 +6274,14 @@ snapshots:
text-table@0.2.0: {}
- through@2.3.8: {}
+ thread-stream@4.0.0:
+ dependencies:
+ real-require: 0.2.0
+
+ through2@2.0.5:
+ dependencies:
+ readable-stream: 2.3.8
+ xtend: 4.0.2
tinybench@2.9.0: {}
@@ -6145,14 +6294,12 @@ snapshots:
tinyrainbow@3.1.0: {}
- tmp@0.0.33:
- dependencies:
- os-tmpdir: 1.0.2
-
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
+ toad-cache@3.7.0: {}
+
toidentifier@1.0.1: {}
token-types@6.1.2:
@@ -6161,15 +6308,11 @@ snapshots:
'@tokenizer/token': 0.3.0
ieee754: 1.2.1
- tr46@0.0.3: {}
-
- tree-kill@1.2.2: {}
-
ts-api-utils@1.4.3(typescript@5.9.3):
dependencies:
typescript: 5.9.3
- ts-loader@9.5.7(typescript@5.9.3)(webpack@5.97.1(@swc/core@1.15.24)(esbuild@0.27.7)):
+ ts-loader@9.5.7(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.24)(esbuild@0.27.7)):
dependencies:
chalk: 4.1.2
enhanced-resolve: 5.20.1
@@ -6177,7 +6320,7 @@ snapshots:
semver: 7.7.4
source-map: 0.7.6
typescript: 5.9.3
- webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.27.7)
+ webpack: 5.106.0(@swc/core@1.15.24)(esbuild@0.27.7)
ts-node@10.9.2(@swc/core@1.15.24)(@types/node@20.19.39)(typescript@5.9.3):
dependencies:
@@ -6227,17 +6370,6 @@ snapshots:
type-fest@0.20.2: {}
- type-fest@0.21.3: {}
-
- type-is@1.6.18:
- dependencies:
- media-typer: 0.3.0
- mime-types: 2.1.35
-
- typedarray@0.0.6: {}
-
- typescript@5.7.2: {}
-
typescript@5.9.3: {}
uid@2.0.2:
@@ -6250,8 +6382,6 @@ snapshots:
universalify@2.0.1: {}
- unpipe@1.0.0: {}
-
unplugin-swc@1.5.9(@swc/core@1.15.24):
dependencies:
'@rollup/pluginutils': 5.3.0
@@ -6280,12 +6410,8 @@ snapshots:
util-deprecate@1.0.2: {}
- utils-merge@1.0.1: {}
-
v8-compile-cache-lib@3.0.1: {}
- vary@1.1.2: {}
-
vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
dependencies:
lightningcss: 1.32.0
@@ -6338,26 +6464,26 @@ snapshots:
dependencies:
defaults: 1.0.4
- webidl-conversions@3.0.1: {}
-
webpack-node-externals@3.0.0: {}
webpack-sources@3.3.4: {}
webpack-virtual-modules@0.6.2: {}
- webpack@5.97.1(@swc/core@1.15.24)(esbuild@0.27.7):
+ webpack@5.106.0(@swc/core@1.15.24)(esbuild@0.27.7):
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.8
+ '@types/json-schema': 7.0.15
'@webassemblyjs/ast': 1.14.1
'@webassemblyjs/wasm-edit': 1.14.1
'@webassemblyjs/wasm-parser': 1.14.1
acorn: 8.16.0
+ acorn-import-phases: 1.0.4(acorn@8.16.0)
browserslist: 4.28.2
chrome-trace-event: 1.0.4
enhanced-resolve: 5.20.1
- es-module-lexer: 1.7.0
+ es-module-lexer: 2.0.0
eslint-scope: 5.1.1
events: 3.3.0
glob-to-regexp: 0.4.1
@@ -6366,9 +6492,9 @@ snapshots:
loader-runner: 4.3.1
mime-types: 2.1.35
neo-async: 2.6.2
- schema-utils: 3.3.0
+ schema-utils: 4.3.3
tapable: 2.3.2
- terser-webpack-plugin: 5.4.0(@swc/core@1.15.24)(esbuild@0.27.7)(webpack@5.97.1(@swc/core@1.15.24)(esbuild@0.27.7))
+ terser-webpack-plugin: 5.4.0(@swc/core@1.15.24)(esbuild@0.27.7)(webpack@5.106.0(@swc/core@1.15.24)(esbuild@0.27.7))
watchpack: 2.5.1
webpack-sources: 3.3.4
transitivePeerDependencies:
@@ -6376,11 +6502,6 @@ snapshots:
- esbuild
- uglify-js
- whatwg-url@5.0.0:
- dependencies:
- tr46: 0.0.3
- webidl-conversions: 3.0.1
-
which@2.0.2:
dependencies:
isexe: 2.0.0
@@ -6398,18 +6519,6 @@ snapshots:
string-width: 4.2.3
strip-ansi: 6.0.1
- wrap-ansi@7.0.0:
- dependencies:
- ansi-styles: 4.3.0
- string-width: 4.2.3
- strip-ansi: 6.0.1
-
- wrap-ansi@8.1.0:
- dependencies:
- ansi-styles: 6.2.3
- string-width: 5.1.2
- strip-ansi: 7.2.0
-
wrap-ansi@9.0.2:
dependencies:
ansi-styles: 6.2.3
@@ -6428,4 +6537,6 @@ snapshots:
yocto-queue@0.1.0: {}
+ yoctocolors-cjs@2.1.3: {}
+
zod@4.3.6: {}
diff --git a/src/app.module.ts b/src/app.module.ts
index 74ca97c..0f9f1cf 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -5,6 +5,8 @@ import { ConfigModule } from '@libs/config';
import { DatabaseModule } from '@libs/database';
import { ConfigService } from '@nestjs/config';
import * as schema from './shared/entities';
+import { APP_FILTER, APP_PIPE } from '@nestjs/core';
+import { ZodValidationPipe, ZodValidationException } from 'nestjs-zod';
@Module({
imports: [
@@ -22,6 +24,16 @@ import * as schema from './shared/entities';
}),
],
controllers: [AppController],
- providers: [AppService],
+ providers: [
+ AppService,
+ {
+ provide: APP_PIPE,
+ useClass: ZodValidationPipe,
+ },
+ {
+ provide: APP_FILTER,
+ useClass: ZodValidationException,
+ },
+ ],
})
export class AppModule {}
diff --git a/src/main.ts b/src/main.ts
index df101b2..aa7c5a0 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,14 +1,18 @@
-import { NestFactory } from '@nestjs/core';
+import { bootstrapApp } from '@libs/bootstrap';
import { AppModule } from './app.module';
-import { ConfigService } from '@nestjs/config';
-async function bootstrap() {
- const app = await NestFactory.create(AppModule);
-
- const config = app.get(ConfigService);
- const port = config.getOrThrow('PORT');
-
- await app.listen(port);
-}
-
-bootstrap();
+bootstrapApp({
+ serviceName: 'Tracker Monolit',
+ appModule: AppModule,
+ apiPrefix: 'api/v1',
+ defaultPort: 2000,
+ portEnvKey: 'PORT',
+ swaggerOptions: {
+ title: 'Task Tracker API',
+ description: 'API бэкенда таск-трекера',
+ version: '0.1.0',
+ path: 'ui',
+ },
+ useCors: true,
+ useCookieParser: true,
+});
diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts
index 4d26f6b..95c5212 100644
--- a/test/app.e2e-spec.ts
+++ b/test/app.e2e-spec.ts
@@ -1,6 +1,6 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
-import * as request from 'supertest';
+import { agent } from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
@@ -16,6 +16,6 @@ describe('AppController (e2e)', () => {
});
it('/ (GET)', () => {
- return request(app.getHttpServer()).get('/').expect(200).expect('Hello World!');
+ return agent(app.getHttpServer()).get('/').expect(200).expect('Hello World!');
});
});
diff --git a/tsconfig.json b/tsconfig.json
index d798e4f..503f4c1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -21,7 +21,9 @@
"@libs/config": ["./libs/config/src"],
"@libs/config/*": ["./libs/config/src/*"],
"@libs/database": ["./libs/database/src"],
- "@libs/database/*": ["./libs/database/src/*"]
+ "@libs/database/*": ["./libs/database/src/*"],
+ "@libs/bootstrap": ["./libs/bootstrap/src"],
+ "@libs/bootstrap/*": ["./libs/bootstrap/src/*"]
}
},
"include": [
From 46c40004cc4eb590d74bbd7b0d5c521b89c57cbe Mon Sep 17 00:00:00 2001
From: Danil <123034340+soorq@users.noreply.github.com>
Date: Fri, 10 Apr 2026 22:50:03 +0300
Subject: [PATCH 07/30] feat(infra): establish open-source automation and
contribution standards
* ci: add docker build check workflow
* docs(gh): setup issue templates and community configuration
---
.github/ISSUE_TEMPLATE/bug_report.yml | 41 ++++++++++++++++++
.github/ISSUE_TEMPLATE/config.yml | 10 +++++
.github/ISSUE_TEMPLATE/feature_request.yml | 18 ++++++++
.github/workflows/build.yml | 49 ++++++++++++++++++++++
.github/workflows/ci.yml | 37 ++++++++++++++++
.github/workflows/codeql.yml | 33 +++++++++++++++
.github/workflows/release-please.yml | 18 ++++++++
.github/workflows/stale.yml | 19 +++++++++
8 files changed, 225 insertions(+)
create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml
create mode 100644 .github/ISSUE_TEMPLATE/config.yml
create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml
create mode 100644 .github/workflows/build.yml
create mode 100644 .github/workflows/ci.yml
create mode 100644 .github/workflows/codeql.yml
create mode 100644 .github/workflows/release-please.yml
create mode 100644 .github/workflows/stale.yml
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..f166041
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,41 @@
+name: "Bug Report"
+description: "Сообщить об ошибке в работе приложения"
+labels: ["bug", "triage"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Спасибо, что решили помочь сделать проект лучше!
+ - type: input
+ id: version
+ attributes:
+ label: "Версия приложения"
+ description: "Какую версию вы используете? (например, 0.0.1)"
+ placeholder: "0.0.x"
+ validations:
+ required: true
+ - type: textarea
+ id: steps
+ attributes:
+ label: "Шаги воспроизведения"
+ description: "Как нам увидеть эту ошибку?"
+ placeholder: |
+ 1. Запустить docker-compose
+ 2. Отправить POST запрос на /api/v1/auth...
+ validations:
+ required: true
+ - type: dropdown
+ id: environment
+ attributes:
+ label: "Окружение"
+ options:
+ - Docker
+ - Local (pnpm)
+ - Production
+ validations:
+ required: true
+ - type: textarea
+ id: expected
+ attributes:
+ label: "Ожидаемое поведение"
+ placeholder: "Что должно было произойти?"
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..656e5e5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,10 @@
+blank_issues_enabled: false
+
+contact_links:
+ - name: "❓ Вопросы по использованию"
+ url: "https://github.com/Task-Tracker-Lab/task-tracker-backend/discussions/new?category=q-a"
+ about: "Если вы не уверены, баг это или нет, или вам нужна помощь в настройке — спросите здесь."
+
+ - name: "💡 Идеи и предложения"
+ url: "https://github.com/Task-Tracker-Lab/task-tracker-backend/discussions/new?category=ideas"
+ about: "Хотите обсудить новую крутую фичу перед тем, как заводить задачу? Вам сюда."
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000..4c6bacc
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,18 @@
+name: "🚀 Feature Request"
+description: "Предложить новую идею или улучшение"
+labels: ["enhancement"]
+body:
+ - type: textarea
+ id: problem
+ attributes:
+ label: "Какую проблему мы решаем?"
+ description: "Опишите, почему текущего функционала недостаточно."
+ validations:
+ required: true
+ - type: textarea
+ id: solution
+ attributes:
+ label: "Ваше предложение"
+ description: "Как именно вы видите реализацию этой фичи?"
+ validations:
+ required: true
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..9b1744f
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,49 @@
+name: Build and Push
+
+on:
+ push:
+ branches: [dev, main]
+
+env:
+ REGISTRY: ghcr.io
+ IMAGE_NAME: ${{ github.repository }}
+
+jobs:
+ build-push:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ # packages: write
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ # - name: Log in to the Container registry
+ # uses: docker/login-action@v3
+ # with:
+ # registry: ${{ env.REGISTRY }}
+ # username: ${{ github.actor }}
+ # password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata (tags, labels)
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: |
+ type=ref,event=branch
+ type=sha,format=short
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./Dockerfile.prod
+ push: false # add true, if your setup variables
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..aa9a576
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,37 @@
+name: CI
+
+on:
+ pull_request:
+ branches: [dev, main]
+ push:
+ branches: [dev, main]
+
+jobs:
+ quality-check:
+ name: Lint & Test
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: "pnpm"
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Run Lint
+ run: pnpm run lint
+
+ - name: Type Check
+ run: pnpm exec tsc --noEmit
+
+ - name: Run Tests
+ run: pnpm run test
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..c168cd6
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,33 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [main, dev, feat/**, chore/**, build/**]
+ pull_request:
+ branches: [main]
+ schedule:
+ - cron: "15 13 * * 5"
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: javascript-typescript
+
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v3
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
new file mode 100644
index 0000000..95b126c
--- /dev/null
+++ b/.github/workflows/release-please.yml
@@ -0,0 +1,18 @@
+name: release-please
+
+on:
+ push:
+ branches:
+ - main
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ release-please:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: googleapis/release-please-action@v4
+ with:
+ release-type: node
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 0000000..fbc62d1
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,19 @@
+name: "Close stale issues and PRs"
+
+on:
+ schedule:
+ - cron: "30 1 * * *"
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - uses: actions/stale@v9
+ with:
+ stale-issue-message: "Эта задача давно не обновлялась. Она будет закрыта через 5 дней, если не появится новой активности."
+ stale-pr-message: "Этот PR замер. Мы закроем его через 5 дней, чтобы не копить очередь, но вы всегда можете переоткрыть его позже."
+ days-before-stale: 30
+ days-before-close: 5
From 298d5124768ebff1bb1df6a9a1e219a6d03a951e Mon Sep 17 00:00:00 2001
From: Maksym Berehovyi <108676512+maksberegovoi@users.noreply.github.com>
Date: Fri, 10 Apr 2026 23:07:21 +0300
Subject: [PATCH 08/30] docs: update README, contributing guide and address
reviewer feedback
* docs: add README with stack and quick start guide
* docs: add contributing guidelines with development standards
* chore: bump per reviewer requests
---
CONTRIBUTING.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++++
README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 114 insertions(+), 2 deletions(-)
create mode 100644 CONTRIBUTING.md
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..8272506
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,59 @@
+# Contributing Guidelines 🤝
+
+Добро пожаловать в проект! Чтобы поддерживать высокое качество кода и чистоту истории коммитов, мы следуем строгим стандартам разработки.
+
+## 1. Workflow (Работа с ветками)
+
+Мы используем **Trunk-based** подход. Это означает, что `main` (trunk) — единственный источник истины.
+
+- **Запрет на прямые пуши:** Пуш напрямую в `main` (или `master/dev`) строго запрещен. Любые изменения вносятся **только через Pull Request (PR)**.
+- **Code Review:** Каждый PR должен пройти обязательное код-ревью перед мерджем.
+- **Short-lived branches:** Ветки должны быть короткими (1-2 дня).
+
+**Правила именования веток:**
+
+- `feat/` — для новых функциональных возможностей.
+- `fix/` — для исправления багов.
+- `refactor/` — для переписывания кода без изменения логики.
+- `docs/` — для обновления документации.
+
+## 2. Commit Message Convention
+
+Мы используем стандарт **Conventional Commits**. Это позволяет автоматически генерировать логи изменений и поддерживать историю в чистоте.
+
+**Формат:** `(): `
+
+- **Пример:** `feat(database): add user entity and drizzle migrations`
+- **Пример:** `fix(auth): resolve jwt expiration issue`
+
+## 3. Разработка и стандарты кода
+
+- **Validation (Zod):** Использование схем **Zod** для эндпоинтов (DTO) обязательно. Без них не будет работать валидация данных и автоматическая типизация в Swagger.
+- **Linting & Formatting:** Перед каждым коммитом обязательно запускайте `pnpm lint` и `pnpm format`. Код, не прошедший проверку линтером, не будет принят.
+- **Drizzle Migrations:** **Никаких ручных изменений в базе данных.** Все изменения схем должны сопровождаться миграциями.
+ - Команда: `pnpm db:generate`
+- **No God-commits:** Разделяйте свои изменения на небольшие логические коммиты.
+
+## 4. Pull Request (PR) Process
+
+Прежде чем отправить PR, убедитесь, что:
+
+1. **Self-Review:** Вы сами перечитали свой код и удалили отладочные логи (`console.log`).
+2. **Checks:** Все автоматические тесты (`pnpm test`) и линтер проходят успешно.
+3. **Description:** В описании PR четко указано, что именно было сделано и зачем.
+
+_PR не принимается, если тесты или линтер упали на этапе CI._
+
+## 5. Local Setup
+
+Для быстрой настройки локального окружения обратитесь к разделу **Quick Start** в [README.md](./README.md).
+
+Краткий список команд для старта:
+
+```bash
+pnpm install
+cp .env.example .env
+pnpm db:generate
+pnpm db:migrate
+pnpm start:dev
+```
diff --git a/README.md b/README.md
index 6d83c08..cd789c0 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,55 @@
-# task-tracker-backend
-Task-tracker-backend
+# Task Tracker Backend 🚀
+
+Современная лёгкая open-source система управления IT-проектами (альтернатива Jira/Yandex Tracker). Бэкенд построен на высокопроизводительном стеке с упором на типизацию и скорость разработки.
+
+**Статус:** `In Development`
+
+## Технологический стек
+
+- **Runtime:** Node.js 22+ (pnpm)
+- **Framework:** NestJS 11 (**Fastify**)
+- **Database:** PostgreSQL + **Drizzle ORM**
+- **Validation:** Zod
+- **API:** Swagger (OpenAPI)
+- **Infrastructure:** Docker (Multi-stage builds)
+- **Testing:** Vitest
+
+## Quick Start
+
+### 1. Окружение
+
+Скопируйте пример файла окружения и настройте переменные (БД, API ключи DeepSeek):
+
+```bash
+cp .env.example .env
+```
+
+### 2. Запуск через Docker (Рекомендуется)
+
+Проект полностью контейнеризирован:
+
+```bash
+docker-compose up --build
+```
+
+### 3. Локальный запуск
+
+Если вы хотите запустить проект без Docker:
+
+```bash
+pnpm install
+pnpm db:generate
+pnpm db:migrate
+pnpm start:dev
+```
+
+## API Documentation
+
+После запуска проекта документация доступна по адресу:
+
+**http://localhost:3000/api/v1/docs**
+
+## Infrastructure
+
+- CI/CD: Настроены GitHub Actions для автоматической проверки типов, линтинга и запуска тестов.
+- Docker: Используются оптимизированные multi-stage образы для минимизации размера production-билда.
From ed00545b5bf7a476f6bd55254ad2801b0f94213a Mon Sep 17 00:00:00 2001
From: Maksym Berehovyi <108676512+maksberegovoi@users.noreply.github.com>
Date: Fri, 10 Apr 2026 23:40:31 +0300
Subject: [PATCH 09/30] chore: setup husky, commitlint and lint-staged
* docs(contributing): add chore to allowed commit types
* chore: setup husky, commitlint and lint-staged
* chore: change lint-staged config from json to mjs
---
.commitlintrc.mjs | 3 +
.husky/commit-msg | 1 +
.lintstagedrc.mjs | 2 +-
CONTRIBUTING.md | 1 +
package.json | 144 +++++++-------
pnpm-lock.yaml | 480 +++++++++++++++++++++++++++++++++++++++++++++-
6 files changed, 552 insertions(+), 79 deletions(-)
create mode 100644 .commitlintrc.mjs
create mode 100755 .husky/commit-msg
diff --git a/.commitlintrc.mjs b/.commitlintrc.mjs
new file mode 100644
index 0000000..803dc4f
--- /dev/null
+++ b/.commitlintrc.mjs
@@ -0,0 +1,3 @@
+export default {
+ extends: ['@commitlint/config-conventional'],
+};
diff --git a/.husky/commit-msg b/.husky/commit-msg
new file mode 100755
index 0000000..0a4b97d
--- /dev/null
+++ b/.husky/commit-msg
@@ -0,0 +1 @@
+npx --no -- commitlint --edit $1
diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs
index 68caded..90da1d6 100644
--- a/.lintstagedrc.mjs
+++ b/.lintstagedrc.mjs
@@ -1,4 +1,4 @@
export default {
- '*.{ts,js}': ['eslint --fix', 'prettier --write'],
+ '*.{ts,js}': ['eslint --fix'],
'*.{json,css,md}': ['prettier --write'],
};
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8272506..6aa8cca 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -15,6 +15,7 @@
- `feat/` — для новых функциональных возможностей.
- `fix/` — для исправления багов.
- `refactor/` — для переписывания кода без изменения логики.
+- `chore/` — для технических задач, настройки окружения и зависимостей
- `docs/` — для обновления документации.
## 2. Commit Message Convention
diff --git a/package.json b/package.json
index 1468528..e770cf6 100644
--- a/package.json
+++ b/package.json
@@ -1,71 +1,75 @@
{
- "name": "task-backend",
- "version": "0.0.1",
- "description": "",
- "author": "",
- "private": true,
- "license": "UNLICENSED",
- "scripts": {
- "build": "nest build",
- "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"libs/**/*.ts\"",
- "start": "nest start",
- "start:dev": "nest start --watch",
- "start:debug": "nest start --debug --watch",
- "start:prod": "node dist/main",
- "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
- "test": "vitest run",
- "test:watch": "vitest",
- "test:cov": "vitest run --coverage",
- "test:debug": "vitest --inspect-brk --inspect --logHeapUsage --threads=false",
- "test:e2e": "vitest run --config ./vitest.config.e2e.ts",
- "db:generate": "drizzle-kit generate",
- "db:migrate": "drizzle-kit migrate",
- "db:studio": "drizzle-kit studio"
- },
- "dependencies": {
- "@fastify/compress": "^8.3.1",
- "@fastify/cookie": "^11.0.2",
- "@fastify/cors": "^11.2.0",
- "@fastify/static": "^9.1.0",
- "@nestjs/common": "^11.1.18",
- "@nestjs/config": "^4.0.4",
- "@nestjs/core": "^11.1.18",
- "@nestjs/platform-fastify": "^11.1.18",
- "@nestjs/swagger": "^11.2.7",
- "@nestjs/throttler": "^6.5.0",
- "drizzle-orm": "^0.45.2",
- "drizzle-zod": "^0.8.3",
- "fastify": "^5.8.4",
- "nestjs-zod": "^5.3.0",
- "pg": "^8.20.0",
- "reflect-metadata": "^0.2.0",
- "rxjs": "^7.8.1",
- "zod": "^4.3.6"
- },
- "devDependencies": {
- "@nestjs/cli": "^11.0.19",
- "@nestjs/schematics": "^11.0.10",
- "@nestjs/testing": "^11.1.18",
- "@types/node": "^20.3.1",
- "@types/pg": "^8.20.0",
- "@types/supertest": "^6.0.0",
- "@typescript-eslint/eslint-plugin": "^6.0.0",
- "@typescript-eslint/parser": "^6.0.0",
- "@vitest/coverage-v8": "^4.1.4",
- "drizzle-kit": "^0.31.10",
- "eslint": "^8.42.0",
- "eslint-config-prettier": "^9.0.0",
- "eslint-plugin-prettier": "^5.0.0",
- "lint-staged": "^16.4.0",
- "prettier": "^3.0.0",
- "source-map-support": "^0.5.21",
- "supertest": "^6.3.3",
- "ts-loader": "^9.4.3",
- "ts-node": "^10.9.1",
- "tsconfig-paths": "^4.2.0",
- "typescript": "^5.1.3",
- "unplugin-swc": "^1.5.9",
- "vitest": "^4.1.4"
- },
- "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
-}
\ No newline at end of file
+ "name": "task-backend",
+ "version": "0.0.1",
+ "description": "",
+ "author": "",
+ "private": true,
+ "license": "UNLICENSED",
+ "scripts": {
+ "build": "nest build",
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"libs/**/*.ts\"",
+ "start": "nest start",
+ "start:dev": "nest start --watch",
+ "start:debug": "nest start --debug --watch",
+ "start:prod": "node dist/main",
+ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "test:cov": "vitest run --coverage",
+ "test:debug": "vitest --inspect-brk --inspect --logHeapUsage --threads=false",
+ "test:e2e": "vitest run --config ./vitest.config.e2e.ts",
+ "db:generate": "drizzle-kit generate",
+ "db:migrate": "drizzle-kit migrate",
+ "db:studio": "drizzle-kit studio",
+ "prepare": "husky"
+ },
+ "dependencies": {
+ "@fastify/compress": "^8.3.1",
+ "@fastify/cookie": "^11.0.2",
+ "@fastify/cors": "^11.2.0",
+ "@fastify/static": "^9.1.0",
+ "@nestjs/common": "^11.1.18",
+ "@nestjs/config": "^4.0.4",
+ "@nestjs/core": "^11.1.18",
+ "@nestjs/platform-fastify": "^11.1.18",
+ "@nestjs/swagger": "^11.2.7",
+ "@nestjs/throttler": "^6.5.0",
+ "drizzle-orm": "^0.45.2",
+ "drizzle-zod": "^0.8.3",
+ "fastify": "^5.8.4",
+ "nestjs-zod": "^5.3.0",
+ "pg": "^8.20.0",
+ "reflect-metadata": "^0.2.0",
+ "rxjs": "^7.8.1",
+ "zod": "^4.3.6"
+ },
+ "devDependencies": {
+ "@commitlint/cli": "^20.5.0",
+ "@commitlint/config-conventional": "^20.5.0",
+ "@nestjs/cli": "^11.0.19",
+ "@nestjs/schematics": "^11.0.10",
+ "@nestjs/testing": "^11.1.18",
+ "@types/node": "^20.3.1",
+ "@types/pg": "^8.20.0",
+ "@types/supertest": "^6.0.0",
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
+ "@typescript-eslint/parser": "^6.0.0",
+ "@vitest/coverage-v8": "^4.1.4",
+ "drizzle-kit": "^0.31.10",
+ "eslint": "^8.42.0",
+ "eslint-config-prettier": "^9.0.0",
+ "eslint-plugin-prettier": "^5.0.0",
+ "husky": "^9.1.7",
+ "lint-staged": "^16.4.0",
+ "prettier": "^3.0.0",
+ "source-map-support": "^0.5.21",
+ "supertest": "^6.3.3",
+ "ts-loader": "^9.4.3",
+ "ts-node": "^10.9.1",
+ "tsconfig-paths": "^4.2.0",
+ "typescript": "^5.1.3",
+ "unplugin-swc": "^1.5.9",
+ "vitest": "^4.1.4"
+ },
+ "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3c4618f..f1b9c94 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -63,6 +63,12 @@ importers:
specifier: ^4.3.6
version: 4.3.6
devDependencies:
+ '@commitlint/cli':
+ specifier: ^20.5.0
+ version: 20.5.0(@types/node@20.19.39)(conventional-commits-parser@6.4.0)(typescript@5.9.3)
+ '@commitlint/config-conventional':
+ specifier: ^20.5.0
+ version: 20.5.0
'@nestjs/cli':
specifier: ^11.0.19
version: 11.0.19(@swc/core@1.15.24)(@types/node@20.19.39)(esbuild@0.27.7)
@@ -102,6 +108,9 @@ importers:
eslint-plugin-prettier:
specifier: ^5.0.0
version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.8.2)
+ husky:
+ specifier: ^9.1.7
+ version: 9.1.7
lint-staged:
specifier: ^16.4.0
version: 16.4.0
@@ -131,7 +140,7 @@ importers:
version: 1.5.9(@swc/core@1.15.24)
vitest:
specifier: ^4.1.4
- version: 4.1.4(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
+ version: 4.1.4(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
packages:
@@ -198,6 +207,87 @@ packages:
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
engines: {node: '>=0.1.90'}
+ '@commitlint/cli@20.5.0':
+ resolution: {integrity: sha512-yNkyN/tuKTJS3wdVfsZ2tXDM4G4Gi7z+jW54Cki8N8tZqwKBltbIvUUrSbT4hz1bhW/h0CdR+5sCSpXD+wMKaQ==}
+ engines: {node: '>=v18'}
+ hasBin: true
+
+ '@commitlint/config-conventional@20.5.0':
+ resolution: {integrity: sha512-t3Ni88rFw1XMa4nZHgOKJ8fIAT9M2j5TnKyTqJzsxea7FUetlNdYFus9dz+MhIRZmc16P0PPyEfh6X2d/qw8SA==}
+ engines: {node: '>=v18'}
+
+ '@commitlint/config-validator@20.5.0':
+ resolution: {integrity: sha512-T/Uh6iJUzyx7j35GmHWdIiGRQB+ouZDk0pwAaYq4SXgB54KZhFdJ0vYmxiW6AMYICTIWuyMxDBl1jK74oFp/Gw==}
+ engines: {node: '>=v18'}
+
+ '@commitlint/ensure@20.5.0':
+ resolution: {integrity: sha512-IpHqAUesBeW1EDDdjzJeaOxU9tnogLAyXLRBn03SHlj1SGENn2JGZqSWGkFvBJkJzfXAuCNtsoYzax+ZPS+puw==}
+ engines: {node: '>=v18'}
+
+ '@commitlint/execute-rule@20.0.0':
+ resolution: {integrity: sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==}
+ engines: {node: '>=v18'}
+
+ '@commitlint/format@20.5.0':
+ resolution: {integrity: sha512-TI9EwFU/qZWSK7a5qyXMpKPPv3qta7FO4tKW+Wt2al7sgMbLWTsAcDpX1cU8k16TRdsiiet9aOw0zpvRXNJu7Q==}
+ engines: {node: '>=v18'}
+
+ '@commitlint/is-ignored@20.5.0':
+ resolution: {integrity: sha512-JWLarAsurHJhPozbuAH6GbP4p/hdOCoqS9zJMfqwswne+/GPs5V0+rrsfOkP68Y8PSLphwtFXV0EzJ+GTXTTGg==}
+ engines: {node: '>=v18'}
+
+ '@commitlint/lint@20.5.0':
+ resolution: {integrity: sha512-jiM3hNUdu04jFBf1VgPdjtIPvbuVfDTBAc6L98AWcoLjF5sYqkulBHBzlVWll4rMF1T5zeQFB6r//a+s+BBKlA==}
+ engines: {node: '>=v18'}
+
+ '@commitlint/load@20.5.0':
+ resolution: {integrity: sha512-sLhhYTL/KxeOTZjjabKDhwidGZan84XKK1+XFkwDYL/4883kIajcz/dZFAhBJmZPtL8+nBx6bnkzA95YxPeDPw==}
+ engines: {node: '>=v18'}
+
+ '@commitlint/message@20.4.3':
+ resolution: {integrity: sha512-6akwCYrzcrFcTYz9GyUaWlhisY4lmQ3KvrnabmhoeAV8nRH4dXJAh4+EUQ3uArtxxKQkvxJS78hNX2EU3USgxQ==}
+ engines: {node: '>=v18'}
+
+ '@commitlint/parse@20.5.0':
+ resolution: {integrity: sha512-SeKWHBMk7YOTnnEWUhx+d1a9vHsjjuo6Uo1xRfPNfeY4bdYFasCH1dDpAv13Lyn+dDPOels+jP6D2GRZqzc5fA==}
+ engines: {node: '>=v18'}
+
+ '@commitlint/read@20.5.0':
+ resolution: {integrity: sha512-JDEIJ2+GnWpK8QqwfmW7O42h0aycJEWNqcdkJnyzLD11nf9dW2dWLTVEa8Wtlo4IZFGLPATjR5neA5QlOvIH1w==}
+ engines: {node: '>=v18'}
+
+ '@commitlint/resolve-extends@20.5.0':
+ resolution: {integrity: sha512-3SHPWUW2v0tyspCTcfSsYml0gses92l6TlogwzvM2cbxDgmhSRc+fldDjvGkCXJrjSM87BBaWYTPWwwyASZRrg==}
+ engines: {node: '>=v18'}
+
+ '@commitlint/rules@20.5.0':
+ resolution: {integrity: sha512-5NdQXQEdnDPT5pK8O39ZA7HohzPRHEsDGU23cyVCNPQy4WegAbAwrQk3nIu7p2sl3dutPk8RZd91yKTrMTnRkQ==}
+ engines: {node: '>=v18'}
+
+ '@commitlint/to-lines@20.0.0':
+ resolution: {integrity: sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==}
+ engines: {node: '>=v18'}
+
+ '@commitlint/top-level@20.4.3':
+ resolution: {integrity: sha512-qD9xfP6dFg5jQ3NMrOhG0/w5y3bBUsVGyJvXxdWEwBm8hyx4WOk3kKXw28T5czBYvyeCVJgJJ6aoJZUWDpaacQ==}
+ engines: {node: '>=v18'}
+
+ '@commitlint/types@20.5.0':
+ resolution: {integrity: sha512-ZJoS8oSq2CAZEpc/YI9SulLrdiIyXeHb/OGqGrkUP6Q7YV+0ouNAa7GjqRdXeQPncHQIDz/jbCTlHScvYvO/gA==}
+ engines: {node: '>=v18'}
+
+ '@conventional-changelog/git-client@2.7.0':
+ resolution: {integrity: sha512-j7A8/LBEQ+3rugMzPXoKYzyUPpw/0CBQCyvtTR7Lmu4olG4yRC/Tfkq79Mr3yuPs0SUitlO2HwGP3gitMJnRFw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ conventional-commits-filter: ^5.0.0
+ conventional-commits-parser: ^6.4.0
+ peerDependenciesMeta:
+ conventional-commits-filter:
+ optional: true
+ conventional-commits-parser:
+ optional: true
+
'@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
@@ -1180,6 +1270,14 @@ packages:
'@scarf/scarf@1.4.0':
resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==}
+ '@simple-libs/child-process-utils@1.0.2':
+ resolution: {integrity: sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==}
+ engines: {node: '>=18'}
+
+ '@simple-libs/stream-utils@1.2.0':
+ resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==}
+ engines: {node: '>=18'}
+
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
@@ -1580,6 +1678,9 @@ packages:
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ array-ify@1.0.0:
+ resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==}
+
array-timsort@1.0.3:
resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==}
@@ -1711,6 +1812,10 @@ packages:
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
engines: {node: '>= 12'}
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
+
clone@1.0.4:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
@@ -1744,6 +1849,9 @@ packages:
resolution: {integrity: sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==}
engines: {node: '>= 6'}
+ compare-func@2.0.0:
+ resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==}
+
component-emitter@1.3.1:
resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
@@ -1758,6 +1866,19 @@ packages:
resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==}
engines: {node: '>=18'}
+ conventional-changelog-angular@8.3.1:
+ resolution: {integrity: sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==}
+ engines: {node: '>=18'}
+
+ conventional-changelog-conventionalcommits@9.3.1:
+ resolution: {integrity: sha512-dTYtpIacRpcZgrvBYvBfArMmK2xvIpv2TaxM0/ZI5CBtNUzvF2x0t15HsbRABWprS6UPmvj+PzHVjSx4qAVKyw==}
+ engines: {node: '>=18'}
+
+ conventional-commits-parser@6.4.0:
+ resolution: {integrity: sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==}
+ engines: {node: '>=18'}
+ hasBin: true
+
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
@@ -1771,6 +1892,14 @@ packages:
core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
+ cosmiconfig-typescript-loader@6.3.0:
+ resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==}
+ engines: {node: '>=v18'}
+ peerDependencies:
+ '@types/node': '*'
+ cosmiconfig: '>=9'
+ typescript: '>=5'
+
cosmiconfig@8.3.6:
resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
engines: {node: '>=14'}
@@ -1780,6 +1909,15 @@ packages:
typescript:
optional: true
+ cosmiconfig@9.0.1:
+ resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ typescript: '>=4.9.5'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
@@ -1837,6 +1975,10 @@ packages:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'}
+ dot-prop@5.3.0:
+ resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
+ engines: {node: '>=8'}
+
dotenv-expand@12.0.3:
resolution: {integrity: sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==}
engines: {node: '>=12'}
@@ -1977,6 +2119,10 @@ packages:
resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
engines: {node: '>=10.13.0'}
+ env-paths@2.2.1:
+ resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
+ engines: {node: '>=6'}
+
environment@1.1.0:
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
engines: {node: '>=18'}
@@ -2225,6 +2371,10 @@ packages:
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
get-east-asian-width@1.5.0:
resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
engines: {node: '>=18'}
@@ -2240,6 +2390,11 @@ packages:
get-tsconfig@4.13.7:
resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==}
+ git-raw-commits@5.0.1:
+ resolution: {integrity: sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==}
+ engines: {node: '>=18'}
+ hasBin: true
+
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -2259,6 +2414,10 @@ packages:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
+ global-directory@4.0.1:
+ resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
+ engines: {node: '>=18'}
+
globals@13.24.0:
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
engines: {node: '>=8'}
@@ -2300,6 +2459,11 @@ packages:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
+ husky@9.1.7:
+ resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
+ engines: {node: '>=18'}
+ hasBin: true
+
iconv-lite@0.7.2:
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
engines: {node: '>=0.10.0'}
@@ -2315,6 +2479,9 @@ packages:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
+ import-meta-resolve@4.2.0:
+ resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==}
+
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
@@ -2326,6 +2493,10 @@ packages:
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+ ini@4.1.1:
+ resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
ipaddr.js@2.3.0:
resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==}
engines: {node: '>= 10'}
@@ -2357,10 +2528,18 @@ packages:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
+ is-obj@2.0.0:
+ resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
+ engines: {node: '>=8'}
+
is-path-inside@3.0.3:
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
engines: {node: '>=8'}
+ is-plain-obj@4.1.0:
+ resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
+ engines: {node: '>=12'}
+
is-unicode-supported@0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
@@ -2391,6 +2570,10 @@ packages:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
engines: {node: '>= 10.13.0'}
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
+ hasBin: true
+
js-tokens@10.0.0:
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
@@ -2542,9 +2725,27 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
+ lodash.camelcase@4.3.0:
+ resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
+
+ lodash.kebabcase@4.1.1:
+ resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==}
+
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+ lodash.mergewith@4.6.2:
+ resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==}
+
+ lodash.snakecase@4.1.1:
+ resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==}
+
+ lodash.startcase@4.4.0:
+ resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==}
+
+ lodash.upperfirst@4.3.1:
+ resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==}
+
lodash@4.18.1:
resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
@@ -2584,6 +2785,10 @@ packages:
resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==}
engines: {node: '>= 4.0.0'}
+ meow@13.2.0:
+ resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==}
+ engines: {node: '>=18'}
+
merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
@@ -2903,6 +3108,10 @@ packages:
reflect-metadata@0.2.2:
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
@@ -2911,6 +3120,10 @@ packages:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
+ resolve-from@5.0.0:
+ resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
+ engines: {node: '>=8'}
+
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
@@ -3438,6 +3651,10 @@ packages:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
wrap-ansi@9.0.2:
resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
engines: {node: '>=18'}
@@ -3449,6 +3666,10 @@ packages:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
yaml@2.8.3:
resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==}
engines: {node: '>= 14.6'}
@@ -3458,6 +3679,10 @@ packages:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
+ yargs@17.7.2:
+ resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+ engines: {node: '>=12'}
+
yn@3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
@@ -3555,6 +3780,128 @@ snapshots:
'@colors/colors@1.5.0':
optional: true
+ '@commitlint/cli@20.5.0(@types/node@20.19.39)(conventional-commits-parser@6.4.0)(typescript@5.9.3)':
+ dependencies:
+ '@commitlint/format': 20.5.0
+ '@commitlint/lint': 20.5.0
+ '@commitlint/load': 20.5.0(@types/node@20.19.39)(typescript@5.9.3)
+ '@commitlint/read': 20.5.0(conventional-commits-parser@6.4.0)
+ '@commitlint/types': 20.5.0
+ tinyexec: 1.1.1
+ yargs: 17.7.2
+ transitivePeerDependencies:
+ - '@types/node'
+ - conventional-commits-filter
+ - conventional-commits-parser
+ - typescript
+
+ '@commitlint/config-conventional@20.5.0':
+ dependencies:
+ '@commitlint/types': 20.5.0
+ conventional-changelog-conventionalcommits: 9.3.1
+
+ '@commitlint/config-validator@20.5.0':
+ dependencies:
+ '@commitlint/types': 20.5.0
+ ajv: 8.18.0
+
+ '@commitlint/ensure@20.5.0':
+ dependencies:
+ '@commitlint/types': 20.5.0
+ lodash.camelcase: 4.3.0
+ lodash.kebabcase: 4.1.1
+ lodash.snakecase: 4.1.1
+ lodash.startcase: 4.4.0
+ lodash.upperfirst: 4.3.1
+
+ '@commitlint/execute-rule@20.0.0': {}
+
+ '@commitlint/format@20.5.0':
+ dependencies:
+ '@commitlint/types': 20.5.0
+ picocolors: 1.1.1
+
+ '@commitlint/is-ignored@20.5.0':
+ dependencies:
+ '@commitlint/types': 20.5.0
+ semver: 7.7.4
+
+ '@commitlint/lint@20.5.0':
+ dependencies:
+ '@commitlint/is-ignored': 20.5.0
+ '@commitlint/parse': 20.5.0
+ '@commitlint/rules': 20.5.0
+ '@commitlint/types': 20.5.0
+
+ '@commitlint/load@20.5.0(@types/node@20.19.39)(typescript@5.9.3)':
+ dependencies:
+ '@commitlint/config-validator': 20.5.0
+ '@commitlint/execute-rule': 20.0.0
+ '@commitlint/resolve-extends': 20.5.0
+ '@commitlint/types': 20.5.0
+ cosmiconfig: 9.0.1(typescript@5.9.3)
+ cosmiconfig-typescript-loader: 6.3.0(@types/node@20.19.39)(cosmiconfig@9.0.1(typescript@5.9.3))(typescript@5.9.3)
+ is-plain-obj: 4.1.0
+ lodash.mergewith: 4.6.2
+ picocolors: 1.1.1
+ transitivePeerDependencies:
+ - '@types/node'
+ - typescript
+
+ '@commitlint/message@20.4.3': {}
+
+ '@commitlint/parse@20.5.0':
+ dependencies:
+ '@commitlint/types': 20.5.0
+ conventional-changelog-angular: 8.3.1
+ conventional-commits-parser: 6.4.0
+
+ '@commitlint/read@20.5.0(conventional-commits-parser@6.4.0)':
+ dependencies:
+ '@commitlint/top-level': 20.4.3
+ '@commitlint/types': 20.5.0
+ git-raw-commits: 5.0.1(conventional-commits-parser@6.4.0)
+ minimist: 1.2.8
+ tinyexec: 1.1.1
+ transitivePeerDependencies:
+ - conventional-commits-filter
+ - conventional-commits-parser
+
+ '@commitlint/resolve-extends@20.5.0':
+ dependencies:
+ '@commitlint/config-validator': 20.5.0
+ '@commitlint/types': 20.5.0
+ global-directory: 4.0.1
+ import-meta-resolve: 4.2.0
+ lodash.mergewith: 4.6.2
+ resolve-from: 5.0.0
+
+ '@commitlint/rules@20.5.0':
+ dependencies:
+ '@commitlint/ensure': 20.5.0
+ '@commitlint/message': 20.4.3
+ '@commitlint/to-lines': 20.0.0
+ '@commitlint/types': 20.5.0
+
+ '@commitlint/to-lines@20.0.0': {}
+
+ '@commitlint/top-level@20.4.3':
+ dependencies:
+ escalade: 3.2.0
+
+ '@commitlint/types@20.5.0':
+ dependencies:
+ conventional-commits-parser: 6.4.0
+ picocolors: 1.1.1
+
+ '@conventional-changelog/git-client@2.7.0(conventional-commits-parser@6.4.0)':
+ dependencies:
+ '@simple-libs/child-process-utils': 1.0.2
+ '@simple-libs/stream-utils': 1.2.0
+ semver: 7.7.4
+ optionalDependencies:
+ conventional-commits-parser: 6.4.0
+
'@cspotcode/source-map-support@0.8.1':
dependencies:
'@jridgewell/trace-mapping': 0.3.9
@@ -4300,6 +4647,12 @@ snapshots:
'@scarf/scarf@1.4.0': {}
+ '@simple-libs/child-process-utils@1.0.2':
+ dependencies:
+ '@simple-libs/stream-utils': 1.2.0
+
+ '@simple-libs/stream-utils@1.2.0': {}
+
'@standard-schema/spec@1.1.0': {}
'@swc/core-darwin-arm64@1.15.24':
@@ -4533,7 +4886,7 @@ snapshots:
obug: 2.1.1
std-env: 4.0.0
tinyrainbow: 3.1.0
- vitest: 4.1.4(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
+ vitest: 4.1.4(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
'@vitest/expect@4.1.4':
dependencies:
@@ -4544,13 +4897,13 @@ snapshots:
chai: 6.2.2
tinyrainbow: 3.1.0
- '@vitest/mocker@4.1.4(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))':
+ '@vitest/mocker@4.1.4(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))':
dependencies:
'@vitest/spy': 4.1.4
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
- vite: 8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
+ vite: 8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
'@vitest/pretty-format@4.1.4':
dependencies:
@@ -4729,6 +5082,8 @@ snapshots:
argparse@2.0.1: {}
+ array-ify@1.0.0: {}
+
array-timsort@1.0.3: {}
array-union@2.1.0: {}
@@ -4855,6 +5210,12 @@ snapshots:
cli-width@4.1.0: {}
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
clone@1.0.4: {}
color-convert@2.0.1:
@@ -4880,6 +5241,11 @@ snapshots:
array-timsort: 1.0.3
esprima: 4.0.1
+ compare-func@2.0.0:
+ dependencies:
+ array-ify: 1.0.0
+ dot-prop: 5.3.0
+
component-emitter@1.3.1: {}
concat-map@0.0.1: {}
@@ -4888,6 +5254,19 @@ snapshots:
content-disposition@1.1.0: {}
+ conventional-changelog-angular@8.3.1:
+ dependencies:
+ compare-func: 2.0.0
+
+ conventional-changelog-conventionalcommits@9.3.1:
+ dependencies:
+ compare-func: 2.0.0
+
+ conventional-commits-parser@6.4.0:
+ dependencies:
+ '@simple-libs/stream-utils': 1.2.0
+ meow: 13.2.0
+
convert-source-map@2.0.0: {}
cookie@1.1.1: {}
@@ -4896,6 +5275,13 @@ snapshots:
core-util-is@1.0.3: {}
+ cosmiconfig-typescript-loader@6.3.0(@types/node@20.19.39)(cosmiconfig@9.0.1(typescript@5.9.3))(typescript@5.9.3):
+ dependencies:
+ '@types/node': 20.19.39
+ cosmiconfig: 9.0.1(typescript@5.9.3)
+ jiti: 2.6.1
+ typescript: 5.9.3
+
cosmiconfig@8.3.6(typescript@5.9.3):
dependencies:
import-fresh: 3.3.1
@@ -4905,6 +5291,15 @@ snapshots:
optionalDependencies:
typescript: 5.9.3
+ cosmiconfig@9.0.1(typescript@5.9.3):
+ dependencies:
+ env-paths: 2.2.1
+ import-fresh: 3.3.1
+ js-yaml: 4.1.1
+ parse-json: 5.2.0
+ optionalDependencies:
+ typescript: 5.9.3
+
create-require@1.1.1: {}
cross-spawn@7.0.6:
@@ -4948,6 +5343,10 @@ snapshots:
dependencies:
esutils: 2.0.3
+ dot-prop@5.3.0:
+ dependencies:
+ is-obj: 2.0.0
+
dotenv-expand@12.0.3:
dependencies:
dotenv: 16.6.1
@@ -5008,6 +5407,8 @@ snapshots:
graceful-fs: 4.2.11
tapable: 2.3.2
+ env-paths@2.2.1: {}
+
environment@1.1.0: {}
error-ex@1.3.4:
@@ -5371,6 +5772,8 @@ snapshots:
function-bind@1.1.2: {}
+ get-caller-file@2.0.5: {}
+
get-east-asian-width@1.5.0: {}
get-intrinsic@1.3.0:
@@ -5395,6 +5798,14 @@ snapshots:
dependencies:
resolve-pkg-maps: 1.0.0
+ git-raw-commits@5.0.1(conventional-commits-parser@6.4.0):
+ dependencies:
+ '@conventional-changelog/git-client': 2.7.0(conventional-commits-parser@6.4.0)
+ meow: 13.2.0
+ transitivePeerDependencies:
+ - conventional-commits-filter
+ - conventional-commits-parser
+
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
@@ -5420,6 +5831,10 @@ snapshots:
once: 1.4.0
path-is-absolute: 1.0.1
+ global-directory@4.0.1:
+ dependencies:
+ ini: 4.1.1
+
globals@13.24.0:
dependencies:
type-fest: 0.20.2
@@ -5461,6 +5876,8 @@ snapshots:
statuses: 2.0.2
toidentifier: 1.0.1
+ husky@9.1.7: {}
+
iconv-lite@0.7.2:
dependencies:
safer-buffer: 2.1.2
@@ -5474,6 +5891,8 @@ snapshots:
parent-module: 1.0.1
resolve-from: 4.0.0
+ import-meta-resolve@4.2.0: {}
+
imurmurhash@0.1.4: {}
inflight@1.0.6:
@@ -5483,6 +5902,8 @@ snapshots:
inherits@2.0.4: {}
+ ini@4.1.1: {}
+
ipaddr.js@2.3.0: {}
is-arrayish@0.2.1: {}
@@ -5503,8 +5924,12 @@ snapshots:
is-number@7.0.0: {}
+ is-obj@2.0.0: {}
+
is-path-inside@3.0.3: {}
+ is-plain-obj@4.1.0: {}
+
is-unicode-supported@0.1.0: {}
isarray@1.0.0: {}
@@ -5532,6 +5957,8 @@ snapshots:
merge-stream: 2.0.0
supports-color: 8.1.1
+ jiti@2.6.1: {}
+
js-tokens@10.0.0: {}
js-tokens@4.0.0: {}
@@ -5658,8 +6085,20 @@ snapshots:
dependencies:
p-locate: 5.0.0
+ lodash.camelcase@4.3.0: {}
+
+ lodash.kebabcase@4.1.1: {}
+
lodash.merge@4.6.2: {}
+ lodash.mergewith@4.6.2: {}
+
+ lodash.snakecase@4.1.1: {}
+
+ lodash.startcase@4.4.0: {}
+
+ lodash.upperfirst@4.3.1: {}
+
lodash@4.18.1: {}
log-symbols@4.1.0:
@@ -5703,6 +6142,8 @@ snapshots:
dependencies:
fs-monkey: 1.1.0
+ meow@13.2.0: {}
+
merge-stream@2.0.0: {}
merge2@1.4.1: {}
@@ -5998,10 +6439,14 @@ snapshots:
reflect-metadata@0.2.2: {}
+ require-directory@2.1.1: {}
+
require-from-string@2.0.2: {}
resolve-from@4.0.0: {}
+ resolve-from@5.0.0: {}
+
resolve-pkg-maps@1.0.0: {}
restore-cursor@3.1.0:
@@ -6412,7 +6857,7 @@ snapshots:
v8-compile-cache-lib@3.0.1: {}
- vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
+ vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.4
@@ -6423,14 +6868,15 @@ snapshots:
'@types/node': 20.19.39
esbuild: 0.27.7
fsevents: 2.3.3
+ jiti: 2.6.1
terser: 5.46.1
tsx: 4.21.0
yaml: 2.8.3
- vitest@4.1.4(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
+ vitest@4.1.4(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
'@vitest/expect': 4.1.4
- '@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
+ '@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
'@vitest/pretty-format': 4.1.4
'@vitest/runner': 4.1.4
'@vitest/snapshot': 4.1.4
@@ -6447,7 +6893,7 @@ snapshots:
tinyexec: 1.1.1
tinyglobby: 0.2.16
tinyrainbow: 3.1.0
- vite: 8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
+ vite: 8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 20.19.39
@@ -6519,6 +6965,12 @@ snapshots:
string-width: 4.2.3
strip-ansi: 6.0.1
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
wrap-ansi@9.0.2:
dependencies:
ansi-styles: 6.2.3
@@ -6529,10 +6981,22 @@ snapshots:
xtend@4.0.2: {}
+ y18n@5.0.8: {}
+
yaml@2.8.3: {}
yargs-parser@21.1.1: {}
+ yargs@17.7.2:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
+
yn@3.1.1: {}
yocto-queue@0.1.0: {}
From 417bbf68660a93cbc6c95cc14f48339e8ce1def4 Mon Sep 17 00:00:00 2001
From: Danil <123034340+soorq@users.noreply.github.com>
Date: Fri, 10 Apr 2026 23:56:45 +0300
Subject: [PATCH 10/30] feat: implement health check and dump modules
---
.../src/controller/health.controller.ts | 39 ++++++++
libs/health/src/controller/health.swagger.ts | 26 +++++
libs/health/src/dtos/health.dto.ts | 25 +++++
libs/health/src/dtos/index.ts | 1 +
libs/health/src/health.module.ts | 21 ++++
libs/health/src/health.service.ts | 51 ++++++++++
libs/health/src/index.ts | 1 +
libs/health/tsconfig.lib.json | 9 ++
nest-cli.json | 17 +++-
package.json | 1 +
pnpm-lock.yaml | 59 +++++++++--
src/app.controller.ts | 12 ---
src/app.service.ts | 8 --
src/main.ts | 2 +-
src/{ => modules/app}/app.controller.spec.ts | 2 -
src/modules/app/app.controller.ts | 11 +++
src/{ => modules/app}/app.module.ts | 15 ++-
test/app.e2e-spec.ts | 2 +-
tsconfig.json | 97 ++++++++++++-------
19 files changed, 324 insertions(+), 75 deletions(-)
create mode 100644 libs/health/src/controller/health.controller.ts
create mode 100644 libs/health/src/controller/health.swagger.ts
create mode 100644 libs/health/src/dtos/health.dto.ts
create mode 100644 libs/health/src/dtos/index.ts
create mode 100644 libs/health/src/health.module.ts
create mode 100644 libs/health/src/health.service.ts
create mode 100644 libs/health/src/index.ts
create mode 100644 libs/health/tsconfig.lib.json
delete mode 100644 src/app.controller.ts
delete mode 100644 src/app.service.ts
rename src/{ => modules/app}/app.controller.spec.ts (87%)
create mode 100644 src/modules/app/app.controller.ts
rename src/{ => modules/app}/app.module.ts (71%)
diff --git a/libs/health/src/controller/health.controller.ts b/libs/health/src/controller/health.controller.ts
new file mode 100644
index 0000000..cba9bba
--- /dev/null
+++ b/libs/health/src/controller/health.controller.ts
@@ -0,0 +1,39 @@
+import { Controller, Get, HttpException, HttpStatus, Inject, Logger } from '@nestjs/common';
+import { SkipThrottle } from '@nestjs/throttler';
+import { HealthService } from '../health.service';
+import { GetHealthSwagger, GetPingSwagger } from './health.swagger';
+import { ApiTags } from '@nestjs/swagger';
+
+@SkipThrottle()
+@Controller()
+@ApiTags('System')
+export class HealthController {
+ private logger = new Logger(HealthController.name);
+
+ constructor(
+ private readonly healthService: HealthService,
+ @Inject('SERVICE_NAME') private readonly serviceName: string,
+ ) {}
+
+ @Get('health')
+ @GetHealthSwagger()
+ async checkHealth() {
+ const pingData = await this.healthService.getHealthData();
+
+ if (pingData.status !== 'up') {
+ this.logger.error(`${this.serviceName} is unhealthy!`);
+ throw new HttpException(
+ `${this.serviceName} service is unhealthy.`,
+ HttpStatus.SERVICE_UNAVAILABLE,
+ );
+ }
+
+ return 'healthy';
+ }
+
+ @Get('ping')
+ @GetPingSwagger()
+ async ping() {
+ return this.healthService.getHealthData();
+ }
+}
diff --git a/libs/health/src/controller/health.swagger.ts b/libs/health/src/controller/health.swagger.ts
new file mode 100644
index 0000000..2271969
--- /dev/null
+++ b/libs/health/src/controller/health.swagger.ts
@@ -0,0 +1,26 @@
+import { applyDecorators } from '@nestjs/common';
+import { ApiOperation, ApiResponse } from '@nestjs/swagger';
+import { HealthResponse } from '../dtos';
+
+export const GetHealthSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Краткий статус (Health Check)',
+ description: 'Используется внешними системами для проверки доступности сервиса.',
+ }),
+ ApiResponse({ status: 200, description: 'Сервис работает нормально', type: String }),
+ ApiResponse({ status: 503, description: 'Сервис недоступен или критическая ошибка' }),
+ );
+
+export const GetPingSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Детальный дамп состояния',
+ description: 'Возвращает аптайм, время старта и метрики памяти.',
+ }),
+ ApiResponse({
+ status: 200,
+ description: 'Полная статистика сервиса',
+ type: HealthResponse.Output,
+ }),
+ );
diff --git a/libs/health/src/dtos/health.dto.ts b/libs/health/src/dtos/health.dto.ts
new file mode 100644
index 0000000..1877b33
--- /dev/null
+++ b/libs/health/src/dtos/health.dto.ts
@@ -0,0 +1,25 @@
+import { createZodDto } from 'node_modules/nestjs-zod/dist/dto.cjs';
+import { z } from 'zod/v4';
+
+const HealthResponseSchema = z.object({
+ service: z.string().describe('Название сервиса'),
+ status: z.enum(['up', 'down']).describe('Текущий статус'),
+ info: z.object({
+ version: z.string().describe('Версия приложения'),
+ node: z.string().describe('Версия Node.js'),
+ pid: z.number().describe('ID процесса'),
+ }),
+ time: z.object({
+ now: z.string().datetime().describe('Текущее время сервера'),
+ startedAt: z.string().datetime().describe('Время старта сервера'),
+ uptime: z.string().describe('Аптайм в формате ч/м/с'),
+ uptimeSeconds: z.number().describe('Аптайм в секундах'),
+ }),
+ metrics: z.object({
+ rss: z.string().describe('Resident Set Size (общая память)'),
+ heapUsed: z.string().describe('Использованная память в куче'),
+ loadAverage: z.string().describe('Средняя нагрузка на CPU'),
+ }),
+});
+
+export class HealthResponse extends createZodDto(HealthResponseSchema) {}
diff --git a/libs/health/src/dtos/index.ts b/libs/health/src/dtos/index.ts
new file mode 100644
index 0000000..718605a
--- /dev/null
+++ b/libs/health/src/dtos/index.ts
@@ -0,0 +1 @@
+export { HealthResponse } from './health.dto';
diff --git a/libs/health/src/health.module.ts b/libs/health/src/health.module.ts
new file mode 100644
index 0000000..c391e72
--- /dev/null
+++ b/libs/health/src/health.module.ts
@@ -0,0 +1,21 @@
+import { type DynamicModule, Global, Module } from '@nestjs/common';
+import { HealthController } from './controller/health.controller';
+import { HealthService } from './health.service';
+
+@Global()
+@Module({})
+export class HealthModule {
+ static register(serviceName: string): DynamicModule {
+ return {
+ module: HealthModule,
+ providers: [
+ {
+ provide: 'SERVICE_NAME',
+ useValue: serviceName,
+ },
+ HealthService,
+ ],
+ controllers: [HealthController],
+ };
+ }
+}
diff --git a/libs/health/src/health.service.ts b/libs/health/src/health.service.ts
new file mode 100644
index 0000000..076a1a6
--- /dev/null
+++ b/libs/health/src/health.service.ts
@@ -0,0 +1,51 @@
+import { Inject, Injectable } from '@nestjs/common';
+import * as os from 'os';
+
+@Injectable()
+export class HealthService {
+ private readonly startTime: Date;
+
+ constructor(
+ @Inject('SERVICE_NAME')
+ private readonly serviceName: string,
+ ) {
+ this.startTime = new Date();
+ }
+
+ async getHealthData() {
+ const uptimeSeconds = Math.floor(process.uptime());
+ const mem = process.memoryUsage();
+
+ return {
+ service: this.serviceName,
+ status: 'up',
+ info: {
+ version: '1.0.0',
+ node: process.version,
+ pid: process.pid,
+ },
+ time: {
+ now: new Date().toISOString(),
+ startedAt: this.startTime.toISOString(),
+ uptime: this.formatUptime(uptimeSeconds),
+ uptimeSeconds: uptimeSeconds,
+ },
+ metrics: {
+ rss: this.toMb(mem.rss),
+ heapUsed: this.toMb(mem.heapUsed),
+ loadAverage: os.loadavg()[0].toFixed(2),
+ },
+ };
+ }
+
+ private toMb(bytes: number) {
+ return `${Math.round(bytes / 1024 / 1024)}MB`;
+ }
+
+ private formatUptime(seconds: number) {
+ const h = Math.floor(seconds / 3600);
+ const m = Math.floor((seconds % 3600) / 60);
+ const s = seconds % 60;
+ return `${h}h ${m}m ${s}s`;
+ }
+}
diff --git a/libs/health/src/index.ts b/libs/health/src/index.ts
new file mode 100644
index 0000000..f0f0421
--- /dev/null
+++ b/libs/health/src/index.ts
@@ -0,0 +1 @@
+export * from './health.module';
diff --git a/libs/health/tsconfig.lib.json b/libs/health/tsconfig.lib.json
new file mode 100644
index 0000000..8e4d095
--- /dev/null
+++ b/libs/health/tsconfig.lib.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "declaration": true,
+ "outDir": "../../dist/libs/health"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
+}
diff --git a/nest-cli.json b/nest-cli.json
index 8daf27c..5881fec 100644
--- a/nest-cli.json
+++ b/nest-cli.json
@@ -7,6 +7,15 @@
"webpack": true
},
"projects": {
+ "bootstrap": {
+ "type": "library",
+ "root": "libs/bootstrap",
+ "entryFile": "index",
+ "sourceRoot": "libs/bootstrap/src",
+ "compilerOptions": {
+ "tsConfigPath": "libs/bootstrap/tsconfig.lib.json"
+ }
+ },
"config": {
"type": "library",
"root": "libs/config",
@@ -25,13 +34,13 @@
"tsConfigPath": "libs/database/tsconfig.lib.json"
}
},
- "bootstrap": {
+ "health": {
"type": "library",
- "root": "libs/bootstrap",
+ "root": "libs/health",
"entryFile": "index",
- "sourceRoot": "libs/bootstrap/src",
+ "sourceRoot": "libs/health/src",
"compilerOptions": {
- "tsConfigPath": "libs/bootstrap/tsconfig.lib.json"
+ "tsConfigPath": "libs/health/tsconfig.lib.json"
}
}
}
diff --git a/package.json b/package.json
index e770cf6..3aee9e1 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
"@nestjs/platform-fastify": "^11.1.18",
"@nestjs/swagger": "^11.2.7",
"@nestjs/throttler": "^6.5.0",
+ "@willsoto/nestjs-prometheus": "^6.1.0",
"drizzle-orm": "^0.45.2",
"drizzle-zod": "^0.8.3",
"fastify": "^5.8.4",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f1b9c94..91a0420 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -38,12 +38,15 @@ importers:
'@nestjs/throttler':
specifier: ^6.5.0
version: 6.5.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)
+ '@willsoto/nestjs-prometheus':
+ specifier: ^6.1.0
+ version: 6.1.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(prom-client@15.1.3)
drizzle-orm:
specifier: ^0.45.2
- version: 0.45.2(@types/pg@8.20.0)(pg@8.20.0)
+ version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.20.0)
drizzle-zod:
specifier: ^0.8.3
- version: 0.8.3(drizzle-orm@0.45.2(@types/pg@8.20.0)(pg@8.20.0))(zod@4.3.6)
+ version: 0.8.3(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.20.0))(zod@4.3.6)
fastify:
specifier: ^5.8.4
version: 5.8.4
@@ -140,7 +143,7 @@ importers:
version: 1.5.9(@swc/core@1.15.24)
vitest:
specifier: ^4.1.4
- version: 4.1.4(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
+ version: 4.1.4(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
packages:
@@ -1147,6 +1150,10 @@ packages:
engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'}
hasBin: true
+ '@opentelemetry/api@1.9.1':
+ resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==}
+ engines: {node: '>=8.0.0'}
+
'@oxc-project/types@0.124.0':
resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==}
@@ -1579,6 +1586,12 @@ packages:
'@webassemblyjs/wast-printer@1.14.1':
resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==}
+ '@willsoto/nestjs-prometheus@6.1.0':
+ resolution: {integrity: sha512-lrCEnJBBSzUIYWGR+PsZw1YXs1B9jzxFEuNAa3RzTxuFAFdI+sW7Fp52il/U/dX2MWoHc32x06OS0nm56QwyzQ==}
+ peerDependencies:
+ '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
+ prom-client: ^15.0.0
+
'@xtuc/ieee754@1.2.0':
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
@@ -1723,6 +1736,9 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
+ bintrees@1.0.2:
+ resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==}
+
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
@@ -3066,6 +3082,10 @@ packages:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
+ prom-client@15.1.3:
+ resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==}
+ engines: {node: ^16 || ^18 || >=20}
+
pump@3.0.4:
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
@@ -3364,6 +3384,9 @@ packages:
resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==}
engines: {node: '>=6'}
+ tdigest@0.1.2:
+ resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==}
+
terser-webpack-plugin@5.4.0:
resolution: {integrity: sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==}
engines: {node: '>= 10.13.0'}
@@ -4578,6 +4601,8 @@ snapshots:
dependencies:
consola: 3.4.2
+ '@opentelemetry/api@1.9.1': {}
+
'@oxc-project/types@0.124.0': {}
'@paralleldrive/cuid2@2.3.1':
@@ -4886,7 +4911,7 @@ snapshots:
obug: 2.1.1
std-env: 4.0.0
tinyrainbow: 3.1.0
- vitest: 4.1.4(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
+ vitest: 4.1.4(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
'@vitest/expect@4.1.4':
dependencies:
@@ -5005,6 +5030,11 @@ snapshots:
'@webassemblyjs/ast': 1.14.1
'@xtuc/long': 4.2.2
+ '@willsoto/nestjs-prometheus@6.1.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(prom-client@15.1.3)':
+ dependencies:
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ prom-client: 15.1.3
+
'@xtuc/ieee754@1.2.0': {}
'@xtuc/long@4.2.2': {}
@@ -5115,6 +5145,8 @@ snapshots:
baseline-browser-mapping@2.10.17: {}
+ bintrees@1.0.2: {}
+
bl@4.1.0:
dependencies:
buffer: 5.7.1
@@ -5362,14 +5394,15 @@ snapshots:
esbuild: 0.25.12
tsx: 4.21.0
- drizzle-orm@0.45.2(@types/pg@8.20.0)(pg@8.20.0):
+ drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.20.0):
optionalDependencies:
+ '@opentelemetry/api': 1.9.1
'@types/pg': 8.20.0
pg: 8.20.0
- drizzle-zod@0.8.3(drizzle-orm@0.45.2(@types/pg@8.20.0)(pg@8.20.0))(zod@4.3.6):
+ drizzle-zod@0.8.3(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.20.0))(zod@4.3.6):
dependencies:
- drizzle-orm: 0.45.2(@types/pg@8.20.0)(pg@8.20.0)
+ drizzle-orm: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.20.0)
zod: 4.3.6
dunder-proto@1.0.1:
@@ -6388,6 +6421,11 @@ snapshots:
process@0.11.10: {}
+ prom-client@15.1.3:
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ tdigest: 0.1.2
+
pump@3.0.4:
dependencies:
end-of-stream: 1.4.5
@@ -6699,6 +6737,10 @@ snapshots:
tapable@2.3.2: {}
+ tdigest@0.1.2:
+ dependencies:
+ bintrees: 1.0.2
+
terser-webpack-plugin@5.4.0(@swc/core@1.15.24)(esbuild@0.27.7)(webpack@5.106.0(@swc/core@1.15.24)(esbuild@0.27.7)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
@@ -6873,7 +6915,7 @@ snapshots:
tsx: 4.21.0
yaml: 2.8.3
- vitest@4.1.4(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
+ vitest@4.1.4(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
'@vitest/expect': 4.1.4
'@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
@@ -6896,6 +6938,7 @@ snapshots:
vite: 8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
why-is-node-running: 2.3.0
optionalDependencies:
+ '@opentelemetry/api': 1.9.1
'@types/node': 20.19.39
'@vitest/coverage-v8': 4.1.4(vitest@4.1.4)
transitivePeerDependencies:
diff --git a/src/app.controller.ts b/src/app.controller.ts
deleted file mode 100644
index a325e8b..0000000
--- a/src/app.controller.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Controller, Get } from '@nestjs/common';
-import { AppService } from './app.service';
-
-@Controller()
-export class AppController {
- constructor(private readonly appService: AppService) {}
-
- @Get()
- getHello(): string {
- return this.appService.getHello();
- }
-}
diff --git a/src/app.service.ts b/src/app.service.ts
deleted file mode 100644
index 61b7a5b..0000000
--- a/src/app.service.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { Injectable } from '@nestjs/common';
-
-@Injectable()
-export class AppService {
- getHello(): string {
- return 'Hello World!';
- }
-}
diff --git a/src/main.ts b/src/main.ts
index aa7c5a0..de5c124 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,5 +1,5 @@
import { bootstrapApp } from '@libs/bootstrap';
-import { AppModule } from './app.module';
+import { AppModule } from './modules/app/app.module';
bootstrapApp({
serviceName: 'Tracker Monolit',
diff --git a/src/app.controller.spec.ts b/src/modules/app/app.controller.spec.ts
similarity index 87%
rename from src/app.controller.spec.ts
rename to src/modules/app/app.controller.spec.ts
index 2552ec5..169b786 100644
--- a/src/app.controller.spec.ts
+++ b/src/modules/app/app.controller.spec.ts
@@ -1,6 +1,5 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
-import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
@@ -8,7 +7,6 @@ describe('AppController', () => {
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
- providers: [AppService],
}).compile();
appController = app.get(AppController);
diff --git a/src/modules/app/app.controller.ts b/src/modules/app/app.controller.ts
new file mode 100644
index 0000000..eb0cb39
--- /dev/null
+++ b/src/modules/app/app.controller.ts
@@ -0,0 +1,11 @@
+import { Controller, Get } from '@nestjs/common';
+
+@Controller()
+export class AppController {
+ constructor() {}
+
+ @Get()
+ getHello(): string {
+ return 'Hello World!';
+ }
+}
diff --git a/src/app.module.ts b/src/modules/app/app.module.ts
similarity index 71%
rename from src/app.module.ts
rename to src/modules/app/app.module.ts
index 0f9f1cf..c55df26 100644
--- a/src/app.module.ts
+++ b/src/modules/app/app.module.ts
@@ -1,16 +1,25 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
-import { AppService } from './app.service';
import { ConfigModule } from '@libs/config';
import { DatabaseModule } from '@libs/database';
import { ConfigService } from '@nestjs/config';
-import * as schema from './shared/entities';
+import * as schema from '../../shared/entities';
import { APP_FILTER, APP_PIPE } from '@nestjs/core';
import { ZodValidationPipe, ZodValidationException } from 'nestjs-zod';
+import { PrometheusModule } from '@willsoto/nestjs-prometheus';
+import { HealthModule } from '@libs/health';
@Module({
imports: [
ConfigModule,
+ PrometheusModule.registerAsync({
+ useFactory: () => ({
+ path: 'dump',
+ defaultMetrics: {
+ enabled: true,
+ },
+ }),
+ }),
DatabaseModule.registerAsync({
global: true,
inject: [ConfigService],
@@ -22,10 +31,10 @@ import { ZodValidationPipe, ZodValidationException } from 'nestjs-zod';
};
},
}),
+ HealthModule.register('gateway'),
],
controllers: [AppController],
providers: [
- AppService,
{
provide: APP_PIPE,
useClass: ZodValidationPipe,
diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts
index 95c5212..0f04656 100644
--- a/test/app.e2e-spec.ts
+++ b/test/app.e2e-spec.ts
@@ -1,7 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { agent } from 'supertest';
-import { AppModule } from './../src/app.module';
+import { AppModule } from '../src/modules/app/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
diff --git a/tsconfig.json b/tsconfig.json
index 503f4c1..759daf0 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,38 +1,63 @@
{
- "compilerOptions": {
- "module": "commonjs",
- "declaration": false,
- "removeComments": true,
- "emitDecoratorMetadata": true,
- "experimentalDecorators": true,
- "allowSyntheticDefaultImports": true,
- "target": "ES2021",
- "sourceMap": true,
- "outDir": "./dist",
- "incremental": true,
- "skipLibCheck": true,
- "strictNullChecks": false,
- "noImplicitAny": false,
- "strictBindCallApply": false,
- "forceConsistentCasingInFileNames": false,
- "noFallthroughCasesInSwitch": false,
- "types": ["node", "vitest/globals"],
- "paths": {
- "@libs/config": ["./libs/config/src"],
- "@libs/config/*": ["./libs/config/src/*"],
- "@libs/database": ["./libs/database/src"],
- "@libs/database/*": ["./libs/database/src/*"],
- "@libs/bootstrap": ["./libs/bootstrap/src"],
- "@libs/bootstrap/*": ["./libs/bootstrap/src/*"]
- }
- },
- "include": [
- "src/**/*",
- "libs/**/*",
- "test/**/*",
- "drizzle.config.ts",
- "vitest.config.ts",
- "vitest.config.e2e.ts"
+ "compilerOptions": {
+ "module": "commonjs",
+ "declaration": false,
+ "removeComments": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "allowSyntheticDefaultImports": true,
+ "target": "ES2021",
+ "sourceMap": true,
+ "outDir": "./dist",
+ "incremental": true,
+ "skipLibCheck": true,
+ "strictNullChecks": false,
+ "noImplicitAny": false,
+ "strictBindCallApply": false,
+ "forceConsistentCasingInFileNames": false,
+ "noFallthroughCasesInSwitch": false,
+ "types": [
+ "node",
+ "vitest/globals"
],
- "exclude": ["dist", "node_modules"]
-}
+ "paths": {
+ "@libs/bootstrap": [
+ "./libs/bootstrap/src"
+ ],
+ "@libs/bootstrap/*": [
+ "./libs/bootstrap/src/*"
+ ],
+ "@libs/config": [
+ "./libs/config/src"
+ ],
+ "@libs/config/*": [
+ "./libs/config/src/*"
+ ],
+ "@libs/database": [
+ "./libs/database/src"
+ ],
+ "@libs/database/*": [
+ "./libs/database/src/*"
+ ],
+ "@libs/health": [
+ "libs/health/src"
+ ],
+ "@libs/health/*": [
+ "libs/health/src/*"
+ ]
+ },
+ "baseUrl": "./"
+ },
+ "include": [
+ "src/**/*",
+ "libs/**/*",
+ "test/**/*",
+ "drizzle.config.ts",
+ "vitest.config.ts",
+ "vitest.config.e2e.ts"
+ ],
+ "exclude": [
+ "dist",
+ "node_modules"
+ ]
+}
\ No newline at end of file
From bdd4dce8f6700fa806bc5639b8f3c80f6827d6a2 Mon Sep 17 00:00:00 2001
From: Danil <123034340+soorq@users.noreply.github.com>
Date: Sat, 11 Apr 2026 00:00:28 +0300
Subject: [PATCH 11/30] fix(docker): image at pnpm prune scripts
From 07cb9d48360c38222c88d78c78c83bdeb65f950d Mon Sep 17 00:00:00 2001
From: Danil <123034340+soorq@users.noreply.github.com>
Date: Sat, 11 Apr 2026 00:05:35 +0300
Subject: [PATCH 12/30] revert:fix: docker image (#12)
---
Dockerfile.prod | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Dockerfile.prod b/Dockerfile.prod
index 16325c9..7112e09 100644
--- a/Dockerfile.prod
+++ b/Dockerfile.prod
@@ -18,7 +18,7 @@ COPY . .
RUN pnpm run build
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
- pnpm prune --prod
+ pnpm prune --prod --ignore-scripts
FROM node:20-alpine AS runner
WORKDIR /app
From 4fb50b5b682edb7e67631a7f980a51647ccc799f Mon Sep 17 00:00:00 2001
From: Maksym Berehovyi <108676512+maksberegovoi@users.noreply.github.com>
Date: Sat, 11 Apr 2026 00:51:31 +0300
Subject: [PATCH 13/30] chore: global reformat and setup prettier ignore rules
(#14)
---
.github/ISSUE_TEMPLATE/bug_report.yml | 80 +++++++-------
.github/ISSUE_TEMPLATE/config.yml | 12 +--
.github/ISSUE_TEMPLATE/feature_request.yml | 34 +++---
.github/workflows/ci.yml | 50 ++++-----
.github/workflows/codeql.yml | 4 +-
.github/workflows/stale.yml | 8 +-
.prettierignore | 9 ++
.prettierrc | 10 +-
drizzle.config.ts | 12 +--
infra/compose.dev.yaml | 90 ++++++++--------
libs/bootstrap/src/configs/swagger.ts | 6 +-
libs/bootstrap/src/configs/throttler.ts | 10 +-
.../src/interfaces/options.interface.ts | 40 +++----
libs/bootstrap/src/setups/throttler.ts | 22 ++--
libs/bootstrap/tsconfig.lib.json | 14 +--
libs/config/tsconfig.lib.json | 14 +--
libs/database/tsconfig.lib.json | 14 +--
libs/health/tsconfig.lib.json | 14 +--
nest-cli.json | 88 +++++++--------
package.json | 2 +-
tsconfig.build.json | 4 +-
tsconfig.json | 100 +++++++-----------
22 files changed, 312 insertions(+), 325 deletions(-)
create mode 100644 .prettierignore
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index f166041..26f3a9a 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -1,41 +1,41 @@
-name: "Bug Report"
-description: "Сообщить об ошибке в работе приложения"
-labels: ["bug", "triage"]
+name: 'Bug Report'
+description: 'Сообщить об ошибке в работе приложения'
+labels: ['bug', 'triage']
body:
- - type: markdown
- attributes:
- value: |
- Спасибо, что решили помочь сделать проект лучше!
- - type: input
- id: version
- attributes:
- label: "Версия приложения"
- description: "Какую версию вы используете? (например, 0.0.1)"
- placeholder: "0.0.x"
- validations:
- required: true
- - type: textarea
- id: steps
- attributes:
- label: "Шаги воспроизведения"
- description: "Как нам увидеть эту ошибку?"
- placeholder: |
- 1. Запустить docker-compose
- 2. Отправить POST запрос на /api/v1/auth...
- validations:
- required: true
- - type: dropdown
- id: environment
- attributes:
- label: "Окружение"
- options:
- - Docker
- - Local (pnpm)
- - Production
- validations:
- required: true
- - type: textarea
- id: expected
- attributes:
- label: "Ожидаемое поведение"
- placeholder: "Что должно было произойти?"
\ No newline at end of file
+ - type: markdown
+ attributes:
+ value: |
+ Спасибо, что решили помочь сделать проект лучше!
+ - type: input
+ id: version
+ attributes:
+ label: 'Версия приложения'
+ description: 'Какую версию вы используете? (например, 0.0.1)'
+ placeholder: '0.0.x'
+ validations:
+ required: true
+ - type: textarea
+ id: steps
+ attributes:
+ label: 'Шаги воспроизведения'
+ description: 'Как нам увидеть эту ошибку?'
+ placeholder: |
+ 1. Запустить docker-compose
+ 2. Отправить POST запрос на /api/v1/auth...
+ validations:
+ required: true
+ - type: dropdown
+ id: environment
+ attributes:
+ label: 'Окружение'
+ options:
+ - Docker
+ - Local (pnpm)
+ - Production
+ validations:
+ required: true
+ - type: textarea
+ id: expected
+ attributes:
+ label: 'Ожидаемое поведение'
+ placeholder: 'Что должно было произойти?'
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 656e5e5..c6c9292 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,10 +1,10 @@
blank_issues_enabled: false
contact_links:
- - name: "❓ Вопросы по использованию"
- url: "https://github.com/Task-Tracker-Lab/task-tracker-backend/discussions/new?category=q-a"
- about: "Если вы не уверены, баг это или нет, или вам нужна помощь в настройке — спросите здесь."
+ - name: '❓ Вопросы по использованию'
+ url: 'https://github.com/Task-Tracker-Lab/task-tracker-backend/discussions/new?category=q-a'
+ about: 'Если вы не уверены, баг это или нет, или вам нужна помощь в настройке — спросите здесь.'
- - name: "💡 Идеи и предложения"
- url: "https://github.com/Task-Tracker-Lab/task-tracker-backend/discussions/new?category=ideas"
- about: "Хотите обсудить новую крутую фичу перед тем, как заводить задачу? Вам сюда."
\ No newline at end of file
+ - name: '💡 Идеи и предложения'
+ url: 'https://github.com/Task-Tracker-Lab/task-tracker-backend/discussions/new?category=ideas'
+ about: 'Хотите обсудить новую крутую фичу перед тем, как заводить задачу? Вам сюда.'
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index 4c6bacc..c90da45 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -1,18 +1,18 @@
-name: "🚀 Feature Request"
-description: "Предложить новую идею или улучшение"
-labels: ["enhancement"]
+name: '🚀 Feature Request'
+description: 'Предложить новую идею или улучшение'
+labels: ['enhancement']
body:
- - type: textarea
- id: problem
- attributes:
- label: "Какую проблему мы решаем?"
- description: "Опишите, почему текущего функционала недостаточно."
- validations:
- required: true
- - type: textarea
- id: solution
- attributes:
- label: "Ваше предложение"
- description: "Как именно вы видите реализацию этой фичи?"
- validations:
- required: true
\ No newline at end of file
+ - type: textarea
+ id: problem
+ attributes:
+ label: 'Какую проблему мы решаем?'
+ description: 'Опишите, почему текущего функционала недостаточно.'
+ validations:
+ required: true
+ - type: textarea
+ id: solution
+ attributes:
+ label: 'Ваше предложение'
+ description: 'Как именно вы видите реализацию этой фичи?'
+ validations:
+ required: true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index aa9a576..3034e40 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,37 +1,37 @@
name: CI
on:
- pull_request:
- branches: [dev, main]
- push:
- branches: [dev, main]
+ pull_request:
+ branches: [dev, main]
+ push:
+ branches: [dev, main]
jobs:
- quality-check:
- name: Lint & Test
- runs-on: ubuntu-latest
+ quality-check:
+ name: Lint & Test
+ runs-on: ubuntu-latest
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
- - name: Install pnpm
- uses: pnpm/action-setup@v4
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: 20
- cache: "pnpm"
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'pnpm'
- - name: Install dependencies
- run: pnpm install --frozen-lockfile
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
- - name: Run Lint
- run: pnpm run lint
+ - name: Run Lint
+ run: pnpm run lint
- - name: Type Check
- run: pnpm exec tsc --noEmit
+ - name: Type Check
+ run: pnpm exec tsc --noEmit
- - name: Run Tests
- run: pnpm run test
+ - name: Run Tests
+ run: pnpm run test
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index c168cd6..129d47e 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -1,4 +1,4 @@
-name: "CodeQL"
+name: 'CodeQL'
on:
push:
@@ -6,7 +6,7 @@ on:
pull_request:
branches: [main]
schedule:
- - cron: "15 13 * * 5"
+ - cron: '15 13 * * 5'
jobs:
analyze:
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index fbc62d1..f812282 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -1,8 +1,8 @@
-name: "Close stale issues and PRs"
+name: 'Close stale issues and PRs'
on:
schedule:
- - cron: "30 1 * * *"
+ - cron: '30 1 * * *'
jobs:
stale:
@@ -13,7 +13,7 @@ jobs:
steps:
- uses: actions/stale@v9
with:
- stale-issue-message: "Эта задача давно не обновлялась. Она будет закрыта через 5 дней, если не появится новой активности."
- stale-pr-message: "Этот PR замер. Мы закроем его через 5 дней, чтобы не копить очередь, но вы всегда можете переоткрыть его позже."
+ stale-issue-message: 'Эта задача давно не обновлялась. Она будет закрыта через 5 дней, если не появится новой активности.'
+ stale-pr-message: 'Этот PR замер. Мы закроем его через 5 дней, чтобы не копить очередь, но вы всегда можете переоткрыть его позже.'
days-before-stale: 30
days-before-close: 5
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..c8d0ce7
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,9 @@
+dist
+node_modules
+pnpm-lock.yaml
+
+migrations
+
+*.sql
+Dockerfile*
+.dockerignore
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
index c5c6203..a6643be 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,7 +1,7 @@
{
- "singleQuote": true,
- "trailingComma": "all",
- "printWidth": 100,
- "tabWidth": 4,
- "semi": true
+ "singleQuote": true,
+ "trailingComma": "all",
+ "printWidth": 100,
+ "tabWidth": 4,
+ "semi": true
}
diff --git a/drizzle.config.ts b/drizzle.config.ts
index 4bb294b..247cdcc 100644
--- a/drizzle.config.ts
+++ b/drizzle.config.ts
@@ -1,10 +1,10 @@
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
- schema: './src/shared/entities/index.ts',
- out: './migrations',
- dialect: 'postgresql',
- dbCredentials: {
- url: process.env.DATABASE_URL!,
- },
+ schema: './src/shared/entities/index.ts',
+ out: './migrations',
+ dialect: 'postgresql',
+ dbCredentials: {
+ url: process.env.DATABASE_URL!,
+ },
});
diff --git a/infra/compose.dev.yaml b/infra/compose.dev.yaml
index 7c54f3a..e7c4d67 100644
--- a/infra/compose.dev.yaml
+++ b/infra/compose.dev.yaml
@@ -1,54 +1,54 @@
-version: "3.9"
+version: '3.9'
name: task-tracker
services:
- api:
- hostname: api
- container_name: api
- build:
- context: ../
- dockerfile: Dockerfile.dev
- restart: always
- env_file:
- - ../.env
- ports:
- - "3000:3000"
- depends_on:
- database:
- condition: service_healthy
- redis:
- condition: service_started
- networks:
- - backend
+ api:
+ hostname: api
+ container_name: api
+ build:
+ context: ../
+ dockerfile: Dockerfile.dev
+ restart: always
+ env_file:
+ - ../.env
+ ports:
+ - '3000:3000'
+ depends_on:
+ database:
+ condition: service_healthy
+ redis:
+ condition: service_started
+ networks:
+ - backend
- database:
- hostname: database
- container_name: database
- image: postgres:16-alpine
- restart: always
- env_file:
- - ../.env
- environment:
- POSTGRES_USER: ${DB_USERNAME:-admin}
- POSTGRES_PASSWORD: ${DB_PASSWORD:-admin}
- POSTGRES_DB: ${DB_DATABASE:-tracker}
- ports:
- - "6000:5432"
- volumes:
- - postgres_data:/var/lib/postgresql/data
- networks:
- - backend
- healthcheck:
- test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE}"]
- interval: 5s
- timeout: 5s
- retries: 5
- profiles: ["infra"]
+ database:
+ hostname: database
+ container_name: database
+ image: postgres:16-alpine
+ restart: always
+ env_file:
+ - ../.env
+ environment:
+ POSTGRES_USER: ${DB_USERNAME:-admin}
+ POSTGRES_PASSWORD: ${DB_PASSWORD:-admin}
+ POSTGRES_DB: ${DB_DATABASE:-tracker}
+ ports:
+ - '6000:5432'
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ networks:
+ - backend
+ healthcheck:
+ test: ['CMD-SHELL', 'pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE}']
+ interval: 5s
+ timeout: 5s
+ retries: 5
+ profiles: ['infra']
volumes:
- postgres_data:
- redis_data:
+ postgres_data:
+ redis_data:
networks:
- backend:
+ backend:
diff --git a/libs/bootstrap/src/configs/swagger.ts b/libs/bootstrap/src/configs/swagger.ts
index 918911d..a13eb68 100644
--- a/libs/bootstrap/src/configs/swagger.ts
+++ b/libs/bootstrap/src/configs/swagger.ts
@@ -1,7 +1,7 @@
import type { SwaggerOptions } from '../interfaces/options.interface';
export const SWAGGER_DEFAULTS: SwaggerOptions = {
- title: 'API',
- description: 'API Documentation',
- version: '1.0.0',
+ title: 'API',
+ description: 'API Documentation',
+ version: '1.0.0',
};
diff --git a/libs/bootstrap/src/configs/throttler.ts b/libs/bootstrap/src/configs/throttler.ts
index 135f264..08f8cbe 100644
--- a/libs/bootstrap/src/configs/throttler.ts
+++ b/libs/bootstrap/src/configs/throttler.ts
@@ -1,9 +1,9 @@
import type { ThrottlerModuleOptions } from '@nestjs/throttler';
export const DEFAULT_THROTTLER_OPTIONS: ThrottlerModuleOptions = [
- {
- ttl: 60000,
- limit: 100,
- skipIf: (context) => context.getType() !== 'http',
- },
+ {
+ ttl: 60000,
+ limit: 100,
+ skipIf: (context) => context.getType() !== 'http',
+ },
];
diff --git a/libs/bootstrap/src/interfaces/options.interface.ts b/libs/bootstrap/src/interfaces/options.interface.ts
index 8b2f22c..fed3ded 100644
--- a/libs/bootstrap/src/interfaces/options.interface.ts
+++ b/libs/bootstrap/src/interfaces/options.interface.ts
@@ -4,32 +4,32 @@ import type { NestFastifyApplication } from '@nestjs/platform-fastify';
import type { ThrottlerModuleOptions } from '@nestjs/throttler';
export interface SwaggerMetadata {
- title?: string;
- description?: string;
- version?: string;
- path?: string;
+ title?: string;
+ description?: string;
+ version?: string;
+ path?: string;
}
export interface SwaggerInfrastructure {
- server?: {
- port?: string | number;
- domain?: string;
- stage?: string;
- };
- services?: { name: string; port: number }[];
+ server?: {
+ port?: string | number;
+ domain?: string;
+ stage?: string;
+ };
+ services?: { name: string; port: number }[];
}
export interface SwaggerOptions extends SwaggerMetadata, SwaggerInfrastructure {}
export interface BootstrapOptions {
- apiPrefix?: string;
- appModule: Type;
- defaultPort?: number;
- portEnvKey?: keyof Config;
- serviceName: string;
- setupApp?: (app: NestFastifyApplication) => Promise | void;
- swaggerOptions?: SwaggerMetadata;
- throttlerOptions?: ThrottlerModuleOptions;
- useCookieParser?: boolean;
- useCors?: boolean;
+ apiPrefix?: string;
+ appModule: Type;
+ defaultPort?: number;
+ portEnvKey?: keyof Config;
+ serviceName: string;
+ setupApp?: (app: NestFastifyApplication) => Promise | void;
+ swaggerOptions?: SwaggerMetadata;
+ throttlerOptions?: ThrottlerModuleOptions;
+ useCookieParser?: boolean;
+ useCors?: boolean;
}
diff --git a/libs/bootstrap/src/setups/throttler.ts b/libs/bootstrap/src/setups/throttler.ts
index 59ac61a..29f683b 100644
--- a/libs/bootstrap/src/setups/throttler.ts
+++ b/libs/bootstrap/src/setups/throttler.ts
@@ -4,16 +4,16 @@ import { APP_GUARD } from '@nestjs/core';
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
export function setupThrottler(module: Type, options: ThrottlerModuleOptions) {
- @Module({
- imports: [module, ThrottlerModule.forRoot(options)],
- providers: [
- {
- provide: APP_GUARD,
- useClass: ThrottlerGuard,
- },
- ],
- })
- class RootModule {}
+ @Module({
+ imports: [module, ThrottlerModule.forRoot(options)],
+ providers: [
+ {
+ provide: APP_GUARD,
+ useClass: ThrottlerGuard,
+ },
+ ],
+ })
+ class RootModule {}
- return RootModule;
+ return RootModule;
}
diff --git a/libs/bootstrap/tsconfig.lib.json b/libs/bootstrap/tsconfig.lib.json
index 208ac7d..909ede0 100644
--- a/libs/bootstrap/tsconfig.lib.json
+++ b/libs/bootstrap/tsconfig.lib.json
@@ -1,9 +1,9 @@
{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "declaration": true,
- "outDir": "../../dist/libs/bootstrap"
- },
- "include": ["src/**/*"],
- "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "declaration": true,
+ "outDir": "../../dist/libs/bootstrap"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
}
diff --git a/libs/config/tsconfig.lib.json b/libs/config/tsconfig.lib.json
index ec3efa6..855908d 100644
--- a/libs/config/tsconfig.lib.json
+++ b/libs/config/tsconfig.lib.json
@@ -1,9 +1,9 @@
{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "declaration": true,
- "outDir": "../../dist/libs/config"
- },
- "include": ["src/**/*"],
- "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "declaration": true,
+ "outDir": "../../dist/libs/config"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
}
diff --git a/libs/database/tsconfig.lib.json b/libs/database/tsconfig.lib.json
index 21c8d58..0add6d4 100644
--- a/libs/database/tsconfig.lib.json
+++ b/libs/database/tsconfig.lib.json
@@ -1,9 +1,9 @@
{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "declaration": true,
- "outDir": "../../dist/libs/database"
- },
- "include": ["src/**/*"],
- "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "declaration": true,
+ "outDir": "../../dist/libs/database"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
}
diff --git a/libs/health/tsconfig.lib.json b/libs/health/tsconfig.lib.json
index 8e4d095..5c0ebaa 100644
--- a/libs/health/tsconfig.lib.json
+++ b/libs/health/tsconfig.lib.json
@@ -1,9 +1,9 @@
{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "declaration": true,
- "outDir": "../../dist/libs/health"
- },
- "include": ["src/**/*"],
- "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "declaration": true,
+ "outDir": "../../dist/libs/health"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
}
diff --git a/nest-cli.json b/nest-cli.json
index 5881fec..03f8cdf 100644
--- a/nest-cli.json
+++ b/nest-cli.json
@@ -1,47 +1,47 @@
{
- "$schema": "https://json.schemastore.org/nest-cli",
- "collection": "@nestjs/schematics",
- "sourceRoot": "src",
- "compilerOptions": {
- "deleteOutDir": true,
- "webpack": true
- },
- "projects": {
- "bootstrap": {
- "type": "library",
- "root": "libs/bootstrap",
- "entryFile": "index",
- "sourceRoot": "libs/bootstrap/src",
- "compilerOptions": {
- "tsConfigPath": "libs/bootstrap/tsconfig.lib.json"
- }
+ "$schema": "https://json.schemastore.org/nest-cli",
+ "collection": "@nestjs/schematics",
+ "sourceRoot": "src",
+ "compilerOptions": {
+ "deleteOutDir": true,
+ "webpack": true
},
- "config": {
- "type": "library",
- "root": "libs/config",
- "entryFile": "index",
- "sourceRoot": "libs/config/src",
- "compilerOptions": {
- "tsConfigPath": "libs/config/tsconfig.lib.json"
- }
- },
- "database": {
- "type": "library",
- "root": "libs/database",
- "entryFile": "index",
- "sourceRoot": "libs/database/src",
- "compilerOptions": {
- "tsConfigPath": "libs/database/tsconfig.lib.json"
- }
- },
- "health": {
- "type": "library",
- "root": "libs/health",
- "entryFile": "index",
- "sourceRoot": "libs/health/src",
- "compilerOptions": {
- "tsConfigPath": "libs/health/tsconfig.lib.json"
- }
+ "projects": {
+ "bootstrap": {
+ "type": "library",
+ "root": "libs/bootstrap",
+ "entryFile": "index",
+ "sourceRoot": "libs/bootstrap/src",
+ "compilerOptions": {
+ "tsConfigPath": "libs/bootstrap/tsconfig.lib.json"
+ }
+ },
+ "config": {
+ "type": "library",
+ "root": "libs/config",
+ "entryFile": "index",
+ "sourceRoot": "libs/config/src",
+ "compilerOptions": {
+ "tsConfigPath": "libs/config/tsconfig.lib.json"
+ }
+ },
+ "database": {
+ "type": "library",
+ "root": "libs/database",
+ "entryFile": "index",
+ "sourceRoot": "libs/database/src",
+ "compilerOptions": {
+ "tsConfigPath": "libs/database/tsconfig.lib.json"
+ }
+ },
+ "health": {
+ "type": "library",
+ "root": "libs/health",
+ "entryFile": "index",
+ "sourceRoot": "libs/health/src",
+ "compilerOptions": {
+ "tsConfigPath": "libs/health/tsconfig.lib.json"
+ }
+ }
}
- }
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index 3aee9e1..f202d2e 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
- "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"libs/**/*.ts\"",
+ "format": "prettier --write \".\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
diff --git a/tsconfig.build.json b/tsconfig.build.json
index 64f86c6..aed3485 100644
--- a/tsconfig.build.json
+++ b/tsconfig.build.json
@@ -1,4 +1,4 @@
{
- "extends": "./tsconfig.json",
- "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
+ "extends": "./tsconfig.json",
+ "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}
diff --git a/tsconfig.json b/tsconfig.json
index 759daf0..de35319 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,63 +1,41 @@
{
- "compilerOptions": {
- "module": "commonjs",
- "declaration": false,
- "removeComments": true,
- "emitDecoratorMetadata": true,
- "experimentalDecorators": true,
- "allowSyntheticDefaultImports": true,
- "target": "ES2021",
- "sourceMap": true,
- "outDir": "./dist",
- "incremental": true,
- "skipLibCheck": true,
- "strictNullChecks": false,
- "noImplicitAny": false,
- "strictBindCallApply": false,
- "forceConsistentCasingInFileNames": false,
- "noFallthroughCasesInSwitch": false,
- "types": [
- "node",
- "vitest/globals"
- ],
- "paths": {
- "@libs/bootstrap": [
- "./libs/bootstrap/src"
- ],
- "@libs/bootstrap/*": [
- "./libs/bootstrap/src/*"
- ],
- "@libs/config": [
- "./libs/config/src"
- ],
- "@libs/config/*": [
- "./libs/config/src/*"
- ],
- "@libs/database": [
- "./libs/database/src"
- ],
- "@libs/database/*": [
- "./libs/database/src/*"
- ],
- "@libs/health": [
- "libs/health/src"
- ],
- "@libs/health/*": [
- "libs/health/src/*"
- ]
+ "compilerOptions": {
+ "module": "commonjs",
+ "declaration": false,
+ "removeComments": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "allowSyntheticDefaultImports": true,
+ "target": "ES2021",
+ "sourceMap": true,
+ "outDir": "./dist",
+ "incremental": true,
+ "skipLibCheck": true,
+ "strictNullChecks": false,
+ "noImplicitAny": false,
+ "strictBindCallApply": false,
+ "forceConsistentCasingInFileNames": false,
+ "noFallthroughCasesInSwitch": false,
+ "types": ["node", "vitest/globals"],
+ "paths": {
+ "@libs/bootstrap": ["./libs/bootstrap/src"],
+ "@libs/bootstrap/*": ["./libs/bootstrap/src/*"],
+ "@libs/config": ["./libs/config/src"],
+ "@libs/config/*": ["./libs/config/src/*"],
+ "@libs/database": ["./libs/database/src"],
+ "@libs/database/*": ["./libs/database/src/*"],
+ "@libs/health": ["libs/health/src"],
+ "@libs/health/*": ["libs/health/src/*"]
+ },
+ "baseUrl": "./"
},
- "baseUrl": "./"
- },
- "include": [
- "src/**/*",
- "libs/**/*",
- "test/**/*",
- "drizzle.config.ts",
- "vitest.config.ts",
- "vitest.config.e2e.ts"
- ],
- "exclude": [
- "dist",
- "node_modules"
- ]
-}
\ No newline at end of file
+ "include": [
+ "src/**/*",
+ "libs/**/*",
+ "test/**/*",
+ "drizzle.config.ts",
+ "vitest.config.ts",
+ "vitest.config.e2e.ts"
+ ],
+ "exclude": ["dist", "node_modules"]
+}
From 2dcd2bdd0bbdca2f5b40c1aa8d1cb43252a90c4c Mon Sep 17 00:00:00 2001
From: Danil <123034340+soorq@users.noreply.github.com>
Date: Mon, 13 Apr 2026 19:43:21 +0300
Subject: [PATCH 14/30] feat: implement core modules (Auth, User), S3
integration, and CI/CD infra
* feat: add users module and standardized API error responses
* feat: core domain entity type / add service and repository
* chore: add auth module arch
* feat(shared): add ApiBaseController decorator for global swagger responses
* feat(swagger): add reusable swagger error decorators
* docs(users): describe user api endpoints in swagger
* feat(auth): scaffold auth controller with route stubs
* feat(auth): scaffold auth service with dependencies
* feat(auth): add zod-based dtos for authentication and 2fa
* refactor(swagger): rename ApiRequireAuth to ApiUnauthorized for better reuse
* docs(auth): describe auth api endpoints in swagger
* chore: feat libs per auth flow / rename endpoints
* chore: feat per auth module libs and redis
* feat(auth): intergrate with jwt module and update config module
* feat(redis):chore(infra): integrate redis and setup correct infra
* feat(auth):chore(ua-parser): integrate logic per register flow #1
* feat(auth): integrate logic per sign flow #2
* feat(mail): add mail module and Handlebars templates
* feat(user): implement user controller with facade integration
* feat(auth): integrate logic per reset password flow #3 / bug with otplib always code apply
* fix(auth): fix OTP verification bypass in AuthService
* fix(auth): correct session expiration time in signIn and verify
* fix(email): fix verification code copy formatting
* fix(infra): specify postgres user and db for healthcheck
* fix(cors): normalize origins to hostname to resolve local blocks
* fix(compose): resolve error with depends and correct output per users
* feat(s3):chore(aws): add s3 module at libs and env file
* refactor(auth): fix type inconsistencies in user models
* feat(infra): setup migrations, optimize pg pool, and add ghcr workflow
* chore(auth): add password reset and sign-up confirmation Swagger documentation
* feat(user): implement avatar upload functionality with S3 integration
* chore(auth): add password reset and sign-up confirmation Swagger documentation
---------
Co-authored-by: Maksym Berehovyi <108676512+maksberegovoi@users.noreply.github.com>
Co-authored-by: Maxim
---
.env.example | 32 +-
.github/workflows/build.yml | 19 +-
.github/workflows/ci.yml | 50 +-
Dockerfile.prod | 2 +
infra/README.md | 7 +
infra/compose.dev.yaml | 54 -
infra/dev/README.md | 41 +
infra/dev/compose.dev.yaml | 121 +
libs/bootstrap/src/bootstrap.ts | 7 +
libs/bootstrap/src/setups/cors.ts | 19 +-
libs/bootstrap/src/setups/swagger.ts | 5 +-
libs/config/src/config.schema.ts | 58 +-
.../src/helpers/jwt-secren-validation.ts | 7 +
libs/database/src/database.module.ts | 12 +-
libs/s3/src/dtos/upload-avatar.dto.ts | 5 +
libs/s3/src/index.ts | 2 +
libs/s3/src/interfaces/index.ts | 5 +
libs/s3/src/interfaces/module.interface.ts | 38 +
libs/s3/src/s3.constants.ts | 1 +
libs/s3/src/s3.module.ts | 41 +
libs/s3/src/s3.service.ts | 50 +
libs/s3/tsconfig.lib.json | 9 +
migrations/0001_solid_kronos.sql | 57 +
migrations/meta/0001_snapshot.json | 369 +++
migrations/meta/_journal.json | 31 +-
nest-cli.json | 9 +
package.json | 26 +
pnpm-lock.yaml | 2483 ++++++++++++++++-
src/modules/app/app.controller.spec.ts | 20 -
src/modules/app/app.controller.ts | 11 -
src/modules/app/app.module.ts | 54 +-
src/modules/auth/auth.module.ts | 75 +
.../auth/controller/auth.controller.ts | 126 +
src/modules/auth/controller/auth.swagger.ts | 271 ++
src/modules/auth/controller/index.ts | 1 +
src/modules/auth/dtos/2fa.dto.ts | 25 +
src/modules/auth/dtos/auth.dto.ts | 58 +
src/modules/auth/dtos/index.ts | 3 +
src/modules/auth/dtos/password.dto.ts | 45 +
src/modules/auth/entities/index.ts | 1 +
src/modules/auth/entities/session.entity.ts | 25 +
src/modules/auth/helpers/get-device-meta.ts | 30 +
src/modules/auth/helpers/index.ts | 1 +
src/modules/auth/index.ts | 1 +
src/modules/auth/repository/index.ts | 2 +
.../session.repository.interface.ts | 13 +
.../auth/repository/session.repository.ts | 68 +
src/modules/auth/services/auth.service.ts | 389 +++
src/modules/auth/services/index.ts | 2 +
src/modules/auth/services/token.service.ts | 52 +
.../auth/strategies/bearer.strategy.ts | 21 +
.../auth/strategies/cookie.strategy.ts | 32 +
src/modules/auth/strategies/index.ts | 2 +
src/modules/auth/types/index.ts | 1 +
src/modules/auth/types/jwt-payload.ts | 8 +
src/modules/user/commands/create.command.ts | 29 +
src/modules/user/commands/find-one.command.ts | 27 +
src/modules/user/commands/index.ts | 3 +
.../user/commands/update-pass.command.ts | 24 +
src/modules/user/controller/index.ts | 1 +
.../user/controller/user.controller.ts | 70 +
src/modules/user/controller/user.swagger.ts | 134 +
src/modules/user/dtos/index.ts | 1 +
src/modules/user/dtos/user.dto.ts | 75 +
src/modules/user/entities/index.ts | 1 +
src/modules/user/entities/user.domain.ts | 25 +
src/modules/user/entities/user.entity.ts | 60 +
src/modules/user/index.ts | 3 +
src/modules/user/repository/index.ts | 1 +
.../repository/user.repository.interface.ts | 28 +
.../user/repository/user.repository.ts | 142 +
src/modules/user/user.module.ts | 20 +
src/modules/user/user.service.ts | 166 ++
src/shared/adapters/mail/adapter.ts | 56 +
src/shared/adapters/mail/index.ts | 2 +
src/shared/adapters/mail/port.ts | 4 +
.../decorators/api-controller.decorator.ts | 15 +
src/shared/decorators/index.ts | 2 +
src/shared/decorators/user.decorator.ts | 24 +
src/shared/dtos/index.ts | 2 +
src/shared/dtos/pagination.dto.ts | 9 +
src/shared/dtos/response.dto.ts | 9 +
src/shared/entities/index.ts | 2 +
src/shared/error/filter.ts | 50 +
src/shared/error/index.ts | 2 +
src/shared/error/schema.ts | 57 +
src/shared/error/swagger.ts | 53 +
src/shared/guards/bearer.guard.ts | 5 +
src/shared/guards/cookie.guard.ts | 5 +
src/shared/guards/index.ts | 2 +
src/shared/migration/index.ts | 1 +
src/shared/migration/migration.service.ts | 27 +
src/shared/types/fastify.d.ts | 7 +
src/shared/workers/enum.ts | 9 +
src/shared/workers/events/index.ts | 2 +
.../workers/events/register-code.event.ts | 7 +
.../workers/events/reset-password.event.ts | 6 +
src/shared/workers/index.ts | 3 +
src/shared/workers/mail/index.ts | 1 +
src/shared/workers/mail/worker.ts | 72 +
templates/confirmation.hbs | 53 +
templates/reset-password.hbs | 52 +
tsconfig.json | 6 +-
103 files changed, 5968 insertions(+), 284 deletions(-)
create mode 100644 infra/README.md
delete mode 100644 infra/compose.dev.yaml
create mode 100644 infra/dev/README.md
create mode 100644 infra/dev/compose.dev.yaml
create mode 100644 libs/config/src/helpers/jwt-secren-validation.ts
create mode 100644 libs/s3/src/dtos/upload-avatar.dto.ts
create mode 100644 libs/s3/src/index.ts
create mode 100644 libs/s3/src/interfaces/index.ts
create mode 100644 libs/s3/src/interfaces/module.interface.ts
create mode 100644 libs/s3/src/s3.constants.ts
create mode 100644 libs/s3/src/s3.module.ts
create mode 100644 libs/s3/src/s3.service.ts
create mode 100644 libs/s3/tsconfig.lib.json
create mode 100644 migrations/0001_solid_kronos.sql
create mode 100644 migrations/meta/0001_snapshot.json
delete mode 100644 src/modules/app/app.controller.spec.ts
delete mode 100644 src/modules/app/app.controller.ts
create mode 100644 src/modules/auth/auth.module.ts
create mode 100644 src/modules/auth/controller/auth.controller.ts
create mode 100644 src/modules/auth/controller/auth.swagger.ts
create mode 100644 src/modules/auth/controller/index.ts
create mode 100644 src/modules/auth/dtos/2fa.dto.ts
create mode 100644 src/modules/auth/dtos/auth.dto.ts
create mode 100644 src/modules/auth/dtos/index.ts
create mode 100644 src/modules/auth/dtos/password.dto.ts
create mode 100644 src/modules/auth/entities/index.ts
create mode 100644 src/modules/auth/entities/session.entity.ts
create mode 100644 src/modules/auth/helpers/get-device-meta.ts
create mode 100644 src/modules/auth/helpers/index.ts
create mode 100644 src/modules/auth/index.ts
create mode 100644 src/modules/auth/repository/index.ts
create mode 100644 src/modules/auth/repository/session.repository.interface.ts
create mode 100644 src/modules/auth/repository/session.repository.ts
create mode 100644 src/modules/auth/services/auth.service.ts
create mode 100644 src/modules/auth/services/index.ts
create mode 100644 src/modules/auth/services/token.service.ts
create mode 100644 src/modules/auth/strategies/bearer.strategy.ts
create mode 100644 src/modules/auth/strategies/cookie.strategy.ts
create mode 100644 src/modules/auth/strategies/index.ts
create mode 100644 src/modules/auth/types/index.ts
create mode 100644 src/modules/auth/types/jwt-payload.ts
create mode 100644 src/modules/user/commands/create.command.ts
create mode 100644 src/modules/user/commands/find-one.command.ts
create mode 100644 src/modules/user/commands/index.ts
create mode 100644 src/modules/user/commands/update-pass.command.ts
create mode 100644 src/modules/user/controller/index.ts
create mode 100644 src/modules/user/controller/user.controller.ts
create mode 100644 src/modules/user/controller/user.swagger.ts
create mode 100644 src/modules/user/dtos/index.ts
create mode 100644 src/modules/user/dtos/user.dto.ts
create mode 100644 src/modules/user/entities/index.ts
create mode 100644 src/modules/user/entities/user.domain.ts
create mode 100644 src/modules/user/entities/user.entity.ts
create mode 100644 src/modules/user/index.ts
create mode 100644 src/modules/user/repository/index.ts
create mode 100644 src/modules/user/repository/user.repository.interface.ts
create mode 100644 src/modules/user/repository/user.repository.ts
create mode 100644 src/modules/user/user.module.ts
create mode 100644 src/modules/user/user.service.ts
create mode 100644 src/shared/adapters/mail/adapter.ts
create mode 100644 src/shared/adapters/mail/index.ts
create mode 100644 src/shared/adapters/mail/port.ts
create mode 100644 src/shared/decorators/api-controller.decorator.ts
create mode 100644 src/shared/decorators/index.ts
create mode 100644 src/shared/decorators/user.decorator.ts
create mode 100644 src/shared/dtos/index.ts
create mode 100644 src/shared/dtos/pagination.dto.ts
create mode 100644 src/shared/dtos/response.dto.ts
create mode 100644 src/shared/error/filter.ts
create mode 100644 src/shared/error/index.ts
create mode 100644 src/shared/error/schema.ts
create mode 100644 src/shared/error/swagger.ts
create mode 100644 src/shared/guards/bearer.guard.ts
create mode 100644 src/shared/guards/cookie.guard.ts
create mode 100644 src/shared/guards/index.ts
create mode 100644 src/shared/migration/index.ts
create mode 100644 src/shared/migration/migration.service.ts
create mode 100644 src/shared/types/fastify.d.ts
create mode 100644 src/shared/workers/enum.ts
create mode 100644 src/shared/workers/events/index.ts
create mode 100644 src/shared/workers/events/register-code.event.ts
create mode 100644 src/shared/workers/events/reset-password.event.ts
create mode 100644 src/shared/workers/index.ts
create mode 100644 src/shared/workers/mail/index.ts
create mode 100644 src/shared/workers/mail/worker.ts
create mode 100644 templates/confirmation.hbs
create mode 100644 templates/reset-password.hbs
diff --git a/.env.example b/.env.example
index d3f2c1a..7421df7 100644
--- a/.env.example
+++ b/.env.example
@@ -1,7 +1,7 @@
# --- APP ---
PORT=3000
NODE_ENV=development
-CORS_ALLOWED_ORIGINS=http://localhost:3000
+CORS_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
# --- POSTGRES ---
DB_USERNAME=admin
@@ -18,6 +18,30 @@ DB_SCHEMA=base
DATABASE_URL=postgres://${DB_USERNAME}:${DB_PASSWORD}@localhost:${DB_PORT}/${DB_DATABASE}
# --- REDIS ---
-REDIS_HOST=redis
-REDIS_PORT=6379
-REDIS_EXTERNAL_PORT=6380
+# in the docker network will be, not show port redis, at prod env
+# REDIS_HOST=redis
+# at development mode
+REDIS_HOST=127.0.0.1
+REDIS_PORT=7000
+
+JWT_ACCESS_SECRET=same-same-same-same-same
+JWT_ACCESS_EXPIRES_IN=15m
+
+JWT_REFRESH_SECRET=same-same-same-same-same
+JWT_REFRESH_EXPIRES_IN=15m
+
+# --- MAIL SETTINGS ---
+MAIL_HOST=smtp.gmail.com
+MAIL_PORT=465
+MAIL_USER=example@gmail.com
+
+# 16x password
+MAIL_PASSWORD=xxxxxxxxyyyyyyyy
+MAIL_FROM_NAME="Task Tracker"
+MAIL_FROM_EMAIL=example@gmail.com
+
+S3_BUCKET_NAME=''
+S3_ENDPOINT=''
+S3_REGION=''
+S3_ACCESS_KEY=''
+S3_SECRET_KEY=''
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 9b1744f..f1a92a4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -2,7 +2,7 @@ name: Build and Push
on:
push:
- branches: [dev, main]
+ branches: [dev, main, feat/**]
env:
REGISTRY: ghcr.io
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: read
- # packages: write
+ packages: write
steps:
- uses: actions/checkout@v4
@@ -21,12 +21,12 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- # - name: Log in to the Container registry
- # uses: docker/login-action@v3
- # with:
- # registry: ${{ env.REGISTRY }}
- # username: ${{ github.actor }}
- # password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Log in to the Container registry
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
@@ -36,13 +36,14 @@ jobs:
tags: |
type=ref,event=branch
type=sha,format=short
+ type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.prod
- push: false # add true, if your setup variables
+ push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3034e40..4569ae1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,37 +1,37 @@
name: CI
on:
- pull_request:
- branches: [dev, main]
- push:
- branches: [dev, main]
+ pull_request:
+ branches: [dev, main, "feat/**"]
+ push:
+ branches: [dev, main, "feat/**"]
jobs:
- quality-check:
- name: Lint & Test
- runs-on: ubuntu-latest
+ quality-check:
+ name: Lint & Test
+ runs-on: ubuntu-latest
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
- - name: Install pnpm
- uses: pnpm/action-setup@v4
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: 20
- cache: 'pnpm'
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: "pnpm"
- - name: Install dependencies
- run: pnpm install --frozen-lockfile
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
- - name: Run Lint
- run: pnpm run lint
+ - name: Run Lint
+ run: pnpm run lint
- - name: Type Check
- run: pnpm exec tsc --noEmit
+ - name: Type Check
+ run: pnpm exec tsc --noEmit
- - name: Run Tests
- run: pnpm run test
+ - name: Run Tests
+ run: pnpm run test
diff --git a/Dockerfile.prod b/Dockerfile.prod
index 7112e09..b0ac8e7 100644
--- a/Dockerfile.prod
+++ b/Dockerfile.prod
@@ -28,7 +28,9 @@ ENV PORT=3000
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
+COPY --from=build /app/migrations ./migrations
COPY --from=build /app/package.json ./
+COPY --from=build /app/drizzle.config.ts ./drizzle.config.ts
EXPOSE 3000
diff --git a/infra/README.md b/infra/README.md
new file mode 100644
index 0000000..57e9bf6
--- /dev/null
+++ b/infra/README.md
@@ -0,0 +1,7 @@
+# Command to run infra at dev mode
+
+Run it by pwd at root! Not include at this dir
+
+```sh
+docker compose -f ./infra/dev/compose.dev.yaml --env-file .env --profile infra up --build -d -V
+```
diff --git a/infra/compose.dev.yaml b/infra/compose.dev.yaml
deleted file mode 100644
index e7c4d67..0000000
--- a/infra/compose.dev.yaml
+++ /dev/null
@@ -1,54 +0,0 @@
-version: '3.9'
-
-name: task-tracker
-
-services:
- api:
- hostname: api
- container_name: api
- build:
- context: ../
- dockerfile: Dockerfile.dev
- restart: always
- env_file:
- - ../.env
- ports:
- - '3000:3000'
- depends_on:
- database:
- condition: service_healthy
- redis:
- condition: service_started
- networks:
- - backend
-
- database:
- hostname: database
- container_name: database
- image: postgres:16-alpine
- restart: always
- env_file:
- - ../.env
- environment:
- POSTGRES_USER: ${DB_USERNAME:-admin}
- POSTGRES_PASSWORD: ${DB_PASSWORD:-admin}
- POSTGRES_DB: ${DB_DATABASE:-tracker}
- ports:
- - '6000:5432'
- volumes:
- - postgres_data:/var/lib/postgresql/data
- networks:
- - backend
- healthcheck:
- test: ['CMD-SHELL', 'pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE}']
- interval: 5s
- timeout: 5s
- retries: 5
- profiles: ['infra']
-
-volumes:
- postgres_data:
- redis_data:
-
-networks:
- backend:
diff --git a/infra/dev/README.md b/infra/dev/README.md
new file mode 100644
index 0000000..ac46b3a
--- /dev/null
+++ b/infra/dev/README.md
@@ -0,0 +1,41 @@
+# Файл для фронт разрабов
+
+## Описание
+
+Данный конфиг разворачивает полный инстанс бэкенда (API + DB + Redis)
+для локальной разработки фронтенда.
+
+## ТРЕБОВАНИЯ:
+
+1. Положить актуальный файл .env в директорию с этим файлом
+ (путь: ./infra/dev/.env).
+2. Наличие Docker Desktop / Docker Engine.
+
+## ЗАПУСК:
+
+Выполните команду из корня проекта:
+
+```sh
+docker compose -f ./infra/dev/compose.dev.yaml --profile infra up --pull always --build -d -V
+```
+
+## ЧТО ВНУТРИ:
+
+- API: http://localhost:3000
+- Postgres: localhost:6000 (пароли и база берутся из .env)
+- Redis: localhost:7000
+
+## ОСОБЕННОСТИ:
+
+- Авто-миграции: Приложение само накатит SQL-схему при старте.
+- Healthchecks: Контейнер API не поднимется, пока DB и Redis
+ не станут доступны (status: healthy).
+- Изоляция: Используется выделенная сеть 'task-tracker-gateway'.
+
+## RESET:
+
+Если нужно полностью очистить базу и начать с нуля:
+
+```sh
+docker compose -f ./infra/dev/compose.dev.yaml --profile infra down -v
+```
diff --git a/infra/dev/compose.dev.yaml b/infra/dev/compose.dev.yaml
new file mode 100644
index 0000000..19e2855
--- /dev/null
+++ b/infra/dev/compose.dev.yaml
@@ -0,0 +1,121 @@
+version: "3.9"
+
+name: task-tracker-api
+
+services:
+ api:
+ hostname: api
+ container_name: api
+ image: ghcr.io/task-tracker-lab/task-tracker-backend:feat-user
+ env_file:
+ - .env
+ ports:
+ - "3000:3000"
+ depends_on:
+ database:
+ condition: service_healthy
+ redis:
+ condition: service_healthy
+ networks:
+ - backend
+ deploy:
+ resources:
+ limits:
+ cpus: "2.0"
+ memory: 1024M
+ reservations:
+ cpus: "0.5"
+ memory: 256M
+
+ database:
+ hostname: database
+ container_name: database
+ image: postgres:16-alpine
+ restart: always
+ env_file:
+ - .env
+ environment:
+ POSTGRES_USER: ${DB_USERNAME}
+ POSTGRES_PASSWORD: ${DB_PASSWORD}
+ POSTGRES_DB: ${DB_DATABASE}
+ ports:
+ - "6000:5432"
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ networks:
+ - backend
+ healthcheck:
+ test:
+ [
+ "CMD-SHELL",
+ 'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" -q || exit 1',
+ ]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+ profiles: ["infra"]
+
+ redis:
+ hostname: redis
+ container_name: redis
+ image: redis:7-alpine
+ restart: always
+ ports:
+ - "7000:6379"
+ command: redis-server --save 60 1 --loglevel notice
+ volumes:
+ - redis_data:/data
+ networks:
+ - backend
+ healthcheck:
+ test: ["CMD", "redis-cli", "ping"]
+ interval: 5s
+ timeout: 3s
+ retries: 5
+ profiles: ["infra"]
+
+ minio:
+ hostname: minio
+ container_name: minio
+ image: minio/minio:latest
+ restart: always
+ environment:
+ MINIO_ROOT_USER: ${S3_ACCESS_KEY}
+ MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY}
+ ports:
+ - "9000:9000" # API
+ - "9001:9001" # Console (UI)
+ command: server /data --console-address ":9001"
+ volumes:
+ - minio_data:/data
+ networks:
+ - backend
+ profiles: [ "infra" ]
+
+ minio-init:
+ image: minio/mc:latest
+ depends_on:
+ - minio
+ environment:
+ MINIO_ROOT_USER: ${S3_ACCESS_KEY}
+ MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY}
+ networks:
+ - backend
+ profiles: [ "infra" ]
+ entrypoint: >
+ /bin/sh -c "
+ sleep 5;
+ mc alias set myminio http://minio:9000 ${S3_ACCESS_KEY} ${S3_SECRET_KEY};
+ mc mb myminio/${S3_BUCKET_NAME} --ignore-existing;
+ mc anonymous set download myminio/${S3_BUCKET_NAME};
+ exit 0;
+ "
+
+volumes:
+ postgres_data:
+ redis_data:
+ minio_data:
+
+networks:
+ backend:
+ name: task-tracker-gateway
diff --git a/libs/bootstrap/src/bootstrap.ts b/libs/bootstrap/src/bootstrap.ts
index 80ec104..0ca163f 100644
--- a/libs/bootstrap/src/bootstrap.ts
+++ b/libs/bootstrap/src/bootstrap.ts
@@ -8,6 +8,7 @@ import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fa
import type { BootstrapOptions } from './interfaces/options.interface';
import fastifyCookie from '@fastify/cookie';
import fastifyCompress from '@fastify/compress';
+import fastifyMultipart from '@fastify/multipart';
export async function bootstrapApp(options: BootstrapOptions) {
const adapter = new FastifyAdapter();
@@ -47,6 +48,12 @@ export async function bootstrapApp(options: BootstrapOptions) {
threshold: 1024,
});
+ await app.register(fastifyMultipart, {
+ limits: {
+ fileSize: 5 * 1024 * 1024,
+ },
+ });
+
if (apiPrefix) app.setGlobalPrefix(apiPrefix);
if (useCors) setupCors(app, origins);
if (swaggerOptions) {
diff --git a/libs/bootstrap/src/setups/cors.ts b/libs/bootstrap/src/setups/cors.ts
index 73d2847..59a7959 100644
--- a/libs/bootstrap/src/setups/cors.ts
+++ b/libs/bootstrap/src/setups/cors.ts
@@ -11,13 +11,22 @@ export function setupCors(app: NestFastifyApplication, origins: string[]) {
return callback(null, true);
}
- const { hostname } = new URL(origin);
+ try {
+ const { hostname } = new URL(origin);
+ const allowedHostnames = origins.map((o) => new URL(o).hostname);
- if (origins.some((o) => hostname === o || hostname.endsWith(`.${o}`))) {
- callback(null, origin);
- }
+ if (
+ allowedHostnames.some(
+ (allowed) => hostname === allowed || hostname.endsWith(`.${allowed}`),
+ )
+ ) {
+ return callback(null, origin);
+ }
- callback(new Error('Not allowed by CORS'), false);
+ callback(new Error('Not allowed by CORS'), false);
+ } catch (e) {
+ callback(new Error('Invalid origin format'), false);
+ }
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
diff --git a/libs/bootstrap/src/setups/swagger.ts b/libs/bootstrap/src/setups/swagger.ts
index 90e938f..58d79af 100644
--- a/libs/bootstrap/src/setups/swagger.ts
+++ b/libs/bootstrap/src/setups/swagger.ts
@@ -3,6 +3,7 @@ import { cleanupOpenApiDoc } from 'nestjs-zod';
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
import type { SwaggerOptions } from '../interfaces';
import { SWAGGER_DEFAULTS } from '../configs/swagger';
+import { GlobalErrorResponse } from 'src/shared/error/schema';
export async function setupSwagger(app: NestFastifyApplication, options: SwaggerOptions = {}) {
const { title, description, version, path, server } = {
@@ -22,7 +23,9 @@ export async function setupSwagger(app: NestFastifyApplication, options: Swagger
if (stage) builder.addServer(`https://api.${stage}`, 'Staging');
if (domain) builder.addServer(`https://api.${domain}`, 'Production');
- const document = SwaggerModule.createDocument(app, builder.build());
+ const document = SwaggerModule.createDocument(app, builder.build(), {
+ extraModels: [GlobalErrorResponse.Output],
+ });
SwaggerModule.setup(path, app, cleanupOpenApiDoc(document), {
jsonDocumentUrl: `${path}/s/json`,
diff --git a/libs/config/src/config.schema.ts b/libs/config/src/config.schema.ts
index 348a00f..e28d54f 100644
--- a/libs/config/src/config.schema.ts
+++ b/libs/config/src/config.schema.ts
@@ -1,4 +1,9 @@
import { z } from 'zod/v4';
+import { jwtSecretValidation } from './helpers/jwt-secren-validation';
+
+const timeStringSchema = z.string().regex(/^[0-9]+[smhdw]$/, {
+ message: 'Invalid time format. Use: s, m, h, d, w (e.g., 15m, 24h, 30d)',
+});
export const ConfigSchema = z.object({
PORT: z.coerce.number().default(3000),
@@ -9,7 +14,7 @@ export const ConfigSchema = z.object({
DB_SCHEMA: z.string({ error: 'DB_SCHEMA is missing' }),
DATABASE_URL: z.string().url('DATABASE_URL must be a valid connection string'),
REDIS_HOST: z.string().default('redis'),
- REDIS_PORT: z.coerce.number().default(6379),
+ REDIS_PORT: z.coerce.number().optional().default(6379),
DOMAIN: z
.string()
.toLowerCase()
@@ -29,6 +34,57 @@ export const ConfigSchema = z.object({
.min(1, "CORS_ALLOWED_ORIGINS can't be empty")
.transform((val) => val.split(',').map((s) => s.trim()))
.pipe(z.array(z.string().url('Each origin must be a valid URL'))),
+ JWT_ACCESS_SECRET: z.string().refine(jwtSecretValidation, {
+ message:
+ 'JWT_ACCESS_SECRET must be at least 32 characters long OR contain at least 5 words separated by hyphens',
+ }),
+ JWT_REFRESH_SECRET: z.string().refine(jwtSecretValidation, {
+ message:
+ 'JWT_REFRESH_SECRET must be at least 32 characters long OR contain at least 5 words separated by hyphens',
+ }),
+ JWT_ACCESS_EXPIRES_IN: timeStringSchema.default('15m'),
+ JWT_REFRESH_EXPIRES_IN: timeStringSchema.default('30d'),
+ MAIL_HOST: z
+ .string({
+ error: 'Mail server host (MAIL_HOST) is not specified',
+ })
+ .min(1, 'MAIL_HOST cannot be empty'),
+ MAIL_PORT: z.coerce.number({
+ error: 'Mail port (MAIL_PORT) is not specified',
+ }),
+ MAIL_USER: z
+ .string({
+ error: 'Sender email (MAIL_USER) is not specified',
+ })
+ .email('MAIL_USER must be a valid email address'),
+ MAIL_PASSWORD: z
+ .string({
+ error: 'Mail password (MAIL_PASSWORD) is required',
+ })
+ .min(1, 'Mail password cannot be empty'),
+ MAIL_FROM_NAME: z
+ .string({
+ error: 'Sender name (MAIL_FROM_NAME) is not specified',
+ })
+ .min(1, 'Sender name cannot be empty'),
+ MAIL_FROM_EMAIL: z.string().email('Invalid MAIL_FROM_EMAIL format').optional(),
+ S3_BUCKET_NAME: z
+ .string({
+ error: "S3_BUCKET_NAME is required. Example: 'avatars'",
+ })
+ .min(1),
+ S3_ENDPOINT: z
+ .string({
+ error: "S3_ENDPOINT is required. Example: 'http://localhost:9000'",
+ })
+ .url('S3_ENDPOINT must be a valid URL'),
+ S3_REGION: z.string().default('us-east-1'),
+ S3_ACCESS_KEY: z.string({
+ error: 'S3_ACCESS_KEY is missing (MinIO root user or IAM user)',
+ }),
+ S3_SECRET_KEY: z.string({
+ error: 'S3_SECRET_KEY is missing (MinIO root password or IAM secret)',
+ }),
});
export type Config = z.infer;
diff --git a/libs/config/src/helpers/jwt-secren-validation.ts b/libs/config/src/helpers/jwt-secren-validation.ts
new file mode 100644
index 0000000..27a4e18
--- /dev/null
+++ b/libs/config/src/helpers/jwt-secren-validation.ts
@@ -0,0 +1,7 @@
+export function jwtSecretValidation(val: string) {
+ const isLongEnough = val.length >= 32;
+ const words = val.split('-');
+ const hasFiveWords = words.length >= 5 && words.every((word) => word.length > 0);
+
+ return isLongEnough || hasFiveWords;
+}
diff --git a/libs/database/src/database.module.ts b/libs/database/src/database.module.ts
index 7a89484..07d5c78 100644
--- a/libs/database/src/database.module.ts
+++ b/libs/database/src/database.module.ts
@@ -55,15 +55,17 @@ export class DatabaseModule implements OnApplicationShutdown {
provide: DATABASE_SERVICE,
useFactory: async (cfg: ConfigService, opts: DatabaseModuleOptions) => {
const baseUrl = cfg.get('DATABASE_URL');
+ const url = new URL(baseUrl);
+ url.searchParams.set('options', `-c search_path=${opts.schemaName || 'public'}`);
const pool = new Pool({
- connectionString: baseUrl,
+ connectionString: url.toString(),
max: 20,
+ min: 5,
+ connectionTimeoutMillis: 5000,
idleTimeoutMillis: 30000,
- });
-
- pool.on('connect', (client) => {
- client.query(`SET search_path TO ${opts.schemaName || 'public'}`);
+ maxUses: 7500,
+ keepAlive: true,
});
this.pool = pool;
diff --git a/libs/s3/src/dtos/upload-avatar.dto.ts b/libs/s3/src/dtos/upload-avatar.dto.ts
new file mode 100644
index 0000000..32a11f5
--- /dev/null
+++ b/libs/s3/src/dtos/upload-avatar.dto.ts
@@ -0,0 +1,5 @@
+export class FileUploadDto {
+ buffer: Buffer;
+ filename: string;
+ mimetype: string;
+}
diff --git a/libs/s3/src/index.ts b/libs/s3/src/index.ts
new file mode 100644
index 0000000..d819c35
--- /dev/null
+++ b/libs/s3/src/index.ts
@@ -0,0 +1,2 @@
+export * from './s3.module';
+export * from './s3.service';
diff --git a/libs/s3/src/interfaces/index.ts b/libs/s3/src/interfaces/index.ts
new file mode 100644
index 0000000..073bc43
--- /dev/null
+++ b/libs/s3/src/interfaces/index.ts
@@ -0,0 +1,5 @@
+export type {
+ S3ModuleOptions,
+ S3ModuleAsyncOptions,
+ S3ModuleOptionsFactory,
+} from './module.interface';
diff --git a/libs/s3/src/interfaces/module.interface.ts b/libs/s3/src/interfaces/module.interface.ts
new file mode 100644
index 0000000..1edd054
--- /dev/null
+++ b/libs/s3/src/interfaces/module.interface.ts
@@ -0,0 +1,38 @@
+import type { S3ClientConfig } from '@aws-sdk/client-s3';
+import type { FactoryProvider, ModuleMetadata, Provider, Type } from '@nestjs/common';
+
+export interface S3ConnectionOptions extends Pick<
+ S3ClientConfig,
+ 'credentials' | 'endpoint' | 'region'
+> {
+ bucket: string;
+}
+
+export interface S3OtherOptions extends Omit<
+ S3ClientConfig,
+ 'credentials' | 'endpoint' | 'region'
+> {}
+
+export interface S3ModuleOptions {
+ connection: S3ConnectionOptions;
+ config?: S3OtherOptions;
+ global?: boolean;
+}
+
+export interface S3ModuleOptionsFactory {
+ createS3Options(): Promise | S3ModuleOptions;
+}
+
+export interface S3ModuleAsyncOptions extends Pick<
+ ModuleMetadata,
+ 'imports'
+> {
+ useExisting?: Type;
+ useClass?: Type;
+ useFactory?: (
+ ...args: T
+ ) => Promise> | Omit;
+ inject?: FactoryProvider['inject'];
+ global?: boolean;
+ extraProviders?: Provider[];
+}
diff --git a/libs/s3/src/s3.constants.ts b/libs/s3/src/s3.constants.ts
new file mode 100644
index 0000000..c55a9ab
--- /dev/null
+++ b/libs/s3/src/s3.constants.ts
@@ -0,0 +1 @@
+export const S3_OPTIONS = 'S3_OPTIONS';
diff --git a/libs/s3/src/s3.module.ts b/libs/s3/src/s3.module.ts
new file mode 100644
index 0000000..ee7d610
--- /dev/null
+++ b/libs/s3/src/s3.module.ts
@@ -0,0 +1,41 @@
+import { type DynamicModule, Module, type Provider } from '@nestjs/common';
+import type { S3ModuleOptions, S3ModuleAsyncOptions } from './interfaces';
+import { S3Service } from './s3.service';
+import { S3_OPTIONS } from './s3.constants';
+
+@Module({
+ providers: [S3Service],
+ exports: [S3Service],
+})
+export class S3Module {
+ static register(options: S3ModuleOptions): DynamicModule {
+ const { global, ...config } = options;
+
+ return {
+ global,
+ module: S3Module,
+ providers: [{ provide: S3_OPTIONS, useValue: config }, S3Service],
+ exports: [S3Service],
+ };
+ }
+
+ static registerAsync(options: S3ModuleAsyncOptions): DynamicModule {
+ const { global, imports } = options;
+
+ return {
+ global,
+ module: S3Module,
+ imports: imports || [],
+ providers: [this.createAsyncOptionsProvider(options), S3Service],
+ exports: [S3Service],
+ };
+ }
+
+ private static createAsyncOptionsProvider(options: S3ModuleAsyncOptions): Provider {
+ return {
+ provide: S3_OPTIONS,
+ useFactory: options.useFactory,
+ inject: options.inject || [],
+ };
+ }
+}
diff --git a/libs/s3/src/s3.service.ts b/libs/s3/src/s3.service.ts
new file mode 100644
index 0000000..47d8a8d
--- /dev/null
+++ b/libs/s3/src/s3.service.ts
@@ -0,0 +1,50 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { S3Client } from '@aws-sdk/client-s3';
+import { S3_OPTIONS } from './s3.constants';
+import { S3ModuleOptions } from './interfaces';
+import { PutObjectCommand } from '@aws-sdk/client-s3';
+import { randomUUID } from 'crypto';
+import { extname } from 'path';
+
+@Injectable()
+export class S3Service {
+ private readonly s3Client: S3Client;
+ public readonly bucket: string;
+ private readonly endpoint: string;
+
+ constructor(
+ @Inject(S3_OPTIONS)
+ private readonly options: S3ModuleOptions,
+ ) {
+ const { bucket, credentials, endpoint, region } = options.connection;
+ this.bucket = bucket;
+ this.endpoint = endpoint as string;
+
+ this.s3Client = new S3Client({
+ region,
+ endpoint,
+ credentials,
+ ...options.config,
+ });
+ }
+
+ async uploadPublicFile(
+ fileBuffer: Buffer,
+ originalName: string,
+ mimetype: string,
+ ): Promise {
+ const extension = extname(originalName);
+ const fileName = `${randomUUID()}${extension}`;
+
+ const command = new PutObjectCommand({
+ Bucket: this.bucket,
+ Key: fileName,
+ Body: fileBuffer,
+ ContentType: mimetype,
+ });
+
+ await this.s3Client.send(command);
+
+ return `${this.endpoint}/${this.bucket}/${fileName}`;
+ }
+}
diff --git a/libs/s3/tsconfig.lib.json b/libs/s3/tsconfig.lib.json
new file mode 100644
index 0000000..0cd20fa
--- /dev/null
+++ b/libs/s3/tsconfig.lib.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "declaration": true,
+ "outDir": "../../dist/libs/s3"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
+}
diff --git a/migrations/0001_solid_kronos.sql b/migrations/0001_solid_kronos.sql
new file mode 100644
index 0000000..faed36a
--- /dev/null
+++ b/migrations/0001_solid_kronos.sql
@@ -0,0 +1,57 @@
+CREATE TABLE "base"."user_activity" (
+ "id" text PRIMARY KEY NOT NULL,
+ "user_id" text NOT NULL,
+ "event_type" varchar(50) NOT NULL,
+ "entity_id" varchar,
+ "metadata" jsonb,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+
+CREATE TABLE "base"."user_notifications" (
+ "user_id" text PRIMARY KEY NOT NULL,
+ "settings" jsonb DEFAULT '{"email":{"task_assigned":true,"mentions":true,"daily_summary":false},"push":{"task_assigned":true,"reminders":true}}'::jsonb NOT NULL
+);
+
+CREATE TABLE "base"."user_security" (
+ "user_id" text PRIMARY KEY NOT NULL,
+ "password_hash" varchar(255) NOT NULL,
+ "is_2fa_enabled" boolean DEFAULT false NOT NULL,
+ "two_factor_secret" text,
+ "last_password_change" timestamp with time zone DEFAULT now() NOT NULL
+);
+
+CREATE TABLE "base"."users" (
+ "id" text PRIMARY KEY NOT NULL,
+ "first_name" varchar(50) NOT NULL,
+ "last_name" varchar(50) NOT NULL,
+ "middle_name" varchar(50),
+ "email" varchar(255) NOT NULL,
+ "bio" text,
+ "avatar_url" varchar(512),
+ "timezone" varchar(50) DEFAULT 'UTC' NOT NULL,
+ "language" varchar(5) DEFAULT 'ru' NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
+ CONSTRAINT "users_email_unique" UNIQUE("email")
+);
+
+CREATE TABLE "base"."sessions" (
+ "id" text PRIMARY KEY NOT NULL,
+ "user_id" text NOT NULL,
+ "device_type" varchar(20),
+ "browser" varchar(50),
+ "os" varchar(50),
+ "user_agent" text NOT NULL,
+ "ip" varchar(45) NOT NULL,
+ "city" varchar(100),
+ "country_code" varchar(5),
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "expires_at" timestamp with time zone NOT NULL,
+ "is_revoked" boolean DEFAULT false NOT NULL
+);
+
+ALTER TABLE "base"."user_activity" ADD CONSTRAINT "user_activity_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "base"."users"("id") ON DELETE cascade ON UPDATE no action;
+ALTER TABLE "base"."user_notifications" ADD CONSTRAINT "user_notifications_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "base"."users"("id") ON DELETE cascade ON UPDATE no action;
+ALTER TABLE "base"."user_security" ADD CONSTRAINT "user_security_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "base"."users"("id") ON DELETE cascade ON UPDATE no action;
+ALTER TABLE "base"."sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "base"."users"("id") ON DELETE cascade ON UPDATE no action;
\ No newline at end of file
diff --git a/migrations/meta/0001_snapshot.json b/migrations/meta/0001_snapshot.json
new file mode 100644
index 0000000..3cbcbd8
--- /dev/null
+++ b/migrations/meta/0001_snapshot.json
@@ -0,0 +1,369 @@
+{
+ "id": "c5575cbf-cbee-46d8-af83-95b96a2afceb",
+ "prevId": "a40dfb7f-7d44-4721-bf37-a197b5f1e479",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "base.user_activity": {
+ "name": "user_activity",
+ "schema": "base",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_activity_user_id_users_id_fk": {
+ "name": "user_activity_user_id_users_id_fk",
+ "tableFrom": "user_activity",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.user_notifications": {
+ "name": "user_notifications",
+ "schema": "base",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "settings": {
+ "name": "settings",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"email\":{\"task_assigned\":true,\"mentions\":true,\"daily_summary\":false},\"push\":{\"task_assigned\":true,\"reminders\":true}}'::jsonb"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_notifications_user_id_users_id_fk": {
+ "name": "user_notifications_user_id_users_id_fk",
+ "tableFrom": "user_notifications",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.user_security": {
+ "name": "user_security",
+ "schema": "base",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_2fa_enabled": {
+ "name": "is_2fa_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "two_factor_secret": {
+ "name": "two_factor_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_password_change": {
+ "name": "last_password_change",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_security_user_id_users_id_fk": {
+ "name": "user_security_user_id_users_id_fk",
+ "tableFrom": "user_security",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.users": {
+ "name": "users",
+ "schema": "base",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "first_name": {
+ "name": "first_name",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_name": {
+ "name": "last_name",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "middle_name": {
+ "name": "middle_name",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bio": {
+ "name": "bio",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "avatar_url": {
+ "name": "avatar_url",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'UTC'"
+ },
+ "language": {
+ "name": "language",
+ "type": "varchar(5)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ru'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": ["email"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.sessions": {
+ "name": "sessions",
+ "schema": "base",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "browser": {
+ "name": "browser",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "os": {
+ "name": "os",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip": {
+ "name": "ip",
+ "type": "varchar(45)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "city": {
+ "name": "city",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "country_code": {
+ "name": "country_code",
+ "type": "varchar(5)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_revoked": {
+ "name": "is_revoked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {
+ "base": "base"
+ },
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json
index 17d6d2b..713b19d 100644
--- a/migrations/meta/_journal.json
+++ b/migrations/meta/_journal.json
@@ -1,13 +1,20 @@
{
- "version": "7",
- "dialect": "postgresql",
- "entries": [
- {
- "idx": 0,
- "version": "7",
- "when": 1775839169154,
- "tag": "0000_stale_sunspot",
- "breakpoints": true
- }
- ]
-}
\ No newline at end of file
+ "version": "7",
+ "dialect": "postgresql",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "7",
+ "when": 1775839169154,
+ "tag": "0000_stale_sunspot",
+ "breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "7",
+ "when": 1775925642197,
+ "tag": "0001_solid_kronos",
+ "breakpoints": true
+ }
+ ]
+}
diff --git a/nest-cli.json b/nest-cli.json
index 03f8cdf..572e181 100644
--- a/nest-cli.json
+++ b/nest-cli.json
@@ -42,6 +42,15 @@
"compilerOptions": {
"tsConfigPath": "libs/health/tsconfig.lib.json"
}
+ },
+ "s3": {
+ "type": "library",
+ "root": "libs/s3",
+ "entryFile": "index",
+ "sourceRoot": "libs/s3/src",
+ "compilerOptions": {
+ "tsConfigPath": "libs/s3/tsconfig.lib.json"
+ }
}
}
}
diff --git a/package.json b/package.json
index f202d2e..f9cd3cf 100644
--- a/package.json
+++ b/package.json
@@ -24,24 +24,46 @@
"prepare": "husky"
},
"dependencies": {
+ "@aws-sdk/client-s3": "^3.1029.0",
+ "@aws-sdk/s3-request-presigner": "^3.1029.0",
+ "@bull-board/api": "^6.21.0",
+ "@bull-board/fastify": "^6.21.0",
+ "@bull-board/nestjs": "^6.21.0",
"@fastify/compress": "^8.3.1",
"@fastify/cookie": "^11.0.2",
"@fastify/cors": "^11.2.0",
+ "@fastify/multipart": "^10.0.0",
"@fastify/static": "^9.1.0",
+ "@nestjs-modules/ioredis": "^2.2.1",
+ "@nestjs/bullmq": "^11.0.4",
"@nestjs/common": "^11.1.18",
"@nestjs/config": "^4.0.4",
"@nestjs/core": "^11.1.18",
+ "@nestjs/jwt": "^11.0.2",
+ "@nestjs/passport": "^11.0.5",
"@nestjs/platform-fastify": "^11.1.18",
"@nestjs/swagger": "^11.2.7",
"@nestjs/throttler": "^6.5.0",
+ "@paralleldrive/cuid2": "^3.3.0",
"@willsoto/nestjs-prometheus": "^6.1.0",
+ "argon2": "^0.44.0",
+ "bullmq": "^5.73.4",
"drizzle-orm": "^0.45.2",
"drizzle-zod": "^0.8.3",
+ "email-validator": "^2.0.4",
"fastify": "^5.8.4",
+ "handlebars": "^4.7.9",
+ "ioredis": "^5.10.1",
"nestjs-zod": "^5.3.0",
+ "nodemailer": "^8.0.5",
+ "otplib": "^13.4.0",
+ "passport": "^0.7.0",
+ "passport-jwt": "^4.0.1",
+ "passport-local": "^1.0.0",
"pg": "^8.20.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
+ "ua-parser-js": "^2.0.9",
"zod": "^4.3.6"
},
"devDependencies": {
@@ -51,8 +73,12 @@
"@nestjs/schematics": "^11.0.10",
"@nestjs/testing": "^11.1.18",
"@types/node": "^20.3.1",
+ "@types/nodemailer": "^8.0.0",
+ "@types/passport-jwt": "^4.0.1",
+ "@types/passport-local": "^1.0.38",
"@types/pg": "^8.20.0",
"@types/supertest": "^6.0.0",
+ "@types/ua-parser-js": "^0.7.39",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitest/coverage-v8": "^4.1.4",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 91a0420..4d0faec 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,21 @@ importers:
.:
dependencies:
+ '@aws-sdk/client-s3':
+ specifier: ^3.1029.0
+ version: 3.1029.0
+ '@aws-sdk/s3-request-presigner':
+ specifier: ^3.1029.0
+ version: 3.1029.0
+ '@bull-board/api':
+ specifier: ^6.21.0
+ version: 6.21.0(@bull-board/ui@6.21.0)
+ '@bull-board/fastify':
+ specifier: ^6.21.0
+ version: 6.21.0
+ '@bull-board/nestjs':
+ specifier: ^6.21.0
+ version: 6.21.0(@bull-board/api@6.21.0(@bull-board/ui@6.21.0))(@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)))(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@fastify/compress':
specifier: ^8.3.1
version: 8.3.1
@@ -17,9 +32,18 @@ importers:
'@fastify/cors':
specifier: ^11.2.0
version: 11.2.0
+ '@fastify/multipart':
+ specifier: ^10.0.0
+ version: 10.0.0
'@fastify/static':
specifier: ^9.1.0
version: 9.1.0
+ '@nestjs-modules/ioredis':
+ specifier: ^2.2.1
+ version: 2.2.1(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(ioredis@5.10.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/bullmq':
+ specifier: ^11.0.4
+ version: 11.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(bullmq@5.73.4)
'@nestjs/common':
specifier: ^11.1.18
version: 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
@@ -29,30 +53,69 @@ importers:
'@nestjs/core':
specifier: ^11.1.18
version: 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/jwt':
+ specifier: ^11.0.2
+ version: 11.0.2(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))
+ '@nestjs/passport':
+ specifier: ^11.0.5
+ version: 11.0.5(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)
'@nestjs/platform-fastify':
specifier: ^11.1.18
- version: 11.1.18(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))
+ version: 11.1.18(@fastify/static@9.1.0)(@fastify/view@11.1.1)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))
'@nestjs/swagger':
specifier: ^11.2.7
version: 11.2.7(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)
'@nestjs/throttler':
specifier: ^6.5.0
version: 6.5.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)
+ '@paralleldrive/cuid2':
+ specifier: ^3.3.0
+ version: 3.3.0
'@willsoto/nestjs-prometheus':
specifier: ^6.1.0
version: 6.1.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(prom-client@15.1.3)
+ argon2:
+ specifier: ^0.44.0
+ version: 0.44.0
+ bullmq:
+ specifier: ^5.73.4
+ version: 5.73.4
drizzle-orm:
specifier: ^0.45.2
version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.20.0)
drizzle-zod:
specifier: ^0.8.3
version: 0.8.3(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.20.0))(zod@4.3.6)
+ email-validator:
+ specifier: ^2.0.4
+ version: 2.0.4
fastify:
specifier: ^5.8.4
version: 5.8.4
+ handlebars:
+ specifier: ^4.7.9
+ version: 4.7.9
+ ioredis:
+ specifier: ^5.10.1
+ version: 5.10.1
nestjs-zod:
specifier: ^5.3.0
version: 5.3.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.2.7(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6)
+ nodemailer:
+ specifier: ^8.0.5
+ version: 8.0.5
+ otplib:
+ specifier: ^13.4.0
+ version: 13.4.0
+ passport:
+ specifier: ^0.7.0
+ version: 0.7.0
+ passport-jwt:
+ specifier: ^4.0.1
+ version: 4.0.1
+ passport-local:
+ specifier: ^1.0.0
+ version: 1.0.0
pg:
specifier: ^8.20.0
version: 8.20.0
@@ -62,6 +125,9 @@ importers:
rxjs:
specifier: ^7.8.1
version: 7.8.2
+ ua-parser-js:
+ specifier: ^2.0.9
+ version: 2.0.9
zod:
specifier: ^4.3.6
version: 4.3.6
@@ -84,12 +150,24 @@ importers:
'@types/node':
specifier: ^20.3.1
version: 20.19.39
+ '@types/nodemailer':
+ specifier: ^8.0.0
+ version: 8.0.0
+ '@types/passport-jwt':
+ specifier: ^4.0.1
+ version: 4.0.1
+ '@types/passport-local':
+ specifier: ^1.0.38
+ version: 1.0.38
'@types/pg':
specifier: ^8.20.0
version: 8.20.0
'@types/supertest':
specifier: ^6.0.0
version: 6.0.3
+ '@types/ua-parser-js':
+ specifier: ^0.7.39
+ version: 0.7.39
'@typescript-eslint/eslint-plugin':
specifier: ^6.0.0
version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)
@@ -178,6 +256,173 @@ packages:
resolution: {integrity: sha512-lnw+ZM1Io+cJAkReC0NPDjqObL8NtKzKIkdgEEKC8CUmkhurYhedbicN8Y8NYHgG1uLd2GozW3+/QqPRZaN+Lw==}
engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
+ '@aws-crypto/crc32@5.2.0':
+ resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
+ engines: {node: '>=16.0.0'}
+
+ '@aws-crypto/crc32c@5.2.0':
+ resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==}
+
+ '@aws-crypto/sha1-browser@5.2.0':
+ resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==}
+
+ '@aws-crypto/sha256-browser@5.2.0':
+ resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==}
+
+ '@aws-crypto/sha256-js@5.2.0':
+ resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==}
+ engines: {node: '>=16.0.0'}
+
+ '@aws-crypto/supports-web-crypto@5.2.0':
+ resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==}
+
+ '@aws-crypto/util@5.2.0':
+ resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==}
+
+ '@aws-sdk/client-s3@3.1029.0':
+ resolution: {integrity: sha512-OuA8RZTxsAaHDcI25j2NGLMaYFI2WpJdDzK3uLmVBmaHwjQKQZOUDVVBcln8pNo3IgkY+HRSJhRR4/xlM//UyQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/core@3.973.27':
+ resolution: {integrity: sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/crc64-nvme@3.972.6':
+ resolution: {integrity: sha512-NMbiqKdruhwwgI6nzBVe2jWMkXjaoQz2YOs3rFX+2F3gGyrJDkDPwMpV/RsTFeq2vAQ055wZNtOXFK4NYSkM8g==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-env@3.972.25':
+ resolution: {integrity: sha512-6QfI0wv4jpG5CrdO/AO0JfZ2ux+tKwJPrUwmvxXF50vI5KIypKVGNF6b4vlkYEnKumDTI1NX2zUBi8JoU5QU3A==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-http@3.972.27':
+ resolution: {integrity: sha512-3V3Usj9Gs93h865DqN4M2NWJhC5kXU9BvZskfN3+69omuYlE3TZxOEcVQtBGLOloJB7BVfJKXVLqeNhOzHqSlQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-ini@3.972.29':
+ resolution: {integrity: sha512-SiBuAnXecCbT/OpAf3vqyI/AVE3mTaYr9ShXLybxZiPLBiPCCOIWSGAtYYGQWMRvobBTiqOewaB+wcgMMZI2Aw==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-login@3.972.29':
+ resolution: {integrity: sha512-OGOslTbOlxXexKMqhxCEbBQbUIfuhGxU5UXw3Fm56ypXHvrXH4aTt/xb5Y884LOoteP1QST1lVZzHfcTnWhiPQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-node@3.972.30':
+ resolution: {integrity: sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-process@3.972.25':
+ resolution: {integrity: sha512-HR7ynNRdNhNsdVCOCegy1HsfsRzozCOPtD3RzzT1JouuaHobWyRfJzCBue/3jP7gECHt+kQyZUvwg/cYLWurNQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-sso@3.972.29':
+ resolution: {integrity: sha512-HWv4SEq3jZDYPlwryZVef97+U8CxxRos5mK8sgGO1dQaFZpV5giZLzqGE5hkDmh2csYcBO2uf5XHjPTpZcJlig==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-web-identity@3.972.29':
+ resolution: {integrity: sha512-PdMBza1WEKEUPFEmMGCfnU2RYCz9MskU2e8JxjyUOsMKku7j9YaDKvbDi2dzC0ihFoM6ods2SbhfAAro+Gwlew==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-bucket-endpoint@3.972.9':
+ resolution: {integrity: sha512-COToYKgquDyligbcAep7ygs48RK+mwe/IYprq4+TSrVFzNOYmzWvHf6werpnKV5VYpRiwdn+Wa5ZXkPqLVwcTg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-expect-continue@3.972.9':
+ resolution: {integrity: sha512-V/FNCjFxnh4VGu+HdSiW4Yg5GELihA1MIDSAdsEPvuayXBVmr0Jaa6jdLAZLH38KYXl/vVjri9DQJWnTAujHEA==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-flexible-checksums@3.974.7':
+ resolution: {integrity: sha512-uU4/ch2CLHB8Phu1oTKnnQ4e8Ujqi49zEnQYBhWYT53zfFvtJCdGsaOoypBr8Fm/pmCBssRmGoIQ4sixgdLP9w==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-host-header@3.972.9':
+ resolution: {integrity: sha512-je5vRdNw4SkuTnmRbFZLdye4sQ0faLt8kwka5wnnSU30q1mHO4X+idGEJOOE+Tn1ME7Oryn05xxkDvIb3UaLaQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-location-constraint@3.972.9':
+ resolution: {integrity: sha512-TyfOi2XNdOZpNKeTJwRUsVAGa+14nkyMb2VVGG+eDgcWG/ed6+NUo72N3hT6QJioxym80NSinErD+LBRF0Ir1w==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-logger@3.972.9':
+ resolution: {integrity: sha512-HsVgDrruhqI28RkaXALm8grJ7Agc1wF6Et0xh6pom8NdO2VdO/SD9U/tPwUjewwK/pVoka+EShBxyCvgsPCtog==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-recursion-detection@3.972.10':
+ resolution: {integrity: sha512-RVQQbq5orQ/GHUnXvqEOj2HHPBJm+mM+ySwZKS5UaLBwra5ugRtiH09PLUoOZRl7a1YzaOzXSuGbn9iD5j60WQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-sdk-s3@3.972.28':
+ resolution: {integrity: sha512-qJHcJQH9UNPUrnPlRtCozKjtqAaypQ5IgQxTNoPsVYIQeuwNIA8Rwt3NvGij1vCDYDfCmZaPLpnJEHlZXeFqmg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-ssec@3.972.9':
+ resolution: {integrity: sha512-wSA2BR7L0CyBNDJeSrleIIzC+DzL93YNTdfU0KPGLiocK6YsRv1nPAzPF+BFSdcs0Qa5ku5Kcf4KvQcWwKGenQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-user-agent@3.972.29':
+ resolution: {integrity: sha512-f/sIRzuTfEjg6NsbMYvye2VsmnQoNgntntleQyx5uGacUYzszbfIlO3GcI6G6daWUmTm0IDZc11qMHWwF0o0mQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/nested-clients@3.996.19':
+ resolution: {integrity: sha512-uFkmCDXvmQYLanlYdOFS0+MQWkrj9wPMt/ZCc/0J0fjPim6F5jBVBmEomvGY/j77ILW6GTPwN22Jc174Mhkw6Q==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/region-config-resolver@3.972.11':
+ resolution: {integrity: sha512-6Q8B1dcx6BBqUTY1Mc/eROKA0FImEEY5VPSd6AGPEUf0ErjExz4snVqa9kNJSoVDV1rKaNf3qrWojgcKW+SdDg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/s3-request-presigner@3.1029.0':
+ resolution: {integrity: sha512-YbHPaha4DYgJWdPorGV5ZSCCqHafGj4GiyqXmXFlCJSsqlOd3xEcemhOZGjrB9epdiVEUtB3DDJXGYYj55ITdQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/signature-v4-multi-region@3.996.16':
+ resolution: {integrity: sha512-EMdXYB4r/k5RWq86fugjRhid5JA+Z6MpS7n4sij4u5/C+STrkvuf9aFu41rJA9MjUzxCLzv8U2XL8cH2GSRYpQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/token-providers@3.1026.0':
+ resolution: {integrity: sha512-Ieq/HiRrbEtrYP387Nes0XlR7H1pJiJOZKv+QyQzMYpvTiDs0VKy2ZB3E2Zf+aFovWmeE7lRE4lXyF7dYM6GgA==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/types@3.973.7':
+ resolution: {integrity: sha512-reXRwoJ6CfChoqAsBszUYajAF8Z2LRE+CRcKocvFSMpIiLOtYU3aJ9trmn6VVPAzbbY5LXF+FfmUslbXk1SYFg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/util-arn-parser@3.972.3':
+ resolution: {integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/util-endpoints@3.996.6':
+ resolution: {integrity: sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/util-format-url@3.972.9':
+ resolution: {integrity: sha512-fNJXHrs0ZT7Wx0KGIqKv7zLxlDXt2vqjx9z6oKUQFmpE5o4xxnSryvVHfHpIifYHWKz94hFccIldJ0YSZjlCBw==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/util-locate-window@3.965.5':
+ resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/util-user-agent-browser@3.972.9':
+ resolution: {integrity: sha512-sn/LMzTbGjYqCCF24390WxPd6hkpoSptiUn5DzVp4cD71yqw+yGEGm1YCxyEoPXyc8qciM8UzLJcZBFslxo5Uw==}
+
+ '@aws-sdk/util-user-agent-node@3.973.15':
+ resolution: {integrity: sha512-fYn3s9PtKdgQkczGZCFMgkNEe8aq1JCVbnRqjqN9RSVW43xn2RV9xdcZ3z01a48Jpkuh/xCmBKJxdLOo4Ozg7w==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ aws-crt: '>=1.0.0'
+ peerDependenciesMeta:
+ aws-crt:
+ optional: true
+
+ '@aws-sdk/xml-builder@3.972.17':
+ resolution: {integrity: sha512-Ra7hjqAZf1OXRRMueB13qex7mFJRDK/pgCvdSFemXBT8KCGnQDPoKzHY1SjN+TjJVmnpSF14W5tJ1vDamFu+Gg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws/lambda-invoke-store@0.2.4':
+ resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==}
+ engines: {node: '>=18.0.0'}
+
'@babel/code-frame@7.29.0':
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
engines: {node: '>=6.9.0'}
@@ -206,6 +451,27 @@ packages:
'@borewit/text-codec@0.2.2':
resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==}
+ '@bull-board/api@6.21.0':
+ resolution: {integrity: sha512-5bX3U8baU4OulDLeXwqWI6/FZolpi1APfoJVXndR4fKdmuYr9cdbH8cg7juublfzX01T+3zoiZkveX7iD5y8gA==}
+ peerDependencies:
+ '@bull-board/ui': 6.21.0
+
+ '@bull-board/fastify@6.21.0':
+ resolution: {integrity: sha512-2Og70c0Br9fKF6cX5MKLt2WTvGw3yiu+4OG2K8UAE+yFBrm+VNHxEmfvXvsyoVlnT1bzBpLzaxqC21NWCzY6SA==}
+
+ '@bull-board/nestjs@6.21.0':
+ resolution: {integrity: sha512-h4UhJw9Hc4ehQcs4y+fd7CgSTyIxHN1uFttwWiFuPpMkA+t5/OcAdlB0THigjxwmL2vYgcFzuk9nKb0qHtlRkw==}
+ peerDependencies:
+ '@bull-board/api': ^6.21.0
+ '@nestjs/bull-shared': ^10.0.0 || ^11.0.0
+ '@nestjs/common': ^9.0.0 || ^10.0.0 || ^11.0.0
+ '@nestjs/core': ^9.0.0 || ^10.0.0 || ^11.0.0
+ reflect-metadata: ^0.1.13 || ^0.2.0
+ rxjs: ^7.8.1
+
+ '@bull-board/ui@6.21.0':
+ resolution: {integrity: sha512-SemKRipdrZVqboae/Xhl7CTdIwWJ+F3G/DEP7XHi1Qt1kXZUIKJkySXlFHILunygCiHRpCJ6/Ax/XNdHI/n3QA==}
+
'@colors/colors@1.5.0':
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
engines: {node: '>=0.1.90'}
@@ -307,6 +573,9 @@ packages:
'@emnapi/wasi-threads@1.2.1':
resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
+ '@epic-web/invariant@1.0.0':
+ resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==}
+
'@esbuild-kit/core-utils@3.3.2':
resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
deprecated: 'Merged into tsx: https://tsx.is'
@@ -783,6 +1052,9 @@ packages:
'@fastify/ajv-compiler@4.0.5':
resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==}
+ '@fastify/busboy@3.2.0':
+ resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==}
+
'@fastify/compress@8.3.1':
resolution: {integrity: sha512-BUpItLr6MUX9e9ukg5Y6xekyA/7pBFG8QWtFCrUDm9ctoBc3R2/nA16yOaOWtVoccpXGjdDEYA/MxAb5+8cxag==}
@@ -792,6 +1064,9 @@ packages:
'@fastify/cors@11.2.0':
resolution: {integrity: sha512-LbLHBuSAdGdSFZYTLVA3+Ch2t+sA6nq3Ejc6XLAKiQ6ViS2qFnvicpj0htsx03FyYeLs04HfRNBsz/a8SvbcUw==}
+ '@fastify/deepmerge@3.2.1':
+ resolution: {integrity: sha512-N5Oqvltoa2r9z1tbx4xjky0oRR60v+T47Ic4J1ukoVQcptLOrIdRnCSdTGmOmajZuHVKlTnfcmrjyqsGEW1ztA==}
+
'@fastify/error@4.2.0':
resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==}
@@ -807,6 +1082,9 @@ packages:
'@fastify/merge-json-schemas@0.2.1':
resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==}
+ '@fastify/multipart@10.0.0':
+ resolution: {integrity: sha512-pUx3Z1QStY7E7kwvDTIvB6P+rF5lzP+iqPgZyJyG3yBJVPvQaZxzDHYbQD89rbY0ciXrMOyGi8ezHDVexLvJDA==}
+
'@fastify/proxy-addr@5.1.0':
resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==}
@@ -816,6 +1094,9 @@ packages:
'@fastify/static@9.1.0':
resolution: {integrity: sha512-EPRNQYqEYEYTK8yyGbcM0iHpyJaupb94bey5O6iCQfLTADr02kaZU+qeHSdd9H9TiMwTBVkrMa59V8CMbn3avQ==}
+ '@fastify/view@11.1.1':
+ resolution: {integrity: sha512-GiHqT3R2eKJgWmy0s45eELTC447a4+lTM2o+8fSWeKwBe9VToeePuHJcKtOEXPrKGSddGO0RsNayULiS3aeHeQ==}
+
'@humanwhocodes/config-array@0.13.0':
resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==}
engines: {node: '>=10.10.0'}
@@ -972,6 +1253,9 @@ packages:
'@types/node':
optional: true
+ '@ioredis/commands@1.5.1':
+ resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==}
+
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
@@ -1005,12 +1289,62 @@ packages:
'@microsoft/tsdoc@0.16.0':
resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==}
+ '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
+ resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3':
+ resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3':
+ resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3':
+ resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3':
+ resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
+ resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==}
+ cpu: [x64]
+ os: [win32]
+
'@napi-rs/wasm-runtime@1.1.3':
resolution: {integrity: sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==}
peerDependencies:
'@emnapi/core': ^1.7.1
'@emnapi/runtime': ^1.7.1
+ '@nestjs-modules/ioredis@2.2.1':
+ resolution: {integrity: sha512-wQ08XvlV2s9V+01SKcC5XmFoQ2hMAHP0KuVja8UFZyE/dM0bKI5HSHr+3wQ5ChRpsyhfxF/vKrlPXMlJIr7FIg==}
+ peerDependencies:
+ '@nestjs/common': '>=6.7.0'
+ '@nestjs/core': '>=6.7.0'
+ ioredis: '>=5.0.0'
+
+ '@nestjs/bull-shared@11.0.4':
+ resolution: {integrity: sha512-VBJcDHSAzxQnpcDfA0kt9MTGUD1XZzfByV70su0W0eDCQ9aqIEBlzWRW21tv9FG9dIut22ysgDidshdjlnczLw==}
+ peerDependencies:
+ '@nestjs/common': ^10.0.0 || ^11.0.0
+ '@nestjs/core': ^10.0.0 || ^11.0.0
+
+ '@nestjs/bullmq@11.0.4':
+ resolution: {integrity: sha512-wBzK9raAVG0/6NTMdvLGM4/FQ1lsB35/pYS8L6a0SDgkTiLpd7mAjQ8R692oMx5s7IjvgntaZOuTUrKYLNfIkA==}
+ peerDependencies:
+ '@nestjs/common': ^10.0.0 || ^11.0.0
+ '@nestjs/core': ^10.0.0 || ^11.0.0
+ bullmq: ^3.0.0 || ^4.0.0 || ^5.0.0
+
'@nestjs/cli@11.0.19':
resolution: {integrity: sha512-9htODqTVVNH4lJqyeIotsAgfeaYngDi020cVCd6JhJRKuOT83c/t4JDSky6+xr0lhHyNTNMgZmulxqcMNZFfrw==}
engines: {node: '>= 20.11'}
@@ -1061,6 +1395,11 @@ packages:
'@nestjs/websockets':
optional: true
+ '@nestjs/jwt@11.0.2':
+ resolution: {integrity: sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==}
+ peerDependencies:
+ '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
+
'@nestjs/mapped-types@2.1.1':
resolution: {integrity: sha512-SCCoMEJ6jdeI5h/N+KCVF1+pmg/hmEkNA5nHTS8Gvww7T/LCl4o1gFLinw2iQ60w7slFkszHcGLKGdazVI4F8A==}
peerDependencies:
@@ -1074,6 +1413,12 @@ packages:
class-validator:
optional: true
+ '@nestjs/passport@11.0.5':
+ resolution: {integrity: sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==}
+ peerDependencies:
+ '@nestjs/common': ^10.0.0 || ^11.0.0
+ passport: ^0.5.0 || ^0.6.0 || ^0.7.0
+
'@nestjs/platform-fastify@11.1.18':
resolution: {integrity: sha512-iJtbqQz51k7Z1vOTUEHO1mU8PsDO1WdgPSJ/6CuXBnazkrkePXoszhefFaPwJreBVn35GE3WTd/6ou7bFwnhmA==}
peerDependencies:
@@ -1109,6 +1454,54 @@ packages:
class-validator:
optional: true
+ '@nestjs/terminus@11.1.1':
+ resolution: {integrity: sha512-Ssql79H+EQY/Wg108eJqN4NiNsO/tLrj+qbzOWSQUf2JE4vJQ2RG3WTqUOrYjfjWmVHD3+Ys0+azed7LSMKScw==}
+ peerDependencies:
+ '@grpc/grpc-js': '*'
+ '@grpc/proto-loader': '*'
+ '@mikro-orm/core': '*'
+ '@mikro-orm/nestjs': '*'
+ '@nestjs/axios': ^2.0.0 || ^3.0.0 || ^4.0.0
+ '@nestjs/common': ^10.0.0 || ^11.0.0
+ '@nestjs/core': ^10.0.0 || ^11.0.0
+ '@nestjs/microservices': ^10.0.0 || ^11.0.0
+ '@nestjs/mongoose': ^11.0.0
+ '@nestjs/sequelize': ^10.0.0 || ^11.0.0
+ '@nestjs/typeorm': ^10.0.0 || ^11.0.0
+ '@prisma/client': '*'
+ mongoose: '*'
+ reflect-metadata: 0.1.x || 0.2.x
+ rxjs: 7.x
+ sequelize: '*'
+ typeorm: '*'
+ peerDependenciesMeta:
+ '@grpc/grpc-js':
+ optional: true
+ '@grpc/proto-loader':
+ optional: true
+ '@mikro-orm/core':
+ optional: true
+ '@mikro-orm/nestjs':
+ optional: true
+ '@nestjs/axios':
+ optional: true
+ '@nestjs/microservices':
+ optional: true
+ '@nestjs/mongoose':
+ optional: true
+ '@nestjs/sequelize':
+ optional: true
+ '@nestjs/typeorm':
+ optional: true
+ '@prisma/client':
+ optional: true
+ mongoose:
+ optional: true
+ sequelize:
+ optional: true
+ typeorm:
+ optional: true
+
'@nestjs/testing@11.1.18':
resolution: {integrity: sha512-frzwNlpBgtAzI3hp/qo57DZoRO4RMTH1wST3QUYEhRTHyfPkLpzkWz3jV/mhApXjD0yT56Ptlzn6zuYPLh87Lw==}
peerDependencies:
@@ -1133,6 +1526,10 @@ packages:
resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
engines: {node: ^14.21.3 || >=16}
+ '@noble/hashes@2.0.1':
+ resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==}
+ engines: {node: '>= 20.19.0'}
+
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -1154,12 +1551,38 @@ packages:
resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==}
engines: {node: '>=8.0.0'}
+ '@otplib/core@13.4.0':
+ resolution: {integrity: sha512-JqOGcvZQi2wIkEQo8f3/iAjstavpXy6gouIDMHygjNuH6Q0FjbHOiXMdcE94RwfgDNMABhzwUmvaPsxvgm9NYw==}
+
+ '@otplib/hotp@13.4.0':
+ resolution: {integrity: sha512-MJjE0x06mn2ptymz5qZmQveb+vWFuaIftqE0b5/TZZqUOK7l97cV8lRTmid5BpAQMwJDNLW6RnYxGeCRiNdekw==}
+
+ '@otplib/plugin-base32-scure@13.4.0':
+ resolution: {integrity: sha512-/t9YWJmMbB8bF5z8mXrBZc2FXBe8B/3hG5FhWr9K8cFwFhyxScbPysmZe8s1UTzSA6N+s8Uv8aIfCtVXPNjJWw==}
+
+ '@otplib/plugin-crypto-noble@13.4.0':
+ resolution: {integrity: sha512-KrvE4m7Zv+TT1944HzgqFJWJpKb6AyoxDbvhPStmBqdMlv5Gekb80d66cuFRL08kkPgJ5gXUSb5SFpYeB+bACg==}
+
+ '@otplib/totp@13.4.0':
+ resolution: {integrity: sha512-dK+vl0f0ekzf6mCENRI9AKS2NJUC7OjI3+X8e7QSnhQ2WM7I+i4PGpb3QxKi5hxjTtwVuoZwXR2CFtXdcRtNdQ==}
+
+ '@otplib/uri@13.4.0':
+ resolution: {integrity: sha512-x1ozBa5bPbdZCrrTL/HK21qchiK7jYElTu+0ft22abeEhiLYgH1+SIULvOcVk3CK8YwF4kdcidvkq4ciejucJA==}
+
'@oxc-project/types@0.124.0':
resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==}
'@paralleldrive/cuid2@2.3.1':
resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==}
+ '@paralleldrive/cuid2@3.3.0':
+ resolution: {integrity: sha512-OqiFvSOF0dBSesELYY2CAMa4YINvlLpvKOz/rv6NeZEqiyttlHgv98Juwv4Ch+GrEV7IZ8jfI2VcEoYUjXXCjw==}
+ hasBin: true
+
+ '@phc/format@1.0.0':
+ resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
+ engines: {node: '>=10'}
+
'@pinojs/redact@0.4.0':
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
@@ -1277,6 +1700,9 @@ packages:
'@scarf/scarf@1.4.0':
resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==}
+ '@scure/base@2.0.0':
+ resolution: {integrity: sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==}
+
'@simple-libs/child-process-utils@1.0.2':
resolution: {integrity: sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==}
engines: {node: '>=18'}
@@ -1285,105 +1711,317 @@ packages:
resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==}
engines: {node: '>=18'}
- '@standard-schema/spec@1.1.0':
- resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+ '@smithy/chunked-blob-reader-native@4.2.3':
+ resolution: {integrity: sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==}
+ engines: {node: '>=18.0.0'}
- '@swc/core-darwin-arm64@1.15.24':
- resolution: {integrity: sha512-uM5ZGfFXjtvtJ+fe448PVBEbn/CSxS3UAyLj3O9xOqKIWy3S6hPTXSPbszxkSsGDYKi+YFhzAsR4r/eXLxEQ0g==}
- engines: {node: '>=10'}
- cpu: [arm64]
- os: [darwin]
+ '@smithy/chunked-blob-reader@5.2.2':
+ resolution: {integrity: sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==}
+ engines: {node: '>=18.0.0'}
- '@swc/core-darwin-x64@1.15.24':
- resolution: {integrity: sha512-fMIb/Zfn929pw25VMBhV7Ji2Dl+lCWtUPNdYJQYOke+00E5fcQ9ynxtP8+qhUo/HZc+mYQb1gJxwHM9vty+lXg==}
- engines: {node: '>=10'}
- cpu: [x64]
- os: [darwin]
+ '@smithy/config-resolver@4.4.14':
+ resolution: {integrity: sha512-N55f8mPEccpzKetUagdvmAy8oohf0J5cuj9jLI1TaSceRlq0pJsIZepY3kmAXAhyxqXPV6hDerDQhqQPKWgAoQ==}
+ engines: {node: '>=18.0.0'}
- '@swc/core-linux-arm-gnueabihf@1.15.24':
- resolution: {integrity: sha512-vOkjsyjjxnoYx3hMEWcGxQrMgnNrRm6WAegBXrN8foHtDAR+zpdhpGF5a4lj1bNPgXAvmysjui8cM1ov/Clkaw==}
- engines: {node: '>=10'}
- cpu: [arm]
- os: [linux]
+ '@smithy/core@3.23.14':
+ resolution: {integrity: sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg==}
+ engines: {node: '>=18.0.0'}
- '@swc/core-linux-arm64-gnu@1.15.24':
- resolution: {integrity: sha512-h/oNu+upkXJ6Cicnq7YGVj9PkdfarLCdQa8l/FlHYvfv8CEiMaeeTnpLU7gSBH/rGxosM6Qkfa/J9mThGF9CLA==}
- engines: {node: '>=10'}
- cpu: [arm64]
- os: [linux]
- libc: [glibc]
+ '@smithy/credential-provider-imds@4.2.13':
+ resolution: {integrity: sha512-wboCPijzf6RJKLOvnjDAiBxGSmSnGXj35o5ZAWKDaHa/cvQ5U3ZJ13D4tMCE8JG4dxVAZFy/P0x/V9CwwdfULQ==}
+ engines: {node: '>=18.0.0'}
- '@swc/core-linux-arm64-musl@1.15.24':
- resolution: {integrity: sha512-ZpF/pRe1guk6sKzQI9D1jAORtjTdNlyeXn9GDz8ophof/w2WhojRblvSDJaGe7rJjcPN8AaOkhwdRUh7q8oYIg==}
- engines: {node: '>=10'}
- cpu: [arm64]
- os: [linux]
- libc: [musl]
+ '@smithy/eventstream-codec@4.2.13':
+ resolution: {integrity: sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ==}
+ engines: {node: '>=18.0.0'}
- '@swc/core-linux-ppc64-gnu@1.15.24':
- resolution: {integrity: sha512-QZEsZfisHTSJlmyChgDFNmKPb3W6Lhbfo/O76HhIngfEdnQNmukS38/VSe1feho+xkV5A5hETyCbx3sALBZKAQ==}
- engines: {node: '>=10'}
- cpu: [ppc64]
- os: [linux]
- libc: [glibc]
+ '@smithy/eventstream-serde-browser@4.2.13':
+ resolution: {integrity: sha512-wwybfcOX0tLqCcBP378TIU9IqrDuZq/tDV48LlZNydMpCnqnYr+hWBAYbRE+rFFf/p7IkDJySM3bgiMKP2ihPg==}
+ engines: {node: '>=18.0.0'}
- '@swc/core-linux-s390x-gnu@1.15.24':
- resolution: {integrity: sha512-DLdJKVsJgglqQrJBuoUYNmzm3leI7kUZhLbZGHv42onfKsGf6JDS3+bzCUQfte/XOqDjh/tmmn1DR/CF/tCJFw==}
- engines: {node: '>=10'}
- cpu: [s390x]
- os: [linux]
- libc: [glibc]
+ '@smithy/eventstream-serde-config-resolver@4.3.13':
+ resolution: {integrity: sha512-ied1lO559PtAsMJzg2TKRlctLnEi1PfkNeMMpdwXDImk1zV9uvS/Oxoy/vcy9uv1GKZAjDAB5xT6ziE9fzm5wA==}
+ engines: {node: '>=18.0.0'}
- '@swc/core-linux-x64-gnu@1.15.24':
- resolution: {integrity: sha512-IpLYfposPA/XLxYOKpRfeccl1p5dDa3+okZDHHTchBkXEaVCnq5MADPmIWwIYj1tudt7hORsEHccG5no6IUQRw==}
- engines: {node: '>=10'}
- cpu: [x64]
- os: [linux]
- libc: [glibc]
+ '@smithy/eventstream-serde-node@4.2.13':
+ resolution: {integrity: sha512-hFyK+ORJrxAN3RYoaD6+gsGDQjeix8HOEkosoajvXYZ4VeqonM3G4jd9IIRm/sWGXUKmudkY9KdYjzosUqdM8A==}
+ engines: {node: '>=18.0.0'}
- '@swc/core-linux-x64-musl@1.15.24':
- resolution: {integrity: sha512-JHy3fMSc0t/EPWgo74+OK5TGr51aElnzqfUPaiRf2qJ/BfX5CUCfMiWVBuhI7qmVMBnk1jTRnL/xZnOSHDPLYg==}
- engines: {node: '>=10'}
- cpu: [x64]
- os: [linux]
- libc: [musl]
+ '@smithy/eventstream-serde-universal@4.2.13':
+ resolution: {integrity: sha512-kRrq4EKLGeOxhC2CBEhRNcu1KSzNJzYY7RK3S7CxMPgB5dRrv55WqQOtRwQxQLC04xqORFLUgnDlc6xrNUULaA==}
+ engines: {node: '>=18.0.0'}
- '@swc/core-win32-arm64-msvc@1.15.24':
- resolution: {integrity: sha512-Txj+qUH1z2bUd1P3JvwByfjKFti3cptlAxhWgmunBUUxy/IW3CXLZ6l6Gk4liANadKkU71nIU1X30Z5vpMT3BA==}
- engines: {node: '>=10'}
- cpu: [arm64]
- os: [win32]
+ '@smithy/fetch-http-handler@5.3.16':
+ resolution: {integrity: sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ==}
+ engines: {node: '>=18.0.0'}
- '@swc/core-win32-ia32-msvc@1.15.24':
- resolution: {integrity: sha512-15D/nl3XwrhFpMv+MADFOiVwv3FvH9j8c6Rf8EXBT3Q5LoMh8YnDnSgPYqw1JzPnksvsBX6QPXLiPqmcR/Z4qQ==}
- engines: {node: '>=10'}
- cpu: [ia32]
- os: [win32]
+ '@smithy/hash-blob-browser@4.2.14':
+ resolution: {integrity: sha512-rtQ5es8r/5v4rav7q5QTsfx9CtCyzrz/g7ZZZBH2xtMmd6G/KQrLOWfSHTvFOUPlVy59RQvxeBYJaLRoybMEyA==}
+ engines: {node: '>=18.0.0'}
- '@swc/core-win32-x64-msvc@1.15.24':
- resolution: {integrity: sha512-PR0PlTlPra2JbaDphrOAzm6s0v9rA0F17YzB+XbWD95B4g2cWcZY9LAeTa4xll70VLw9Jr7xBrlohqlQmelMFQ==}
- engines: {node: '>=10'}
- cpu: [x64]
- os: [win32]
+ '@smithy/hash-node@4.2.13':
+ resolution: {integrity: sha512-4/oy9h0jjmY80a2gOIo75iLl8TOPhmtx4E2Hz+PfMjvx/vLtGY4TMU/35WRyH2JHPfT5CVB38u4JRow7gnmzJA==}
+ engines: {node: '>=18.0.0'}
- '@swc/core@1.15.24':
- resolution: {integrity: sha512-5Hj8aNasue7yusUt8LGCUe/AjM7RMAce8ZoyDyiFwx7Al+GbYKL+yE7g4sJk8vEr1dKIkTRARkNIJENc4CjkBQ==}
- engines: {node: '>=10'}
- peerDependencies:
- '@swc/helpers': '>=0.5.17'
- peerDependenciesMeta:
- '@swc/helpers':
- optional: true
+ '@smithy/hash-stream-node@4.2.13':
+ resolution: {integrity: sha512-WdQ7HwUjINXETeh6dqUeob1UHIYx8kAn9PSp1HhM2WWegiZBYVy2WXIs1lB07SZLan/udys9SBnQGt9MQbDpdg==}
+ engines: {node: '>=18.0.0'}
- '@swc/counter@0.1.3':
- resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
+ '@smithy/invalid-dependency@4.2.13':
+ resolution: {integrity: sha512-jvC0RB/8BLj2SMIkY0Npl425IdnxZJxInpZJbu563zIRnVjpDMXevU3VMCRSabaLB0kf/eFIOusdGstrLJ8IDg==}
+ engines: {node: '>=18.0.0'}
- '@swc/types@0.1.26':
- resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==}
+ '@smithy/is-array-buffer@2.2.0':
+ resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==}
+ engines: {node: '>=14.0.0'}
- '@tokenizer/inflate@0.4.1':
- resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==}
- engines: {node: '>=18'}
+ '@smithy/is-array-buffer@4.2.2':
+ resolution: {integrity: sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/md5-js@4.2.13':
+ resolution: {integrity: sha512-cNm7I9NXolFxtS20ojROddOEpSAeI1Obq6pd1Kj5HtHws3s9Fkk8DdHDfQSs5KuxCewZuVK6UqrJnfJmiMzDuQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/middleware-content-length@4.2.13':
+ resolution: {integrity: sha512-IPMLm/LE4AZwu6qiE8Rr8vJsWhs9AtOdySRXrOM7xnvclp77Tyh7hMs/FRrMf26kgIe67vFJXXOSmVxS7oKeig==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/middleware-endpoint@4.4.29':
+ resolution: {integrity: sha512-R9Q/58U+qBiSARGWbAbFLczECg/RmysRksX6Q8BaQEpt75I7LI6WGDZnjuC9GXSGKljEbA7N118LhGaMbfrTXw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/middleware-retry@4.5.1':
+ resolution: {integrity: sha512-/zY+Gp7Qj2D2hVm3irkCyONER7E9MiX3cUUm/k2ZmhkzZkrPgwVS4aJ5NriZUEN/M0D1hhjrgjUmX04HhRwdWA==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/middleware-serde@4.2.17':
+ resolution: {integrity: sha512-0T2mcaM6v9W1xku86Dk0bEW7aEseG6KenFkPK98XNw0ZhOqOiD1MrMsdnQw9QsL3/Oa85T53iSMlm0SZdSuIEQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/middleware-stack@4.2.13':
+ resolution: {integrity: sha512-g72jN/sGDLyTanrCLH9fhg3oysO3f7tQa6eWWsMyn2BiYNCgjF24n4/I9wff/5XidFvjj9ilipAoQrurTUrLvw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/node-config-provider@4.3.13':
+ resolution: {integrity: sha512-iGxQ04DsKXLckbgnX4ipElrOTk+IHgTyu0q0WssZfYhDm9CQWHmu6cOeI5wmWRxpXbBDhIIfXMWz5tPEtcVqbw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/node-http-handler@4.5.2':
+ resolution: {integrity: sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/property-provider@4.2.13':
+ resolution: {integrity: sha512-bGzUCthxRmezuxkbu9wD33wWg9KX3hJpCXpQ93vVkPrHn9ZW6KNNdY5xAUWNuRCwQ+VyboFuWirG1lZhhkcyRQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/protocol-http@5.3.13':
+ resolution: {integrity: sha512-+HsmuJUF4u8POo6s8/a2Yb/AQ5t/YgLovCuHF9oxbocqv+SZ6gd8lC2duBFiCA/vFHoHQhoq7QjqJqZC6xOxxg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/querystring-builder@4.2.13':
+ resolution: {integrity: sha512-tG4aOYFCZdPMjbgfhnIQ322H//ojujldp1SrHPHpBSb3NqgUp3dwiUGRJzie87hS1DYwWGqDuPaowoDF+rYCbQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/querystring-parser@4.2.13':
+ resolution: {integrity: sha512-hqW3Q4P+CDzUyQ87GrboGMeD7XYNMOF+CuTwu936UQRB/zeYn3jys8C3w+wMkDfY7CyyyVwZQ5cNFoG0x1pYmA==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/service-error-classification@4.2.13':
+ resolution: {integrity: sha512-a0s8XZMfOC/qpqq7RCPvJlk93rWFrElH6O++8WJKz0FqnA4Y7fkNi/0mnGgSH1C4x6MFsuBA8VKu4zxFrMe5Vw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/shared-ini-file-loader@4.4.8':
+ resolution: {integrity: sha512-VZCZx2bZasxdqxVgEAhREvDSlkatTPnkdWy1+Kiy8w7kYPBosW0V5IeDwzDUMvWBt56zpK658rx1cOBFOYaPaw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/signature-v4@5.3.13':
+ resolution: {integrity: sha512-YpYSyM0vMDwKbHD/JA7bVOF6kToVRpa+FM5ateEVRpsTNu564g1muBlkTubXhSKKYXInhpADF46FPyrZcTLpXg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/smithy-client@4.12.9':
+ resolution: {integrity: sha512-ovaLEcTU5olSeHcRXcxV6viaKtpkHZumn6Ps0yn7dRf2rRSfy794vpjOtrWDO0d1auDSvAqxO+lyhERSXQ03EQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/types@4.14.0':
+ resolution: {integrity: sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/url-parser@4.2.13':
+ resolution: {integrity: sha512-2G03yoboIRZlZze2+PT4GZEjgwQsJjUgn6iTsvxA02bVceHR6vp4Cuk7TUnPFWKF+ffNUk3kj4COwkENS2K3vw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-base64@4.3.2':
+ resolution: {integrity: sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-body-length-browser@4.2.2':
+ resolution: {integrity: sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-body-length-node@4.2.3':
+ resolution: {integrity: sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-buffer-from@2.2.0':
+ resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==}
+ engines: {node: '>=14.0.0'}
+
+ '@smithy/util-buffer-from@4.2.2':
+ resolution: {integrity: sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-config-provider@4.2.2':
+ resolution: {integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-defaults-mode-browser@4.3.45':
+ resolution: {integrity: sha512-ag9sWc6/nWZAuK3Wm9KlFJUnRkXLrXn33RFjIAmCTFThqLHY+7wCst10BGq56FxslsDrjhSie46c8OULS+BiIw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-defaults-mode-node@4.2.49':
+ resolution: {integrity: sha512-jlN6vHwE8gY5AfiFBavtD3QtCX2f7lM3BKkz7nFKSNfFR5nXLXLg6sqXTJEEyDwtxbztIDBQCfjsGVXlIru2lQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-endpoints@3.3.4':
+ resolution: {integrity: sha512-BKoR/ubPp9KNKFxPpg1J28N1+bgu8NGAtJblBP7yHy8yQPBWhIAv9+l92SlQLpolGm71CVO+btB60gTgzT0wog==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-hex-encoding@4.2.2':
+ resolution: {integrity: sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-middleware@4.2.13':
+ resolution: {integrity: sha512-GTooyrlmRTqvUen4eK7/K1p6kryF7bnDfq6XsAbIsf2mo51B/utaH+XThY6dKgNCWzMAaH/+OLmqaBuLhLWRow==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-retry@4.3.1':
+ resolution: {integrity: sha512-FwmicpgWOkP5kZUjN3y+3JIom8NLGqSAJBeoIgK0rIToI817TEBHCrd0A2qGeKQlgDeP+Jzn4i0H/NLAXGy9uQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-stream@4.5.22':
+ resolution: {integrity: sha512-3H8iq/0BfQjUs2/4fbHZ9aG9yNzcuZs24LPkcX1Q7Z+qpqaGM8+qbGmE8zo9m2nCRgamyvS98cHdcWvR6YUsew==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-uri-escape@4.2.2':
+ resolution: {integrity: sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-utf8@2.3.0':
+ resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==}
+ engines: {node: '>=14.0.0'}
+
+ '@smithy/util-utf8@4.2.2':
+ resolution: {integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-waiter@4.2.15':
+ resolution: {integrity: sha512-oUt9o7n8hBv3BL56sLSneL0XeigZSuem0Hr78JaoK33D9oKieyCvVP8eTSe3j7g2mm/S1DvzxKieG7JEWNJUNg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/uuid@1.1.2':
+ resolution: {integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==}
+ engines: {node: '>=18.0.0'}
+
+ '@standard-schema/spec@1.1.0':
+ resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+
+ '@swc/core-darwin-arm64@1.15.24':
+ resolution: {integrity: sha512-uM5ZGfFXjtvtJ+fe448PVBEbn/CSxS3UAyLj3O9xOqKIWy3S6hPTXSPbszxkSsGDYKi+YFhzAsR4r/eXLxEQ0g==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@swc/core-darwin-x64@1.15.24':
+ resolution: {integrity: sha512-fMIb/Zfn929pw25VMBhV7Ji2Dl+lCWtUPNdYJQYOke+00E5fcQ9ynxtP8+qhUo/HZc+mYQb1gJxwHM9vty+lXg==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@swc/core-linux-arm-gnueabihf@1.15.24':
+ resolution: {integrity: sha512-vOkjsyjjxnoYx3hMEWcGxQrMgnNrRm6WAegBXrN8foHtDAR+zpdhpGF5a4lj1bNPgXAvmysjui8cM1ov/Clkaw==}
+ engines: {node: '>=10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@swc/core-linux-arm64-gnu@1.15.24':
+ resolution: {integrity: sha512-h/oNu+upkXJ6Cicnq7YGVj9PkdfarLCdQa8l/FlHYvfv8CEiMaeeTnpLU7gSBH/rGxosM6Qkfa/J9mThGF9CLA==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@swc/core-linux-arm64-musl@1.15.24':
+ resolution: {integrity: sha512-ZpF/pRe1guk6sKzQI9D1jAORtjTdNlyeXn9GDz8ophof/w2WhojRblvSDJaGe7rJjcPN8AaOkhwdRUh7q8oYIg==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@swc/core-linux-ppc64-gnu@1.15.24':
+ resolution: {integrity: sha512-QZEsZfisHTSJlmyChgDFNmKPb3W6Lhbfo/O76HhIngfEdnQNmukS38/VSe1feho+xkV5A5hETyCbx3sALBZKAQ==}
+ engines: {node: '>=10'}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@swc/core-linux-s390x-gnu@1.15.24':
+ resolution: {integrity: sha512-DLdJKVsJgglqQrJBuoUYNmzm3leI7kUZhLbZGHv42onfKsGf6JDS3+bzCUQfte/XOqDjh/tmmn1DR/CF/tCJFw==}
+ engines: {node: '>=10'}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@swc/core-linux-x64-gnu@1.15.24':
+ resolution: {integrity: sha512-IpLYfposPA/XLxYOKpRfeccl1p5dDa3+okZDHHTchBkXEaVCnq5MADPmIWwIYj1tudt7hORsEHccG5no6IUQRw==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@swc/core-linux-x64-musl@1.15.24':
+ resolution: {integrity: sha512-JHy3fMSc0t/EPWgo74+OK5TGr51aElnzqfUPaiRf2qJ/BfX5CUCfMiWVBuhI7qmVMBnk1jTRnL/xZnOSHDPLYg==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@swc/core-win32-arm64-msvc@1.15.24':
+ resolution: {integrity: sha512-Txj+qUH1z2bUd1P3JvwByfjKFti3cptlAxhWgmunBUUxy/IW3CXLZ6l6Gk4liANadKkU71nIU1X30Z5vpMT3BA==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@swc/core-win32-ia32-msvc@1.15.24':
+ resolution: {integrity: sha512-15D/nl3XwrhFpMv+MADFOiVwv3FvH9j8c6Rf8EXBT3Q5LoMh8YnDnSgPYqw1JzPnksvsBX6QPXLiPqmcR/Z4qQ==}
+ engines: {node: '>=10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@swc/core-win32-x64-msvc@1.15.24':
+ resolution: {integrity: sha512-PR0PlTlPra2JbaDphrOAzm6s0v9rA0F17YzB+XbWD95B4g2cWcZY9LAeTa4xll70VLw9Jr7xBrlohqlQmelMFQ==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@swc/core@1.15.24':
+ resolution: {integrity: sha512-5Hj8aNasue7yusUt8LGCUe/AjM7RMAce8ZoyDyiFwx7Al+GbYKL+yE7g4sJk8vEr1dKIkTRARkNIJENc4CjkBQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@swc/helpers': '>=0.5.17'
+ peerDependenciesMeta:
+ '@swc/helpers':
+ optional: true
+
+ '@swc/counter@0.1.3':
+ resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
+
+ '@swc/types@0.1.26':
+ resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==}
+
+ '@tokenizer/inflate@0.4.1':
+ resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==}
+ engines: {node: '>=18'}
'@tokenizer/token@0.3.0':
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
@@ -1403,9 +2041,15 @@ packages:
'@tybys/wasm-util@0.10.1':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
+ '@types/body-parser@1.19.6':
+ resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}
+
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+ '@types/connect@3.4.38':
+ resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
+
'@types/cookiejar@2.1.5':
resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==}
@@ -1421,27 +2065,72 @@ packages:
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+ '@types/express-serve-static-core@5.1.1':
+ resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==}
+
+ '@types/express@5.0.6':
+ resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==}
+
+ '@types/http-errors@2.0.5':
+ resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
+
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+ '@types/jsonwebtoken@9.0.10':
+ resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==}
+
'@types/methods@1.1.4':
resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==}
+ '@types/ms@2.1.0':
+ resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
+
'@types/node@20.19.39':
resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==}
+ '@types/nodemailer@8.0.0':
+ resolution: {integrity: sha512-fyf8jWULsCo0d0BuoQ75i6IeoHs47qcqxWc7yUdUcV0pOZGjUTTOvwdG1PRXUDqN/8A64yQdQdnA2pZgcdi+cA==}
+
+ '@types/passport-jwt@4.0.1':
+ resolution: {integrity: sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==}
+
+ '@types/passport-local@1.0.38':
+ resolution: {integrity: sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg==}
+
+ '@types/passport-strategy@0.2.38':
+ resolution: {integrity: sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==}
+
+ '@types/passport@1.0.17':
+ resolution: {integrity: sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==}
+
'@types/pg@8.20.0':
resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==}
+ '@types/qs@6.15.0':
+ resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==}
+
+ '@types/range-parser@1.2.7':
+ resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
+
'@types/semver@7.7.1':
resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==}
+ '@types/send@1.2.1':
+ resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==}
+
+ '@types/serve-static@2.2.0':
+ resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==}
+
'@types/superagent@8.1.9':
resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==}
'@types/supertest@6.0.3':
resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==}
+ '@types/ua-parser-js@0.7.39':
+ resolution: {integrity: sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==}
+
'@typescript-eslint/eslint-plugin@6.21.0':
resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -1657,6 +2346,9 @@ packages:
ajv@8.18.0:
resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
+ ansi-align@3.0.1:
+ resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
+
ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
@@ -1688,6 +2380,10 @@ packages:
arg@4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
+ argon2@0.44.0:
+ resolution: {integrity: sha512-zHPGN3S55sihSQo0dBbK0A5qpi2R31z7HZDZnry3ifOyj8bZZnpZND2gpmhnRGO1V/d555RwBqIK5W4Mrmv3ig==}
+ engines: {node: '>=16.17.0'}
+
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@@ -1711,6 +2407,9 @@ packages:
ast-v8-to-istanbul@1.0.0:
resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==}
+ async@3.2.6:
+ resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
+
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
@@ -1736,12 +2435,22 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
+ bignumber.js@9.3.1:
+ resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
+
bintrees@1.0.2:
resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==}
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
+ bowser@2.14.1:
+ resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==}
+
+ boxen@5.1.2:
+ resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==}
+ engines: {node: '>=10'}
+
brace-expansion@1.1.13:
resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==}
@@ -1761,6 +2470,9 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
+ buffer-equal-constant-time@1.0.1:
+ resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
+
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@@ -1770,6 +2482,9 @@ packages:
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+ bullmq@5.73.4:
+ resolution: {integrity: sha512-Q+NeFLtdKSD3GDPYSX4pH+Mc9E4OZVKimXwrnZ5WmndNy31COMy4vQV9zfhgfHGSUFrlpsBicfKYbSjx9FbO+A==}
+
call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
@@ -1782,6 +2497,10 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
+ camelcase@6.3.0:
+ resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
+ engines: {node: '>=10'}
+
caniuse-lite@1.0.30001787:
resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==}
@@ -1796,6 +2515,10 @@ packages:
chardet@2.1.1:
resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==}
+ check-disk-space@3.4.0:
+ resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==}
+ engines: {node: '>=16'}
+
chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
@@ -1804,6 +2527,10 @@ packages:
resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==}
engines: {node: '>=6.0'}
+ cli-boxes@2.2.1:
+ resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==}
+ engines: {node: '>=6'}
+
cli-cursor@3.1.0:
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
engines: {node: '>=8'}
@@ -1836,6 +2563,10 @@ packages:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
+ cluster-key-slot@1.1.2:
+ resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
+ engines: {node: '>=0.10.0'}
+
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -1937,6 +2668,15 @@ packages:
create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
+ cron-parser@4.9.0:
+ resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
+ engines: {node: '>=12.0.0'}
+
+ cross-env@10.1.0:
+ resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==}
+ engines: {node: '>=20'}
+ hasBin: true
+
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
@@ -1964,6 +2704,10 @@ packages:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
+ denque@2.1.0:
+ resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
+ engines: {node: '>=0.10'}
+
depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
@@ -1972,6 +2716,9 @@ packages:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
+ detect-europe-js@0.1.2:
+ resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==}
+
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
@@ -2119,9 +2866,21 @@ packages:
duplexify@4.1.3:
resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==}
+ ecdsa-sig-formatter@1.0.11:
+ resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
+
+ ejs@3.1.10:
+ resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==}
+ engines: {node: '>=0.10.0'}
+ hasBin: true
+
electron-to-chromium@1.5.334:
resolution: {integrity: sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==}
+ email-validator@2.0.4:
+ resolution: {integrity: sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==}
+ engines: {node: '>4.0'}
+
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
@@ -2143,6 +2902,9 @@ packages:
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
engines: {node: '>=18'}
+ error-causes@3.0.2:
+ resolution: {integrity: sha512-i0B8zq1dHL6mM85FGoxaJnVtx6LD5nL2v0hlpGdntg5FOSyzQ46c9lmz5qx0xRS2+PWHGOHcYxGIBC5Le2dRMw==}
+
error-ex@1.3.4:
resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
@@ -2310,6 +3072,13 @@ packages:
fast-uri@3.1.0:
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
+ fast-xml-builder@1.1.4:
+ resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==}
+
+ fast-xml-parser@5.5.8:
+ resolution: {integrity: sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==}
+ hasBin: true
+
fastify-plugin@5.1.0:
resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==}
@@ -2336,6 +3105,9 @@ packages:
resolution: {integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==}
engines: {node: '>=20'}
+ filelist@1.0.6:
+ resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==}
+
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@@ -2452,6 +3224,11 @@ packages:
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
+ handlebars@4.7.9:
+ resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==}
+ engines: {node: '>=0.4.7'}
+ hasBin: true
+
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@@ -2513,6 +3290,10 @@ packages:
resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ ioredis@5.10.1:
+ resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==}
+ engines: {node: '>=12.22.0'}
+
ipaddr.js@2.3.0:
resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==}
engines: {node: '>= 10'}
@@ -2556,6 +3337,9 @@ packages:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
+ is-standalone-pwa@0.1.1:
+ resolution: {integrity: sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==}
+
is-unicode-supported@0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
@@ -2582,6 +3366,11 @@ packages:
resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==}
engines: {node: '>=6'}
+ jake@10.9.4:
+ resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
jest-worker@27.5.1:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
engines: {node: '>= 10.13.0'}
@@ -2629,6 +3418,16 @@ packages:
jsonfile@6.2.0:
resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
+ jsonwebtoken@9.0.3:
+ resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==}
+ engines: {node: '>=12', npm: '>=6'}
+
+ jwa@2.0.1:
+ resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==}
+
+ jws@4.0.1:
+ resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
+
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -2744,6 +3543,30 @@ packages:
lodash.camelcase@4.3.0:
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
+ lodash.defaults@4.2.0:
+ resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
+
+ lodash.includes@4.3.0:
+ resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
+
+ lodash.isarguments@3.1.0:
+ resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
+
+ lodash.isboolean@3.0.3:
+ resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
+
+ lodash.isinteger@4.0.4:
+ resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
+
+ lodash.isnumber@3.0.3:
+ resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
+
+ lodash.isplainobject@4.0.6:
+ resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
+
+ lodash.isstring@4.0.1:
+ resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
+
lodash.kebabcase@4.1.1:
resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==}
@@ -2753,6 +3576,9 @@ packages:
lodash.mergewith@4.6.2:
resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==}
+ lodash.once@4.1.1:
+ resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
+
lodash.snakecase@4.1.1:
resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==}
@@ -2777,6 +3603,10 @@ packages:
resolution: {integrity: sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==}
engines: {node: 20 || >=22}
+ luxon@3.7.2:
+ resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==}
+ engines: {node: '>=12'}
+
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
@@ -2853,6 +3683,10 @@ packages:
minimatch@3.1.5:
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
+ minimatch@5.1.9:
+ resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==}
+ engines: {node: '>=10'}
+
minimatch@9.0.3:
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -2867,6 +3701,13 @@ packages:
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+ msgpackr-extract@3.0.3:
+ resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==}
+ hasBin: true
+
+ msgpackr@1.11.5:
+ resolution: {integrity: sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==}
+
mute-stream@2.0.0:
resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
engines: {node: ^18.17.0 || >=20.5.0}
@@ -2896,13 +3737,29 @@ packages:
node-abort-controller@3.1.1:
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
+ node-addon-api@8.7.0:
+ resolution: {integrity: sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==}
+ engines: {node: ^18 || ^20 || >= 21}
+
node-emoji@1.11.0:
resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==}
+ node-gyp-build-optional-packages@5.2.2:
+ resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
+ hasBin: true
+
+ node-gyp-build@4.8.4:
+ resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
+ hasBin: true
+
node-releases@2.0.37:
resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==}
- object-inspect@1.13.4:
+ nodemailer@8.0.5:
+ resolution: {integrity: sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==}
+ engines: {node: '>=6.0.0'}
+
+ object-inspect@1.13.4:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'}
@@ -2932,6 +3789,9 @@ packages:
resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==}
engines: {node: '>=10'}
+ otplib@13.4.0:
+ resolution: {integrity: sha512-RUcYcRMCgRWhUE/XabRppXpUwCwaWBNHe5iPXhdvP8wwDGpGpsIf/kxX/ec3zFsOaM1Oq8lEhUqDwk6W7DHkwg==}
+
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
@@ -2948,10 +3808,29 @@ packages:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
+ passport-jwt@4.0.1:
+ resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==}
+
+ passport-local@1.0.0:
+ resolution: {integrity: sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==}
+ engines: {node: '>= 0.4.0'}
+
+ passport-strategy@1.0.0:
+ resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==}
+ engines: {node: '>= 0.4.0'}
+
+ passport@0.7.0:
+ resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==}
+ engines: {node: '>= 0.4.0'}
+
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
+ path-expression-matcher@1.5.0:
+ resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==}
+ engines: {node: '>=14.0.0'}
+
path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
@@ -2974,6 +3853,9 @@ packages:
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+ pause@0.0.1:
+ resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
+
peek-stream@1.1.3:
resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==}
@@ -3125,6 +4007,17 @@ packages:
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
engines: {node: '>= 12.13.0'}
+ redis-errors@1.2.0:
+ resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
+ engines: {node: '>=4'}
+
+ redis-info@3.1.0:
+ resolution: {integrity: sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==}
+
+ redis-parser@3.0.0:
+ resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
+ engines: {node: '>=4'}
+
reflect-metadata@0.2.2:
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
@@ -3299,6 +4192,9 @@ packages:
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+ standard-as-callback@2.1.0:
+ resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
+
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
@@ -3347,6 +4243,9 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
+ strnum@2.2.3:
+ resolution: {integrity: sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==}
+
strtok3@10.3.5:
resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==}
engines: {node: '>=18'}
@@ -3505,6 +4404,18 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
+ ua-is-frozen@0.1.2:
+ resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==}
+
+ ua-parser-js@2.0.9:
+ resolution: {integrity: sha512-OsqGhxyo/wGdLSXMSJxuMGN6H4gDnKz6Fb3IBm4bxZFMnyy0sdf6MN96Ie8tC6z/btdO+Bsy8guxlvLdwT076w==}
+ hasBin: true
+
+ uglify-js@3.19.3:
+ resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==}
+ engines: {node: '>=0.8.0'}
+ hasBin: true
+
uid@2.0.2:
resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==}
engines: {node: '>=8'}
@@ -3541,6 +4452,14 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ utils-merge@1.0.1:
+ resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
+ engines: {node: '>= 0.4.0'}
+
+ uuid@11.1.0:
+ resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
+ hasBin: true
+
v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
@@ -3666,10 +4585,17 @@ packages:
engines: {node: '>=8'}
hasBin: true
+ widest-line@3.1.0:
+ resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==}
+ engines: {node: '>=8'}
+
word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
+ wordwrap@1.0.0:
+ resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
+
wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
@@ -3777,6 +4703,468 @@ snapshots:
transitivePeerDependencies:
- chokidar
+ '@aws-crypto/crc32@5.2.0':
+ dependencies:
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.973.7
+ tslib: 2.8.1
+
+ '@aws-crypto/crc32c@5.2.0':
+ dependencies:
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.973.7
+ tslib: 2.8.1
+
+ '@aws-crypto/sha1-browser@5.2.0':
+ dependencies:
+ '@aws-crypto/supports-web-crypto': 5.2.0
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.973.7
+ '@aws-sdk/util-locate-window': 3.965.5
+ '@smithy/util-utf8': 2.3.0
+ tslib: 2.8.1
+
+ '@aws-crypto/sha256-browser@5.2.0':
+ dependencies:
+ '@aws-crypto/sha256-js': 5.2.0
+ '@aws-crypto/supports-web-crypto': 5.2.0
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.973.7
+ '@aws-sdk/util-locate-window': 3.965.5
+ '@smithy/util-utf8': 2.3.0
+ tslib: 2.8.1
+
+ '@aws-crypto/sha256-js@5.2.0':
+ dependencies:
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.973.7
+ tslib: 2.8.1
+
+ '@aws-crypto/supports-web-crypto@5.2.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@aws-crypto/util@5.2.0':
+ dependencies:
+ '@aws-sdk/types': 3.973.7
+ '@smithy/util-utf8': 2.3.0
+ tslib: 2.8.1
+
+ '@aws-sdk/client-s3@3.1029.0':
+ dependencies:
+ '@aws-crypto/sha1-browser': 5.2.0
+ '@aws-crypto/sha256-browser': 5.2.0
+ '@aws-crypto/sha256-js': 5.2.0
+ '@aws-sdk/core': 3.973.27
+ '@aws-sdk/credential-provider-node': 3.972.30
+ '@aws-sdk/middleware-bucket-endpoint': 3.972.9
+ '@aws-sdk/middleware-expect-continue': 3.972.9
+ '@aws-sdk/middleware-flexible-checksums': 3.974.7
+ '@aws-sdk/middleware-host-header': 3.972.9
+ '@aws-sdk/middleware-location-constraint': 3.972.9
+ '@aws-sdk/middleware-logger': 3.972.9
+ '@aws-sdk/middleware-recursion-detection': 3.972.10
+ '@aws-sdk/middleware-sdk-s3': 3.972.28
+ '@aws-sdk/middleware-ssec': 3.972.9
+ '@aws-sdk/middleware-user-agent': 3.972.29
+ '@aws-sdk/region-config-resolver': 3.972.11
+ '@aws-sdk/signature-v4-multi-region': 3.996.16
+ '@aws-sdk/types': 3.973.7
+ '@aws-sdk/util-endpoints': 3.996.6
+ '@aws-sdk/util-user-agent-browser': 3.972.9
+ '@aws-sdk/util-user-agent-node': 3.973.15
+ '@smithy/config-resolver': 4.4.14
+ '@smithy/core': 3.23.14
+ '@smithy/eventstream-serde-browser': 4.2.13
+ '@smithy/eventstream-serde-config-resolver': 4.3.13
+ '@smithy/eventstream-serde-node': 4.2.13
+ '@smithy/fetch-http-handler': 5.3.16
+ '@smithy/hash-blob-browser': 4.2.14
+ '@smithy/hash-node': 4.2.13
+ '@smithy/hash-stream-node': 4.2.13
+ '@smithy/invalid-dependency': 4.2.13
+ '@smithy/md5-js': 4.2.13
+ '@smithy/middleware-content-length': 4.2.13
+ '@smithy/middleware-endpoint': 4.4.29
+ '@smithy/middleware-retry': 4.5.1
+ '@smithy/middleware-serde': 4.2.17
+ '@smithy/middleware-stack': 4.2.13
+ '@smithy/node-config-provider': 4.3.13
+ '@smithy/node-http-handler': 4.5.2
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/smithy-client': 4.12.9
+ '@smithy/types': 4.14.0
+ '@smithy/url-parser': 4.2.13
+ '@smithy/util-base64': 4.3.2
+ '@smithy/util-body-length-browser': 4.2.2
+ '@smithy/util-body-length-node': 4.2.3
+ '@smithy/util-defaults-mode-browser': 4.3.45
+ '@smithy/util-defaults-mode-node': 4.2.49
+ '@smithy/util-endpoints': 3.3.4
+ '@smithy/util-middleware': 4.2.13
+ '@smithy/util-retry': 4.3.1
+ '@smithy/util-stream': 4.5.22
+ '@smithy/util-utf8': 4.2.2
+ '@smithy/util-waiter': 4.2.15
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/core@3.973.27':
+ dependencies:
+ '@aws-sdk/types': 3.973.7
+ '@aws-sdk/xml-builder': 3.972.17
+ '@smithy/core': 3.23.14
+ '@smithy/node-config-provider': 4.3.13
+ '@smithy/property-provider': 4.2.13
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/signature-v4': 5.3.13
+ '@smithy/smithy-client': 4.12.9
+ '@smithy/types': 4.14.0
+ '@smithy/util-base64': 4.3.2
+ '@smithy/util-middleware': 4.2.13
+ '@smithy/util-utf8': 4.2.2
+ tslib: 2.8.1
+
+ '@aws-sdk/crc64-nvme@3.972.6':
+ dependencies:
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-env@3.972.25':
+ dependencies:
+ '@aws-sdk/core': 3.973.27
+ '@aws-sdk/types': 3.973.7
+ '@smithy/property-provider': 4.2.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-http@3.972.27':
+ dependencies:
+ '@aws-sdk/core': 3.973.27
+ '@aws-sdk/types': 3.973.7
+ '@smithy/fetch-http-handler': 5.3.16
+ '@smithy/node-http-handler': 4.5.2
+ '@smithy/property-provider': 4.2.13
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/smithy-client': 4.12.9
+ '@smithy/types': 4.14.0
+ '@smithy/util-stream': 4.5.22
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-ini@3.972.29':
+ dependencies:
+ '@aws-sdk/core': 3.973.27
+ '@aws-sdk/credential-provider-env': 3.972.25
+ '@aws-sdk/credential-provider-http': 3.972.27
+ '@aws-sdk/credential-provider-login': 3.972.29
+ '@aws-sdk/credential-provider-process': 3.972.25
+ '@aws-sdk/credential-provider-sso': 3.972.29
+ '@aws-sdk/credential-provider-web-identity': 3.972.29
+ '@aws-sdk/nested-clients': 3.996.19
+ '@aws-sdk/types': 3.973.7
+ '@smithy/credential-provider-imds': 4.2.13
+ '@smithy/property-provider': 4.2.13
+ '@smithy/shared-ini-file-loader': 4.4.8
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/credential-provider-login@3.972.29':
+ dependencies:
+ '@aws-sdk/core': 3.973.27
+ '@aws-sdk/nested-clients': 3.996.19
+ '@aws-sdk/types': 3.973.7
+ '@smithy/property-provider': 4.2.13
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/shared-ini-file-loader': 4.4.8
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/credential-provider-node@3.972.30':
+ dependencies:
+ '@aws-sdk/credential-provider-env': 3.972.25
+ '@aws-sdk/credential-provider-http': 3.972.27
+ '@aws-sdk/credential-provider-ini': 3.972.29
+ '@aws-sdk/credential-provider-process': 3.972.25
+ '@aws-sdk/credential-provider-sso': 3.972.29
+ '@aws-sdk/credential-provider-web-identity': 3.972.29
+ '@aws-sdk/types': 3.973.7
+ '@smithy/credential-provider-imds': 4.2.13
+ '@smithy/property-provider': 4.2.13
+ '@smithy/shared-ini-file-loader': 4.4.8
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/credential-provider-process@3.972.25':
+ dependencies:
+ '@aws-sdk/core': 3.973.27
+ '@aws-sdk/types': 3.973.7
+ '@smithy/property-provider': 4.2.13
+ '@smithy/shared-ini-file-loader': 4.4.8
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-sso@3.972.29':
+ dependencies:
+ '@aws-sdk/core': 3.973.27
+ '@aws-sdk/nested-clients': 3.996.19
+ '@aws-sdk/token-providers': 3.1026.0
+ '@aws-sdk/types': 3.973.7
+ '@smithy/property-provider': 4.2.13
+ '@smithy/shared-ini-file-loader': 4.4.8
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/credential-provider-web-identity@3.972.29':
+ dependencies:
+ '@aws-sdk/core': 3.973.27
+ '@aws-sdk/nested-clients': 3.996.19
+ '@aws-sdk/types': 3.973.7
+ '@smithy/property-provider': 4.2.13
+ '@smithy/shared-ini-file-loader': 4.4.8
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/middleware-bucket-endpoint@3.972.9':
+ dependencies:
+ '@aws-sdk/types': 3.973.7
+ '@aws-sdk/util-arn-parser': 3.972.3
+ '@smithy/node-config-provider': 4.3.13
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/types': 4.14.0
+ '@smithy/util-config-provider': 4.2.2
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-expect-continue@3.972.9':
+ dependencies:
+ '@aws-sdk/types': 3.973.7
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-flexible-checksums@3.974.7':
+ dependencies:
+ '@aws-crypto/crc32': 5.2.0
+ '@aws-crypto/crc32c': 5.2.0
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/core': 3.973.27
+ '@aws-sdk/crc64-nvme': 3.972.6
+ '@aws-sdk/types': 3.973.7
+ '@smithy/is-array-buffer': 4.2.2
+ '@smithy/node-config-provider': 4.3.13
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/types': 4.14.0
+ '@smithy/util-middleware': 4.2.13
+ '@smithy/util-stream': 4.5.22
+ '@smithy/util-utf8': 4.2.2
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-host-header@3.972.9':
+ dependencies:
+ '@aws-sdk/types': 3.973.7
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-location-constraint@3.972.9':
+ dependencies:
+ '@aws-sdk/types': 3.973.7
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-logger@3.972.9':
+ dependencies:
+ '@aws-sdk/types': 3.973.7
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-recursion-detection@3.972.10':
+ dependencies:
+ '@aws-sdk/types': 3.973.7
+ '@aws/lambda-invoke-store': 0.2.4
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-sdk-s3@3.972.28':
+ dependencies:
+ '@aws-sdk/core': 3.973.27
+ '@aws-sdk/types': 3.973.7
+ '@aws-sdk/util-arn-parser': 3.972.3
+ '@smithy/core': 3.23.14
+ '@smithy/node-config-provider': 4.3.13
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/signature-v4': 5.3.13
+ '@smithy/smithy-client': 4.12.9
+ '@smithy/types': 4.14.0
+ '@smithy/util-config-provider': 4.2.2
+ '@smithy/util-middleware': 4.2.13
+ '@smithy/util-stream': 4.5.22
+ '@smithy/util-utf8': 4.2.2
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-ssec@3.972.9':
+ dependencies:
+ '@aws-sdk/types': 3.973.7
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-user-agent@3.972.29':
+ dependencies:
+ '@aws-sdk/core': 3.973.27
+ '@aws-sdk/types': 3.973.7
+ '@aws-sdk/util-endpoints': 3.996.6
+ '@smithy/core': 3.23.14
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/types': 4.14.0
+ '@smithy/util-retry': 4.3.1
+ tslib: 2.8.1
+
+ '@aws-sdk/nested-clients@3.996.19':
+ dependencies:
+ '@aws-crypto/sha256-browser': 5.2.0
+ '@aws-crypto/sha256-js': 5.2.0
+ '@aws-sdk/core': 3.973.27
+ '@aws-sdk/middleware-host-header': 3.972.9
+ '@aws-sdk/middleware-logger': 3.972.9
+ '@aws-sdk/middleware-recursion-detection': 3.972.10
+ '@aws-sdk/middleware-user-agent': 3.972.29
+ '@aws-sdk/region-config-resolver': 3.972.11
+ '@aws-sdk/types': 3.973.7
+ '@aws-sdk/util-endpoints': 3.996.6
+ '@aws-sdk/util-user-agent-browser': 3.972.9
+ '@aws-sdk/util-user-agent-node': 3.973.15
+ '@smithy/config-resolver': 4.4.14
+ '@smithy/core': 3.23.14
+ '@smithy/fetch-http-handler': 5.3.16
+ '@smithy/hash-node': 4.2.13
+ '@smithy/invalid-dependency': 4.2.13
+ '@smithy/middleware-content-length': 4.2.13
+ '@smithy/middleware-endpoint': 4.4.29
+ '@smithy/middleware-retry': 4.5.1
+ '@smithy/middleware-serde': 4.2.17
+ '@smithy/middleware-stack': 4.2.13
+ '@smithy/node-config-provider': 4.3.13
+ '@smithy/node-http-handler': 4.5.2
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/smithy-client': 4.12.9
+ '@smithy/types': 4.14.0
+ '@smithy/url-parser': 4.2.13
+ '@smithy/util-base64': 4.3.2
+ '@smithy/util-body-length-browser': 4.2.2
+ '@smithy/util-body-length-node': 4.2.3
+ '@smithy/util-defaults-mode-browser': 4.3.45
+ '@smithy/util-defaults-mode-node': 4.2.49
+ '@smithy/util-endpoints': 3.3.4
+ '@smithy/util-middleware': 4.2.13
+ '@smithy/util-retry': 4.3.1
+ '@smithy/util-utf8': 4.2.2
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/region-config-resolver@3.972.11':
+ dependencies:
+ '@aws-sdk/types': 3.973.7
+ '@smithy/config-resolver': 4.4.14
+ '@smithy/node-config-provider': 4.3.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@aws-sdk/s3-request-presigner@3.1029.0':
+ dependencies:
+ '@aws-sdk/signature-v4-multi-region': 3.996.16
+ '@aws-sdk/types': 3.973.7
+ '@aws-sdk/util-format-url': 3.972.9
+ '@smithy/middleware-endpoint': 4.4.29
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/smithy-client': 4.12.9
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@aws-sdk/signature-v4-multi-region@3.996.16':
+ dependencies:
+ '@aws-sdk/middleware-sdk-s3': 3.972.28
+ '@aws-sdk/types': 3.973.7
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/signature-v4': 5.3.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@aws-sdk/token-providers@3.1026.0':
+ dependencies:
+ '@aws-sdk/core': 3.973.27
+ '@aws-sdk/nested-clients': 3.996.19
+ '@aws-sdk/types': 3.973.7
+ '@smithy/property-provider': 4.2.13
+ '@smithy/shared-ini-file-loader': 4.4.8
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/types@3.973.7':
+ dependencies:
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@aws-sdk/util-arn-parser@3.972.3':
+ dependencies:
+ tslib: 2.8.1
+
+ '@aws-sdk/util-endpoints@3.996.6':
+ dependencies:
+ '@aws-sdk/types': 3.973.7
+ '@smithy/types': 4.14.0
+ '@smithy/url-parser': 4.2.13
+ '@smithy/util-endpoints': 3.3.4
+ tslib: 2.8.1
+
+ '@aws-sdk/util-format-url@3.972.9':
+ dependencies:
+ '@aws-sdk/types': 3.973.7
+ '@smithy/querystring-builder': 4.2.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@aws-sdk/util-locate-window@3.965.5':
+ dependencies:
+ tslib: 2.8.1
+
+ '@aws-sdk/util-user-agent-browser@3.972.9':
+ dependencies:
+ '@aws-sdk/types': 3.973.7
+ '@smithy/types': 4.14.0
+ bowser: 2.14.1
+ tslib: 2.8.1
+
+ '@aws-sdk/util-user-agent-node@3.973.15':
+ dependencies:
+ '@aws-sdk/middleware-user-agent': 3.972.29
+ '@aws-sdk/types': 3.973.7
+ '@smithy/node-config-provider': 4.3.13
+ '@smithy/types': 4.14.0
+ '@smithy/util-config-provider': 4.2.2
+ tslib: 2.8.1
+
+ '@aws-sdk/xml-builder@3.972.17':
+ dependencies:
+ '@smithy/types': 4.14.0
+ fast-xml-parser: 5.5.8
+ tslib: 2.8.1
+
+ '@aws/lambda-invoke-store@0.2.4': {}
+
'@babel/code-frame@7.29.0':
dependencies:
'@babel/helper-validator-identifier': 7.28.5
@@ -3800,6 +5188,32 @@ snapshots:
'@borewit/text-codec@0.2.2': {}
+ '@bull-board/api@6.21.0(@bull-board/ui@6.21.0)':
+ dependencies:
+ '@bull-board/ui': 6.21.0
+ redis-info: 3.1.0
+
+ '@bull-board/fastify@6.21.0':
+ dependencies:
+ '@bull-board/api': 6.21.0(@bull-board/ui@6.21.0)
+ '@bull-board/ui': 6.21.0
+ '@fastify/static': 9.1.0
+ '@fastify/view': 11.1.1
+ ejs: 3.1.10
+
+ '@bull-board/nestjs@6.21.0(@bull-board/api@6.21.0(@bull-board/ui@6.21.0))(@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)))(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)':
+ dependencies:
+ '@bull-board/api': 6.21.0(@bull-board/ui@6.21.0)
+ '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ reflect-metadata: 0.2.2
+ rxjs: 7.8.2
+
+ '@bull-board/ui@6.21.0':
+ dependencies:
+ '@bull-board/api': 6.21.0(@bull-board/ui@6.21.0)
+
'@colors/colors@1.5.0':
optional: true
@@ -3947,6 +5361,8 @@ snapshots:
tslib: 2.8.1
optional: true
+ '@epic-web/invariant@1.0.0': {}
+
'@esbuild-kit/core-utils@3.3.2':
dependencies:
esbuild: 0.18.20
@@ -4210,6 +5626,8 @@ snapshots:
ajv-formats: 3.0.1(ajv@8.18.0)
fast-uri: 3.1.0
+ '@fastify/busboy@3.2.0': {}
+
'@fastify/compress@8.3.1':
dependencies:
'@fastify/accept-negotiator': 2.0.1
@@ -4231,6 +5649,8 @@ snapshots:
fastify-plugin: 5.1.0
toad-cache: 3.7.0
+ '@fastify/deepmerge@3.2.1': {}
+
'@fastify/error@4.2.0': {}
'@fastify/fast-json-stringify-compiler@5.0.3':
@@ -4248,6 +5668,14 @@ snapshots:
dependencies:
dequal: 2.0.3
+ '@fastify/multipart@10.0.0':
+ dependencies:
+ '@fastify/busboy': 3.2.0
+ '@fastify/deepmerge': 3.2.1
+ '@fastify/error': 4.2.0
+ fastify-plugin: 5.1.0
+ secure-json-parse: 4.1.0
+
'@fastify/proxy-addr@5.1.0':
dependencies:
'@fastify/forwarded': 3.0.1
@@ -4270,6 +5698,11 @@ snapshots:
fastq: 1.20.1
glob: 13.0.6
+ '@fastify/view@11.1.1':
+ dependencies:
+ fastify-plugin: 5.1.0
+ toad-cache: 3.7.0
+
'@humanwhocodes/config-array@0.13.0':
dependencies:
'@humanwhocodes/object-schema': 2.0.3
@@ -4422,6 +5855,8 @@ snapshots:
optionalDependencies:
'@types/node': 20.19.39
+ '@ioredis/commands@1.5.1': {}
+
'@jridgewell/gen-mapping@0.3.13':
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -4457,6 +5892,24 @@ snapshots:
'@microsoft/tsdoc@0.16.0': {}
+ '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3':
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3':
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3':
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3':
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
+ optional: true
+
'@napi-rs/wasm-runtime@1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)':
dependencies:
'@emnapi/core': 1.9.2
@@ -4464,6 +5917,44 @@ snapshots:
'@tybys/wasm-util': 0.10.1
optional: true
+ '@nestjs-modules/ioredis@2.2.1(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(ioredis@5.10.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)':
+ dependencies:
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ ioredis: 5.10.1
+ optionalDependencies:
+ '@nestjs/terminus': 11.1.1(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ transitivePeerDependencies:
+ - '@grpc/grpc-js'
+ - '@grpc/proto-loader'
+ - '@mikro-orm/core'
+ - '@mikro-orm/nestjs'
+ - '@nestjs/axios'
+ - '@nestjs/microservices'
+ - '@nestjs/mongoose'
+ - '@nestjs/sequelize'
+ - '@nestjs/typeorm'
+ - '@prisma/client'
+ - mongoose
+ - reflect-metadata
+ - rxjs
+ - sequelize
+ - typeorm
+
+ '@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))':
+ dependencies:
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ tslib: 2.8.1
+
+ '@nestjs/bullmq@11.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(bullmq@5.73.4)':
+ dependencies:
+ '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ bullmq: 5.73.4
+ tslib: 2.8.1
+
'@nestjs/cli@11.0.19(@swc/core@1.15.24)(@types/node@20.19.39)(esbuild@0.27.7)':
dependencies:
'@angular-devkit/core': 19.2.24(chokidar@4.0.3)
@@ -4524,12 +6015,23 @@ snapshots:
tslib: 2.8.1
uid: 2.0.2
+ '@nestjs/jwt@11.0.2(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))':
+ dependencies:
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@types/jsonwebtoken': 9.0.10
+ jsonwebtoken: 9.0.3
+
'@nestjs/mapped-types@2.1.1(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)':
dependencies:
'@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
reflect-metadata: 0.2.2
- '@nestjs/platform-fastify@11.1.18(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))':
+ '@nestjs/passport@11.0.5(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)':
+ dependencies:
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ passport: 0.7.0
+
+ '@nestjs/platform-fastify@11.1.18(@fastify/static@9.1.0)(@fastify/view@11.1.1)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))':
dependencies:
'@fastify/cors': 11.2.0
'@fastify/formbody': 8.0.2
@@ -4545,6 +6047,7 @@ snapshots:
tslib: 2.8.1
optionalDependencies:
'@fastify/static': 9.1.0
+ '@fastify/view': 11.1.1
'@nestjs/schematics@11.0.10(chokidar@4.0.3)(typescript@5.9.3)':
dependencies:
@@ -4571,6 +6074,16 @@ snapshots:
optionalDependencies:
'@fastify/static': 9.1.0
+ '@nestjs/terminus@11.1.1(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)':
+ dependencies:
+ '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ boxen: 5.1.2
+ check-disk-space: 3.4.0
+ reflect-metadata: 0.2.2
+ rxjs: 7.8.2
+ optional: true
+
'@nestjs/testing@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))':
dependencies:
'@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)
@@ -4585,6 +6098,8 @@ snapshots:
'@noble/hashes@1.8.0': {}
+ '@noble/hashes@2.0.1': {}
+
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -4603,80 +6118,450 @@ snapshots:
'@opentelemetry/api@1.9.1': {}
+ '@otplib/core@13.4.0': {}
+
+ '@otplib/hotp@13.4.0':
+ dependencies:
+ '@otplib/core': 13.4.0
+ '@otplib/uri': 13.4.0
+
+ '@otplib/plugin-base32-scure@13.4.0':
+ dependencies:
+ '@otplib/core': 13.4.0
+ '@scure/base': 2.0.0
+
+ '@otplib/plugin-crypto-noble@13.4.0':
+ dependencies:
+ '@noble/hashes': 2.0.1
+ '@otplib/core': 13.4.0
+
+ '@otplib/totp@13.4.0':
+ dependencies:
+ '@otplib/core': 13.4.0
+ '@otplib/hotp': 13.4.0
+ '@otplib/uri': 13.4.0
+
+ '@otplib/uri@13.4.0':
+ dependencies:
+ '@otplib/core': 13.4.0
+
'@oxc-project/types@0.124.0': {}
'@paralleldrive/cuid2@2.3.1':
dependencies:
- '@noble/hashes': 1.8.0
+ '@noble/hashes': 1.8.0
+
+ '@paralleldrive/cuid2@3.3.0':
+ dependencies:
+ '@noble/hashes': 2.0.1
+ bignumber.js: 9.3.1
+ error-causes: 3.0.2
+
+ '@phc/format@1.0.0': {}
+
+ '@pinojs/redact@0.4.0': {}
+
+ '@pkgr/core@0.2.9': {}
+
+ '@rolldown/binding-android-arm64@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-darwin-arm64@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-darwin-x64@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-freebsd-x64@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-linux-x64-musl@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-openharmony-arm64@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-wasm32-wasi@1.0.0-rc.15':
+ dependencies:
+ '@emnapi/core': 1.9.2
+ '@emnapi/runtime': 1.9.2
+ '@napi-rs/wasm-runtime': 1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+ optional: true
+
+ '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15':
+ optional: true
+
+ '@rolldown/pluginutils@1.0.0-rc.15': {}
+
+ '@rollup/pluginutils@5.3.0':
+ dependencies:
+ '@types/estree': 1.0.8
+ estree-walker: 2.0.2
+ picomatch: 4.0.4
+
+ '@scarf/scarf@1.4.0': {}
+
+ '@scure/base@2.0.0': {}
+
+ '@simple-libs/child-process-utils@1.0.2':
+ dependencies:
+ '@simple-libs/stream-utils': 1.2.0
+
+ '@simple-libs/stream-utils@1.2.0': {}
+
+ '@smithy/chunked-blob-reader-native@4.2.3':
+ dependencies:
+ '@smithy/util-base64': 4.3.2
+ tslib: 2.8.1
+
+ '@smithy/chunked-blob-reader@5.2.2':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/config-resolver@4.4.14':
+ dependencies:
+ '@smithy/node-config-provider': 4.3.13
+ '@smithy/types': 4.14.0
+ '@smithy/util-config-provider': 4.2.2
+ '@smithy/util-endpoints': 3.3.4
+ '@smithy/util-middleware': 4.2.13
+ tslib: 2.8.1
+
+ '@smithy/core@3.23.14':
+ dependencies:
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/types': 4.14.0
+ '@smithy/url-parser': 4.2.13
+ '@smithy/util-base64': 4.3.2
+ '@smithy/util-body-length-browser': 4.2.2
+ '@smithy/util-middleware': 4.2.13
+ '@smithy/util-stream': 4.5.22
+ '@smithy/util-utf8': 4.2.2
+ '@smithy/uuid': 1.1.2
+ tslib: 2.8.1
+
+ '@smithy/credential-provider-imds@4.2.13':
+ dependencies:
+ '@smithy/node-config-provider': 4.3.13
+ '@smithy/property-provider': 4.2.13
+ '@smithy/types': 4.14.0
+ '@smithy/url-parser': 4.2.13
+ tslib: 2.8.1
+
+ '@smithy/eventstream-codec@4.2.13':
+ dependencies:
+ '@aws-crypto/crc32': 5.2.0
+ '@smithy/types': 4.14.0
+ '@smithy/util-hex-encoding': 4.2.2
+ tslib: 2.8.1
+
+ '@smithy/eventstream-serde-browser@4.2.13':
+ dependencies:
+ '@smithy/eventstream-serde-universal': 4.2.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@smithy/eventstream-serde-config-resolver@4.3.13':
+ dependencies:
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@smithy/eventstream-serde-node@4.2.13':
+ dependencies:
+ '@smithy/eventstream-serde-universal': 4.2.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@smithy/eventstream-serde-universal@4.2.13':
+ dependencies:
+ '@smithy/eventstream-codec': 4.2.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@smithy/fetch-http-handler@5.3.16':
+ dependencies:
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/querystring-builder': 4.2.13
+ '@smithy/types': 4.14.0
+ '@smithy/util-base64': 4.3.2
+ tslib: 2.8.1
+
+ '@smithy/hash-blob-browser@4.2.14':
+ dependencies:
+ '@smithy/chunked-blob-reader': 5.2.2
+ '@smithy/chunked-blob-reader-native': 4.2.3
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@smithy/hash-node@4.2.13':
+ dependencies:
+ '@smithy/types': 4.14.0
+ '@smithy/util-buffer-from': 4.2.2
+ '@smithy/util-utf8': 4.2.2
+ tslib: 2.8.1
+
+ '@smithy/hash-stream-node@4.2.13':
+ dependencies:
+ '@smithy/types': 4.14.0
+ '@smithy/util-utf8': 4.2.2
+ tslib: 2.8.1
+
+ '@smithy/invalid-dependency@4.2.13':
+ dependencies:
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@smithy/is-array-buffer@2.2.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/is-array-buffer@4.2.2':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/md5-js@4.2.13':
+ dependencies:
+ '@smithy/types': 4.14.0
+ '@smithy/util-utf8': 4.2.2
+ tslib: 2.8.1
+
+ '@smithy/middleware-content-length@4.2.13':
+ dependencies:
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@smithy/middleware-endpoint@4.4.29':
+ dependencies:
+ '@smithy/core': 3.23.14
+ '@smithy/middleware-serde': 4.2.17
+ '@smithy/node-config-provider': 4.3.13
+ '@smithy/shared-ini-file-loader': 4.4.8
+ '@smithy/types': 4.14.0
+ '@smithy/url-parser': 4.2.13
+ '@smithy/util-middleware': 4.2.13
+ tslib: 2.8.1
+
+ '@smithy/middleware-retry@4.5.1':
+ dependencies:
+ '@smithy/core': 3.23.14
+ '@smithy/node-config-provider': 4.3.13
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/service-error-classification': 4.2.13
+ '@smithy/smithy-client': 4.12.9
+ '@smithy/types': 4.14.0
+ '@smithy/util-middleware': 4.2.13
+ '@smithy/util-retry': 4.3.1
+ '@smithy/uuid': 1.1.2
+ tslib: 2.8.1
+
+ '@smithy/middleware-serde@4.2.17':
+ dependencies:
+ '@smithy/core': 3.23.14
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@smithy/middleware-stack@4.2.13':
+ dependencies:
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@smithy/node-config-provider@4.3.13':
+ dependencies:
+ '@smithy/property-provider': 4.2.13
+ '@smithy/shared-ini-file-loader': 4.4.8
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@smithy/node-http-handler@4.5.2':
+ dependencies:
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/querystring-builder': 4.2.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@smithy/property-provider@4.2.13':
+ dependencies:
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@smithy/protocol-http@5.3.13':
+ dependencies:
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@smithy/querystring-builder@4.2.13':
+ dependencies:
+ '@smithy/types': 4.14.0
+ '@smithy/util-uri-escape': 4.2.2
+ tslib: 2.8.1
+
+ '@smithy/querystring-parser@4.2.13':
+ dependencies:
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
+
+ '@smithy/service-error-classification@4.2.13':
+ dependencies:
+ '@smithy/types': 4.14.0
+
+ '@smithy/shared-ini-file-loader@4.4.8':
+ dependencies:
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
- '@pinojs/redact@0.4.0': {}
+ '@smithy/signature-v4@5.3.13':
+ dependencies:
+ '@smithy/is-array-buffer': 4.2.2
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/types': 4.14.0
+ '@smithy/util-hex-encoding': 4.2.2
+ '@smithy/util-middleware': 4.2.13
+ '@smithy/util-uri-escape': 4.2.2
+ '@smithy/util-utf8': 4.2.2
+ tslib: 2.8.1
- '@pkgr/core@0.2.9': {}
+ '@smithy/smithy-client@4.12.9':
+ dependencies:
+ '@smithy/core': 3.23.14
+ '@smithy/middleware-endpoint': 4.4.29
+ '@smithy/middleware-stack': 4.2.13
+ '@smithy/protocol-http': 5.3.13
+ '@smithy/types': 4.14.0
+ '@smithy/util-stream': 4.5.22
+ tslib: 2.8.1
- '@rolldown/binding-android-arm64@1.0.0-rc.15':
- optional: true
+ '@smithy/types@4.14.0':
+ dependencies:
+ tslib: 2.8.1
- '@rolldown/binding-darwin-arm64@1.0.0-rc.15':
- optional: true
+ '@smithy/url-parser@4.2.13':
+ dependencies:
+ '@smithy/querystring-parser': 4.2.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
- '@rolldown/binding-darwin-x64@1.0.0-rc.15':
- optional: true
+ '@smithy/util-base64@4.3.2':
+ dependencies:
+ '@smithy/util-buffer-from': 4.2.2
+ '@smithy/util-utf8': 4.2.2
+ tslib: 2.8.1
- '@rolldown/binding-freebsd-x64@1.0.0-rc.15':
- optional: true
+ '@smithy/util-body-length-browser@4.2.2':
+ dependencies:
+ tslib: 2.8.1
- '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15':
- optional: true
+ '@smithy/util-body-length-node@4.2.3':
+ dependencies:
+ tslib: 2.8.1
- '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15':
- optional: true
+ '@smithy/util-buffer-from@2.2.0':
+ dependencies:
+ '@smithy/is-array-buffer': 2.2.0
+ tslib: 2.8.1
- '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15':
- optional: true
+ '@smithy/util-buffer-from@4.2.2':
+ dependencies:
+ '@smithy/is-array-buffer': 4.2.2
+ tslib: 2.8.1
- '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15':
- optional: true
+ '@smithy/util-config-provider@4.2.2':
+ dependencies:
+ tslib: 2.8.1
- '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15':
- optional: true
+ '@smithy/util-defaults-mode-browser@4.3.45':
+ dependencies:
+ '@smithy/property-provider': 4.2.13
+ '@smithy/smithy-client': 4.12.9
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
- '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15':
- optional: true
+ '@smithy/util-defaults-mode-node@4.2.49':
+ dependencies:
+ '@smithy/config-resolver': 4.4.14
+ '@smithy/credential-provider-imds': 4.2.13
+ '@smithy/node-config-provider': 4.3.13
+ '@smithy/property-provider': 4.2.13
+ '@smithy/smithy-client': 4.12.9
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
- '@rolldown/binding-linux-x64-musl@1.0.0-rc.15':
- optional: true
+ '@smithy/util-endpoints@3.3.4':
+ dependencies:
+ '@smithy/node-config-provider': 4.3.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
- '@rolldown/binding-openharmony-arm64@1.0.0-rc.15':
- optional: true
+ '@smithy/util-hex-encoding@4.2.2':
+ dependencies:
+ tslib: 2.8.1
- '@rolldown/binding-wasm32-wasi@1.0.0-rc.15':
+ '@smithy/util-middleware@4.2.13':
dependencies:
- '@emnapi/core': 1.9.2
- '@emnapi/runtime': 1.9.2
- '@napi-rs/wasm-runtime': 1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
- optional: true
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
- '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15':
- optional: true
+ '@smithy/util-retry@4.3.1':
+ dependencies:
+ '@smithy/service-error-classification': 4.2.13
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
- '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15':
- optional: true
+ '@smithy/util-stream@4.5.22':
+ dependencies:
+ '@smithy/fetch-http-handler': 5.3.16
+ '@smithy/node-http-handler': 4.5.2
+ '@smithy/types': 4.14.0
+ '@smithy/util-base64': 4.3.2
+ '@smithy/util-buffer-from': 4.2.2
+ '@smithy/util-hex-encoding': 4.2.2
+ '@smithy/util-utf8': 4.2.2
+ tslib: 2.8.1
- '@rolldown/pluginutils@1.0.0-rc.15': {}
+ '@smithy/util-uri-escape@4.2.2':
+ dependencies:
+ tslib: 2.8.1
- '@rollup/pluginutils@5.3.0':
+ '@smithy/util-utf8@2.3.0':
dependencies:
- '@types/estree': 1.0.8
- estree-walker: 2.0.2
- picomatch: 4.0.4
+ '@smithy/util-buffer-from': 2.2.0
+ tslib: 2.8.1
- '@scarf/scarf@1.4.0': {}
+ '@smithy/util-utf8@4.2.2':
+ dependencies:
+ '@smithy/util-buffer-from': 4.2.2
+ tslib: 2.8.1
- '@simple-libs/child-process-utils@1.0.2':
+ '@smithy/util-waiter@4.2.15':
dependencies:
- '@simple-libs/stream-utils': 1.2.0
+ '@smithy/types': 4.14.0
+ tslib: 2.8.1
- '@simple-libs/stream-utils@1.2.0': {}
+ '@smithy/uuid@1.1.2':
+ dependencies:
+ tslib: 2.8.1
'@standard-schema/spec@1.1.0': {}
@@ -4762,11 +6647,20 @@ snapshots:
tslib: 2.8.1
optional: true
+ '@types/body-parser@1.19.6':
+ dependencies:
+ '@types/connect': 3.4.38
+ '@types/node': 20.19.39
+
'@types/chai@5.2.3':
dependencies:
'@types/deep-eql': 4.0.2
assertion-error: 2.0.1
+ '@types/connect@3.4.38':
+ dependencies:
+ '@types/node': 20.19.39
+
'@types/cookiejar@2.1.5': {}
'@types/deep-eql@4.0.2': {}
@@ -4783,22 +6677,81 @@ snapshots:
'@types/estree@1.0.8': {}
+ '@types/express-serve-static-core@5.1.1':
+ dependencies:
+ '@types/node': 20.19.39
+ '@types/qs': 6.15.0
+ '@types/range-parser': 1.2.7
+ '@types/send': 1.2.1
+
+ '@types/express@5.0.6':
+ dependencies:
+ '@types/body-parser': 1.19.6
+ '@types/express-serve-static-core': 5.1.1
+ '@types/serve-static': 2.2.0
+
+ '@types/http-errors@2.0.5': {}
+
'@types/json-schema@7.0.15': {}
+ '@types/jsonwebtoken@9.0.10':
+ dependencies:
+ '@types/ms': 2.1.0
+ '@types/node': 20.19.39
+
'@types/methods@1.1.4': {}
+ '@types/ms@2.1.0': {}
+
'@types/node@20.19.39':
dependencies:
undici-types: 6.21.0
+ '@types/nodemailer@8.0.0':
+ dependencies:
+ '@types/node': 20.19.39
+
+ '@types/passport-jwt@4.0.1':
+ dependencies:
+ '@types/jsonwebtoken': 9.0.10
+ '@types/passport-strategy': 0.2.38
+
+ '@types/passport-local@1.0.38':
+ dependencies:
+ '@types/express': 5.0.6
+ '@types/passport': 1.0.17
+ '@types/passport-strategy': 0.2.38
+
+ '@types/passport-strategy@0.2.38':
+ dependencies:
+ '@types/express': 5.0.6
+ '@types/passport': 1.0.17
+
+ '@types/passport@1.0.17':
+ dependencies:
+ '@types/express': 5.0.6
+
'@types/pg@8.20.0':
dependencies:
'@types/node': 20.19.39
pg-protocol: 1.13.0
pg-types: 2.2.0
+ '@types/qs@6.15.0': {}
+
+ '@types/range-parser@1.2.7': {}
+
'@types/semver@7.7.1': {}
+ '@types/send@1.2.1':
+ dependencies:
+ '@types/node': 20.19.39
+
+ '@types/serve-static@2.2.0':
+ dependencies:
+ '@types/http-errors': 2.0.5
+ '@types/node': 20.19.39
+
'@types/superagent@8.1.9':
dependencies:
'@types/cookiejar': 2.1.5
@@ -4811,6 +6764,8 @@ snapshots:
'@types/methods': 1.1.4
'@types/superagent': 8.1.9
+ '@types/ua-parser-js@0.7.39': {}
+
'@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
@@ -5090,6 +7045,11 @@ snapshots:
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
+ ansi-align@3.0.1:
+ dependencies:
+ string-width: 4.2.3
+ optional: true
+
ansi-colors@4.1.3: {}
ansi-escapes@7.3.0:
@@ -5110,6 +7070,13 @@ snapshots:
arg@4.1.3: {}
+ argon2@0.44.0:
+ dependencies:
+ '@phc/format': 1.0.0
+ cross-env: 10.1.0
+ node-addon-api: 8.7.0
+ node-gyp-build: 4.8.4
+
argparse@2.0.1: {}
array-ify@1.0.0: {}
@@ -5128,6 +7095,8 @@ snapshots:
estree-walker: 3.0.3
js-tokens: 10.0.0
+ async@3.2.6: {}
+
asynckit@0.4.0: {}
atomic-sleep@1.0.0: {}
@@ -5145,6 +7114,8 @@ snapshots:
baseline-browser-mapping@2.10.17: {}
+ bignumber.js@9.3.1: {}
+
bintrees@1.0.2: {}
bl@4.1.0:
@@ -5153,6 +7124,20 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.2
+ bowser@2.14.1: {}
+
+ boxen@5.1.2:
+ dependencies:
+ ansi-align: 3.0.1
+ camelcase: 6.3.0
+ chalk: 4.1.2
+ cli-boxes: 2.2.1
+ string-width: 4.2.3
+ type-fest: 0.20.2
+ widest-line: 3.1.0
+ wrap-ansi: 7.0.0
+ optional: true
+
brace-expansion@1.1.13:
dependencies:
balanced-match: 1.0.2
@@ -5178,6 +7163,8 @@ snapshots:
node-releases: 2.0.37
update-browserslist-db: 1.2.3(browserslist@4.28.2)
+ buffer-equal-constant-time@1.0.1: {}
+
buffer-from@1.1.2: {}
buffer@5.7.1:
@@ -5190,6 +7177,18 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
+ bullmq@5.73.4:
+ dependencies:
+ cron-parser: 4.9.0
+ ioredis: 5.10.1
+ msgpackr: 1.11.5
+ node-abort-controller: 3.1.1
+ semver: 7.7.4
+ tslib: 2.8.1
+ uuid: 11.1.0
+ transitivePeerDependencies:
+ - supports-color
+
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
@@ -5202,6 +7201,9 @@ snapshots:
callsites@3.1.0: {}
+ camelcase@6.3.0:
+ optional: true
+
caniuse-lite@1.0.30001787: {}
chai@6.2.2: {}
@@ -5213,12 +7215,18 @@ snapshots:
chardet@2.1.1: {}
+ check-disk-space@3.4.0:
+ optional: true
+
chokidar@4.0.3:
dependencies:
readdirp: 4.1.2
chrome-trace-event@1.0.4: {}
+ cli-boxes@2.2.1:
+ optional: true
+
cli-cursor@3.1.0:
dependencies:
restore-cursor: 3.1.0
@@ -5250,6 +7258,8 @@ snapshots:
clone@1.0.4: {}
+ cluster-key-slot@1.1.2: {}
+
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -5334,6 +7344,15 @@ snapshots:
create-require@1.1.1: {}
+ cron-parser@4.9.0:
+ dependencies:
+ luxon: 3.7.2
+
+ cross-env@10.1.0:
+ dependencies:
+ '@epic-web/invariant': 1.0.0
+ cross-spawn: 7.0.6
+
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
@@ -5354,10 +7373,14 @@ snapshots:
delayed-stream@1.0.0: {}
+ denque@2.1.0: {}
+
depd@2.0.0: {}
dequal@2.0.3: {}
+ detect-europe-js@0.1.2: {}
+
detect-libc@2.1.2: {}
dezalgo@1.0.4:
@@ -5425,8 +7448,18 @@ snapshots:
readable-stream: 3.6.2
stream-shift: 1.0.3
+ ecdsa-sig-formatter@1.0.11:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ ejs@3.1.10:
+ dependencies:
+ jake: 10.9.4
+
electron-to-chromium@1.5.334: {}
+ email-validator@2.0.4: {}
+
emoji-regex@10.6.0: {}
emoji-regex@8.0.0: {}
@@ -5444,6 +7477,8 @@ snapshots:
environment@1.1.0: {}
+ error-causes@3.0.2: {}
+
error-ex@1.3.4:
dependencies:
is-arrayish: 0.2.1
@@ -5694,6 +7729,16 @@ snapshots:
fast-uri@3.1.0: {}
+ fast-xml-builder@1.1.4:
+ dependencies:
+ path-expression-matcher: 1.5.0
+
+ fast-xml-parser@5.5.8:
+ dependencies:
+ fast-xml-builder: 1.1.4
+ path-expression-matcher: 1.5.0
+ strnum: 2.2.3
+
fastify-plugin@5.1.0: {}
fastify@5.8.4:
@@ -5735,6 +7780,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ filelist@1.0.6:
+ dependencies:
+ minimatch: 5.1.9
+
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@@ -5887,6 +7936,15 @@ snapshots:
graphemer@1.4.0: {}
+ handlebars@4.7.9:
+ dependencies:
+ minimist: 1.2.8
+ neo-async: 2.6.2
+ source-map: 0.6.1
+ wordwrap: 1.0.0
+ optionalDependencies:
+ uglify-js: 3.19.3
+
has-flag@4.0.0: {}
has-symbols@1.1.0: {}
@@ -5937,6 +7995,20 @@ snapshots:
ini@4.1.1: {}
+ ioredis@5.10.1:
+ dependencies:
+ '@ioredis/commands': 1.5.1
+ cluster-key-slot: 1.1.2
+ debug: 4.4.3
+ denque: 2.1.0
+ lodash.defaults: 4.2.0
+ lodash.isarguments: 3.1.0
+ redis-errors: 1.2.0
+ redis-parser: 3.0.0
+ standard-as-callback: 2.1.0
+ transitivePeerDependencies:
+ - supports-color
+
ipaddr.js@2.3.0: {}
is-arrayish@0.2.1: {}
@@ -5963,6 +8035,8 @@ snapshots:
is-plain-obj@4.1.0: {}
+ is-standalone-pwa@0.1.1: {}
+
is-unicode-supported@0.1.0: {}
isarray@1.0.0: {}
@@ -5984,6 +8058,12 @@ snapshots:
iterare@1.2.1: {}
+ jake@10.9.4:
+ dependencies:
+ async: 3.2.6
+ filelist: 1.0.6
+ picocolors: 1.1.1
+
jest-worker@27.5.1:
dependencies:
'@types/node': 20.19.39
@@ -6024,6 +8104,30 @@ snapshots:
optionalDependencies:
graceful-fs: 4.2.11
+ jsonwebtoken@9.0.3:
+ dependencies:
+ jws: 4.0.1
+ lodash.includes: 4.3.0
+ lodash.isboolean: 3.0.3
+ lodash.isinteger: 4.0.4
+ lodash.isnumber: 3.0.3
+ lodash.isplainobject: 4.0.6
+ lodash.isstring: 4.0.1
+ lodash.once: 4.1.1
+ ms: 2.1.3
+ semver: 7.7.4
+
+ jwa@2.0.1:
+ dependencies:
+ buffer-equal-constant-time: 1.0.1
+ ecdsa-sig-formatter: 1.0.11
+ safe-buffer: 5.2.1
+
+ jws@4.0.1:
+ dependencies:
+ jwa: 2.0.1
+ safe-buffer: 5.2.1
+
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
@@ -6120,12 +8224,30 @@ snapshots:
lodash.camelcase@4.3.0: {}
+ lodash.defaults@4.2.0: {}
+
+ lodash.includes@4.3.0: {}
+
+ lodash.isarguments@3.1.0: {}
+
+ lodash.isboolean@3.0.3: {}
+
+ lodash.isinteger@4.0.4: {}
+
+ lodash.isnumber@3.0.3: {}
+
+ lodash.isplainobject@4.0.6: {}
+
+ lodash.isstring@4.0.1: {}
+
lodash.kebabcase@4.1.1: {}
lodash.merge@4.6.2: {}
lodash.mergewith@4.6.2: {}
+ lodash.once@4.1.1: {}
+
lodash.snakecase@4.1.1: {}
lodash.startcase@4.4.0: {}
@@ -6149,6 +8271,8 @@ snapshots:
lru-cache@11.3.3: {}
+ luxon@3.7.2: {}
+
magic-string@0.30.17:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -6210,6 +8334,10 @@ snapshots:
dependencies:
brace-expansion: 1.1.13
+ minimatch@5.1.9:
+ dependencies:
+ brace-expansion: 2.0.3
+
minimatch@9.0.3:
dependencies:
brace-expansion: 2.0.3
@@ -6220,6 +8348,22 @@ snapshots:
ms@2.1.3: {}
+ msgpackr-extract@3.0.3:
+ dependencies:
+ node-gyp-build-optional-packages: 5.2.2
+ optionalDependencies:
+ '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3
+ '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3
+ '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3
+ '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3
+ '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3
+ '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3
+ optional: true
+
+ msgpackr@1.11.5:
+ optionalDependencies:
+ msgpackr-extract: 3.0.3
+
mute-stream@2.0.0: {}
nanoid@3.3.11: {}
@@ -6239,12 +8383,23 @@ snapshots:
node-abort-controller@3.1.1: {}
+ node-addon-api@8.7.0: {}
+
node-emoji@1.11.0:
dependencies:
lodash: 4.18.1
+ node-gyp-build-optional-packages@5.2.2:
+ dependencies:
+ detect-libc: 2.1.2
+ optional: true
+
+ node-gyp-build@4.8.4: {}
+
node-releases@2.0.37: {}
+ nodemailer@8.0.5: {}
+
object-inspect@1.13.4: {}
obug@2.1.1: {}
@@ -6284,6 +8439,15 @@ snapshots:
strip-ansi: 6.0.1
wcwidth: 1.0.1
+ otplib@13.4.0:
+ dependencies:
+ '@otplib/core': 13.4.0
+ '@otplib/hotp': 13.4.0
+ '@otplib/plugin-base32-scure': 13.4.0
+ '@otplib/plugin-crypto-noble': 13.4.0
+ '@otplib/totp': 13.4.0
+ '@otplib/uri': 13.4.0
+
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
@@ -6303,8 +8467,27 @@ snapshots:
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
+ passport-jwt@4.0.1:
+ dependencies:
+ jsonwebtoken: 9.0.3
+ passport-strategy: 1.0.0
+
+ passport-local@1.0.0:
+ dependencies:
+ passport-strategy: 1.0.0
+
+ passport-strategy@1.0.0: {}
+
+ passport@0.7.0:
+ dependencies:
+ passport-strategy: 1.0.0
+ pause: 0.0.1
+ utils-merge: 1.0.1
+
path-exists@4.0.0: {}
+ path-expression-matcher@1.5.0: {}
+
path-is-absolute@1.0.1: {}
path-key@3.1.1: {}
@@ -6320,6 +8503,8 @@ snapshots:
pathe@2.0.3: {}
+ pause@0.0.1: {}
+
peek-stream@1.1.3:
dependencies:
buffer-from: 1.1.2
@@ -6475,6 +8660,16 @@ snapshots:
real-require@0.2.0: {}
+ redis-errors@1.2.0: {}
+
+ redis-info@3.1.0:
+ dependencies:
+ lodash: 4.18.1
+
+ redis-parser@3.0.0:
+ dependencies:
+ redis-errors: 1.2.0
+
reflect-metadata@0.2.2: {}
require-directory@2.1.1: {}
@@ -6646,6 +8841,8 @@ snapshots:
stackback@0.0.2: {}
+ standard-as-callback@2.1.0: {}
+
statuses@2.0.2: {}
std-env@4.0.0: {}
@@ -6691,6 +8888,8 @@ snapshots:
strip-json-comments@3.1.1: {}
+ strnum@2.2.3: {}
+
strtok3@10.3.5:
dependencies:
'@tokenizer/token': 0.3.0
@@ -6859,6 +9058,17 @@ snapshots:
typescript@5.9.3: {}
+ ua-is-frozen@0.1.2: {}
+
+ ua-parser-js@2.0.9:
+ dependencies:
+ detect-europe-js: 0.1.2
+ is-standalone-pwa: 0.1.1
+ ua-is-frozen: 0.1.2
+
+ uglify-js@3.19.3:
+ optional: true
+
uid@2.0.2:
dependencies:
'@lukeed/csprng': 1.1.0
@@ -6897,6 +9107,10 @@ snapshots:
util-deprecate@1.0.2: {}
+ utils-merge@1.0.1: {}
+
+ uuid@11.1.0: {}
+
v8-compile-cache-lib@3.0.1: {}
vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
@@ -7000,8 +9214,15 @@ snapshots:
siginfo: 2.0.0
stackback: 0.0.2
+ widest-line@3.1.0:
+ dependencies:
+ string-width: 4.2.3
+ optional: true
+
word-wrap@1.2.5: {}
+ wordwrap@1.0.0: {}
+
wrap-ansi@6.2.0:
dependencies:
ansi-styles: 4.3.0
diff --git a/src/modules/app/app.controller.spec.ts b/src/modules/app/app.controller.spec.ts
deleted file mode 100644
index 169b786..0000000
--- a/src/modules/app/app.controller.spec.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Test, TestingModule } from '@nestjs/testing';
-import { AppController } from './app.controller';
-
-describe('AppController', () => {
- let appController: AppController;
-
- beforeEach(async () => {
- const app: TestingModule = await Test.createTestingModule({
- controllers: [AppController],
- }).compile();
-
- appController = app.get(AppController);
- });
-
- describe('root', () => {
- it('should return "Hello World!"', () => {
- expect(appController.getHello()).toBe('Hello World!');
- });
- });
-});
diff --git a/src/modules/app/app.controller.ts b/src/modules/app/app.controller.ts
deleted file mode 100644
index eb0cb39..0000000
--- a/src/modules/app/app.controller.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Controller, Get } from '@nestjs/common';
-
-@Controller()
-export class AppController {
- constructor() {}
-
- @Get()
- getHello(): string {
- return 'Hello World!';
- }
-}
diff --git a/src/modules/app/app.module.ts b/src/modules/app/app.module.ts
index c55df26..0e01a2c 100644
--- a/src/modules/app/app.module.ts
+++ b/src/modules/app/app.module.ts
@@ -1,13 +1,22 @@
import { Module } from '@nestjs/common';
-import { AppController } from './app.controller';
import { ConfigModule } from '@libs/config';
import { DatabaseModule } from '@libs/database';
import { ConfigService } from '@nestjs/config';
import * as schema from '../../shared/entities';
import { APP_FILTER, APP_PIPE } from '@nestjs/core';
-import { ZodValidationPipe, ZodValidationException } from 'nestjs-zod';
+import { ZodValidationPipe } from 'nestjs-zod';
import { PrometheusModule } from '@willsoto/nestjs-prometheus';
import { HealthModule } from '@libs/health';
+import { UserModule } from '../user';
+import { GlobalExceptionFilter } from 'src/shared/error';
+import { AuthModule } from '../auth';
+import { BullBoardModule } from '@bull-board/nestjs';
+import { FastifyAdapter } from '@bull-board/fastify';
+import { MailProcessor } from 'src/shared/workers';
+import { BullModule } from '@nestjs/bullmq';
+import { MailAdapter } from 'src/shared/adapters/mail';
+import { S3Module } from '@libs/s3';
+import { MigrationService } from 'src/shared/migration';
@Module({
imports: [
@@ -31,17 +40,54 @@ import { HealthModule } from '@libs/health';
};
},
}),
+ S3Module.registerAsync({
+ inject: [ConfigService],
+ global: true,
+ useFactory: (cfg: ConfigService) => ({
+ connection: {
+ bucket: cfg.getOrThrow('S3_BUCKET_NAME'),
+ endpoint: cfg.getOrThrow('S3_ENDPOINT'),
+ region: cfg.getOrThrow('S3_REGION'),
+ credentials: {
+ accessKeyId: cfg.getOrThrow('S3_ACCESS_KEY'),
+ secretAccessKey: cfg.getOrThrow('S3_SECRET_KEY'),
+ },
+ },
+ // FOR MINIO COMPARTABLE
+ config: { forcePathStyle: true },
+ }),
+ }),
+ BullModule.forRootAsync({
+ inject: [ConfigService],
+ useFactory: (cfg: ConfigService) => ({
+ connection: {
+ host: cfg.getOrThrow('REDIS_HOST'),
+ port: cfg.get('REDIS_PORT'),
+ },
+ }),
+ }),
+ AuthModule,
+ UserModule,
+ BullBoardModule.forRoot({
+ route: '/queues',
+ adapter: FastifyAdapter,
+ }),
HealthModule.register('gateway'),
],
- controllers: [AppController],
providers: [
+ MigrationService,
+ {
+ provide: 'IMailPort',
+ useClass: MailAdapter,
+ },
+ MailProcessor,
{
provide: APP_PIPE,
useClass: ZodValidationPipe,
},
{
provide: APP_FILTER,
- useClass: ZodValidationException,
+ useClass: GlobalExceptionFilter,
},
],
})
diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts
new file mode 100644
index 0000000..1b8555d
--- /dev/null
+++ b/src/modules/auth/auth.module.ts
@@ -0,0 +1,75 @@
+import { Module, forwardRef } from '@nestjs/common';
+import { UserModule } from '../user';
+import { AuthController } from './controller';
+import { AuthService, TokenService } from './services';
+import { JwtModule } from '@nestjs/jwt';
+import { ConfigService } from '@nestjs/config';
+import { RedisModule } from '@nestjs-modules/ioredis';
+import { SessionRepository } from './repository';
+import { BearerStrategy, CookieStrategy } from './strategies';
+import { BullModule } from '@nestjs/bullmq';
+import { Queues } from 'src/shared/workers';
+import { BullBoardModule } from '@bull-board/nestjs';
+import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
+
+@Module({
+ imports: [
+ JwtModule.registerAsync({
+ inject: [ConfigService],
+ useFactory: async (cfg: ConfigService) => ({
+ secret: cfg.get('JWT_ACCESS_SECRET'),
+ signOptions: {
+ /**
+ * Использование 'any' здесь необходимо, так как Zod гарантирует
+ * формат строки (напр. '15m', '30d') через regex в ConfigSchema, но внутренний тип
+ * 'StringValue' из библиотеки 'ms' слишком строг для обычного string.
+ */
+ expiresIn: cfg.get('JWT_ACCESS_EXPIRES_IN'),
+ algorithm: 'HS256',
+ },
+ verifyOptions: {
+ algorithms: ['HS256'],
+ ignoreExpiration: false,
+ clockTolerance: 10,
+ },
+ }),
+ }),
+ RedisModule.forRootAsync({
+ inject: [ConfigService],
+ useFactory: async (cfg: ConfigService) => {
+ const host = cfg.getOrThrow('REDIS_HOST', { infer: true });
+ const port = cfg.get('REDIS_PORT');
+ const url = `redis://${host}${port ? `:${port}` : ''}`;
+
+ return {
+ type: 'single',
+ url,
+ options: {
+ retryStrategy(times) {
+ return Math.min(times * 50, 2000);
+ },
+ commandTimeout: 3000,
+ },
+ };
+ },
+ }),
+ BullModule.registerQueue({
+ name: Queues.MAIL,
+ }),
+ BullBoardModule.forFeature({
+ name: Queues.MAIL,
+ adapter: BullMQAdapter,
+ }),
+ forwardRef(() => UserModule),
+ ],
+ controllers: [AuthController],
+ providers: [
+ AuthService,
+ TokenService,
+ CookieStrategy,
+ BearerStrategy,
+ { provide: 'ISessionRepository', useClass: SessionRepository },
+ ],
+ exports: [],
+})
+export class AuthModule {}
diff --git a/src/modules/auth/controller/auth.controller.ts b/src/modules/auth/controller/auth.controller.ts
new file mode 100644
index 0000000..acb1689
--- /dev/null
+++ b/src/modules/auth/controller/auth.controller.ts
@@ -0,0 +1,126 @@
+import { ApiBaseController } from '../../../shared/decorators';
+import { Body, HttpCode, Post, Req, Res, UseGuards } from '@nestjs/common';
+import { AuthService } from '../services/auth.service';
+import {
+ PostLoginSwagger,
+ PostLogoutSwagger,
+ PostPasswordResetConfirmSwagger,
+ PostPasswordResetSwagger,
+ PostPasswordResetVerifySwagger,
+ PostRefreshSwagger,
+ PostRegisterSwagger,
+ PostSignUpConfirmSwagger,
+} from './auth.swagger';
+import {
+ PasswordResetConfirmDto,
+ ResetPasswordDto,
+ SignInDto,
+ SignUpDto,
+ VerifyDto,
+ VerifyResetCodeDto,
+} from '../dtos';
+import type { FastifyReply, FastifyRequest } from 'fastify';
+import { getDeviceMeta } from '../helpers';
+import { BearerAuthGuard, CookieAuthGuard } from 'src/shared/guards';
+
+@ApiBaseController('auth', 'Auth')
+export class AuthController {
+ constructor(private readonly facade: AuthService) {}
+
+ @Post('sign-up')
+ @PostRegisterSwagger()
+ @HttpCode(202)
+ async signUp(@Body() dto: SignUpDto) {
+ return this.facade.signUp(dto);
+ }
+
+ @Post('sign-up/confirm')
+ @PostSignUpConfirmSwagger()
+ @HttpCode(201)
+ async verify(
+ @Res({ passthrough: true }) res: FastifyReply,
+ @Req() req: FastifyRequest,
+ @Body() dto: VerifyDto,
+ ) {
+ const meta = getDeviceMeta(req);
+ const { tokens, ...response } = await this.facade.verify(dto, meta);
+
+ res.setCookie('refresh', tokens.refresh, {
+ httpOnly: true,
+ secure: false,
+ path: '/',
+ sameSite: 'lax',
+ });
+
+ return { ...response, token: tokens.access };
+ }
+
+ @Post('sign-in')
+ @PostLoginSwagger()
+ async signIn(
+ @Res({ passthrough: true }) res: FastifyReply,
+ @Req() req: FastifyRequest,
+ @Body() dto: SignInDto,
+ ) {
+ const meta = getDeviceMeta(req);
+ const { tokens, ...response } = await this.facade.signIn(dto, meta);
+
+ res.setCookie('refresh', tokens.refresh, {
+ httpOnly: true,
+ secure: false,
+ path: '/',
+ sameSite: 'lax',
+ });
+
+ return { ...response, token: tokens.access };
+ }
+
+ @Post('sign-out')
+ @UseGuards(BearerAuthGuard)
+ @PostLogoutSwagger()
+ async logout(@Res({ passthrough: true }) res: FastifyReply, @Req() req: FastifyRequest) {
+ const session = req.cookies['refresh'];
+ const response = await this.facade.signOut(session);
+
+ res.clearCookie('refresh', { path: '/' });
+
+ return response;
+ }
+
+ @Post('refresh')
+ @UseGuards(CookieAuthGuard)
+ @PostRefreshSwagger()
+ @HttpCode(200)
+ async refresh(@Res({ passthrough: true }) res: FastifyReply, @Req() req: FastifyRequest) {
+ const meta = getDeviceMeta(req);
+ const session = req.cookies['refresh'];
+ const { tokens, ...response } = await this.facade.refresh(session, meta);
+
+ res.setCookie('refresh', tokens.refresh, {
+ httpOnly: true,
+ secure: false,
+ path: '/',
+ sameSite: 'lax',
+ });
+
+ return { token: tokens.access, ...response };
+ }
+
+ @Post('password/reset')
+ @PostPasswordResetSwagger()
+ async resetPasswordRequest(@Body() dto: ResetPasswordDto) {
+ return this.facade.resetPass(dto);
+ }
+
+ @Post('password/reset/verify')
+ @PostPasswordResetVerifySwagger()
+ async verifyResetCode(@Body() dto: VerifyResetCodeDto) {
+ return this.facade.verifyResetPassword(dto);
+ }
+
+ @Post('password/reset/confirm')
+ @PostPasswordResetConfirmSwagger()
+ async confirmPasswordReset(@Body() dto: PasswordResetConfirmDto) {
+ return this.facade.confirmResetPass(dto);
+ }
+}
diff --git a/src/modules/auth/controller/auth.swagger.ts b/src/modules/auth/controller/auth.swagger.ts
new file mode 100644
index 0000000..49f4d72
--- /dev/null
+++ b/src/modules/auth/controller/auth.swagger.ts
@@ -0,0 +1,271 @@
+import { applyDecorators } from '@nestjs/common';
+import { ApiBody, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger';
+import {
+ ApiBadRequest,
+ ApiConflict,
+ ApiErrorResponse,
+ ApiForbidden,
+ ApiNotFound,
+ ApiUnauthorized,
+ ApiValidationError,
+} from 'src/shared/error';
+import {
+ ChangePasswordDto,
+ Confirm2FaDto,
+ Disable2FaDto,
+ PasswordResetConfirmDto,
+ ResetPasswordDto,
+ SignInDto,
+ SignUpDto,
+ VerifyDto,
+ VerifyResetCodeDto,
+} from '../dtos';
+import { ActionResponse } from 'src/shared/dtos';
+
+export const PostRegisterSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Регистрация нового пользователя',
+ description: 'Создает пользователя, базовые настройки безопасности и уведомлений.',
+ }),
+ ApiBody({ type: SignUpDto.Output }),
+ ApiResponse({
+ status: 201,
+ description: 'Пользователь успешно зарегистрирован.',
+ type: ActionResponse.Output,
+ }),
+ ApiValidationError('Ошибка валидации данных (например, неверный формат email)'),
+ ApiConflict('Пользователь с таким email уже существует'),
+ );
+
+export const PostLoginSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Вход в систему',
+ description:
+ 'Возвращает Access/Refresh токены. Если у пользователя включена 2FA, вернет временный токен.',
+ }),
+ ApiBody({ type: SignInDto.Output }),
+ ApiResponse({
+ status: 200,
+ description: 'Успешный вход.',
+ schema: {
+ example: {
+ success: true,
+ message: false,
+ token: 'eyJhbGciOiJIUzI1NiIsInR5c...',
+ },
+ },
+ }),
+ ApiBadRequest('Неверный формат email'),
+ ApiUnauthorized('Неверный email или пароль'),
+ );
+
+export const PostRefreshSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Обновление токенов',
+ description: 'Выдает новую пару Access и Refresh токенов.',
+ }),
+ ApiResponse({
+ status: 200,
+ description: 'Токены успешно обновлены.',
+ schema: {
+ example: {
+ success: true,
+ token: 'eyJhbGciOiJIUzI1NiIsInR5c...',
+ message: 'def50200508a1768c7e...',
+ },
+ },
+ }),
+ ApiBadRequest('Ошибка валидации (не передан refresh токен)'),
+ ApiUnauthorized('Refresh токен недействителен, истек или отозван'),
+ );
+
+export const PostLogoutSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Выход из системы',
+ description: 'Удаляет текущую сессию пользователя из Redis.',
+ }),
+ ApiResponse({ status: 200, description: 'Успешный выход.', type: ActionResponse.Output }),
+ ApiUnauthorized(),
+ );
+
+export const PostSignUpConfirmSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Подтверждение регистрации по коду',
+ description:
+ 'Проверяет OTP из письма, создаёт аккаунт, выдаёт access-токен в теле ответа и устанавливает refresh в httpOnly cookie.',
+ }),
+ ApiBody({ type: VerifyDto.Output }),
+ ApiResponse({
+ status: 201,
+ description: 'Аккаунт подтверждён, сессия создана.',
+ schema: {
+ example: {
+ success: true,
+ message: 'Аккаунт успешно подтвержден',
+ token: 'eyJhbGciOiJIUzI1NiIsInR5c...',
+ },
+ },
+ }),
+ ApiValidationError('Ошибка валидации (неверный формат email или длина кода)'),
+ ApiBadRequest('Срок регистрации истёк или сессия не найдена'),
+ ApiBadRequest('Неверный или истёкший код подтверждения'),
+ );
+
+export const PostPasswordResetSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Запрос кода восстановления пароля',
+ description: 'Отправляет одноразовый код на email, если пользователь существует.',
+ }),
+ ApiBody({ type: ResetPasswordDto.Output }),
+ ApiResponse({
+ status: 201,
+ description: 'Код отправлен на почту (при успешной обработке запроса).',
+ type: ActionResponse.Output,
+ }),
+ ApiValidationError('Некорректный формат email'),
+ ApiErrorResponse(
+ 422,
+ 'INVALID_EMAIL_FORMAT',
+ 'Указанный email адрес имеет некорректный формат',
+ ),
+ ApiNotFound('Пользователь с таким email не найден'),
+ );
+
+export const PostPasswordResetVerifySwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Проверка кода восстановления пароля',
+ description: 'Проверяет код из письма и помечает сессию сброса как подтверждённую.',
+ }),
+ ApiBody({ type: VerifyResetCodeDto.Output }),
+ ApiResponse({
+ status: 201,
+ description: 'Код подтверждён, можно задать новый пароль.',
+ type: ActionResponse.Output,
+ }),
+ ApiValidationError('Ошибка валидации (email или формат кода)'),
+ ApiBadRequest('Время подтверждения истекло или запрос не найден'),
+ ApiBadRequest('Неверный или истёкший код подтверждения'),
+ );
+
+export const PostPasswordResetConfirmSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Установка нового пароля после сброса',
+ description: 'Доступно только после успешной проверки кода на шаге verify.',
+ }),
+ ApiBody({ type: PasswordResetConfirmDto.Output }),
+ ApiResponse({
+ status: 201,
+ description: 'Пароль успешно изменён.',
+ type: ActionResponse.Output,
+ }),
+ ApiValidationError('Ошибка валидации (пароли не совпадают или неверная длина)'),
+ ApiBadRequest('Сессия восстановления не найдена или истекла'),
+ ApiForbidden(),
+ ApiErrorResponse(
+ 500,
+ 'PASSWORD_UPDATE_FAILED',
+ 'Не удалось обновить пароль. Попробуйте позже.',
+ ),
+ );
+
+export const GetSessionsSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Получить активные сессии',
+ description: 'Возвращает список всех активных устройств/сессий пользователя.',
+ }),
+ ApiResponse({
+ status: 200,
+ description: 'Список сессий успешно получен.',
+ schema: {
+ example: [
+ {
+ id: 'clj1xyz990000abc1',
+ device: 'Chrome on macOS',
+ ip: '192.168.1.1',
+ lastActive: '2026-04-11T14:30:00.000Z',
+ isCurrent: true,
+ },
+ ],
+ },
+ }),
+ ApiUnauthorized(),
+ );
+
+export const DeleteSessionSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Завершить чужую сессию',
+ description: 'Принудительно удаляет указанную сессию из Redis.',
+ }),
+ ApiParam({ name: 'cuid', description: 'ID сессии, которую нужно завершить' }),
+ ApiResponse({ status: 200, description: 'Сессия успешно завершена.' }),
+ ApiUnauthorized(),
+ ApiForbidden(),
+ ApiNotFound('Сессия не найдена или уже истекла'),
+ );
+
+export const PostChangePasswordSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Смена пароля',
+ description: 'Требует текущий и новый пароль. Инвалидирует все остальные сессии.',
+ }),
+ ApiBody({ type: ChangePasswordDto.Output }),
+ ApiResponse({ status: 200, description: 'Пароль успешно изменен.' }),
+ ApiBadRequest('Неверный старый пароль'),
+ ApiUnauthorized(),
+ );
+
+export const PostEnable2faSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Генерация QR-кода для 2FA',
+ description: 'Создает секрет и возвращает ссылку (otpauth) для Google Authenticator.',
+ }),
+ ApiResponse({
+ status: 200,
+ description: 'QR-код сгенерирован.',
+ schema: {
+ example: {
+ secret: 'JBSWY3DPEHPK3PXP',
+ qrCodeUrl:
+ 'otpauth://totp/TaskTracker:alexey?secret=JBSWY3DPEHPK3PXP&issuer=TaskTracker',
+ },
+ },
+ }),
+ ApiUnauthorized(),
+ );
+
+export const PostDisable2faSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Подтверждение включения 2FA',
+ description: 'Проверяет первый код из приложения для окончательной активации 2FA.',
+ }),
+ ApiBody({ type: Confirm2FaDto.Output }),
+ ApiResponse({ status: 200, description: 'Двухфакторная аутентификация успешно включена.' }),
+ ApiBadRequest('Неверный код подтверждения'),
+ ApiUnauthorized(),
+ );
+
+export const PostConfirm2faSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Отключение 2FA',
+ description:
+ 'Отключает двухфакторную аутентификацию (требует подтверждения паролем или текущим кодом).',
+ }),
+ ApiBody({ type: Disable2FaDto.Output }),
+ ApiResponse({ status: 200, description: '2FA успешно отключена.' }),
+ ApiBadRequest('Неверный код или пароль для отключения'),
+ ApiUnauthorized(),
+ );
diff --git a/src/modules/auth/controller/index.ts b/src/modules/auth/controller/index.ts
new file mode 100644
index 0000000..74c6815
--- /dev/null
+++ b/src/modules/auth/controller/index.ts
@@ -0,0 +1 @@
+export { AuthController } from './auth.controller';
diff --git a/src/modules/auth/dtos/2fa.dto.ts b/src/modules/auth/dtos/2fa.dto.ts
new file mode 100644
index 0000000..8d10068
--- /dev/null
+++ b/src/modules/auth/dtos/2fa.dto.ts
@@ -0,0 +1,25 @@
+import z from 'zod/v4';
+import { createZodDto } from 'nestjs-zod';
+
+export const Confirm2FaSchema = z
+ .object({
+ code: z
+ .string()
+ .length(6, 'Код должен состоять ровно из 6 символов')
+ .describe('6-значный код из Google Authenticator'),
+ })
+ .describe('Схема подтверждения 2FA');
+
+export class Confirm2FaDto extends createZodDto(Confirm2FaSchema) {}
+
+export const Disable2FaSchema = z
+ .object({
+ password: z.string().optional().describe('Текущий пароль для подтверждения (опционально)'),
+ code: z.string().optional().describe('Код из приложения (опционально)'),
+ })
+ .refine((data) => data.password || data.code, {
+ message: 'Нужно передать либо пароль, либо код',
+ })
+ .describe('Схема отключения 2FA');
+
+export class Disable2FaDto extends createZodDto(Disable2FaSchema) {}
diff --git a/src/modules/auth/dtos/auth.dto.ts b/src/modules/auth/dtos/auth.dto.ts
new file mode 100644
index 0000000..5aeac05
--- /dev/null
+++ b/src/modules/auth/dtos/auth.dto.ts
@@ -0,0 +1,58 @@
+import { createZodDto } from 'nestjs-zod';
+import { z } from 'zod/v4';
+
+export const SignInSchema = z
+ .object({
+ email: z.email('Некорректный формат email').describe('Email пользователя'),
+ password: z.string().describe('Пароль пользователя'),
+ })
+ .describe('Схема входа в систему');
+
+export class SignInDto extends createZodDto(SignInSchema) {}
+
+export const SignUpSchema = z
+ .object({
+ email: z.email('Некорректный формат email').describe('Email пользователя'),
+ password: z
+ .string()
+ .min(8, 'Пароль должен содержать минимум 8 символов')
+ .max(32, 'Пароль должен содержать максимум 32 символа')
+ .describe('Пароль (минимум 8 символов)'),
+ firstName: z
+ .string()
+ .min(2, 'Имя должно содержать минимум 2 символа')
+ .max(50)
+ .trim()
+ .describe('Имя'),
+ lastName: z
+ .string()
+ .min(2, 'Фамилия должна содержать минимум 2 символа')
+ .max(50)
+ .trim()
+ .describe('Фамилия'),
+ middleName: z
+ .string()
+ .max(50)
+ .trim()
+ .optional()
+ .or(z.literal(''))
+ .describe('Отчество (опционально)'),
+ })
+ .describe('Схема регистрации пользователя');
+
+export class SignUpDto extends createZodDto(SignUpSchema) {}
+
+export const VerifySchema = z
+ .object({
+ email: z
+ .string()
+ .email('Некорректный формат email')
+ .describe('Email пользователя, на который был отправлен код'),
+ code: z
+ .string()
+ .length(6, 'Код должен содержать ровно 6 символов')
+ .describe('6-значный OTP код подтверждения'),
+ })
+ .describe('Схема верификации OTP кода');
+
+export class VerifyDto extends createZodDto(VerifySchema) {}
diff --git a/src/modules/auth/dtos/index.ts b/src/modules/auth/dtos/index.ts
new file mode 100644
index 0000000..6a0829f
--- /dev/null
+++ b/src/modules/auth/dtos/index.ts
@@ -0,0 +1,3 @@
+export * from './auth.dto';
+export * from './2fa.dto';
+export * from './password.dto';
diff --git a/src/modules/auth/dtos/password.dto.ts b/src/modules/auth/dtos/password.dto.ts
new file mode 100644
index 0000000..e0a260f
--- /dev/null
+++ b/src/modules/auth/dtos/password.dto.ts
@@ -0,0 +1,45 @@
+import { createZodDto } from 'nestjs-zod';
+import z from 'zod/v4';
+
+export const ChangePasswordSchema = z
+ .object({
+ oldPassword: z.string().describe('Текущий пароль'),
+ newPassword: z
+ .string()
+ .min(8, 'Новый пароль должен содержать минимум 8 символов')
+ .max(32, 'Новый пароль должен содержать максимум 32 символа')
+ .describe('Новый пароль (минимум 8 символов)'),
+ })
+ .describe('Схема смены пароля');
+
+export class ChangePasswordDto extends createZodDto(ChangePasswordSchema) {}
+
+export const ResetPasswordSchema = z.object({
+ email: z.string().email('Некорректный формат email').describe('Email для восстановления'),
+});
+
+export class ResetPasswordDto extends createZodDto(ResetPasswordSchema) {}
+
+export const VerifyResetCodeSchema = z.object({
+ email: z.string().email(),
+ code: z.string().length(6, 'Код должен содержать 6 цифр').describe('Код из письма'),
+});
+
+export class VerifyResetCodeDto extends createZodDto(VerifyResetCodeSchema) {}
+
+export const PasswordResetConfirmSchema = z
+ .object({
+ email: z.string().email(),
+ password: z
+ .string()
+ .min(8, 'Минимум 8 символов')
+ .max(32, 'Максимум 32 символа')
+ .describe('Новый пароль'),
+ confirmPassword: z.string().describe('Повторите новый пароль'),
+ })
+ .refine((data) => data.password === data.confirmPassword, {
+ message: 'Пароли не совпадают',
+ path: ['confirmPassword'],
+ });
+
+export class PasswordResetConfirmDto extends createZodDto(PasswordResetConfirmSchema) {}
diff --git a/src/modules/auth/entities/index.ts b/src/modules/auth/entities/index.ts
new file mode 100644
index 0000000..5330080
--- /dev/null
+++ b/src/modules/auth/entities/index.ts
@@ -0,0 +1 @@
+export { sessions } from './session.entity';
diff --git a/src/modules/auth/entities/session.entity.ts b/src/modules/auth/entities/session.entity.ts
new file mode 100644
index 0000000..5b7414d
--- /dev/null
+++ b/src/modules/auth/entities/session.entity.ts
@@ -0,0 +1,25 @@
+import { createId } from '@paralleldrive/cuid2';
+import { text, timestamp, varchar } from 'drizzle-orm/pg-core';
+import { boolean } from 'drizzle-orm/pg-core';
+import { baseSchema } from 'src/shared/entities';
+import { users } from '../../user/entities';
+
+export const sessions = baseSchema.table('sessions', {
+ id: text('id')
+ .primaryKey()
+ .$defaultFn(() => createId()),
+ userId: text('user_id')
+ .references(() => users.id, { onDelete: 'cascade' })
+ .notNull(),
+ deviceType: varchar('device_type', { length: 20 }).$type<'mobile' | 'desktop' | 'tablet'>(),
+ browser: varchar('browser', { length: 50 }),
+ os: varchar('os', { length: 50 }),
+ userAgent: text('user_agent').notNull(),
+ ip: varchar('ip', { length: 45 }).notNull(),
+ city: varchar('city', { length: 100 }),
+ countryCode: varchar('country_code', { length: 5 }),
+ createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
+ updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
+ expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
+ isRevoked: boolean('is_revoked').default(false).notNull(),
+});
diff --git a/src/modules/auth/helpers/get-device-meta.ts b/src/modules/auth/helpers/get-device-meta.ts
new file mode 100644
index 0000000..b37f69e
--- /dev/null
+++ b/src/modules/auth/helpers/get-device-meta.ts
@@ -0,0 +1,30 @@
+import type { FastifyRequest } from 'fastify';
+import { UAParser } from 'ua-parser-js';
+
+export interface DeviceMetadata {
+ ip: string;
+ userAgent: string;
+ browser: string;
+ os: string;
+ deviceType: 'mobile' | 'desktop' | 'tablet';
+}
+
+export function getDeviceMeta(req: FastifyRequest): DeviceMetadata {
+ const uaString = req.headers['user-agent'] || '';
+ const parser = new UAParser(uaString);
+ const res = parser.getResult();
+
+ const ip = (req.headers['x-forwarded-for'] as string)?.split(',')[0] || req.ip || '0.0.0.0';
+
+ let deviceType: 'mobile' | 'desktop' | 'tablet' = 'desktop';
+ if (res.device.type === 'mobile') deviceType = 'mobile';
+ if (res.device.type === 'tablet') deviceType = 'tablet';
+
+ return {
+ ip,
+ userAgent: uaString,
+ browser: `${res.browser.name || 'Unknown'} ${res.browser.version || ''}`.trim(),
+ os: `${res.os.name || 'Unknown'} ${res.os.version || ''}`.trim(),
+ deviceType,
+ };
+}
diff --git a/src/modules/auth/helpers/index.ts b/src/modules/auth/helpers/index.ts
new file mode 100644
index 0000000..1740a4d
--- /dev/null
+++ b/src/modules/auth/helpers/index.ts
@@ -0,0 +1 @@
+export { type DeviceMetadata, getDeviceMeta } from './get-device-meta';
diff --git a/src/modules/auth/index.ts b/src/modules/auth/index.ts
new file mode 100644
index 0000000..faa5c33
--- /dev/null
+++ b/src/modules/auth/index.ts
@@ -0,0 +1 @@
+export { AuthModule } from './auth.module';
diff --git a/src/modules/auth/repository/index.ts b/src/modules/auth/repository/index.ts
new file mode 100644
index 0000000..f1ead53
--- /dev/null
+++ b/src/modules/auth/repository/index.ts
@@ -0,0 +1,2 @@
+export * from './session.repository.interface';
+export { SessionRepository } from './session.repository';
diff --git a/src/modules/auth/repository/session.repository.interface.ts b/src/modules/auth/repository/session.repository.interface.ts
new file mode 100644
index 0000000..ede9fc5
--- /dev/null
+++ b/src/modules/auth/repository/session.repository.interface.ts
@@ -0,0 +1,13 @@
+import { sessions } from '../entities';
+
+export type SessionInsert = typeof sessions.$inferInsert;
+export type SessionSelect = typeof sessions.$inferSelect;
+
+export interface ISessionRepository {
+ create(data: SessionInsert): Promise;
+ findById(id: string): Promise;
+ findAllByUserId(userId: string): Promise;
+ revoke(id: string): Promise;
+ revokeAllByUserId(userId: string, exceptSessionId?: string): Promise;
+ deleteExpired(): Promise;
+}
diff --git a/src/modules/auth/repository/session.repository.ts b/src/modules/auth/repository/session.repository.ts
new file mode 100644
index 0000000..be4ba1c
--- /dev/null
+++ b/src/modules/auth/repository/session.repository.ts
@@ -0,0 +1,68 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { eq, and, ne, lt, desc } from 'drizzle-orm';
+import * as schema from '../entities';
+import { DATABASE_SERVICE, DatabaseService } from '@libs/database';
+import {
+ ISessionRepository,
+ type SessionInsert,
+ SessionSelect,
+} from './session.repository.interface';
+
+@Injectable()
+export class SessionRepository implements ISessionRepository {
+ constructor(
+ @Inject(DATABASE_SERVICE)
+ private readonly db: DatabaseService,
+ ) {}
+
+ async create(data: SessionInsert): Promise {
+ const [result] = await this.db.insert(schema.sessions).values(data).returning();
+ return result;
+ }
+
+ async findById(id: string): Promise {
+ const [result] = await this.db
+ .select()
+ .from(schema.sessions)
+ .where(and(eq(schema.sessions.id, id), eq(schema.sessions.isRevoked, false)))
+ .limit(1);
+
+ return result || null;
+ }
+
+ async findAllByUserId(userId: string): Promise {
+ return this.db
+ .select()
+ .from(schema.sessions)
+ .where(and(eq(schema.sessions.userId, userId), eq(schema.sessions.isRevoked, false)))
+ .orderBy(desc(schema.sessions.createdAt));
+ }
+
+ async revoke(id: string): Promise {
+ await this.db
+ .update(schema.sessions)
+ .set({ isRevoked: true, updatedAt: new Date() })
+ .where(eq(schema.sessions.id, id));
+ }
+
+ async revokeAllByUserId(userId: string, exceptSessionId?: string): Promise {
+ const filters = [eq(schema.sessions.userId, userId)];
+
+ if (exceptSessionId) {
+ filters.push(ne(schema.sessions.id, exceptSessionId));
+ }
+
+ await this.db
+ .update(schema.sessions)
+ .set({ isRevoked: true, updatedAt: new Date() })
+ .where(and(...filters));
+ }
+
+ async deleteExpired(): Promise {
+ const result = await this.db
+ .delete(schema.sessions)
+ .where(lt(schema.sessions.expiresAt, new Date()));
+
+ return result.rowCount;
+ }
+}
diff --git a/src/modules/auth/services/auth.service.ts b/src/modules/auth/services/auth.service.ts
new file mode 100644
index 0000000..093b1f3
--- /dev/null
+++ b/src/modules/auth/services/auth.service.ts
@@ -0,0 +1,389 @@
+import {
+ BadRequestException,
+ ConflictException,
+ ForbiddenException,
+ Inject,
+ Injectable,
+ InternalServerErrorException,
+ NotFoundException,
+ UnauthorizedException,
+ UnprocessableEntityException,
+} from '@nestjs/common';
+import { InjectRedis } from '@nestjs-modules/ioredis';
+import Redis from 'ioredis';
+import {
+ PasswordResetConfirmDto,
+ ResetPasswordDto,
+ SignInDto,
+ SignUpDto,
+ VerifyDto,
+ VerifyResetCodeDto,
+} from '../dtos';
+import { validate } from 'email-validator';
+import { generate, generateSecret, verify as verifyOTP } from 'otplib';
+import * as argon from 'argon2';
+import { CreateUserCommand, FindOneUserCommand, UpdatePassUserCommand } from '../../user';
+import { TokenService } from './token.service';
+import { ISessionRepository } from '../repository';
+import { DeviceMetadata } from '../helpers';
+import { InjectQueue } from '@nestjs/bullmq';
+import { Queues, RegisterCodeEvent } from 'src/shared/workers';
+import type { Queue } from 'bullmq';
+import { MailJobs } from 'src/shared/workers/enum';
+import { ResetPasswordEvent } from 'src/shared/workers/events';
+
+@Injectable()
+export class AuthService {
+ constructor(
+ @InjectRedis()
+ private readonly redis: Redis,
+ @Inject('ISessionRepository')
+ private readonly sessionRepo: ISessionRepository,
+ @InjectQueue(Queues.MAIL)
+ private readonly mailQueue: Queue,
+ private readonly tokenService: TokenService,
+ private readonly findUserCommand: FindOneUserCommand,
+ private readonly createUserCommand: CreateUserCommand,
+ private readonly updateUserPass: UpdatePassUserCommand,
+ ) {}
+
+ public signUp = async (dto: SignUpDto) => {
+ const isValidEmail = validate(dto.email);
+
+ if (!isValidEmail) {
+ throw new UnprocessableEntityException({
+ code: 'INVALID_EMAIL_FORMAT',
+ message: 'Указанный email адрес имеет некорректный формат',
+ details: { email: dto.email },
+ });
+ }
+
+ const isExists = await this.findUserCommand.execute({ email: dto.email });
+
+ if (isExists) {
+ throw new ConflictException({
+ code: 'USER_ALREADY_EXISTS',
+ message: 'Email уже занят другим аккаунтом',
+ details: { email: dto.email },
+ });
+ }
+
+ const hashPass = await argon.hash(dto.password);
+
+ const secret = generateSecret();
+ const token = await generate({
+ secret,
+ algorithm: 'sha256',
+ digits: 6,
+ period: 900,
+ strategy: 'totp',
+ });
+
+ const data = {
+ user: dto,
+ password: hashPass,
+ otp: { token, secret },
+ };
+
+ await this.redis.set(`reg:${dto.email}`, JSON.stringify(data), 'EX', 900);
+
+ const event = new RegisterCodeEvent(dto.email, dto.firstName, token);
+ await this.mailQueue.add(MailJobs.SEND_REGISTER_CODE, event, {
+ attempts: 3,
+ backoff: {
+ type: 'exponential',
+ delay: 5000,
+ },
+ });
+
+ return {
+ success: true,
+ message: 'Код подтверждения отправлен на вашу почту',
+ };
+ };
+
+ public verify = async (dto: VerifyDto, meta: DeviceMetadata) => {
+ const redisKey = `reg:${dto.email}`;
+
+ const cachedData = await this.redis.get(redisKey);
+
+ if (!cachedData) {
+ throw new BadRequestException({
+ code: 'REGISTRATION_EXPIRED',
+ message: 'Срок регистрации истек или email не найден. Попробуйте снова.',
+ });
+ }
+
+ const userData = JSON.parse(cachedData);
+
+ const verifyResult = await verifyOTP({
+ token: dto.code,
+ secret: userData.otp.secret,
+ algorithm: 'sha256',
+ digits: 6,
+ period: 900,
+ strategy: 'totp',
+ });
+
+ if (!verifyResult.valid) {
+ throw new BadRequestException({
+ code: 'INVALID_OTP',
+ message: 'Неверный или истекший код подтверждения',
+ });
+ }
+
+ const user = await this.createUserCommand.execute({
+ ...userData.user,
+ password: userData.password,
+ });
+
+ const session = await this.sessionRepo.create({
+ userId: user.id,
+ expiresAt: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000),
+ ...meta,
+ });
+ const { access, refresh } = await this.tokenService.generateTokens(user, session.id);
+
+ await this.redis.del(redisKey);
+
+ return {
+ success: true,
+ tokens: { access, refresh },
+ message: 'Аккаунт успешно подтвержден',
+ };
+ };
+
+ public signIn = async (dto: SignInDto, meta: DeviceMetadata) => {
+ const { user, security } = await this.findUserCommand.execute({ email: dto.email });
+
+ if (!user || !security) {
+ throw new UnauthorizedException({
+ code: 'INVALID_CREDENTIALS',
+ message: 'Неверный email или пароль',
+ });
+ }
+
+ const isPasswordValid = await argon.verify(security.passwordHash, dto.password);
+
+ if (!isPasswordValid) {
+ throw new UnauthorizedException({
+ code: 'INVALID_CREDENTIALS',
+ message: 'Неверный email или пароль',
+ });
+ }
+
+ const { id } = await this.sessionRepo.create({
+ userId: user.id,
+ expiresAt: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000),
+ ...meta,
+ });
+
+ const { access, refresh } = await this.tokenService.generateTokens(user, id);
+
+ return {
+ success: true,
+ tokens: {
+ access,
+ refresh,
+ },
+ message: 'Вы успешно вошли в систему',
+ };
+ };
+
+ public refresh = async (token: string, metadata: DeviceMetadata) => {
+ const payload = await this.tokenService.validateToken(token, 'refresh');
+
+ if (!payload || !payload.jti) {
+ throw new UnauthorizedException({
+ code: 'INVALID_TOKEN',
+ message: 'Сессия недействительна или истекла',
+ });
+ }
+
+ const session = await this.sessionRepo.findById(payload.jti);
+
+ if (!session || session.isRevoked) {
+ throw new UnauthorizedException({
+ code: 'SESSION_REVOKED',
+ message: 'Ваша сессия была отозвана или завершена',
+ });
+ }
+
+ const { user } = await this.findUserCommand.execute({ id: session.userId });
+
+ if (!user) {
+ await this.sessionRepo.revoke(session.id);
+ throw new UnauthorizedException({
+ code: 'USER_NOT_FOUND',
+ message: 'Аккаунт пользователя не найден',
+ });
+ }
+
+ await this.sessionRepo.revoke(session.id);
+
+ const newSession = await this.sessionRepo.create({
+ userId: user.id,
+ ...metadata,
+ expiresAt: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000),
+ });
+
+ const { access, refresh } = await this.tokenService.generateTokens(user, newSession.id);
+
+ return {
+ tokens: { access, refresh },
+ success: true,
+ message: 'Токены успешно обновлены',
+ };
+ };
+
+ public signOut = async (token: string) => {
+ const payload = await this.tokenService.validateToken(token, 'refresh');
+
+ if (!payload?.jti) {
+ throw new UnauthorizedException({ code: 'SESSION_EXPIRED', message: 'Сессия истекла' });
+ }
+
+ const session = await this.sessionRepo.findById(payload.jti);
+
+ if (!session) {
+ throw new UnauthorizedException({
+ code: 'SESSION_NOT_FOUND',
+ message: 'Сессия не найдена',
+ });
+ }
+
+ await this.sessionRepo.revoke(session.id);
+
+ return { success: true, message: 'Успешно вышли из системы!' };
+ };
+
+ public resetPass = async (dto: ResetPasswordDto) => {
+ const isValidEmail = validate(dto.email);
+
+ if (!isValidEmail) {
+ throw new UnprocessableEntityException({
+ code: 'INVALID_EMAIL_FORMAT',
+ message: 'Указанный email адрес имеет некорректный формат',
+ details: { email: dto.email },
+ });
+ }
+
+ const { user } = await this.findUserCommand.execute({ email: dto.email });
+
+ if (!user) {
+ throw new NotFoundException({
+ code: 'USER_NOT_FOUND',
+ message: 'Пользователь с таким email не найден',
+ details: { email: dto.email },
+ });
+ }
+
+ const secret = generateSecret();
+ const token = await generate({
+ secret,
+ digits: 6,
+ period: 900,
+ strategy: 'totp',
+ });
+
+ const resetPayload = {
+ email: user.email,
+ otp: { secret, token },
+ isVerified: false,
+ };
+
+ await this.redis.set(`pass:reset:${dto.email}`, JSON.stringify(resetPayload), 'EX', 900);
+
+ const event = new ResetPasswordEvent(dto.email, token);
+ await this.mailQueue.add(MailJobs.SEND_RESET_PASSWORD, event, {
+ attempts: 3,
+ backoff: {
+ type: 'exponential',
+ delay: 5000,
+ },
+ });
+
+ return {
+ success: true,
+ message: 'Код для восстановления пароля отправлен на вашу почту',
+ };
+ };
+
+ public verifyResetPassword = async (dto: VerifyResetCodeDto) => {
+ const redisKey = `pass:reset:${dto.email}`;
+ const cachedData = await this.redis.get(redisKey);
+
+ if (!cachedData) {
+ throw new BadRequestException({
+ code: 'RESET_SESSION_EXPIRED',
+ message: 'Время подтверждения истекло или запрос не найден. Запросите код снова.',
+ });
+ }
+
+ const resetSession = JSON.parse(cachedData);
+
+ const verifyResult = await verifyOTP({
+ token: dto.code,
+ secret: resetSession.otp.secret,
+ digits: 6,
+ period: 900,
+ strategy: 'totp',
+ });
+
+ if (!verifyResult.valid) {
+ throw new BadRequestException({
+ code: 'INVALID_VERIFICATION_CODE',
+ message: 'Неверный или истекший код подтверждения',
+ });
+ }
+
+ await this.redis.set(
+ redisKey,
+ JSON.stringify({ ...resetSession, isVerified: true }),
+ 'EX',
+ 600,
+ );
+
+ return {
+ success: true,
+ message: 'Код успешно подтвержден. Теперь вы можете установить новый пароль.',
+ };
+ };
+
+ public confirmResetPass = async (dto: PasswordResetConfirmDto) => {
+ const redisKey = `pass:reset:${dto.email}`;
+ const cachedData = await this.redis.get(redisKey);
+
+ if (!cachedData) {
+ throw new BadRequestException({
+ code: 'RESET_SESSION_NOT_FOUND',
+ message: 'Сессия восстановления не найдена или истекла. Начните процесс заново.',
+ });
+ }
+
+ const resetSession = JSON.parse(cachedData);
+
+ if (!resetSession.isVerified) {
+ throw new ForbiddenException({
+ code: 'CODE_NOT_VERIFIED',
+ message: 'Код подтверждения еще не был верифицирован.',
+ });
+ }
+
+ const hashed = await argon.hash(dto.password);
+ const isUpdated = await this.updateUserPass.execute(dto.email, hashed);
+
+ if (!isUpdated) {
+ throw new InternalServerErrorException({
+ code: 'PASSWORD_UPDATE_FAILED',
+ message: 'Не удалось обновить пароль. Попробуйте позже.',
+ });
+ }
+ await this.redis.del(redisKey);
+
+ return {
+ success: true,
+ message: 'Пароль успешно изменен. Теперь вы можете войти в аккаунт.',
+ };
+ };
+}
diff --git a/src/modules/auth/services/index.ts b/src/modules/auth/services/index.ts
new file mode 100644
index 0000000..f39bab2
--- /dev/null
+++ b/src/modules/auth/services/index.ts
@@ -0,0 +1,2 @@
+export { AuthService } from './auth.service';
+export { TokenService } from './token.service';
diff --git a/src/modules/auth/services/token.service.ts b/src/modules/auth/services/token.service.ts
new file mode 100644
index 0000000..b61426c
--- /dev/null
+++ b/src/modules/auth/services/token.service.ts
@@ -0,0 +1,52 @@
+import { Injectable } from '@nestjs/common';
+import { JwtService } from '@nestjs/jwt';
+import { ConfigService } from '@nestjs/config';
+import { JwtPayload } from '../types';
+
+@Injectable()
+export class TokenService {
+ constructor(
+ private readonly jwtService: JwtService,
+ private readonly configService: ConfigService,
+ ) {}
+
+ async generateTokens(user: any, sessionId: string) {
+ const domain = this.configService.get('DOMAIN');
+
+ const payload = {
+ jti: sessionId,
+ sub: user.id,
+ email: user.email,
+ iss: btoa(domain),
+ // TODO: ADD TO ENV GLOBAL
+ aud: btoa('task-tracker-client'),
+ role: user.role,
+ };
+
+ const [access, refresh] = await Promise.all([
+ this.jwtService.signAsync(payload, {
+ secret: this.configService.get('JWT_ACCESS_SECRET'),
+ expiresIn: this.configService.get('JWT_ACCESS_EXPIRES_IN'),
+ }),
+ this.jwtService.signAsync(payload, {
+ secret: this.configService.get('JWT_REFRESH_SECRET'),
+ expiresIn: this.configService.get('JWT_REFRESH_EXPIRES_IN'),
+ }),
+ ]);
+
+ return { access, refresh };
+ }
+
+ async validateToken(token: string, type: 'access' | 'refresh'): Promise {
+ try {
+ const secret =
+ type === 'access'
+ ? this.configService.get('JWT_ACCESS_SECRET')
+ : this.configService.get('JWT_REFRESH_SECRET');
+
+ return this.jwtService.verifyAsync(token, { secret });
+ } catch (e) {
+ return null;
+ }
+ }
+}
diff --git a/src/modules/auth/strategies/bearer.strategy.ts b/src/modules/auth/strategies/bearer.strategy.ts
new file mode 100644
index 0000000..d7914ed
--- /dev/null
+++ b/src/modules/auth/strategies/bearer.strategy.ts
@@ -0,0 +1,21 @@
+import { Injectable } from '@nestjs/common';
+import { JwtPayload } from '../types';
+import { ConfigService } from '@nestjs/config';
+import { PassportStrategy } from '@nestjs/passport';
+import { Strategy, ExtractJwt } from 'passport-jwt';
+
+@Injectable()
+export class BearerStrategy extends PassportStrategy(Strategy, 'bearer') {
+ constructor(configService: ConfigService) {
+ super({
+ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
+ secretOrKey: configService.get('JWT_ACCESS_SECRET'),
+ issuer: configService.get('JWT_ISSUER'),
+ audience: configService.get('JWT_AUDIENCE'),
+ });
+ }
+
+ validate(payload: JwtPayload) {
+ return payload;
+ }
+}
diff --git a/src/modules/auth/strategies/cookie.strategy.ts b/src/modules/auth/strategies/cookie.strategy.ts
new file mode 100644
index 0000000..d821a1f
--- /dev/null
+++ b/src/modules/auth/strategies/cookie.strategy.ts
@@ -0,0 +1,32 @@
+import { PassportStrategy } from '@nestjs/passport';
+import { ExtractJwt, Strategy } from 'passport-jwt';
+import { Injectable, UnauthorizedException } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import type { FastifyRequest } from 'fastify';
+import type { JwtPayload } from '../types';
+
+@Injectable()
+export class CookieStrategy extends PassportStrategy(Strategy, 'cookie') {
+ constructor(configService: ConfigService) {
+ super({
+ jwtFromRequest: ExtractJwt.fromExtractors([
+ (request: FastifyRequest) => {
+ return request?.cookies?.['refresh'];
+ },
+ ]),
+ secretOrKey: configService.get('JWT_REFRESH_SECRET'),
+ passReqToCallback: true,
+ });
+ }
+
+ validate(_req: FastifyRequest, payload: JwtPayload) {
+ if (!payload || !payload.jti) {
+ throw new UnauthorizedException({
+ code: 'INVALID_REFRESH_TOKEN',
+ message: 'Refresh токен невалиден или протух',
+ });
+ }
+
+ return payload;
+ }
+}
diff --git a/src/modules/auth/strategies/index.ts b/src/modules/auth/strategies/index.ts
new file mode 100644
index 0000000..4ea10ce
--- /dev/null
+++ b/src/modules/auth/strategies/index.ts
@@ -0,0 +1,2 @@
+export { BearerStrategy } from './bearer.strategy';
+export { CookieStrategy } from './cookie.strategy';
diff --git a/src/modules/auth/types/index.ts b/src/modules/auth/types/index.ts
new file mode 100644
index 0000000..324f5b4
--- /dev/null
+++ b/src/modules/auth/types/index.ts
@@ -0,0 +1 @@
+export * from './jwt-payload';
diff --git a/src/modules/auth/types/jwt-payload.ts b/src/modules/auth/types/jwt-payload.ts
new file mode 100644
index 0000000..c788698
--- /dev/null
+++ b/src/modules/auth/types/jwt-payload.ts
@@ -0,0 +1,8 @@
+export interface JwtPayload {
+ sub: string;
+ email: string;
+ role: string;
+ iss: string;
+ aud: string;
+ jti: string;
+}
diff --git a/src/modules/user/commands/create.command.ts b/src/modules/user/commands/create.command.ts
new file mode 100644
index 0000000..b5e1d54
--- /dev/null
+++ b/src/modules/user/commands/create.command.ts
@@ -0,0 +1,29 @@
+import { ConflictException, Inject, Injectable } from '@nestjs/common';
+import { IUserRepository } from '../repository/user.repository.interface';
+import { NewUser } from '../entities/user.domain';
+import { createId } from '@paralleldrive/cuid2';
+
+@Injectable()
+export class CreateUserCommand {
+ constructor(
+ @Inject('IUserRepository')
+ private readonly repository: IUserRepository,
+ ) {}
+
+ async execute(dto: NewUser & { password: string }) {
+ const existingUser = await this.repository.findByEmail(dto.email);
+
+ if (existingUser) {
+ throw new ConflictException(`User with email ${dto.email} already exists`);
+ }
+
+ const user = await this.repository.create(dto);
+ await this.repository.logActivity({
+ eventType: 'registered',
+ userId: user.id,
+ id: createId(),
+ });
+ await this.repository.updatePasswordHash(user.id, dto.password);
+ return user;
+ }
+}
diff --git a/src/modules/user/commands/find-one.command.ts b/src/modules/user/commands/find-one.command.ts
new file mode 100644
index 0000000..1e44d15
--- /dev/null
+++ b/src/modules/user/commands/find-one.command.ts
@@ -0,0 +1,27 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { IUserRepository } from '../repository/user.repository.interface';
+import type { UserWithSecurity } from '../entities/user.domain';
+
+@Injectable()
+export class FindOneUserCommand {
+ constructor(
+ @Inject('IUserRepository')
+ private readonly repository: IUserRepository,
+ ) {}
+
+ async execute(params: { email: string }): Promise;
+ async execute(params: { id: string }): Promise;
+ async execute(params: { email?: string; id?: string }): Promise {
+ const { email, id } = params;
+
+ if (email) {
+ return this.repository.findByEmail(email);
+ }
+
+ if (id) {
+ return this.repository.findById(id);
+ }
+
+ throw new Error('FindOneUserCommand: email or id must be provided');
+ }
+}
diff --git a/src/modules/user/commands/index.ts b/src/modules/user/commands/index.ts
new file mode 100644
index 0000000..7a59139
--- /dev/null
+++ b/src/modules/user/commands/index.ts
@@ -0,0 +1,3 @@
+export { CreateUserCommand } from './create.command';
+export { FindOneUserCommand } from './find-one.command';
+export { UpdatePassUserCommand } from './update-pass.command';
diff --git a/src/modules/user/commands/update-pass.command.ts b/src/modules/user/commands/update-pass.command.ts
new file mode 100644
index 0000000..3ad7228
--- /dev/null
+++ b/src/modules/user/commands/update-pass.command.ts
@@ -0,0 +1,24 @@
+import { Inject, Injectable, NotFoundException } from '@nestjs/common';
+import { IUserRepository } from '../repository/user.repository.interface';
+
+@Injectable()
+export class UpdatePassUserCommand {
+ constructor(
+ @Inject('IUserRepository')
+ private readonly repository: IUserRepository,
+ ) {}
+
+ async execute(email: string, password: string) {
+ const { user } = await this.repository.findByEmail(email);
+
+ if (!user) {
+ throw new NotFoundException({
+ code: 'USER_NOT_FOUND',
+ message: 'Пользователь для обновления пароля не найден',
+ details: { email },
+ });
+ }
+
+ return this.repository.updatePasswordHash(user.id, password);
+ }
+}
diff --git a/src/modules/user/controller/index.ts b/src/modules/user/controller/index.ts
new file mode 100644
index 0000000..07eed15
--- /dev/null
+++ b/src/modules/user/controller/index.ts
@@ -0,0 +1 @@
+export { UserController } from './user.controller';
diff --git a/src/modules/user/controller/user.controller.ts b/src/modules/user/controller/user.controller.ts
new file mode 100644
index 0000000..122e3f6
--- /dev/null
+++ b/src/modules/user/controller/user.controller.ts
@@ -0,0 +1,70 @@
+import { BadRequestException, Body, Get, Patch, Post, Query, Req, UseGuards } from '@nestjs/common';
+import { UserService } from '../user.service';
+import {
+ GetMeActivitySwagger,
+ GetMeSwagger,
+ PatchMeNotificationsSwagger,
+ PatchMeSwagger,
+ PostMeAvatarSwagger,
+} from './user.swagger';
+import { UpdateNotificationsDto, UpdateProfileDto } from '../dtos';
+import { ApiBaseController, GetUserId } from '../../../shared/decorators';
+import { BearerAuthGuard } from 'src/shared/guards';
+import { PaginationDto } from '../../../shared/dtos';
+import { FastifyRequest } from 'fastify';
+
+@ApiBaseController('users', 'Users')
+@UseGuards(BearerAuthGuard)
+export class UserController {
+ constructor(private readonly facade: UserService) {}
+
+ @Get('me')
+ @GetMeSwagger()
+ async getProfile(@GetUserId() id: string) {
+ return this.facade.getProfile(id);
+ }
+
+ @Patch('me')
+ @PatchMeSwagger()
+ async updateProfile(@Body() dto: UpdateProfileDto, @GetUserId() id: string) {
+ return this.facade.updateProfile(id, dto);
+ }
+
+ @Patch('me/notifications')
+ @PatchMeNotificationsSwagger()
+ async updateNotifications(@Body() settings: UpdateNotificationsDto, @GetUserId() id: string) {
+ return this.facade.updateNotifications(id, settings);
+ }
+
+ @Get('me/activity')
+ @GetMeActivitySwagger()
+ async getActivity(@Query() query: PaginationDto, @GetUserId() id: string) {
+ return this.facade.getActivity(id, query.page, query.limit);
+ }
+
+ @Post('me/avatar')
+ @PostMeAvatarSwagger()
+ async uploadAvatar(@Req() req: FastifyRequest, @GetUserId() userId: string) {
+ if (!req.isMultipart()) {
+ throw new BadRequestException('Request is not multipart');
+ }
+
+ const file = await req.file();
+ if (!file || file.fieldname !== 'file') {
+ throw new BadRequestException('Поле file не найдено');
+ }
+
+ const allowedMimeTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/jpg'];
+ if (!allowedMimeTypes.includes(file.mimetype)) {
+ throw new BadRequestException('Недопустимый формат файла');
+ }
+
+ const buffer = await file.toBuffer();
+
+ return this.facade.uploadAvatar(userId, {
+ buffer,
+ filename: file.filename,
+ mimetype: file.mimetype,
+ });
+ }
+}
diff --git a/src/modules/user/controller/user.swagger.ts b/src/modules/user/controller/user.swagger.ts
new file mode 100644
index 0000000..423699c
--- /dev/null
+++ b/src/modules/user/controller/user.swagger.ts
@@ -0,0 +1,134 @@
+import {
+ ApiBody,
+ ApiConsumes,
+ ApiExtraModels,
+ ApiOperation,
+ ApiQuery,
+ ApiResponse,
+} from '@nestjs/swagger';
+import { UpdateNotificationsDto, UpdateProfileDto, UserResponse } from '../dtos';
+import { applyDecorators } from '@nestjs/common';
+import { ApiBadRequest, ApiUnauthorized, ApiValidationError } from 'src/shared/error';
+import { ActionResponse } from 'src/shared/dtos';
+
+export const GetMeSwagger = () =>
+ applyDecorators(
+ ApiExtraModels(UserResponse.Output),
+ ApiOperation({
+ summary: 'Получить профиль текущего пользователя',
+ description:
+ 'Возвращает полную структуру профиля, включая вложенные объекты безопасности и настроек.',
+ }),
+ ApiResponse({
+ status: 200,
+ description: 'Данные профиля успешно получены.',
+ type: UserResponse.Output,
+ }),
+ ApiUnauthorized(),
+ );
+
+export const PatchMeSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Обновить данные профиля',
+ description: 'Позволяет точечно обновить имя, bio, часовой пояс и язык интерфейса.',
+ }),
+ ApiBody({ type: UpdateProfileDto.Output }),
+ ApiResponse({
+ status: 200,
+ description: 'Профиль успешно обновлен.',
+ type: ActionResponse.Output,
+ }),
+ ApiValidationError('Ошибка валидации (например, слишком короткое имя)', [
+ {
+ field: 'fullName',
+ message: 'Строка должна содержать минимум 2 символа',
+ code: 'too_small',
+ },
+ ]),
+ ApiUnauthorized(),
+ );
+
+export const PatchMeNotificationsSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Обновить настройки уведомлений',
+ description: 'Частичное обновление настроек email и push уведомлений.',
+ }),
+ ApiBody({
+ type: UpdateNotificationsDto.Output,
+ }),
+ ApiResponse({
+ status: 200,
+ description: 'Настройки успешно сохранены.',
+ type: ActionResponse.Output,
+ }),
+ ApiValidationError('Некорректный формат настроек'),
+ ApiUnauthorized(),
+ );
+
+export const GetMeActivitySwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Получить ленту активности пользователя',
+ description: 'Возвращает список последних действий пользователя (логи).',
+ }),
+ ApiQuery({
+ name: 'limit',
+ required: false,
+ type: String,
+ description: 'Количество записей для вывода (по умолчанию 10)',
+ example: '15',
+ }),
+ ApiResponse({
+ status: 200,
+ description: 'Список активностей успешно получен.',
+ schema: {
+ example: {
+ data: [
+ {
+ id: 'clj1abc230000jk78',
+ eventType: 'TASK_COMPLETED',
+ description: 'Завершена задача "Обновить текст лендинга"',
+ createdAt: '2026-04-10T20:00:00.000Z',
+ metadata: { taskId: 'clj1xyz990000abc1' },
+ },
+ ],
+ meta: {
+ total: 45,
+ page: 1,
+ limit: 20,
+ totalPages: 3,
+ },
+ },
+ },
+ }),
+ ApiUnauthorized(),
+ );
+
+export const PostMeAvatarSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Загрузить новую аватарку',
+ description: 'Загрузка файла изображения для профиля пользователя.',
+ }),
+ ApiConsumes('multipart/form-data'),
+ ApiBody({
+ schema: {
+ type: 'object',
+ properties: {
+ file: {
+ type: 'string',
+ format: 'binary',
+ },
+ },
+ },
+ }),
+ ApiResponse({
+ status: 201,
+ description: 'Аватар успешно загружен.',
+ type: ActionResponse.Output,
+ }),
+ ApiBadRequest('Файл не передан или имеет неверный формат'),
+ ApiUnauthorized(),
+ );
diff --git a/src/modules/user/dtos/index.ts b/src/modules/user/dtos/index.ts
new file mode 100644
index 0000000..bdcec8b
--- /dev/null
+++ b/src/modules/user/dtos/index.ts
@@ -0,0 +1 @@
+export { UpdateProfileDto, UpdateNotificationsDto, UserResponse } from './user.dto';
diff --git a/src/modules/user/dtos/user.dto.ts b/src/modules/user/dtos/user.dto.ts
new file mode 100644
index 0000000..d342b79
--- /dev/null
+++ b/src/modules/user/dtos/user.dto.ts
@@ -0,0 +1,75 @@
+import { createZodDto } from 'nestjs-zod';
+import { z } from 'zod/v4';
+
+const NotificationsSchema = z
+ .object({
+ email: z.object({
+ task_assigned: z.boolean().describe('Уведомление на почту при назначении задачи'),
+ mentions: z.boolean().describe('Уведомление на почту при упоминании в комментариях'),
+ daily_summary: z.boolean().describe('Ежедневная сводка задач на почту'),
+ }),
+ push: z.object({
+ task_assigned: z.boolean().describe('Push-уведомление при назначении задачи'),
+ reminders: z.boolean().describe('Push-уведомления о дедлайнах'),
+ }),
+ })
+ .describe('Настройки уведомлений пользователя');
+
+export const UpdateNotificationsSchema = NotificationsSchema.partial().describe(
+ 'Схема для частичного обновления настроек уведомлений',
+);
+
+export class UpdateNotificationsDto extends createZodDto(UpdateNotificationsSchema) {}
+
+const SecuritySchema = z
+ .object({
+ is2faEnabled: z.boolean().describe('Статус двухфакторной аутентификации'),
+ lastPasswordChange: z.string().datetime().describe('Дата последнего изменения пароля'),
+ })
+ .describe('Данные безопасности аккаунта');
+
+const ProfileSchema = z.object({
+ firstName: z.string().describe('Имя пользователя'),
+ lastName: z.string().describe('Фамилия'),
+ middleName: z.string().nullable().describe('Отчество'),
+ bio: z.string().nullable().describe('О себе'),
+ avatarUrl: z.string().url().nullable().describe('Ссылка на аватар в S3'),
+ timezone: z.string().describe('Временная зона'),
+ language: z.string().describe('Язык интерфейса'),
+ createdAt: z.string().datetime().describe('Дата регистрации'),
+ updatedAt: z.string().datetime().describe('Дата последнего обновления профиля'),
+});
+
+export const UserSchema = z.object({
+ id: z.string().describe('Уникальный идентификатор (CUID/UUID)'),
+ email: z.string().email().describe('Электронная почта'),
+ profile: ProfileSchema,
+ security: SecuritySchema,
+ notifications: NotificationsSchema,
+});
+
+export class UserResponse extends createZodDto(UserSchema) {}
+
+export const UpdateProfileSchema = z
+ .object({
+ firstName: z
+ .string()
+ .min(1, 'Имя не может быть пустым')
+ .max(50, 'Имя слишком длинное')
+ .optional(),
+ lastName: z
+ .string()
+ .min(1, 'Фамилия не может быть пустой')
+ .max(50, 'Фамилия слишком длинная')
+ .optional(),
+ middleName: z.string().max(50, 'Отчество слишком длинное').nullish(),
+ bio: z.string().max(1000, 'О себе не более 1000 символов').nullish(),
+ timezone: z.string().max(50).optional(),
+ language: z
+ .string()
+ .length(2, 'Используйте формат ISO (например, "ru" или "en")')
+ .optional(),
+ })
+ .describe('Схема для частичного обновления данных профиля');
+
+export class UpdateProfileDto extends createZodDto(UpdateProfileSchema) {}
diff --git a/src/modules/user/entities/index.ts b/src/modules/user/entities/index.ts
new file mode 100644
index 0000000..426faed
--- /dev/null
+++ b/src/modules/user/entities/index.ts
@@ -0,0 +1 @@
+export { userActivity, userNotifications, userSecurity, users } from './user.entity';
diff --git a/src/modules/user/entities/user.domain.ts b/src/modules/user/entities/user.domain.ts
new file mode 100644
index 0000000..0721065
--- /dev/null
+++ b/src/modules/user/entities/user.domain.ts
@@ -0,0 +1,25 @@
+import { InferSelectModel, InferInsertModel } from 'drizzle-orm';
+import { users, userSecurity, userNotifications, userActivity } from './user.entity';
+
+export type User = InferSelectModel;
+export type NewUser = InferInsertModel;
+
+export type UserSecurity = InferSelectModel;
+export type NewUserSecurity = InferInsertModel;
+
+export type UserNotifications = InferSelectModel;
+export type NotificationSettings = Pick;
+
+export type UserActivity = InferSelectModel;
+export type NewUserActivity = InferInsertModel;
+
+export type UserProfile = {
+ user: User;
+ security: Pick;
+ notifications: NotificationSettings['settings'];
+};
+
+export type UserWithSecurity = {
+ user: User;
+ security: Pick;
+};
diff --git a/src/modules/user/entities/user.entity.ts b/src/modules/user/entities/user.entity.ts
new file mode 100644
index 0000000..9d06268
--- /dev/null
+++ b/src/modules/user/entities/user.entity.ts
@@ -0,0 +1,60 @@
+import { createId } from '@paralleldrive/cuid2';
+import { varchar, text, timestamp, boolean, jsonb } from 'drizzle-orm/pg-core';
+import { baseSchema } from 'src/shared/entities';
+
+export const users = baseSchema.table('users', {
+ id: text('id')
+ .primaryKey()
+ .$defaultFn(() => createId()),
+
+ firstName: varchar('first_name', { length: 50 }).notNull(),
+ lastName: varchar('last_name', { length: 50 }).notNull(),
+ middleName: varchar('middle_name', { length: 50 }),
+
+ email: varchar('email', { length: 255 }).notNull().unique(),
+ bio: text('bio'),
+ avatarUrl: varchar('avatar_url', { length: 512 }),
+ timezone: varchar('timezone', { length: 50 }).default('UTC').notNull(),
+ language: varchar('language', { length: 5 }).default('ru').notNull(),
+ createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
+ updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
+});
+
+export const userSecurity = baseSchema.table('user_security', {
+ userId: text('user_id')
+ .primaryKey()
+ .references(() => users.id, { onDelete: 'cascade' }),
+ passwordHash: varchar('password_hash', { length: 255 }).notNull(),
+ is2faEnabled: boolean('is_2fa_enabled').default(false).notNull(),
+ twoFactorSecret: text('two_factor_secret'),
+ lastPasswordChange: timestamp('last_password_change', { withTimezone: true })
+ .defaultNow()
+ .notNull(),
+});
+
+export const userNotifications = baseSchema.table('user_notifications', {
+ userId: text('user_id')
+ .primaryKey()
+ .references(() => users.id, { onDelete: 'cascade' }),
+ settings: jsonb('settings')
+ .$type<{
+ email: { task_assigned: boolean; mentions: boolean; daily_summary: boolean };
+ push: { task_assigned: boolean; reminders: boolean };
+ }>()
+ .default({
+ email: { task_assigned: true, mentions: true, daily_summary: false },
+ push: { task_assigned: true, reminders: true },
+ })
+ .notNull(),
+});
+
+export const userActivity = baseSchema.table('user_activity', {
+ id: text('id').primaryKey(),
+ userId: text('user_id')
+ .references(() => users.id, { onDelete: 'cascade' })
+ .notNull(),
+ eventType: varchar('event_type', { length: 50 }).notNull(),
+ entityId: varchar('entity_id'),
+ metadata: jsonb('metadata'),
+ createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
+});
diff --git a/src/modules/user/index.ts b/src/modules/user/index.ts
new file mode 100644
index 0000000..3b9d53d
--- /dev/null
+++ b/src/modules/user/index.ts
@@ -0,0 +1,3 @@
+export { UserModule } from './user.module';
+export { UserRepository } from './repository/user.repository';
+export { CreateUserCommand, FindOneUserCommand, UpdatePassUserCommand } from './commands';
diff --git a/src/modules/user/repository/index.ts b/src/modules/user/repository/index.ts
new file mode 100644
index 0000000..3e89261
--- /dev/null
+++ b/src/modules/user/repository/index.ts
@@ -0,0 +1 @@
+export {} from './user.repository';
diff --git a/src/modules/user/repository/user.repository.interface.ts b/src/modules/user/repository/user.repository.interface.ts
new file mode 100644
index 0000000..e2c4bbe
--- /dev/null
+++ b/src/modules/user/repository/user.repository.interface.ts
@@ -0,0 +1,28 @@
+import type {
+ NewUser,
+ NewUserActivity,
+ User,
+ UserActivity,
+ UserNotifications,
+ UserProfile,
+ UserWithSecurity,
+} from '../entities/user.domain';
+
+export interface IUserRepository {
+ create(data: NewUser): Promise;
+ findById(id: string): Promise;
+ findByEmail(email: string): Promise;
+ findProfile(id: string): Promise;
+ findActivityByUser(
+ userId: string,
+ options: { limit: number; offset: number },
+ ): Promise<{
+ items: UserActivity[];
+ total: number;
+ }>;
+ updateAvatar(id: string, url: string): Promise;
+ updateProfile(id: string, data: Partial): Promise;
+ updatePasswordHash(id: string, hash: string): Promise;
+ updateNotifications(id: string, settings: UserNotifications['settings']): Promise;
+ logActivity(data: NewUserActivity): Promise;
+}
diff --git a/src/modules/user/repository/user.repository.ts b/src/modules/user/repository/user.repository.ts
new file mode 100644
index 0000000..757958b
--- /dev/null
+++ b/src/modules/user/repository/user.repository.ts
@@ -0,0 +1,142 @@
+import * as sc from '../entities';
+import { DATABASE_SERVICE, DatabaseService } from '@libs/database';
+import { IUserRepository } from './user.repository.interface';
+import { Inject, Injectable } from '@nestjs/common';
+import type { NewUser, NewUserActivity, User, UserNotifications } from '../entities/user.domain';
+import { createId } from '@paralleldrive/cuid2';
+import { desc, eq, count } from 'drizzle-orm';
+
+@Injectable()
+export class UserRepository implements IUserRepository {
+ constructor(
+ @Inject(DATABASE_SERVICE)
+ private readonly db: DatabaseService,
+ ) {}
+
+ private get fullUserQuery() {
+ return this.db
+ .select()
+ .from(sc.users)
+ .leftJoin(sc.userSecurity, eq(sc.users.id, sc.userSecurity.userId))
+ .leftJoin(sc.userNotifications, eq(sc.users.id, sc.userNotifications.userId));
+ }
+
+ async findProfile(id: string) {
+ const [rows] = await this.fullUserQuery.where(eq(sc.users.id, id));
+ if (!rows.users) return null;
+ const { lastPasswordChange, is2faEnabled } = rows.user_security;
+ const { settings } = rows.user_notifications;
+
+ return {
+ user: rows.users,
+ security: { lastPasswordChange, is2faEnabled },
+ notifications: settings,
+ };
+ }
+
+ async findById(id: string) {
+ const [row] = await this.fullUserQuery.where(eq(sc.users.id, id));
+ if (!row || !row.user_security) return null;
+ return {
+ user: row.users,
+ security: {
+ passwordHash: row.user_security.passwordHash,
+ },
+ };
+ }
+
+ async findByEmail(email: string) {
+ const [row] = await this.fullUserQuery.where(eq(sc.users.email, email.toLowerCase()));
+ if (!row || !row.user_security) return null;
+ return {
+ user: row.users,
+ security: {
+ passwordHash: row.user_security.passwordHash,
+ },
+ };
+ }
+
+ async findSecurityByUserId(userId: string) {
+ const [result] = await this.db
+ .select()
+ .from(sc.userSecurity)
+ .where(eq(sc.userSecurity.userId, userId));
+ return result || null;
+ }
+
+ async create(data: NewUser) {
+ return await this.db.transaction(async (tx) => {
+ const [newUser] = await tx.insert(sc.users).values(data).returning();
+
+ await tx.insert(sc.userNotifications).values({
+ userId: newUser.id,
+ });
+
+ return newUser;
+ });
+ }
+
+ async updateProfile(id: string, data: Partial) {
+ const { rowCount } = await this.db
+ .update(sc.users)
+ .set({ ...data, updatedAt: new Date() })
+ .where(eq(sc.users.id, id));
+ return (rowCount ?? 0) > 0;
+ }
+
+ async updateNotifications(id: string, settings: UserNotifications['settings']) {
+ const { rowCount } = await this.db
+ .update(sc.userNotifications)
+ .set({ settings })
+ .where(eq(sc.userNotifications.userId, id));
+ return (rowCount ?? 0) > 0;
+ }
+
+ async updateAvatar(id: string, url: string) {
+ const { rowCount } = await this.db
+ .update(sc.users)
+ .set({ avatarUrl: url, updatedAt: new Date() })
+ .where(eq(sc.users.id, id));
+ return (rowCount ?? 0) > 0;
+ }
+
+ async updatePasswordHash(id: string, hash: string) {
+ const { rowCount } = await this.db
+ .insert(sc.userSecurity)
+ .values({ userId: id, passwordHash: hash })
+ .onConflictDoUpdate({
+ target: sc.userSecurity.userId,
+ set: { passwordHash: hash, lastPasswordChange: new Date() },
+ });
+ return (rowCount ?? 0) > 0;
+ }
+
+ async logActivity(data: NewUserActivity) {
+ const { rowCount } = await this.db.insert(sc.userActivity).values({
+ ...data,
+ id: data.id ?? createId(),
+ });
+ return (rowCount ?? 0) > 0;
+ }
+
+ async findActivityByUser(userId: string, options: { limit: number; offset: number }) {
+ const [totalResult, items] = await Promise.all([
+ this.db
+ .select({ value: count() })
+ .from(sc.userActivity)
+ .where(eq(sc.userActivity.userId, userId)),
+ this.db
+ .select()
+ .from(sc.userActivity)
+ .where(eq(sc.userActivity.userId, userId))
+ .limit(options.limit)
+ .offset(options.offset)
+ .orderBy(desc(sc.userActivity.createdAt)),
+ ]);
+
+ return {
+ items,
+ total: Number(totalResult[0]?.value ?? 0),
+ };
+ }
+}
diff --git a/src/modules/user/user.module.ts b/src/modules/user/user.module.ts
new file mode 100644
index 0000000..a5b7941
--- /dev/null
+++ b/src/modules/user/user.module.ts
@@ -0,0 +1,20 @@
+import { Module } from '@nestjs/common';
+import { UserController } from './controller';
+import { UserService } from './user.service';
+import { UserRepository } from './repository/user.repository';
+import { CreateUserCommand, FindOneUserCommand, UpdatePassUserCommand } from './commands';
+
+const REPOSITORY = {
+ provide: 'IUserRepository',
+ useClass: UserRepository,
+};
+
+const COMMANDS = [CreateUserCommand, FindOneUserCommand, UpdatePassUserCommand];
+
+@Module({
+ imports: [],
+ controllers: [UserController],
+ providers: [...COMMANDS, REPOSITORY, UserService],
+ exports: [...COMMANDS],
+})
+export class UserModule {}
diff --git a/src/modules/user/user.service.ts b/src/modules/user/user.service.ts
new file mode 100644
index 0000000..4bbb06c
--- /dev/null
+++ b/src/modules/user/user.service.ts
@@ -0,0 +1,166 @@
+import {
+ BadRequestException,
+ Inject,
+ Injectable,
+ InternalServerErrorException,
+ NotFoundException,
+} from '@nestjs/common';
+import { IUserRepository } from './repository/user.repository.interface';
+import { UpdateNotificationsDto, UpdateProfileDto } from './dtos';
+import { createId } from '@paralleldrive/cuid2';
+import { S3Service } from '@libs/s3';
+import { FileUploadDto } from '@libs/s3/dtos/upload-avatar.dto';
+
+@Injectable()
+export class UserService {
+ constructor(
+ @Inject('IUserRepository')
+ private readonly userRepo: IUserRepository,
+ private readonly s3: S3Service,
+ ) {}
+
+ private throwUserNotFound() {
+ throw new NotFoundException({
+ code: 'USER_NOT_FOUND',
+ message: 'Пользователь не найден в системе',
+ });
+ }
+
+ public getProfile = async (userId: string) => {
+ const { user, notifications, security } = await this.userRepo.findProfile(userId);
+ if (!user) this.throwUserNotFound();
+ const { id, email, ...profile } = user;
+
+ return {
+ id,
+ email,
+ profile,
+ security,
+ notifications,
+ };
+ };
+
+ public updateProfile = async (id: string, dto: UpdateProfileDto) => {
+ const keysToUpdate = Object.keys(dto);
+ if (keysToUpdate.length === 0) {
+ return {
+ success: true,
+ message: 'Изменений не обнаружено',
+ };
+ }
+
+ try {
+ const isUpdated = await this.userRepo.updateProfile(id, dto);
+
+ if (!isUpdated) {
+ throw new InternalServerErrorException('Не удалось обновить профиль');
+ }
+
+ await this.userRepo.logActivity({
+ id: createId(),
+ userId: id,
+ eventType: 'PROFILE_UPDATED',
+ metadata: {
+ fields: keysToUpdate,
+ },
+ });
+
+ return {
+ success: true,
+ message: 'Профиль успешно обновлен',
+ };
+ } catch (error) {
+ throw error;
+ }
+ };
+
+ public updateNotifications = async (id: string, dto: UpdateNotificationsDto) => {
+ const keysToUpdate = Object.keys(dto);
+ if (keysToUpdate.length === 0) {
+ return {
+ success: true,
+ message: 'Изменений не обнаружено',
+ };
+ }
+
+ const user = await this.userRepo.findById(id);
+ if (!user) this.throwUserNotFound();
+
+ try {
+ const isUpdated = await this.userRepo.updateNotifications(id, {
+ email: dto.email,
+ push: dto.push,
+ });
+
+ if (!isUpdated) {
+ throw new InternalServerErrorException(
+ 'Ошибка при сохранении настроек уведомлений',
+ );
+ }
+
+ await this.userRepo.logActivity({
+ id: createId(),
+ userId: id,
+ eventType: 'NOTIFICATIONS_UPDATED',
+ });
+
+ return {
+ success: true,
+ message: 'Настройки уведомлений обновлены',
+ };
+ } catch (error) {
+ throw error;
+ }
+ };
+
+ public getActivity = async (id: string, page: number, limit: number) => {
+ const safeLimit = Math.min(limit, 50);
+ const offset = (page - 1) * safeLimit;
+
+ const { items, total } = await this.userRepo.findActivityByUser(id, {
+ limit: safeLimit,
+ offset,
+ });
+
+ return {
+ items,
+ meta: {
+ total,
+ page,
+ limit: safeLimit,
+ totalPages: Math.ceil(total / safeLimit),
+ },
+ };
+ };
+
+ public uploadAvatar = async (userId: string, fileDto: FileUploadDto) => {
+ const avatarUrl = await this.s3.uploadPublicFile(
+ fileDto.buffer,
+ fileDto.filename,
+ fileDto.mimetype,
+ );
+
+ try {
+ new URL(avatarUrl);
+ } catch {
+ throw new BadRequestException({
+ code: 'INVALID_AVATAR_URL',
+ message: 'Провайдер хранилища вернул некорректный URL',
+ });
+ }
+
+ await this.userRepo.updateAvatar(userId, avatarUrl);
+
+ await this.userRepo.logActivity({
+ id: createId(),
+ userId,
+ eventType: 'AVATAR_CHANGED',
+ metadata: { url: avatarUrl },
+ });
+
+ return {
+ success: true,
+ avatarUrl,
+ };
+ };
+}
diff --git a/src/shared/adapters/mail/adapter.ts b/src/shared/adapters/mail/adapter.ts
new file mode 100644
index 0000000..eadbdf9
--- /dev/null
+++ b/src/shared/adapters/mail/adapter.ts
@@ -0,0 +1,56 @@
+import { Injectable } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import * as nodemailer from 'nodemailer';
+import * as hbs from 'handlebars';
+import * as fs from 'fs';
+import * as path from 'path';
+import { IMailPort } from './port';
+
+@Injectable()
+export class MailAdapter implements IMailPort {
+ private transporter: nodemailer.Transporter;
+
+ constructor(private cfg: ConfigService) {
+ this.transporter = nodemailer.createTransport({
+ host: this.cfg.get('MAIL_HOST'),
+ port: this.cfg.get('MAIL_PORT'),
+ secure: true,
+ auth: {
+ user: this.cfg.get('MAIL_USER'),
+ pass: this.cfg.get('MAIL_PASSWORD'),
+ },
+ });
+ }
+
+ private async sendMail(to: string, subject: string, templateName: string, context: any) {
+ const templatePath = path.join(process.cwd(), 'templates', `${templateName}.hbs`);
+ const templateSource = fs.readFileSync(templatePath, 'utf8');
+
+ const template = hbs.compile(templateSource);
+ const html = template(context);
+
+ return await this.transporter.sendMail({
+ from: `"${this.cfg.get('MAIL_FROM_NAME')}" <${this.cfg.get('MAIL_FROM_EMAIL')}>`,
+ to,
+ subject,
+ html,
+ });
+ }
+
+ async sendRegistrationCode(email: string, name: string, code: string) {
+ const codeArray = code.toString().split('');
+
+ return this.sendMail(email, 'Код подтверждения регистрации', 'confirmation', {
+ name,
+ codeArray,
+ });
+ }
+
+ async sendResetPasswordCode(email: string, code: string) {
+ const codeArray = code.toString().split('');
+
+ return this.sendMail(email, 'Восстановление пароля', 'reset-password', {
+ codeArray,
+ });
+ }
+}
diff --git a/src/shared/adapters/mail/index.ts b/src/shared/adapters/mail/index.ts
new file mode 100644
index 0000000..f798bbb
--- /dev/null
+++ b/src/shared/adapters/mail/index.ts
@@ -0,0 +1,2 @@
+export { MailAdapter } from './adapter';
+export { IMailPort } from './port';
diff --git a/src/shared/adapters/mail/port.ts b/src/shared/adapters/mail/port.ts
new file mode 100644
index 0000000..8a0de98
--- /dev/null
+++ b/src/shared/adapters/mail/port.ts
@@ -0,0 +1,4 @@
+export interface IMailPort {
+ sendRegistrationCode(email: string, name: string, code: string): Promise;
+ sendResetPasswordCode(email: string, code: string): Promise;
+}
diff --git a/src/shared/decorators/api-controller.decorator.ts b/src/shared/decorators/api-controller.decorator.ts
new file mode 100644
index 0000000..d8c9d9c
--- /dev/null
+++ b/src/shared/decorators/api-controller.decorator.ts
@@ -0,0 +1,15 @@
+import { Controller, applyDecorators } from '@nestjs/common';
+import { ApiTags } from '@nestjs/swagger';
+import { ApiErrorResponse } from 'src/shared/error';
+
+export const ApiBaseController = (path: string, tag: string) => {
+ return applyDecorators(
+ ApiTags(tag),
+ Controller(path),
+ ApiErrorResponse(
+ 500,
+ 'INTERNAL_SERVER_ERROR',
+ 'Произошла критическая ошибка на стороне сервера',
+ ),
+ );
+};
diff --git a/src/shared/decorators/index.ts b/src/shared/decorators/index.ts
new file mode 100644
index 0000000..c2f9d19
--- /dev/null
+++ b/src/shared/decorators/index.ts
@@ -0,0 +1,2 @@
+export { ApiBaseController } from './api-controller.decorator';
+export * from './user.decorator';
diff --git a/src/shared/decorators/user.decorator.ts b/src/shared/decorators/user.decorator.ts
new file mode 100644
index 0000000..7fc2467
--- /dev/null
+++ b/src/shared/decorators/user.decorator.ts
@@ -0,0 +1,24 @@
+import { createParamDecorator, ExecutionContext } from '@nestjs/common';
+import { FastifyRequest } from 'fastify';
+import { JwtPayload } from '../../modules/auth/types';
+
+export const GetUser = createParamDecorator(
+ (data: keyof JwtPayload | undefined, ctx: ExecutionContext) => {
+ const request = ctx.switchToHttp().getRequest();
+
+ const user = request.user as JwtPayload;
+
+ if (!user) return null;
+
+ return data ? user[data] : user;
+ },
+);
+
+export const GetUserId = createParamDecorator(
+ (_data: unknown, ctx: ExecutionContext): string | undefined => {
+ const request = ctx.switchToHttp().getRequest();
+ const user = request.user as JwtPayload;
+
+ return user?.sub;
+ },
+);
diff --git a/src/shared/dtos/index.ts b/src/shared/dtos/index.ts
new file mode 100644
index 0000000..5a8e94b
--- /dev/null
+++ b/src/shared/dtos/index.ts
@@ -0,0 +1,2 @@
+export * from './pagination.dto';
+export * from './response.dto';
diff --git a/src/shared/dtos/pagination.dto.ts b/src/shared/dtos/pagination.dto.ts
new file mode 100644
index 0000000..d0e8d38
--- /dev/null
+++ b/src/shared/dtos/pagination.dto.ts
@@ -0,0 +1,9 @@
+import { z } from 'zod/v4';
+import { createZodDto } from 'nestjs-zod';
+
+export const PaginationSchema = z.object({
+ page: z.coerce.number().int().min(1).default(1),
+ limit: z.coerce.number().int().min(1).max(100).default(20),
+});
+
+export class PaginationDto extends createZodDto(PaginationSchema) {}
diff --git a/src/shared/dtos/response.dto.ts b/src/shared/dtos/response.dto.ts
new file mode 100644
index 0000000..325f719
--- /dev/null
+++ b/src/shared/dtos/response.dto.ts
@@ -0,0 +1,9 @@
+import { z } from 'zod/v4';
+import { createZodDto } from 'nestjs-zod';
+
+export const ActionResponseSchema = z.object({
+ success: z.boolean().describe('Статус операции'),
+ message: z.string().optional().describe('Сообщение для пользователя'),
+});
+
+export class ActionResponse extends createZodDto(ActionResponseSchema) {}
diff --git a/src/shared/entities/index.ts b/src/shared/entities/index.ts
index 9d8074b..2e1f6bc 100644
--- a/src/shared/entities/index.ts
+++ b/src/shared/entities/index.ts
@@ -1 +1,3 @@
export { baseSchema } from './schema';
+export * from '../../modules/user/entities';
+export * from '../../modules/auth/entities';
diff --git a/src/shared/error/filter.ts b/src/shared/error/filter.ts
new file mode 100644
index 0000000..d571387
--- /dev/null
+++ b/src/shared/error/filter.ts
@@ -0,0 +1,50 @@
+import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '@nestjs/common';
+import { createId } from '@paralleldrive/cuid2';
+
+@Catch()
+export class GlobalExceptionFilter implements ExceptionFilter {
+ catch(exception: any, host: ArgumentsHost) {
+ const ctx = host.switchToHttp();
+ const response = ctx.getResponse();
+ const request = ctx.getRequest();
+
+ // 1. Определяем статус
+ let status =
+ exception instanceof HttpException
+ ? exception.getStatus()
+ : HttpStatus.INTERNAL_SERVER_ERROR;
+
+ let details = [];
+ let message = exception.message;
+ let code = 'INTERNAL_ERROR';
+
+ if (exception?.name === 'ZodValidationException') {
+ status = 400;
+ code = 'VALIDATION_FAILED';
+ details = exception.getResponse()?.errors || [];
+ message = 'Validation failed';
+ } else if (exception instanceof HttpException) {
+ const res = exception.getResponse() as any;
+ code = res.code || 'HTTP_ERROR';
+ details = res.details || [];
+ }
+
+ const requestId = request.headers['x-request-id'] || createId();
+
+ const errorResponse = {
+ code,
+ message,
+ retryable: status >= 500,
+ details,
+ meta: {
+ requestId,
+ timestamp: new Date().toISOString(),
+ path: request.url,
+ method: request.method,
+ service: 'main-api',
+ },
+ };
+
+ response.status(status).send(errorResponse);
+ }
+}
diff --git a/src/shared/error/index.ts b/src/shared/error/index.ts
new file mode 100644
index 0000000..544657a
--- /dev/null
+++ b/src/shared/error/index.ts
@@ -0,0 +1,2 @@
+export * from './swagger';
+export * from './filter';
diff --git a/src/shared/error/schema.ts b/src/shared/error/schema.ts
new file mode 100644
index 0000000..20e2a8b
--- /dev/null
+++ b/src/shared/error/schema.ts
@@ -0,0 +1,57 @@
+import { z } from 'zod/v4';
+import { createZodDto } from 'nestjs-zod';
+
+const ErrorDetailSchema = z
+ .object({
+ field: z.string().describe('Путь к полю в формате dot-notation (например, "user.email")'),
+ message: z.string().describe('Человекочитаемое сообщение о конкретной ошибке в этом поле'),
+ code: z
+ .string()
+ .describe(
+ 'Машиночитаемый код ошибки валидации (например, "invalid_email", "too_short")',
+ ),
+ })
+ .describe('Детальная информация о конкретном нарушении в запросе');
+
+const ErrorMetaSchema = z
+ .object({
+ requestId: z
+ .string()
+ .describe(
+ 'Уникальный ID запроса (Trace ID). Используется для поиска логов в Sentry/ELK/Kibana',
+ ),
+ timestamp: z
+ .string()
+ .datetime()
+ .describe('Точное время возникновения ошибки в формате ISO 8601'),
+ path: z.string().describe('URL-путь эндпоинта, который вернул ошибку'),
+ method: z.string().describe('HTTP метод запроса (GET, POST, etc.)'),
+ service: z
+ .string()
+ .optional()
+ .describe(
+ 'Имя микросервиса, в котором произошел сбой (полезно для будущего масштабирования)',
+ ),
+ })
+ .describe('Техническая мета-информация для мониторинга и отладки');
+
+export const GlobalErrorSchema = z.object({
+ code: z
+ .string()
+ .describe(
+ 'Уникальный бизнес-код ошибки (например, "INSUFFICIENT_FUNDS", "TEAM_NOT_FOUND")',
+ ),
+ message: z.string().describe('Краткое описание ошибки для пользователя или разработчика'),
+ retryable: z
+ .boolean()
+ .describe(
+ 'Флаг, указывающий клиенту, есть ли смысл повторять запрос без изменений (например, при 503 или Lock Timeout)',
+ ),
+ details: z
+ .array(ErrorDetailSchema)
+ .optional()
+ .describe('Список ошибок валидации (заполняется только для 400 ошибок)'),
+ meta: ErrorMetaSchema,
+});
+
+export class GlobalErrorResponse extends createZodDto(GlobalErrorSchema) {}
diff --git a/src/shared/error/swagger.ts b/src/shared/error/swagger.ts
new file mode 100644
index 0000000..29def94
--- /dev/null
+++ b/src/shared/error/swagger.ts
@@ -0,0 +1,53 @@
+import { ApiResponse, getSchemaPath } from '@nestjs/swagger';
+import { GlobalErrorResponse } from './schema';
+import { applyDecorators } from '@nestjs/common';
+
+export const ApiErrorResponse = (
+ status: number,
+ bizCode: string,
+ description: string,
+ details?: { field: string; message: string; code: string }[],
+) => {
+ return ApiResponse({
+ status,
+ description,
+ schema: {
+ allOf: [{ $ref: getSchemaPath(GlobalErrorResponse.Output) }],
+ example: {
+ code: bizCode,
+ message: description,
+ retryable: status >= 500,
+ details: details || [],
+ meta: {
+ requestId: 'req-clj1abc230000jk78',
+ timestamp: new Date().toISOString(),
+ path: '/api/v1/...',
+ method: 'POST',
+ service: 'main-backend',
+ },
+ },
+ },
+ });
+};
+
+export const ApiBadRequest = (description: string = 'Некорректный запрос') =>
+ applyDecorators(ApiErrorResponse(400, 'BAD_REQUEST', description));
+
+export const ApiUnauthorized = (description: string = 'Сессия истекла или токен не валиден') =>
+ applyDecorators(ApiErrorResponse(401, 'AUTH_REQUIRED', description));
+
+export const ApiForbidden = () =>
+ applyDecorators(
+ ApiErrorResponse(403, 'ACCESS_DENIED', 'У вас недостаточно прав для этого действия'),
+ );
+
+export const ApiNotFound = (description: string = 'Ресурс не найден') =>
+ applyDecorators(ApiErrorResponse(404, 'NOT_FOUND', description));
+
+export const ApiValidationError = (
+ description: string = 'Ошибка валидации входных данных',
+ fields: any[] = [],
+) => applyDecorators(ApiErrorResponse(400, 'VALIDATION_FAILED', description, fields));
+
+export const ApiConflict = (description: string = 'Ресурс уже существует') =>
+ applyDecorators(ApiErrorResponse(409, 'CONFLICT', description));
diff --git a/src/shared/guards/bearer.guard.ts b/src/shared/guards/bearer.guard.ts
new file mode 100644
index 0000000..65f33b7
--- /dev/null
+++ b/src/shared/guards/bearer.guard.ts
@@ -0,0 +1,5 @@
+import { Injectable } from '@nestjs/common';
+import { AuthGuard } from '@nestjs/passport';
+
+@Injectable()
+export class BearerAuthGuard extends AuthGuard('bearer') {}
diff --git a/src/shared/guards/cookie.guard.ts b/src/shared/guards/cookie.guard.ts
new file mode 100644
index 0000000..9ae8936
--- /dev/null
+++ b/src/shared/guards/cookie.guard.ts
@@ -0,0 +1,5 @@
+import { Injectable } from '@nestjs/common';
+import { AuthGuard } from '@nestjs/passport';
+
+@Injectable()
+export class CookieAuthGuard extends AuthGuard('cookie') {}
diff --git a/src/shared/guards/index.ts b/src/shared/guards/index.ts
new file mode 100644
index 0000000..20ada34
--- /dev/null
+++ b/src/shared/guards/index.ts
@@ -0,0 +1,2 @@
+export { BearerAuthGuard } from './bearer.guard';
+export { CookieAuthGuard } from './cookie.guard';
diff --git a/src/shared/migration/index.ts b/src/shared/migration/index.ts
new file mode 100644
index 0000000..1be4fe2
--- /dev/null
+++ b/src/shared/migration/index.ts
@@ -0,0 +1 @@
+export { MigrationService } from './migration.service';
diff --git a/src/shared/migration/migration.service.ts b/src/shared/migration/migration.service.ts
new file mode 100644
index 0000000..e1e49f9
--- /dev/null
+++ b/src/shared/migration/migration.service.ts
@@ -0,0 +1,27 @@
+import { Inject, Injectable, OnModuleInit, Logger } from '@nestjs/common';
+import { migrate } from 'drizzle-orm/node-postgres/migrator';
+import { DATABASE_SERVICE, type DatabaseService } from '@libs/database';
+import * as path from 'path';
+
+@Injectable()
+export class MigrationService implements OnModuleInit {
+ private readonly logger = new Logger(MigrationService.name);
+
+ constructor(
+ @Inject(DATABASE_SERVICE)
+ private readonly db: DatabaseService>,
+ ) {}
+
+ async onModuleInit() {
+ this.logger.debug('Checking for database migrations...');
+ try {
+ await migrate(this.db, {
+ migrationsFolder: path.resolve(process.cwd(), 'migrations'),
+ });
+ this.logger.debug('Migrations completed or already up to date');
+ } catch (error) {
+ this.logger.error('Migration failed', error);
+ process.exit(1);
+ }
+ }
+}
diff --git a/src/shared/types/fastify.d.ts b/src/shared/types/fastify.d.ts
new file mode 100644
index 0000000..db45904
--- /dev/null
+++ b/src/shared/types/fastify.d.ts
@@ -0,0 +1,7 @@
+import { JwtPayload } from './jwt-payload.type';
+
+declare module 'fastify' {
+ interface FastifyRequest {
+ user?: JwtPayload;
+ }
+}
diff --git a/src/shared/workers/enum.ts b/src/shared/workers/enum.ts
new file mode 100644
index 0000000..dffe92b
--- /dev/null
+++ b/src/shared/workers/enum.ts
@@ -0,0 +1,9 @@
+export enum Queues {
+ MAIL = 'MAIL_QUEUE',
+}
+
+export enum MailJobs {
+ SEND_REGISTER_CODE = 'SEND_REGISTER_CODE',
+ SEND_RESET_PASSWORD = 'SEND_RESET_PASSWORD',
+ SEND_CHANGE_EMAIL = 'SEND_CHANGE_EMAIL',
+}
diff --git a/src/shared/workers/events/index.ts b/src/shared/workers/events/index.ts
new file mode 100644
index 0000000..61a6360
--- /dev/null
+++ b/src/shared/workers/events/index.ts
@@ -0,0 +1,2 @@
+export { RegisterCodeEvent } from './register-code.event';
+export { ResetPasswordEvent } from './reset-password.event';
diff --git a/src/shared/workers/events/register-code.event.ts b/src/shared/workers/events/register-code.event.ts
new file mode 100644
index 0000000..df87ca8
--- /dev/null
+++ b/src/shared/workers/events/register-code.event.ts
@@ -0,0 +1,7 @@
+export class RegisterCodeEvent {
+ constructor(
+ public email: string,
+ public name: string,
+ public otp: string,
+ ) {}
+}
diff --git a/src/shared/workers/events/reset-password.event.ts b/src/shared/workers/events/reset-password.event.ts
new file mode 100644
index 0000000..1f50e09
--- /dev/null
+++ b/src/shared/workers/events/reset-password.event.ts
@@ -0,0 +1,6 @@
+export class ResetPasswordEvent {
+ constructor(
+ public email: string,
+ public otp: string,
+ ) {}
+}
diff --git a/src/shared/workers/index.ts b/src/shared/workers/index.ts
new file mode 100644
index 0000000..2111275
--- /dev/null
+++ b/src/shared/workers/index.ts
@@ -0,0 +1,3 @@
+export { MailJobs, Queues } from './enum';
+export { RegisterCodeEvent } from './events';
+export { MailProcessor } from './mail';
diff --git a/src/shared/workers/mail/index.ts b/src/shared/workers/mail/index.ts
new file mode 100644
index 0000000..a059e2b
--- /dev/null
+++ b/src/shared/workers/mail/index.ts
@@ -0,0 +1 @@
+export { MailProcessor } from './worker';
diff --git a/src/shared/workers/mail/worker.ts b/src/shared/workers/mail/worker.ts
new file mode 100644
index 0000000..06ce4b1
--- /dev/null
+++ b/src/shared/workers/mail/worker.ts
@@ -0,0 +1,72 @@
+import { Processor, WorkerHost } from '@nestjs/bullmq';
+import { MailJobs, Queues } from '../enum';
+import type { Job } from 'bullmq';
+import { IMailPort } from 'src/shared/adapters/mail';
+import { Inject } from '@nestjs/common';
+import type { RegisterCodeEvent, ResetPasswordEvent } from '../events';
+
+@Processor(Queues.MAIL)
+export class MailProcessor extends WorkerHost {
+ constructor(
+ @Inject('IMailPort')
+ private readonly mailAdapter: IMailPort,
+ ) {
+ super();
+ }
+
+ async process(job: Job): Promise;
+ async process(job: Job): Promise;
+ async process(job: Job): Promise {
+ await job.log(`[START] Job ID: ${job.id} | Type: ${job.name}`);
+
+ try {
+ switch (job.name) {
+ case MailJobs.SEND_REGISTER_CODE:
+ await this.sendRegisterCode(job);
+ break;
+ case MailJobs.SEND_RESET_PASSWORD:
+ await this.sendResetPassCode(job);
+ break;
+ default:
+ await job.log(`[WRN] No handler for job: ${job.name}`);
+ await job.updateProgress(100);
+ }
+
+ await job.log(`[DONE] Job ${job.id} processed`);
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ const errorStack = error instanceof Error ? error.stack : '';
+
+ await job.log(`[FAIL] ${errorMessage}`);
+ if (errorStack) {
+ await job.log(errorStack);
+ }
+
+ throw error;
+ }
+ }
+
+ private sendRegisterCode = async (job: Job) => {
+ const { email, name, otp } = job.data;
+
+ await job.log(`Sending registration code to: ${email}`);
+ await job.updateProgress(20);
+
+ await this.mailAdapter.sendRegistrationCode(email, name, otp);
+
+ await job.log(`Successfully sent to ${email}`);
+ await job.updateProgress(100);
+ };
+
+ private sendResetPassCode = async (job: Job) => {
+ const { email, otp } = job.data;
+
+ await job.log(`Sending password reset to: ${email}`);
+ await job.updateProgress(30);
+
+ await this.mailAdapter.sendResetPasswordCode(email, otp);
+
+ await job.log(`Reset link delivered to ${email}`);
+ await job.updateProgress(100);
+ };
+}
diff --git a/templates/confirmation.hbs b/templates/confirmation.hbs
new file mode 100644
index 0000000..c30923b
--- /dev/null
+++ b/templates/confirmation.hbs
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
Проверка безопасности
+
Привет, {{name}}! Используйте этот код для подтверждения:
+
+
{{#each codeArray}}{{this}}{{/each}}
+
+
Код будет активен в течение 15 минут.
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/reset-password.hbs b/templates/reset-password.hbs
new file mode 100644
index 0000000..1fa520e
--- /dev/null
+++ b/templates/reset-password.hbs
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
Сброс пароля
+
Здравствуйте!
+
Мы получили запрос на восстановление пароля для вашего аккаунта.
Ваш одноразовый код для сброса:
+
+
{{#each codeArray}}
{{this}}
{{/each}}
+
+
Никому не сообщайте этот код. Если вы не запрашивали сброс пароля, немедленно обратитесь в поддержку.
+
+
+
+
+
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index de35319..4f21469 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -24,8 +24,10 @@
"@libs/config/*": ["./libs/config/src/*"],
"@libs/database": ["./libs/database/src"],
"@libs/database/*": ["./libs/database/src/*"],
- "@libs/health": ["libs/health/src"],
- "@libs/health/*": ["libs/health/src/*"]
+ "@libs/health": ["./libs/health/src"],
+ "@libs/health/*": ["./libs/health/src/*"],
+ "@libs/s3": ["./libs/s3/src"],
+ "@libs/s3/*": ["./libs/s3/src/*"]
},
"baseUrl": "./"
},
From be5545365589bad7b872574ad88909ece4f9faff Mon Sep 17 00:00:00 2001
From: Danil <123034340+soorq@users.noreply.github.com>
Date: Wed, 15 Apr 2026 16:45:07 +0300
Subject: [PATCH 15/30] feat(teams): full implementation of team management
system
* chore(team): integrate base snippets per module
* feat(teams): implement team management core with RBAC and metadata
* refactor(user): extract file upload logic into custom decorator
* feat(teams): implement core structure, entities and swagger documentation
* feat(teams): implement team tags and paginated retrieval
* feat(teams): add media module for team avatar and banner uploads
* feat(teams): implement team invitation email template and logic
* feat(teams): implement core logic and repository methods for team membership
* feat(teams): add team invitation event and processing logic
* feat(teams): implement invitations flow and refactor members repository
* chore(fastify-csrf): implementation of core infrastructure, logging and error filters
---------
Co-authored-by: Maxim
---
.env.example | 1 +
libs/bootstrap/src/bootstrap.ts | 37 +-
libs/config/src/config.schema.ts | 1 +
libs/s3/src/s3.module.ts | 8 +-
libs/s3/src/s3.service.ts | 24 +-
migrations/0002_pink_krista_starr.sql | 56 ++
migrations/0003_open_oracle.sql | 18 +
migrations/meta/0002_snapshot.json | 717 ++++++++++++++++
migrations/meta/0003_snapshot.json | 808 ++++++++++++++++++
migrations/meta/_journal.json | 52 +-
package.json | 2 +
pnpm-lock.yaml | 27 +
src/main.ts | 2 +-
src/modules/app/app.module.ts | 20 +-
.../auth/controller/auth.controller.ts | 2 +-
src/modules/auth/services/auth.service.ts | 13 +
src/modules/media/dtos/index.ts | 2 +
.../media/dtos/upload-file-response.dto.ts | 12 +
.../modules/media/dtos/upload-file.dto.ts | 0
.../media/interfaces/team-media.interface.ts | 16 +
.../media/interfaces/user-media.interface.ts | 11 +
src/modules/media/media.module.ts | 40 +
src/modules/media/media.service.ts | 60 ++
src/modules/teams/controller/index.ts | 2 +
.../teams/controller/members.controller.ts | 56 ++
.../teams/controller/teams.controller.ts | 101 +++
src/modules/teams/controller/teams.swagger.ts | 308 +++++++
src/modules/teams/dtos/index.ts | 15 +
src/modules/teams/dtos/member.dto.ts | 56 ++
src/modules/teams/dtos/team.dto.ts | 82 ++
src/modules/teams/entities/enums.ts | 15 +
src/modules/teams/entities/index.ts | 3 +
src/modules/teams/entities/teams.domain.ts | 31 +
src/modules/teams/entities/teams.entity.ts | 74 ++
src/modules/teams/index.ts | 1 +
src/modules/teams/mappers/index.ts | 1 +
src/modules/teams/mappers/member.mapper.ts | 70 ++
src/modules/teams/repository/index.ts | 6 +
.../repository/teams.repository.interface.ts | 60 ++
.../teams/repository/teams.repository.ts | 292 +++++++
src/modules/teams/services/index.ts | 2 +
src/modules/teams/services/members.service.ts | 257 ++++++
src/modules/teams/services/teams.service.ts | 219 +++++
src/modules/teams/teams.module.ts | 48 ++
.../user/controller/user.controller.ts | 34 +-
src/modules/user/user.module.ts | 3 +-
src/modules/user/user.service.ts | 29 +-
src/shared/adapters/mail/adapter.ts | 14 +-
src/shared/adapters/mail/port.ts | 1 +
src/shared/constants/file.constants.ts | 1 +
src/shared/constants/index.ts | 1 +
.../decorators/api-controller.decorator.ts | 12 +-
.../extract-fastify-file.decorator.ts | 34 +
src/shared/decorators/index.ts | 1 +
src/shared/entities/index.ts | 1 +
src/shared/error/filter.ts | 3 +-
src/shared/error/swagger.ts | 5 +-
src/shared/schemas/index.ts | 1 +
.../schemas/pagination-response.schema.ts | 29 +
src/shared/workers/enum.ts | 1 +
src/shared/workers/events/index.ts | 1 +
.../workers/events/team-invitation.event.ts | 7 +
src/shared/workers/mail/worker.ts | 18 +-
templates/confirmation.hbs | 2 +-
templates/reset-password.hbs | 2 +-
templates/team-invitation.hbs | 52 ++
66 files changed, 3768 insertions(+), 112 deletions(-)
create mode 100644 migrations/0002_pink_krista_starr.sql
create mode 100644 migrations/0003_open_oracle.sql
create mode 100644 migrations/meta/0002_snapshot.json
create mode 100644 migrations/meta/0003_snapshot.json
create mode 100644 src/modules/media/dtos/index.ts
create mode 100644 src/modules/media/dtos/upload-file-response.dto.ts
rename libs/s3/src/dtos/upload-avatar.dto.ts => src/modules/media/dtos/upload-file.dto.ts (100%)
create mode 100644 src/modules/media/interfaces/team-media.interface.ts
create mode 100644 src/modules/media/interfaces/user-media.interface.ts
create mode 100644 src/modules/media/media.module.ts
create mode 100644 src/modules/media/media.service.ts
create mode 100644 src/modules/teams/controller/index.ts
create mode 100644 src/modules/teams/controller/members.controller.ts
create mode 100644 src/modules/teams/controller/teams.controller.ts
create mode 100644 src/modules/teams/controller/teams.swagger.ts
create mode 100644 src/modules/teams/dtos/index.ts
create mode 100644 src/modules/teams/dtos/member.dto.ts
create mode 100644 src/modules/teams/dtos/team.dto.ts
create mode 100644 src/modules/teams/entities/enums.ts
create mode 100644 src/modules/teams/entities/index.ts
create mode 100644 src/modules/teams/entities/teams.domain.ts
create mode 100644 src/modules/teams/entities/teams.entity.ts
create mode 100644 src/modules/teams/index.ts
create mode 100644 src/modules/teams/mappers/index.ts
create mode 100644 src/modules/teams/mappers/member.mapper.ts
create mode 100644 src/modules/teams/repository/index.ts
create mode 100644 src/modules/teams/repository/teams.repository.interface.ts
create mode 100644 src/modules/teams/repository/teams.repository.ts
create mode 100644 src/modules/teams/services/index.ts
create mode 100644 src/modules/teams/services/members.service.ts
create mode 100644 src/modules/teams/services/teams.service.ts
create mode 100644 src/modules/teams/teams.module.ts
create mode 100644 src/shared/constants/file.constants.ts
create mode 100644 src/shared/constants/index.ts
create mode 100644 src/shared/decorators/extract-fastify-file.decorator.ts
create mode 100644 src/shared/schemas/index.ts
create mode 100644 src/shared/schemas/pagination-response.schema.ts
create mode 100644 src/shared/workers/events/team-invitation.event.ts
create mode 100644 templates/team-invitation.hbs
diff --git a/.env.example b/.env.example
index 7421df7..5954e6e 100644
--- a/.env.example
+++ b/.env.example
@@ -1,6 +1,7 @@
# --- APP ---
PORT=3000
NODE_ENV=development
+COOKIE_SECRET=same-serious-secret
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
# --- POSTGRES ---
diff --git a/libs/bootstrap/src/bootstrap.ts b/libs/bootstrap/src/bootstrap.ts
index 0ca163f..9f7ced1 100644
--- a/libs/bootstrap/src/bootstrap.ts
+++ b/libs/bootstrap/src/bootstrap.ts
@@ -9,9 +9,17 @@ import type { BootstrapOptions } from './interfaces/options.interface';
import fastifyCookie from '@fastify/cookie';
import fastifyCompress from '@fastify/compress';
import fastifyMultipart from '@fastify/multipart';
+import fastifyCsrf from '@fastify/csrf-protection';
+import { createId } from '@paralleldrive/cuid2';
export async function bootstrapApp(options: BootstrapOptions) {
- const adapter = new FastifyAdapter();
+ const startTime = performance.now();
+ const adapter = new FastifyAdapter({
+ requestIdHeader: 'x-request-id',
+ genReqId: (req) => {
+ return (req.headers['x-request-id'] as string) || createId();
+ },
+ });
const {
appModule,
@@ -28,7 +36,7 @@ export async function bootstrapApp(options: BootstrapOptions) {
let rootModule = appModule;
- // TODO: Improve merging modules (in case of multiple features needed)
+ // TODO: Improve merging modules (in case of multiple features needed) or migrate to fastify throttle
if (throttlerOptions) {
rootModule = setupThrottler(rootModule, throttlerOptions);
}
@@ -74,15 +82,36 @@ export async function bootstrapApp(options: BootstrapOptions) {
await setupSwagger(app, fullOptions);
}
- if (useCookieParser) app.register(fastifyCookie, { secret: 'SAME-SECRET' });
+ if (useCookieParser) {
+ const secret = configService.getOrThrow('COOKIE_SECRET');
+ await app.register(fastifyCookie, { secret });
+ await app.register(fastifyCsrf, {
+ cookieOpts: {
+ signed: true,
+ httpOnly: true,
+ sameSite: 'strict',
+ secure: configService.getOrThrow('NODE_ENV') === 'production',
+ },
+ });
+ }
if (setupApp) setupApp(app);
await app.listen(port, '0.0.0.0', (_err, address) => {
+ const baseUrl = `${address}${apiPrefix ? '/' + apiPrefix : ''}`;
+
if (_err) {
logger.error(_err);
process.exit(1);
}
- logger.verbose(`Application is running on: ${address}${apiPrefix ? '/' + apiPrefix : ''}`);
+ const startupTime = (performance.now() - startTime).toFixed(2);
+ logger.verbose(`Environment: ${process.env.NODE_ENV || 'development'}`);
+ logger.verbose(`API Endpoint: ${baseUrl}`);
+ logger.verbose(`Health Check: ${baseUrl}/health`);
+ logger.verbose(`Swagger UI: ${baseUrl}/${swaggerOptions?.path ?? 'docs'}`);
+ logger.verbose(
+ `OpenAPI (Specs): ${baseUrl}/${swaggerOptions?.path ?? 'docs'}/s/{json,yaml}`,
+ );
+ logger.verbose(`Boot Time: ${startupTime}ms`);
});
}
diff --git a/libs/config/src/config.schema.ts b/libs/config/src/config.schema.ts
index e28d54f..81a90bc 100644
--- a/libs/config/src/config.schema.ts
+++ b/libs/config/src/config.schema.ts
@@ -8,6 +8,7 @@ const timeStringSchema = z.string().regex(/^[0-9]+[smhdw]$/, {
export const ConfigSchema = z.object({
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
+ COOKIE_SECRET: z.string({ error: 'COOKIE_SECRET is missing' }),
DB_USERNAME: z.string({ error: 'DB_USERNAME is missing' }),
DB_PASSWORD: z.string({ error: 'DB_PASSWORD is missing' }),
DB_DATABASE: z.string({ error: 'DB_DATABASE is missing' }),
diff --git a/libs/s3/src/s3.module.ts b/libs/s3/src/s3.module.ts
index ee7d610..2c4b1f2 100644
--- a/libs/s3/src/s3.module.ts
+++ b/libs/s3/src/s3.module.ts
@@ -3,10 +3,7 @@ import type { S3ModuleOptions, S3ModuleAsyncOptions } from './interfaces';
import { S3Service } from './s3.service';
import { S3_OPTIONS } from './s3.constants';
-@Module({
- providers: [S3Service],
- exports: [S3Service],
-})
+@Module({})
export class S3Module {
static register(options: S3ModuleOptions): DynamicModule {
const { global, ...config } = options;
@@ -20,10 +17,9 @@ export class S3Module {
}
static registerAsync(options: S3ModuleAsyncOptions): DynamicModule {
- const { global, imports } = options;
+ const { imports } = options;
return {
- global,
module: S3Module,
imports: imports || [],
providers: [this.createAsyncOptionsProvider(options), S3Service],
diff --git a/libs/s3/src/s3.service.ts b/libs/s3/src/s3.service.ts
index 47d8a8d..16b3d3e 100644
--- a/libs/s3/src/s3.service.ts
+++ b/libs/s3/src/s3.service.ts
@@ -1,5 +1,5 @@
import { Inject, Injectable } from '@nestjs/common';
-import { S3Client } from '@aws-sdk/client-s3';
+import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { S3_OPTIONS } from './s3.constants';
import { S3ModuleOptions } from './interfaces';
import { PutObjectCommand } from '@aws-sdk/client-s3';
@@ -28,13 +28,31 @@ export class S3Service {
});
}
- async uploadPublicFile(
+ async deleteFile(fileUrl: string): Promise {
+ try {
+ const url = new URL(fileUrl);
+ const pathParts = url.pathname.split('/');
+ const key = pathParts.slice(2).join('/');
+
+ await this.s3Client.send(
+ new DeleteObjectCommand({
+ Bucket: this.bucket,
+ Key: key,
+ }),
+ );
+ } catch (error) {
+ console.error('S3 Rollback failed:', error);
+ }
+ }
+
+ async uploadFile(
fileBuffer: Buffer,
originalName: string,
mimetype: string,
+ folder: string,
): Promise {
const extension = extname(originalName);
- const fileName = `${randomUUID()}${extension}`;
+ const fileName = `${folder}/${randomUUID()}${extension}`;
const command = new PutObjectCommand({
Bucket: this.bucket,
diff --git a/migrations/0002_pink_krista_starr.sql b/migrations/0002_pink_krista_starr.sql
new file mode 100644
index 0000000..e44a9d8
--- /dev/null
+++ b/migrations/0002_pink_krista_starr.sql
@@ -0,0 +1,56 @@
+CREATE TYPE "base"."team_role" AS ENUM ('admin', 'moderator', 'member');
+
+CREATE TYPE "base"."member_status" AS ENUM ('pending', 'active', 'declined', 'banned');
+
+CREATE TABLE
+ "base"."tags" (
+ "id" text PRIMARY KEY NOT NULL,
+ "name" varchar(50) NOT NULL,
+ CONSTRAINT "tags_name_unique" UNIQUE ("name")
+ );
+
+CREATE TABLE
+ "base"."team_members" (
+ "team_id" text NOT NULL,
+ "user_id" text NOT NULL,
+ "role" "base"."team_role" DEFAULT 'member' NOT NULL,
+ "status" "base"."member_status" DEFAULT 'pending' NOT NULL,
+ "joined_at" timestamp,
+ "created_at" timestamp DEFAULT now () NOT NULL,
+ CONSTRAINT "team_members_team_id_user_id_pk" PRIMARY KEY ("team_id", "user_id")
+ );
+
+CREATE TABLE
+ "base"."teams" (
+ "id" text PRIMARY KEY NOT NULL,
+ "slug" varchar(120) NOT NULL,
+ "name" varchar(100) NOT NULL,
+ "description" text,
+ "avatar_url" text,
+ "cover_url" text,
+ "owner_id" text,
+ "created_at" timestamp DEFAULT now () NOT NULL,
+ "updated_at" timestamp DEFAULT now () NOT NULL,
+ CONSTRAINT "teams_slug_unique" UNIQUE ("slug")
+ );
+
+CREATE TABLE
+ "base"."teams_to_tags" (
+ "team_id" text NOT NULL,
+ "tag_id" text NOT NULL,
+ CONSTRAINT "teams_to_tags_team_id_tag_id_pk" PRIMARY KEY ("team_id", "tag_id")
+ );
+
+ALTER TABLE "base"."team_members" ADD CONSTRAINT "team_members_team_id_teams_id_fk" FOREIGN KEY ("team_id") REFERENCES "base"."teams" ("id") ON DELETE cascade ON UPDATE no action;
+
+ALTER TABLE "base"."team_members" ADD CONSTRAINT "team_members_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "base"."users" ("id") ON DELETE cascade ON UPDATE no action;
+
+ALTER TABLE "base"."teams" ADD CONSTRAINT "teams_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "base"."users" ("id") ON DELETE no action ON UPDATE no action;
+
+ALTER TABLE "base"."teams_to_tags" ADD CONSTRAINT "teams_to_tags_team_id_teams_id_fk" FOREIGN KEY ("team_id") REFERENCES "base"."teams" ("id") ON DELETE cascade ON UPDATE no action;
+
+ALTER TABLE "base"."teams_to_tags" ADD CONSTRAINT "teams_to_tags_tag_id_tags_id_fk" FOREIGN KEY ("tag_id") REFERENCES "base"."tags" ("id") ON DELETE cascade ON UPDATE no action;
+
+CREATE INDEX "member_status_idx" ON "base"."team_members" USING btree ("status");
+
+CREATE INDEX "team_slug_idx" ON "base"."teams" USING btree ("slug");
\ No newline at end of file
diff --git a/migrations/0003_open_oracle.sql b/migrations/0003_open_oracle.sql
new file mode 100644
index 0000000..4fe9269
--- /dev/null
+++ b/migrations/0003_open_oracle.sql
@@ -0,0 +1,18 @@
+ALTER TYPE "base"."team_role" ADD VALUE 'owner' BEFORE 'admin';
+ALTER TYPE "base"."team_role" ADD VALUE 'lead' BEFORE 'moderator';
+ALTER TYPE "base"."team_role" ADD VALUE 'viewer';
+ALTER TABLE "base"."teams" DROP CONSTRAINT "teams_owner_id_users_id_fk";
+
+ALTER TABLE "base"."team_members" ALTER COLUMN "status" SET DATA TYPE text;
+ALTER TABLE "base"."team_members" ALTER COLUMN "status" SET DEFAULT 'inactive'::text;
+DROP TYPE "base"."member_status";
+CREATE TYPE "base"."member_status" AS ENUM('active', 'banned', 'inactive');
+ALTER TABLE "base"."team_members" ALTER COLUMN "status" SET DEFAULT 'inactive'::"base"."member_status";
+ALTER TABLE "base"."team_members" ALTER COLUMN "status" SET DATA TYPE "base"."member_status" USING "status"::"base"."member_status";
+ALTER TABLE "base"."teams" ADD COLUMN "deleted_at" timestamp;
+ALTER TABLE "base"."teams" ADD CONSTRAINT "teams_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "base"."users"("id") ON DELETE set null ON UPDATE no action;
+CREATE INDEX "member_role_idx" ON "base"."team_members" USING btree ("user_id","role");
+CREATE UNIQUE INDEX "team_active_slug_idx" ON "base"."teams" USING btree ("slug") WHERE "base"."teams"."deleted_at" is null;
+CREATE INDEX "team_owner_idx" ON "base"."teams" USING btree ("owner_id");
+CREATE INDEX "team_deleted_at_idx" ON "base"."teams" USING btree ("deleted_at");
+CREATE INDEX "teams_to_tags_tag_id_idx" ON "base"."teams_to_tags" USING btree ("tag_id");
\ No newline at end of file
diff --git a/migrations/meta/0002_snapshot.json b/migrations/meta/0002_snapshot.json
new file mode 100644
index 0000000..80c77c1
--- /dev/null
+++ b/migrations/meta/0002_snapshot.json
@@ -0,0 +1,717 @@
+{
+ "id": "995af10c-f9b7-416a-b20b-85034dbd20d5",
+ "prevId": "c5575cbf-cbee-46d8-af83-95b96a2afceb",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "base.user_activity": {
+ "name": "user_activity",
+ "schema": "base",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_activity_user_id_users_id_fk": {
+ "name": "user_activity_user_id_users_id_fk",
+ "tableFrom": "user_activity",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.user_notifications": {
+ "name": "user_notifications",
+ "schema": "base",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "settings": {
+ "name": "settings",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"email\":{\"task_assigned\":true,\"mentions\":true,\"daily_summary\":false},\"push\":{\"task_assigned\":true,\"reminders\":true}}'::jsonb"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_notifications_user_id_users_id_fk": {
+ "name": "user_notifications_user_id_users_id_fk",
+ "tableFrom": "user_notifications",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.user_security": {
+ "name": "user_security",
+ "schema": "base",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_2fa_enabled": {
+ "name": "is_2fa_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "two_factor_secret": {
+ "name": "two_factor_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_password_change": {
+ "name": "last_password_change",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_security_user_id_users_id_fk": {
+ "name": "user_security_user_id_users_id_fk",
+ "tableFrom": "user_security",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.users": {
+ "name": "users",
+ "schema": "base",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "first_name": {
+ "name": "first_name",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_name": {
+ "name": "last_name",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "middle_name": {
+ "name": "middle_name",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bio": {
+ "name": "bio",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "avatar_url": {
+ "name": "avatar_url",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'UTC'"
+ },
+ "language": {
+ "name": "language",
+ "type": "varchar(5)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ru'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.sessions": {
+ "name": "sessions",
+ "schema": "base",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "browser": {
+ "name": "browser",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "os": {
+ "name": "os",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip": {
+ "name": "ip",
+ "type": "varchar(45)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "city": {
+ "name": "city",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "country_code": {
+ "name": "country_code",
+ "type": "varchar(5)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_revoked": {
+ "name": "is_revoked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.tags": {
+ "name": "tags",
+ "schema": "base",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "tags_name_unique": {
+ "name": "tags_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.team_members": {
+ "name": "team_members",
+ "schema": "base",
+ "columns": {
+ "team_id": {
+ "name": "team_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "team_role",
+ "typeSchema": "base",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'member'"
+ },
+ "status": {
+ "name": "status",
+ "type": "member_status",
+ "typeSchema": "base",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "joined_at": {
+ "name": "joined_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "member_status_idx": {
+ "name": "member_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "team_members_team_id_teams_id_fk": {
+ "name": "team_members_team_id_teams_id_fk",
+ "tableFrom": "team_members",
+ "tableTo": "teams",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_members_user_id_users_id_fk": {
+ "name": "team_members_user_id_users_id_fk",
+ "tableFrom": "team_members",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_members_team_id_user_id_pk": {
+ "name": "team_members_team_id_user_id_pk",
+ "columns": [
+ "team_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.teams": {
+ "name": "teams",
+ "schema": "base",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "avatar_url": {
+ "name": "avatar_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cover_url": {
+ "name": "cover_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "team_slug_idx": {
+ "name": "team_slug_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "teams_owner_id_users_id_fk": {
+ "name": "teams_owner_id_users_id_fk",
+ "tableFrom": "teams",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "owner_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "teams_slug_unique": {
+ "name": "teams_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "slug"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.teams_to_tags": {
+ "name": "teams_to_tags",
+ "schema": "base",
+ "columns": {
+ "team_id": {
+ "name": "team_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tag_id": {
+ "name": "tag_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "teams_to_tags_team_id_teams_id_fk": {
+ "name": "teams_to_tags_team_id_teams_id_fk",
+ "tableFrom": "teams_to_tags",
+ "tableTo": "teams",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "teams_to_tags_tag_id_tags_id_fk": {
+ "name": "teams_to_tags_tag_id_tags_id_fk",
+ "tableFrom": "teams_to_tags",
+ "tableTo": "tags",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "tag_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "teams_to_tags_team_id_tag_id_pk": {
+ "name": "teams_to_tags_team_id_tag_id_pk",
+ "columns": [
+ "team_id",
+ "tag_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "base.team_role": {
+ "name": "team_role",
+ "schema": "base",
+ "values": [
+ "admin",
+ "moderator",
+ "member"
+ ]
+ },
+ "base.member_status": {
+ "name": "member_status",
+ "schema": "base",
+ "values": [
+ "pending",
+ "active",
+ "declined",
+ "banned"
+ ]
+ }
+ },
+ "schemas": {
+ "base": "base"
+ },
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/migrations/meta/0003_snapshot.json b/migrations/meta/0003_snapshot.json
new file mode 100644
index 0000000..3845d25
--- /dev/null
+++ b/migrations/meta/0003_snapshot.json
@@ -0,0 +1,808 @@
+{
+ "id": "6fbd096d-2d73-46c8-b4f9-a337fb5cb1c2",
+ "prevId": "995af10c-f9b7-416a-b20b-85034dbd20d5",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "base.user_activity": {
+ "name": "user_activity",
+ "schema": "base",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_activity_user_id_users_id_fk": {
+ "name": "user_activity_user_id_users_id_fk",
+ "tableFrom": "user_activity",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.user_notifications": {
+ "name": "user_notifications",
+ "schema": "base",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "settings": {
+ "name": "settings",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"email\":{\"task_assigned\":true,\"mentions\":true,\"daily_summary\":false},\"push\":{\"task_assigned\":true,\"reminders\":true}}'::jsonb"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_notifications_user_id_users_id_fk": {
+ "name": "user_notifications_user_id_users_id_fk",
+ "tableFrom": "user_notifications",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.user_security": {
+ "name": "user_security",
+ "schema": "base",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_2fa_enabled": {
+ "name": "is_2fa_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "two_factor_secret": {
+ "name": "two_factor_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_password_change": {
+ "name": "last_password_change",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_security_user_id_users_id_fk": {
+ "name": "user_security_user_id_users_id_fk",
+ "tableFrom": "user_security",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.users": {
+ "name": "users",
+ "schema": "base",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "first_name": {
+ "name": "first_name",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_name": {
+ "name": "last_name",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "middle_name": {
+ "name": "middle_name",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bio": {
+ "name": "bio",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "avatar_url": {
+ "name": "avatar_url",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'UTC'"
+ },
+ "language": {
+ "name": "language",
+ "type": "varchar(5)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ru'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.sessions": {
+ "name": "sessions",
+ "schema": "base",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "browser": {
+ "name": "browser",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "os": {
+ "name": "os",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip": {
+ "name": "ip",
+ "type": "varchar(45)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "city": {
+ "name": "city",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "country_code": {
+ "name": "country_code",
+ "type": "varchar(5)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_revoked": {
+ "name": "is_revoked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.tags": {
+ "name": "tags",
+ "schema": "base",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "tags_name_unique": {
+ "name": "tags_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.team_members": {
+ "name": "team_members",
+ "schema": "base",
+ "columns": {
+ "team_id": {
+ "name": "team_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "team_role",
+ "typeSchema": "base",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'member'"
+ },
+ "status": {
+ "name": "status",
+ "type": "member_status",
+ "typeSchema": "base",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'inactive'"
+ },
+ "joined_at": {
+ "name": "joined_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "member_status_idx": {
+ "name": "member_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "member_role_idx": {
+ "name": "member_role_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "role",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "team_members_team_id_teams_id_fk": {
+ "name": "team_members_team_id_teams_id_fk",
+ "tableFrom": "team_members",
+ "tableTo": "teams",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_members_user_id_users_id_fk": {
+ "name": "team_members_user_id_users_id_fk",
+ "tableFrom": "team_members",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_members_team_id_user_id_pk": {
+ "name": "team_members_team_id_user_id_pk",
+ "columns": [
+ "team_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.teams": {
+ "name": "teams",
+ "schema": "base",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "avatar_url": {
+ "name": "avatar_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cover_url": {
+ "name": "cover_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "team_active_slug_idx": {
+ "name": "team_active_slug_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"base\".\"teams\".\"deleted_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "team_slug_idx": {
+ "name": "team_slug_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "team_owner_idx": {
+ "name": "team_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "team_deleted_at_idx": {
+ "name": "team_deleted_at_idx",
+ "columns": [
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "teams_owner_id_users_id_fk": {
+ "name": "teams_owner_id_users_id_fk",
+ "tableFrom": "teams",
+ "tableTo": "users",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "owner_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "teams_slug_unique": {
+ "name": "teams_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "slug"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "base.teams_to_tags": {
+ "name": "teams_to_tags",
+ "schema": "base",
+ "columns": {
+ "team_id": {
+ "name": "team_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tag_id": {
+ "name": "tag_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "teams_to_tags_tag_id_idx": {
+ "name": "teams_to_tags_tag_id_idx",
+ "columns": [
+ {
+ "expression": "tag_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "teams_to_tags_team_id_teams_id_fk": {
+ "name": "teams_to_tags_team_id_teams_id_fk",
+ "tableFrom": "teams_to_tags",
+ "tableTo": "teams",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "teams_to_tags_tag_id_tags_id_fk": {
+ "name": "teams_to_tags_tag_id_tags_id_fk",
+ "tableFrom": "teams_to_tags",
+ "tableTo": "tags",
+ "schemaTo": "base",
+ "columnsFrom": [
+ "tag_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "teams_to_tags_team_id_tag_id_pk": {
+ "name": "teams_to_tags_team_id_tag_id_pk",
+ "columns": [
+ "team_id",
+ "tag_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "base.team_role": {
+ "name": "team_role",
+ "schema": "base",
+ "values": [
+ "owner",
+ "admin",
+ "lead",
+ "moderator",
+ "member",
+ "viewer"
+ ]
+ },
+ "base.member_status": {
+ "name": "member_status",
+ "schema": "base",
+ "values": [
+ "active",
+ "banned",
+ "inactive"
+ ]
+ }
+ },
+ "schemas": {
+ "base": "base"
+ },
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json
index 713b19d..696b35a 100644
--- a/migrations/meta/_journal.json
+++ b/migrations/meta/_journal.json
@@ -1,20 +1,34 @@
{
- "version": "7",
- "dialect": "postgresql",
- "entries": [
- {
- "idx": 0,
- "version": "7",
- "when": 1775839169154,
- "tag": "0000_stale_sunspot",
- "breakpoints": true
- },
- {
- "idx": 1,
- "version": "7",
- "when": 1775925642197,
- "tag": "0001_solid_kronos",
- "breakpoints": true
- }
- ]
-}
+ "version": "7",
+ "dialect": "postgresql",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "7",
+ "when": 1775839169154,
+ "tag": "0000_stale_sunspot",
+ "breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "7",
+ "when": 1775925642197,
+ "tag": "0001_solid_kronos",
+ "breakpoints": true
+ },
+ {
+ "idx": 2,
+ "version": "7",
+ "when": 1776100122085,
+ "tag": "0002_pink_krista_starr",
+ "breakpoints": true
+ },
+ {
+ "idx": 3,
+ "version": "7",
+ "when": 1776171079742,
+ "tag": "0003_open_oracle",
+ "breakpoints": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index f9cd3cf..bc6c9f4 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"@fastify/compress": "^8.3.1",
"@fastify/cookie": "^11.0.2",
"@fastify/cors": "^11.2.0",
+ "@fastify/csrf-protection": "^7.1.0",
"@fastify/multipart": "^10.0.0",
"@fastify/static": "^9.1.0",
"@nestjs-modules/ioredis": "^2.2.1",
@@ -63,6 +64,7 @@
"pg": "^8.20.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
+ "transliteration": "^2.6.1",
"ua-parser-js": "^2.0.9",
"zod": "^4.3.6"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4d0faec..5df5466 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,6 +32,9 @@ importers:
'@fastify/cors':
specifier: ^11.2.0
version: 11.2.0
+ '@fastify/csrf-protection':
+ specifier: ^7.1.0
+ version: 7.1.0
'@fastify/multipart':
specifier: ^10.0.0
version: 10.0.0
@@ -125,6 +128,9 @@ importers:
rxjs:
specifier: ^7.8.1
version: 7.8.2
+ transliteration:
+ specifier: ^2.6.1
+ version: 2.6.1
ua-parser-js:
specifier: ^2.0.9
version: 2.0.9
@@ -1064,6 +1070,12 @@ packages:
'@fastify/cors@11.2.0':
resolution: {integrity: sha512-LbLHBuSAdGdSFZYTLVA3+Ch2t+sA6nq3Ejc6XLAKiQ6ViS2qFnvicpj0htsx03FyYeLs04HfRNBsz/a8SvbcUw==}
+ '@fastify/csrf-protection@7.1.0':
+ resolution: {integrity: sha512-I2TDd4SRRYQivKCMHdB/8py+CPO9DT0e63lh4DO8MDCJh8NROq8HD/iO0IjYtwhsD3bZhr0cBXsFdfPvyTmzNw==}
+
+ '@fastify/csrf@8.0.1':
+ resolution: {integrity: sha512-dAmCrdfJ3CV/A/hHHK/rRBjjLRRSIltgJB0BxiVfbhr/31G6fgF8l2I8evtH8mjS5kTIvd0JOh7MOA3HA6eYDw==}
+
'@fastify/deepmerge@3.2.1':
resolution: {integrity: sha512-N5Oqvltoa2r9z1tbx4xjky0oRR60v+T47Ic4J1ukoVQcptLOrIdRnCSdTGmOmajZuHVKlTnfcmrjyqsGEW1ztA==}
@@ -4348,6 +4360,11 @@ packages:
resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==}
engines: {node: '>=14.16'}
+ transliteration@2.6.1:
+ resolution: {integrity: sha512-hJ9BhrQAOnNTbpOr1MxsNjZISkn7ppvF5TKUeFmTE1mG4ZPD/XVxF0L0LUoIUCWmQyxH0gJpVtfYLAWf298U9w==}
+ engines: {node: '>=20.0.0'}
+ hasBin: true
+
ts-api-utils@1.4.3:
resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==}
engines: {node: '>=16'}
@@ -5649,6 +5666,14 @@ snapshots:
fastify-plugin: 5.1.0
toad-cache: 3.7.0
+ '@fastify/csrf-protection@7.1.0':
+ dependencies:
+ '@fastify/csrf': 8.0.1
+ '@fastify/error': 4.2.0
+ fastify-plugin: 5.1.0
+
+ '@fastify/csrf@8.0.1': {}
+
'@fastify/deepmerge@3.2.1': {}
'@fastify/error@4.2.0': {}
@@ -8994,6 +9019,8 @@ snapshots:
'@tokenizer/token': 0.3.0
ieee754: 1.2.1
+ transliteration@2.6.1: {}
+
ts-api-utils@1.4.3(typescript@5.9.3):
dependencies:
typescript: 5.9.3
diff --git a/src/main.ts b/src/main.ts
index de5c124..e414faf 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -11,7 +11,7 @@ bootstrapApp({
title: 'Task Tracker API',
description: 'API бэкенда таск-трекера',
version: '0.1.0',
- path: 'ui',
+ path: 'docs',
},
useCors: true,
useCookieParser: true,
diff --git a/src/modules/app/app.module.ts b/src/modules/app/app.module.ts
index 0e01a2c..1db26a0 100644
--- a/src/modules/app/app.module.ts
+++ b/src/modules/app/app.module.ts
@@ -15,8 +15,8 @@ import { FastifyAdapter } from '@bull-board/fastify';
import { MailProcessor } from 'src/shared/workers';
import { BullModule } from '@nestjs/bullmq';
import { MailAdapter } from 'src/shared/adapters/mail';
-import { S3Module } from '@libs/s3';
import { MigrationService } from 'src/shared/migration';
+import { TeamsModule } from '../teams';
@Module({
imports: [
@@ -40,23 +40,6 @@ import { MigrationService } from 'src/shared/migration';
};
},
}),
- S3Module.registerAsync({
- inject: [ConfigService],
- global: true,
- useFactory: (cfg: ConfigService) => ({
- connection: {
- bucket: cfg.getOrThrow('S3_BUCKET_NAME'),
- endpoint: cfg.getOrThrow('S3_ENDPOINT'),
- region: cfg.getOrThrow('S3_REGION'),
- credentials: {
- accessKeyId: cfg.getOrThrow('S3_ACCESS_KEY'),
- secretAccessKey: cfg.getOrThrow('S3_SECRET_KEY'),
- },
- },
- // FOR MINIO COMPARTABLE
- config: { forcePathStyle: true },
- }),
- }),
BullModule.forRootAsync({
inject: [ConfigService],
useFactory: (cfg: ConfigService) => ({
@@ -68,6 +51,7 @@ import { MigrationService } from 'src/shared/migration';
}),
AuthModule,
UserModule,
+ TeamsModule,
BullBoardModule.forRoot({
route: '/queues',
adapter: FastifyAdapter,
diff --git a/src/modules/auth/controller/auth.controller.ts b/src/modules/auth/controller/auth.controller.ts
index acb1689..8acc890 100644
--- a/src/modules/auth/controller/auth.controller.ts
+++ b/src/modules/auth/controller/auth.controller.ts
@@ -1,6 +1,6 @@
import { ApiBaseController } from '../../../shared/decorators';
import { Body, HttpCode, Post, Req, Res, UseGuards } from '@nestjs/common';
-import { AuthService } from '../services/auth.service';
+import { AuthService } from '../services';
import {
PostLoginSwagger,
PostLogoutSwagger,
diff --git a/src/modules/auth/services/auth.service.ts b/src/modules/auth/services/auth.service.ts
index 093b1f3..90ca5f7 100644
--- a/src/modules/auth/services/auth.service.ts
+++ b/src/modules/auth/services/auth.service.ts
@@ -48,6 +48,17 @@ export class AuthService {
) {}
public signUp = async (dto: SignUpDto) => {
+ const redisKey = `reg:${dto.email}`;
+
+ const cachedData = await this.redis.get(redisKey);
+
+ if (cachedData) {
+ throw new BadRequestException({
+ code: 'REGISTRATION_IN_PROGRESS',
+ message: 'Код уже был отправлен. Проверьте почту или подождите 15 минут.',
+ });
+ }
+
const isValidEmail = validate(dto.email);
if (!isValidEmail) {
@@ -116,6 +127,7 @@ export class AuthService {
const userData = JSON.parse(cachedData);
+ // TODO: APPORCH WINDOW STEP INLIGHT
const verifyResult = await verifyOTP({
token: dto.code,
secret: userData.otp.secret,
@@ -123,6 +135,7 @@ export class AuthService {
digits: 6,
period: 900,
strategy: 'totp',
+ afterTimeStep: 1,
});
if (!verifyResult.valid) {
diff --git a/src/modules/media/dtos/index.ts b/src/modules/media/dtos/index.ts
new file mode 100644
index 0000000..9f9e6fe
--- /dev/null
+++ b/src/modules/media/dtos/index.ts
@@ -0,0 +1,2 @@
+export * from './upload-file.dto';
+export * from './upload-file-response.dto';
diff --git a/src/modules/media/dtos/upload-file-response.dto.ts b/src/modules/media/dtos/upload-file-response.dto.ts
new file mode 100644
index 0000000..9c6662f
--- /dev/null
+++ b/src/modules/media/dtos/upload-file-response.dto.ts
@@ -0,0 +1,12 @@
+import { createZodDto } from 'nestjs-zod';
+import { z } from 'zod/v4';
+
+export const FileUploadResponseSchema = z.object({
+ success: z.boolean().describe('Статус операции'),
+ url: z.string().describe('URL загруженного файла'),
+ message: z.string().optional().describe('Сообщение для пользователя'),
+});
+
+export type FileUploadResponseDto = z.infer;
+
+export class FileUploadResponse extends createZodDto(FileUploadResponseSchema) {}
diff --git a/libs/s3/src/dtos/upload-avatar.dto.ts b/src/modules/media/dtos/upload-file.dto.ts
similarity index 100%
rename from libs/s3/src/dtos/upload-avatar.dto.ts
rename to src/modules/media/dtos/upload-file.dto.ts
diff --git a/src/modules/media/interfaces/team-media.interface.ts b/src/modules/media/interfaces/team-media.interface.ts
new file mode 100644
index 0000000..5e5ef8c
--- /dev/null
+++ b/src/modules/media/interfaces/team-media.interface.ts
@@ -0,0 +1,16 @@
+import { FileUploadDto, FileUploadResponse } from '../dtos';
+
+export const TEAM_MEDIA_TOKEN = 'ITeamMedia';
+
+export interface ITeamMedia {
+ uploadTeamAvatar(
+ teamId: string,
+ file: FileUploadDto,
+ updateFn: (url: string) => Promise,
+ ): Promise;
+ uploadTeamBanner(
+ teamId: string,
+ file: FileUploadDto,
+ updateFn: (url: string) => Promise,
+ ): Promise;
+}
diff --git a/src/modules/media/interfaces/user-media.interface.ts b/src/modules/media/interfaces/user-media.interface.ts
new file mode 100644
index 0000000..f0c2c47
--- /dev/null
+++ b/src/modules/media/interfaces/user-media.interface.ts
@@ -0,0 +1,11 @@
+import { FileUploadDto, FileUploadResponse } from '../dtos';
+
+export const USER_MEDIA_TOKEN = 'IUserMedia';
+
+export interface IUserMedia {
+ uploadUserAvatar(
+ userId: string,
+ file: FileUploadDto,
+ updateFn: (url: string) => Promise,
+ ): Promise;
+}
diff --git a/src/modules/media/media.module.ts b/src/modules/media/media.module.ts
new file mode 100644
index 0000000..8eff7d7
--- /dev/null
+++ b/src/modules/media/media.module.ts
@@ -0,0 +1,40 @@
+import { Module } from '@nestjs/common';
+import { MediaService } from './media.service';
+import { S3Module } from '@libs/s3';
+import { USER_MEDIA_TOKEN } from './interfaces/user-media.interface';
+import { TEAM_MEDIA_TOKEN } from './interfaces/team-media.interface';
+import { ConfigService } from '@nestjs/config';
+
+@Module({
+ imports: [
+ S3Module.registerAsync({
+ inject: [ConfigService],
+ useFactory: (cfg: ConfigService) => ({
+ connection: {
+ bucket: cfg.getOrThrow('S3_BUCKET_NAME'),
+ endpoint: cfg.getOrThrow('S3_ENDPOINT'),
+ region: cfg.getOrThrow('S3_REGION'),
+ credentials: {
+ accessKeyId: cfg.getOrThrow('S3_ACCESS_KEY'),
+ secretAccessKey: cfg.getOrThrow('S3_SECRET_KEY'),
+ },
+ },
+ // FOR MINIO COMPARTABLE
+ config: { forcePathStyle: true },
+ }),
+ }),
+ ],
+ providers: [
+ MediaService,
+ {
+ provide: USER_MEDIA_TOKEN,
+ useExisting: MediaService,
+ },
+ {
+ provide: TEAM_MEDIA_TOKEN,
+ useExisting: MediaService,
+ },
+ ],
+ exports: [USER_MEDIA_TOKEN, TEAM_MEDIA_TOKEN],
+})
+export class MediaModule {}
diff --git a/src/modules/media/media.service.ts b/src/modules/media/media.service.ts
new file mode 100644
index 0000000..dda27d7
--- /dev/null
+++ b/src/modules/media/media.service.ts
@@ -0,0 +1,60 @@
+import { BadRequestException, Injectable } from '@nestjs/common';
+import { S3Service } from '@libs/s3';
+import { FileUploadDto, FileUploadResponseDto } from './dtos';
+import { IUserMedia } from './interfaces/user-media.interface';
+import { ITeamMedia } from './interfaces/team-media.interface';
+
+@Injectable()
+export class MediaService implements IUserMedia, ITeamMedia {
+ constructor(private readonly s3: S3Service) {}
+
+ private async uploadAndLink(
+ file: FileUploadDto,
+ folder: string,
+ updateDbFn: (url: string) => Promise,
+ ): Promise {
+ const url = await this.s3.uploadFile(file.buffer, file.filename, file.mimetype, folder);
+
+ try {
+ const isUpdated = await updateDbFn(url);
+
+ if (!isUpdated) {
+ throw new Error('ENTITY_NOT_FOUND');
+ }
+
+ return { success: true, url };
+ } catch (error) {
+ await this.s3.deleteFile(url);
+
+ if (error.message === 'ENTITY_NOT_FOUND') {
+ throw new BadRequestException('Сущность не найдена, обновление отменено');
+ }
+
+ throw new BadRequestException('Ошибка при сохранении медиа-данных');
+ }
+ }
+
+ public async uploadUserAvatar(
+ userId: string,
+ file: FileUploadDto,
+ updateFn: (url: string) => Promise,
+ ) {
+ return this.uploadAndLink(file, `users/${userId}/avatars`, updateFn);
+ }
+
+ public async uploadTeamAvatar(
+ teamId: string,
+ file: FileUploadDto,
+ updateFn: (url: string) => Promise,
+ ) {
+ return this.uploadAndLink(file, `teams/${teamId}/avatars`, updateFn);
+ }
+
+ public async uploadTeamBanner(
+ teamId: string,
+ file: FileUploadDto,
+ updateFn: (url: string) => Promise,
+ ) {
+ return this.uploadAndLink(file, `teams/${teamId}/banners`, updateFn);
+ }
+}
diff --git a/src/modules/teams/controller/index.ts b/src/modules/teams/controller/index.ts
new file mode 100644
index 0000000..be1bbc7
--- /dev/null
+++ b/src/modules/teams/controller/index.ts
@@ -0,0 +1,2 @@
+export { TeamsController } from './teams.controller';
+export { MembersController } from './members.controller';
diff --git a/src/modules/teams/controller/members.controller.ts b/src/modules/teams/controller/members.controller.ts
new file mode 100644
index 0000000..4a97594
--- /dev/null
+++ b/src/modules/teams/controller/members.controller.ts
@@ -0,0 +1,56 @@
+import { Body, Delete, Get, Param, Patch, Post } from '@nestjs/common';
+import { ApiBaseController, GetUser, GetUserId } from 'src/shared/decorators';
+import { MembersService } from '../services';
+import {
+ AcceptInviteSwagger,
+ GetMembersSwagger,
+ InviteMemberSwagger,
+ RemoveMemberSwagger,
+ UpdateMemberSwagger,
+} from './teams.swagger';
+import type { JwtPayload } from 'src/modules/auth/types';
+import type { UpdateMemberDto } from '../dtos/member.dto';
+
+@ApiBaseController('teams/:slug', 'Teams', true)
+export class MembersController {
+ constructor(private readonly facade: MembersService) {}
+
+ @Get('members')
+ @GetMembersSwagger()
+ async getMembers(@Param('slug') slug: string) {
+ return this.facade.getMembers(slug);
+ }
+
+ @Post('invitations')
+ @InviteMemberSwagger()
+ async invite(@Param('slug') slug: string, @GetUserId() inviterId: string, @Body() dto: any) {
+ return this.facade.invite(slug, inviterId, dto);
+ }
+
+ @Post('invitations/:code/accept')
+ @AcceptInviteSwagger()
+ async accept(@Param('code') code: string, @GetUser() user: JwtPayload) {
+ return this.facade.acceptInvite(code, user.sub, user.email);
+ }
+
+ @Patch('members/:userId')
+ @UpdateMemberSwagger()
+ async updateMember(
+ @Param('slug') slug: string,
+ @Param('userId') targetUserId: string,
+ @GetUserId() currentUserId: string,
+ @Body() dto: UpdateMemberDto,
+ ) {
+ return this.facade.updateMember(slug, currentUserId, targetUserId, dto);
+ }
+
+ @Delete('members/:userId')
+ @RemoveMemberSwagger()
+ async removeMember(
+ @Param('slug') slug: string,
+ @Param('userId') targerUserId: string,
+ @GetUserId() currentUserId: string,
+ ) {
+ return this.facade.removeMember(slug, currentUserId, targerUserId);
+ }
+}
diff --git a/src/modules/teams/controller/teams.controller.ts b/src/modules/teams/controller/teams.controller.ts
new file mode 100644
index 0000000..99296ae
--- /dev/null
+++ b/src/modules/teams/controller/teams.controller.ts
@@ -0,0 +1,101 @@
+import {
+ Body,
+ Delete,
+ Get,
+ HttpCode,
+ HttpStatus,
+ Param,
+ Patch,
+ Post,
+ Put,
+ Query,
+} from '@nestjs/common';
+import { ApiBaseController, ExtractFastifyFile, GetUser, GetUserId } from 'src/shared/decorators';
+import { TeamsService } from '../services';
+import {
+ CreateTeamSwagger,
+ FindOneTeamSwagger,
+ RemoveTeamSwagger,
+ SyncTeamTagsSwagger,
+ UpdateTeamSwagger,
+ PatchTeamAvatarSwagger,
+ PatchTeamBannerSwagger,
+ FindTeamsSwagger,
+ CheckSlugSwagger,
+ FindInvitesSwagger,
+} from './teams.swagger';
+import type { FileUploadDto } from '../../media/dtos';
+import type { CreateTeamDto, SyncTagsDto } from '../dtos';
+import type { JwtPayload } from 'src/modules/auth/types';
+
+@ApiBaseController('teams', 'Teams', true)
+export class TeamsController {
+ constructor(private readonly facade: TeamsService) {}
+
+ @Post()
+ @CreateTeamSwagger()
+ async create(@GetUserId() userId: string, @Body() dto: CreateTeamDto) {
+ return this.facade.create(userId, dto);
+ }
+
+ @Get('check-slug/:slug')
+ @CheckSlugSwagger()
+ async checkSlug(@Param('slug') slug: string) {
+ return this.facade.checkSlug(slug);
+ }
+
+ @Get('my')
+ @FindTeamsSwagger()
+ async findAll(@GetUserId() userId: string, @Query() query: any) {
+ return this.facade.getAll(userId, query);
+ }
+
+ @Get('my/invites')
+ @FindInvitesSwagger()
+ async findAllInvites(@GetUser() user: JwtPayload) {
+ return this.facade.getMyInvites(user.email);
+ }
+
+ @Get(':slug')
+ @FindOneTeamSwagger()
+ async findOne(@Param('slug') slug: string) {
+ return this.facade.getOne(slug);
+ }
+
+ @Patch(':slug')
+ @UpdateTeamSwagger()
+ async update(@Param('slug') slug: string, @GetUserId() userId: string, @Body() dto: any) {
+ return this.facade.update(slug, userId, dto);
+ }
+
+ @Delete(':slug')
+ @RemoveTeamSwagger()
+ @HttpCode(HttpStatus.OK)
+ async remove(@Param('slug') slug: string, @GetUserId() userId: string) {
+ return this.facade.remove(slug, userId);
+ }
+
+ @Put(':slug/tags')
+ @SyncTeamTagsSwagger()
+ async syncTags(@Param('slug') slug: string, @Body() dto: SyncTagsDto) {
+ return this.facade.syncTags(slug, dto.tags);
+ }
+
+ @Patch(':slug/avatar')
+ @PatchTeamAvatarSwagger()
+ async updateTeamAvatar(
+ @ExtractFastifyFile() fileDto: FileUploadDto,
+ @Param('slug') slug: string,
+ ) {
+ return this.facade.updateTeamAvatar(slug, fileDto);
+ }
+
+ @Patch(':slug/banner')
+ @PatchTeamBannerSwagger()
+ async updateTeamBanner(
+ @ExtractFastifyFile() fileDto: FileUploadDto,
+ @Param('slug') slug: string,
+ ) {
+ return this.facade.updateTeamBanner(slug, fileDto);
+ }
+}
diff --git a/src/modules/teams/controller/teams.swagger.ts b/src/modules/teams/controller/teams.swagger.ts
new file mode 100644
index 0000000..494713a
--- /dev/null
+++ b/src/modules/teams/controller/teams.swagger.ts
@@ -0,0 +1,308 @@
+import { applyDecorators } from '@nestjs/common';
+import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiConsumes } from '@nestjs/swagger';
+import { ActionResponse } from 'src/shared/dtos';
+import {
+ ApiBadRequest,
+ ApiConflict,
+ ApiForbidden,
+ ApiNotFound,
+ ApiUnauthorized,
+ ApiValidationError,
+} from 'src/shared/error';
+import {
+ CreateTeamDto,
+ InviteMemberDto,
+ SyncTagsDto,
+ UpdateTeamDto,
+ TagResponse,
+ TeamMemberResponse,
+ CheckSlugResponse,
+ UpdateMemberDto,
+ UserTeamResponse,
+ UserInviteResponse,
+} from '../dtos';
+import { FileUploadResponse } from '../../media/dtos';
+
+export const CreateTeamSwagger = () =>
+ applyDecorators(
+ ApiOperation({ summary: 'Создать новую команду' }),
+ ApiBody({ type: CreateTeamDto.Output }),
+ ApiResponse({
+ status: 201,
+ description: 'Команда успешно создана',
+ type: ActionResponse.Output,
+ }),
+ ApiConflict('Команда с таким slug уже существует'),
+ ApiValidationError(),
+ ApiUnauthorized(),
+ );
+
+export const CheckSlugSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Проверить доступность слага',
+ description: 'Проверяет, свободен ли уникальный адрес команды для использования.',
+ }),
+ ApiParam({
+ name: 'slug',
+ description: 'Желаемый слаг команды',
+ example: 'my-super-team',
+ }),
+ ApiResponse({
+ status: 200,
+ description: 'Результат проверки доступности',
+ type: CheckSlugResponse.Output,
+ }),
+ ApiUnauthorized(),
+ );
+
+export const FindTeamsSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Получить список команд пользователя',
+ description:
+ 'Возвращает все команды, в которых текущий пользователь является участником или владельцем.',
+ }),
+ ApiResponse({
+ status: 200,
+ description: 'Список команд получен',
+ type: [UserTeamResponse.Output],
+ }),
+ ApiUnauthorized(),
+ );
+
+export const FindInvitesSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Получить список входящих приглашений',
+ description:
+ 'Возвращает все активные приглашения в команды, отправленные на email текущего пользователя.',
+ }),
+ ApiResponse({
+ status: 200,
+ description: 'Список приглашений успешно получен',
+ type: [UserInviteResponse.Output],
+ }),
+ ApiUnauthorized(),
+ );
+
+export const FindOneTeamSwagger = () =>
+ applyDecorators(
+ ApiOperation({ summary: 'Получить детальную информацию о команде по slug' }),
+ ApiParam({ name: 'slug', description: 'Уникальный идентификатор (слаг) команды' }),
+ ApiResponse({
+ status: 200,
+ description: 'Данные команды получены',
+ type: Object,
+ }),
+ ApiNotFound('Команда не найдена'),
+ ApiUnauthorized(),
+ );
+
+export const UpdateTeamSwagger = () =>
+ applyDecorators(
+ ApiOperation({ summary: 'Обновить данные команды' }),
+ ApiBody({ type: UpdateTeamDto.Output }),
+ ApiParam({ name: 'slug', description: 'Слаг команды для редактирования' }),
+ ApiResponse({
+ status: 200,
+ description: 'Команда успешно обновлена',
+ type: ActionResponse.Output,
+ }),
+ ApiForbidden(),
+ ApiNotFound(),
+ ApiValidationError(),
+ ApiUnauthorized(),
+ );
+
+export const RemoveTeamSwagger = () =>
+ applyDecorators(
+ ApiOperation({ summary: 'Удалить команду' }),
+ ApiParam({ name: 'slug', description: 'Слаг команды для удаления' }),
+ ApiResponse({
+ status: 200,
+ description: 'Команда успешно удалена',
+ type: ActionResponse.Output,
+ }),
+ ApiForbidden(),
+ ApiNotFound(),
+ ApiUnauthorized(),
+ );
+
+export const SyncTeamTagsSwagger = () =>
+ applyDecorators(
+ ApiOperation({ summary: 'Синхронизировать теги команды' }),
+ ApiBody({ type: SyncTagsDto.Output }),
+ ApiResponse({ status: 200, description: 'Теги обновлены', type: ActionResponse.Output }),
+ ApiForbidden(),
+ ApiNotFound(),
+ ApiUnauthorized(),
+ );
+
+export const GetAllTagsSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Получить список всех тегов с пагинацией',
+ description:
+ 'Возвращает список всех тегов в системе с пагинацией. Используется для поиска и автокомплита при создании/редактировании команд.',
+ }),
+ ApiResponse({
+ status: 200,
+ description: 'Список тегов успешно получен',
+ type: TagResponse.Output,
+ }),
+ ApiUnauthorized(),
+ );
+
+export const GetMembersSwagger = () =>
+ applyDecorators(
+ ApiOperation({ summary: 'Получить список всех участников команды' }),
+ ApiParam({ name: 'slug', description: 'Слаг команды' }),
+ ApiResponse({
+ status: 200,
+ description: 'Список участников получен',
+ type: [TeamMemberResponse.Output],
+ }),
+ ApiUnauthorized(),
+ ApiForbidden(),
+ );
+
+export const InviteMemberSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Пригласить пользователя в команду по Email',
+ description:
+ 'Создает запись об участнике со статусом "pending".' +
+ ' Если пользователь уже зарегистрирован — он увидит приглашение в разделе "my/invites".' +
+ ' Если нет — ему уйдет письмо на указанный Email.',
+ }),
+ ApiBody({ type: InviteMemberDto.Output }),
+ ApiParam({ name: 'slug', description: 'Слаг команды, в которую приглашаем' }),
+ ApiResponse({
+ status: 201,
+ description: 'Инвайт создан и отправлен',
+ type: ActionResponse.Output,
+ }),
+ ApiValidationError('Некорректный формат Email или роль не поддерживается'),
+ ApiUnauthorized(),
+ ApiForbidden(),
+ );
+
+export const UpdateMemberSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Изменить роль или статус участника',
+ description:
+ 'Позволяет изменить роль участника (member -> admin) или вручную изменить его статус.' +
+ ' Владелец команды (Owner) не может понизить свою роль через этот эндпоинт.',
+ }),
+ ApiBody({ type: UpdateMemberDto.Output }),
+ ApiParam({ name: 'slug', description: 'Слаг команды' }),
+ ApiParam({ name: 'userId', description: 'ID пользователя, чьи права редактируются' }),
+ ApiResponse({
+ status: 200,
+ description: 'Данные участника обновлены',
+ type: ActionResponse.Output,
+ }),
+ ApiNotFound('Участник или команда не найдены'),
+ ApiUnauthorized(),
+ ApiForbidden(),
+ );
+
+export const RemoveMemberSwagger = () =>
+ applyDecorators(
+ ApiOperation({ summary: 'Удалить участника из команды' }),
+ ApiParam({ name: 'slug', description: 'Слаг команды' }),
+ ApiParam({ name: 'userId', description: 'ID пользователя' }),
+ ApiResponse({
+ status: 200,
+ type: ActionResponse.Output,
+ description: 'Участник успешно удален',
+ }),
+ ApiNotFound(),
+ ApiUnauthorized(),
+ ApiForbidden(),
+ );
+
+export const PatchTeamAvatarSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Обновить аватар команды',
+ description: 'Загрузка файла изображения для профиля команды.',
+ }),
+ ApiConsumes('multipart/form-data'),
+ ApiBody({
+ schema: {
+ type: 'object',
+ properties: {
+ file: {
+ type: 'string',
+ format: 'binary',
+ },
+ },
+ },
+ }),
+ ApiResponse({
+ status: 200,
+ description: 'Аватар команды успешно обновлен.',
+ type: FileUploadResponse.Output,
+ }),
+ ApiBadRequest('Файл не передан или имеет неверный формат'),
+ ApiNotFound('Команда не найдена'),
+ ApiUnauthorized(),
+ ApiForbidden(),
+ );
+
+export const PatchTeamBannerSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Обновить баннер команды',
+ description: 'Загрузка файла изображения для обложки (баннера) команды.',
+ }),
+ ApiConsumes('multipart/form-data'),
+ ApiBody({
+ schema: {
+ type: 'object',
+ properties: {
+ file: {
+ type: 'string',
+ format: 'binary',
+ },
+ },
+ },
+ }),
+ ApiResponse({
+ status: 200,
+ description: 'Баннер команды успешно обновлен.',
+ type: FileUploadResponse.Output,
+ }),
+ ApiBadRequest('Файл не передан или имеет неверный формат'),
+ ApiNotFound('Команда не найдена'),
+ ApiUnauthorized(),
+ ApiForbidden(),
+ );
+
+export const AcceptInviteSwagger = () =>
+ applyDecorators(
+ ApiOperation({
+ summary: 'Принять приглашение в команду',
+ description:
+ 'Активирует участие пользователя в команде по уникальному коду приглашения.' +
+ ' После успешного принятия статус участника меняется с "pending" на "active".' +
+ ' Система автоматически связывает текущего авторизованного пользователя с инвайтом через Email.',
+ }),
+ ApiParam({
+ name: 'code',
+ description: 'Уникальный код/токен приглашения (из ссылки или письма)',
+ example: '7df1-4a2b-9e8c',
+ }),
+ ApiResponse({
+ status: 200,
+ description: 'Приглашение успешно принято. Пользователь теперь участник команды.',
+ type: ActionResponse.Output,
+ }),
+ ApiBadRequest('Невалидный код, срок действия приглашения истек или оно уже использовано'),
+ ApiNotFound('Приглашение с таким кодом не найдено'),
+ ApiConflict('Пользователь уже является участником этой команды'),
+ ApiUnauthorized(),
+ );
diff --git a/src/modules/teams/dtos/index.ts b/src/modules/teams/dtos/index.ts
new file mode 100644
index 0000000..fcd13e2
--- /dev/null
+++ b/src/modules/teams/dtos/index.ts
@@ -0,0 +1,15 @@
+export {
+ InviteMemberDto,
+ UpdateMemberDto,
+ TeamMemberResponse,
+ UserInviteResponse,
+} from './member.dto';
+export {
+ CreateTeamDto,
+ UpdateTeamDto,
+ FindTagsQuery,
+ SyncTagsDto,
+ UserTeamResponse,
+ TagResponse,
+ CheckSlugResponse,
+} from './team.dto';
diff --git a/src/modules/teams/dtos/member.dto.ts b/src/modules/teams/dtos/member.dto.ts
new file mode 100644
index 0000000..80eb841
--- /dev/null
+++ b/src/modules/teams/dtos/member.dto.ts
@@ -0,0 +1,56 @@
+import { z } from 'zod/v4';
+import { createZodDto } from 'nestjs-zod';
+
+export const InviteMemberSchema = z.object({
+ email: z.string().email().describe('Email пользователя, которого нужно пригласить'),
+ role: z
+ .string()
+ .default('member')
+ .describe('Роль, которая будет назначена пользователю после принятия инвайта'),
+});
+
+export class InviteMemberDto extends createZodDto(InviteMemberSchema) {}
+
+const UpdateMemberDtoSchema = z.object({
+ role: z.string().optional().describe('Новая роль участника'),
+ status: z.string().optional().describe('Новый статус (active, blocked и т.д.)'),
+});
+
+export class UpdateMemberDto extends createZodDto(UpdateMemberDtoSchema) {}
+
+export const TeamMemberResponseSchema = z.object({
+ id: z.string().describe('Уникальный ID пользователя (UUID или ULID)'),
+ role: z
+ .enum(['owner', 'admin', 'member'])
+ .describe('Роль участника в рамках конкретной команды'),
+ status: z
+ .enum(['active', 'pending', 'blocked'])
+ .describe('Текущий статус членства (активен, ожидает приглашения, заблокирован)'),
+ fullName: z.string().describe('Полное имя для отображения (Фамилия Имя Отчество)'),
+ firstName: z.string().describe('Имя пользователя'),
+ lastName: z.string().describe('Фамилия пользователя'),
+ avatarUrl: z
+ .string()
+ .url()
+ .nullable()
+ .describe('Прямая ссылка на изображение профиля или null, если не задано'),
+
+ initials: z.string().max(2).describe('Две буквы для аватара-заглушки (например, "ИИ")'),
+ joinedAt: z
+ .string()
+ .datetime()
+ .describe('Дата и время вступления в команду в формате ISO 8601'),
+});
+
+export class TeamMemberResponse extends createZodDto(TeamMemberResponseSchema) {}
+
+export const UserInviteSchema = z.object({
+ code: z.string().describe('Код инвайта'),
+ teamName: z.string().describe('Название команды'),
+ teamAvatar: z.string().nullable().describe('Аватар команды'),
+ role: z.string().describe('Роль'),
+ inviterName: z.string().describe('Имя пригласившего'),
+ expiresAt: z.string().datetime().describe('Дата истечения'),
+});
+
+export class UserInviteResponse extends createZodDto(UserInviteSchema) {}
diff --git a/src/modules/teams/dtos/team.dto.ts b/src/modules/teams/dtos/team.dto.ts
new file mode 100644
index 0000000..0f45858
--- /dev/null
+++ b/src/modules/teams/dtos/team.dto.ts
@@ -0,0 +1,82 @@
+import { z } from 'zod/v4';
+import { createZodDto } from 'nestjs-zod';
+import { createPaginationSchema } from '../../../shared/schemas';
+
+export const CreateTeamSchema = z.object({
+ name: z.string().min(2).max(100).describe('Название команды, отображаемое в интерфейсе'),
+ description: z
+ .string()
+ .max(500)
+ .optional()
+ .describe('Краткое описание деятельности или целей команды'),
+ slug: z.string().optional().describe('Уникальная ссылка на изображение команду'),
+ tags: z
+ .array(z.string())
+ .optional()
+ .describe('Список строковых названий тегов для классификации'),
+});
+
+export class CreateTeamDto extends createZodDto(CreateTeamSchema) {}
+export class UpdateTeamDto extends createZodDto(CreateTeamSchema.partial()) {}
+
+export const TagSchema = z.object({
+ id: z.string().describe('Уникальный идентификатор тега (CUID2)'),
+ name: z.string().min(1).max(50).describe('Название тега (например, "Backend", "Design")'),
+});
+
+export const SyncTagsSchema = z.object({
+ tags: z
+ .array(z.string())
+ .min(1, 'Список тегов не может быть пустым')
+ .max(15, 'Нельзя добавить более 15 тегов за раз')
+ .describe(
+ 'Массив названий тегов для привязки к команде. Если тега нет в базе, он будет создан.',
+ ),
+});
+
+const FindTagsQuerySchema = z.object({
+ search: z.string().optional().describe('Поисковый запрос для фильтрации тегов по названию'),
+ page: z.coerce.number().int().min(1).default(1).describe('Номер страницы (от 1)'),
+ limit: z.coerce
+ .number()
+ .int()
+ .min(1)
+ .max(100)
+ .default(20)
+ .describe('Количество возвращаемых результатов (1-100)'),
+});
+
+export class TagResponse extends createZodDto(createPaginationSchema(TagSchema)) {}
+export class SyncTagsDto extends createZodDto(SyncTagsSchema) {}
+export class FindTagsQuery extends createZodDto(FindTagsQuerySchema) {}
+
+export const CheckSlugResponseSchema = z.object({
+ available: z
+ .boolean()
+ .describe('Флаг доступности: true — адрес свободен, false — уже занят или некорректен'),
+});
+
+export class CheckSlugResponse extends createZodDto(CheckSlugResponseSchema) {}
+
+export const TeamPermissionsSchema = z.object({
+ canEdit: z.boolean().describe('Разрешено ли редактировать настройки и профиль команды'),
+ canDelete: z
+ .boolean()
+ .describe('Разрешено ли полностью удалить команду (только для владельца)'),
+ canManageMembers: z.boolean().describe('Разрешено ли менять роли и исключать участников'),
+ canInvite: z.boolean().describe('Разрешено ли приглашать новых участников'),
+ isOwner: z.boolean().describe('Является ли текущий пользователь владельцем (Owner)'),
+});
+
+export const UserTeamSchema = z.object({
+ id: z.string().uuid().describe('Уникальный ID команды'),
+ name: z.string().describe('Название команды'),
+ slug: z.string().describe('Уникальный URL-путь команды'),
+ description: z.string().nullable().describe('Краткое описание команды'),
+ avatarUrl: z.string().nullable().describe('URL изображения профиля команды'),
+ role: z.string().describe('Системное название роли пользователя'),
+ joinedAt: z.string().datetime().describe('Дата, когда пользователь вступил в команду'),
+ permissions: TeamPermissionsSchema.describe('Объект прав доступа текущего пользователя'),
+});
+
+export class UserTeamResponse extends createZodDto(UserTeamSchema) {}
diff --git a/src/modules/teams/entities/enums.ts b/src/modules/teams/entities/enums.ts
new file mode 100644
index 0000000..a446d20
--- /dev/null
+++ b/src/modules/teams/entities/enums.ts
@@ -0,0 +1,15 @@
+import { baseSchema } from 'src/shared/entities';
+
+export const roleEnum = baseSchema.enum('team_role', [
+ 'owner',
+ 'admin', // управление юзерами, настройками
+ 'lead', // управление проектами
+ 'moderator', // чистка контента/сообщений
+ 'member', // обычный работяга
+ 'viewer', // просто смотрит
+]);
+export const statusEnum = baseSchema.enum('member_status', [
+ 'active', // Полноценный участник
+ 'banned', // Заблокирован не может вернуться по инвайту
+ 'inactive', // Доступ закрыт, но запись сохранена
+]);
diff --git a/src/modules/teams/entities/index.ts b/src/modules/teams/entities/index.ts
new file mode 100644
index 0000000..f996b3f
--- /dev/null
+++ b/src/modules/teams/entities/index.ts
@@ -0,0 +1,3 @@
+export { tags, teamsToTags, teams, teamMembers } from './teams.entity';
+export { roleEnum, statusEnum } from './enums';
+export * from './teams.domain';
diff --git a/src/modules/teams/entities/teams.domain.ts b/src/modules/teams/entities/teams.domain.ts
new file mode 100644
index 0000000..c1df53e
--- /dev/null
+++ b/src/modules/teams/entities/teams.domain.ts
@@ -0,0 +1,31 @@
+import type { InferSelectModel, InferInsertModel } from 'drizzle-orm';
+import { teams, teamMembers, tags, teamsToTags } from './teams.entity';
+
+export type Team = InferSelectModel;
+export type NewTeam = InferInsertModel;
+
+export type TeamMember = InferSelectModel;
+export type NewTeamMember = InferInsertModel;
+
+export type Tag = InferSelectModel;
+export type NewTag = InferInsertModel;
+
+export type TeamToTag = InferSelectModel;
+export type NewTeamToTag = InferInsertModel;
+
+export type TeamWithMembers = Team & {
+ members: TeamMember[];
+};
+
+export type TeamWithTags = Team & {
+ tags: Tag[];
+};
+
+// TODO: ADD TO GLOBAL
+export const ROLE_PRIORITY: Record = {
+ owner: 4,
+ admin: 3,
+ moderator: 2,
+ member: 1,
+ viewer: 0,
+};
diff --git a/src/modules/teams/entities/teams.entity.ts b/src/modules/teams/entities/teams.entity.ts
new file mode 100644
index 0000000..c79fea5
--- /dev/null
+++ b/src/modules/teams/entities/teams.entity.ts
@@ -0,0 +1,74 @@
+import { primaryKey, timestamp, text, varchar, index } from 'drizzle-orm/pg-core';
+import { createId } from '@paralleldrive/cuid2';
+import { roleEnum, statusEnum } from './enums';
+import { baseSchema, users } from 'src/shared/entities';
+import { uniqueIndex } from 'drizzle-orm/pg-core';
+import { isNull } from 'drizzle-orm';
+
+export const teams = baseSchema.table(
+ 'teams',
+ {
+ id: text('id')
+ .primaryKey()
+ .$defaultFn(() => createId()),
+ slug: varchar('slug', { length: 120 }).unique().notNull(),
+ name: varchar('name', { length: 100 }).notNull(),
+ description: text('description'),
+ avatarUrl: text('avatar_url'),
+ coverUrl: text('cover_url'),
+ ownerId: text('owner_id').references(() => users.id, { onDelete: 'set null' }),
+ createdAt: timestamp('created_at').defaultNow().notNull(),
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
+ deletedAt: timestamp('deleted_at'),
+ },
+ (t) => ({
+ uniqueActiveSlug: uniqueIndex('team_active_slug_idx').on(t.slug).where(isNull(t.deletedAt)),
+ slugIdx: index('team_slug_idx').on(t.slug),
+ ownerIdx: index('team_owner_idx').on(t.ownerId),
+ softDeleteIdx: index('team_deleted_at_idx').on(t.deletedAt),
+ }),
+);
+
+export const teamMembers = baseSchema.table(
+ 'team_members',
+ {
+ teamId: text('team_id')
+ .references(() => teams.id, { onDelete: 'cascade' })
+ .notNull(),
+ userId: text('user_id')
+ .references(() => users.id, { onDelete: 'cascade' })
+ .notNull(),
+ role: roleEnum('role').default('member').notNull(),
+ status: statusEnum('status').default('inactive').notNull(),
+ joinedAt: timestamp('joined_at'),
+ createdAt: timestamp('created_at').defaultNow().notNull(),
+ },
+ (t) => ({
+ pk: primaryKey({ columns: [t.teamId, t.userId] }),
+ statusIdx: index('member_status_idx').on(t.status),
+ userRoleIdx: index('member_role_idx').on(t.userId, t.role),
+ }),
+);
+
+export const tags = baseSchema.table('tags', {
+ id: text('id')
+ .primaryKey()
+ .$defaultFn(() => createId()),
+ name: varchar('name', { length: 50 }).unique().notNull(),
+});
+
+export const teamsToTags = baseSchema.table(
+ 'teams_to_tags',
+ {
+ teamId: text('team_id')
+ .references(() => teams.id, { onDelete: 'cascade' })
+ .notNull(),
+ tagId: text('tag_id')
+ .references(() => tags.id, { onDelete: 'cascade' })
+ .notNull(),
+ },
+ (t) => ({
+ pk: primaryKey({ columns: [t.teamId, t.tagId] }),
+ tagIdx: index('teams_to_tags_tag_id_idx').on(t.tagId),
+ }),
+);
diff --git a/src/modules/teams/index.ts b/src/modules/teams/index.ts
new file mode 100644
index 0000000..31bcaec
--- /dev/null
+++ b/src/modules/teams/index.ts
@@ -0,0 +1 @@
+export { TeamsModule } from './teams.module';
diff --git a/src/modules/teams/mappers/index.ts b/src/modules/teams/mappers/index.ts
new file mode 100644
index 0000000..f09718a
--- /dev/null
+++ b/src/modules/teams/mappers/index.ts
@@ -0,0 +1 @@
+export { TeamMemberMapper } from './member.mapper';
diff --git a/src/modules/teams/mappers/member.mapper.ts b/src/modules/teams/mappers/member.mapper.ts
new file mode 100644
index 0000000..45c6cf5
--- /dev/null
+++ b/src/modules/teams/mappers/member.mapper.ts
@@ -0,0 +1,70 @@
+import type { RawMemberRow, RawMemberTeams } from '../repository';
+
+export class TeamMemberMapper {
+ public static toDetail(row: RawMemberRow) {
+ const { firstName, lastName, middleName, avatarUrl, userId, ...rest } = row;
+
+ const fullName =
+ [lastName, firstName, middleName].filter(Boolean).join(' ') || 'Unknown User';
+
+ return {
+ id: userId,
+ ...rest,
+ firstName,
+ lastName,
+ middleName,
+ fullName,
+ avatarUrl,
+ initials: this.getInitials(firstName, lastName),
+ };
+ }
+
+ public static toList(rows: RawMemberRow[]) {
+ return rows.map((row) => this.toDetail(row));
+ }
+
+ public static toUserTeam(row: RawMemberTeams) {
+ const role = row.role;
+
+ return {
+ id: row.id,
+ name: row.name,
+ slug: row.slug,
+ description: row.description,
+ avatarUrl: row.avatarUrl,
+ role: role,
+ joinedAt: row.joinedAt,
+ permissions: {
+ canEdit: ['owner', 'admin'].includes(role),
+ canDelete: role === 'owner',
+ canManageMembers: ['owner', 'admin'].includes(role),
+ canInvite: ['owner', 'admin'].includes(role),
+ isOwner: role === 'owner',
+ },
+ };
+ }
+
+ // TODO: FIX ANY TEMPORARY
+ public static toPublicInvite(raw: string | null, code: string) {
+ if (!raw) return null;
+ try {
+ const p = JSON.parse(raw);
+ return {
+ code,
+ teamName: p.teamName,
+ teamAvatar: p.teamAvatar ?? null,
+ inviterName: p.inviterName,
+ role: p.role,
+ expiresAt: p.expiresAt,
+ };
+ } catch {
+ return null;
+ }
+ }
+
+ private static getInitials(fName: string | null, lName: string | null): string {
+ const first = fName?.[0] ?? '';
+ const last = lName?.[0] ?? '';
+ return (first + last).toUpperCase() || '?';
+ }
+}
diff --git a/src/modules/teams/repository/index.ts b/src/modules/teams/repository/index.ts
new file mode 100644
index 0000000..f78a0c8
--- /dev/null
+++ b/src/modules/teams/repository/index.ts
@@ -0,0 +1,6 @@
+export { TeamsRepository } from './teams.repository';
+export {
+ ITeamsRepository,
+ type RawMemberRow,
+ type RawMemberTeams,
+} from './teams.repository.interface';
diff --git a/src/modules/teams/repository/teams.repository.interface.ts b/src/modules/teams/repository/teams.repository.interface.ts
new file mode 100644
index 0000000..f02a9c9
--- /dev/null
+++ b/src/modules/teams/repository/teams.repository.interface.ts
@@ -0,0 +1,60 @@
+import type { Team, NewTeam, NewTeamMember, Tag } from '../entities';
+
+type TResponse = { success: boolean; tags: number; teamId: string };
+
+export type RawMemberRow = {
+ userId: string;
+ role: string;
+ status: string;
+ joinedAt: Date | string | null;
+ firstName: string | null;
+ lastName: string | null;
+ middleName: string | null;
+ avatarUrl: string | null;
+ email?: string;
+};
+
+export type RawMemberTeams = {
+ id: string;
+ name: string;
+ slug: string;
+ description: string | null;
+ avatarUrl: string | null;
+ role: string;
+ joinedAt: Date;
+};
+
+export interface ITeamsRepository {
+ create(ownerId: string, dto: NewTeam, tags?: string[]): Promise;
+ update(id: string, dto: Partial, tags?: string[]): Promise;
+ remove(id: string, userId: string): Promise;
+
+ isSlugAvailable(slug: string): Promise;
+
+ findMember(teamId: string, userId: string): Promise;
+ findMembers(teamId: string): Promise;
+ findBySlug(slug: string): Promise;
+ findByUser(
+ userId: string,
+ // TODO: ADD ZOD QUERY
+ pagination: { search?: string; limit?: number; offset?: number },
+ ): Promise;
+
+ findAllTags(options: {
+ search?: string;
+ limit?: number;
+ offset?: number;
+ }): Promise<{ data: Tag[]; total: number }>;
+ syncTags(teamId: string, tagNames: string[]): Promise;
+
+ updateTeamAvatar(teamId: string, url: string): Promise;
+ updateTeamBanner(teamId: string, url: string): Promise;
+
+ addMember(dto: NewTeamMember): Promise;
+ updateMember(
+ teamId: string,
+ userId: string,
+ dto: { role?: string; status?: string },
+ ): Promise;
+ removeMember(teamId: string, userId: string): Promise;
+}
diff --git a/src/modules/teams/repository/teams.repository.ts b/src/modules/teams/repository/teams.repository.ts
new file mode 100644
index 0000000..97e2446
--- /dev/null
+++ b/src/modules/teams/repository/teams.repository.ts
@@ -0,0 +1,292 @@
+import { Inject, Logger } from '@nestjs/common';
+import { ITeamsRepository } from './teams.repository.interface';
+import { DATABASE_SERVICE, DatabaseService } from '@libs/database';
+import * as schema from '../entities';
+import * as scUsers from 'src/modules/user/entities';
+import { and, asc, count, desc, eq, ilike, inArray, isNull, sql } from 'drizzle-orm';
+
+export class TeamsRepository implements ITeamsRepository {
+ private logger = new Logger(TeamsRepository.name);
+
+ constructor(
+ @Inject(DATABASE_SERVICE)
+ private readonly db: DatabaseService,
+ ) {}
+
+ public isSlugAvailable = async (slug: string) => {
+ const result = await this.db
+ .select({ id: schema.teams.id })
+ .from(schema.teams)
+ .where(eq(schema.teams.slug, slug));
+
+ return result.length === 0;
+ };
+
+ public addMember = async (dto: schema.NewTeamMember) => {
+ const { rowCount } = await this.db
+ .insert(schema.teamMembers)
+ .values(dto)
+ .onConflictDoNothing({
+ target: [schema.teamMembers.teamId, schema.teamMembers.userId],
+ });
+
+ return (rowCount ?? 0) > 0;
+ };
+
+ public create = async (ownerId: string, dto: schema.NewTeam, tags?: string[]) => {
+ return this.db.transaction(async (tx) => {
+ const [{ teamId }] = await tx
+ .insert(schema.teams)
+ .values({ ...dto, ownerId })
+ .returning({ teamId: schema.teams.id });
+
+ let insertedTagsCount = 0;
+
+ if (tags?.length) {
+ const insertedTags = await tx
+ .insert(schema.tags)
+ .values(tags.map((name) => ({ name })))
+ .onConflictDoUpdate({
+ target: schema.tags.name,
+ set: { name: sql`${schema.tags.name}` },
+ })
+ .returning({ id: schema.tags.id });
+
+ if (insertedTags.length > 0) {
+ await tx.insert(schema.teamsToTags).values(
+ insertedTags.map((tag) => ({
+ teamId,
+ tagId: tag.id,
+ })),
+ );
+
+ insertedTagsCount = insertedTags.length;
+ }
+ }
+
+ await tx.insert(schema.teamMembers).values({
+ teamId,
+ userId: ownerId,
+ role: 'owner',
+ status: 'active',
+ joinedAt: new Date(),
+ });
+
+ return {
+ success: true,
+ teamId,
+ tags: insertedTagsCount,
+ };
+ });
+ };
+
+ public update = async (id: string, dto: Partial, tags?: string[]) => {
+ return this.db.transaction(async (tx) => {
+ const [{ teamId }] = await tx
+ .update(schema.teams)
+ .set(dto)
+ .where(eq(schema.teams.id, id))
+ .returning({ teamId: schema.teams.id });
+
+ if (tags?.length) {
+ }
+
+ return {
+ success: true,
+ teamId,
+ tags: 0,
+ };
+ });
+ };
+
+ public remove = async (teamId: string, userId) => {
+ const suffix = Date.now().toString();
+
+ const { rowCount } = await this.db
+ .update(schema.teams)
+ .set({
+ deletedAt: new Date(),
+ slug: sql`${schema.teams.slug} || '-' || ${suffix}`,
+ })
+ .where(and(eq(schema.teams.id, teamId), eq(schema.teams.ownerId, userId)));
+
+ return (rowCount ?? 0) > 0;
+ };
+
+ public findMember = async (teamId: string, userId: string) => {
+ const [member] = await this.membersQuery.where(
+ and(eq(schema.teamMembers.teamId, teamId), eq(schema.teamMembers.userId, userId)),
+ );
+
+ return member || null;
+ };
+
+ public findMembers = async (teamId: string) => {
+ return this.membersQuery
+ .where(eq(schema.teamMembers.teamId, teamId))
+ .orderBy(desc(schema.teamMembers.joinedAt));
+ };
+
+ public findByUser = async (
+ userId: string,
+ pagination: { search?: string; limit?: number; offset?: number },
+ ) => {
+ const { search, limit = 10, offset = 0 } = pagination;
+
+ const filters = [
+ eq(schema.teamMembers.userId, userId),
+ eq(schema.teamMembers.status, 'active'),
+ isNull(schema.teams.deletedAt),
+ ];
+
+ if (search) {
+ filters.push(ilike(schema.teams.name, `%${search}%`));
+ }
+
+ const query = this.db
+ .select({
+ id: schema.teams.id,
+ name: schema.teams.name,
+ slug: schema.teams.slug,
+ description: schema.teams.description,
+ avatarUrl: schema.teams.avatarUrl,
+ role: schema.teamMembers.role,
+ joinedAt: schema.teamMembers.joinedAt,
+ })
+ .from(schema.teamMembers)
+ .innerJoin(schema.teams, eq(schema.teams.id, schema.teamMembers.teamId))
+ .where(and(...filters))
+ .orderBy(desc(schema.teamMembers.joinedAt))
+ .limit(limit)
+ .offset(offset);
+
+ return query;
+ };
+
+ public findAllTags = async (options: { search?: string; limit?: number; offset?: number }) => {
+ const cleanSearch = options.search?.trim();
+ const escapedSearch = cleanSearch?.replace(/([%_\\])/g, '\\$1');
+
+ const whereCondition = escapedSearch
+ ? ilike(schema.tags.name, `%${escapedSearch}%`)
+ : undefined;
+
+ const [data, [{ total }]] = await Promise.all([
+ this.db
+ .select()
+ .from(schema.tags)
+ .where(whereCondition)
+ .limit(options.limit)
+ .offset(options.offset)
+ .orderBy(asc(schema.tags.name)),
+
+ this.db.select({ total: count() }).from(schema.tags).where(whereCondition),
+ ]);
+
+ return {
+ data,
+ total: Number(total ?? 0),
+ };
+ };
+
+ public findBySlug = async (slug: string) => {
+ const [team] = await this.db.select().from(schema.teams).where(eq(schema.teams.slug, slug));
+ if (!team) return null;
+ return team;
+ };
+
+ public removeMember = async (teamId: string, userId: string) => {
+ const result = await this.db
+ .delete(schema.teamMembers)
+ .where(
+ and(eq(schema.teamMembers.teamId, teamId), eq(schema.teamMembers.userId, userId)),
+ );
+
+ return (result.rowCount ?? 0) > 0;
+ };
+
+ public syncTags = async (teamId: string, tagNames: string[]) => {
+ await this.db.transaction(async (tx) => {
+ await tx.delete(schema.teamsToTags).where(eq(schema.teamsToTags.teamId, teamId));
+
+ if (tagNames.length === 0) {
+ return;
+ }
+
+ await tx
+ .insert(schema.tags)
+ .values(tagNames.map((name) => ({ name })))
+ .onConflictDoNothing({ target: schema.tags.name });
+
+ const existingTags = await tx
+ .select({ id: schema.tags.id })
+ .from(schema.tags)
+ .where(inArray(schema.tags.name, tagNames));
+
+ await tx
+ .insert(schema.teamsToTags)
+ .values(existingTags.map((tag) => ({ teamId, tagId: tag.id })));
+ });
+
+ return true;
+ };
+
+ public updateMember = async (
+ teamId: string,
+ userId: string,
+ dto: Partial,
+ ) => {
+ const { role, status } = dto;
+
+ const data = {
+ role,
+ ...(status === 'active' ? { joinedAt: new Date() } : {}),
+ };
+
+ const result = await this.db
+ .update(schema.teamMembers)
+ .set(data)
+ .where(
+ and(eq(schema.teamMembers.teamId, teamId), eq(schema.teamMembers.userId, userId)),
+ );
+
+ return (result.rowCount ?? 0) > 0;
+ };
+
+ public async updateTeamAvatar(teamId: string, url: string): Promise {
+ const { rowCount } = await this.db
+ .update(schema.teams)
+ .set({ avatarUrl: url, updatedAt: new Date() })
+ .where(eq(schema.teams.id, teamId));
+ return (rowCount ?? 0) > 0;
+ }
+
+ public async updateTeamBanner(teamId: string, url: string): Promise {
+ const { rowCount } = await this.db
+ .update(schema.teams)
+ .set({ coverUrl: url, updatedAt: new Date() })
+ .where(eq(schema.teams.id, teamId));
+ return (rowCount ?? 0) > 0;
+ }
+
+ private get memberSelection() {
+ return {
+ userId: schema.teamMembers.userId,
+ role: schema.teamMembers.role,
+ status: schema.teamMembers.status,
+ joinedAt: schema.teamMembers.joinedAt,
+ firstName: scUsers.users.firstName,
+ lastName: scUsers.users.lastName,
+ middleName: scUsers.users.middleName,
+ avatarUrl: scUsers.users.avatarUrl,
+ email: scUsers.users.email,
+ };
+ }
+
+ private get membersQuery() {
+ return this.db
+ .select(this.memberSelection)
+ .from(schema.teamMembers)
+ .innerJoin(scUsers.users, eq(schema.teamMembers.userId, scUsers.users.id));
+ }
+}
diff --git a/src/modules/teams/services/index.ts b/src/modules/teams/services/index.ts
new file mode 100644
index 0000000..f1b5b9a
--- /dev/null
+++ b/src/modules/teams/services/index.ts
@@ -0,0 +1,2 @@
+export { TeamsService } from './teams.service';
+export { MembersService } from './members.service';
diff --git a/src/modules/teams/services/members.service.ts b/src/modules/teams/services/members.service.ts
new file mode 100644
index 0000000..3865d5b
--- /dev/null
+++ b/src/modules/teams/services/members.service.ts
@@ -0,0 +1,257 @@
+import {
+ BadRequestException,
+ ForbiddenException,
+ GoneException,
+ Inject,
+ Injectable,
+ NotFoundException,
+ UnprocessableEntityException,
+} from '@nestjs/common';
+import { ITeamsRepository } from '../repository';
+import { ROLE_PRIORITY } from '../entities';
+import { generateSecret } from 'otplib';
+import { InjectRedis } from '@nestjs-modules/ioredis';
+import Redis from 'ioredis';
+import { InjectQueue } from '@nestjs/bullmq';
+import { MailJobs, Queues } from 'src/shared/workers';
+import { Queue } from 'bullmq';
+import { validate } from 'email-validator';
+import { TeamInvitationEvent } from 'src/shared/workers/events';
+import type { InviteMemberDto, UpdateMemberDto } from '../dtos';
+import { ConfigService } from '@nestjs/config';
+import { TeamMemberMapper } from '../mappers';
+
+@Injectable()
+export class MembersService {
+ constructor(
+ @Inject('ITeamsRepository')
+ private readonly teamsRepo: ITeamsRepository,
+ @InjectRedis()
+ private readonly redis: Redis,
+ @InjectQueue(Queues.MAIL)
+ private readonly mailQueue: Queue,
+ private readonly cfg: ConfigService,
+ ) {}
+
+ public getMembers = async (slug: string) => {
+ const team = await this.teamsRepo.findBySlug(slug);
+
+ if (!team) {
+ throw new NotFoundException(`Команда ${slug} не найдена`);
+ }
+
+ const members = await this.teamsRepo.findMembers(team.id);
+ return TeamMemberMapper.toList(members);
+ };
+
+ public invite = async (slug: string, inviterId: string, dto: InviteMemberDto) => {
+ const isValidEmail = validate(dto.email);
+
+ if (!isValidEmail) {
+ throw new UnprocessableEntityException({
+ code: 'INVALID_EMAIL_FORMAT',
+ message: 'Указанный email адрес имеет некорректный формат',
+ details: { email: dto.email },
+ });
+ }
+
+ const team = await this.teamsRepo.findBySlug(slug);
+ if (!team) throw new NotFoundException('Команда не найдена');
+
+ const inviter = await this.teamsRepo.findMember(team.id, inviterId);
+ if (!inviter || (inviter.role !== 'owner' && inviter.role !== 'admin')) {
+ throw new ForbiddenException('У вас нет прав приглашать новых участников');
+ }
+
+ const code = generateSecret({ length: 8 });
+
+ const INVITE_TTL = 86400;
+ const now = new Date();
+ const expiresAt = new Date(now.getTime() + INVITE_TTL * 1000);
+
+ const inviteData = {
+ teamId: team.id,
+ teamName: team.name,
+ teamAvatar: team.avatarUrl,
+ email: dto.email,
+ role: dto.role || 'member',
+ inviterId,
+ inviterName: inviter.firstName,
+ createdAt: new Date().toISOString(),
+ expiresAt: expiresAt.toISOString(),
+ };
+
+ const multi = this.redis.multi();
+ multi.set(`inv:code:${code}`, JSON.stringify(inviteData), 'EX', INVITE_TTL);
+ multi.sadd(`team:invites:${team.id}`, code);
+ multi.sadd(`user:invites:${dto.email}`, code);
+ await multi.exec();
+
+ const origins = this.cfg.get('CORS_ALLOWED_ORIGINS');
+ const FRONTEND_URL = origins[0];
+
+ /**
+ * Человек кликает: ttopen.ru/invites/accept?code=...
+ * Фронт видит, что токена нет -> Редирект на /signup?inviteCode=...
+ * Юзер регистрируется.
+ * После успешного входа фронт видит inviteCode в URL или стейте и автоматом завершает процесс вступления.
+ */
+ const event = new TeamInvitationEvent(
+ dto.email,
+ team.name,
+ `${FRONTEND_URL}/invites/accept?code=${code}`,
+ );
+ await this.mailQueue.add(MailJobs.SEND_TEAM_INVITATION, event, {
+ attempts: 3,
+ backoff: {
+ type: 'exponential',
+ delay: 5000,
+ },
+ });
+
+ return {
+ success: true,
+ message: `Приглашение отправлено на ${dto.email}`,
+ code,
+ };
+ };
+
+ public acceptInvite = async (code: string, userId: string, email: string) => {
+ const inviteRaw = await this.redis.get(`inv:code:${code}`);
+ if (!inviteRaw) {
+ throw new GoneException('Срок действия приглашения истек или код неверен');
+ }
+
+ const invite = JSON.parse(inviteRaw);
+
+ if (invite.email.toLowerCase() !== email.toLowerCase()) {
+ throw new ForbiddenException('Этот инвайт предназначен для другого почтового адреса');
+ }
+
+ const member = await this.teamsRepo.findMember(invite.teamId, userId);
+
+ if (member) {
+ if (member.status === 'banned') {
+ throw new ForbiddenException('Вы заблокированы в этой команде');
+ }
+
+ if (member.status === 'active') {
+ throw new BadRequestException('Вы уже являетесь участником этой команды');
+ }
+ }
+
+ await this.teamsRepo.addMember({
+ teamId: invite.teamId,
+ userId,
+ role: invite.role,
+ status: 'active',
+ joinedAt: new Date(),
+ });
+
+ const multi = this.redis.multi();
+ multi.del(`inv:code:${code}`);
+ multi.srem(`team:invites:${invite.teamId}`, code);
+ multi.srem(`user:invites:${email}`, code);
+ await multi.exec();
+
+ return {
+ success: true,
+ message: 'Вы успешно присоединились к команде',
+ };
+ };
+
+ public updateMember = async (
+ slug: string,
+ currentUserId: string,
+ targetUserId: string,
+ dto: UpdateMemberDto,
+ ) => {
+ const team = await this.teamsRepo.findBySlug(slug);
+ if (!team) throw new NotFoundException('Команда не найдена');
+
+ const [currentUser, targetUser] = await Promise.all([
+ this.teamsRepo.findMember(team.id, currentUserId),
+ this.teamsRepo.findMember(team.id, targetUserId),
+ ]);
+
+ if (!currentUser || !targetUser) throw new NotFoundException('Участник не найден');
+
+ if (ROLE_PRIORITY[currentUser.role] < ROLE_PRIORITY.admin) {
+ throw new ForbiddenException('У вас нет прав на редактирование участников');
+ }
+
+ // Нельзя менять роль тому, кто выше тебя или равен тебе по весу
+ if (
+ currentUserId !== targetUserId &&
+ ROLE_PRIORITY[currentUser.role] <= ROLE_PRIORITY[targetUser.role]
+ ) {
+ throw new ForbiddenException(
+ 'Вы не можете менять данные участника с равным или высшим рангом',
+ );
+ }
+
+ // Защита от потери овнера: нельзя разжаловать овнера в админа
+ if (targetUser.role === 'owner' && dto.role && dto.role !== 'owner') {
+ throw new BadRequestException(
+ 'Нельзя изменить роль владельца. Используйте процедуру передачи прав.',
+ );
+ }
+
+ // Нельзя назначить роль выше своей (Админ не может сделать кого-то Овнером)
+ if (
+ dto.role &&
+ ROLE_PRIORITY[dto.role] >= ROLE_PRIORITY[currentUser.role] &&
+ currentUser.role !== 'owner'
+ ) {
+ throw new ForbiddenException('Вы не можете назначить роль выше своей');
+ }
+
+ const result = await this.teamsRepo.updateMember(team.id, targetUserId, dto);
+
+ return {
+ success: result,
+ message: `Данные участника команды "${team.name}" успешно обновлены`,
+ };
+ };
+
+ public removeMember = async (slug: string, currentUserId: string, targetUserId: string) => {
+ const team = await this.teamsRepo.findBySlug(slug);
+ if (!team) throw new NotFoundException('Команда не найдена');
+
+ const [currentUser, targetUser] = await Promise.all([
+ this.teamsRepo.findMember(team.id, currentUserId),
+ this.teamsRepo.findMember(team.id, targetUserId),
+ ]);
+
+ if (!targetUser) throw new NotFoundException('Участник не найден в этой команде');
+ if (!currentUser) throw new ForbiddenException('Вы не состоите в этой команде');
+
+ const isSelfRemoval = currentUserId === targetUserId;
+
+ if (isSelfRemoval) {
+ if (currentUser.role === 'owner') {
+ throw new BadRequestException(
+ 'Владелец не может покинуть команду. Передайте права или удалите команду.',
+ );
+ }
+ } else {
+ const canKick = ROLE_PRIORITY[currentUser.role] > ROLE_PRIORITY[targetUser.role];
+ const hasAuthority = ROLE_PRIORITY[currentUser.role] >= ROLE_PRIORITY.admin;
+
+ if (!hasAuthority || !canKick) {
+ throw new ForbiddenException(
+ 'У вас недостаточно прав, чтобы исключить этого участника',
+ );
+ }
+ }
+
+ const result = await this.teamsRepo.removeMember(team.id, targetUserId);
+
+ return {
+ success: result,
+ message: isSelfRemoval
+ ? `Вы успешно покинули команду ${team.name}`
+ : `Участник успешно исключен из команды ${team.name}`,
+ };
+ };
+}
diff --git a/src/modules/teams/services/teams.service.ts b/src/modules/teams/services/teams.service.ts
new file mode 100644
index 0000000..7af6312
--- /dev/null
+++ b/src/modules/teams/services/teams.service.ts
@@ -0,0 +1,219 @@
+import {
+ Inject,
+ Injectable,
+ InternalServerErrorException,
+ ConflictException,
+ ForbiddenException,
+ NotFoundException,
+} from '@nestjs/common';
+import { ITeamsRepository } from '../repository';
+import { FindTagsQuery } from '../dtos';
+import { ITeamMedia, TEAM_MEDIA_TOKEN } from '../../media/interfaces/team-media.interface';
+import type { FileUploadDto } from '../../media/dtos';
+import type { CreateTeamDto, UpdateTeamDto } from '../dtos';
+import { slugify } from 'transliteration';
+import { TeamMemberMapper } from '../mappers';
+import { InjectRedis } from '@nestjs-modules/ioredis';
+import Redis from 'ioredis';
+
+@Injectable()
+export class TeamsService {
+ constructor(
+ @Inject('ITeamsRepository')
+ private readonly teamsRepo: ITeamsRepository,
+ @Inject(TEAM_MEDIA_TOKEN)
+ private readonly mediaService: ITeamMedia,
+ @InjectRedis()
+ private readonly redis: Redis,
+ ) {}
+
+ public checkSlug = async (slug: string) => {
+ const available = await this.teamsRepo.isSlugAvailable(slug);
+ return { available };
+ };
+
+ public getMyInvites = async (email: string) => {
+ const codes = await this.redis.smembers(`user:invites:${email}`);
+
+ if (!codes.length) return [];
+
+ const results = await this.redis.mget(codes.map((c) => `inv:code:${c}`));
+
+ return results
+ .map((raw, i) => TeamMemberMapper.toPublicInvite(raw, codes[i]))
+ .filter(Boolean);
+ };
+
+ public updateTeamAvatar = async (slug: string, fileDto: FileUploadDto) => {
+ const team = await this.teamsRepo.findBySlug(slug);
+ if (!team) {
+ throw new NotFoundException({
+ code: 'TEAM_NOT_FOUND',
+ message: 'Команда не найдена',
+ });
+ }
+
+ return this.mediaService.uploadTeamAvatar(team.id, fileDto, (url) =>
+ this.teamsRepo.updateTeamAvatar(team.id, url),
+ );
+ };
+
+ public updateTeamBanner = async (slug: string, fileDto: FileUploadDto) => {
+ const team = await this.teamsRepo.findBySlug(slug);
+ if (!team) {
+ throw new NotFoundException({
+ code: 'TEAM_NOT_FOUND',
+ message: 'Команда не найдена',
+ });
+ }
+
+ return this.mediaService.uploadTeamBanner(team.id, fileDto, (url) =>
+ this.teamsRepo.updateTeamBanner(team.id, url),
+ );
+ };
+
+ public create = async (userId: string, dto: CreateTeamDto) => {
+ const baseSlug = slugify(dto.slug || dto.name, { lowercase: true, separator: '-' });
+ const existingTeam = await this.teamsRepo.findBySlug(baseSlug);
+
+ if (existingTeam) {
+ throw new ConflictException(`Команда со ссылкой "${baseSlug}" уже существует`);
+ }
+
+ const { tags, ...teamData } = dto;
+
+ try {
+ const result = await this.teamsRepo.create(
+ userId,
+ {
+ ...teamData,
+ slug: baseSlug,
+ },
+ tags,
+ );
+
+ return {
+ ...result,
+ slug: baseSlug,
+ message: 'Команда успешно создана',
+ };
+ } catch (error) {
+ throw error;
+ }
+ };
+
+ public update = async (slug: string, userId: string, dto: UpdateTeamDto) => {
+ const team = await this.teamsRepo.findBySlug(slug);
+ if (!team) {
+ throw new NotFoundException(`Команда ${slug} не найдена`);
+ }
+
+ const member = await this.teamsRepo.findMember(team.id, userId);
+
+ const canEdit = member?.role === 'admin' || member?.role === 'owner';
+
+ if (!canEdit) {
+ throw new ForbiddenException('У вас нет прав для выполнения этой команды');
+ }
+
+ const { tags, ...data } = dto;
+
+ try {
+ const result = await this.teamsRepo.update(team.id, data, tags);
+
+ return {
+ ...result,
+ message: 'Данные команды успешно обновлены',
+ };
+ } catch (error) {
+ throw error;
+ }
+ };
+
+ public remove = async (slug: string, userId: string) => {
+ const team = await this.teamsRepo.findBySlug(slug);
+
+ if (!team) {
+ throw new NotFoundException(`Команда ${slug} не найдена`);
+ }
+
+ const member = await this.teamsRepo.findMember(team.id, userId);
+
+ const canEdit = team.ownerId === userId || member?.role === 'owner';
+
+ if (!canEdit) {
+ throw new ForbiddenException('У вас нет прав для выполнения этой команды');
+ }
+
+ try {
+ const result = await this.teamsRepo.remove(team.id, userId);
+
+ return {
+ success: result,
+ message: 'Данные команды успешно обновлены',
+ };
+ } catch (error) {
+ throw error;
+ }
+ };
+
+ public syncTags = async (slug: string, tags: string[]) => {
+ const team = await this.teamsRepo.findBySlug(slug);
+ if (!team) {
+ throw new NotFoundException({
+ code: 'TEAM_NOT_FOUND',
+ message: 'Команда не найдена',
+ });
+ }
+
+ const normalizedTags = [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))];
+ const isSynced = await this.teamsRepo.syncTags(team.id, normalizedTags);
+
+ if (!isSynced) {
+ throw new InternalServerErrorException('Не удалось обновить теги команды');
+ }
+
+ return {
+ success: true,
+ message: 'Теги команды обновлены',
+ };
+ };
+
+ public getAllTags = async (query: FindTagsQuery) => {
+ const safePage = Math.max(query.page ?? 1, 1);
+ const safeLimit = Math.min(Math.max(query.limit ?? 20, 1), 50);
+ const offset = (safePage - 1) * safeLimit;
+
+ const { data, total } = await this.teamsRepo.findAllTags({
+ search: query.search,
+ limit: safeLimit,
+ offset,
+ });
+
+ const totalPages = total === 0 ? 0 : Math.ceil(total / safeLimit);
+ return {
+ data,
+ meta: {
+ hasNextPage: safePage < totalPages,
+ hasPrevPage: safePage > 1,
+ total,
+ totalPages,
+ page: safePage,
+ limit: safeLimit,
+ },
+ };
+ };
+
+ public getAll = async (userId: string, pagination: Record) => {
+ const teams = await this.teamsRepo.findByUser(userId, pagination);
+ return teams.map((t) => TeamMemberMapper.toUserTeam(t));
+ };
+
+ public getOne = async (slug: string) => {
+ const team = await this.teamsRepo.findBySlug(slug);
+ if (!team) {
+ throw new NotFoundException(`Команда ${slug} не найдена`);
+ }
+ return team;
+ };
+}
diff --git a/src/modules/teams/teams.module.ts b/src/modules/teams/teams.module.ts
new file mode 100644
index 0000000..3030908
--- /dev/null
+++ b/src/modules/teams/teams.module.ts
@@ -0,0 +1,48 @@
+import { Module } from '@nestjs/common';
+import { MembersController, TeamsController } from './controller';
+import { MediaModule } from '../media/media.module';
+import { TeamsService, MembersService } from './services';
+import { TeamsRepository } from './repository';
+import { RedisModule } from '@nestjs-modules/ioredis';
+import { ConfigService } from '@nestjs/config';
+import { BullModule } from '@nestjs/bullmq';
+import { Queues } from 'src/shared/workers';
+import { BullBoardModule } from '@bull-board/nestjs';
+import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
+
+const REPOSITORY = { provide: 'ITeamsRepository', useClass: TeamsRepository };
+
+@Module({
+ imports: [
+ MediaModule,
+ RedisModule.forRootAsync({
+ inject: [ConfigService],
+ useFactory: async (cfg: ConfigService) => {
+ const host = cfg.getOrThrow('REDIS_HOST', { infer: true });
+ const port = cfg.get('REDIS_PORT');
+ const url = `redis://${host}${port ? `:${port}` : ''}`;
+
+ return {
+ type: 'single',
+ url,
+ options: {
+ retryStrategy(times) {
+ return Math.min(times * 50, 2000);
+ },
+ commandTimeout: 3000,
+ },
+ };
+ },
+ }),
+ BullModule.registerQueue({
+ name: Queues.MAIL,
+ }),
+ BullBoardModule.forFeature({
+ name: Queues.MAIL,
+ adapter: BullMQAdapter,
+ }),
+ ],
+ controllers: [TeamsController, MembersController],
+ providers: [REPOSITORY, TeamsService, MembersService],
+})
+export class TeamsModule {}
diff --git a/src/modules/user/controller/user.controller.ts b/src/modules/user/controller/user.controller.ts
index 122e3f6..96e9eb3 100644
--- a/src/modules/user/controller/user.controller.ts
+++ b/src/modules/user/controller/user.controller.ts
@@ -1,4 +1,4 @@
-import { BadRequestException, Body, Get, Patch, Post, Query, Req, UseGuards } from '@nestjs/common';
+import { Body, Get, Patch, Post, Query, UseGuards } from '@nestjs/common';
import { UserService } from '../user.service';
import {
GetMeActivitySwagger,
@@ -8,10 +8,10 @@ import {
PostMeAvatarSwagger,
} from './user.swagger';
import { UpdateNotificationsDto, UpdateProfileDto } from '../dtos';
-import { ApiBaseController, GetUserId } from '../../../shared/decorators';
+import { ApiBaseController, ExtractFastifyFile, GetUserId } from '../../../shared/decorators';
import { BearerAuthGuard } from 'src/shared/guards';
import { PaginationDto } from '../../../shared/dtos';
-import { FastifyRequest } from 'fastify';
+import { FileUploadDto } from '../../media/dtos';
@ApiBaseController('users', 'Users')
@UseGuards(BearerAuthGuard)
@@ -44,27 +44,11 @@ export class UserController {
@Post('me/avatar')
@PostMeAvatarSwagger()
- async uploadAvatar(@Req() req: FastifyRequest, @GetUserId() userId: string) {
- if (!req.isMultipart()) {
- throw new BadRequestException('Request is not multipart');
- }
-
- const file = await req.file();
- if (!file || file.fieldname !== 'file') {
- throw new BadRequestException('Поле file не найдено');
- }
-
- const allowedMimeTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/jpg'];
- if (!allowedMimeTypes.includes(file.mimetype)) {
- throw new BadRequestException('Недопустимый формат файла');
- }
-
- const buffer = await file.toBuffer();
-
- return this.facade.uploadAvatar(userId, {
- buffer,
- filename: file.filename,
- mimetype: file.mimetype,
- });
+ async uploadAvatar(
+ @ExtractFastifyFile() fileDto: FileUploadDto,
+ @GetUserId()
+ userId: string,
+ ) {
+ return this.facade.uploadAvatar(userId, fileDto);
}
}
diff --git a/src/modules/user/user.module.ts b/src/modules/user/user.module.ts
index a5b7941..784e8d6 100644
--- a/src/modules/user/user.module.ts
+++ b/src/modules/user/user.module.ts
@@ -3,6 +3,7 @@ import { UserController } from './controller';
import { UserService } from './user.service';
import { UserRepository } from './repository/user.repository';
import { CreateUserCommand, FindOneUserCommand, UpdatePassUserCommand } from './commands';
+import { MediaModule } from '../media/media.module';
const REPOSITORY = {
provide: 'IUserRepository',
@@ -12,7 +13,7 @@ const REPOSITORY = {
const COMMANDS = [CreateUserCommand, FindOneUserCommand, UpdatePassUserCommand];
@Module({
- imports: [],
+ imports: [MediaModule],
controllers: [UserController],
providers: [...COMMANDS, REPOSITORY, UserService],
exports: [...COMMANDS],
diff --git a/src/modules/user/user.service.ts b/src/modules/user/user.service.ts
index 4bbb06c..4d3e4fd 100644
--- a/src/modules/user/user.service.ts
+++ b/src/modules/user/user.service.ts
@@ -1,5 +1,4 @@
import {
- BadRequestException,
Inject,
Injectable,
InternalServerErrorException,
@@ -8,15 +7,16 @@ import {
import { IUserRepository } from './repository/user.repository.interface';
import { UpdateNotificationsDto, UpdateProfileDto } from './dtos';
import { createId } from '@paralleldrive/cuid2';
-import { S3Service } from '@libs/s3';
-import { FileUploadDto } from '@libs/s3/dtos/upload-avatar.dto';
+import { IUserMedia, USER_MEDIA_TOKEN } from '../media/interfaces/user-media.interface';
+import { FileUploadDto } from '../media/dtos';
@Injectable()
export class UserService {
constructor(
@Inject('IUserRepository')
private readonly userRepo: IUserRepository,
- private readonly s3: S3Service,
+ @Inject(USER_MEDIA_TOKEN)
+ private readonly mediaService: IUserMedia,
) {}
private throwUserNotFound() {
@@ -134,33 +134,20 @@ export class UserService {
};
public uploadAvatar = async (userId: string, fileDto: FileUploadDto) => {
- const avatarUrl = await this.s3.uploadPublicFile(
- fileDto.buffer,
- fileDto.filename,
- fileDto.mimetype,
+ const { url } = await this.mediaService.uploadUserAvatar(userId, fileDto, (url) =>
+ this.userRepo.updateAvatar(userId, url),
);
- try {
- new URL(avatarUrl);
- } catch {
- throw new BadRequestException({
- code: 'INVALID_AVATAR_URL',
- message: 'Провайдер хранилища вернул некорректный URL',
- });
- }
-
- await this.userRepo.updateAvatar(userId, avatarUrl);
-
await this.userRepo.logActivity({
id: createId(),
userId,
eventType: 'AVATAR_CHANGED',
- metadata: { url: avatarUrl },
+ metadata: { url },
});
return {
success: true,
- avatarUrl,
+ url,
};
};
}
diff --git a/src/shared/adapters/mail/adapter.ts b/src/shared/adapters/mail/adapter.ts
index eadbdf9..12362a3 100644
--- a/src/shared/adapters/mail/adapter.ts
+++ b/src/shared/adapters/mail/adapter.ts
@@ -26,8 +26,13 @@ export class MailAdapter implements IMailPort {
const templatePath = path.join(process.cwd(), 'templates', `${templateName}.hbs`);
const templateSource = fs.readFileSync(templatePath, 'utf8');
+ const contextWithYear = {
+ ...context,
+ year: new Date().getFullYear(),
+ };
+
const template = hbs.compile(templateSource);
- const html = template(context);
+ const html = template(contextWithYear);
return await this.transporter.sendMail({
from: `"${this.cfg.get('MAIL_FROM_NAME')}" <${this.cfg.get('MAIL_FROM_EMAIL')}>`,
@@ -53,4 +58,11 @@ export class MailAdapter implements IMailPort {
codeArray,
});
}
+
+ async sendTeamInvitation(email: string, teamName: string, inviteUrl: string) {
+ return this.sendMail(email, `Приглашение в команду ${teamName}`, 'team-invitation', {
+ teamName,
+ inviteUrl,
+ });
+ }
}
diff --git a/src/shared/adapters/mail/port.ts b/src/shared/adapters/mail/port.ts
index 8a0de98..0ae1a57 100644
--- a/src/shared/adapters/mail/port.ts
+++ b/src/shared/adapters/mail/port.ts
@@ -1,4 +1,5 @@
export interface IMailPort {
sendRegistrationCode(email: string, name: string, code: string): Promise;
sendResetPasswordCode(email: string, code: string): Promise;
+ sendTeamInvitation(email: string, teamName: string, inviteUrl: string): Promise;
}
diff --git a/src/shared/constants/file.constants.ts b/src/shared/constants/file.constants.ts
new file mode 100644
index 0000000..be950f2
--- /dev/null
+++ b/src/shared/constants/file.constants.ts
@@ -0,0 +1 @@
+export const IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/jpg'];
diff --git a/src/shared/constants/index.ts b/src/shared/constants/index.ts
new file mode 100644
index 0000000..b4b8a55
--- /dev/null
+++ b/src/shared/constants/index.ts
@@ -0,0 +1 @@
+export * from './file.constants';
diff --git a/src/shared/decorators/api-controller.decorator.ts b/src/shared/decorators/api-controller.decorator.ts
index d8c9d9c..a950e6a 100644
--- a/src/shared/decorators/api-controller.decorator.ts
+++ b/src/shared/decorators/api-controller.decorator.ts
@@ -1,15 +1,19 @@
-import { Controller, applyDecorators } from '@nestjs/common';
+import { Controller, UseGuards, applyDecorators } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { ApiErrorResponse } from 'src/shared/error';
+import { BearerAuthGuard } from '../guards';
-export const ApiBaseController = (path: string, tag: string) => {
- return applyDecorators(
+export const ApiBaseController = (path: string, tag: string, hasJWTGuard?: boolean) => {
+ const decorators = [
ApiTags(tag),
Controller(path),
+ hasJWTGuard ? UseGuards(BearerAuthGuard) : null,
ApiErrorResponse(
500,
'INTERNAL_SERVER_ERROR',
'Произошла критическая ошибка на стороне сервера',
),
- );
+ ].filter(Boolean);
+
+ return applyDecorators(...decorators);
};
diff --git a/src/shared/decorators/extract-fastify-file.decorator.ts b/src/shared/decorators/extract-fastify-file.decorator.ts
new file mode 100644
index 0000000..763b5db
--- /dev/null
+++ b/src/shared/decorators/extract-fastify-file.decorator.ts
@@ -0,0 +1,34 @@
+import { createParamDecorator, ExecutionContext, BadRequestException } from '@nestjs/common';
+import { FastifyRequest } from 'fastify';
+import { IMAGE_MIME_TYPES } from '../constants';
+import { FileUploadDto } from '../../modules/media/dtos';
+
+export const ExtractFastifyFile = createParamDecorator(
+ async (
+ data: { allowedMimetypes?: string[] } = { allowedMimetypes: IMAGE_MIME_TYPES },
+ ctx: ExecutionContext,
+ ): Promise => {
+ const req = ctx.switchToHttp().getRequest();
+
+ if (!req.isMultipart()) {
+ throw new BadRequestException('Request is not multipart');
+ }
+
+ const file = await req.file();
+ if (!file) {
+ throw new BadRequestException('Файл не найден');
+ }
+
+ if (data?.allowedMimetypes && !data.allowedMimetypes.includes(file.mimetype)) {
+ throw new BadRequestException('Недопустимый формат файла');
+ }
+
+ const buffer = await file.toBuffer();
+
+ return {
+ buffer,
+ filename: file.filename,
+ mimetype: file.mimetype,
+ };
+ },
+);
diff --git a/src/shared/decorators/index.ts b/src/shared/decorators/index.ts
index c2f9d19..bd15c0b 100644
--- a/src/shared/decorators/index.ts
+++ b/src/shared/decorators/index.ts
@@ -1,2 +1,3 @@
export { ApiBaseController } from './api-controller.decorator';
export * from './user.decorator';
+export { ExtractFastifyFile } from './extract-fastify-file.decorator';
diff --git a/src/shared/entities/index.ts b/src/shared/entities/index.ts
index 2e1f6bc..94f5a0e 100644
--- a/src/shared/entities/index.ts
+++ b/src/shared/entities/index.ts
@@ -1,3 +1,4 @@
export { baseSchema } from './schema';
export * from '../../modules/user/entities';
export * from '../../modules/auth/entities';
+export * from '../../modules/teams/entities';
diff --git a/src/shared/error/filter.ts b/src/shared/error/filter.ts
index d571387..4857ed7 100644
--- a/src/shared/error/filter.ts
+++ b/src/shared/error/filter.ts
@@ -1,5 +1,4 @@
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '@nestjs/common';
-import { createId } from '@paralleldrive/cuid2';
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
@@ -29,7 +28,7 @@ export class GlobalExceptionFilter implements ExceptionFilter {
details = res.details || [];
}
- const requestId = request.headers['x-request-id'] || createId();
+ const requestId = request.id ?? request.headers['x-request-id'];
const errorResponse = {
code,
diff --git a/src/shared/error/swagger.ts b/src/shared/error/swagger.ts
index 29def94..26088f5 100644
--- a/src/shared/error/swagger.ts
+++ b/src/shared/error/swagger.ts
@@ -7,8 +7,8 @@ export const ApiErrorResponse = (
bizCode: string,
description: string,
details?: { field: string; message: string; code: string }[],
-) => {
- return ApiResponse({
+) =>
+ ApiResponse({
status,
description,
schema: {
@@ -28,7 +28,6 @@ export const ApiErrorResponse = (
},
},
});
-};
export const ApiBadRequest = (description: string = 'Некорректный запрос') =>
applyDecorators(ApiErrorResponse(400, 'BAD_REQUEST', description));
diff --git a/src/shared/schemas/index.ts b/src/shared/schemas/index.ts
new file mode 100644
index 0000000..b3c8aa4
--- /dev/null
+++ b/src/shared/schemas/index.ts
@@ -0,0 +1 @@
+export * from './pagination-response.schema';
diff --git a/src/shared/schemas/pagination-response.schema.ts b/src/shared/schemas/pagination-response.schema.ts
new file mode 100644
index 0000000..0d3fcca
--- /dev/null
+++ b/src/shared/schemas/pagination-response.schema.ts
@@ -0,0 +1,29 @@
+import { z } from 'zod/v4';
+
+export const paginationResponseSchema = z.object({
+ hasNextPage: z
+ .boolean()
+ .describe('Флаг наличия следующей страницы. True, если текущая страница не последняя.'),
+ hasPrevPage: z
+ .boolean()
+ .describe('Флаг наличия предыдущей страницы. True, если текущая страница больше первой.'),
+ total: z
+ .number()
+ .int()
+ .nonnegative()
+ .describe('Общее количество записей, соответствующих поисковому запросу/фильтрам.'),
+ totalPages: z
+ .number()
+ .int()
+ .nonnegative()
+ .describe('Общее количество страниц, рассчитанное на основе limit.'),
+ page: z.number().int().positive().describe('Номер текущей страницы (начиная с 1).'),
+ limit: z.number().int().positive().describe('Количество элементов на одну страницу.'),
+});
+
+export const createPaginationSchema = (itemSchema: T) => {
+ return z.object({
+ data: z.array(itemSchema),
+ meta: paginationResponseSchema,
+ });
+};
diff --git a/src/shared/workers/enum.ts b/src/shared/workers/enum.ts
index dffe92b..863d67a 100644
--- a/src/shared/workers/enum.ts
+++ b/src/shared/workers/enum.ts
@@ -6,4 +6,5 @@ export enum MailJobs {
SEND_REGISTER_CODE = 'SEND_REGISTER_CODE',
SEND_RESET_PASSWORD = 'SEND_RESET_PASSWORD',
SEND_CHANGE_EMAIL = 'SEND_CHANGE_EMAIL',
+ SEND_TEAM_INVITATION = 'SEND_TEAM_INVITATION',
}
diff --git a/src/shared/workers/events/index.ts b/src/shared/workers/events/index.ts
index 61a6360..6430cb9 100644
--- a/src/shared/workers/events/index.ts
+++ b/src/shared/workers/events/index.ts
@@ -1,2 +1,3 @@
export { RegisterCodeEvent } from './register-code.event';
export { ResetPasswordEvent } from './reset-password.event';
+export { TeamInvitationEvent } from './team-invitation.event';
diff --git a/src/shared/workers/events/team-invitation.event.ts b/src/shared/workers/events/team-invitation.event.ts
new file mode 100644
index 0000000..5dc9d67
--- /dev/null
+++ b/src/shared/workers/events/team-invitation.event.ts
@@ -0,0 +1,7 @@
+export class TeamInvitationEvent {
+ constructor(
+ public email: string,
+ public teamName: string,
+ public inviteUrl: string,
+ ) {}
+}
diff --git a/src/shared/workers/mail/worker.ts b/src/shared/workers/mail/worker.ts
index 06ce4b1..fdb0b1f 100644
--- a/src/shared/workers/mail/worker.ts
+++ b/src/shared/workers/mail/worker.ts
@@ -3,7 +3,7 @@ import { MailJobs, Queues } from '../enum';
import type { Job } from 'bullmq';
import { IMailPort } from 'src/shared/adapters/mail';
import { Inject } from '@nestjs/common';
-import type { RegisterCodeEvent, ResetPasswordEvent } from '../events';
+import { RegisterCodeEvent, ResetPasswordEvent, TeamInvitationEvent } from '../events';
@Processor(Queues.MAIL)
export class MailProcessor extends WorkerHost {
@@ -16,6 +16,7 @@ export class MailProcessor extends WorkerHost {
async process(job: Job): Promise;
async process(job: Job): Promise;
+ async process(job: Job): Promise;
async process(job: Job): Promise {
await job.log(`[START] Job ID: ${job.id} | Type: ${job.name}`);
@@ -27,6 +28,9 @@ export class MailProcessor extends WorkerHost {
case MailJobs.SEND_RESET_PASSWORD:
await this.sendResetPassCode(job);
break;
+ case MailJobs.SEND_TEAM_INVITATION:
+ await this.sendTeamInvitation(job);
+ break;
default:
await job.log(`[WRN] No handler for job: ${job.name}`);
await job.updateProgress(100);
@@ -69,4 +73,16 @@ export class MailProcessor extends WorkerHost {
await job.log(`Reset link delivered to ${email}`);
await job.updateProgress(100);
};
+
+ private sendTeamInvitation = async (job: Job) => {
+ const { email, teamName, inviteUrl } = job.data;
+
+ await job.log(`Sending team(${teamName}) invitation link to: ${email}`);
+ await job.updateProgress(30);
+
+ await this.mailAdapter.sendTeamInvitation(email, teamName, inviteUrl);
+
+ await job.log(`Team invitation link delivered to ${email}`);
+ await job.updateProgress(100);
+ };
}
diff --git a/templates/confirmation.hbs b/templates/confirmation.hbs
index c30923b..da7afbb 100644
--- a/templates/confirmation.hbs
+++ b/templates/confirmation.hbs
@@ -45,7 +45,7 @@
Код будет активен в течение 15 минут.
diff --git a/templates/reset-password.hbs b/templates/reset-password.hbs
index 1fa520e..2e41881 100644
--- a/templates/reset-password.hbs
+++ b/templates/reset-password.hbs
@@ -45,7 +45,7 @@
Никому не сообщайте этот код. Если вы не запрашивали сброс пароля, немедленно обратитесь в поддержку.
+
+
+
+
Приглашение в команду
+
Вас пригласили присоединиться к команде {{teamName}}!
+
+
Присоединиться к команде
+
+
+ Если кнопка не работает, скопируйте и вставьте эту ссылку в браузер:
+ {{inviteUrl}}
+
+
+
+
+
diff --git a/templates/team-invitation.hbs b/templates/team-invitation.hbs
new file mode 100644
index 0000000..4d7198a
--- /dev/null
+++ b/templates/team-invitation.hbs
@@ -0,0 +1,52 @@
+
+
+