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,119 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import * as authService from './auth.service';
3
- import * as authRepo from './auth.repo';
4
- import bcrypt from 'bcryptjs';
5
- import jwt from 'jsonwebtoken';
6
- import { env } from '../../config/env.js';
7
- import { ConflictError, UnauthorizedError } from '../../utils/errors.js';
8
-
9
- // Mock dependencies
10
- vi.mock('./auth.repo');
11
- vi.mock('bcryptjs');
12
- vi.mock('jsonwebtoken');
13
- vi.mock('@/libs/logger', () => ({
14
- default: {
15
- info: vi.fn(),
16
- error: vi.fn(),
17
- warn: vi.fn(),
18
- },
19
- }));
20
-
21
- describe('Auth Service', () => {
22
- beforeEach(() => {
23
- vi.resetAllMocks();
24
- // Setup default env mocks if needed
25
- env.JWT_SECRET = 'test-secret';
26
- env.JWT_ACCESS_EXPIRATION = '15m';
27
- env.JWT_REFRESH_EXPIRATION = '7d';
28
- env.JWT_ISSUER = 'test-issuer';
29
- });
30
-
31
- describe('register', () => {
32
- const registerData = {
33
- email: 'test@example.com',
34
- password: 'Password123!',
35
- name: 'Test User',
36
- };
37
-
38
- it('should successfully register a new user', async () => {
39
- // Mocks
40
- vi.mocked(authRepo.findUserByEmail).mockResolvedValue(null);
41
- vi.mocked(bcrypt.hash).mockResolvedValue('hashed_password' as any);
42
- vi.mocked(authRepo.createUser).mockResolvedValue({
43
- id: 'user-123',
44
- email: registerData.email,
45
- name: registerData.name,
46
- role: 'USER',
47
- emailVerified: false,
48
- createdAt: new Date(),
49
- updatedAt: new Date(),
50
- });
51
- vi.mocked(jwt.sign).mockReturnValue('mock_token' as any);
52
- vi.mocked(authRepo.createSession).mockResolvedValue({} as any);
53
-
54
- // Execute
55
- const result = await authService.register(registerData);
56
-
57
- // Assert
58
- expect(result).toBeDefined();
59
- expect(result.user.email).toBe(registerData.email);
60
- expect(result.tokens.accessToken).toBe('mock_token');
61
- expect(authRepo.createUser).toHaveBeenCalled();
62
- expect(authRepo.createSession).toHaveBeenCalled();
63
- });
64
-
65
- it('should throw ConflictError if email already exists', async () => {
66
- // Mocks
67
- vi.mocked(authRepo.findUserByEmail).mockResolvedValue({ id: 'existing' } as any);
68
-
69
- // Execute & Assert
70
- await expect(authService.register(registerData))
71
- .rejects
72
- .toThrow(ConflictError);
73
- });
74
- });
75
-
76
- describe('login', () => {
77
- const loginData = {
78
- email: 'test@example.com',
79
- password: 'Password123!',
80
- };
81
-
82
- it('should successfully login user', async () => {
83
- // Mocks
84
- vi.mocked(authRepo.findUserByEmailWithPassword).mockResolvedValue({
85
- id: 'user-123',
86
- email: loginData.email,
87
- password: 'hashed_password', // Match what bcrypt.compare expects
88
- role: 'USER',
89
- } as any);
90
- vi.mocked(bcrypt.compare).mockResolvedValue(true as any);
91
- vi.mocked(authRepo.findUserById).mockResolvedValue({
92
- id: 'user-123',
93
- email: loginData.email,
94
- role: 'USER',
95
- } as any);
96
- vi.mocked(jwt.sign).mockReturnValue('mock_token' as any);
97
- vi.mocked(authRepo.createSession).mockResolvedValue({} as any);
98
-
99
- const result = await authService.login(loginData);
100
-
101
- expect(result).toBeDefined();
102
- expect(result.user.id).toBe('user-123');
103
- expect(result.tokens.accessToken).toBeDefined();
104
- });
105
-
106
- it('should throw UnauthorizedError on invalid password', async () => {
107
- // Mocks
108
- vi.mocked(authRepo.findUserByEmailWithPassword).mockResolvedValue({
109
- id: 'user-123',
110
- password: 'hashed_password',
111
- } as any);
112
- vi.mocked(bcrypt.compare).mockResolvedValue(false as any);
113
-
114
- await expect(authService.login(loginData))
115
- .rejects
116
- .toThrow(UnauthorizedError);
117
- });
118
- });
119
- });
@@ -1,97 +0,0 @@
1
- /**
2
- * Authentication Types
3
- *
4
- * TypeScript types and interfaces for authentication module.
5
- * These types are used throughout the auth flow.
6
- */
7
-
8
- /**
9
- * User Role Enum
10
- */
11
- export type UserRole = 'USER' | 'ADMIN';
12
-
13
- /**
14
- * User object (without sensitive fields like password)
15
- *
16
- * This is the safe user representation returned to clients.
17
- */
18
- export interface User {
19
- id: string;
20
- email: string;
21
- name: string | null;
22
- role: UserRole;
23
- emailVerified: boolean;
24
- createdAt: Date;
25
- updatedAt: Date;
26
- }
27
-
28
- /**
29
- * Login Request Payload
30
- */
31
- export interface LoginRequest {
32
- email: string;
33
- password: string;
34
- }
35
-
36
- /**
37
- * Register Request Payload
38
- */
39
- export interface RegisterRequest {
40
- email: string;
41
- password: string;
42
- name: string;
43
- }
44
-
45
- /**
46
- * Token Response
47
- *
48
- * Contains JWT tokens and expiration info.
49
- */
50
- export interface TokenResponse {
51
- accessToken: string;
52
- refreshToken: string;
53
- expiresIn: number; // Seconds until access token expires
54
- }
55
-
56
- /**
57
- * Authentication Response
58
- *
59
- * Complete response after successful login/register.
60
- * Contains user info and tokens.
61
- */
62
- export interface AuthResponse {
63
- user: User;
64
- tokens: TokenResponse;
65
- }
66
-
67
- /**
68
- * Refresh Token Request Payload
69
- */
70
- export interface RefreshTokenRequest {
71
- refreshToken: string;
72
- }
73
-
74
- /**
75
- * JWT Payload
76
- *
77
- * Data stored inside the JWT access token.
78
- */
79
- export interface JwtPayload {
80
- userId: string;
81
- email: string;
82
- role: UserRole;
83
- iat?: number; // Issued at
84
- exp?: number; // Expiration
85
- }
86
-
87
- /**
88
- * JWT Refresh Payload
89
- *
90
- * Data stored inside the JWT refresh token.
91
- */
92
- export interface JwtRefreshPayload {
93
- userId: string;
94
- sessionId: string;
95
- iat?: number;
96
- exp?: number;
97
- }
@@ -1,218 +0,0 @@
1
- /**
2
- * Resources Controller
3
- *
4
- * HTTP request handlers for resources endpoints.
5
- * Controllers should ONLY handle HTTP concerns, no business logic.
6
- *
7
- * @see /mnt/project/02-general-rules.md
8
- * @see /mnt/project/04-pagination.md
9
- * @see /mnt/project/06-response-handling.md
10
- */
11
-
12
- import type { FastifyRequest, FastifyReply } from 'fastify';
13
- import { successResponse } from '../../utils/response.js';
14
- import { paginatedResponse } from '../../utils/pagination.js';
15
- import * as resourceService from './resources.service.js';
16
- import {
17
- CreateResourceSchema,
18
- UpdateResourceSchema,
19
- ResourceFiltersSchema,
20
- PaginationSchema,
21
- } from './resources.schemas.js';
22
- import type {
23
- CreateResourceInput,
24
- UpdateResourceInput,
25
- ResourceFiltersInput,
26
- PaginationInput,
27
- } from './resources.schemas.js';
28
-
29
- /**
30
- * List resources with filters and pagination
31
- *
32
- * @route GET /resources
33
- * @access Public
34
- */
35
- export async function listResources(
36
- request: FastifyRequest<{
37
- Querystring: ResourceFiltersInput & PaginationInput;
38
- }>,
39
- reply: FastifyReply
40
- ): Promise<FastifyReply> {
41
- // Parse and validate query parameters
42
- const filters = ResourceFiltersSchema.parse({
43
- status: request.query.status,
44
- minPrice: request.query.minPrice,
45
- maxPrice: request.query.maxPrice,
46
- ownerId: request.query.ownerId,
47
- search: request.query.search,
48
- });
49
-
50
- const pagination = PaginationSchema.parse({
51
- page: request.query.page,
52
- limit: request.query.limit,
53
- });
54
-
55
- // Call service
56
- const { items, totalItems } = await resourceService.getResources(
57
- filters,
58
- pagination.page,
59
- pagination.limit
60
- );
61
-
62
- // Return paginated response (EXACT format from 04-pagination.md)
63
- return reply.status(200).send(
64
- paginatedResponse(
65
- 'Resources retrieved successfully',
66
- items,
67
- pagination.page,
68
- pagination.limit,
69
- totalItems
70
- )
71
- );
72
- }
73
-
74
- /**
75
- * Get resource by ID
76
- *
77
- * @route GET /resources/:id
78
- * @access Public
79
- */
80
- export async function getResource(
81
- request: FastifyRequest<{
82
- Params: { id: string };
83
- }>,
84
- reply: FastifyReply
85
- ): Promise<FastifyReply> {
86
- const { id } = request.params;
87
-
88
- // Call service
89
- const resource = await resourceService.getResource(id);
90
-
91
- // Return success response
92
- return reply.status(200).send(
93
- successResponse('Resource retrieved successfully', resource)
94
- );
95
- }
96
-
97
- /**
98
- * Create a new resource
99
- *
100
- * @route POST /resources
101
- * @access Private (requires authentication)
102
- */
103
- export async function createResource(
104
- request: FastifyRequest<{
105
- Body: CreateResourceInput;
106
- }>,
107
- reply: FastifyReply
108
- ): Promise<FastifyReply> {
109
- // Validate request body
110
- const body = CreateResourceSchema.parse(request.body);
111
-
112
- // Get user ID from authenticated request
113
- const userId = (request.user as any)?.userId;
114
-
115
- // Call service
116
- const resource = await resourceService.createResource(body, userId);
117
-
118
- // Return success response
119
- return reply.status(201).send(
120
- successResponse('Resource created successfully', resource)
121
- );
122
- }
123
-
124
- /**
125
- * Update a resource
126
- *
127
- * @route PATCH /resources/:id
128
- * @access Private (requires authentication and ownership)
129
- */
130
- export async function updateResource(
131
- request: FastifyRequest<{
132
- Params: { id: string };
133
- Body: UpdateResourceInput;
134
- }>,
135
- reply: FastifyReply
136
- ): Promise<FastifyReply> {
137
- const { id } = request.params;
138
-
139
- // Validate request body
140
- const body = UpdateResourceSchema.parse(request.body);
141
-
142
- // Get user ID from authenticated request
143
- const userId = (request.user as any)?.userId;
144
-
145
- // Call service (includes ownership verification)
146
- const resource = await resourceService.updateResource(id, userId, body);
147
-
148
- // Return success response
149
- return reply.status(200).send(
150
- successResponse('Resource updated successfully', resource)
151
- );
152
- }
153
-
154
- /**
155
- * Delete a resource
156
- *
157
- * @route DELETE /resources/:id
158
- * @access Private (requires authentication and ownership)
159
- */
160
- export async function deleteResource(
161
- request: FastifyRequest<{
162
- Params: { id: string };
163
- }>,
164
- reply: FastifyReply
165
- ): Promise<FastifyReply> {
166
- const { id } = request.params;
167
-
168
- // Get user ID from authenticated request
169
- const userId = (request.user as any)?.userId;
170
-
171
- // Call service (includes ownership verification)
172
- const resource = await resourceService.deleteResource(id, userId);
173
-
174
- // Return success response
175
- return reply.status(200).send(
176
- successResponse('Resource deleted successfully', resource)
177
- );
178
- }
179
-
180
- /**
181
- * Get current user's resources
182
- *
183
- * @route GET /resources/my
184
- * @access Private (requires authentication)
185
- */
186
- export async function getMyResources(
187
- request: FastifyRequest<{
188
- Querystring: PaginationInput;
189
- }>,
190
- reply: FastifyReply
191
- ): Promise<FastifyReply> {
192
- // Parse pagination parameters
193
- const pagination = PaginationSchema.parse({
194
- page: request.query.page,
195
- limit: request.query.limit,
196
- });
197
-
198
- // Get user ID from authenticated request
199
- const userId = (request.user as any)?.userId;
200
-
201
- // Call service
202
- const { items, totalItems } = await resourceService.getMyResources(
203
- userId,
204
- pagination.page,
205
- pagination.limit
206
- );
207
-
208
- // Return paginated response (EXACT format from 04-pagination.md)
209
- return reply.status(200).send(
210
- paginatedResponse(
211
- 'Your resources retrieved successfully',
212
- items,
213
- pagination.page,
214
- pagination.limit,
215
- totalItems
216
- )
217
- );
218
- }
@@ -1,253 +0,0 @@
1
- /**
2
- * Resources Repository
3
- *
4
- * Database operations for resources module.
5
- * Handles all Prisma queries related to resources.
6
- *
7
- * @see /mnt/project/02-general-rules.md
8
- */
9
-
10
- import { prisma } from '../../libs/db.js';
11
- import type { Resource as PrismaResource } from '@prisma/client';
12
- import type { ResourceFilters, Resource, ResourceWithOwner } from './resources.types.js';
13
- import type { CreateResourceInput, UpdateResourceInput } from './resources.schemas.js';
14
-
15
- /**
16
- * Find many resources with filters and pagination
17
- *
18
- * @param filters - Filter criteria
19
- * @param page - Page number (1-indexed)
20
- * @param limit - Items per page
21
- * @returns Array of resources
22
- */
23
- export async function findMany(
24
- filters: ResourceFilters,
25
- page: number,
26
- limit: number
27
- ): Promise<Resource[]> {
28
- // Build where clause
29
- const where: any = {};
30
-
31
- // Status filter
32
- if (filters.status) {
33
- where.status = filters.status;
34
- }
35
-
36
- // Price range filters
37
- if (filters.minPrice !== undefined || filters.maxPrice !== undefined) {
38
- where.price = {};
39
- if (filters.minPrice !== undefined) {
40
- where.price.gte = filters.minPrice;
41
- }
42
- if (filters.maxPrice !== undefined) {
43
- where.price.lte = filters.maxPrice;
44
- }
45
- }
46
-
47
- // Owner filter
48
- if (filters.ownerId) {
49
- where.ownerId = filters.ownerId;
50
- }
51
-
52
- // Search filter (title contains)
53
- if (filters.search) {
54
- where.title = {
55
- contains: filters.search,
56
- mode: 'insensitive', // Case-insensitive search
57
- };
58
- }
59
-
60
- // Calculate offset
61
- const skip = (page - 1) * limit;
62
-
63
- // Execute query
64
- const resources = await prisma.resource.findMany({
65
- where,
66
- skip,
67
- take: limit,
68
- orderBy: {
69
- createdAt: 'desc',
70
- },
71
- });
72
-
73
- return resources;
74
- }
75
-
76
- /**
77
- * Count resources with filters
78
- *
79
- * @param filters - Filter criteria
80
- * @returns Total count of resources matching filters
81
- */
82
- export async function count(filters: ResourceFilters): Promise<number> {
83
- // Build where clause (same as findMany)
84
- const where: any = {};
85
-
86
- if (filters.status) {
87
- where.status = filters.status;
88
- }
89
-
90
- if (filters.minPrice !== undefined || filters.maxPrice !== undefined) {
91
- where.price = {};
92
- if (filters.minPrice !== undefined) {
93
- where.price.gte = filters.minPrice;
94
- }
95
- if (filters.maxPrice !== undefined) {
96
- where.price.lte = filters.maxPrice;
97
- }
98
- }
99
-
100
- if (filters.ownerId) {
101
- where.ownerId = filters.ownerId;
102
- }
103
-
104
- if (filters.search) {
105
- where.title = {
106
- contains: filters.search,
107
- mode: 'insensitive',
108
- };
109
- }
110
-
111
- // Execute count query
112
- const total = await prisma.resource.count({ where });
113
-
114
- return total;
115
- }
116
-
117
- /**
118
- * Find resource by ID
119
- *
120
- * @param id - Resource ID
121
- * @returns Resource with owner relation, or null if not found
122
- */
123
- export async function findById(id: string): Promise<ResourceWithOwner | null> {
124
- const resource = await prisma.resource.findUnique({
125
- where: { id },
126
- include: {
127
- owner: {
128
- select: {
129
- id: true,
130
- email: true,
131
- name: true,
132
- },
133
- },
134
- },
135
- });
136
-
137
- return resource;
138
- }
139
-
140
- /**
141
- * Create a new resource
142
- *
143
- * @param data - Resource creation data
144
- * @param ownerId - Owner user ID
145
- * @returns Created resource
146
- */
147
- export async function create(
148
- data: CreateResourceInput,
149
- ownerId: string
150
- ): Promise<Resource> {
151
- const resource = await prisma.resource.create({
152
- data: {
153
- title: data.title,
154
- summary: data.summary || null,
155
- price: data.price,
156
- ownerId,
157
- status: 'active',
158
- },
159
- });
160
-
161
- return resource;
162
- }
163
-
164
- /**
165
- * Update a resource
166
- *
167
- * @param id - Resource ID
168
- * @param data - Update data (partial)
169
- * @returns Updated resource
170
- */
171
- export async function update(
172
- id: string,
173
- data: UpdateResourceInput
174
- ): Promise<Resource> {
175
- const resource = await prisma.resource.update({
176
- where: { id },
177
- data: {
178
- ...(data.title !== undefined && { title: data.title }),
179
- ...(data.summary !== undefined && { summary: data.summary }),
180
- ...(data.price !== undefined && { price: data.price }),
181
- },
182
- });
183
-
184
- return resource;
185
- }
186
-
187
- /**
188
- * Delete a resource (soft delete)
189
- *
190
- * Sets status to 'deleted' instead of removing from database.
191
- *
192
- * @param id - Resource ID
193
- * @returns Deleted resource
194
- */
195
- export async function deleteResource(id: string): Promise<Resource> {
196
- const resource = await prisma.resource.update({
197
- where: { id },
198
- data: {
199
- status: 'deleted',
200
- },
201
- });
202
-
203
- return resource;
204
- }
205
-
206
- /**
207
- * Hard delete a resource
208
- *
209
- * Permanently removes resource from database.
210
- * Use with caution - prefer soft delete.
211
- *
212
- * @param id - Resource ID
213
- */
214
- export async function hardDelete(id: string): Promise<void> {
215
- await prisma.resource.delete({
216
- where: { id },
217
- });
218
- }
219
-
220
- /**
221
- * Check if resource exists
222
- *
223
- * @param id - Resource ID
224
- * @returns True if resource exists, false otherwise
225
- */
226
- export async function exists(id: string): Promise<boolean> {
227
- const count = await prisma.resource.count({
228
- where: { id },
229
- });
230
-
231
- return count > 0;
232
- }
233
-
234
- /**
235
- * Check if user owns resource
236
- *
237
- * @param resourceId - Resource ID
238
- * @param userId - User ID
239
- * @returns True if user owns resource, false otherwise
240
- */
241
- export async function isOwner(
242
- resourceId: string,
243
- userId: string
244
- ): Promise<boolean> {
245
- const count = await prisma.resource.count({
246
- where: {
247
- id: resourceId,
248
- ownerId: userId,
249
- },
250
- });
251
-
252
- return count > 0;
253
- }