create-tigra 1.1.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +80 -87
- package/bin/create-tigra.js +259 -308
- package/package.json +49 -41
- package/template/_claude/QUICK_REFERENCE.md +193 -0
- package/template/_claude/README.md +53 -0
- package/template/_claude/commands/create-client.md +881 -0
- package/template/_claude/commands/create-server.md +383 -0
- package/template/_claude/rules/client/01-project-structure.md +133 -0
- package/template/_claude/rules/client/02-components-and-types.md +146 -0
- package/template/_claude/rules/client/03-data-and-state.md +156 -0
- package/template/_claude/rules/client/04-design-system.md +185 -0
- package/template/_claude/rules/client/05-security.md +55 -0
- package/template/_claude/rules/client/06-ux-checklist.md +81 -0
- package/template/_claude/rules/client/core.md +42 -0
- package/template/_claude/rules/global/core.md +77 -0
- package/template/_claude/rules/server/core.md +50 -0
- package/template/_claude/rules/server/database.md +124 -0
- package/template/_claude/rules/server/project-conventions.md +150 -0
- package/template/_claude/rules/server/response-handling.md +144 -0
- package/template/client/.env.example +5 -0
- package/template/client/README.md +36 -0
- package/template/client/components.json +23 -0
- package/template/client/eslint.config.mjs +18 -0
- package/template/client/next.config.ts +34 -0
- package/template/client/package.json +44 -0
- package/template/client/postcss.config.mjs +7 -0
- package/template/client/src/app/(auth)/layout.tsx +18 -0
- package/template/client/src/app/(auth)/login/page.tsx +13 -0
- package/template/client/src/app/(auth)/register/page.tsx +13 -0
- package/template/client/src/app/(main)/dashboard/page.tsx +22 -0
- package/template/client/src/app/(main)/layout.tsx +11 -0
- package/template/client/src/app/error.tsx +27 -0
- package/template/client/src/app/favicon.ico +0 -0
- package/template/client/src/app/globals.css +145 -0
- package/template/client/src/app/layout.tsx +36 -0
- package/template/client/src/app/loading.tsx +11 -0
- package/template/client/src/app/not-found.tsx +23 -0
- package/template/client/src/app/page.tsx +45 -0
- package/template/client/src/app/providers.tsx +43 -0
- package/template/client/src/components/common/ConfirmDialog.tsx +56 -0
- package/template/client/src/components/common/EmptyState.tsx +31 -0
- package/template/client/src/components/common/LoadingSpinner.tsx +30 -0
- package/template/client/src/components/common/Pagination.tsx +55 -0
- package/template/client/src/components/layout/Footer.tsx +17 -0
- package/template/client/src/components/layout/Header.tsx +173 -0
- package/template/client/src/components/layout/MainLayout.tsx +18 -0
- package/template/client/src/components/ui/alert-dialog.tsx +196 -0
- package/template/client/src/components/ui/badge.tsx +48 -0
- package/template/client/src/components/ui/button.tsx +64 -0
- package/template/client/src/components/ui/card.tsx +92 -0
- package/template/client/src/components/ui/input.tsx +21 -0
- package/template/client/src/components/ui/label.tsx +24 -0
- package/template/client/src/components/ui/select.tsx +190 -0
- package/template/client/src/components/ui/skeleton.tsx +13 -0
- package/template/client/src/components/ui/table.tsx +116 -0
- package/template/client/src/features/auth/components/AuthInitializer.tsx +55 -0
- package/template/client/src/features/auth/components/LoginForm.tsx +107 -0
- package/template/client/src/features/auth/components/RegisterForm.tsx +178 -0
- package/template/client/src/features/auth/hooks/useAuth.ts +84 -0
- package/template/client/src/features/auth/services/auth.service.ts +52 -0
- package/template/client/src/features/auth/store/authSlice.ts +38 -0
- package/template/client/src/features/auth/types/auth.types.ts +32 -0
- package/template/client/src/hooks/useDebounce.ts +14 -0
- package/template/client/src/hooks/useLocalStorage.ts +55 -0
- package/template/client/src/hooks/useMediaQuery.ts +27 -0
- package/template/client/src/lib/api/api.types.ts +34 -0
- package/template/client/src/lib/api/axios.config.ts +98 -0
- package/template/client/src/lib/constants/api-endpoints.ts +18 -0
- package/template/client/src/lib/constants/app.constants.ts +12 -0
- package/template/client/src/lib/constants/routes.ts +9 -0
- package/template/client/src/lib/utils/error.ts +32 -0
- package/template/client/src/lib/utils/format.ts +37 -0
- package/template/client/src/lib/utils/security.ts +34 -0
- package/template/client/src/lib/utils.ts +6 -0
- package/template/client/src/middleware.ts +57 -0
- package/template/client/src/store/hooks.ts +7 -0
- package/template/client/src/store/index.ts +12 -0
- package/template/client/src/types/index.ts +3 -0
- package/template/client/tsconfig.json +34 -0
- package/template/gitignore +34 -0
- package/template/server/.dockerignore +66 -0
- package/template/server/.env.example +96 -69
- package/template/server/.env.production.example +90 -0
- package/template/server/Dockerfile +94 -0
- package/template/server/docker-compose.yml +82 -111
- package/template/server/docs/logging.md +62 -0
- package/template/server/eslint.config.mjs +17 -0
- package/template/server/package.json +68 -81
- package/template/server/phpmyadmin-config.php +26 -0
- package/template/server/postman_collection.json +666 -0
- package/template/server/prisma/schema.prisma +77 -93
- package/template/server/prisma/seed.ts +46 -142
- package/template/server/scripts/flush-redis.ts +41 -0
- package/template/server/src/app.ts +243 -71
- package/template/server/src/config/env.ts +67 -94
- package/template/server/src/libs/auth.ts +88 -0
- package/template/server/src/libs/cleanup.ts +35 -0
- package/template/server/src/libs/cookies.ts +46 -0
- package/template/server/src/libs/logger.ts +33 -60
- package/template/server/src/libs/monitoring.ts +205 -0
- package/template/server/src/libs/password.ts +38 -0
- package/template/server/src/libs/prisma.ts +68 -0
- package/template/server/src/libs/redis.ts +60 -79
- package/template/server/src/libs/requestLogger.ts +66 -0
- package/template/server/src/libs/storage/file-storage.service.ts +211 -0
- package/template/server/src/libs/storage/file-validator.ts +97 -0
- package/template/server/src/libs/storage/filename-sanitizer.ts +71 -0
- package/template/server/src/libs/storage/image-optimizer.service.ts +144 -0
- package/template/server/src/modules/auth/__tests__/auth.service.test.ts +365 -0
- package/template/server/src/modules/auth/auth.controller.ts +90 -141
- package/template/server/src/modules/auth/auth.repo.ts +120 -218
- package/template/server/src/modules/auth/auth.routes.ts +96 -83
- package/template/server/src/modules/auth/auth.schemas.ts +35 -137
- package/template/server/src/modules/auth/auth.service.ts +286 -329
- package/template/server/src/modules/auth/session.repo.ts +110 -0
- package/template/server/src/modules/users/users.controller.ts +120 -0
- package/template/server/src/modules/users/users.repo.ts +77 -0
- package/template/server/src/modules/users/users.routes.ts +89 -0
- package/template/server/src/modules/users/users.schemas.ts +21 -0
- package/template/server/src/modules/users/users.service.ts +169 -0
- package/template/server/src/server.ts +58 -139
- package/template/server/src/shared/errors/AppError.ts +21 -0
- package/template/server/src/shared/errors/errors.ts +43 -0
- package/template/server/src/shared/responses/paginatedResponse.ts +38 -0
- package/template/server/src/shared/responses/successResponse.ts +17 -0
- package/template/server/src/shared/schemas/pagination.schema.ts +12 -0
- package/template/server/src/shared/types/index.ts +26 -0
- package/template/server/src/test/setup.ts +74 -38
- package/template/server/tsconfig.json +27 -89
- package/template/server/uploads/avatars/.gitkeep +1 -0
- package/template/server/vitest.config.ts +43 -98
- package/template/.agent/rules/client/01-project-structure.md +0 -326
- package/template/.agent/rules/client/02-component-patterns.md +0 -249
- package/template/.agent/rules/client/03-typescript-rules.md +0 -226
- package/template/.agent/rules/client/04-state-management.md +0 -474
- package/template/.agent/rules/client/05-api-integration.md +0 -129
- package/template/.agent/rules/client/06-forms-validation.md +0 -129
- package/template/.agent/rules/client/07-common-patterns.md +0 -150
- package/template/.agent/rules/client/08-color-system.md +0 -93
- package/template/.agent/rules/client/09-security-rules.md +0 -97
- package/template/.agent/rules/client/10-testing-strategy.md +0 -370
- package/template/.agent/rules/global/ai-edit-safety.md +0 -38
- package/template/.agent/rules/server/01-db-and-migrations.md +0 -242
- package/template/.agent/rules/server/02-general-rules.md +0 -111
- package/template/.agent/rules/server/03-migrations.md +0 -20
- package/template/.agent/rules/server/04-pagination.md +0 -130
- package/template/.agent/rules/server/05-project-conventions.md +0 -71
- package/template/.agent/rules/server/06-response-handling.md +0 -173
- package/template/.agent/rules/server/07-testing-strategy.md +0 -506
- package/template/.agent/rules/server/08-observability.md +0 -180
- package/template/.agent/rules/server/10-background-jobs-v2.md +0 -185
- package/template/.agent/rules/server/11-rate-limiting-v2.md +0 -210
- package/template/.agent/rules/server/12-performance-optimization.md +0 -567
- package/template/.claude/rules/client-01-project-structure.md +0 -327
- package/template/.claude/rules/client-02-component-patterns.md +0 -250
- package/template/.claude/rules/client-03-typescript-rules.md +0 -227
- package/template/.claude/rules/client-04-state-management.md +0 -475
- package/template/.claude/rules/client-05-api-integration.md +0 -130
- package/template/.claude/rules/client-06-forms-validation.md +0 -130
- package/template/.claude/rules/client-07-common-patterns.md +0 -151
- package/template/.claude/rules/client-08-color-system.md +0 -94
- package/template/.claude/rules/client-09-security-rules.md +0 -98
- package/template/.claude/rules/client-10-testing-strategy.md +0 -371
- package/template/.claude/rules/global-ai-edit-safety.md +0 -39
- package/template/.claude/rules/server-01-db-and-migrations.md +0 -243
- package/template/.claude/rules/server-02-general-rules.md +0 -112
- package/template/.claude/rules/server-03-migrations.md +0 -21
- package/template/.claude/rules/server-04-pagination.md +0 -131
- package/template/.claude/rules/server-05-project-conventions.md +0 -72
- package/template/.claude/rules/server-06-response-handling.md +0 -174
- package/template/.claude/rules/server-07-testing-strategy.md +0 -507
- package/template/.claude/rules/server-08-observability.md +0 -181
- package/template/.claude/rules/server-10-background-jobs-v2.md +0 -186
- package/template/.claude/rules/server-11-rate-limiting-v2.md +0 -211
- package/template/.claude/rules/server-12-performance-optimization.md +0 -568
- package/template/.cursor/rules/client-01-project-structure.mdc +0 -327
- package/template/.cursor/rules/client-02-component-patterns.mdc +0 -250
- package/template/.cursor/rules/client-03-typescript-rules.mdc +0 -227
- package/template/.cursor/rules/client-04-state-management.mdc +0 -475
- package/template/.cursor/rules/client-05-api-integration.mdc +0 -130
- package/template/.cursor/rules/client-06-forms-validation.mdc +0 -130
- package/template/.cursor/rules/client-07-common-patterns.mdc +0 -151
- package/template/.cursor/rules/client-08-color-system.mdc +0 -94
- package/template/.cursor/rules/client-09-security-rules.mdc +0 -98
- package/template/.cursor/rules/client-10-testing-strategy.mdc +0 -371
- package/template/.cursor/rules/global-ai-edit-safety.mdc +0 -39
- package/template/.cursor/rules/server-01-db-and-migrations.mdc +0 -243
- package/template/.cursor/rules/server-02-general-rules.mdc +0 -112
- package/template/.cursor/rules/server-03-migrations.mdc +0 -21
- package/template/.cursor/rules/server-04-pagination.mdc +0 -131
- package/template/.cursor/rules/server-05-project-conventions.mdc +0 -72
- package/template/.cursor/rules/server-06-response-handling.mdc +0 -174
- package/template/.cursor/rules/server-07-testing-strategy.mdc +0 -507
- package/template/.cursor/rules/server-08-observability.mdc +0 -181
- package/template/.cursor/rules/server-09-api-documentation-v2.mdc +0 -169
- package/template/.cursor/rules/server-10-background-jobs-v2.mdc +0 -186
- package/template/.cursor/rules/server-11-rate-limiting-v2.mdc +0 -211
- package/template/.cursor/rules/server-12-performance-optimization.mdc +0 -568
- package/template/CLAUDE.md +0 -207
- package/template/server/.tsc-aliasrc.json +0 -13
- package/template/server/IMPORT_FIX_CHECKLIST.md +0 -98
- package/template/server/IMPORT_FIX_COMPLETE.md +0 -89
- package/template/server/README.md +0 -183
- package/template/server/REMAINING_IMPORT_FIXES.md +0 -150
- package/template/server/SECURITY.md +0 -190
- package/template/server/Tigra-API.postman_collection.json +0 -733
- package/template/server/biome.json +0 -42
- package/template/server/scripts/fix-all-imports.ps1 +0 -52
- package/template/server/scripts/fix-imports-reference.ps1 +0 -16
- package/template/server/scripts/fix-imports.mjs +0 -55
- package/template/server/scripts/setup-env.js +0 -50
- package/template/server/scripts/wait-for-db.js +0 -60
- package/template/server/src/hooks/request-timing.hook.ts +0 -26
- package/template/server/src/libs/auth/authenticate.middleware.ts +0 -22
- package/template/server/src/libs/auth/rbac.middleware.test.ts +0 -134
- package/template/server/src/libs/auth/rbac.middleware.ts +0 -147
- package/template/server/src/libs/db.ts +0 -76
- package/template/server/src/libs/error-handler.ts +0 -89
- package/template/server/src/libs/queue.ts +0 -79
- package/template/server/src/modules/admin/admin.controller.ts +0 -122
- package/template/server/src/modules/admin/admin.routes.ts +0 -62
- package/template/server/src/modules/admin/admin.schemas.ts +0 -35
- package/template/server/src/modules/admin/admin.service.ts +0 -167
- package/template/server/src/modules/auth/auth.integration.test.ts +0 -150
- package/template/server/src/modules/auth/auth.service.test.ts +0 -119
- package/template/server/src/modules/auth/auth.types.ts +0 -97
- package/template/server/src/modules/resources/resources.controller.ts +0 -218
- package/template/server/src/modules/resources/resources.repo.ts +0 -253
- package/template/server/src/modules/resources/resources.routes.ts +0 -116
- package/template/server/src/modules/resources/resources.schemas.ts +0 -146
- package/template/server/src/modules/resources/resources.service.ts +0 -218
- package/template/server/src/modules/resources/resources.types.ts +0 -73
- package/template/server/src/plugins/rate-limit.plugin.ts +0 -21
- package/template/server/src/plugins/security.plugin.ts +0 -21
- package/template/server/src/routes/health.routes.ts +0 -31
- package/template/server/src/types/fastify.d.ts +0 -36
- package/template/server/src/utils/errors.ts +0 -108
- package/template/server/src/utils/pagination.ts +0 -120
- package/template/server/src/utils/response.ts +0 -110
- package/template/server/src/workers/file.worker.ts +0 -106
- package/template/server/tsconfig.build.json +0 -30
- package/template/server/tsconfig.test.json +0 -22
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
|
|
3
|
-
"organizeImports": {
|
|
4
|
-
"enabled": true
|
|
5
|
-
},
|
|
6
|
-
"linter": {
|
|
7
|
-
"enabled": true,
|
|
8
|
-
"rules": {
|
|
9
|
-
"recommended": true,
|
|
10
|
-
"complexity": {
|
|
11
|
-
"noForEach": "off"
|
|
12
|
-
},
|
|
13
|
-
"suspicious": {
|
|
14
|
-
"noExplicitAny": "warn"
|
|
15
|
-
},
|
|
16
|
-
"style": {
|
|
17
|
-
"noNonNullAssertion": "off"
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
"formatter": {
|
|
22
|
-
"enabled": true,
|
|
23
|
-
"indentStyle": "space",
|
|
24
|
-
"indentWidth": 4,
|
|
25
|
-
"lineWidth": 100
|
|
26
|
-
},
|
|
27
|
-
"javascript": {
|
|
28
|
-
"formatter": {
|
|
29
|
-
"quoteStyle": "single",
|
|
30
|
-
"trailingCommas": "es5",
|
|
31
|
-
"semicolons": "always"
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
"files": {
|
|
35
|
-
"ignore": [
|
|
36
|
-
"node_modules",
|
|
37
|
-
"dist",
|
|
38
|
-
"coverage",
|
|
39
|
-
"*.json"
|
|
40
|
-
]
|
|
41
|
-
}
|
|
42
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# PowerShell script to fix all @/ imports in TypeScript files
|
|
2
|
-
# Run this from the server directory: .\scripts\fix-all-imports.ps1
|
|
3
|
-
|
|
4
|
-
$files = Get-ChildItem -Path "src" -Filter "*.ts" -Recurse | Where-Object { $_.Name -notlike "*.d.ts" }
|
|
5
|
-
|
|
6
|
-
foreach ($file in $files) {
|
|
7
|
-
$content = Get-Content $file.FullName -Raw
|
|
8
|
-
$originalContent = $content
|
|
9
|
-
|
|
10
|
-
# Calculate relative path depth based on file location
|
|
11
|
-
$relativePath = $file.FullName.Replace((Get-Location).Path + "\src\", "")
|
|
12
|
-
$depth = ($relativePath.Split('\').Length - 1)
|
|
13
|
-
$prefix = if ($depth -eq 0) { "./" } else { ("../" * $depth) }
|
|
14
|
-
|
|
15
|
-
# Replace @/config/* imports
|
|
16
|
-
$content = $content -replace "from '@/config/([^']+)'", "from '${prefix}config/`$1.js'"
|
|
17
|
-
|
|
18
|
-
# Replace @/libs/* imports
|
|
19
|
-
$content = $content -replace "from '@/libs/([^']+)'", "from '${prefix}libs/`$1.js'"
|
|
20
|
-
|
|
21
|
-
# Replace @/modules/* imports
|
|
22
|
-
$content = $content -replace "from '@/modules/([^']+)'", "from '${prefix}modules/`$1.js'"
|
|
23
|
-
|
|
24
|
-
# Replace @/utils/* imports
|
|
25
|
-
$content = $content -replace "from '@/utils/([^']+)'", "from '${prefix}utils/`$1.js'"
|
|
26
|
-
|
|
27
|
-
# Replace @/types/* imports
|
|
28
|
-
$content = $content -replace "from '@/types/([^']+)'", "from '${prefix}types/`$1.js'"
|
|
29
|
-
|
|
30
|
-
# Replace @/plugins/* imports
|
|
31
|
-
$content = $content -replace "from '@/plugins/([^']+)'", "from '${prefix}plugins/`$1.js'"
|
|
32
|
-
|
|
33
|
-
# Replace @/hooks/* imports
|
|
34
|
-
$content = $content -replace "from '@/hooks/([^']+)'", "from '${prefix}hooks/`$1.js'"
|
|
35
|
-
|
|
36
|
-
# Replace @/routes/* imports
|
|
37
|
-
$content = $content -replace "from '@/routes/([^']+)'", "from '${prefix}routes/`$1.js'"
|
|
38
|
-
|
|
39
|
-
# Replace @/workers/* imports
|
|
40
|
-
$content = $content -replace "from '@/workers/([^']+)'", "from '${prefix}workers/`$1.js'"
|
|
41
|
-
|
|
42
|
-
# Fix relative imports that don't have .js extension
|
|
43
|
-
$content = $content -replace "from '\./([^']+)(?<!\.js)'", "from './`$1.js'"
|
|
44
|
-
$content = $content -replace "from '\.\.\/([^']+)(?<!\.js)'", "from '../`$1.js'"
|
|
45
|
-
|
|
46
|
-
if ($content -ne $originalContent) {
|
|
47
|
-
Set-Content -Path $file.FullName -Value $content -NoNewline
|
|
48
|
-
Write-Host "✓ Fixed: $($file.FullName)" -ForegroundColor Green
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
Write-Host "`n✅ All imports have been converted!" -ForegroundColor Cyan
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# PowerShell script to convert @/ imports to relative .js imports
|
|
2
|
-
# This is a reference - the actual conversion will be done file by file
|
|
3
|
-
|
|
4
|
-
Write-Host "This script shows the pattern for converting imports:" -ForegroundColor Yellow
|
|
5
|
-
Write-Host ""
|
|
6
|
-
Write-Host "Pattern to find:" -ForegroundColor Cyan
|
|
7
|
-
Write-Host " from '@/config/env'"
|
|
8
|
-
Write-Host " from '@/libs/logger'"
|
|
9
|
-
Write-Host " from '@/libs/db'"
|
|
10
|
-
Write-Host ""
|
|
11
|
-
Write-Host "Convert to relative paths with .js:" -ForegroundColor Green
|
|
12
|
-
Write-Host " from './config/env.js' (if in src/)"
|
|
13
|
-
Write-Host " from '../config/env.js' (if in src/subfolder/)"
|
|
14
|
-
Write-Host " from '../../config/env.js' (if in src/subfolder/subfolder/)"
|
|
15
|
-
Write-Host ""
|
|
16
|
-
Write-Host "The conversion is being done manually for each file." -ForegroundColor Yellow
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Script to convert @/ path aliases to relative imports with .js extensions
|
|
5
|
-
* Run this after removing path aliases from tsconfig
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync, writeFileSync } from 'fs';
|
|
9
|
-
import { glob } from 'glob';
|
|
10
|
-
import { relative, dirname, join } from 'path';
|
|
11
|
-
|
|
12
|
-
const files = glob.sync('src/**/*.ts', { ignore: ['**/*.d.ts'] });
|
|
13
|
-
|
|
14
|
-
files.forEach(file => {
|
|
15
|
-
let content = readFileSync(file, 'utf-8');
|
|
16
|
-
const lines = content.split('\n');
|
|
17
|
-
|
|
18
|
-
const newLines = lines.map(line => {
|
|
19
|
-
// Match import statements with @/ aliases
|
|
20
|
-
const match = line.match(/^(import .* from ['"])@\/(.+)(['"];?)$/);
|
|
21
|
-
|
|
22
|
-
if (match) {
|
|
23
|
-
const [, prefix, importPath, suffix] = match;
|
|
24
|
-
const currentDir = dirname(file);
|
|
25
|
-
const targetPath = join('src', importPath);
|
|
26
|
-
let relativePath = relative(currentDir, targetPath);
|
|
27
|
-
|
|
28
|
-
// Ensure it starts with ./ or ../
|
|
29
|
-
if (!relativePath.startsWith('.')) {
|
|
30
|
-
relativePath = './' + relativePath;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Add .js extension if not already there
|
|
34
|
-
if (!relativePath.endsWith('.js')) {
|
|
35
|
-
relativePath += '.js';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Fix Windows backslashes
|
|
39
|
-
relativePath = relativePath.replace(/\\/g, '/');
|
|
40
|
-
|
|
41
|
-
return `${prefix}${relativePath}${suffix}`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return line;
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const newContent = newLines.join('\n');
|
|
48
|
-
|
|
49
|
-
if (newContent !== content) {
|
|
50
|
-
writeFileSync(file, newContent);
|
|
51
|
-
console.log(`✓ Fixed: ${file}`);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
console.log('\n✅ All imports converted!');
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Setup environment file
|
|
5
|
-
* Copies .env.example to .env if .env doesn't exist
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import fs from 'fs';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
|
-
import { dirname } from 'path';
|
|
12
|
-
|
|
13
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
-
const __dirname = dirname(__filename);
|
|
15
|
-
|
|
16
|
-
const ROOT_DIR = path.resolve(__dirname, '..');
|
|
17
|
-
const ENV_FILE = path.join(ROOT_DIR, '.env');
|
|
18
|
-
const EXAMPLE_ENV_FILE = path.join(ROOT_DIR, '.env.example');
|
|
19
|
-
|
|
20
|
-
function setupEnv() {
|
|
21
|
-
// Check if .env already exists
|
|
22
|
-
if (fs.existsSync(ENV_FILE)) {
|
|
23
|
-
console.log('✓ .env file already exists');
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Check if .env.example exists
|
|
28
|
-
if (!fs.existsSync(EXAMPLE_ENV_FILE)) {
|
|
29
|
-
console.error('✗ .env.example file not found!');
|
|
30
|
-
console.error(' Please create a .env.example file first');
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Copy .env.example to .env
|
|
35
|
-
try {
|
|
36
|
-
fs.copyFileSync(EXAMPLE_ENV_FILE, ENV_FILE);
|
|
37
|
-
console.log('✓ Created .env file from .env.example');
|
|
38
|
-
console.log('');
|
|
39
|
-
console.log('⚠ IMPORTANT: Please update the following values in .env:');
|
|
40
|
-
console.log(' - DATABASE_URL (replace {{DATABASE_PASSWORD}} and {{DATABASE_NAME}})');
|
|
41
|
-
console.log(' - JWT_SECRET (use a strong secret in production)');
|
|
42
|
-
console.log(' - ADMIN_EMAIL and ADMIN_PASSWORD');
|
|
43
|
-
console.log('');
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error('✗ Failed to create .env file:', error.message);
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
setupEnv();
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Wait for database to be ready before running migrations
|
|
5
|
-
* This script polls the database connection until it's available
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createConnection } from 'mysql2/promise';
|
|
9
|
-
import { config } from 'dotenv';
|
|
10
|
-
import { resolve } from 'path';
|
|
11
|
-
|
|
12
|
-
// Load environment variables
|
|
13
|
-
config({ path: resolve(process.cwd(), '.env') });
|
|
14
|
-
|
|
15
|
-
const MAX_ATTEMPTS = 30; // 30 attempts
|
|
16
|
-
const RETRY_DELAY = 2000; // 2 seconds between attempts
|
|
17
|
-
|
|
18
|
-
async function waitForDatabase() {
|
|
19
|
-
const dbConfig = {
|
|
20
|
-
host: process.env.DB_HOST || 'localhost',
|
|
21
|
-
port: parseInt(process.env.DB_PORT || '3306'),
|
|
22
|
-
user: process.env.DB_USER || 'root',
|
|
23
|
-
password: process.env.DB_PASSWORD || 'password',
|
|
24
|
-
database: process.env.DB_NAME,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
console.log('Waiting for database to be ready...');
|
|
28
|
-
console.log(` Host: ${dbConfig.host}:${dbConfig.port}`);
|
|
29
|
-
console.log(` Database: ${dbConfig.database}`);
|
|
30
|
-
console.log();
|
|
31
|
-
|
|
32
|
-
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
33
|
-
try {
|
|
34
|
-
const connection = await createConnection(dbConfig);
|
|
35
|
-
await connection.ping();
|
|
36
|
-
await connection.end();
|
|
37
|
-
|
|
38
|
-
console.log('Database is ready!');
|
|
39
|
-
console.log();
|
|
40
|
-
process.exit(0);
|
|
41
|
-
} catch (error) {
|
|
42
|
-
if (attempt === MAX_ATTEMPTS) {
|
|
43
|
-
console.error('Database failed to become ready after maximum attempts');
|
|
44
|
-
console.error(` Error: ${error.message}`);
|
|
45
|
-
console.error();
|
|
46
|
-
console.error('Troubleshooting:');
|
|
47
|
-
console.error(' 1. Ensure Docker is running: docker ps');
|
|
48
|
-
console.error(' 2. Check if containers are healthy: docker-compose ps');
|
|
49
|
-
console.error(' 3. View MySQL logs: docker-compose logs mysql');
|
|
50
|
-
console.error(' 4. Restart containers: docker-compose restart');
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
process.stdout.write(` Attempt ${attempt}/${MAX_ATTEMPTS} - Database not ready yet, retrying in ${RETRY_DELAY / 1000}s...\r`);
|
|
55
|
-
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
waitForDatabase();
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { FastifyInstance } from 'fastify';
|
|
2
|
-
import { env } from '../config/env.js';
|
|
3
|
-
|
|
4
|
-
export function registerRequestHooks(app: FastifyInstance) {
|
|
5
|
-
app.addHook('onRequest', async (request) => {
|
|
6
|
-
request.startTime = Date.now();
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
app.addHook('onResponse', async (request, reply) => {
|
|
10
|
-
const duration = Date.now() - request.startTime;
|
|
11
|
-
const logData = {
|
|
12
|
-
method: request.method,
|
|
13
|
-
url: request.url,
|
|
14
|
-
statusCode: reply.statusCode,
|
|
15
|
-
duration: `${duration}ms`,
|
|
16
|
-
ip: request.ip,
|
|
17
|
-
requestId: request.id,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
if (duration > env.SLOW_QUERY_THRESHOLD) {
|
|
21
|
-
app.log.warn(logData, 'Slow response detected');
|
|
22
|
-
} else {
|
|
23
|
-
app.log.info(logData, 'Request completed');
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { FastifyReply, FastifyRequest } from 'fastify';
|
|
2
|
-
import { verifyAccessToken } from '../../modules/auth/auth.service.js';
|
|
3
|
-
import { UnauthorizedError, AppError } from '../../utils/errors.js';
|
|
4
|
-
|
|
5
|
-
export async function authenticateMiddleware(request: FastifyRequest, reply: FastifyReply) {
|
|
6
|
-
try {
|
|
7
|
-
const authHeader = request.headers.authorization;
|
|
8
|
-
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
9
|
-
throw new UnauthorizedError('Authentication token missing');
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const token = authHeader.split(' ')[1];
|
|
13
|
-
if (!token) {
|
|
14
|
-
throw new UnauthorizedError('Malformed authentication token');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const payload = await verifyAccessToken(token);
|
|
18
|
-
request.user = payload;
|
|
19
|
-
} catch (error) {
|
|
20
|
-
throw error instanceof AppError ? error : new UnauthorizedError('Invalid or expired token');
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RBAC Middleware Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for role-based access control middleware functions.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
8
|
-
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
9
|
-
import { requireRole, requireAdmin, requireUser, hasRole, isAdmin } from './rbac.middleware';
|
|
10
|
-
import { ForbiddenError, UnauthorizedError } from '../../utils/errors';
|
|
11
|
-
|
|
12
|
-
describe('RBAC Middleware', () => {
|
|
13
|
-
let mockRequest: any;
|
|
14
|
-
let mockReply: any;
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
mockRequest = {
|
|
18
|
-
user: undefined,
|
|
19
|
-
};
|
|
20
|
-
mockReply = {};
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe('requireRole', () => {
|
|
24
|
-
it('should allow access when user has required role', async () => {
|
|
25
|
-
mockRequest.user = { userId: '123', email: 'admin@test.com', role: 'ADMIN' };
|
|
26
|
-
const middleware = requireRole('ADMIN');
|
|
27
|
-
|
|
28
|
-
await expect(
|
|
29
|
-
middleware(mockRequest as FastifyRequest, mockReply as FastifyReply)
|
|
30
|
-
).resolves.not.toThrow();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should allow access when user has one of multiple allowed roles', async () => {
|
|
34
|
-
mockRequest.user = { userId: '123', email: 'user@test.com', role: 'USER' };
|
|
35
|
-
const middleware = requireRole('USER', 'ADMIN');
|
|
36
|
-
|
|
37
|
-
await expect(
|
|
38
|
-
middleware(mockRequest as FastifyRequest, mockReply as FastifyReply)
|
|
39
|
-
).resolves.not.toThrow();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should throw UnauthorizedError when user is not authenticated', async () => {
|
|
43
|
-
mockRequest.user = undefined;
|
|
44
|
-
const middleware = requireRole('ADMIN');
|
|
45
|
-
|
|
46
|
-
await expect(
|
|
47
|
-
middleware(mockRequest as FastifyRequest, mockReply as FastifyReply)
|
|
48
|
-
).rejects.toThrow(UnauthorizedError);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should throw ForbiddenError when user lacks required role', async () => {
|
|
52
|
-
mockRequest.user = { userId: '123', email: 'user@test.com', role: 'USER' };
|
|
53
|
-
const middleware = requireRole('ADMIN');
|
|
54
|
-
|
|
55
|
-
await expect(
|
|
56
|
-
middleware(mockRequest as FastifyRequest, mockReply as FastifyReply)
|
|
57
|
-
).rejects.toThrow(ForbiddenError);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('requireAdmin', () => {
|
|
62
|
-
it('should allow access for ADMIN users', async () => {
|
|
63
|
-
mockRequest.user = { userId: '123', email: 'admin@test.com', role: 'ADMIN' };
|
|
64
|
-
const middleware = requireAdmin();
|
|
65
|
-
|
|
66
|
-
await expect(
|
|
67
|
-
middleware(mockRequest as FastifyRequest, mockReply as FastifyReply)
|
|
68
|
-
).resolves.not.toThrow();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('should deny access for USER role', async () => {
|
|
72
|
-
mockRequest.user = { userId: '123', email: 'user@test.com', role: 'USER' };
|
|
73
|
-
const middleware = requireAdmin();
|
|
74
|
-
|
|
75
|
-
await expect(
|
|
76
|
-
middleware(mockRequest as FastifyRequest, mockReply as FastifyReply)
|
|
77
|
-
).rejects.toThrow(ForbiddenError);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe('requireUser', () => {
|
|
82
|
-
it('should allow access for USER role', async () => {
|
|
83
|
-
mockRequest.user = { userId: '123', email: 'user@test.com', role: 'USER' };
|
|
84
|
-
const middleware = requireUser();
|
|
85
|
-
|
|
86
|
-
await expect(
|
|
87
|
-
middleware(mockRequest as FastifyRequest, mockReply as FastifyReply)
|
|
88
|
-
).resolves.not.toThrow();
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('should deny access for ADMIN role', async () => {
|
|
92
|
-
mockRequest.user = { userId: '123', email: 'admin@test.com', role: 'ADMIN' };
|
|
93
|
-
const middleware = requireUser();
|
|
94
|
-
|
|
95
|
-
await expect(
|
|
96
|
-
middleware(mockRequest as FastifyRequest, mockReply as FastifyReply)
|
|
97
|
-
).rejects.toThrow(ForbiddenError);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe('hasRole utility', () => {
|
|
102
|
-
it('should return true when user has the role', () => {
|
|
103
|
-
mockRequest.user = { userId: '123', email: 'admin@test.com', role: 'ADMIN' };
|
|
104
|
-
|
|
105
|
-
expect(hasRole(mockRequest as FastifyRequest, 'ADMIN')).toBe(true);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('should return false when user does not have the role', () => {
|
|
109
|
-
mockRequest.user = { userId: '123', email: 'user@test.com', role: 'USER' };
|
|
110
|
-
|
|
111
|
-
expect(hasRole(mockRequest as FastifyRequest, 'ADMIN')).toBe(false);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('should return false when user is not authenticated', () => {
|
|
115
|
-
mockRequest.user = undefined;
|
|
116
|
-
|
|
117
|
-
expect(hasRole(mockRequest as FastifyRequest, 'ADMIN')).toBe(false);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
describe('isAdmin utility', () => {
|
|
122
|
-
it('should return true for ADMIN users', () => {
|
|
123
|
-
mockRequest.user = { userId: '123', email: 'admin@test.com', role: 'ADMIN' };
|
|
124
|
-
|
|
125
|
-
expect(isAdmin(mockRequest as FastifyRequest)).toBe(true);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('should return false for non-ADMIN users', () => {
|
|
129
|
-
mockRequest.user = { userId: '123', email: 'user@test.com', role: 'USER' };
|
|
130
|
-
|
|
131
|
-
expect(isAdmin(mockRequest as FastifyRequest)).toBe(false);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
});
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Role-Based Access Control (RBAC) Middleware
|
|
3
|
-
*
|
|
4
|
-
* Provides middleware functions to enforce role-based permissions on routes.
|
|
5
|
-
* Must be used AFTER the authenticate middleware.
|
|
6
|
-
*
|
|
7
|
-
* @see /mnt/project/02-general-rules.md
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
11
|
-
import { ForbiddenError, UnauthorizedError } from '../../utils/errors.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* User Role Type
|
|
15
|
-
*
|
|
16
|
-
* Must match the roles defined in Prisma schema and JWT payload
|
|
17
|
-
*/
|
|
18
|
-
export type UserRole = 'USER' | 'ADMIN';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* RBAC Middleware Factory
|
|
22
|
-
*
|
|
23
|
-
* Creates a preHandler middleware that checks if the authenticated user
|
|
24
|
-
* has one of the required roles.
|
|
25
|
-
*
|
|
26
|
-
* @param allowedRoles - Array of roles that are allowed to access the route
|
|
27
|
-
* @returns Fastify preHandler function
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* // Single role
|
|
31
|
-
* app.get('/admin/users', {
|
|
32
|
-
* preHandler: [app.authenticate, requireRole('ADMIN')],
|
|
33
|
-
* handler: adminController.listUsers
|
|
34
|
-
* });
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* // Multiple roles
|
|
38
|
-
* app.post('/resources', {
|
|
39
|
-
* preHandler: [app.authenticate, requireRole('USER', 'ADMIN')],
|
|
40
|
-
* handler: resourceController.create
|
|
41
|
-
* });
|
|
42
|
-
*/
|
|
43
|
-
export function requireRole(...allowedRoles: UserRole[]) {
|
|
44
|
-
return async (request: FastifyRequest, _reply: FastifyReply): Promise<void> => {
|
|
45
|
-
// Ensure user is authenticated (should be set by app.authenticate)
|
|
46
|
-
if (!request.user) {
|
|
47
|
-
throw new UnauthorizedError('Authentication required');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const userRole = request.user.role as UserRole;
|
|
51
|
-
|
|
52
|
-
// Validate that user's role is in the allowed roles list
|
|
53
|
-
if (!allowedRoles.includes(userRole)) {
|
|
54
|
-
throw new ForbiddenError(
|
|
55
|
-
`Access denied. Required role: ${allowedRoles.join(' or ')}`
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// User has required role, allow request to proceed
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Require ADMIN role
|
|
65
|
-
*
|
|
66
|
-
* Shorthand helper for routes that require admin access only.
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* app.delete('/users/:id', {
|
|
70
|
-
* preHandler: [app.authenticate, requireAdmin()],
|
|
71
|
-
* handler: userController.deleteUser
|
|
72
|
-
* });
|
|
73
|
-
*/
|
|
74
|
-
export const requireAdmin = () => requireRole('ADMIN');
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Require USER role
|
|
78
|
-
*
|
|
79
|
-
* Shorthand helper for routes that require standard user access.
|
|
80
|
-
* Note: This excludes ADMIN users. Use requireAny() to allow both.
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* app.get('/profile', {
|
|
84
|
-
* preHandler: [app.authenticate, requireUser()],
|
|
85
|
-
* handler: userController.getProfile
|
|
86
|
-
* });
|
|
87
|
-
*/
|
|
88
|
-
export const requireUser = () => requireRole('USER');
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Require any authenticated user (USER or ADMIN)
|
|
92
|
-
*
|
|
93
|
-
* Shorthand helper for routes that require authentication but accept any role.
|
|
94
|
-
* This is equivalent to just using app.authenticate without role checking.
|
|
95
|
-
*
|
|
96
|
-
* @example
|
|
97
|
-
* app.get('/resources', {
|
|
98
|
-
* preHandler: [app.authenticate, requireAny()],
|
|
99
|
-
* handler: resourceController.list
|
|
100
|
-
* });
|
|
101
|
-
*/
|
|
102
|
-
export const requireAny = () => requireRole('USER', 'ADMIN');
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Check if user has specific role (utility function)
|
|
106
|
-
*
|
|
107
|
-
* Can be used within route handlers for conditional logic.
|
|
108
|
-
*
|
|
109
|
-
* @param request - Fastify request object
|
|
110
|
-
* @param role - Role to check
|
|
111
|
-
* @returns true if user has the role, false otherwise
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* export async function getResources(request: FastifyRequest) {
|
|
115
|
-
* const resources = await resourceRepo.findAll();
|
|
116
|
-
*
|
|
117
|
-
* // Filter sensitive data for non-admin users
|
|
118
|
-
* if (!hasRole(request, 'ADMIN')) {
|
|
119
|
-
* return resources.map(r => omit(r, 'internalNotes'));
|
|
120
|
-
* }
|
|
121
|
-
*
|
|
122
|
-
* return resources;
|
|
123
|
-
* }
|
|
124
|
-
*/
|
|
125
|
-
export function hasRole(request: FastifyRequest, role: UserRole): boolean {
|
|
126
|
-
return request.user?.role === role;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Check if user is admin (utility function)
|
|
131
|
-
*
|
|
132
|
-
* @param request - Fastify request object
|
|
133
|
-
* @returns true if user is admin, false otherwise
|
|
134
|
-
*/
|
|
135
|
-
export function isAdmin(request: FastifyRequest): boolean {
|
|
136
|
-
return hasRole(request, 'ADMIN');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Check if user is regular user (utility function)
|
|
141
|
-
*
|
|
142
|
-
* @param request - Fastify request object
|
|
143
|
-
* @returns true if user is regular user, false otherwise
|
|
144
|
-
*/
|
|
145
|
-
export function isUser(request: FastifyRequest): boolean {
|
|
146
|
-
return hasRole(request, 'USER');
|
|
147
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Database Connection
|
|
3
|
-
*
|
|
4
|
-
* Prisma Client instance with query logging and error handling.
|
|
5
|
-
*
|
|
6
|
-
* @see /mnt/project/01-db-and-migrations.md
|
|
7
|
-
* @see /mnt/project/08-observability.md
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { PrismaClient, Prisma } from '@prisma/client';
|
|
11
|
-
import logger from './logger.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Create Prisma Client instance
|
|
15
|
-
*
|
|
16
|
-
* Features:
|
|
17
|
-
* - Slow query logging (> 1000ms)
|
|
18
|
-
* - Error logging
|
|
19
|
-
* - Query logging in development (optional)
|
|
20
|
-
*/
|
|
21
|
-
const prisma = new PrismaClient({
|
|
22
|
-
log:
|
|
23
|
-
process.env['PRISMA_QUERY_LOG'] === 'true'
|
|
24
|
-
? [
|
|
25
|
-
{ level: 'query', emit: 'event' },
|
|
26
|
-
{ level: 'info', emit: 'stdout' },
|
|
27
|
-
{ level: 'warn', emit: 'stdout' },
|
|
28
|
-
{ level: 'error', emit: 'event' },
|
|
29
|
-
]
|
|
30
|
-
: [
|
|
31
|
-
{ level: 'warn', emit: 'stdout' },
|
|
32
|
-
{ level: 'error', emit: 'event' },
|
|
33
|
-
{ level: 'query', emit: 'event' },
|
|
34
|
-
],
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Slow Query Logging
|
|
39
|
-
*
|
|
40
|
-
* Logs a warning when a query takes longer than the threshold.
|
|
41
|
-
* Helps identify performance bottlenecks.
|
|
42
|
-
*/
|
|
43
|
-
prisma.$on('query', (e: Prisma.QueryEvent) => {
|
|
44
|
-
const threshold = parseInt(process.env['SLOW_QUERY_THRESHOLD'] || '1000');
|
|
45
|
-
|
|
46
|
-
if (e.duration > threshold) {
|
|
47
|
-
logger.warn(
|
|
48
|
-
{
|
|
49
|
-
query: e.query,
|
|
50
|
-
params: e.params,
|
|
51
|
-
duration: e.duration,
|
|
52
|
-
target: e.target,
|
|
53
|
-
},
|
|
54
|
-
`Slow query detected (${e.duration}ms)`
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Error Logging
|
|
61
|
-
*
|
|
62
|
-
* Logs database errors for monitoring and debugging.
|
|
63
|
-
*/
|
|
64
|
-
prisma.$on('error', (e: Prisma.LogEvent) => {
|
|
65
|
-
logger.error(
|
|
66
|
-
{
|
|
67
|
-
message: e.message,
|
|
68
|
-
target: e.target,
|
|
69
|
-
},
|
|
70
|
-
'Database error occurred'
|
|
71
|
-
);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
export { prisma };
|