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,89 +0,0 @@
1
- import { FastifyError, FastifyReply, FastifyRequest } from 'fastify';
2
- import { ZodError } from 'zod';
3
- import { Prisma } from '@prisma/client';
4
- import jsonwebtoken from 'jsonwebtoken';
5
- import { env } from '../config/env.js';
6
- import { AppError, InternalError } from '../utils/errors.js';
7
-
8
- export function setupErrorHandler(error: FastifyError, request: FastifyRequest, reply: FastifyReply) {
9
- // 1. Log the error internally
10
- request.log.error({
11
- err: error,
12
- requestId: request.id,
13
- userId: request.user?.userId,
14
- path: request.url,
15
- });
16
-
17
- // 2. Handle known AppErrors
18
- if (error instanceof AppError) {
19
- return reply.status(error.statusCode).send({
20
- success: false,
21
- error: {
22
- code: error.code,
23
- message: error.message,
24
- },
25
- });
26
- }
27
-
28
- // 3. Handle Zod Validation Errors
29
- if (error instanceof ZodError) {
30
- return reply.status(400).send({
31
- success: false,
32
- error: {
33
- code: 'VALIDATION_ERROR',
34
- message: error.errors[0]?.message || 'Invalid input data',
35
- details: error.errors,
36
- },
37
- });
38
- }
39
-
40
- // 4. Handle Prisma Database Errors
41
- if (error instanceof Prisma.PrismaClientKnownRequestError) {
42
- // P2002: Unique constraint failed
43
- if (error.code === 'P2002') {
44
- return reply.status(409).send({
45
- success: false,
46
- error: { code: 'CONFLICT', message: 'Resource already exists' },
47
- });
48
- }
49
- // P2025: Record not found
50
- if (error.code === 'P2025') {
51
- return reply.status(404).send({
52
- success: false,
53
- error: { code: 'NOT_FOUND', message: 'Resource not found' },
54
- });
55
- }
56
- }
57
-
58
- // 5. Handle JWT Errors explicitly
59
- if (error instanceof jsonwebtoken.TokenExpiredError) {
60
- return reply.status(401).send({
61
- success: false,
62
- error: { code: 'UNAUTHORIZED', message: 'Token expired' },
63
- });
64
- }
65
-
66
- if (error instanceof jsonwebtoken.JsonWebTokenError) {
67
- return reply.status(401).send({
68
- success: false,
69
- error: { code: 'UNAUTHORIZED', message: 'Invalid token' },
70
- });
71
- }
72
-
73
- // 6. Handle Rate Limit Errors
74
- if (reply.statusCode === 429) {
75
- return reply.send(error); // Keep custom error from rate-limit plugin
76
- }
77
-
78
- // 7. Fallback: Internal Server Error
79
- const internalError = new InternalError();
80
- return reply.status(500).send({
81
- success: false,
82
- error: {
83
- code: internalError.code,
84
- message: env.NODE_ENV === 'production'
85
- ? 'An unexpected error occurred'
86
- : error.message,
87
- },
88
- });
89
- }
@@ -1,79 +0,0 @@
1
- /**
2
- * Background Job Queues
3
- *
4
- * BullMQ queue configuration for asynchronous task processing.
5
- *
6
- * @see /mnt/project/10-background-jobs-v2.md
7
- */
8
-
9
- import { Queue } from 'bullmq';
10
- import { redis } from './redis.js';
11
- import logger from './logger.js';
12
-
13
- /**
14
- * File Processing Queue
15
- *
16
- * Handles file processing jobs (thumbnails, compression, format conversion, etc.)
17
- *
18
- * Job Types:
19
- * - generate-thumbnail
20
- * - compress-image
21
- * - convert-format
22
- * - process-upload
23
- */
24
- export const fileQueue = new Queue('files', {
25
- connection: redis as any,
26
-
27
- defaultJobOptions: {
28
- // Retry configuration
29
- attempts: 3,
30
- backoff: {
31
- type: 'exponential',
32
- delay: 2000, // Start with 2 seconds, then 4s, 8s
33
- },
34
-
35
- // Job cleanup
36
- removeOnComplete: {
37
- age: 3600, // Remove completed jobs after 1 hour
38
- count: 100, // Keep max 100 completed jobs
39
- },
40
- removeOnFail: {
41
- age: 86400, // Keep failed jobs for 24 hours
42
- },
43
-
44
-
45
- },
46
- });
47
-
48
- /**
49
- * Queue Event Handlers
50
- */
51
- fileQueue.on('error', (error: Error) => {
52
- // Suppress ECONNREFUSED logs during reconnection attempts to reduce noise
53
- if (error.message.includes('ECONNREFUSED') || error.stack?.includes('ECONNREFUSED')) {
54
- return;
55
- }
56
-
57
- logger.error(
58
- {
59
- queue: 'files',
60
- error: error.message,
61
- stack: error.stack,
62
- },
63
- 'File queue error'
64
- );
65
- });
66
-
67
- fileQueue.on('waiting', (job) => {
68
- logger.debug(
69
- {
70
- queue: 'files',
71
- jobId: job.id,
72
- },
73
- 'Job waiting in queue'
74
- );
75
- });
76
-
77
- logger.info('File queue initialized');
78
-
79
-
@@ -1,122 +0,0 @@
1
- /**
2
- * Admin Controller
3
- * HTTP request handlers for admin operations
4
- */
5
-
6
- import type { FastifyRequest, FastifyReply } from 'fastify';
7
- import { successResponse, paginatedResponse } from '../../utils/response.js';
8
- import { BadRequestError } from '../../utils/errors.js';
9
- import * as adminService from './admin.service.js';
10
- import type { ListUsersInput, UserIdInput, ChangeRoleInput } from './admin.schemas.js';
11
-
12
- /**
13
- * GET /admin/users
14
- * List all users with pagination
15
- */
16
- export async function listAllUsers(
17
- request: FastifyRequest<{ Querystring: ListUsersInput }>,
18
- reply: FastifyReply
19
- ): Promise<FastifyReply> {
20
- const { page, limit } = request.query;
21
-
22
- const { users, totalCount } = await adminService.listAllUsers(page, limit);
23
-
24
- return reply.status(200).send(
25
- paginatedResponse('Users retrieved successfully', users, page, limit, totalCount)
26
- );
27
- }
28
-
29
- /**
30
- * GET /admin/users/:id
31
- * Get user by ID
32
- */
33
- export async function getUserById(
34
- request: FastifyRequest<{ Params: UserIdInput }>,
35
- reply: FastifyReply
36
- ): Promise<FastifyReply> {
37
- const { id } = request.params;
38
-
39
- const user = await adminService.getUserById(id);
40
-
41
- return reply.status(200).send(
42
- successResponse('User retrieved successfully', user)
43
- );
44
- }
45
-
46
- /**
47
- * DELETE /admin/users/:id
48
- * Delete user
49
- */
50
- export async function deleteUser(
51
- request: FastifyRequest<{ Params: UserIdInput }>,
52
- reply: FastifyReply
53
- ): Promise<FastifyReply> {
54
- const { id } = request.params;
55
-
56
- if (request.user?.userId === id) {
57
- throw new BadRequestError('Cannot delete your own account');
58
- }
59
-
60
- await adminService.deleteUser(id);
61
-
62
- return reply.status(200).send(
63
- successResponse('User deleted successfully', null)
64
- );
65
- }
66
-
67
- /**
68
- * POST /admin/users/:id/change-role
69
- * Change user role
70
- */
71
- export async function changeUserRole(
72
- request: FastifyRequest<{
73
- Params: UserIdInput;
74
- Body: ChangeRoleInput;
75
- }>,
76
- reply: FastifyReply
77
- ): Promise<FastifyReply> {
78
- const { id } = request.params;
79
- const { role } = request.body;
80
-
81
- if (request.user?.userId === id) {
82
- throw new BadRequestError('Cannot change your own role');
83
- }
84
-
85
- const updated = await adminService.changeUserRole(id, role);
86
-
87
- return reply.status(200).send(
88
- successResponse('User role updated successfully', updated)
89
- );
90
- }
91
-
92
- /**
93
- * POST /admin/users/:id/verify-email
94
- * Manually verify user email
95
- */
96
- export async function verifyUserEmail(
97
- request: FastifyRequest<{ Params: UserIdInput }>,
98
- reply: FastifyReply
99
- ): Promise<FastifyReply> {
100
- const { id } = request.params;
101
-
102
- await adminService.verifyUserEmail(id);
103
-
104
- return reply.status(200).send(
105
- successResponse('User email verified successfully', null)
106
- );
107
- }
108
-
109
- /**
110
- * GET /admin/stats
111
- * Get system statistics
112
- */
113
- export async function getSystemStats(
114
- request: FastifyRequest,
115
- reply: FastifyReply
116
- ): Promise<FastifyReply> {
117
- const stats = await adminService.getSystemStats();
118
-
119
- return reply.status(200).send(
120
- successResponse('System statistics retrieved successfully', stats)
121
- );
122
- }
@@ -1,62 +0,0 @@
1
- import { FastifyInstance } from 'fastify';
2
- import * as adminController from './admin.controller.js';
3
-
4
- /**
5
- * Admin Routes
6
- * All routes require ADMIN role
7
- */
8
- export async function adminRoutes(app: FastifyInstance) {
9
- /**
10
- * GET /admin/users
11
- * List all users with pagination
12
- */
13
- app.get('/users', {
14
- preHandler: [app.authenticate, app.requireAdmin()],
15
- handler: adminController.listAllUsers,
16
- });
17
-
18
- /**
19
- * GET /admin/users/:id
20
- * Get user details
21
- */
22
- app.get('/users/:id', {
23
- preHandler: [app.authenticate, app.requireAdmin()],
24
- handler: adminController.getUserById,
25
- });
26
-
27
- /**
28
- * DELETE /admin/users/:id
29
- * Delete user
30
- */
31
- app.delete('/users/:id', {
32
- preHandler: [app.authenticate, app.requireAdmin()],
33
- handler: adminController.deleteUser,
34
- });
35
-
36
- /**
37
- * POST /admin/users/:id/change-role
38
- * Change user role
39
- */
40
- app.post('/users/:id/change-role', {
41
- preHandler: [app.authenticate, app.requireAdmin()],
42
- handler: adminController.changeUserRole,
43
- });
44
-
45
- /**
46
- * POST /admin/users/:id/verify-email
47
- * Manually verify user email
48
- */
49
- app.post('/users/:id/verify-email', {
50
- preHandler: [app.authenticate, app.requireAdmin()],
51
- handler: adminController.verifyUserEmail,
52
- });
53
-
54
- /**
55
- * GET /admin/stats
56
- * Get system statistics
57
- */
58
- app.get('/stats', {
59
- preHandler: [app.authenticate, app.requireAdmin()],
60
- handler: adminController.getSystemStats,
61
- });
62
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * Admin Module Validation Schemas
3
- */
4
-
5
- import { z } from 'zod';
6
-
7
- /**
8
- * List users query parameters
9
- */
10
- export const ListUsersSchema = z.object({
11
- page: z.coerce.number().int().min(1).default(1),
12
- limit: z.coerce.number().int().min(1).max(100).default(10),
13
- });
14
-
15
- export type ListUsersInput = z.infer<typeof ListUsersSchema>;
16
-
17
- /**
18
- * User ID parameter
19
- */
20
- export const UserIdSchema = z.object({
21
- id: z.string().uuid('Invalid user ID format'),
22
- });
23
-
24
- export type UserIdInput = z.infer<typeof UserIdSchema>;
25
-
26
- /**
27
- * Change role request body
28
- */
29
- export const ChangeRoleSchema = z.object({
30
- role: z.enum(['USER', 'ADMIN'], {
31
- errorMap: () => ({ message: 'Role must be USER or ADMIN' }),
32
- }),
33
- });
34
-
35
- export type ChangeRoleInput = z.infer<typeof ChangeRoleSchema>;
@@ -1,167 +0,0 @@
1
- /**
2
- * Admin Service
3
- * Business logic for admin operations
4
- */
5
-
6
- import { prisma } from '../../libs/db.js';
7
- import { NotFoundError, BadRequestError } from '../../utils/errors.js';
8
- import logger from '../../libs/logger.js';
9
- import type { UserRole } from '@prisma/client';
10
-
11
- /**
12
- * List all users with pagination
13
- */
14
- export async function listAllUsers(page: number = 1, limit: number = 10) {
15
- const skip = (page - 1) * limit;
16
-
17
- const [users, totalCount] = await Promise.all([
18
- prisma.user.findMany({
19
- select: {
20
- id: true,
21
- email: true,
22
- name: true,
23
- role: true,
24
- emailVerified: true,
25
- createdAt: true,
26
- updatedAt: true,
27
- },
28
- skip,
29
- take: limit,
30
- orderBy: { createdAt: 'desc' },
31
- }),
32
- prisma.user.count(),
33
- ]);
34
-
35
- return { users, totalCount };
36
- }
37
-
38
- /**
39
- * Get user by ID
40
- */
41
- export async function getUserById(userId: string) {
42
- const user = await prisma.user.findUnique({
43
- where: { id: userId },
44
- select: {
45
- id: true,
46
- email: true,
47
- name: true,
48
- role: true,
49
- emailVerified: true,
50
- createdAt: true,
51
- updatedAt: true,
52
- },
53
- });
54
-
55
- if (!user) {
56
- throw new NotFoundError('User not found');
57
- }
58
-
59
- return user;
60
- }
61
-
62
- /**
63
- * Delete user
64
- */
65
- export async function deleteUser(userId: string) {
66
- const user = await prisma.user.findUnique({
67
- where: { id: userId },
68
- });
69
-
70
- if (!user) {
71
- throw new NotFoundError('User not found');
72
- }
73
-
74
- await prisma.user.delete({
75
- where: { id: userId },
76
- });
77
-
78
- logger.info({ userId }, 'User deleted by admin');
79
- }
80
-
81
- /**
82
- * Change user role
83
- */
84
- export async function changeUserRole(userId: string, newRole: UserRole) {
85
- const user = await prisma.user.findUnique({
86
- where: { id: userId },
87
- });
88
-
89
- if (!user) {
90
- throw new NotFoundError('User not found');
91
- }
92
-
93
- const updated = await prisma.user.update({
94
- where: { id: userId },
95
- data: { role: newRole },
96
- select: {
97
- id: true,
98
- email: true,
99
- role: true,
100
- },
101
- });
102
-
103
- logger.info({ userId, oldRole: user.role, newRole }, 'User role changed by admin');
104
-
105
- return updated;
106
- }
107
-
108
- /**
109
- * Manually verify user email
110
- */
111
- export async function verifyUserEmail(userId: string) {
112
- const user = await prisma.user.findUnique({
113
- where: { id: userId },
114
- });
115
-
116
- if (!user) {
117
- throw new NotFoundError('User not found');
118
- }
119
-
120
- if (user.emailVerified) {
121
- throw new BadRequestError('Email already verified');
122
- }
123
-
124
- await prisma.user.update({
125
- where: { id: userId },
126
- data: { emailVerified: true },
127
- });
128
-
129
- logger.info({ userId }, 'User email verified by admin');
130
- }
131
-
132
- /**
133
- * Get system statistics
134
- */
135
- export async function getSystemStats() {
136
- const [
137
- totalUsers,
138
- totalAdmins,
139
- totalResources,
140
- verifiedUsers,
141
- usersToday,
142
- ] = await Promise.all([
143
- prisma.user.count(),
144
- prisma.user.count({ where: { role: 'ADMIN' } }),
145
- prisma.resource.count(),
146
- prisma.user.count({ where: { emailVerified: true } }),
147
- prisma.user.count({
148
- where: {
149
- createdAt: {
150
- gte: new Date(new Date().setHours(0, 0, 0, 0)),
151
- },
152
- },
153
- }),
154
- ]);
155
-
156
- return {
157
- users: {
158
- total: totalUsers,
159
- admins: totalAdmins,
160
- verified: verifiedUsers,
161
- registeredToday: usersToday,
162
- },
163
- resources: {
164
- total: totalResources,
165
- },
166
- };
167
- }
@@ -1,150 +0,0 @@
1
- import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest';
2
- import buildApp from '../../app';
3
- import * as authService from './auth.service';
4
- import type { FastifyInstance } from 'fastify';
5
-
6
- // Mock auth service to avoid real DB calls
7
- vi.mock('./auth.service');
8
-
9
- // Mock db and redis to prevent connection attempts during integration tests
10
- vi.mock('@/libs/db', () => ({
11
- prisma: {
12
- $connect: vi.fn(),
13
- $disconnect: vi.fn(),
14
- $on: vi.fn(),
15
- user: { findUnique: vi.fn() },
16
- $queryRaw: vi.fn(),
17
- },
18
- }));
19
- vi.mock('@/libs/redis', () => ({
20
- redis: {
21
- get: vi.fn(),
22
- set: vi.fn(),
23
- setex: vi.fn(),
24
- del: vi.fn(),
25
- ping: vi.fn().mockResolvedValue('PONG'),
26
- call: vi.fn(),
27
- on: vi.fn(),
28
- off: vi.fn(),
29
- quit: vi.fn(),
30
- incr: vi.fn().mockResolvedValue(1),
31
- expire: vi.fn(),
32
- ttl: vi.fn().mockResolvedValue(-1),
33
- },
34
- }));
35
-
36
- vi.mock('@/libs/logger', () => ({
37
- default: {
38
- info: vi.fn(),
39
- error: vi.fn(),
40
- warn: vi.fn(),
41
- debug: vi.fn(),
42
- fatal: vi.fn(),
43
- child: vi.fn().mockReturnThis(),
44
- },
45
- }));
46
-
47
- /**
48
- * Integration tests are skipped because they require full Fastify app initialization
49
- * with all plugins (rate-limit, etc.) which need complex mocking.
50
- *
51
- * For proper integration testing, use a test database and real dependencies.
52
- * Unit tests (auth.service.test.ts, rbac.middleware.test.ts) provide adequate coverage.
53
- */
54
- describe.skip('Auth Integration', () => {
55
- let app: FastifyInstance;
56
-
57
- beforeAll(async () => {
58
- try {
59
- app = await buildApp();
60
- await app.ready();
61
- } catch (error) {
62
- console.error('Failed to build app:', error);
63
- throw error;
64
- }
65
- });
66
-
67
- afterAll(async () => {
68
- if (app) {
69
- await app.close();
70
- }
71
- });
72
-
73
- describe('POST /auth/register', () => {
74
- it('should register a user', async () => {
75
- const payload = {
76
- email: 'newuser@example.com',
77
- password: 'Password123!',
78
- name: 'New User',
79
- };
80
-
81
- const mockResponse = {
82
- user: {
83
- id: '123',
84
- email: payload.email,
85
- name: payload.name,
86
- role: 'USER',
87
- emailVerified: false,
88
- createdAt: new Date(),
89
- updatedAt: new Date(),
90
- },
91
- tokens: {
92
- accessToken: 'access-token',
93
- refreshToken: 'refresh-token',
94
- expiresIn: 3600,
95
- },
96
- };
97
-
98
- vi.mocked(authService.register).mockResolvedValue(mockResponse as any);
99
-
100
- const response = await app.inject({
101
- method: 'POST',
102
- url: '/api/v1/auth/register',
103
- payload,
104
- });
105
-
106
- expect(response.statusCode).toBe(201);
107
- expect(response.json().success).toBe(true);
108
- expect(response.json().data.user.email).toBe(payload.email);
109
- });
110
-
111
- it('should validate input', async () => {
112
- const response = await app.inject({
113
- method: 'POST',
114
- url: '/api/v1/auth/register',
115
- payload: {
116
- email: 'invalid-email',
117
- password: 'short',
118
- },
119
- });
120
-
121
- expect(response.statusCode).toBe(400);
122
- expect(response.json().success).toBe(false);
123
- });
124
- });
125
-
126
- describe('POST /auth/login', () => {
127
- it('should login a user', async () => {
128
- const payload = {
129
- email: 'user@example.com',
130
- password: 'Password123!',
131
- };
132
-
133
- const mockResponse = {
134
- user: { id: '123', email: payload.email, role: 'USER' },
135
- tokens: { accessToken: 'token', refreshToken: 'refresh' },
136
- };
137
-
138
- vi.mocked(authService.login).mockResolvedValue(mockResponse as any);
139
-
140
- const response = await app.inject({
141
- method: 'POST',
142
- url: '/api/v1/auth/login',
143
- payload,
144
- });
145
-
146
- expect(response.statusCode).toBe(200);
147
- expect(response.json().success).toBe(true);
148
- });
149
- });
150
- });