diff --git a/.gitignore b/.gitignore
index 3ee761b..fad5c77 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ node_modules/
# Build outputs
dist/
+dist-ssr
build/
*.tsbuildinfo
.next/
@@ -74,3 +75,13 @@ dist/
# pnpm
.pnpm-store/
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/apps/admin/.gitignore b/apps/admin/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/apps/admin/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/apps/admin/README.md b/apps/admin/README.md
new file mode 100644
index 0000000..4dcad1f
--- /dev/null
+++ b/apps/admin/README.md
@@ -0,0 +1,73 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## React Compiler
+
+The React Compiler is currently not compatible with SWC. See [this issue](https://github.com/vitejs/vite-plugin-react/issues/428) for tracking the progress.
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
+
+```js
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+
+ // Remove tseslint.configs.recommended and replace with this
+ tseslint.configs.recommendedTypeChecked,
+ // Alternatively, use this for stricter rules
+ tseslint.configs.strictTypeChecked,
+ // Optionally, add this for stylistic rules
+ tseslint.configs.stylisticTypeChecked,
+
+ // Other configs...
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+])
+```
+
+You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
+
+```js
+// eslint.config.js
+import reactX from 'eslint-plugin-react-x'
+import reactDom from 'eslint-plugin-react-dom'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+ // Enable lint rules for React
+ reactX.configs['recommended-typescript'],
+ // Enable lint rules for React DOM
+ reactDom.configs.recommended,
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+])
+```
diff --git a/apps/admin/eslint.config.js b/apps/admin/eslint.config.js
new file mode 100644
index 0000000..5e6b472
--- /dev/null
+++ b/apps/admin/eslint.config.js
@@ -0,0 +1,23 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ tseslint.configs.recommended,
+ reactHooks.configs.flat.recommended,
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ },
+])
diff --git a/apps/admin/index.html b/apps/admin/index.html
new file mode 100644
index 0000000..14f807b
--- /dev/null
+++ b/apps/admin/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ admin
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/admin/package.json b/apps/admin/package.json
new file mode 100644
index 0000000..d258783
--- /dev/null
+++ b/apps/admin/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "admin",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.1",
+ "@types/node": "^24.10.0",
+ "@types/react": "^19.2.2",
+ "@types/react-dom": "^19.2.2",
+ "@vitejs/plugin-react-swc": "^4.2.1",
+ "eslint": "^9.39.1",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.4.24",
+ "globals": "^16.5.0",
+ "typescript": "~5.9.3",
+ "typescript-eslint": "^8.46.3",
+ "vite": "^7.2.2"
+ }
+}
diff --git a/apps/admin/public/vite.svg b/apps/admin/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/apps/admin/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/admin/src/App.css b/apps/admin/src/App.css
new file mode 100644
index 0000000..b9d355d
--- /dev/null
+++ b/apps/admin/src/App.css
@@ -0,0 +1,42 @@
+#root {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx
new file mode 100644
index 0000000..3d7ded3
--- /dev/null
+++ b/apps/admin/src/App.tsx
@@ -0,0 +1,35 @@
+import { useState } from 'react'
+import reactLogo from './assets/react.svg'
+import viteLogo from '/vite.svg'
+import './App.css'
+
+function App() {
+ const [count, setCount] = useState(0)
+
+ return (
+ <>
+
+ Vite + React
+
+
+
+ Edit src/App.tsx and save to test HMR
+
+
+
+ Click on the Vite and React logos to learn more
+
+ >
+ )
+}
+
+export default App
diff --git a/apps/admin/src/assets/react.svg b/apps/admin/src/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/apps/admin/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/admin/src/index.css b/apps/admin/src/index.css
new file mode 100644
index 0000000..08a3ac9
--- /dev/null
+++ b/apps/admin/src/index.css
@@ -0,0 +1,68 @@
+:root {
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/apps/admin/src/main.tsx b/apps/admin/src/main.tsx
new file mode 100644
index 0000000..bef5202
--- /dev/null
+++ b/apps/admin/src/main.tsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import App from './App.tsx'
+
+createRoot(document.getElementById('root')!).render(
+
+
+ ,
+)
diff --git a/apps/admin/tsconfig.app.json b/apps/admin/tsconfig.app.json
new file mode 100644
index 0000000..a9b5a59
--- /dev/null
+++ b/apps/admin/tsconfig.app.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "types": ["vite/client"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/apps/admin/tsconfig.json b/apps/admin/tsconfig.json
new file mode 100644
index 0000000..1ffef60
--- /dev/null
+++ b/apps/admin/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/apps/admin/tsconfig.node.json b/apps/admin/tsconfig.node.json
new file mode 100644
index 0000000..8a67f62
--- /dev/null
+++ b/apps/admin/tsconfig.node.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "types": ["node"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/apps/admin/vite.config.ts b/apps/admin/vite.config.ts
new file mode 100644
index 0000000..2328e17
--- /dev/null
+++ b/apps/admin/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react-swc'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+})
diff --git a/apps/api/.env.development b/apps/api/.env.development
index 6697180..0fb3a10 100644
--- a/apps/api/.env.development
+++ b/apps/api/.env.development
@@ -7,3 +7,6 @@ DB_USER=
DB_PASSWORD=
DB_DATABASE=vest_mind_test
# DB_DATABASE_TEST=vest_mind_test
+
+JWT_SECRET=vest_thinking_key
+JWT_EXPIRES_IN=7d
\ No newline at end of file
diff --git a/apps/api/package.json b/apps/api/package.json
index 89c85ad..a9ba651 100644
--- a/apps/api/package.json
+++ b/apps/api/package.json
@@ -24,16 +24,22 @@
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
+ "@nestjs/jwt": "^11.0.1",
"@nestjs/mapped-types": "^2.1.0",
+ "@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.2.2",
"@nestjs/typeorm": "^11.0.0",
+ "@types/jsonwebtoken": "^9.0.10",
+ "bcrypt": "^6.0.0",
"body-parser": "^2.2.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"compression": "^1.8.1",
"express-rate-limit": "^8.2.1",
"helmet": "^8.1.0",
+ "passport": "^0.7.0",
+ "passport-jwt": "^4.0.1",
"pg": "^8.16.3",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
@@ -45,11 +51,13 @@
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
+ "@types/bcrypt": "^6.0.0",
"@types/body-parser": "^1.19.6",
"@types/compression": "^1.8.1",
"@types/express": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^22.10.7",
+ "@types/passport-jwt": "^4.0.1",
"@types/pg": "^8.15.6",
"@types/supertest": "^6.0.2",
"eslint": "^9.18.0",
diff --git a/apps/api/src/app.controller.spec.ts b/apps/api/src/app.controller.spec.ts
deleted file mode 100644
index 2552ec5..0000000
--- a/apps/api/src/app.controller.spec.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Test, TestingModule } from '@nestjs/testing';
-import { AppController } from './app.controller';
-import { AppService } from './app.service';
-
-describe('AppController', () => {
- let appController: AppController;
-
- beforeEach(async () => {
- const app: TestingModule = await Test.createTestingModule({
- controllers: [AppController],
- providers: [AppService],
- }).compile();
-
- appController = app.get(AppController);
- });
-
- describe('root', () => {
- it('should return "Hello World!"', () => {
- expect(appController.getHello()).toBe('Hello World!');
- });
- });
-});
diff --git a/apps/api/src/app.controller.ts b/apps/api/src/app.controller.ts
deleted file mode 100644
index a325e8b..0000000
--- a/apps/api/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/apps/api/src/app.module.ts b/apps/api/src/app.module.ts
index 2f7b470..b29f273 100644
--- a/apps/api/src/app.module.ts
+++ b/apps/api/src/app.module.ts
@@ -3,8 +3,8 @@ import { ConfigModule } from '@nestjs/config';
import { DatabaseModule } from './database/database.module';
import { CoreModule } from './core/core.module';
import { BrokerModule } from './modules/broker/broker.module';
-import { AppController } from './app.controller';
-import { AppService } from './app.service';
+import { UserModule } from './modules/user/user.module';
+import { AuthModule } from './modules/auth/auth.module';
@Module({
imports: [
@@ -18,8 +18,10 @@ import { AppService } from './app.service';
CoreModule,
DatabaseModule,
BrokerModule,
+ UserModule,
+ AuthModule,
],
- controllers: [AppController],
- providers: [AppService],
+ controllers: [],
+ providers: [],
})
export class AppModule {}
diff --git a/apps/api/src/app.service.ts b/apps/api/src/app.service.ts
deleted file mode 100644
index 61b7a5b..0000000
--- a/apps/api/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/apps/api/src/modules/auth/auth.controller.ts b/apps/api/src/modules/auth/auth.controller.ts
new file mode 100644
index 0000000..968b759
--- /dev/null
+++ b/apps/api/src/modules/auth/auth.controller.ts
@@ -0,0 +1,66 @@
+import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
+import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
+import { AuthService } from './auth.service';
+import { LoginDto } from './dto/login.dto';
+import { LoginResponse } from './interfaces/login-response.interface';
+
+@ApiTags('auth')
+@Controller('auth')
+export class AuthController {
+ constructor(private readonly authService: AuthService) {}
+
+ /**
+ * 用户登录
+ */
+ @Post('login')
+ @HttpCode(HttpStatus.OK)
+ @ApiOperation({
+ summary: '用户登录',
+ description: '使用用户名/邮箱和密码登录,返回 access_token 和用户信息',
+ })
+ @ApiResponse({
+ status: 200,
+ description: '登录成功',
+ schema: {
+ type: 'object',
+ properties: {
+ code: { type: 'number', example: 0 },
+ message: { type: 'string', example: 'success' },
+ data: {
+ type: 'object',
+ properties: {
+ accessToken: {
+ type: 'string',
+ example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
+ },
+ user: {
+ type: 'object',
+ properties: {
+ userId: { type: 'number', example: 1 },
+ username: {
+ type: 'string',
+ example: 'john_doe',
+ },
+ email: {
+ type: 'string',
+ example: 'user@example.com',
+ },
+ nickname: {
+ type: 'string',
+ example: 'John Doe',
+ },
+ role: { type: 'string', example: 'user' },
+ },
+ },
+ },
+ },
+ timestamp: { type: 'string' },
+ },
+ },
+ })
+ @ApiResponse({ status: 401, description: '用户名或密码错误' })
+ @ApiResponse({ status: 400, description: '请求参数错误' })
+ async login(@Body() loginDto: LoginDto): Promise {
+ return this.authService.login(loginDto);
+ }
+}
diff --git a/apps/api/src/modules/auth/auth.module.ts b/apps/api/src/modules/auth/auth.module.ts
new file mode 100644
index 0000000..22791b9
--- /dev/null
+++ b/apps/api/src/modules/auth/auth.module.ts
@@ -0,0 +1,41 @@
+import { Module } from '@nestjs/common';
+import { JwtModule } from '@nestjs/jwt';
+import { PassportModule } from '@nestjs/passport';
+import { ConfigModule, ConfigService } from '@nestjs/config';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { AuthService } from './auth.service';
+import { AuthController } from './auth.controller';
+import { JwtStrategy } from './strategies/jwt.strategy';
+import { User } from '../user/user.entity';
+
+@Module({
+ imports: [
+ TypeOrmModule.forFeature([User]),
+ PassportModule.register({ defaultStrategy: 'jwt' }),
+ JwtModule.registerAsync({
+ imports: [ConfigModule],
+ // @ts-expect-error - JWT expiresIn accepts string but type definition is strict
+ useFactory: (configService: ConfigService) => {
+ const expiresIn = configService.get(
+ 'JWT_EXPIRES_IN',
+ '7d',
+ );
+ const secret = configService.get(
+ 'JWT_SECRET',
+ 'your-secret-key-change-in-production',
+ );
+ return {
+ secret: secret,
+ signOptions: {
+ expiresIn: expiresIn || '7d', // 默认7天过期
+ },
+ };
+ },
+ inject: [ConfigService],
+ }),
+ ],
+ controllers: [AuthController],
+ providers: [AuthService, JwtStrategy],
+ exports: [AuthService, JwtModule],
+})
+export class AuthModule {}
diff --git a/apps/api/src/modules/auth/auth.service.ts b/apps/api/src/modules/auth/auth.service.ts
new file mode 100644
index 0000000..d6cf2c4
--- /dev/null
+++ b/apps/api/src/modules/auth/auth.service.ts
@@ -0,0 +1,73 @@
+import { Injectable, UnauthorizedException } from '@nestjs/common';
+import { JwtService } from '@nestjs/jwt';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Repository } from 'typeorm';
+import * as bcrypt from 'bcrypt';
+import { User } from '../user/user.entity';
+import { LoginDto } from './dto/login.dto';
+import { JwtPayload } from './interfaces/jwt-payload.interface';
+import { LoginResponse } from './interfaces/login-response.interface';
+
+@Injectable()
+export class AuthService {
+ constructor(
+ @InjectRepository(User)
+ private readonly userRepository: Repository,
+ private readonly jwtService: JwtService,
+ ) {}
+
+ /**
+ * 用户登录
+ */
+ async login(loginDto: LoginDto): Promise {
+ // 根据用户名或邮箱查找用户
+ const user = await this.userRepository.findOne({
+ where: [
+ { username: loginDto.usernameOrEmail },
+ { email: loginDto.usernameOrEmail },
+ ],
+ });
+
+ if (!user) {
+ throw new UnauthorizedException('用户名或密码错误');
+ }
+
+ // 检查用户状态
+ if (user.status !== 'active') {
+ throw new UnauthorizedException('用户已被禁用');
+ }
+
+ // 验证密码
+ const isPasswordValid = await bcrypt.compare(
+ loginDto.password,
+ user.passwordHash,
+ );
+
+ if (!isPasswordValid) {
+ throw new UnauthorizedException('用户名或密码错误');
+ }
+
+ // 更新最后登录时间
+ user.lastLoginAt = new Date();
+ await this.userRepository.save(user);
+
+ // 生成 JWT token
+ const payload: JwtPayload = {
+ sub: user.userId,
+ username: user.username,
+ email: user.email,
+ role: user.role,
+ };
+
+ const accessToken = this.jwtService.sign(payload);
+
+ // 返回 token 和用户信息(排除密码)
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { passwordHash, ...userWithoutPassword } = user;
+
+ return {
+ accessToken,
+ user: userWithoutPassword,
+ };
+ }
+}
diff --git a/apps/api/src/modules/auth/decorators/roles.decorator.ts b/apps/api/src/modules/auth/decorators/roles.decorator.ts
new file mode 100644
index 0000000..802d2d0
--- /dev/null
+++ b/apps/api/src/modules/auth/decorators/roles.decorator.ts
@@ -0,0 +1,37 @@
+import { SetMetadata } from '@nestjs/common';
+
+/**
+ * 角色权限装饰器
+ *
+ * 这个装饰器用于在控制器或方法上标记需要的角色权限。
+ * 配合 RolesGuard 使用,实现基于角色的访问控制(RBAC)。
+ *
+ * 使用示例:
+ * @Roles('admin', 'super_admin')
+ * @Get()
+ * findAll() { ... }
+ *
+ * 工作原理:
+ * 1. SetMetadata 会在目标方法或类上设置元数据
+ * 2. ROLES_KEY 作为元数据的键,存储角色数组
+ * 3. RolesGuard 通过 Reflector 读取这些元数据来验证用户权限
+ */
+
+/**
+ * 元数据键名
+ * 用于在 Reflector 中存储和读取角色信息
+ */
+export const ROLES_KEY = 'roles';
+
+/**
+ * 角色权限装饰器工厂函数
+ *
+ * @param roles - 允许访问的角色列表(可变参数)
+ * @returns 返回一个装饰器函数,用于设置元数据
+ *
+ * 示例:
+ * - @Roles('admin') - 只允许 admin 角色访问
+ * - @Roles('admin', 'super_admin') - 允许 admin 或 super_admin 角色访问
+ * - @Roles() - 不传参数时,RolesGuard 会允许所有已认证用户访问
+ */
+export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
diff --git a/apps/api/src/modules/auth/dto/login.dto.ts b/apps/api/src/modules/auth/dto/login.dto.ts
new file mode 100644
index 0000000..142dd58
--- /dev/null
+++ b/apps/api/src/modules/auth/dto/login.dto.ts
@@ -0,0 +1,22 @@
+import { IsString, IsNotEmpty, MinLength } from 'class-validator';
+import { ApiProperty } from '@nestjs/swagger';
+
+export class LoginDto {
+ @ApiProperty({
+ description: '用户名或邮箱',
+ example: 'john_doe',
+ })
+ @IsString()
+ @IsNotEmpty()
+ @MinLength(3)
+ usernameOrEmail: string;
+
+ @ApiProperty({
+ description: '密码',
+ example: 'password123',
+ })
+ @IsString()
+ @IsNotEmpty()
+ @MinLength(6)
+ password: string;
+}
diff --git a/apps/api/src/modules/auth/guards/jwt-auth.guard.ts b/apps/api/src/modules/auth/guards/jwt-auth.guard.ts
new file mode 100644
index 0000000..c26e82a
--- /dev/null
+++ b/apps/api/src/modules/auth/guards/jwt-auth.guard.ts
@@ -0,0 +1,24 @@
+import {
+ Injectable,
+ ExecutionContext,
+ UnauthorizedException,
+} from '@nestjs/common';
+import { AuthGuard } from '@nestjs/passport';
+
+@Injectable()
+export class JwtAuthGuard extends AuthGuard('jwt') {
+ handleRequest(
+ err: Error | null,
+ user: TUser | false,
+ info: Error | string | undefined,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ _context: ExecutionContext,
+ ): TUser {
+ // 如果认证失败(user 为 false 或 undefined,或者有错误)
+ if (err || !user || info) {
+ throw new UnauthorizedException('身份验证失败');
+ }
+
+ return user;
+ }
+}
diff --git a/apps/api/src/modules/auth/guards/roles.guard.ts b/apps/api/src/modules/auth/guards/roles.guard.ts
new file mode 100644
index 0000000..12b51c6
--- /dev/null
+++ b/apps/api/src/modules/auth/guards/roles.guard.ts
@@ -0,0 +1,44 @@
+import {
+ Injectable,
+ CanActivate,
+ ExecutionContext,
+ ForbiddenException,
+} from '@nestjs/common';
+import { Reflector } from '@nestjs/core';
+import { ROLES_KEY } from '../decorators/roles.decorator';
+
+@Injectable()
+export class RolesGuard implements CanActivate {
+ constructor(private reflector: Reflector) {}
+
+ canActivate(context: ExecutionContext): boolean {
+ const requiredRoles = this.reflector.getAllAndOverride(
+ ROLES_KEY,
+ [context.getHandler(), context.getClass()],
+ );
+
+ if (!requiredRoles) {
+ // 如果没有设置角色要求,允许访问
+ return true;
+ }
+
+ const request = context.switchToHttp().getRequest<{
+ user?: { role: string };
+ }>();
+ const user = request.user;
+
+ if (!user) {
+ throw new ForbiddenException('未授权访问');
+ }
+
+ const hasRole = requiredRoles.some((role) => user.role === role);
+
+ if (!hasRole) {
+ throw new ForbiddenException(
+ `需要以下角色之一:${requiredRoles.join('、')}`,
+ );
+ }
+
+ return true;
+ }
+}
diff --git a/apps/api/src/modules/auth/interfaces/jwt-payload.interface.ts b/apps/api/src/modules/auth/interfaces/jwt-payload.interface.ts
new file mode 100644
index 0000000..bbab832
--- /dev/null
+++ b/apps/api/src/modules/auth/interfaces/jwt-payload.interface.ts
@@ -0,0 +1,6 @@
+export interface JwtPayload {
+ sub: number; // 用户ID
+ username: string;
+ email: string;
+ role: string;
+}
diff --git a/apps/api/src/modules/auth/interfaces/login-response.interface.ts b/apps/api/src/modules/auth/interfaces/login-response.interface.ts
new file mode 100644
index 0000000..4bcb2b8
--- /dev/null
+++ b/apps/api/src/modules/auth/interfaces/login-response.interface.ts
@@ -0,0 +1,6 @@
+import { User } from '../../user/user.entity';
+
+export interface LoginResponse {
+ accessToken: string;
+ user: Omit; // 排除密码哈希
+}
diff --git a/apps/api/src/modules/auth/strategies/jwt.strategy.ts b/apps/api/src/modules/auth/strategies/jwt.strategy.ts
new file mode 100644
index 0000000..b5a99ea
--- /dev/null
+++ b/apps/api/src/modules/auth/strategies/jwt.strategy.ts
@@ -0,0 +1,38 @@
+import { Injectable, UnauthorizedException } from '@nestjs/common';
+import { PassportStrategy } from '@nestjs/passport';
+import { ExtractJwt, Strategy } from 'passport-jwt';
+import { ConfigService } from '@nestjs/config';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Repository } from 'typeorm';
+import { User } from '../../user/user.entity';
+import { JwtPayload } from '../interfaces/jwt-payload.interface';
+
+@Injectable()
+export class JwtStrategy extends PassportStrategy(Strategy) {
+ constructor(
+ private readonly configService: ConfigService,
+ @InjectRepository(User)
+ private readonly userRepository: Repository,
+ ) {
+ super({
+ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
+ ignoreExpiration: false,
+ secretOrKey: configService.get(
+ 'JWT_SECRET',
+ 'your-secret-key',
+ ),
+ });
+ }
+
+ async validate(payload: JwtPayload): Promise {
+ const user = await this.userRepository.findOne({
+ where: { userId: payload.sub },
+ });
+
+ if (!user || user.status !== 'active') {
+ throw new UnauthorizedException('用户不存在或已被禁用');
+ }
+
+ return user;
+ }
+}
diff --git a/apps/api/src/modules/broker/broker.service.ts b/apps/api/src/modules/broker/broker.service.ts
index a9e753c..7c3ef10 100644
--- a/apps/api/src/modules/broker/broker.service.ts
+++ b/apps/api/src/modules/broker/broker.service.ts
@@ -32,7 +32,7 @@ export class BrokerService {
if (existingByCode) {
throw new ConflictException(
- `Broker with code "${createBrokerDto.brokerCode}" already exists in region "${createBrokerDto.region}"`,
+ `地区 "${createBrokerDto.region}" 中已存在代码为 "${createBrokerDto.brokerCode}" 的券商`,
);
}
@@ -46,7 +46,7 @@ export class BrokerService {
if (existingByName) {
throw new ConflictException(
- `Broker with name "${createBrokerDto.brokerName}" already exists in region "${createBrokerDto.region}"`,
+ `地区 "${createBrokerDto.region}" 中已存在名称为 "${createBrokerDto.brokerName}" 的券商`,
);
}
@@ -88,10 +88,10 @@ export class BrokerService {
if (existingBrokers.length > 0) {
const conflicts = existingBrokers.map(
- (b) => `${b.brokerCode} in ${b.region}`,
+ (b) => `${b.brokerCode} (${b.region})`,
);
throw new ConflictException(
- `The following brokers already exist: ${conflicts.join(', ')}`,
+ `以下券商已存在:${conflicts.join('、')}`,
);
}
@@ -101,7 +101,7 @@ export class BrokerService {
);
if (uniquePairs.size !== codeRegionPairs.length) {
throw new ConflictException(
- 'Duplicate broker_code and region combinations in batch data',
+ '批量数据中存在重复的券商代码和地区组合',
);
}
@@ -152,7 +152,7 @@ export class BrokerService {
});
if (!broker) {
- throw new NotFoundException(`Broker with ID ${id} not found`);
+ throw new NotFoundException(`未找到ID为 ${id} 的券商`);
}
return broker;
@@ -187,9 +187,7 @@ export class BrokerService {
const broker = await this.brokerRepository.findOne({ where });
if (!broker) {
- throw new NotFoundException(
- 'Broker not found with the given conditions',
- );
+ throw new NotFoundException('未找到符合给定条件的券商');
}
return broker;
@@ -218,7 +216,7 @@ export class BrokerService {
if (existing && existing.brokerId !== id) {
throw new ConflictException(
- `Broker with code "${newCode}" already exists in region "${newRegion}"`,
+ `地区 "${newRegion}" 中已存在代码为 "${newCode}" 的券商`,
);
}
}
@@ -237,7 +235,7 @@ export class BrokerService {
if (existing && existing.brokerId !== id) {
throw new ConflictException(
- `Broker with name "${newName}" already exists in region "${newRegion}"`,
+ `地区 "${newRegion}" 中已存在名称为 "${newName}" 的券商`,
);
}
}
diff --git a/apps/api/src/modules/user/dto/change-password.dto.ts b/apps/api/src/modules/user/dto/change-password.dto.ts
new file mode 100644
index 0000000..50fa22c
--- /dev/null
+++ b/apps/api/src/modules/user/dto/change-password.dto.ts
@@ -0,0 +1,23 @@
+import { IsString, IsNotEmpty, MinLength, MaxLength } from 'class-validator';
+import { ApiProperty } from '@nestjs/swagger';
+
+export class ChangePasswordDto {
+ @ApiProperty({
+ description: '旧密码',
+ example: 'OldPassword123!',
+ })
+ @IsString()
+ @IsNotEmpty()
+ oldPassword: string;
+
+ @ApiProperty({
+ description: '新密码',
+ example: 'NewPassword123!',
+ minLength: 6,
+ })
+ @IsString()
+ @IsNotEmpty()
+ @MinLength(6)
+ @MaxLength(100)
+ newPassword: string;
+}
diff --git a/apps/api/src/modules/user/dto/create-user.dto.ts b/apps/api/src/modules/user/dto/create-user.dto.ts
new file mode 100644
index 0000000..25c6ff2
--- /dev/null
+++ b/apps/api/src/modules/user/dto/create-user.dto.ts
@@ -0,0 +1,109 @@
+import {
+ IsString,
+ IsNotEmpty,
+ IsOptional,
+ IsEmail,
+ MinLength,
+ MaxLength,
+ Matches,
+ IsIn,
+} from 'class-validator';
+import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
+
+export class CreateUserDto {
+ @ApiProperty({
+ description: '用户名',
+ example: 'john_doe',
+ maxLength: 100,
+ })
+ @IsString()
+ @IsNotEmpty()
+ @MinLength(3)
+ @MaxLength(100)
+ username: string;
+
+ @ApiProperty({
+ description: '密码(明文,服务端会使用 bcrypt 加密后存储)',
+ example: 'SecurePassword123!',
+ minLength: 6,
+ })
+ @IsString()
+ @IsNotEmpty()
+ @MinLength(6)
+ @MaxLength(100)
+ password: string;
+
+ @ApiProperty({
+ description: '邮箱',
+ example: 'user@example.com',
+ maxLength: 100,
+ })
+ @IsEmail()
+ @IsNotEmpty()
+ @MaxLength(100)
+ email: string;
+
+ @ApiPropertyOptional({
+ description: '用户昵称',
+ example: 'John Doe',
+ maxLength: 100,
+ })
+ @IsOptional()
+ @IsString()
+ @MaxLength(100)
+ nickname?: string;
+
+ @ApiPropertyOptional({
+ description: '头像URL',
+ example: 'https://example.com/avatar.jpg',
+ maxLength: 255,
+ })
+ @IsOptional()
+ @IsString()
+ @MaxLength(255)
+ avatarUrl?: string;
+
+ @ApiPropertyOptional({
+ description: '电话号码',
+ example: '13800138000',
+ maxLength: 20,
+ })
+ @IsOptional()
+ @IsString()
+ @Matches(/^[0-9+\-() ]+$/, {
+ message: '电话号码格式不正确',
+ })
+ @MaxLength(20)
+ phone?: string;
+
+ @ApiPropertyOptional({
+ description: '微信 openId',
+ example: 'openid123456',
+ maxLength: 100,
+ })
+ @IsOptional()
+ @IsString()
+ @MaxLength(100)
+ openId?: string;
+
+ @ApiPropertyOptional({
+ description: '微信 unionId',
+ example: 'unionid123456',
+ maxLength: 100,
+ })
+ @IsOptional()
+ @IsString()
+ @MaxLength(100)
+ unionId?: string;
+
+ @ApiPropertyOptional({
+ description: '用户角色',
+ example: 'user',
+ enum: ['user', 'admin', 'super_admin'],
+ default: 'user',
+ })
+ @IsOptional()
+ @IsString()
+ @IsIn(['user', 'admin', 'super_admin'])
+ role?: string;
+}
diff --git a/apps/api/src/modules/user/dto/query-user.dto.ts b/apps/api/src/modules/user/dto/query-user.dto.ts
new file mode 100644
index 0000000..90d6f57
--- /dev/null
+++ b/apps/api/src/modules/user/dto/query-user.dto.ts
@@ -0,0 +1,20 @@
+import { IsOptional, IsString, IsEmail } from 'class-validator';
+import { ApiPropertyOptional } from '@nestjs/swagger';
+
+export class QueryUserDto {
+ @ApiPropertyOptional({
+ description: '用户名',
+ example: 'john_doe',
+ })
+ @IsOptional()
+ @IsString()
+ username?: string;
+
+ @ApiPropertyOptional({
+ description: '邮箱',
+ example: 'user@example.com',
+ })
+ @IsOptional()
+ @IsEmail()
+ email?: string;
+}
diff --git a/apps/api/src/modules/user/dto/update-user.dto.ts b/apps/api/src/modules/user/dto/update-user.dto.ts
new file mode 100644
index 0000000..4f45ebc
--- /dev/null
+++ b/apps/api/src/modules/user/dto/update-user.dto.ts
@@ -0,0 +1,73 @@
+import {
+ IsString,
+ IsOptional,
+ IsEmail,
+ MaxLength,
+ Matches,
+ IsIn,
+} from 'class-validator';
+import { ApiPropertyOptional } from '@nestjs/swagger';
+
+export class UpdateUserDto {
+ @ApiPropertyOptional({
+ description: '邮箱',
+ example: 'newemail@example.com',
+ maxLength: 100,
+ })
+ @IsOptional()
+ @IsEmail()
+ @MaxLength(100)
+ email?: string;
+
+ @ApiPropertyOptional({
+ description: '电话号码',
+ example: '13800138000',
+ maxLength: 20,
+ })
+ @IsOptional()
+ @IsString()
+ @Matches(/^[0-9+\-() ]+$/, {
+ message: '电话号码格式不正确',
+ })
+ @MaxLength(20)
+ phone?: string;
+
+ @ApiPropertyOptional({
+ description: '用户昵称',
+ example: 'John Doe',
+ maxLength: 100,
+ })
+ @IsOptional()
+ @IsString()
+ @MaxLength(100)
+ nickname?: string;
+
+ @ApiPropertyOptional({
+ description: '头像URL',
+ example: 'https://example.com/avatar.jpg',
+ maxLength: 255,
+ })
+ @IsOptional()
+ @IsString()
+ @MaxLength(255)
+ avatarUrl?: string;
+
+ @ApiPropertyOptional({
+ description: '用户状态',
+ example: 'active',
+ enum: ['active', 'inactive', 'deleted'],
+ })
+ @IsOptional()
+ @IsString()
+ status?: string;
+
+ @ApiPropertyOptional({
+ description: '用户角色',
+ example: 'user',
+ enum: ['user', 'admin', 'super_admin'],
+ })
+ @IsOptional()
+ @IsString()
+ @IsIn(['user', 'admin', 'super_admin'])
+ role?: string;
+}
diff --git a/apps/api/src/modules/user/user.controller.ts b/apps/api/src/modules/user/user.controller.ts
new file mode 100644
index 0000000..8cb51da
--- /dev/null
+++ b/apps/api/src/modules/user/user.controller.ts
@@ -0,0 +1,180 @@
+import {
+ Controller,
+ Get,
+ Post,
+ Body,
+ Patch,
+ Param,
+ Delete,
+ Query,
+ HttpCode,
+ HttpStatus,
+ UseGuards,
+} from '@nestjs/common';
+import {
+ ApiTags,
+ ApiOperation,
+ ApiResponse,
+ ApiParam,
+ ApiBearerAuth,
+} from '@nestjs/swagger';
+import { UserService } from './user.service';
+import { User } from './user.entity';
+import { CreateUserDto } from './dto/create-user.dto';
+import { UpdateUserDto } from './dto/update-user.dto';
+import { QueryUserDto } from './dto/query-user.dto';
+import { ChangePasswordDto } from './dto/change-password.dto';
+import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
+import { RolesGuard } from '../auth/guards/roles.guard';
+import { Roles } from '../auth/decorators/roles.decorator';
+
+@ApiTags('user')
+@Controller('user')
+export class UserController {
+ constructor(private readonly userService: UserService) {}
+
+ /**
+ * 注册用户
+ */
+ @Post('register')
+ @HttpCode(HttpStatus.CREATED)
+ @ApiOperation({
+ summary: '注册用户',
+ description: '创建新用户,username、password、email 为必填项',
+ })
+ @ApiResponse({
+ status: 201,
+ description: '注册成功',
+ type: User,
+ })
+ @ApiResponse({ status: 400, description: '请求参数错误' })
+ @ApiResponse({
+ status: 409,
+ description: '用户名、邮箱或其他唯一字段已存在',
+ })
+ create(@Body() createUserDto: CreateUserDto): Promise {
+ return this.userService.create(createUserDto);
+ }
+
+ /**
+ * 查询所有用户
+ */
+ @Get()
+ @UseGuards(JwtAuthGuard, RolesGuard)
+ @Roles('admin', 'super_admin')
+ @ApiBearerAuth()
+ @ApiOperation({
+ summary: '查询所有用户',
+ description: '获取所有用户列表(需要管理员权限)',
+ })
+ @ApiResponse({
+ status: 200,
+ description: '查询成功',
+ type: [User],
+ })
+ @ApiResponse({ status: 401, description: '未授权' })
+ @ApiResponse({ status: 403, description: '权限不足' })
+ findAll(): Promise {
+ return this.userService.findAll();
+ }
+
+ /**
+ * 根据 username 或 email 查询单个用户
+ */
+ @Get('find')
+ @ApiOperation({
+ summary: '查询单个用户',
+ description: '根据 username 或 email 查询用户',
+ })
+ @ApiResponse({
+ status: 200,
+ description: '查询成功',
+ type: User,
+ })
+ @ApiResponse({
+ status: 400,
+ description: '请求参数错误,必须提供 username 或 email',
+ })
+ @ApiResponse({ status: 404, description: '用户不存在' })
+ findOne(@Query() queryDto: QueryUserDto): Promise {
+ return this.userService.findOne(queryDto);
+ }
+
+ /**
+ * 根据 ID 查询单个用户
+ */
+ @Get(':id')
+ @ApiOperation({
+ summary: '根据ID查询用户',
+ description: '根据用户ID获取详细信息',
+ })
+ @ApiParam({ name: 'id', description: '用户ID', type: Number })
+ @ApiResponse({
+ status: 200,
+ description: '查询成功',
+ type: User,
+ })
+ @ApiResponse({ status: 404, description: '用户不存在' })
+ findOneById(@Param('id') id: string): Promise {
+ return this.userService.findOneById(+id);
+ }
+
+ /**
+ * 更新用户信息
+ */
+ @Patch(':id')
+ @ApiOperation({
+ summary: '更新用户信息',
+ description: '更新用户信息,不允许修改 username、openId、unionId',
+ })
+ @ApiParam({ name: 'id', description: '用户ID', type: Number })
+ @ApiResponse({
+ status: 200,
+ description: '更新成功',
+ type: User,
+ })
+ @ApiResponse({ status: 404, description: '用户不存在' })
+ @ApiResponse({ status: 409, description: '邮箱或手机号已存在' })
+ update(
+ @Param('id') id: string,
+ @Body() updateUserDto: UpdateUserDto,
+ ): Promise {
+ return this.userService.update(+id, updateUserDto);
+ }
+
+ /**
+ * 修改密码
+ */
+ @Patch(':id/password')
+ @HttpCode(HttpStatus.NO_CONTENT)
+ @ApiOperation({
+ summary: '修改密码',
+ description: '修改用户密码,需要先验证旧密码',
+ })
+ @ApiParam({ name: 'id', description: '用户ID', type: Number })
+ @ApiResponse({ status: 204, description: '密码修改成功' })
+ @ApiResponse({ status: 400, description: '旧密码错误' })
+ @ApiResponse({ status: 404, description: '用户不存在' })
+ changePassword(
+ @Param('id') id: string,
+ @Body() changePasswordDto: ChangePasswordDto,
+ ): Promise {
+ return this.userService.changePassword(+id, changePasswordDto);
+ }
+
+ /**
+ * 删除用户(软删除)
+ */
+ @Delete(':id')
+ @HttpCode(HttpStatus.NO_CONTENT)
+ @ApiOperation({
+ summary: '删除用户',
+ description: '软删除用户,将状态更新为 deleted',
+ })
+ @ApiParam({ name: 'id', description: '用户ID', type: Number })
+ @ApiResponse({ status: 204, description: '删除成功' })
+ @ApiResponse({ status: 404, description: '用户不存在' })
+ remove(@Param('id') id: string): Promise {
+ return this.userService.remove(+id);
+ }
+}
diff --git a/apps/api/src/modules/user/user.entity.ts b/apps/api/src/modules/user/user.entity.ts
new file mode 100644
index 0000000..6271736
--- /dev/null
+++ b/apps/api/src/modules/user/user.entity.ts
@@ -0,0 +1,142 @@
+import {
+ Entity,
+ Column,
+ PrimaryGeneratedColumn,
+ CreateDateColumn,
+ UpdateDateColumn,
+ Index,
+} from 'typeorm';
+import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
+
+@Entity('user')
+export class User {
+ @ApiProperty({ description: '用户ID', example: 1 })
+ @PrimaryGeneratedColumn({ name: 'user_id' })
+ userId: number;
+
+ @ApiPropertyOptional({
+ description: '微信 openId',
+ example: 'openid123456',
+ maxLength: 100,
+ })
+ @Column({ name: 'open_id', type: 'varchar', length: 100, nullable: true })
+ @Index()
+ openId?: string;
+
+ @ApiPropertyOptional({
+ description: '微信 unionId',
+ example: 'unionid123456',
+ maxLength: 100,
+ })
+ @Column({ name: 'union_id', type: 'varchar', length: 100, nullable: true })
+ @Index()
+ unionId?: string;
+
+ @ApiProperty({
+ description: '用户名',
+ example: 'john_doe',
+ maxLength: 100,
+ })
+ @Column({ name: 'username', type: 'varchar', length: 100 })
+ @Index()
+ username: string;
+
+ @ApiProperty({
+ description: '密码哈希值(bcrypt加密)',
+ example: '$2b$10$...',
+ maxLength: 255,
+ })
+ @Column({ name: 'password_hash', type: 'varchar', length: 255 })
+ passwordHash: string;
+
+ @ApiProperty({
+ description: '邮箱',
+ example: 'user@example.com',
+ maxLength: 100,
+ })
+ @Column({ name: 'email', type: 'varchar', length: 100 })
+ @Index()
+ email: string;
+
+ @ApiPropertyOptional({
+ description: '电话号码',
+ example: '13800138000',
+ maxLength: 20,
+ })
+ @Column({ name: 'phone', type: 'varchar', length: 20, nullable: true })
+ @Index()
+ phone?: string;
+
+ @ApiPropertyOptional({
+ description: '用户昵称',
+ example: 'John Doe',
+ maxLength: 100,
+ })
+ @Column({ name: 'nickname', type: 'varchar', length: 100, nullable: true })
+ nickname?: string;
+
+ @ApiPropertyOptional({
+ description: '头像URL',
+ example: 'https://example.com/avatar.jpg',
+ maxLength: 255,
+ })
+ @Column({
+ name: 'avatar_url',
+ type: 'varchar',
+ length: 255,
+ nullable: true,
+ })
+ avatarUrl?: string;
+
+ @ApiProperty({
+ description: '创建时间',
+ example: '2024-01-01T00:00:00.000Z',
+ })
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+
+ @ApiProperty({
+ description: '更新时间',
+ example: '2024-01-01T00:00:00.000Z',
+ })
+ @UpdateDateColumn({ name: 'updated_at' })
+ updatedAt: Date;
+
+ @ApiPropertyOptional({
+ description: '最后登录时间',
+ example: '2024-01-01T00:00:00.000Z',
+ })
+ @Column({ name: 'last_login_at', type: 'timestamp', nullable: true })
+ lastLoginAt?: Date;
+
+ @ApiProperty({
+ description: '用户状态',
+ example: 'active',
+ enum: ['active', 'inactive', 'deleted'],
+ default: 'active',
+ })
+ @Column({
+ name: 'status',
+ type: 'varchar',
+ length: 20,
+ default: 'active',
+ nullable: false,
+ })
+ status: string;
+
+ @ApiProperty({
+ description: '用户角色',
+ example: 'user',
+ enum: ['user', 'admin', 'super_admin'],
+ default: 'user',
+ })
+ @Column({
+ name: 'role',
+ type: 'varchar',
+ length: 20,
+ default: 'user',
+ nullable: false,
+ })
+ @Index()
+ role: string;
+}
diff --git a/apps/api/src/modules/user/user.module.ts b/apps/api/src/modules/user/user.module.ts
index 309e84a..28a1728 100644
--- a/apps/api/src/modules/user/user.module.ts
+++ b/apps/api/src/modules/user/user.module.ts
@@ -1,4 +1,13 @@
import { Module } from '@nestjs/common';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { UserService } from './user.service';
+import { UserController } from './user.controller';
+import { User } from './user.entity';
-@Module({})
+@Module({
+ imports: [TypeOrmModule.forFeature([User])],
+ controllers: [UserController],
+ providers: [UserService],
+ exports: [UserService],
+})
export class UserModule {}
diff --git a/apps/api/src/modules/user/user.service.ts b/apps/api/src/modules/user/user.service.ts
new file mode 100644
index 0000000..c8ce85e
--- /dev/null
+++ b/apps/api/src/modules/user/user.service.ts
@@ -0,0 +1,240 @@
+import {
+ Injectable,
+ NotFoundException,
+ ConflictException,
+ BadRequestException,
+} from '@nestjs/common';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Repository } from 'typeorm';
+import * as bcrypt from 'bcrypt';
+import { User } from './user.entity';
+import { CreateUserDto } from './dto/create-user.dto';
+import { UpdateUserDto } from './dto/update-user.dto';
+import { QueryUserDto } from './dto/query-user.dto';
+import { ChangePasswordDto } from './dto/change-password.dto';
+
+@Injectable()
+export class UserService {
+ constructor(
+ @InjectRepository(User)
+ private readonly userRepository: Repository,
+ ) {}
+
+ /**
+ * 注册用户
+ */
+ async create(createUserDto: CreateUserDto): Promise {
+ // 检查用户名是否已存在
+ const existingByUsername = await this.userRepository.findOne({
+ where: { username: createUserDto.username },
+ });
+
+ if (existingByUsername) {
+ throw new ConflictException(
+ `用户名 "${createUserDto.username}" 已存在`,
+ );
+ }
+
+ // 检查邮箱是否已存在
+ const existingByEmail = await this.userRepository.findOne({
+ where: { email: createUserDto.email },
+ });
+
+ if (existingByEmail) {
+ throw new ConflictException(`邮箱 "${createUserDto.email}" 已存在`);
+ }
+
+ // 检查 phone 是否已存在(如果提供了)
+ /* if (createUserDto.phone) {
+ const existingByPhone = await this.userRepository.findOne({
+ where: { phone: createUserDto.phone },
+ });
+
+ if (existingByPhone) {
+ throw new ConflictException(
+ `Phone "${createUserDto.phone}" already exists`,
+ );
+ }
+ }
+
+ // 检查 openId 是否已存在(如果提供了)
+ if (createUserDto.openId) {
+ const existingByOpenId = await this.userRepository.findOne({
+ where: { openId: createUserDto.openId },
+ });
+
+ if (existingByOpenId) {
+ throw new ConflictException(
+ `OpenId "${createUserDto.openId}" already exists`,
+ );
+ }
+ }
+
+ // 检查 unionId 是否已存在(如果提供了)
+ if (createUserDto.unionId) {
+ const existingByUnionId = await this.userRepository.findOne({
+ where: { unionId: createUserDto.unionId },
+ });
+
+ if (existingByUnionId) {
+ throw new ConflictException(
+ `UnionId "${createUserDto.unionId}" already exists`,
+ );
+ }
+ } */
+
+ // 使用 bcrypt 加密密码
+ const saltRounds = 10;
+ const passwordHash = await bcrypt.hash(
+ createUserDto.password,
+ saltRounds,
+ );
+
+ // 创建用户
+ const user = this.userRepository.create({
+ username: createUserDto.username,
+ passwordHash,
+ email: createUserDto.email,
+ nickname: createUserDto.nickname,
+ avatarUrl: createUserDto.avatarUrl,
+ phone: createUserDto.phone,
+ openId: createUserDto.openId,
+ unionId: createUserDto.unionId,
+ status: 'active',
+ role: createUserDto.role || 'user', // 默认为普通用户
+ });
+
+ return this.userRepository.save(user);
+ }
+
+ /**
+ * 查询所有用户
+ */
+ async findAll(): Promise {
+ return this.userRepository.find({
+ order: {
+ createdAt: 'DESC',
+ },
+ });
+ }
+
+ /**
+ * 根据 username 或 email 查询单个用户
+ */
+ async findOne(queryDto: QueryUserDto): Promise {
+ if (!queryDto.username && !queryDto.email) {
+ throw new BadRequestException('必须提供用户名或邮箱');
+ }
+
+ const where: { username?: string; email?: string } = {};
+ if (queryDto.username) {
+ where.username = queryDto.username;
+ }
+ if (queryDto.email) {
+ where.email = queryDto.email;
+ }
+
+ const user = await this.userRepository.findOne({ where });
+
+ if (!user) {
+ const identifier = queryDto.username || queryDto.email;
+ throw new NotFoundException(
+ `未找到${queryDto.username ? '用户名' : '邮箱'}为 "${identifier}" 的用户`,
+ );
+ }
+
+ return user;
+ }
+
+ /**
+ * 根据 ID 查询单个用户
+ */
+ async findOneById(id: number): Promise {
+ const user = await this.userRepository.findOne({
+ where: { userId: id },
+ });
+
+ if (!user) {
+ throw new NotFoundException(`未找到ID为 ${id} 的用户`);
+ }
+
+ return user;
+ }
+
+ /**
+ * 更新用户信息(不允许修改 username、openId、unionId)
+ */
+ async update(id: number, updateUserDto: UpdateUserDto): Promise {
+ const user = await this.findOneById(id);
+
+ // 如果更新邮箱,检查是否与其他用户冲突
+ if (updateUserDto.email && updateUserDto.email !== user.email) {
+ const existingByEmail = await this.userRepository.findOne({
+ where: { email: updateUserDto.email },
+ });
+
+ if (existingByEmail) {
+ throw new ConflictException(
+ `邮箱 "${updateUserDto.email}" 已存在`,
+ );
+ }
+ }
+
+ // 如果更新 phone,检查是否与其他用户冲突
+ if (updateUserDto.phone && updateUserDto.phone !== user.phone) {
+ const existingByPhone = await this.userRepository.findOne({
+ where: { phone: updateUserDto.phone },
+ });
+
+ if (existingByPhone) {
+ throw new ConflictException(
+ `手机号 "${updateUserDto.phone}" 已存在`,
+ );
+ }
+ }
+
+ // 更新用户信息
+ Object.assign(user, updateUserDto);
+ return this.userRepository.save(user);
+ }
+
+ /**
+ * 修改密码(需要先验证旧密码)
+ */
+ async changePassword(
+ id: number,
+ changePasswordDto: ChangePasswordDto,
+ ): Promise {
+ const user = await this.findOneById(id);
+
+ // 验证旧密码
+ const isOldPasswordValid = await bcrypt.compare(
+ changePasswordDto.oldPassword,
+ user.passwordHash,
+ );
+
+ if (!isOldPasswordValid) {
+ throw new BadRequestException('旧密码错误');
+ }
+
+ // 加密新密码
+ const saltRounds = 10;
+ const newPasswordHash = await bcrypt.hash(
+ changePasswordDto.newPassword,
+ saltRounds,
+ );
+
+ // 更新密码
+ user.passwordHash = newPasswordHash;
+ await this.userRepository.save(user);
+ }
+
+ /**
+ * 删除用户(软删除,更新状态为 deleted)
+ */
+ async remove(id: number): Promise {
+ const user = await this.findOneById(id);
+ user.status = 'deleted';
+ await this.userRepository.save(user);
+ }
+}
diff --git a/apps/api/src/modules/user/user.spec.ts b/apps/api/src/modules/user/user.spec.ts
deleted file mode 100644
index f30b81e..0000000
--- a/apps/api/src/modules/user/user.spec.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { User } from './user';
-
-describe('User', () => {
- it('should be defined', () => {
- expect(new User()).toBeDefined();
- });
-});
diff --git a/apps/api/src/modules/user/user.ts b/apps/api/src/modules/user/user.ts
deleted file mode 100644
index 4f82c14..0000000
--- a/apps/api/src/modules/user/user.ts
+++ /dev/null
@@ -1 +0,0 @@
-export class User {}
diff --git a/apps/api/test/modules/user/user.service.integration.spec.ts b/apps/api/test/modules/user/user.service.integration.spec.ts
new file mode 100644
index 0000000..8efb363
--- /dev/null
+++ b/apps/api/test/modules/user/user.service.integration.spec.ts
@@ -0,0 +1,293 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { ConfigModule, ConfigService } from '@nestjs/config';
+import { getRepositoryToken } from '@nestjs/typeorm';
+import { Repository } from 'typeorm';
+import { UserService } from '../../../src/modules/user/user.service';
+import { User } from '../../../src/modules/user/user.entity';
+import { UserModule } from '../../../src/modules/user/user.module';
+import { CreateUserDto } from '../../../src/modules/user/dto/create-user.dto';
+import { getDatabaseConfig } from '../../../src/database/database.config';
+import { ConflictException } from '@nestjs/common';
+
+describe('UserService (集成测试)', () => {
+ let service: UserService;
+ let repository: Repository;
+ let module: TestingModule;
+
+ beforeAll(async () => {
+ jest.setTimeout(30000); // 设置超时为 30 秒
+ // 创建测试模块,使用真实数据库连接
+ module = await Test.createTestingModule({
+ imports: [
+ ConfigModule.forRoot({
+ isGlobal: true,
+ envFilePath: ['.env.development', '.env'],
+ }),
+ TypeOrmModule.forRootAsync({
+ imports: [ConfigModule],
+ useFactory: (configService: ConfigService) => {
+ const config = getDatabaseConfig(configService);
+ // 使用测试数据库(如果配置了),否则使用开发数据库
+ const testDatabase =
+ configService.get('DB_DATABASE') ||
+ (config.database as string);
+ return {
+ ...config,
+ database: testDatabase,
+ synchronize: true, // 测试环境允许同步
+ dropSchema: false, // 不删除现有数据
+ } as typeof config;
+ },
+ inject: [ConfigService],
+ }),
+ UserModule,
+ ],
+ }).compile();
+
+ service = module.get(UserService);
+ repository = module.get>(getRepositoryToken(User));
+
+ // 清理测试数据(可选)
+ await repository.clear();
+ });
+
+ afterAll(async () => {
+ // 测试结束后清理
+ // await repository.clear();
+ await module.close();
+ });
+
+ describe('create - 集成测试', () => {
+ it('应该成功在数据库中创建用户', async () => {
+ const createUserDto: CreateUserDto = {
+ username: 'test_user_1',
+ password: 'password123',
+ email: 'test1@example.com',
+ nickname: '测试用户1',
+ };
+
+ const result = await service.create(createUserDto);
+ console.log('regitser result', result);
+ expect(result).toBeDefined();
+ expect(result.userId).toBeDefined();
+ expect(result.username).toBe(createUserDto.username);
+ expect(result.email).toBe(createUserDto.email);
+ expect(result.nickname).toBe(createUserDto.nickname);
+ expect(result.status).toBe('active');
+ expect(result.role).toBe('user'); // 默认角色
+ expect(result.passwordHash).toBeDefined();
+ expect(result.passwordHash).not.toBe(createUserDto.password); // 密码应该被加密
+
+ // 验证数据确实保存到数据库
+ const savedUser = await repository.findOne({
+ where: { userId: result.userId },
+ });
+ expect(savedUser).toBeDefined();
+ expect(savedUser?.username).toBe(createUserDto.username);
+ expect(savedUser?.email).toBe(createUserDto.email);
+
+ // 清理
+ await repository.remove(result);
+ });
+
+ it('应该使用默认角色和状态', async () => {
+ const createUserDto: CreateUserDto = {
+ username: 'test_user_2',
+ password: 'password123',
+ email: 'test2@example.com',
+ // 不提供 role
+ };
+
+ const result = await service.create(createUserDto);
+
+ expect(result.role).toBe('user'); // 默认角色
+ expect(result.status).toBe('active'); // 默认状态
+
+ // 清理
+ // await repository.remove(result);
+ });
+
+ it('应该支持可选字段', async () => {
+ const createUserDto: CreateUserDto = {
+ username: 'test_user_3',
+ password: 'password123',
+ email: 'test3@example.com',
+ nickname: '测试用户3',
+ avatarUrl: 'https://example.com/avatar.jpg',
+ phone: '13800138000',
+ };
+
+ const result = await service.create(createUserDto);
+
+ expect(result.nickname).toBe(createUserDto.nickname);
+ expect(result.avatarUrl).toBe(createUserDto.avatarUrl);
+ expect(result.phone).toBe(createUserDto.phone);
+
+ // 清理
+ // await repository.remove(result);
+ });
+
+ it('应该抛出 ConflictException 当用户名已存在时', async () => {
+ const createUserDto: CreateUserDto = {
+ username: 'duplicate_username',
+ password: 'password123',
+ email: 'unique@example.com',
+ };
+
+ // 先创建一个用户
+ await service.create(createUserDto);
+
+ // 尝试创建相同用户名的用户
+ const duplicateDto: CreateUserDto = {
+ username: 'duplicate_username',
+ password: 'password456',
+ email: 'another@example.com',
+ };
+
+ await expect(service.create(duplicateDto)).rejects.toThrow(
+ ConflictException,
+ );
+ await expect(service.create(duplicateDto)).rejects.toThrow(
+ '用户名',
+ );
+
+ // 清理
+ // const user = await repository.findOne({ where: { username: 'duplicate_username' } });
+ // if (user) await repository.remove(user);
+ });
+
+ it('应该抛出 ConflictException 当邮箱已存在时', async () => {
+ const createUserDto: CreateUserDto = {
+ username: 'unique_username',
+ password: 'password123',
+ email: 'duplicate@example.com',
+ };
+
+ // 先创建一个用户
+ await service.create(createUserDto);
+
+ // 尝试创建相同邮箱的用户
+ const duplicateDto: CreateUserDto = {
+ username: 'another_username',
+ password: 'password456',
+ email: 'duplicate@example.com',
+ };
+
+ await expect(service.create(duplicateDto)).rejects.toThrow(
+ ConflictException,
+ );
+ await expect(service.create(duplicateDto)).rejects.toThrow('邮箱');
+
+ // 清理
+ // const user = await repository.findOne({ where: { email: 'duplicate@example.com' } });
+ // if (user) await repository.remove(user);
+ });
+ });
+
+ describe('findAll - 集成测试', () => {
+ it('应该返回所有用户列表', async () => {
+ // 先创建几个测试用户
+ const users: CreateUserDto[] = [
+ {
+ username: 'findall_user_1',
+ password: 'password123',
+ email: 'findall1@example.com',
+ nickname: '查询测试用户1',
+ },
+ {
+ username: 'findall_user_2',
+ password: 'password123',
+ email: 'findall2@example.com',
+ nickname: '查询测试用户2',
+ },
+ {
+ username: 'findall_user_3',
+ password: 'password123',
+ email: 'findall3@example.com',
+ nickname: '查询测试用户3',
+ role: 'admin',
+ },
+ ];
+
+ const createdUsers: User[] = [];
+ for (const userDto of users) {
+ const user = await service.create(userDto);
+ createdUsers.push(user);
+ }
+
+ // 查询所有用户
+ const result = await service.findAll();
+
+ expect(result).toBeDefined();
+ expect(Array.isArray(result)).toBe(true);
+ expect(result.length).toBeGreaterThanOrEqual(users.length);
+
+ // 验证创建的用户都在列表中
+ const usernames = result.map((u) => u.username);
+ users.forEach((userDto) => {
+ expect(usernames).toContain(userDto.username);
+ });
+
+ // 验证排序(按创建时间倒序)
+ if (result.length > 1) {
+ for (let i = 0; i < result.length - 1; i++) {
+ expect(
+ result[i].createdAt.getTime(),
+ ).toBeGreaterThanOrEqual(result[i + 1].createdAt.getTime());
+ }
+ }
+
+ // 清理
+ // await repository.remove(createdUsers);
+ });
+
+ it('应该返回空数组当没有用户时', async () => {
+ // 清理所有用户
+ await repository.clear();
+
+ const result = await service.findAll();
+
+ expect(result).toBeDefined();
+ expect(Array.isArray(result)).toBe(true);
+ expect(result.length).toBe(0);
+ });
+
+ it('应该包含用户的所有字段', async () => {
+ // 创建一个完整信息的用户
+ const createUserDto: CreateUserDto = {
+ username: 'full_info_user',
+ password: 'password123',
+ email: 'fullinfo@example.com',
+ nickname: '完整信息用户',
+ avatarUrl: 'https://example.com/avatar.jpg',
+ phone: '13800138000',
+ role: 'admin',
+ };
+
+ await service.create(createUserDto);
+
+ // 查询所有用户
+ const result = await service.findAll();
+
+ const foundUser = result.find(
+ (u) => u.username === createUserDto.username,
+ );
+
+ expect(foundUser).toBeDefined();
+ expect(foundUser?.userId).toBeDefined();
+ expect(foundUser?.username).toBe(createUserDto.username);
+ expect(foundUser?.email).toBe(createUserDto.email);
+ expect(foundUser?.nickname).toBe(createUserDto.nickname);
+ expect(foundUser?.avatarUrl).toBe(createUserDto.avatarUrl);
+ expect(foundUser?.phone).toBe(createUserDto.phone);
+ expect(foundUser?.role).toBe(createUserDto.role);
+ expect(foundUser?.status).toBe('active');
+ expect(foundUser?.createdAt).toBeDefined();
+ expect(foundUser?.updatedAt).toBeDefined();
+
+ // 清理
+ // if (foundUser) await repository.remove(foundUser);
+ });
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 12a191e..765c37e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,52 @@ importers:
.: {}
+ apps/admin:
+ dependencies:
+ react:
+ specifier: ^19.2.0
+ version: 19.2.0
+ react-dom:
+ specifier: ^19.2.0
+ version: 19.2.0(react@19.2.0)
+ devDependencies:
+ '@eslint/js':
+ specifier: ^9.39.1
+ version: 9.39.1
+ '@types/node':
+ specifier: ^24.10.0
+ version: 24.10.1
+ '@types/react':
+ specifier: ^19.2.2
+ version: 19.2.6
+ '@types/react-dom':
+ specifier: ^19.2.2
+ version: 19.2.3(@types/react@19.2.6)
+ '@vitejs/plugin-react-swc':
+ specifier: ^4.2.1
+ version: 4.2.2(vite@7.2.2(@types/node@24.10.1)(terser@5.44.1))
+ eslint:
+ specifier: ^9.39.1
+ version: 9.39.1
+ eslint-plugin-react-hooks:
+ specifier: ^7.0.1
+ version: 7.0.1(eslint@9.39.1)
+ eslint-plugin-react-refresh:
+ specifier: ^0.4.24
+ version: 0.4.24(eslint@9.39.1)
+ globals:
+ specifier: ^16.5.0
+ version: 16.5.0
+ typescript:
+ specifier: ~5.9.3
+ version: 5.9.3
+ typescript-eslint:
+ specifier: ^8.46.3
+ version: 8.46.4(eslint@9.39.1)(typescript@5.9.3)
+ vite:
+ specifier: ^7.2.2
+ version: 7.2.2(@types/node@24.10.1)(terser@5.44.1)
+
apps/api:
dependencies:
'@nestjs/common':
@@ -19,9 +65,15 @@ importers:
'@nestjs/core':
specifier: ^11.0.1
version: 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/jwt':
+ specifier: ^11.0.1
+ version: 11.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))
'@nestjs/mapped-types':
specifier: ^2.1.0
version: 2.1.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)
+ '@nestjs/passport':
+ specifier: ^11.0.5
+ version: 11.0.5(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)
'@nestjs/platform-express':
specifier: ^11.0.1
version: 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)
@@ -30,7 +82,13 @@ importers:
version: 11.2.2(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)
'@nestjs/typeorm':
specifier: ^11.0.0
- version: 11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(pg@8.16.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))
+ version: 11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(pg@8.16.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3)))
+ '@types/jsonwebtoken':
+ specifier: ^9.0.10
+ version: 9.0.10
+ bcrypt:
+ specifier: ^6.0.0
+ version: 6.0.0
body-parser:
specifier: ^2.2.0
version: 2.2.0
@@ -49,6 +107,12 @@ importers:
helmet:
specifier: ^8.1.0
version: 8.1.0
+ passport:
+ specifier: ^0.7.0
+ version: 0.7.0
+ passport-jwt:
+ specifier: ^4.0.1
+ version: 4.0.1
pg:
specifier: ^8.16.3
version: 8.16.3
@@ -60,7 +124,7 @@ importers:
version: 7.8.2
typeorm:
specifier: ^0.3.27
- version: 0.3.27(pg@8.16.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
+ version: 0.3.27(pg@8.16.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3))
devDependencies:
'@eslint/eslintrc':
specifier: ^3.2.0
@@ -70,13 +134,16 @@ importers:
version: 9.39.1
'@nestjs/cli':
specifier: ^11.0.0
- version: 11.0.10(@types/node@22.19.1)
+ version: 11.0.10(@swc/core@1.15.2)(@types/node@22.19.1)
'@nestjs/schematics':
specifier: ^11.0.0
version: 11.0.9(chokidar@4.0.3)(typescript@5.9.3)
'@nestjs/testing':
specifier: ^11.0.1
version: 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-express@11.1.9)
+ '@types/bcrypt':
+ specifier: ^6.0.0
+ version: 6.0.0
'@types/body-parser':
specifier: ^1.19.6
version: 1.19.6
@@ -92,6 +159,9 @@ importers:
'@types/node':
specifier: ^22.10.7
version: 22.19.1
+ '@types/passport-jwt':
+ specifier: ^4.0.1
+ version: 4.0.1
'@types/pg':
specifier: ^8.15.6
version: 8.15.6
@@ -112,7 +182,7 @@ importers:
version: 16.5.0
jest:
specifier: ^30.0.0
- version: 30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
+ version: 30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3))
prettier:
specifier: ^3.4.2
version: 3.6.2
@@ -124,13 +194,13 @@ importers:
version: 7.1.4
ts-jest:
specifier: ^29.2.5
- version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3)
+ version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3)
ts-loader:
specifier: ^9.5.2
- version: 9.5.4(typescript@5.9.3)(webpack@5.100.2)
+ version: 9.5.4(typescript@5.9.3)(webpack@5.100.2(@swc/core@1.15.2))
ts-node:
specifier: ^10.9.2
- version: 10.9.2(@types/node@22.19.1)(typescript@5.9.3)
+ version: 10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3)
tsconfig-paths:
specifier: ^4.2.0
version: 4.2.0
@@ -359,6 +429,162 @@ packages:
'@emnapi/wasi-threads@1.1.0':
resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
+ '@esbuild/aix-ppc64@0.25.12':
+ resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@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-arm@0.25.12':
+ resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.25.12':
+ resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.25.12':
+ resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.25.12':
+ resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.25.12':
+ resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.25.12':
+ resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.25.12':
+ resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ 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-loong64@0.25.12':
+ resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.25.12':
+ resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ 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-riscv64@0.25.12':
+ resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.25.12':
+ resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.25.12':
+ resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+ 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-x64@0.25.12':
+ resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+ 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-x64@0.25.12':
+ resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+ 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/sunos-x64@0.25.12':
+ resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.25.12':
+ resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.12':
+ resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.25.12':
+ resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
'@eslint-community/eslint-utils@4.9.0':
resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -740,6 +966,11 @@ packages:
'@nestjs/websockets':
optional: true
+ '@nestjs/jwt@11.0.1':
+ resolution: {integrity: sha512-HXSsc7SAnCnjA98TsZqrE7trGtHDnYXWp4Ffy6LwSmck1QvbGYdMzBquXofX5l6tIRpeY4Qidl2Ti2CVG77Pdw==}
+ peerDependencies:
+ '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
+
'@nestjs/mapped-types@2.1.0':
resolution: {integrity: sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==}
peerDependencies:
@@ -753,6 +984,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-express@11.1.9':
resolution: {integrity: sha512-GVd3+0lO0mJq2m1kl9hDDnVrX3Nd4oH3oDfklz0pZEVEVS0KVSp63ufHq2Lu9cyPdSBuelJr9iPm2QQ1yX+Kmw==}
peerDependencies:
@@ -835,6 +1072,119 @@ packages:
resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ '@rolldown/pluginutils@1.0.0-beta.47':
+ resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==}
+
+ '@rollup/rollup-android-arm-eabi@4.53.3':
+ resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.53.3':
+ resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.53.3':
+ resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.53.3':
+ resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.53.3':
+ resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.53.3':
+ resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.53.3':
+ resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.53.3':
+ resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.53.3':
+ resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.53.3':
+ resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-gnu@4.53.3':
+ resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.53.3':
+ resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.53.3':
+ resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.53.3':
+ resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.53.3':
+ resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.53.3':
+ resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.53.3':
+ resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-openharmony-arm64@4.53.3':
+ resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.53.3':
+ resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.53.3':
+ resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.53.3':
+ resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.53.3':
+ resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==}
+ cpu: [x64]
+ os: [win32]
+
'@scarf/scarf@1.4.0':
resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==}
@@ -850,6 +1200,81 @@ packages:
'@sqltools/formatter@1.2.5':
resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==}
+ '@swc/core-darwin-arm64@1.15.2':
+ resolution: {integrity: sha512-Ghyz4RJv4zyXzrUC1B2MLQBbppIB5c4jMZJybX2ebdEQAvryEKp3gq1kBksCNsatKGmEgXul88SETU19sMWcrw==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@swc/core-darwin-x64@1.15.2':
+ resolution: {integrity: sha512-7n/PGJOcL2QoptzL42L5xFFfXY5rFxLHnuz1foU+4ruUTG8x2IebGhtwVTpaDN8ShEv2UZObBlT1rrXTba15Zw==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@swc/core-linux-arm-gnueabihf@1.15.2':
+ resolution: {integrity: sha512-ZUQVCfRJ9wimuxkStRSlLwqX4TEDmv6/J+E6FicGkQ6ssLMWoKDy0cAo93HiWt/TWEee5vFhFaSQYzCuBEGO6A==}
+ engines: {node: '>=10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@swc/core-linux-arm64-gnu@1.15.2':
+ resolution: {integrity: sha512-GZh3pYBmfnpQ+JIg+TqLuz+pM+Mjsk5VOzi8nwKn/m+GvQBsxD5ectRtxuWUxMGNG8h0lMy4SnHRqdK3/iJl7A==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@swc/core-linux-arm64-musl@1.15.2':
+ resolution: {integrity: sha512-5av6VYZZeneiYIodwzGMlnyVakpuYZryGzFIbgu1XP8wVylZxduEzup4eP8atiMDFmIm+s4wn8GySJmYqeJC0A==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@swc/core-linux-x64-gnu@1.15.2':
+ resolution: {integrity: sha512-1nO/UfdCLuT/uE/7oB3EZgTeZDCIa6nL72cFEpdegnqpJVNDI6Qb8U4g/4lfVPkmHq2lvxQ0L+n+JdgaZLhrRA==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@swc/core-linux-x64-musl@1.15.2':
+ resolution: {integrity: sha512-Ksfrb0Tx310kr+TLiUOvB/I80lyZ3lSOp6cM18zmNRT/92NB4mW8oX2Jo7K4eVEI2JWyaQUAFubDSha2Q+439A==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@swc/core-win32-arm64-msvc@1.15.2':
+ resolution: {integrity: sha512-IzUb5RlMUY0r1A9IuJrQ7Tbts1wWb73/zXVXT8VhewbHGoNlBKE0qUhKMED6Tv4wDF+pmbtUJmKXDthytAvLmg==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@swc/core-win32-ia32-msvc@1.15.2':
+ resolution: {integrity: sha512-kCATEzuY2LP9AlbU2uScjcVhgnCAkRdu62vbce17Ro5kxEHxYWcugkveyBRS3AqZGtwAKYbMAuNloer9LS/hpw==}
+ engines: {node: '>=10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@swc/core-win32-x64-msvc@1.15.2':
+ resolution: {integrity: sha512-iJaHeYCF4jTn7OEKSa3KRiuVFIVYts8jYjNmCdyz1u5g8HRyTDISD76r8+ljEOgm36oviRQvcXaw6LFp1m0yyA==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@swc/core@1.15.2':
+ resolution: {integrity: sha512-OQm+yJdXxvSjqGeaWhP6Ia264ogifwAO7Q12uTDVYj/Ks4jBTI4JknlcjDRAXtRhqbWsfbZyK/5RtuIPyptk3w==}
+ 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.25':
+ resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
+
'@tokenizer/inflate@0.3.1':
resolution: {integrity: sha512-4oeoZEBQdLdt5WmP/hx1KZ6D3/Oid/0cUb2nk4F0pTDAWy+KCH3/EnAkZF/bvckWo8I33EqBm01lIPgmgc8rCA==}
engines: {node: '>=18'}
@@ -884,6 +1309,9 @@ packages:
'@types/babel__traverse@7.28.0':
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+ '@types/bcrypt@6.0.0':
+ resolution: {integrity: sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==}
+
'@types/body-parser@1.19.6':
resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}
@@ -929,15 +1357,33 @@ packages:
'@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/mime@1.3.5':
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
+ '@types/ms@2.1.0':
+ resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
+
'@types/node@22.19.1':
resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==}
+ '@types/node@24.10.1':
+ resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==}
+
+ '@types/passport-jwt@4.0.1':
+ resolution: {integrity: sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==}
+
+ '@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.15.6':
resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==}
@@ -947,6 +1393,14 @@ packages:
'@types/range-parser@1.2.7':
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
+ '@types/react-dom@19.2.3':
+ resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
+ peerDependencies:
+ '@types/react': ^19.2.0
+
+ '@types/react@19.2.6':
+ resolution: {integrity: sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==}
+
'@types/send@0.17.6':
resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==}
@@ -1131,6 +1585,12 @@ packages:
cpu: [x64]
os: [win32]
+ '@vitejs/plugin-react-swc@4.2.2':
+ resolution: {integrity: sha512-x+rE6tsxq/gxrEJN3Nv3dIV60lFflPj94c90b+NNo6n1QV1QQUTLoL0MpaOVasUZ0zqVBn7ead1B5ecx1JAGfA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ vite: ^4 || ^5 || ^6 || ^7
+
'@webassemblyjs/ast@1.14.1':
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
@@ -1342,6 +1802,10 @@ packages:
resolution: {integrity: sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==}
hasBin: true
+ bcrypt@6.0.0:
+ resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==}
+ engines: {node: '>= 18'}
+
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
@@ -1371,6 +1835,9 @@ packages:
bser@2.1.1:
resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
+ 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==}
@@ -1567,6 +2034,9 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
dayjs@1.11.19:
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
@@ -1643,6 +2113,9 @@ packages:
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+ ecdsa-sig-formatter@1.0.11:
+ resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
+
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
@@ -1689,6 +2162,11 @@ packages:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
+ esbuild@0.25.12:
+ resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
@@ -1724,6 +2202,17 @@ packages:
eslint-config-prettier:
optional: true
+ eslint-plugin-react-hooks@7.0.1:
+ resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
+
+ eslint-plugin-react-refresh@0.4.24:
+ resolution: {integrity: sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==}
+ peerDependencies:
+ eslint: '>=8.40'
+
eslint-scope@5.1.1:
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
engines: {node: '>=8.0.0'}
@@ -1837,6 +2326,15 @@ packages:
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==}
@@ -2014,6 +2512,12 @@ packages:
resolution: {integrity: sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==}
engines: {node: '>=18.0.0'}
+ hermes-estree@0.25.1:
+ resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
+
+ hermes-parser@0.25.1:
+ resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
+
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
@@ -2329,6 +2833,16 @@ packages:
jsonfile@6.2.0:
resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
+ jsonwebtoken@9.0.2:
+ resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==}
+ engines: {node: '>=12', npm: '>=6'}
+
+ jwa@1.4.2:
+ resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==}
+
+ jws@3.2.2:
+ resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==}
+
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -2362,12 +2876,33 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
+ lodash.includes@4.3.0:
+ resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
+
+ 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.memoize@4.1.2:
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+ lodash.once@4.1.1:
+ resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
+
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
@@ -2494,6 +3029,11 @@ packages:
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==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
napi-postinstall@0.3.4:
resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
@@ -2516,9 +3056,17 @@ packages:
node-abort-controller@3.1.1:
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
+ node-addon-api@8.5.0:
+ resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==}
+ engines: {node: ^18 || ^20 || >= 21}
+
node-emoji@1.11.0:
resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==}
+ node-gyp-build@4.8.4:
+ resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
+ hasBin: true
+
node-int64@0.4.0:
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
@@ -2599,6 +3147,17 @@ packages:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
+ passport-jwt@4.0.1:
+ resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==}
+
+ 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'}
@@ -2626,6 +3185,9 @@ packages:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
+ pause@0.0.1:
+ resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
+
pg-cloudflare@1.2.7:
resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==}
@@ -2691,6 +3253,10 @@ packages:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'}
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
postgres-array@2.0.0:
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
engines: {node: '>=4'}
@@ -2753,9 +3319,18 @@ packages:
resolution: {integrity: sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==}
engines: {node: '>= 0.10'}
+ react-dom@19.2.0:
+ resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==}
+ peerDependencies:
+ react: ^19.2.0
+
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+ react@19.2.0:
+ resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==}
+ engines: {node: '>=0.10.0'}
+
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
@@ -2795,6 +3370,11 @@ packages:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ rollup@4.53.3:
+ resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
router@2.2.0:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
@@ -2814,6 +3394,9 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
schema-utils@3.3.0:
resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==}
engines: {node: '>= 10.13.0'}
@@ -2889,6 +3472,10 @@ packages:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'}
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
source-map-support@0.5.13:
resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==}
@@ -3033,6 +3620,10 @@ packages:
resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
engines: {node: '>=8'}
+ tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+
tmpl@1.0.5:
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
@@ -3241,6 +3832,9 @@ packages:
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+ undici-types@7.16.0:
+ resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
+
universalify@2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
@@ -3264,6 +3858,10 @@ 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
@@ -3283,6 +3881,46 @@ packages:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
+ vite@7.2.2:
+ resolution: {integrity: sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^20.19.0 || >=22.12.0
+ jiti: '>=1.21.0'
+ less: ^4.0.0
+ lightningcss: ^1.21.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:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
walker@1.0.8:
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
@@ -3377,6 +4015,15 @@ packages:
resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==}
engines: {node: '>=18'}
+ zod-validation-error@4.0.2:
+ resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ zod: ^3.25.0 || ^4.0.0
+
+ zod@4.1.12:
+ resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==}
+
snapshots:
'@angular-devkit/core@19.2.15(chokidar@4.0.3)':
@@ -3647,6 +4294,84 @@ snapshots:
tslib: 2.8.1
optional: true
+ '@esbuild/aix-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm@0.25.12':
+ optional: true
+
+ '@esbuild/android-x64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.12':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.12':
+ optional: true
+
'@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)':
dependencies:
eslint: 9.39.1
@@ -3878,7 +4603,7 @@ snapshots:
jest-util: 30.2.0
slash: 3.0.0
- '@jest/core@30.2.0(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))':
+ '@jest/core@30.2.0(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3))':
dependencies:
'@jest/console': 30.2.0
'@jest/pattern': 30.0.1
@@ -3893,7 +4618,7 @@ snapshots:
exit-x: 0.2.2
graceful-fs: 4.2.11
jest-changed-files: 30.2.0
- jest-config: 30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
+ jest-config: 30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3))
jest-haste-map: 30.2.0
jest-message-util: 30.2.0
jest-regex-util: 30.0.1
@@ -4088,7 +4813,7 @@ snapshots:
'@tybys/wasm-util': 0.10.1
optional: true
- '@nestjs/cli@11.0.10(@types/node@22.19.1)':
+ '@nestjs/cli@11.0.10(@swc/core@1.15.2)(@types/node@22.19.1)':
dependencies:
'@angular-devkit/core': 19.2.15(chokidar@4.0.3)
'@angular-devkit/schematics': 19.2.15(chokidar@4.0.3)
@@ -4099,7 +4824,7 @@ snapshots:
chokidar: 4.0.3
cli-table3: 0.6.5
commander: 4.1.1
- fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.8.3)(webpack@5.100.2)
+ fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.8.3)(webpack@5.100.2(@swc/core@1.15.2))
glob: 11.0.3
node-emoji: 1.11.0
ora: 5.4.1
@@ -4107,8 +4832,10 @@ snapshots:
tsconfig-paths: 4.2.0
tsconfig-paths-webpack-plugin: 4.2.0
typescript: 5.8.3
- webpack: 5.100.2
+ webpack: 5.100.2(@swc/core@1.15.2)
webpack-node-externals: 3.0.0
+ optionalDependencies:
+ '@swc/core': 1.15.2
transitivePeerDependencies:
- '@types/node'
- esbuild
@@ -4152,6 +4879,12 @@ snapshots:
optionalDependencies:
'@nestjs/platform-express': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)
+ '@nestjs/jwt@11.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))':
+ dependencies:
+ '@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@types/jsonwebtoken': 9.0.10
+ jsonwebtoken: 9.0.2
+
'@nestjs/mapped-types@2.1.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)':
dependencies:
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
@@ -4160,6 +4893,11 @@ snapshots:
class-transformer: 0.5.1
class-validator: 0.14.2
+ '@nestjs/passport@11.0.5(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)':
+ dependencies:
+ '@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ passport: 0.7.0
+
'@nestjs/platform-express@11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)':
dependencies:
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
@@ -4217,13 +4955,13 @@ snapshots:
optionalDependencies:
'@nestjs/platform-express': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)
- '@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(pg@8.16.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))':
+ '@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(pg@8.16.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3)))':
dependencies:
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)
reflect-metadata: 0.2.2
rxjs: 7.8.2
- typeorm: 0.3.27(pg@8.16.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
+ typeorm: 0.3.27(pg@8.16.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3))
'@noble/hashes@1.8.0': {}
@@ -4252,6 +4990,74 @@ snapshots:
'@pkgr/core@0.2.9': {}
+ '@rolldown/pluginutils@1.0.0-beta.47': {}
+
+ '@rollup/rollup-android-arm-eabi@4.53.3':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.53.3':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.53.3':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.53.3':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.53.3':
+ optional: true
+
'@scarf/scarf@1.4.0': {}
'@sinclair/typebox@0.34.41': {}
@@ -4266,6 +5072,58 @@ snapshots:
'@sqltools/formatter@1.2.5': {}
+ '@swc/core-darwin-arm64@1.15.2':
+ optional: true
+
+ '@swc/core-darwin-x64@1.15.2':
+ optional: true
+
+ '@swc/core-linux-arm-gnueabihf@1.15.2':
+ optional: true
+
+ '@swc/core-linux-arm64-gnu@1.15.2':
+ optional: true
+
+ '@swc/core-linux-arm64-musl@1.15.2':
+ optional: true
+
+ '@swc/core-linux-x64-gnu@1.15.2':
+ optional: true
+
+ '@swc/core-linux-x64-musl@1.15.2':
+ optional: true
+
+ '@swc/core-win32-arm64-msvc@1.15.2':
+ optional: true
+
+ '@swc/core-win32-ia32-msvc@1.15.2':
+ optional: true
+
+ '@swc/core-win32-x64-msvc@1.15.2':
+ optional: true
+
+ '@swc/core@1.15.2':
+ dependencies:
+ '@swc/counter': 0.1.3
+ '@swc/types': 0.1.25
+ optionalDependencies:
+ '@swc/core-darwin-arm64': 1.15.2
+ '@swc/core-darwin-x64': 1.15.2
+ '@swc/core-linux-arm-gnueabihf': 1.15.2
+ '@swc/core-linux-arm64-gnu': 1.15.2
+ '@swc/core-linux-arm64-musl': 1.15.2
+ '@swc/core-linux-x64-gnu': 1.15.2
+ '@swc/core-linux-x64-musl': 1.15.2
+ '@swc/core-win32-arm64-msvc': 1.15.2
+ '@swc/core-win32-ia32-msvc': 1.15.2
+ '@swc/core-win32-x64-msvc': 1.15.2
+
+ '@swc/counter@0.1.3': {}
+
+ '@swc/types@0.1.25':
+ dependencies:
+ '@swc/counter': 0.1.3
+
'@tokenizer/inflate@0.3.1':
dependencies:
debug: 4.4.3
@@ -4310,6 +5168,10 @@ snapshots:
dependencies:
'@babel/types': 7.28.5
+ '@types/bcrypt@6.0.0':
+ dependencies:
+ '@types/node': 22.19.1
+
'@types/body-parser@1.19.6':
dependencies:
'@types/connect': 3.4.38
@@ -4370,14 +5232,39 @@ snapshots:
'@types/json-schema@7.0.15': {}
+ '@types/jsonwebtoken@9.0.10':
+ dependencies:
+ '@types/ms': 2.1.0
+ '@types/node': 22.19.1
+
'@types/methods@1.1.4': {}
'@types/mime@1.3.5': {}
+ '@types/ms@2.1.0': {}
+
'@types/node@22.19.1':
dependencies:
undici-types: 6.21.0
+ '@types/node@24.10.1':
+ dependencies:
+ undici-types: 7.16.0
+
+ '@types/passport-jwt@4.0.1':
+ dependencies:
+ '@types/jsonwebtoken': 9.0.10
+ '@types/passport-strategy': 0.2.38
+
+ '@types/passport-strategy@0.2.38':
+ dependencies:
+ '@types/express': 5.0.5
+ '@types/passport': 1.0.17
+
+ '@types/passport@1.0.17':
+ dependencies:
+ '@types/express': 5.0.5
+
'@types/pg@8.15.6':
dependencies:
'@types/node': 22.19.1
@@ -4388,6 +5275,14 @@ snapshots:
'@types/range-parser@1.2.7': {}
+ '@types/react-dom@19.2.3(@types/react@19.2.6)':
+ dependencies:
+ '@types/react': 19.2.6
+
+ '@types/react@19.2.6':
+ dependencies:
+ csstype: 3.2.3
+
'@types/send@0.17.6':
dependencies:
'@types/mime': 1.3.5
@@ -4579,6 +5474,14 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
optional: true
+ '@vitejs/plugin-react-swc@4.2.2(vite@7.2.2(@types/node@24.10.1)(terser@5.44.1))':
+ dependencies:
+ '@rolldown/pluginutils': 1.0.0-beta.47
+ '@swc/core': 1.15.2
+ vite: 7.2.2(@types/node@24.10.1)(terser@5.44.1)
+ transitivePeerDependencies:
+ - '@swc/helpers'
+
'@webassemblyjs/ast@1.14.1':
dependencies:
'@webassemblyjs/helper-numbers': 1.13.2
@@ -4816,6 +5719,11 @@ snapshots:
baseline-browser-mapping@2.8.28: {}
+ bcrypt@6.0.0:
+ dependencies:
+ node-addon-api: 8.5.0
+ node-gyp-build: 4.8.4
+
bl@4.1.0:
dependencies:
buffer: 5.7.1
@@ -4865,6 +5773,8 @@ snapshots:
dependencies:
node-int64: 0.4.0
+ buffer-equal-constant-time@1.0.1: {}
+
buffer-from@1.1.2: {}
buffer@5.7.1:
@@ -5048,6 +5958,8 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
+ csstype@3.2.3: {}
+
dayjs@1.11.19: {}
debug@2.6.9:
@@ -5101,6 +6013,10 @@ snapshots:
eastasianwidth@0.2.0: {}
+ ecdsa-sig-formatter@1.0.11:
+ dependencies:
+ safe-buffer: 5.2.1
+
ee-first@1.1.1: {}
electron-to-chromium@1.5.254: {}
@@ -5139,6 +6055,35 @@ snapshots:
has-tostringtag: 1.0.2
hasown: 2.0.2
+ 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
+
escalade@3.2.0: {}
escape-html@1.0.3: {}
@@ -5161,6 +6106,21 @@ snapshots:
'@types/eslint': 9.6.1
eslint-config-prettier: 10.1.8(eslint@9.39.1)
+ eslint-plugin-react-hooks@7.0.1(eslint@9.39.1):
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/parser': 7.28.5
+ eslint: 9.39.1
+ hermes-parser: 0.25.1
+ zod: 4.1.12
+ zod-validation-error: 4.0.2(zod@4.1.12)
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-react-refresh@0.4.24(eslint@9.39.1):
+ dependencies:
+ eslint: 9.39.1
+
eslint-scope@5.1.1:
dependencies:
esrecurse: 4.3.0
@@ -5328,6 +6288,10 @@ snapshots:
dependencies:
bser: 2.1.1
+ fdir@6.5.0(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
fflate@0.8.2: {}
file-entry-cache@8.0.0:
@@ -5384,7 +6348,7 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
- fork-ts-checker-webpack-plugin@9.1.0(typescript@5.8.3)(webpack@5.100.2):
+ fork-ts-checker-webpack-plugin@9.1.0(typescript@5.8.3)(webpack@5.100.2(@swc/core@1.15.2)):
dependencies:
'@babel/code-frame': 7.27.1
chalk: 4.1.2
@@ -5399,7 +6363,7 @@ snapshots:
semver: 7.7.3
tapable: 2.3.0
typescript: 5.8.3
- webpack: 5.100.2
+ webpack: 5.100.2(@swc/core@1.15.2)
form-data@4.0.5:
dependencies:
@@ -5534,6 +6498,12 @@ snapshots:
helmet@8.1.0: {}
+ hermes-estree@0.25.1: {}
+
+ hermes-parser@0.25.1:
+ dependencies:
+ hermes-estree: 0.25.1
+
html-escaper@2.0.2: {}
http-errors@2.0.0:
@@ -5690,15 +6660,15 @@ snapshots:
- babel-plugin-macros
- supports-color
- jest-cli@30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
+ jest-cli@30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3)):
dependencies:
- '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
+ '@jest/core': 30.2.0(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3))
'@jest/test-result': 30.2.0
'@jest/types': 30.2.0
chalk: 4.1.2
exit-x: 0.2.2
import-local: 3.2.0
- jest-config: 30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
+ jest-config: 30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3))
jest-util: 30.2.0
jest-validate: 30.2.0
yargs: 17.7.2
@@ -5709,7 +6679,7 @@ snapshots:
- supports-color
- ts-node
- jest-config@30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
+ jest-config@30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3)):
dependencies:
'@babel/core': 7.28.5
'@jest/get-type': 30.1.0
@@ -5737,7 +6707,7 @@ snapshots:
strip-json-comments: 3.1.1
optionalDependencies:
'@types/node': 22.19.1
- ts-node: 10.9.2(@types/node@22.19.1)(typescript@5.9.3)
+ ts-node: 10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3)
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
@@ -5963,12 +6933,12 @@ snapshots:
merge-stream: 2.0.0
supports-color: 8.1.1
- jest@30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
+ jest@30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3)):
dependencies:
- '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
+ '@jest/core': 30.2.0(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3))
'@jest/types': 30.2.0
import-local: 3.2.0
- jest-cli: 30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
+ jest-cli: 30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3))
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
@@ -6009,6 +6979,30 @@ snapshots:
optionalDependencies:
graceful-fs: 4.2.11
+ jsonwebtoken@9.0.2:
+ dependencies:
+ jws: 3.2.2
+ 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.3
+
+ jwa@1.4.2:
+ dependencies:
+ buffer-equal-constant-time: 1.0.1
+ ecdsa-sig-formatter: 1.0.11
+ safe-buffer: 5.2.1
+
+ jws@3.2.2:
+ dependencies:
+ jwa: 1.4.2
+ safe-buffer: 5.2.1
+
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
@@ -6036,10 +7030,24 @@ snapshots:
dependencies:
p-locate: 5.0.0
+ 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.memoize@4.1.2: {}
lodash.merge@4.6.2: {}
+ lodash.once@4.1.1: {}
+
lodash@4.17.21: {}
log-symbols@4.1.0:
@@ -6144,6 +7152,8 @@ snapshots:
mute-stream@2.0.0: {}
+ nanoid@3.3.11: {}
+
napi-postinstall@0.3.4: {}
natural-compare@1.4.0: {}
@@ -6156,10 +7166,14 @@ snapshots:
node-abort-controller@3.1.1: {}
+ node-addon-api@8.5.0: {}
+
node-emoji@1.11.0:
dependencies:
lodash: 4.17.21
+ node-gyp-build@4.8.4: {}
+
node-int64@0.4.0: {}
node-releases@2.0.27: {}
@@ -6242,6 +7256,19 @@ snapshots:
parseurl@1.3.3: {}
+ passport-jwt@4.0.1:
+ dependencies:
+ jsonwebtoken: 9.0.2
+ 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-is-absolute@1.0.1: {}
@@ -6262,6 +7289,8 @@ snapshots:
path-type@4.0.0: {}
+ pause@0.0.1: {}
+
pg-cloudflare@1.2.7:
optional: true
@@ -6315,6 +7344,12 @@ snapshots:
possible-typed-array-names@1.1.0: {}
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
postgres-array@2.0.0: {}
postgres-bytea@1.0.0: {}
@@ -6367,8 +7402,15 @@ snapshots:
iconv-lite: 0.7.0
unpipe: 1.0.0
+ react-dom@19.2.0(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ scheduler: 0.27.0
+
react-is@18.3.1: {}
+ react@19.2.0: {}
+
readable-stream@3.6.2:
dependencies:
inherits: 2.0.4
@@ -6398,6 +7440,34 @@ snapshots:
reusify@1.1.0: {}
+ rollup@4.53.3:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.53.3
+ '@rollup/rollup-android-arm64': 4.53.3
+ '@rollup/rollup-darwin-arm64': 4.53.3
+ '@rollup/rollup-darwin-x64': 4.53.3
+ '@rollup/rollup-freebsd-arm64': 4.53.3
+ '@rollup/rollup-freebsd-x64': 4.53.3
+ '@rollup/rollup-linux-arm-gnueabihf': 4.53.3
+ '@rollup/rollup-linux-arm-musleabihf': 4.53.3
+ '@rollup/rollup-linux-arm64-gnu': 4.53.3
+ '@rollup/rollup-linux-arm64-musl': 4.53.3
+ '@rollup/rollup-linux-loong64-gnu': 4.53.3
+ '@rollup/rollup-linux-ppc64-gnu': 4.53.3
+ '@rollup/rollup-linux-riscv64-gnu': 4.53.3
+ '@rollup/rollup-linux-riscv64-musl': 4.53.3
+ '@rollup/rollup-linux-s390x-gnu': 4.53.3
+ '@rollup/rollup-linux-x64-gnu': 4.53.3
+ '@rollup/rollup-linux-x64-musl': 4.53.3
+ '@rollup/rollup-openharmony-arm64': 4.53.3
+ '@rollup/rollup-win32-arm64-msvc': 4.53.3
+ '@rollup/rollup-win32-ia32-msvc': 4.53.3
+ '@rollup/rollup-win32-x64-gnu': 4.53.3
+ '@rollup/rollup-win32-x64-msvc': 4.53.3
+ fsevents: 2.3.3
+
router@2.2.0:
dependencies:
debug: 4.4.3
@@ -6424,6 +7494,8 @@ snapshots:
safer-buffer@2.1.2: {}
+ scheduler@0.27.0: {}
+
schema-utils@3.3.0:
dependencies:
'@types/json-schema': 7.0.15
@@ -6527,6 +7599,8 @@ snapshots:
slash@3.0.0: {}
+ source-map-js@1.2.1: {}
+
source-map-support@0.5.13:
dependencies:
buffer-from: 1.1.2
@@ -6641,14 +7715,16 @@ snapshots:
tapable@2.3.0: {}
- terser-webpack-plugin@5.3.14(webpack@5.100.2):
+ terser-webpack-plugin@5.3.14(@swc/core@1.15.2)(webpack@5.100.2(@swc/core@1.15.2)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
schema-utils: 4.3.3
serialize-javascript: 6.0.2
terser: 5.44.1
- webpack: 5.100.2
+ webpack: 5.100.2(@swc/core@1.15.2)
+ optionalDependencies:
+ '@swc/core': 1.15.2
terser@5.44.1:
dependencies:
@@ -6663,6 +7739,11 @@ snapshots:
glob: 7.2.3
minimatch: 3.1.2
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+
tmpl@1.0.5: {}
to-buffer@1.2.2:
@@ -6689,12 +7770,12 @@ snapshots:
dependencies:
typescript: 5.9.3
- ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3):
+ ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3):
dependencies:
bs-logger: 0.2.6
fast-json-stable-stringify: 2.1.0
handlebars: 4.7.8
- jest: 30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
+ jest: 30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3))
json5: 2.2.3
lodash.memoize: 4.1.2
make-error: 1.3.6
@@ -6709,7 +7790,7 @@ snapshots:
babel-jest: 30.2.0(@babel/core@7.28.5)
jest-util: 30.2.0
- ts-loader@9.5.4(typescript@5.9.3)(webpack@5.100.2):
+ ts-loader@9.5.4(typescript@5.9.3)(webpack@5.100.2(@swc/core@1.15.2)):
dependencies:
chalk: 4.1.2
enhanced-resolve: 5.18.3
@@ -6717,9 +7798,9 @@ snapshots:
semver: 7.7.3
source-map: 0.7.6
typescript: 5.9.3
- webpack: 5.100.2
+ webpack: 5.100.2(@swc/core@1.15.2)
- ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3):
+ ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.12
@@ -6736,6 +7817,8 @@ snapshots:
typescript: 5.9.3
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
+ optionalDependencies:
+ '@swc/core': 1.15.2
tsconfig-paths-webpack-plugin@4.2.0:
dependencies:
@@ -6781,7 +7864,7 @@ snapshots:
typedarray@0.0.6: {}
- typeorm@0.3.27(pg@8.16.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
+ typeorm@0.3.27(pg@8.16.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3)):
dependencies:
'@sqltools/formatter': 1.2.5
ansis: 3.17.0
@@ -6800,7 +7883,7 @@ snapshots:
yargs: 17.7.2
optionalDependencies:
pg: 8.16.3
- ts-node: 10.9.2(@types/node@22.19.1)(typescript@5.9.3)
+ ts-node: 10.9.2(@swc/core@1.15.2)(@types/node@22.19.1)(typescript@5.9.3)
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
@@ -6831,6 +7914,8 @@ snapshots:
undici-types@6.21.0: {}
+ undici-types@7.16.0: {}
+
universalify@2.0.1: {}
unpipe@1.0.0: {}
@@ -6871,6 +7956,8 @@ snapshots:
util-deprecate@1.0.2: {}
+ utils-merge@1.0.1: {}
+
uuid@11.1.0: {}
v8-compile-cache-lib@3.0.1: {}
@@ -6885,6 +7972,19 @@ snapshots:
vary@1.1.2: {}
+ vite@7.2.2(@types/node@24.10.1)(terser@5.44.1):
+ dependencies:
+ esbuild: 0.25.12
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rollup: 4.53.3
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ '@types/node': 24.10.1
+ fsevents: 2.3.3
+ terser: 5.44.1
+
walker@1.0.8:
dependencies:
makeerror: 1.0.12
@@ -6902,7 +8002,7 @@ snapshots:
webpack-sources@3.3.3: {}
- webpack@5.100.2:
+ webpack@5.100.2(@swc/core@1.15.2):
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.8
@@ -6926,7 +8026,7 @@ snapshots:
neo-async: 2.6.2
schema-utils: 4.3.3
tapable: 2.3.0
- terser-webpack-plugin: 5.3.14(webpack@5.100.2)
+ terser-webpack-plugin: 5.3.14(@swc/core@1.15.2)(webpack@5.100.2(@swc/core@1.15.2))
watchpack: 2.4.4
webpack-sources: 3.3.3
transitivePeerDependencies:
@@ -7000,3 +8100,9 @@ snapshots:
yocto-queue@0.1.0: {}
yoctocolors-cjs@2.1.3: {}
+
+ zod-validation-error@4.0.2(zod@4.1.12):
+ dependencies:
+ zod: 4.1.12
+
+ zod@4.1.12: {}