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.
Files changed (243) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +80 -87
  3. package/bin/create-tigra.js +259 -308
  4. package/package.json +49 -41
  5. package/template/_claude/QUICK_REFERENCE.md +193 -0
  6. package/template/_claude/README.md +53 -0
  7. package/template/_claude/commands/create-client.md +881 -0
  8. package/template/_claude/commands/create-server.md +383 -0
  9. package/template/_claude/rules/client/01-project-structure.md +133 -0
  10. package/template/_claude/rules/client/02-components-and-types.md +146 -0
  11. package/template/_claude/rules/client/03-data-and-state.md +156 -0
  12. package/template/_claude/rules/client/04-design-system.md +185 -0
  13. package/template/_claude/rules/client/05-security.md +55 -0
  14. package/template/_claude/rules/client/06-ux-checklist.md +81 -0
  15. package/template/_claude/rules/client/core.md +42 -0
  16. package/template/_claude/rules/global/core.md +77 -0
  17. package/template/_claude/rules/server/core.md +50 -0
  18. package/template/_claude/rules/server/database.md +124 -0
  19. package/template/_claude/rules/server/project-conventions.md +150 -0
  20. package/template/_claude/rules/server/response-handling.md +144 -0
  21. package/template/client/.env.example +5 -0
  22. package/template/client/README.md +36 -0
  23. package/template/client/components.json +23 -0
  24. package/template/client/eslint.config.mjs +18 -0
  25. package/template/client/next.config.ts +34 -0
  26. package/template/client/package.json +44 -0
  27. package/template/client/postcss.config.mjs +7 -0
  28. package/template/client/src/app/(auth)/layout.tsx +18 -0
  29. package/template/client/src/app/(auth)/login/page.tsx +13 -0
  30. package/template/client/src/app/(auth)/register/page.tsx +13 -0
  31. package/template/client/src/app/(main)/dashboard/page.tsx +22 -0
  32. package/template/client/src/app/(main)/layout.tsx +11 -0
  33. package/template/client/src/app/error.tsx +27 -0
  34. package/template/client/src/app/favicon.ico +0 -0
  35. package/template/client/src/app/globals.css +145 -0
  36. package/template/client/src/app/layout.tsx +36 -0
  37. package/template/client/src/app/loading.tsx +11 -0
  38. package/template/client/src/app/not-found.tsx +23 -0
  39. package/template/client/src/app/page.tsx +45 -0
  40. package/template/client/src/app/providers.tsx +43 -0
  41. package/template/client/src/components/common/ConfirmDialog.tsx +56 -0
  42. package/template/client/src/components/common/EmptyState.tsx +31 -0
  43. package/template/client/src/components/common/LoadingSpinner.tsx +30 -0
  44. package/template/client/src/components/common/Pagination.tsx +55 -0
  45. package/template/client/src/components/layout/Footer.tsx +17 -0
  46. package/template/client/src/components/layout/Header.tsx +173 -0
  47. package/template/client/src/components/layout/MainLayout.tsx +18 -0
  48. package/template/client/src/components/ui/alert-dialog.tsx +196 -0
  49. package/template/client/src/components/ui/badge.tsx +48 -0
  50. package/template/client/src/components/ui/button.tsx +64 -0
  51. package/template/client/src/components/ui/card.tsx +92 -0
  52. package/template/client/src/components/ui/input.tsx +21 -0
  53. package/template/client/src/components/ui/label.tsx +24 -0
  54. package/template/client/src/components/ui/select.tsx +190 -0
  55. package/template/client/src/components/ui/skeleton.tsx +13 -0
  56. package/template/client/src/components/ui/table.tsx +116 -0
  57. package/template/client/src/features/auth/components/AuthInitializer.tsx +55 -0
  58. package/template/client/src/features/auth/components/LoginForm.tsx +107 -0
  59. package/template/client/src/features/auth/components/RegisterForm.tsx +178 -0
  60. package/template/client/src/features/auth/hooks/useAuth.ts +84 -0
  61. package/template/client/src/features/auth/services/auth.service.ts +52 -0
  62. package/template/client/src/features/auth/store/authSlice.ts +38 -0
  63. package/template/client/src/features/auth/types/auth.types.ts +32 -0
  64. package/template/client/src/hooks/useDebounce.ts +14 -0
  65. package/template/client/src/hooks/useLocalStorage.ts +55 -0
  66. package/template/client/src/hooks/useMediaQuery.ts +27 -0
  67. package/template/client/src/lib/api/api.types.ts +34 -0
  68. package/template/client/src/lib/api/axios.config.ts +98 -0
  69. package/template/client/src/lib/constants/api-endpoints.ts +18 -0
  70. package/template/client/src/lib/constants/app.constants.ts +12 -0
  71. package/template/client/src/lib/constants/routes.ts +9 -0
  72. package/template/client/src/lib/utils/error.ts +32 -0
  73. package/template/client/src/lib/utils/format.ts +37 -0
  74. package/template/client/src/lib/utils/security.ts +34 -0
  75. package/template/client/src/lib/utils.ts +6 -0
  76. package/template/client/src/middleware.ts +57 -0
  77. package/template/client/src/store/hooks.ts +7 -0
  78. package/template/client/src/store/index.ts +12 -0
  79. package/template/client/src/types/index.ts +3 -0
  80. package/template/client/tsconfig.json +34 -0
  81. package/template/gitignore +34 -0
  82. package/template/server/.dockerignore +66 -0
  83. package/template/server/.env.example +96 -69
  84. package/template/server/.env.production.example +90 -0
  85. package/template/server/Dockerfile +94 -0
  86. package/template/server/docker-compose.yml +82 -111
  87. package/template/server/docs/logging.md +62 -0
  88. package/template/server/eslint.config.mjs +17 -0
  89. package/template/server/package.json +68 -81
  90. package/template/server/phpmyadmin-config.php +26 -0
  91. package/template/server/postman_collection.json +666 -0
  92. package/template/server/prisma/schema.prisma +77 -93
  93. package/template/server/prisma/seed.ts +46 -142
  94. package/template/server/scripts/flush-redis.ts +41 -0
  95. package/template/server/src/app.ts +243 -71
  96. package/template/server/src/config/env.ts +67 -94
  97. package/template/server/src/libs/auth.ts +88 -0
  98. package/template/server/src/libs/cleanup.ts +35 -0
  99. package/template/server/src/libs/cookies.ts +46 -0
  100. package/template/server/src/libs/logger.ts +33 -60
  101. package/template/server/src/libs/monitoring.ts +205 -0
  102. package/template/server/src/libs/password.ts +38 -0
  103. package/template/server/src/libs/prisma.ts +68 -0
  104. package/template/server/src/libs/redis.ts +60 -79
  105. package/template/server/src/libs/requestLogger.ts +66 -0
  106. package/template/server/src/libs/storage/file-storage.service.ts +211 -0
  107. package/template/server/src/libs/storage/file-validator.ts +97 -0
  108. package/template/server/src/libs/storage/filename-sanitizer.ts +71 -0
  109. package/template/server/src/libs/storage/image-optimizer.service.ts +144 -0
  110. package/template/server/src/modules/auth/__tests__/auth.service.test.ts +365 -0
  111. package/template/server/src/modules/auth/auth.controller.ts +90 -141
  112. package/template/server/src/modules/auth/auth.repo.ts +120 -218
  113. package/template/server/src/modules/auth/auth.routes.ts +96 -83
  114. package/template/server/src/modules/auth/auth.schemas.ts +35 -137
  115. package/template/server/src/modules/auth/auth.service.ts +286 -329
  116. package/template/server/src/modules/auth/session.repo.ts +110 -0
  117. package/template/server/src/modules/users/users.controller.ts +120 -0
  118. package/template/server/src/modules/users/users.repo.ts +77 -0
  119. package/template/server/src/modules/users/users.routes.ts +89 -0
  120. package/template/server/src/modules/users/users.schemas.ts +21 -0
  121. package/template/server/src/modules/users/users.service.ts +169 -0
  122. package/template/server/src/server.ts +58 -139
  123. package/template/server/src/shared/errors/AppError.ts +21 -0
  124. package/template/server/src/shared/errors/errors.ts +43 -0
  125. package/template/server/src/shared/responses/paginatedResponse.ts +38 -0
  126. package/template/server/src/shared/responses/successResponse.ts +17 -0
  127. package/template/server/src/shared/schemas/pagination.schema.ts +12 -0
  128. package/template/server/src/shared/types/index.ts +26 -0
  129. package/template/server/src/test/setup.ts +74 -38
  130. package/template/server/tsconfig.json +27 -89
  131. package/template/server/uploads/avatars/.gitkeep +1 -0
  132. package/template/server/vitest.config.ts +43 -98
  133. package/template/.agent/rules/client/01-project-structure.md +0 -326
  134. package/template/.agent/rules/client/02-component-patterns.md +0 -249
  135. package/template/.agent/rules/client/03-typescript-rules.md +0 -226
  136. package/template/.agent/rules/client/04-state-management.md +0 -474
  137. package/template/.agent/rules/client/05-api-integration.md +0 -129
  138. package/template/.agent/rules/client/06-forms-validation.md +0 -129
  139. package/template/.agent/rules/client/07-common-patterns.md +0 -150
  140. package/template/.agent/rules/client/08-color-system.md +0 -93
  141. package/template/.agent/rules/client/09-security-rules.md +0 -97
  142. package/template/.agent/rules/client/10-testing-strategy.md +0 -370
  143. package/template/.agent/rules/global/ai-edit-safety.md +0 -38
  144. package/template/.agent/rules/server/01-db-and-migrations.md +0 -242
  145. package/template/.agent/rules/server/02-general-rules.md +0 -111
  146. package/template/.agent/rules/server/03-migrations.md +0 -20
  147. package/template/.agent/rules/server/04-pagination.md +0 -130
  148. package/template/.agent/rules/server/05-project-conventions.md +0 -71
  149. package/template/.agent/rules/server/06-response-handling.md +0 -173
  150. package/template/.agent/rules/server/07-testing-strategy.md +0 -506
  151. package/template/.agent/rules/server/08-observability.md +0 -180
  152. package/template/.agent/rules/server/10-background-jobs-v2.md +0 -185
  153. package/template/.agent/rules/server/11-rate-limiting-v2.md +0 -210
  154. package/template/.agent/rules/server/12-performance-optimization.md +0 -567
  155. package/template/.claude/rules/client-01-project-structure.md +0 -327
  156. package/template/.claude/rules/client-02-component-patterns.md +0 -250
  157. package/template/.claude/rules/client-03-typescript-rules.md +0 -227
  158. package/template/.claude/rules/client-04-state-management.md +0 -475
  159. package/template/.claude/rules/client-05-api-integration.md +0 -130
  160. package/template/.claude/rules/client-06-forms-validation.md +0 -130
  161. package/template/.claude/rules/client-07-common-patterns.md +0 -151
  162. package/template/.claude/rules/client-08-color-system.md +0 -94
  163. package/template/.claude/rules/client-09-security-rules.md +0 -98
  164. package/template/.claude/rules/client-10-testing-strategy.md +0 -371
  165. package/template/.claude/rules/global-ai-edit-safety.md +0 -39
  166. package/template/.claude/rules/server-01-db-and-migrations.md +0 -243
  167. package/template/.claude/rules/server-02-general-rules.md +0 -112
  168. package/template/.claude/rules/server-03-migrations.md +0 -21
  169. package/template/.claude/rules/server-04-pagination.md +0 -131
  170. package/template/.claude/rules/server-05-project-conventions.md +0 -72
  171. package/template/.claude/rules/server-06-response-handling.md +0 -174
  172. package/template/.claude/rules/server-07-testing-strategy.md +0 -507
  173. package/template/.claude/rules/server-08-observability.md +0 -181
  174. package/template/.claude/rules/server-10-background-jobs-v2.md +0 -186
  175. package/template/.claude/rules/server-11-rate-limiting-v2.md +0 -211
  176. package/template/.claude/rules/server-12-performance-optimization.md +0 -568
  177. package/template/.cursor/rules/client-01-project-structure.mdc +0 -327
  178. package/template/.cursor/rules/client-02-component-patterns.mdc +0 -250
  179. package/template/.cursor/rules/client-03-typescript-rules.mdc +0 -227
  180. package/template/.cursor/rules/client-04-state-management.mdc +0 -475
  181. package/template/.cursor/rules/client-05-api-integration.mdc +0 -130
  182. package/template/.cursor/rules/client-06-forms-validation.mdc +0 -130
  183. package/template/.cursor/rules/client-07-common-patterns.mdc +0 -151
  184. package/template/.cursor/rules/client-08-color-system.mdc +0 -94
  185. package/template/.cursor/rules/client-09-security-rules.mdc +0 -98
  186. package/template/.cursor/rules/client-10-testing-strategy.mdc +0 -371
  187. package/template/.cursor/rules/global-ai-edit-safety.mdc +0 -39
  188. package/template/.cursor/rules/server-01-db-and-migrations.mdc +0 -243
  189. package/template/.cursor/rules/server-02-general-rules.mdc +0 -112
  190. package/template/.cursor/rules/server-03-migrations.mdc +0 -21
  191. package/template/.cursor/rules/server-04-pagination.mdc +0 -131
  192. package/template/.cursor/rules/server-05-project-conventions.mdc +0 -72
  193. package/template/.cursor/rules/server-06-response-handling.mdc +0 -174
  194. package/template/.cursor/rules/server-07-testing-strategy.mdc +0 -507
  195. package/template/.cursor/rules/server-08-observability.mdc +0 -181
  196. package/template/.cursor/rules/server-09-api-documentation-v2.mdc +0 -169
  197. package/template/.cursor/rules/server-10-background-jobs-v2.mdc +0 -186
  198. package/template/.cursor/rules/server-11-rate-limiting-v2.mdc +0 -211
  199. package/template/.cursor/rules/server-12-performance-optimization.mdc +0 -568
  200. package/template/CLAUDE.md +0 -207
  201. package/template/server/.tsc-aliasrc.json +0 -13
  202. package/template/server/IMPORT_FIX_CHECKLIST.md +0 -98
  203. package/template/server/IMPORT_FIX_COMPLETE.md +0 -89
  204. package/template/server/README.md +0 -183
  205. package/template/server/REMAINING_IMPORT_FIXES.md +0 -150
  206. package/template/server/SECURITY.md +0 -190
  207. package/template/server/Tigra-API.postman_collection.json +0 -733
  208. package/template/server/biome.json +0 -42
  209. package/template/server/scripts/fix-all-imports.ps1 +0 -52
  210. package/template/server/scripts/fix-imports-reference.ps1 +0 -16
  211. package/template/server/scripts/fix-imports.mjs +0 -55
  212. package/template/server/scripts/setup-env.js +0 -50
  213. package/template/server/scripts/wait-for-db.js +0 -60
  214. package/template/server/src/hooks/request-timing.hook.ts +0 -26
  215. package/template/server/src/libs/auth/authenticate.middleware.ts +0 -22
  216. package/template/server/src/libs/auth/rbac.middleware.test.ts +0 -134
  217. package/template/server/src/libs/auth/rbac.middleware.ts +0 -147
  218. package/template/server/src/libs/db.ts +0 -76
  219. package/template/server/src/libs/error-handler.ts +0 -89
  220. package/template/server/src/libs/queue.ts +0 -79
  221. package/template/server/src/modules/admin/admin.controller.ts +0 -122
  222. package/template/server/src/modules/admin/admin.routes.ts +0 -62
  223. package/template/server/src/modules/admin/admin.schemas.ts +0 -35
  224. package/template/server/src/modules/admin/admin.service.ts +0 -167
  225. package/template/server/src/modules/auth/auth.integration.test.ts +0 -150
  226. package/template/server/src/modules/auth/auth.service.test.ts +0 -119
  227. package/template/server/src/modules/auth/auth.types.ts +0 -97
  228. package/template/server/src/modules/resources/resources.controller.ts +0 -218
  229. package/template/server/src/modules/resources/resources.repo.ts +0 -253
  230. package/template/server/src/modules/resources/resources.routes.ts +0 -116
  231. package/template/server/src/modules/resources/resources.schemas.ts +0 -146
  232. package/template/server/src/modules/resources/resources.service.ts +0 -218
  233. package/template/server/src/modules/resources/resources.types.ts +0 -73
  234. package/template/server/src/plugins/rate-limit.plugin.ts +0 -21
  235. package/template/server/src/plugins/security.plugin.ts +0 -21
  236. package/template/server/src/routes/health.routes.ts +0 -31
  237. package/template/server/src/types/fastify.d.ts +0 -36
  238. package/template/server/src/utils/errors.ts +0 -108
  239. package/template/server/src/utils/pagination.ts +0 -120
  240. package/template/server/src/utils/response.ts +0 -110
  241. package/template/server/src/workers/file.worker.ts +0 -106
  242. package/template/server/tsconfig.build.json +0 -30
  243. 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 };