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,139 +1,58 @@
1
- /**
2
- * Server Entry Point
3
- *
4
- * Responsible for initializing the application, starting the HTTP server,
5
- * and handling graceful shutdowns of all core dependencies.
6
- *
7
- * @see /mnt/project/02-general-rules.md
8
- */
9
-
10
- import 'dotenv/config';
11
- import buildApp from './app.js';
12
- import { env } from './config/env.js';
13
- import logger from './libs/logger.js';
14
- import { prisma } from './libs/db.js';
15
- import { redis } from './libs/redis.js';
16
- import { fileQueue } from './libs/queue.js';
17
-
18
- /**
19
- * Start the Fastify server
20
- */
21
- // Track shutdown state to prevent race conditions
22
- let isShuttingDown = false;
23
-
24
- async function startServer() {
25
- try {
26
- const app = await buildApp();
27
-
28
- // Verify Database Connection
29
- try {
30
- await prisma.$connect();
31
- logger.info('Database connected successfully');
32
- } catch (dbError) {
33
- logger.error({
34
- error: dbError instanceof Error ? dbError.message : 'Unknown DB error'
35
- }, 'Failed to connect to database');
36
- logger.warn('Database connection closed. Running without database.');
37
- }
38
-
39
- // Start listening on configured port
40
- await app.listen({
41
- port: env.PORT,
42
- host: '0.0.0.0', // Listen on all interfaces (required for Docker)
43
- });
44
-
45
- const serverUrl = `http://localhost:${env.PORT}`;
46
-
47
- logger.info(`Server started in ${env.NODE_ENV} mode`);
48
- logger.info(`API is listening at: ${serverUrl}${env.API_PREFIX}`);
49
- logger.info(`Health check available at: ${serverUrl}/health`);
50
-
51
- /**
52
- * Graceful Shutdown Handler
53
- *
54
- * Ensures all connections are closed properly before exiting.
55
- */
56
- const gracefulShutdown = async (signal: string) => {
57
- if (isShuttingDown) return;
58
- isShuttingDown = true;
59
-
60
- // Use console.log for shutdown messages to ensure they are printed
61
- // even if the async logger is terminated early.
62
- console.log(`\nReceived ${signal}. Starting graceful shutdown...`);
63
-
64
- try {
65
- await app.close();
66
- console.log('HTTP server closed');
67
- } catch (err) {
68
- console.error('Error closing HTTP server:', err);
69
- }
70
-
71
- try {
72
- await prisma.$disconnect();
73
- console.log('Database connection closed');
74
- } catch (err) {
75
- console.error('Error closing database connection:', err);
76
- }
77
-
78
- // Close Redis-dependent services first, then Redis itself
79
- // fileQueue uses the shared redis connection, so close it before redis
80
- try {
81
- await fileQueue.close();
82
- console.log('File queue connection closed');
83
- } catch (err) {
84
- // Ignore - queue may already be disconnected if Redis failed
85
- }
86
-
87
- // Close Redis connection - use disconnect() for immediate close
88
- // quit() can hang or reject if connection is already closed by BullMQ
89
- try {
90
- // Check if Redis is still connected before attempting to close
91
- if (redis.status === 'ready' || redis.status === 'connect') {
92
- await redis.quit();
93
- console.log('Redis connection closed');
94
- } else {
95
- redis.disconnect();
96
- console.log('Redis connection closed (was not connected)');
97
- }
98
- } catch (err) {
99
- // Force disconnect if quit fails
100
- try { redis.disconnect(); } catch (e) { /* ignore */ }
101
- console.log('Redis connection closed (forced)');
102
- }
103
-
104
- console.log('Graceful shutdown complete. Goodbye');
105
-
106
- // Give a small grace period for any pending logs/events to flush
107
- setTimeout(() => {
108
- process.exit(0);
109
- }, 500);
110
- };
111
-
112
- // Listen for termination signals
113
- process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
114
- process.on('SIGINT', () => gracefulShutdown('SIGINT'));
115
-
116
- } catch (error) {
117
- logger.fatal({
118
- error: error instanceof Error ? error.message : 'Unknown error',
119
- stack: error instanceof Error ? error.stack : undefined
120
- }, 'Failed to start server');
121
- process.exit(1);
122
- }
123
- }
124
-
125
- // Global Rejection Handler for Uncaught Promises
126
- process.on('unhandledRejection', (reason, promise) => {
127
- // During shutdown, BullMQ/ioredis may emit unhandled rejections
128
- // as they try to perform operations on closed connections.
129
- // These are expected and safe to ignore during graceful shutdown.
130
- if (isShuttingDown) {
131
- return;
132
- }
133
-
134
- logger.fatal({ promise, reason }, 'Unhandled Rejection at Promise');
135
- process.exit(1);
136
- });
137
-
138
- // Run the server
139
- startServer();
1
+ import { env } from '@config/env.js';
2
+ import { logger } from '@libs/logger.js';
3
+ import { testDatabaseConnection, disconnectPrisma } from '@libs/prisma.js';
4
+ import { connectRedis, disconnectRedis } from '@libs/redis.js';
5
+ import { buildApp } from './app.js';
6
+
7
+ async function start(): Promise<void> {
8
+ let app: Awaited<ReturnType<typeof buildApp>> | null = null;
9
+ let isShuttingDown = false;
10
+
11
+ // Graceful shutdown
12
+ const shutdown = async (signal: string): Promise<void> => {
13
+ if (isShuttingDown) return; // Prevent multiple shutdown attempts
14
+ isShuttingDown = true;
15
+
16
+ logger.info(`[SERVER] Received ${signal} shutting down gracefully`);
17
+ try {
18
+ if (app) {
19
+ await app.close();
20
+ }
21
+ await disconnectRedis();
22
+ await disconnectPrisma();
23
+ logger.info('[SERVER] Shutdown complete');
24
+ process.exit(0);
25
+ } catch (error) {
26
+ logger.error(error, '[SERVER] Error during shutdown');
27
+ process.exit(1);
28
+ }
29
+ };
30
+
31
+ // Register signal handlers early
32
+ process.on('SIGINT', () => shutdown('SIGINT'));
33
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
34
+ process.on('uncaughtException', (error) => {
35
+ logger.fatal(error, '[SERVER] Uncaught exception');
36
+ shutdown('uncaughtException');
37
+ });
38
+ process.on('unhandledRejection', (reason) => {
39
+ logger.fatal(reason, '[SERVER] Unhandled rejection');
40
+ shutdown('unhandledRejection');
41
+ });
42
+
43
+ try {
44
+ // Test external connections (non-fatal)
45
+ await testDatabaseConnection();
46
+ await connectRedis();
47
+
48
+ app = await buildApp();
49
+
50
+ await app.listen({ port: env.PORT, host: env.HOST });
51
+ logger.info(`[SERVER] Started on http://${env.HOST}:${env.PORT} [${env.NODE_ENV}]`);
52
+ } catch (error) {
53
+ logger.fatal(error, '[SERVER] Failed to start');
54
+ await shutdown('startup-error');
55
+ }
56
+ }
57
+
58
+ start();
@@ -0,0 +1,21 @@
1
+ export class AppError extends Error {
2
+ public readonly isAppError = true;
3
+ public readonly code: string;
4
+ public readonly statusCode: number;
5
+
6
+ constructor(code: string, message: string, statusCode: number) {
7
+ super(message);
8
+ this.code = code;
9
+ this.statusCode = statusCode;
10
+ this.name = this.constructor.name;
11
+ Error.captureStackTrace(this, this.constructor);
12
+ }
13
+ }
14
+
15
+ export function isAppError(error: unknown): error is AppError {
16
+ return (
17
+ error instanceof Error &&
18
+ 'isAppError' in error &&
19
+ (error as AppError).isAppError === true
20
+ );
21
+ }
@@ -0,0 +1,43 @@
1
+ import { AppError } from './AppError.js';
2
+
3
+ export class BadRequestError extends AppError {
4
+ constructor(message = 'Bad request', code = 'BAD_REQUEST') {
5
+ super(code, message, 400);
6
+ }
7
+ }
8
+
9
+ export class ValidationError extends AppError {
10
+ constructor(message = 'Validation failed', code = 'VALIDATION_FAILED') {
11
+ super(code, message, 422);
12
+ }
13
+ }
14
+
15
+ export class UnauthorizedError extends AppError {
16
+ constructor(message = 'Unauthorized', code = 'UNAUTHORIZED') {
17
+ super(code, message, 401);
18
+ }
19
+ }
20
+
21
+ export class ForbiddenError extends AppError {
22
+ constructor(message = 'Forbidden', code = 'FORBIDDEN') {
23
+ super(code, message, 403);
24
+ }
25
+ }
26
+
27
+ export class NotFoundError extends AppError {
28
+ constructor(message = 'Resource not found', code = 'NOT_FOUND') {
29
+ super(code, message, 404);
30
+ }
31
+ }
32
+
33
+ export class ConflictError extends AppError {
34
+ constructor(message = 'Resource already exists', code = 'CONFLICT') {
35
+ super(code, message, 409);
36
+ }
37
+ }
38
+
39
+ export class InternalError extends AppError {
40
+ constructor(message = 'Internal server error', code = 'INTERNAL_ERROR') {
41
+ super(code, message, 500);
42
+ }
43
+ }
@@ -0,0 +1,38 @@
1
+ export function paginatedResponse<T>(
2
+ message: string,
3
+ items: T[],
4
+ page: number,
5
+ limit: number,
6
+ totalItems: number,
7
+ ): {
8
+ success: true;
9
+ message: string;
10
+ data: {
11
+ items: T[];
12
+ pagination: {
13
+ page: number;
14
+ limit: number;
15
+ totalItems: number;
16
+ totalPages: number;
17
+ hasNextPage: boolean;
18
+ hasPreviousPage: boolean;
19
+ };
20
+ };
21
+ } {
22
+ const totalPages = Math.ceil(totalItems / limit);
23
+ return {
24
+ success: true as const,
25
+ message,
26
+ data: {
27
+ items,
28
+ pagination: {
29
+ page,
30
+ limit,
31
+ totalItems,
32
+ totalPages,
33
+ hasNextPage: page < totalPages,
34
+ hasPreviousPage: page > 1,
35
+ },
36
+ },
37
+ };
38
+ }
@@ -0,0 +1,17 @@
1
+ export function successResponse<T>(message: string, data: T): {
2
+ success: true;
3
+ message: string;
4
+ data: T;
5
+ } {
6
+ return { success: true as const, message, data };
7
+ }
8
+
9
+ export function errorResponse(code: string, message: string): {
10
+ success: false;
11
+ error: { code: string; message: string };
12
+ } {
13
+ return {
14
+ success: false as const,
15
+ error: { code, message },
16
+ };
17
+ }
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod';
2
+
3
+ export const PaginationSchema = z.object({
4
+ page: z.coerce.number().int().min(1).default(1),
5
+ limit: z.coerce.number().int().min(1).max(100).default(10),
6
+ });
7
+
8
+ export type PaginationInput = z.infer<typeof PaginationSchema>;
9
+
10
+ export function calculateOffset(page: number, limit: number): number {
11
+ return (page - 1) * limit;
12
+ }
@@ -0,0 +1,26 @@
1
+ import type { FastifyRequest } from 'fastify';
2
+
3
+ export type UserRole = 'USER' | 'ADMIN';
4
+
5
+ export interface JwtPayload {
6
+ userId: string;
7
+ role: UserRole;
8
+ }
9
+
10
+ declare module '@fastify/jwt' {
11
+ interface FastifyJWT {
12
+ payload: JwtPayload;
13
+ user: JwtPayload;
14
+ }
15
+ }
16
+
17
+ declare module 'fastify' {
18
+ interface FastifyRequest {
19
+ user: JwtPayload;
20
+ startTime?: number; // Added for duration calculation
21
+ }
22
+ }
23
+
24
+ export interface AuthenticatedRequest extends FastifyRequest {
25
+ user: JwtPayload;
26
+ }
@@ -1,38 +1,74 @@
1
- import { vi, beforeAll } from 'vitest';
2
-
3
- // Pre-fill process.env with required values for tests
4
- // This ensures zod validation in src/config/env.ts passes
5
- process.env['DATABASE_URL'] = process.env['DATABASE_URL'] || 'mysql://root:root@localhost:3306/test_db';
6
- process.env['JWT_SECRET'] = process.env['JWT_SECRET'] || 'test-secret-must-be-at-least-32-chars-long-for-validation';
7
- process.env['NODE_ENV'] = 'test';
8
-
9
- // Global mocks if needed
10
- console.log('Test setup loaded');
11
-
12
- // Mock database globally to prevent connection attempts
13
- vi.mock('@/libs/db', () => ({
14
- prisma: {
15
- $connect: vi.fn(),
16
- $disconnect: vi.fn(),
17
- $on: vi.fn(),
18
- user: {
19
- findUnique: vi.fn(),
20
- create: vi.fn(),
21
- },
22
- session: {
23
- create: vi.fn(),
24
- findUnique: vi.fn(),
25
- },
26
- $queryRaw: vi.fn(),
27
- },
28
- }));
29
-
30
- // Mock redis globally
31
- vi.mock('@/libs/redis', () => ({
32
- redis: {
33
- get: vi.fn(),
34
- set: vi.fn(),
35
- del: vi.fn(),
36
- ping: vi.fn(),
37
- },
38
- }));
1
+ import { vi } from 'vitest';
2
+
3
+ // Mock environment variables for testing
4
+ process.env.NODE_ENV = 'test';
5
+ process.env.DATABASE_URL = 'mysql://test:test@localhost:3306/test_db';
6
+ process.env.REDIS_URL = 'redis://localhost:6379';
7
+ process.env.JWT_SECRET = 'test-jwt-secret-must-be-at-least-32-characters-long';
8
+ process.env.JWT_ACCESS_EXPIRY = '15m';
9
+ process.env.JWT_REFRESH_EXPIRY = '7d';
10
+ process.env.PORT = '3000';
11
+ process.env.HOST = '0.0.0.0';
12
+
13
+ // Test user data
14
+ export const testUsers = {
15
+ validUser: {
16
+ id: 'test-user-id-1',
17
+ email: 'test@example.com',
18
+ password: '$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5GyYiIJU6u1Em', // hashed "Password123!"
19
+ firstName: 'Test',
20
+ lastName: 'User',
21
+ avatarUrl: null,
22
+ role: 'USER' as const,
23
+ isActive: true,
24
+ deletedAt: null,
25
+ failedLoginAttempts: 0,
26
+ lockedUntil: null,
27
+ createdAt: new Date('2024-01-01T00:00:00Z'),
28
+ updatedAt: new Date('2024-01-01T00:00:00Z'),
29
+ },
30
+ inactiveUser: {
31
+ id: 'test-user-id-2',
32
+ email: 'inactive@example.com',
33
+ password: '$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5GyYiIJU6u1Em',
34
+ firstName: 'Inactive',
35
+ lastName: 'User',
36
+ avatarUrl: null,
37
+ role: 'USER' as const,
38
+ isActive: false,
39
+ deletedAt: null,
40
+ failedLoginAttempts: 0,
41
+ lockedUntil: null,
42
+ createdAt: new Date('2024-01-01T00:00:00Z'),
43
+ updatedAt: new Date('2024-01-01T00:00:00Z'),
44
+ },
45
+ adminUser: {
46
+ id: 'test-user-id-3',
47
+ email: 'admin@example.com',
48
+ password: '$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5GyYiIJU6u1Em',
49
+ firstName: 'Admin',
50
+ lastName: 'User',
51
+ avatarUrl: null,
52
+ role: 'ADMIN' as const,
53
+ isActive: true,
54
+ deletedAt: null,
55
+ failedLoginAttempts: 0,
56
+ lockedUntil: null,
57
+ createdAt: new Date('2024-01-01T00:00:00Z'),
58
+ updatedAt: new Date('2024-01-01T00:00:00Z'),
59
+ },
60
+ };
61
+
62
+ // Test refresh token data
63
+ export const testRefreshToken = {
64
+ id: 'test-token-id-1',
65
+ token: 'test-refresh-token-uuid',
66
+ userId: testUsers.validUser.id,
67
+ expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days from now
68
+ createdAt: new Date(),
69
+ };
70
+
71
+ // Reset all mocks before each test
72
+ export const resetMocks = (): void => {
73
+ vi.clearAllMocks();
74
+ };
@@ -1,89 +1,27 @@
1
- {
2
- "compilerOptions": {
3
- /* Language and Environment */
4
- "target": "ES2022",
5
- "lib": [
6
- "ES2022"
7
- ],
8
- "module": "ES2022",
9
- "moduleResolution": "bundler",
10
- /* Emit */
11
- "outDir": "./dist",
12
- "rootDir": "./src",
13
- "declaration": true,
14
- "declarationMap": true,
15
- "sourceMap": true,
16
- "removeComments": true,
17
- "noEmit": false,
18
- /* Interop Constraints */
19
- "esModuleInterop": true,
20
- "allowSyntheticDefaultImports": true,
21
- "forceConsistentCasingInFileNames": true,
22
- "resolveJsonModule": true,
23
- "isolatedModules": true,
24
- /* Type Checking - STRICT MODE ENABLED */
25
- "strict": true,
26
- "noImplicitAny": true,
27
- "strictNullChecks": true,
28
- "strictFunctionTypes": true,
29
- "strictBindCallApply": true,
30
- "strictPropertyInitialization": true,
31
- "noImplicitThis": true,
32
- "alwaysStrict": true,
33
- "noUnusedLocals": true,
34
- "noUnusedParameters": true,
35
- "noImplicitReturns": true,
36
- "noFallthroughCasesInSwitch": true,
37
- "noUncheckedIndexedAccess": true,
38
- "noImplicitOverride": true,
39
- "noPropertyAccessFromIndexSignature": true,
40
- /* Completeness */
41
- "skipLibCheck": true,
42
- "skipDefaultLibCheck": true,
43
- /* Type Resolution */
44
- "types": [],
45
- /* Performance */
46
- "incremental": true,
47
- /* Path Mapping */
48
- "baseUrl": ".",
49
- "paths": {
50
- "@/*": [
51
- "src/*"
52
- ],
53
- "@/config/*": [
54
- "src/config/*"
55
- ],
56
- "@/libs/*": [
57
- "src/libs/*"
58
- ],
59
- "@/modules/*": [
60
- "src/modules/*"
61
- ],
62
- "@/utils/*": [
63
- "src/utils/*"
64
- ],
65
- "@/types/*": [
66
- "src/types/*"
67
- ]
68
- },
69
- /* Advanced */
70
- "experimentalDecorators": true,
71
- "emitDecoratorMetadata": true
72
- },
73
- "include": [
74
- "src/**/*"
75
- ],
76
- "exclude": [
77
- "node_modules",
78
- "dist",
79
- "**/*.test.ts",
80
- "**/*.spec.ts"
81
- ],
82
- "ts-node": {
83
- "require": [
84
- "tsconfig-paths/register"
85
- ],
86
- "transpileOnly": true,
87
- "files": true
88
- }
89
- }
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "resolveJsonModule": true,
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "sourceMap": true,
14
+ "outDir": "dist",
15
+ "rootDir": "src",
16
+ "baseUrl": ".",
17
+ "paths": {
18
+ "@/*": ["./src/*"],
19
+ "@modules/*": ["./src/modules/*"],
20
+ "@libs/*": ["./src/libs/*"],
21
+ "@config/*": ["./src/config/*"],
22
+ "@shared/*": ["./src/shared/*"]
23
+ }
24
+ },
25
+ "include": ["src/**/*"],
26
+ "exclude": ["node_modules", "dist"]
27
+ }