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,506 +0,0 @@
1
- ---
2
- trigger: always_on
3
- ---
4
-
5
- > **SCOPE**: These rules apply specifically to the **server** directory.
6
-
7
- # Testing Strategy & Best Practices
8
-
9
- ## Test Philosophy
10
-
11
- - **Test the behavior, not the implementation**
12
- - **Aim for 70%+ coverage** on services and critical utilities
13
- - **Fast tests** - Unit tests < 50ms, Integration < 500ms each
14
- - **Deterministic** - Tests must pass/fail consistently
15
-
16
- ---
17
-
18
- ## Test Stack
19
-
20
- ```json
21
- {
22
- "devDependencies": {
23
- "vitest": "^1.0.0",
24
- "supertest": "^6.3.0",
25
- "@faker-js/faker": "^8.0.0",
26
- "@testcontainers/mysql": "^10.0.0"
27
- }
28
- }
29
- ```
30
-
31
- **Tools:**
32
- - **Vitest** - Test runner (faster than Jest)
33
- - **Supertest** - HTTP endpoint testing
34
- - **Faker** - Generate realistic test data
35
- - **Testcontainers** - Isolated MySQL for integration tests
36
-
37
- ---
38
-
39
- ## Folder Structure
40
-
41
- ```
42
- src/
43
- ├── modules/
44
- │ └── users/
45
- │ ├── users.service.ts
46
- │ ├── users.service.test.ts # Unit tests
47
- │ ├── users.controller.ts
48
- │ └── users.integration.test.ts # Integration tests
49
- ├── libs/
50
- │ └── auth/
51
- │ ├── jwt.ts
52
- │ └── jwt.test.ts
53
- └── __tests__/
54
- ├── setup.ts # Global test setup
55
- ├── helpers/ # Test utilities
56
- │ ├── factory.ts # Data factories
57
- │ └── db.ts # Test DB helpers
58
- └── e2e/ # End-to-end tests
59
- └── auth-flow.test.ts
60
- ```
61
-
62
- ---
63
-
64
- ## Test Types & When to Use
65
-
66
- ### 1. Unit Tests (70% of tests)
67
- **Test:** Services, utilities, helpers
68
- **Mock:** Database, external APIs
69
- **Speed:** < 50ms per test
70
-
71
- ```typescript
72
- // users.service.test.ts
73
- import { describe, it, expect, vi, beforeEach } from 'vitest';
74
- import { UserService } from './users.service';
75
- import { UserRepository } from './users.repo';
76
-
77
- vi.mock('./users.repo');
78
-
79
- describe('UserService', () => {
80
- let service: UserService;
81
- let mockRepo: any;
82
-
83
- beforeEach(() => {
84
- mockRepo = {
85
- findById: vi.fn(),
86
- create: vi.fn(),
87
- };
88
- service = new UserService(mockRepo);
89
- });
90
-
91
- describe('getUserById', () => {
92
- it('should return user when found', async () => {
93
- const mockUser = { id: '1', email: 'test@example.com' };
94
- mockRepo.findById.mockResolvedValue(mockUser);
95
-
96
- const result = await service.getUserById('1');
97
-
98
- expect(result).toEqual(mockUser);
99
- expect(mockRepo.findById).toHaveBeenCalledWith('1');
100
- });
101
-
102
- it('should throw NotFoundError when user does not exist', async () => {
103
- mockRepo.findById.mockResolvedValue(null);
104
-
105
- await expect(service.getUserById('999'))
106
- .rejects.toThrow('User not found');
107
- });
108
- });
109
- });
110
- ```
111
-
112
- ### 2. Integration Tests (20% of tests)
113
- **Test:** API endpoints with real DB
114
- **Mock:** External APIs only
115
- **Speed:** < 500ms per test
116
-
117
- ```typescript
118
- // users.integration.test.ts
119
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
120
- import { build } from '../app';
121
- import { setupTestDB, cleanupTestDB } from '../__tests__/helpers/db';
122
-
123
- describe('Users API', () => {
124
- let app: any;
125
-
126
- beforeAll(async () => {
127
- await setupTestDB();
128
- app = await build();
129
- });
130
-
131
- afterAll(async () => {
132
- await app.close();
133
- await cleanupTestDB();
134
- });
135
-
136
- describe('POST /api/v1/users', () => {
137
- it('should create user with valid data', async () => {
138
- const response = await app.inject({
139
- method: 'POST',
140
- url: '/api/v1/users',
141
- payload: {
142
- email: 'test@example.com',
143
- password: 'SecurePass123!',
144
- },
145
- });
146
-
147
- expect(response.statusCode).toBe(201);
148
- expect(response.json()).toMatchObject({
149
- success: true,
150
- message: 'User created successfully',
151
- data: {
152
- email: 'test@example.com',
153
- },
154
- });
155
- });
156
-
157
- it('should reject invalid email', async () => {
158
- const response = await app.inject({
159
- method: 'POST',
160
- url: '/api/v1/users',
161
- payload: {
162
- email: 'invalid-email',
163
- password: 'SecurePass123!',
164
- },
165
- });
166
-
167
- expect(response.statusCode).toBe(400);
168
- expect(response.json().success).toBe(false);
169
- });
170
- });
171
- });
172
- ```
173
-
174
- ### 3. E2E Tests (10% of tests)
175
- **Test:** Complete user flows
176
- **Mock:** Nothing (real environment)
177
- **Speed:** < 2s per flow
178
-
179
- ```typescript
180
- // __tests__/e2e/auth-flow.test.ts
181
- describe('Authentication Flow', () => {
182
- it('should complete full auth lifecycle', async () => {
183
- // 1. Register
184
- const registerRes = await app.inject({
185
- method: 'POST',
186
- url: '/api/v1/auth/register',
187
- payload: { email: 'user@test.com', password: 'Pass123!' },
188
- });
189
- expect(registerRes.statusCode).toBe(201);
190
-
191
- // 2. Login
192
- const loginRes = await app.inject({
193
- method: 'POST',
194
- url: '/api/v1/auth/login',
195
- payload: { email: 'user@test.com', password: 'Pass123!' },
196
- });
197
- const { accessToken } = loginRes.json().data;
198
-
199
- // 3. Access protected route
200
- const profileRes = await app.inject({
201
- method: 'GET',
202
- url: '/api/v1/users/me',
203
- headers: { Authorization: `Bearer ${accessToken}` },
204
- });
205
- expect(profileRes.statusCode).toBe(200);
206
-
207
- // 4. Logout
208
- const logoutRes = await app.inject({
209
- method: 'POST',
210
- url: '/api/v1/auth/logout',
211
- headers: { Authorization: `Bearer ${accessToken}` },
212
- });
213
- expect(logoutRes.statusCode).toBe(200);
214
- });
215
- });
216
- ```
217
-
218
- ---
219
-
220
- ## Test Database Setup
221
-
222
- ### Option 1: Testcontainers (Recommended)
223
- Real MySQL in Docker, isolated per test suite.
224
-
225
- ```typescript
226
- // __tests__/helpers/db.ts
227
- import { MySqlContainer } from '@testcontainers/mysql';
228
- import { PrismaClient } from '@prisma/client';
229
-
230
- let container: MySqlContainer;
231
- let prisma: PrismaClient;
232
-
233
- export async function setupTestDB() {
234
- container = await new MySqlContainer('mysql:8.0')
235
- .withDatabase('test_db')
236
- .start();
237
-
238
- const DATABASE_URL = container.getConnectionUri();
239
- process.env.DATABASE_URL = DATABASE_URL;
240
-
241
- prisma = new PrismaClient();
242
- await prisma.$executeRaw`SET FOREIGN_KEY_CHECKS = 0`;
243
- // Run migrations
244
- await prisma.$executeRaw`/* Your schema setup */`;
245
- }
246
-
247
- export async function cleanupTestDB() {
248
- await prisma.$disconnect();
249
- await container.stop();
250
- }
251
-
252
- export async function clearDB() {
253
- const tables = await prisma.$queryRaw`SHOW TABLES`;
254
- for (const table of tables) {
255
- await prisma.$executeRawUnsafe(`TRUNCATE TABLE ${Object.values(table)[0]}`);
256
- }
257
- }
258
- ```
259
-
260
- ### Option 2: Separate Test Database
261
- Simpler but requires manual setup.
262
-
263
- ```typescript
264
- // __tests__/helpers/db.ts
265
- export async function setupTestDB() {
266
- process.env.DATABASE_URL = 'mysql://root:pass@localhost:3306/test_db';
267
- await prisma.$executeRaw`DROP DATABASE IF EXISTS test_db`;
268
- await prisma.$executeRaw`CREATE DATABASE test_db`;
269
- // Run migrations
270
- }
271
- ```
272
-
273
- ---
274
-
275
- ## Data Factories
276
-
277
- Use factories to create consistent test data:
278
-
279
- ```typescript
280
- // __tests__/helpers/factory.ts
281
- import { faker } from '@faker-js/faker';
282
-
283
- export const UserFactory = {
284
- build: (overrides = {}) => ({
285
- id: faker.string.uuid(),
286
- email: faker.internet.email(),
287
- password: 'SecurePass123!',
288
- createdAt: new Date(),
289
- ...overrides,
290
- }),
291
-
292
- create: async (overrides = {}) => {
293
- const data = UserFactory.build(overrides);
294
- return await prisma.user.create({ data });
295
- },
296
- };
297
-
298
- // Usage in tests
299
- it('should update user', async () => {
300
- const user = await UserFactory.create();
301
- // Test with real DB entity
302
- });
303
- ```
304
-
305
- ---
306
-
307
- ## Mocking Patterns
308
-
309
- ### Mock External APIs
310
- ```typescript
311
- import { vi } from 'vitest';
312
-
313
- vi.mock('../libs/stripe', () => ({
314
- stripeClient: {
315
- charges: {
316
- create: vi.fn().mockResolvedValue({ id: 'ch_123', status: 'succeeded' }),
317
- },
318
- },
319
- }));
320
- ```
321
-
322
- ### Mock Redis
323
- ```typescript
324
- vi.mock('../libs/redis', () => ({
325
- redis: {
326
- get: vi.fn(),
327
- set: vi.fn(),
328
- del: vi.fn(),
329
- },
330
- }));
331
- ```
332
-
333
- ### Mock Environment Variables
334
- ```typescript
335
- beforeEach(() => {
336
- process.env.JWT_SECRET = 'test-secret';
337
- process.env.NODE_ENV = 'test';
338
- });
339
- ```
340
-
341
- ---
342
-
343
- ## Testing Best Practices
344
-
345
- ### ✅ DO:
346
- - **Test edge cases**: Empty arrays, null values, boundary conditions
347
- - **Use descriptive test names**: `it('should throw error when email is already taken')`
348
- - **One assertion per test** (or closely related assertions)
349
- - **Clean up after tests**: Reset mocks, clear DB
350
- - **Use factories** for consistent test data
351
-
352
- ### ❌ DON'T:
353
- - Test Prisma/Fastify internals (trust the library)
354
- - Write tests that depend on execution order
355
- - Mock everything (integration tests need real DB)
356
- - Use production database for tests
357
- - Commit `.env.test` with real credentials
358
-
359
- ---
360
-
361
- ## Coverage Requirements
362
-
363
- ```json
364
- // package.json
365
- {
366
- "scripts": {
367
- "test": "vitest run",
368
- "test:watch": "vitest",
369
- "test:coverage": "vitest run --coverage"
370
- }
371
- }
372
- ```
373
-
374
- **Minimum Coverage:**
375
- - **Services**: 80%
376
- - **Controllers**: 60% (mostly happy paths)
377
- - **Utilities**: 90%
378
- - **Overall**: 70%
379
-
380
- **Ignore from coverage:**
381
- - Migration files
382
- - Type definitions
383
- - Config files
384
-
385
- ---
386
-
387
- ## CI Integration
388
-
389
- ```yaml
390
- # .github/workflows/test.yml
391
- name: Tests
392
- on: [push, pull_request]
393
- jobs:
394
- test:
395
- runs-on: ubuntu-latest
396
- services:
397
- mysql:
398
- image: mysql:8.0
399
- env:
400
- MYSQL_ROOT_PASSWORD: test
401
- MYSQL_DATABASE: test_db
402
- ports:
403
- - 3306:3306
404
- steps:
405
- - uses: actions/checkout@v3
406
- - uses: actions/setup-node@v3
407
- - run: npm ci
408
- - run: npm run test:coverage
409
- - run: npm run test:e2e
410
- ```
411
-
412
- ---
413
-
414
- ## Common Test Scenarios
415
-
416
- ### Testing Authentication
417
- ```typescript
418
- describe('Protected Routes', () => {
419
- it('should reject requests without token', async () => {
420
- const res = await app.inject({
421
- method: 'GET',
422
- url: '/api/v1/users/me',
423
- });
424
- expect(res.statusCode).toBe(401);
425
- });
426
-
427
- it('should accept valid token', async () => {
428
- const token = generateTestToken({ userId: '123' });
429
- const res = await app.inject({
430
- method: 'GET',
431
- url: '/api/v1/users/me',
432
- headers: { Authorization: `Bearer ${token}` },
433
- });
434
- expect(res.statusCode).toBe(200);
435
- });
436
- });
437
- ```
438
-
439
- ### Testing Pagination
440
- ```typescript
441
- it('should paginate results correctly', async () => {
442
- await UserFactory.create({ count: 25 }); // Create 25 users
443
-
444
- const res = await app.inject({
445
- method: 'GET',
446
- url: '/api/v1/users?page=2&limit=10',
447
- });
448
-
449
- const { data } = res.json();
450
- expect(data.items).toHaveLength(10);
451
- expect(data.pagination).toMatchObject({
452
- page: 2,
453
- limit: 10,
454
- totalItems: 25,
455
- totalPages: 3,
456
- hasNextPage: true,
457
- hasPreviousPage: true,
458
- });
459
- });
460
- ```
461
-
462
- ### Testing Transactions
463
- ```typescript
464
- it('should rollback on error', async () => {
465
- await expect(
466
- prisma.$transaction(async (tx) => {
467
- await tx.user.create({ data: { email: 'test@example.com' } });
468
- throw new Error('Simulated error');
469
- })
470
- ).rejects.toThrow();
471
-
472
- const users = await prisma.user.findMany();
473
- expect(users).toHaveLength(0); // Transaction rolled back
474
- });
475
- ```
476
-
477
- ---
478
-
479
- ## Debugging Tests
480
-
481
- ```typescript
482
- // Add logging to failing tests
483
- it.only('should debug this specific test', async () => {
484
- console.log('Request payload:', payload);
485
- const res = await app.inject({ ... });
486
- console.log('Response:', res.json());
487
- expect(res.statusCode).toBe(200);
488
- });
489
- ```
490
-
491
- **Vitest UI** for visual debugging:
492
- ```bash
493
- npm run test -- --ui
494
- ```
495
-
496
- ---
497
-
498
- ## Checklist
499
-
500
- - [ ] Unit tests for all services
501
- - [ ] Integration tests for all API endpoints
502
- - [ ] E2E test for critical user flows
503
- - [ ] 70%+ code coverage
504
- - [ ] Tests pass in CI
505
- - [ ] No hardcoded credentials in tests
506
- - [ ] Test DB isolated from development DB
@@ -1,180 +0,0 @@
1
- ---
2
- trigger: always_on
3
- ---
4
-
5
- > **SCOPE**: These rules apply specifically to the **server** directory.
6
-
7
- # Observability, Logging & Monitoring
8
-
9
- ## Logging Stack
10
-
11
- ```json
12
- {
13
- "dependencies": {
14
- "pino": "^8.16.0",
15
- "pino-pretty": "^10.2.0"
16
- }
17
- }
18
- ```
19
-
20
- ## Logger Setup
21
-
22
- ```typescript
23
- // libs/logger.ts
24
- import pino from 'pino';
25
-
26
- export const logger = pino({
27
- level: process.env.LOG_LEVEL || 'info',
28
- transport: process.env.NODE_ENV === 'development'
29
- ? { target: 'pino-pretty', options: { colorize: true } }
30
- : undefined,
31
- redact: {
32
- paths: ['req.headers.authorization', '*.password', '*.token'],
33
- censor: '[REDACTED]',
34
- },
35
- });
36
- ```
37
-
38
- ## Fastify Integration
39
-
40
- ```typescript
41
- // app.ts
42
- export const app = fastify({
43
- logger: {
44
- level: 'info',
45
- serializers: {
46
- req: (req) => ({ method: req.method, url: req.url, requestId: req.id }),
47
- res: (res) => ({ statusCode: res.statusCode }),
48
- },
49
- },
50
- requestIdHeader: 'x-request-id',
51
- genReqId: (req) => req.headers['x-request-id'] || crypto.randomUUID(),
52
- });
53
- ```
54
-
55
- ## Log Levels
56
-
57
- ```typescript
58
- logger.info({ userId }, 'Normal operations');
59
- logger.warn({ attempts: 3 }, 'Approaching limit');
60
- logger.error({ error, userId }, 'Errors needing attention');
61
- ```
62
-
63
- **Production:** `info` | **Development:** `debug`
64
-
65
- ## Error Logging
66
-
67
- ```typescript
68
- app.setErrorHandler((error, request, reply) => {
69
- request.log.error({
70
- err: error,
71
- requestId: request.id,
72
- userId: request.user?.id,
73
- }, error.message);
74
-
75
- reply.status(error.statusCode || 500).send({
76
- success: false,
77
- error: { code: error.code || 'INTERNAL_ERROR', message: error.message },
78
- });
79
- });
80
- ```
81
-
82
- ## Slow Query Logging
83
-
84
- ```typescript
85
- // libs/db.ts
86
- prisma.$on('query', (e) => {
87
- if (e.duration > 1000) {
88
- logger.warn({ query: e.query, duration: e.duration }, 'Slow query');
89
- }
90
- });
91
- ```
92
-
93
- ## Error Tracking (Sentry)
94
-
95
- ```typescript
96
- // libs/sentry.ts
97
- import * as Sentry from '@sentry/node';
98
-
99
- export function initSentry() {
100
- if (process.env.NODE_ENV === 'production') {
101
- Sentry.init({
102
- dsn: process.env.SENTRY_DSN,
103
- tracesSampleRate: 0.1,
104
- });
105
- }
106
- }
107
- ```
108
-
109
- ## Health Check Endpoint
110
-
111
- ```typescript
112
- app.get('/health', async (request, reply) => {
113
- const checks = {
114
- status: 'healthy',
115
- database: 'unknown',
116
- redis: 'unknown',
117
- };
118
-
119
- try {
120
- await prisma.$queryRaw`SELECT 1`;
121
- checks.database = 'connected';
122
- } catch {
123
- checks.database = 'disconnected';
124
- checks.status = 'unhealthy';
125
- }
126
-
127
- try {
128
- await redis.ping();
129
- checks.redis = 'connected';
130
- } catch {
131
- checks.redis = 'disconnected';
132
- checks.status = 'unhealthy';
133
- }
134
-
135
- return reply.status(checks.status === 'healthy' ? 200 : 503).send(checks);
136
- });
137
- ```
138
-
139
- ## Performance Tracking
140
-
141
- ```typescript
142
- app.addHook('onRequest', (request, reply, done) => {
143
- request.startTime = Date.now();
144
- done();
145
- });
146
-
147
- app.addHook('onResponse', (request, reply, done) => {
148
- const duration = Date.now() - request.startTime;
149
-
150
- if (duration > 500) {
151
- logger.warn({ url: request.url, duration }, 'Slow response');
152
- }
153
-
154
- done();
155
- });
156
- ```
157
-
158
- ## Best Practices
159
-
160
- ### ✅ DO:
161
- - Use structured logging (JSON)
162
- - Include request IDs for tracing
163
- - Redact sensitive data
164
- - Log errors with full context
165
- - Monitor response times
166
-
167
- ### ❌ DON'T:
168
- - Log passwords, tokens, API keys
169
- - Use `console.log` in production
170
- - Log sensitive user data
171
- - Skip error context
172
-
173
- ## Checklist
174
-
175
- - [ ] Pino logger configured
176
- - [ ] Request ID tracking enabled
177
- - [ ] Sensitive data redacted
178
- - [ ] Health check endpoint
179
- - [ ] Slow query logging
180
- - [ ] Error tracking (Sentry) in production