create-tigra 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/bin/create-tigra.js +292 -0
- package/package.json +41 -0
- package/template/.agent/rules/client/01-project-structure.md +326 -0
- package/template/.agent/rules/client/02-component-patterns.md +249 -0
- package/template/.agent/rules/client/03-typescript-rules.md +226 -0
- package/template/.agent/rules/client/04-state-management.md +474 -0
- package/template/.agent/rules/client/05-api-integration.md +129 -0
- package/template/.agent/rules/client/06-forms-validation.md +129 -0
- package/template/.agent/rules/client/07-common-patterns.md +150 -0
- package/template/.agent/rules/client/08-color-system.md +93 -0
- package/template/.agent/rules/client/09-security-rules.md +97 -0
- package/template/.agent/rules/client/10-testing-strategy.md +370 -0
- package/template/.agent/rules/global/ai-edit-safety.md +38 -0
- package/template/.agent/rules/server/01-db-and-migrations.md +242 -0
- package/template/.agent/rules/server/02-general-rules.md +111 -0
- package/template/.agent/rules/server/03-migrations.md +20 -0
- package/template/.agent/rules/server/04-pagination.md +130 -0
- package/template/.agent/rules/server/05-project-conventions.md +71 -0
- package/template/.agent/rules/server/06-response-handling.md +173 -0
- package/template/.agent/rules/server/07-testing-strategy.md +506 -0
- package/template/.agent/rules/server/08-observability.md +180 -0
- package/template/.agent/rules/server/09-api-documentation-v2.md +168 -0
- package/template/.agent/rules/server/10-background-jobs-v2.md +185 -0
- package/template/.agent/rules/server/11-rate-limiting-v2.md +210 -0
- package/template/.agent/rules/server/12-performance-optimization.md +567 -0
- package/template/.claude/rules/client-01-project-structure.md +327 -0
- package/template/.claude/rules/client-02-component-patterns.md +250 -0
- package/template/.claude/rules/client-03-typescript-rules.md +227 -0
- package/template/.claude/rules/client-04-state-management.md +475 -0
- package/template/.claude/rules/client-05-api-integration.md +130 -0
- package/template/.claude/rules/client-06-forms-validation.md +130 -0
- package/template/.claude/rules/client-07-common-patterns.md +151 -0
- package/template/.claude/rules/client-08-color-system.md +94 -0
- package/template/.claude/rules/client-09-security-rules.md +98 -0
- package/template/.claude/rules/client-10-testing-strategy.md +371 -0
- package/template/.claude/rules/global-ai-edit-safety.md +39 -0
- package/template/.claude/rules/server-01-db-and-migrations.md +243 -0
- package/template/.claude/rules/server-02-general-rules.md +112 -0
- package/template/.claude/rules/server-03-migrations.md +21 -0
- package/template/.claude/rules/server-04-pagination.md +131 -0
- package/template/.claude/rules/server-05-project-conventions.md +72 -0
- package/template/.claude/rules/server-06-response-handling.md +174 -0
- package/template/.claude/rules/server-07-testing-strategy.md +507 -0
- package/template/.claude/rules/server-08-observability.md +181 -0
- package/template/.claude/rules/server-09-api-documentation-v2.md +169 -0
- package/template/.claude/rules/server-10-background-jobs-v2.md +186 -0
- package/template/.claude/rules/server-11-rate-limiting-v2.md +211 -0
- package/template/.claude/rules/server-12-performance-optimization.md +568 -0
- package/template/.cursor/rules/client-01-project-structure.mdc +327 -0
- package/template/.cursor/rules/client-02-component-patterns.mdc +250 -0
- package/template/.cursor/rules/client-03-typescript-rules.mdc +227 -0
- package/template/.cursor/rules/client-04-state-management.mdc +475 -0
- package/template/.cursor/rules/client-05-api-integration.mdc +130 -0
- package/template/.cursor/rules/client-06-forms-validation.mdc +130 -0
- package/template/.cursor/rules/client-07-common-patterns.mdc +151 -0
- package/template/.cursor/rules/client-08-color-system.mdc +94 -0
- package/template/.cursor/rules/client-09-security-rules.mdc +98 -0
- package/template/.cursor/rules/client-10-testing-strategy.mdc +371 -0
- package/template/.cursor/rules/global-ai-edit-safety.mdc +39 -0
- package/template/.cursor/rules/server-01-db-and-migrations.mdc +243 -0
- package/template/.cursor/rules/server-02-general-rules.mdc +112 -0
- package/template/.cursor/rules/server-03-migrations.mdc +21 -0
- package/template/.cursor/rules/server-04-pagination.mdc +131 -0
- package/template/.cursor/rules/server-05-project-conventions.mdc +72 -0
- package/template/.cursor/rules/server-06-response-handling.mdc +174 -0
- package/template/.cursor/rules/server-07-testing-strategy.mdc +507 -0
- package/template/.cursor/rules/server-08-observability.mdc +181 -0
- package/template/.cursor/rules/server-09-api-documentation-v2.mdc +169 -0
- package/template/.cursor/rules/server-10-background-jobs-v2.mdc +186 -0
- package/template/.cursor/rules/server-11-rate-limiting-v2.mdc +211 -0
- package/template/.cursor/rules/server-12-performance-optimization.mdc +568 -0
- package/template/CLAUDE.md +207 -0
- package/template/server/.env.example +148 -0
- package/template/server/.tsc-aliasrc.json +12 -0
- package/template/server/README.md +175 -0
- package/template/server/SECURITY.md +190 -0
- package/template/server/biome.json +42 -0
- package/template/server/docker-compose.yml +111 -0
- package/template/server/package.json +83 -0
- package/template/server/postman_collection.json +733 -0
- package/template/server/prisma/schema.prisma +92 -0
- package/template/server/prisma/seed.ts +142 -0
- package/template/server/scripts/wait-for-db.js +60 -0
- package/template/server/src/app.ts +74 -0
- package/template/server/src/config/env.ts +101 -0
- package/template/server/src/hooks/request-timing.hook.ts +26 -0
- package/template/server/src/libs/auth/authenticate.middleware.ts +22 -0
- package/template/server/src/libs/auth/rbac.middleware.test.ts +134 -0
- package/template/server/src/libs/auth/rbac.middleware.ts +147 -0
- package/template/server/src/libs/db.ts +76 -0
- package/template/server/src/libs/error-handler.ts +89 -0
- package/template/server/src/libs/logger.ts +60 -0
- package/template/server/src/libs/queue.ts +79 -0
- package/template/server/src/libs/redis.ts +79 -0
- package/template/server/src/libs/swagger-schemas.ts +16 -0
- package/template/server/src/modules/admin/admin.controller.ts +122 -0
- package/template/server/src/modules/admin/admin.routes.ts +100 -0
- package/template/server/src/modules/admin/admin.schemas.ts +35 -0
- package/template/server/src/modules/admin/admin.service.ts +167 -0
- package/template/server/src/modules/auth/auth.controller.ts +141 -0
- package/template/server/src/modules/auth/auth.integration.test.ts +150 -0
- package/template/server/src/modules/auth/auth.repo.ts +218 -0
- package/template/server/src/modules/auth/auth.routes.ts +204 -0
- package/template/server/src/modules/auth/auth.schemas.ts +137 -0
- package/template/server/src/modules/auth/auth.service.test.ts +119 -0
- package/template/server/src/modules/auth/auth.service.ts +329 -0
- package/template/server/src/modules/auth/auth.types.ts +97 -0
- package/template/server/src/modules/resources/resources.controller.ts +218 -0
- package/template/server/src/modules/resources/resources.repo.ts +253 -0
- package/template/server/src/modules/resources/resources.routes.ts +355 -0
- package/template/server/src/modules/resources/resources.schemas.ts +146 -0
- package/template/server/src/modules/resources/resources.service.ts +218 -0
- package/template/server/src/modules/resources/resources.types.ts +73 -0
- package/template/server/src/plugins/rate-limit.plugin.ts +21 -0
- package/template/server/src/plugins/security.plugin.ts +21 -0
- package/template/server/src/plugins/swagger.plugin.ts +41 -0
- package/template/server/src/routes/health.routes.ts +31 -0
- package/template/server/src/server.ts +142 -0
- package/template/server/src/test/setup.ts +38 -0
- package/template/server/src/types/fastify.d.ts +36 -0
- package/template/server/src/utils/errors.ts +108 -0
- package/template/server/src/utils/pagination.ts +120 -0
- package/template/server/src/utils/response.ts +110 -0
- package/template/server/src/workers/file.worker.ts +106 -0
- package/template/server/tsconfig.build.json +30 -0
- package/template/server/tsconfig.build.tsbuildinfo +1 -0
- package/template/server/tsconfig.json +89 -0
- package/template/server/tsconfig.test.json +22 -0
- package/template/server/vitest.config.ts +98 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: always_on
|
|
3
|
+
globs: "client/**/*"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **SCOPE**: These rules apply specifically to the **client** directory.
|
|
7
|
+
|
|
8
|
+
# Client Testing Strategy
|
|
9
|
+
|
|
10
|
+
## Stack
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"vitest": "^1.0.0",
|
|
16
|
+
"@testing-library/react": "^14.0.0",
|
|
17
|
+
"@testing-library/user-event": "^14.5.0",
|
|
18
|
+
"@testing-library/jest-dom": "^6.1.0",
|
|
19
|
+
"msw": "^2.0.0"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// src/test/setup.ts
|
|
28
|
+
import { expect, afterEach } from 'vitest';
|
|
29
|
+
import { cleanup } from '@testing-library/react';
|
|
30
|
+
import matchers from '@testing-library/jest-dom/matchers';
|
|
31
|
+
|
|
32
|
+
expect.extend(matchers);
|
|
33
|
+
afterEach(() => cleanup());
|
|
34
|
+
|
|
35
|
+
// Mock window.matchMedia
|
|
36
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
37
|
+
writable: true,
|
|
38
|
+
value: vi.fn().mockImplementation((query) => ({
|
|
39
|
+
matches: false,
|
|
40
|
+
media: query,
|
|
41
|
+
addEventListener: vi.fn(),
|
|
42
|
+
removeEventListener: vi.fn(),
|
|
43
|
+
})),
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// vite.config.ts
|
|
49
|
+
export default defineConfig({
|
|
50
|
+
test: {
|
|
51
|
+
globals: true,
|
|
52
|
+
environment: 'jsdom',
|
|
53
|
+
setupFiles: './src/test/setup.ts',
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Test Utilities
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// src/test/utils.tsx
|
|
62
|
+
import { render } from '@testing-library/react';
|
|
63
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
64
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
65
|
+
import { Provider } from 'react-redux';
|
|
66
|
+
import { store } from '@/store';
|
|
67
|
+
|
|
68
|
+
const createTestQueryClient = () =>
|
|
69
|
+
new QueryClient({
|
|
70
|
+
defaultOptions: { queries: { retry: false } },
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export function renderWithProviders(ui: React.ReactElement, options = {}) {
|
|
74
|
+
const queryClient = createTestQueryClient();
|
|
75
|
+
|
|
76
|
+
function Wrapper({ children }: { children: React.ReactNode }) {
|
|
77
|
+
return (
|
|
78
|
+
<Provider store={store}>
|
|
79
|
+
<QueryClientProvider client={queryClient}>
|
|
80
|
+
<BrowserRouter>{children}</BrowserRouter>
|
|
81
|
+
</QueryClientProvider>
|
|
82
|
+
</Provider>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { ...render(ui, { wrapper: Wrapper, ...options }), queryClient };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export * from '@testing-library/react';
|
|
90
|
+
export { renderWithProviders as render };
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## API Mocking (MSW)
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// src/test/mocks/handlers.ts
|
|
97
|
+
import { http, HttpResponse } from 'msw';
|
|
98
|
+
|
|
99
|
+
export const handlers = [
|
|
100
|
+
http.post('/api/v1/auth/login', async ({ request }) => {
|
|
101
|
+
const { email, password } = await request.json();
|
|
102
|
+
|
|
103
|
+
if (email === 'user@test.com' && password === 'password') {
|
|
104
|
+
return HttpResponse.json({
|
|
105
|
+
success: true,
|
|
106
|
+
data: {
|
|
107
|
+
user: { id: '1', email: 'user@test.com' },
|
|
108
|
+
tokens: { accessToken: 'fake-token' },
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return HttpResponse.json(
|
|
114
|
+
{ success: false, error: { code: 'INVALID_CREDENTIALS' } },
|
|
115
|
+
{ status: 401 }
|
|
116
|
+
);
|
|
117
|
+
}),
|
|
118
|
+
|
|
119
|
+
http.get('/api/v1/resources', () => {
|
|
120
|
+
return HttpResponse.json({
|
|
121
|
+
success: true,
|
|
122
|
+
data: {
|
|
123
|
+
items: [
|
|
124
|
+
{ id: '1', title: 'Resource 1', price: 100 },
|
|
125
|
+
{ id: '2', title: 'Resource 2', price: 200 },
|
|
126
|
+
],
|
|
127
|
+
pagination: { page: 1, totalPages: 1, hasNextPage: false },
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}),
|
|
131
|
+
];
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// src/test/mocks/server.ts
|
|
136
|
+
import { setupServer } from 'msw/node';
|
|
137
|
+
import { handlers } from './handlers';
|
|
138
|
+
|
|
139
|
+
export const server = setupServer(...handlers);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// src/test/setup.ts (add to existing)
|
|
144
|
+
import { server } from './mocks/server';
|
|
145
|
+
|
|
146
|
+
beforeAll(() => server.listen());
|
|
147
|
+
afterEach(() => server.resetHandlers());
|
|
148
|
+
afterAll(() => server.close());
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Testing Patterns
|
|
152
|
+
|
|
153
|
+
### 1. Component Tests
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// ResourceCard.test.tsx
|
|
157
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
158
|
+
import { render, screen } from '@/test/utils';
|
|
159
|
+
import { ResourceCard } from './ResourceCard';
|
|
160
|
+
|
|
161
|
+
describe('ResourceCard', () => {
|
|
162
|
+
const mockResource = {
|
|
163
|
+
id: '1',
|
|
164
|
+
title: 'Test Resource',
|
|
165
|
+
price: 99.99,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
it('renders resource information', () => {
|
|
169
|
+
render(<ResourceCard resource={mockResource} />);
|
|
170
|
+
|
|
171
|
+
expect(screen.getByText('Test Resource')).toBeInTheDocument();
|
|
172
|
+
expect(screen.getByText('$99.99')).toBeInTheDocument();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('calls onClick when clicked', async () => {
|
|
176
|
+
const handleClick = vi.fn();
|
|
177
|
+
const { user } = render(
|
|
178
|
+
<ResourceCard resource={mockResource} onClick={handleClick} />
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
await user.click(screen.getByRole('button'));
|
|
182
|
+
expect(handleClick).toHaveBeenCalledWith('1');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### 2. Page/Container Tests
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// ResourcesPage.test.tsx
|
|
191
|
+
import { render, screen, waitFor } from '@/test/utils';
|
|
192
|
+
|
|
193
|
+
describe('ResourcesPage', () => {
|
|
194
|
+
it('displays loading state initially', () => {
|
|
195
|
+
render(<ResourcesPage />);
|
|
196
|
+
expect(screen.getByRole('status')).toBeInTheDocument();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('renders resources after loading', async () => {
|
|
200
|
+
render(<ResourcesPage />);
|
|
201
|
+
|
|
202
|
+
await waitFor(() => {
|
|
203
|
+
expect(screen.getByText('Resource 1')).toBeInTheDocument();
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('handles API errors', async () => {
|
|
208
|
+
server.use(
|
|
209
|
+
http.get('/api/v1/resources', () => {
|
|
210
|
+
return HttpResponse.json(
|
|
211
|
+
{ success: false, error: { message: 'Error' } },
|
|
212
|
+
{ status: 500 }
|
|
213
|
+
);
|
|
214
|
+
})
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
render(<ResourcesPage />);
|
|
218
|
+
|
|
219
|
+
await waitFor(() => {
|
|
220
|
+
expect(screen.getByText(/error/i)).toBeInTheDocument();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 3. Form Tests
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
// ResourceForm.test.tsx
|
|
230
|
+
import userEvent from '@testing-library/user-event';
|
|
231
|
+
|
|
232
|
+
describe('ResourceForm', () => {
|
|
233
|
+
it('submits valid form data', async () => {
|
|
234
|
+
const handleSubmit = vi.fn();
|
|
235
|
+
const user = userEvent.setup();
|
|
236
|
+
|
|
237
|
+
render(<ResourceForm onSubmit={handleSubmit} />);
|
|
238
|
+
|
|
239
|
+
await user.type(screen.getByLabelText(/title/i), 'New Resource');
|
|
240
|
+
await user.type(screen.getByLabelText(/price/i), '99.99');
|
|
241
|
+
await user.click(screen.getByRole('button', { name: /submit/i }));
|
|
242
|
+
|
|
243
|
+
await waitFor(() => {
|
|
244
|
+
expect(handleSubmit).toHaveBeenCalledWith({
|
|
245
|
+
title: 'New Resource',
|
|
246
|
+
price: 99.99,
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('displays validation errors', async () => {
|
|
252
|
+
const user = userEvent.setup();
|
|
253
|
+
render(<ResourceForm onSubmit={vi.fn()} />);
|
|
254
|
+
|
|
255
|
+
await user.click(screen.getByRole('button', { name: /submit/i }));
|
|
256
|
+
|
|
257
|
+
await waitFor(() => {
|
|
258
|
+
expect(screen.getByText(/title is required/i)).toBeInTheDocument();
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 4. Hook Tests
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// useResources.test.tsx
|
|
268
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
|
269
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
270
|
+
|
|
271
|
+
const createWrapper = () => {
|
|
272
|
+
const queryClient = new QueryClient({
|
|
273
|
+
defaultOptions: { queries: { retry: false } },
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
return ({ children }) => (
|
|
277
|
+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
278
|
+
);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
describe('useResources', () => {
|
|
282
|
+
it('fetches resources successfully', async () => {
|
|
283
|
+
const { result } = renderHook(() => useResources(), {
|
|
284
|
+
wrapper: createWrapper(),
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
expect(result.current.isLoading).toBe(true);
|
|
288
|
+
|
|
289
|
+
await waitFor(() => {
|
|
290
|
+
expect(result.current.isSuccess).toBe(true);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
expect(result.current.data).toHaveLength(2);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### 5. User Flow Tests
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// login-flow.test.tsx
|
|
302
|
+
describe('Login Flow', () => {
|
|
303
|
+
it('allows user to login', async () => {
|
|
304
|
+
const user = userEvent.setup();
|
|
305
|
+
render(<App />, { initialRoute: '/login' });
|
|
306
|
+
|
|
307
|
+
await user.type(screen.getByLabelText(/email/i), 'user@test.com');
|
|
308
|
+
await user.type(screen.getByLabelText(/password/i), 'password');
|
|
309
|
+
await user.click(screen.getByRole('button', { name: /login/i }));
|
|
310
|
+
|
|
311
|
+
await waitFor(() => {
|
|
312
|
+
expect(screen.getByText(/dashboard/i)).toBeInTheDocument();
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Best Practices
|
|
319
|
+
|
|
320
|
+
### ✅ DO:
|
|
321
|
+
```typescript
|
|
322
|
+
// Test what user sees
|
|
323
|
+
expect(screen.getByText('Resource 1')).toBeInTheDocument();
|
|
324
|
+
|
|
325
|
+
// Use user-event for interactions
|
|
326
|
+
await user.click(screen.getByRole('button'));
|
|
327
|
+
|
|
328
|
+
// Wait for async updates
|
|
329
|
+
await waitFor(() => {
|
|
330
|
+
expect(screen.getByText('Success')).toBeInTheDocument();
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### ❌ DON'T:
|
|
335
|
+
```typescript
|
|
336
|
+
// Don't test implementation details
|
|
337
|
+
expect(component.state.isLoading).toBe(false); // BAD
|
|
338
|
+
|
|
339
|
+
// Don't use test IDs unnecessarily
|
|
340
|
+
screen.getByTestId('card'); // Prefer semantic queries
|
|
341
|
+
|
|
342
|
+
// Don't test third-party libraries
|
|
343
|
+
expect(mockRouter.navigate).toHaveBeenCalled(); // BAD
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Coverage Requirements
|
|
347
|
+
|
|
348
|
+
```json
|
|
349
|
+
{
|
|
350
|
+
"scripts": {
|
|
351
|
+
"test": "vitest run",
|
|
352
|
+
"test:watch": "vitest",
|
|
353
|
+
"test:coverage": "vitest run --coverage"
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**Minimum:**
|
|
359
|
+
- Hooks: 80%
|
|
360
|
+
- Components: 70%
|
|
361
|
+
- Utils: 90%
|
|
362
|
+
- Overall: 70%
|
|
363
|
+
|
|
364
|
+
## Checklist
|
|
365
|
+
|
|
366
|
+
- [ ] Test setup configured (RTL + MSW)
|
|
367
|
+
- [ ] All critical user flows tested
|
|
368
|
+
- [ ] Forms validated and tested
|
|
369
|
+
- [ ] API mocking for endpoints
|
|
370
|
+
- [ ] 70%+ code coverage
|
|
371
|
+
- [ ] Tests run in CI
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: always_on
|
|
3
|
+
globs: "**/*"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **SCOPE**: These rules apply to the entire workspace (**server** and **client**).
|
|
7
|
+
|
|
8
|
+
# API Server – Safe Editing Rules
|
|
9
|
+
|
|
10
|
+
## General safety
|
|
11
|
+
|
|
12
|
+
- Assume this is a real production project. Avoid destructive or experimental changes.
|
|
13
|
+
- Never delete or radically restructure large parts of the codebase unless explicitly requested.
|
|
14
|
+
|
|
15
|
+
## When modifying existing code
|
|
16
|
+
|
|
17
|
+
- Keep changes as **small and focused** as possible.
|
|
18
|
+
- Do not change:
|
|
19
|
+
- Existing function signatures unless explicitly asked.
|
|
20
|
+
- Existing exports/imports that other modules depend on.
|
|
21
|
+
- If you must introduce a breaking change, call it out clearly in the explanation.
|
|
22
|
+
|
|
23
|
+
## Backwards compatibility
|
|
24
|
+
|
|
25
|
+
- When adding new features, prefer extending modules over rewiring everything.
|
|
26
|
+
- Preserve existing behavior for:
|
|
27
|
+
- Auth
|
|
28
|
+
- Core business flows
|
|
29
|
+
- Payment flows
|
|
30
|
+
|
|
31
|
+
## Comments & TODOs
|
|
32
|
+
|
|
33
|
+
- If something is ambiguous, add a `// TODO:` comment with a short note.
|
|
34
|
+
- Do NOT leave half-implemented features without a TODO or explanation.
|
|
35
|
+
|
|
36
|
+
## Logs & debugging
|
|
37
|
+
|
|
38
|
+
- Do not add noisy debug logs.
|
|
39
|
+
- If temporary logs are necessary, clearly mark them with `// TODO: remove debug log` so they can be cleaned up.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: always_on
|
|
3
|
+
globs: "server/**/*"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **SCOPE**: These rules apply specifically to the **server** directory.
|
|
7
|
+
|
|
8
|
+
# API Server – Database & Migrations Rules
|
|
9
|
+
|
|
10
|
+
## 🎯 Core Principles
|
|
11
|
+
|
|
12
|
+
- **Primary Database**: MySQL 8.0+
|
|
13
|
+
- **ORM**: Prisma (version 6.x - DO NOT upgrade to 7.x without testing)
|
|
14
|
+
- **Migration Philosophy**:
|
|
15
|
+
- Development: Fast iteration with resets
|
|
16
|
+
- Production: Safe, forward-only migrations
|
|
17
|
+
- **Schema Source of Truth**: `prisma/schema.prisma`
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 🚫 CRITICAL RULES - NEVER BREAK THESE
|
|
22
|
+
|
|
23
|
+
### Rule 1: NO Manual Schema Changes
|
|
24
|
+
❌ **NEVER** run raw SQL to change schema (CREATE TABLE, ALTER TABLE, etc.)
|
|
25
|
+
✅ **ALWAYS** use Prisma migrations for schema changes
|
|
26
|
+
✅ **EXCEPTION**: Data changes (INSERT, UPDATE, DELETE) can be raw SQL
|
|
27
|
+
|
|
28
|
+
### Rule 2: Development vs Production Workflows
|
|
29
|
+
**Development** (when iterating on schema):
|
|
30
|
+
```bash
|
|
31
|
+
npm run prisma:reset # Drops everything, reruns migrations
|
|
32
|
+
npm run prisma:seed # Repopulate test data
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Production** (deployed servers):
|
|
36
|
+
```bash
|
|
37
|
+
npm run prisma:migrate deploy # Only applies new migrations, NEVER resets
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Rule 3: Migration Conflicts = Reset
|
|
41
|
+
When you get migration errors in **development**:
|
|
42
|
+
- ❌ DON'T try to fix migration files manually
|
|
43
|
+
- ❌ DON'T delete migration folders
|
|
44
|
+
- ✅ DO run `npm run prisma:reset` to start clean
|
|
45
|
+
|
|
46
|
+
### Rule 4: Never Use `prisma db push` in Production
|
|
47
|
+
- `prisma db push` = Quick prototyping, no migration history
|
|
48
|
+
- `prisma migrate dev` = Proper migrations with history
|
|
49
|
+
- Use `db push` ONLY when rapidly prototyping new features
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 📁 Naming Conventions
|
|
54
|
+
|
|
55
|
+
### Tables
|
|
56
|
+
- Format: `snake_case` plural
|
|
57
|
+
- Examples: `users`, `posts`, `categories`
|
|
58
|
+
|
|
59
|
+
### Columns
|
|
60
|
+
- Format: `snake_case`
|
|
61
|
+
- Timestamps: `created_at`, `updated_at`, `deleted_at`
|
|
62
|
+
- Foreign keys: `<table_singular>_id`
|
|
63
|
+
- Example: `user_id`, `category_id`
|
|
64
|
+
|
|
65
|
+
### Prisma Models
|
|
66
|
+
- Format: `PascalCase` singular
|
|
67
|
+
- Example: `User`, `Post`, `Category`
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 🗄️ Required Fields for Every Table
|
|
72
|
+
|
|
73
|
+
Every main entity table MUST have:
|
|
74
|
+
```prisma
|
|
75
|
+
model EntityName {
|
|
76
|
+
id String @id @default(uuid())
|
|
77
|
+
createdAt DateTime @default(now())
|
|
78
|
+
updatedAt DateTime @updatedAt
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Optional but recommended:
|
|
83
|
+
```prisma
|
|
84
|
+
deletedAt DateTime? // For soft deletes
|
|
85
|
+
isActive Boolean @default(true) // For disabling without deleting
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 🔗 Architecture Relationships
|
|
91
|
+
|
|
92
|
+
### Core Entities (Example)
|
|
93
|
+
```
|
|
94
|
+
users (1) ──→ (many) posts
|
|
95
|
+
users (1) ──→ (1) profiles
|
|
96
|
+
categories (1) ──→ (many) posts
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Media Attachments
|
|
100
|
+
For images/files attached to multiple entity types:
|
|
101
|
+
```prisma
|
|
102
|
+
model Media {
|
|
103
|
+
id String @id @default(uuid())
|
|
104
|
+
entityType String // 'post', 'user', 'product'
|
|
105
|
+
entityId String // ID of the related entity
|
|
106
|
+
url String
|
|
107
|
+
type String // 'image', 'video', 'document'
|
|
108
|
+
order Int // For sorting galleries
|
|
109
|
+
|
|
110
|
+
@@index([entityType, entityId])
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## ⚡ Indexing Strategy
|
|
117
|
+
|
|
118
|
+
### Always Index These:
|
|
119
|
+
1. **Foreign Keys** - Prisma auto-indexes, but verify:
|
|
120
|
+
```prisma
|
|
121
|
+
@@index([userId])
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
2. **Filter Columns** - Fields used in WHERE clauses:
|
|
125
|
+
```prisma
|
|
126
|
+
@@index([isActive])
|
|
127
|
+
@@index([status])
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
3. **Composite Indexes** - Multiple columns filtered together:
|
|
131
|
+
```prisma
|
|
132
|
+
@@index([categoryId, isActive])
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
4. **Unique Constraints** - Business logic requirements:
|
|
136
|
+
```prisma
|
|
137
|
+
@@unique([email])
|
|
138
|
+
@@unique([slug])
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### When NOT to Index:
|
|
142
|
+
- Low-cardinality boolean fields used alone (e.g., `isActive` by itself)
|
|
143
|
+
- Text/blob columns
|
|
144
|
+
- Fields never used in WHERE/ORDER BY
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 🔄 Migration Workflow
|
|
149
|
+
|
|
150
|
+
### When Making Schema Changes:
|
|
151
|
+
|
|
152
|
+
#### Step 1: Edit `prisma/schema.prisma`
|
|
153
|
+
```prisma
|
|
154
|
+
model Resource {
|
|
155
|
+
id String @id @default(uuid())
|
|
156
|
+
title String
|
|
157
|
+
description String? @db.Text // NEW FIELD
|
|
158
|
+
// ... rest of fields
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### Step 2: Create Migration (Development)
|
|
163
|
+
```bash
|
|
164
|
+
# Option A: With descriptive name
|
|
165
|
+
npm run prisma:migrate dev --name add_resource_description
|
|
166
|
+
|
|
167
|
+
# Option B: If migration conflicts occur
|
|
168
|
+
npm run prisma:reset # Nuclear option - drops everything
|
|
169
|
+
npm run prisma:seed # Repopulate test data
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 🚀 Production Deployment Workflow
|
|
175
|
+
|
|
176
|
+
### Pre-Deployment Checklist:
|
|
177
|
+
1. ✅ All migrations tested locally
|
|
178
|
+
2. ✅ Seed data runs successfully
|
|
179
|
+
3. ✅ No pending schema changes in `schema.prisma`
|
|
180
|
+
4. ✅ Migration files committed to git
|
|
181
|
+
|
|
182
|
+
### Deployment Commands:
|
|
183
|
+
```bash
|
|
184
|
+
# On production server:
|
|
185
|
+
npm run prisma:migrate deploy # Applies pending migrations only
|
|
186
|
+
npm run prisma:generate # Regenerates client with new schema
|
|
187
|
+
npm start # Restart application
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 📊 Data Integrity Rules
|
|
193
|
+
|
|
194
|
+
### Foreign Keys
|
|
195
|
+
- ✅ **USE** foreign keys for core relationships
|
|
196
|
+
- ✅ **USE** `onDelete: Cascade` for dependent data
|
|
197
|
+
```prisma
|
|
198
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Soft Deletes
|
|
202
|
+
Prefer soft deletes for user-generated content:
|
|
203
|
+
```prisma
|
|
204
|
+
model Resource {
|
|
205
|
+
deletedAt DateTime?
|
|
206
|
+
// Query helper: where: { deletedAt: null }
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 🛠️ Troubleshooting Guide
|
|
213
|
+
|
|
214
|
+
### Issue: "Environment variable not found: DATABASE_URL"
|
|
215
|
+
**Solution:**
|
|
216
|
+
```bash
|
|
217
|
+
# Verify .env exists and has:
|
|
218
|
+
DATABASE_URL="mysql://user:pass@localhost:3306/dbname"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Issue: "Prisma Client is not generated"
|
|
222
|
+
**Solution:**
|
|
223
|
+
```bash
|
|
224
|
+
npm run prisma:generate
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 🎯 Best Practices Summary
|
|
230
|
+
|
|
231
|
+
### ✅ DO:
|
|
232
|
+
- Use Prisma migrations for ALL schema changes
|
|
233
|
+
- Reset database freely in development
|
|
234
|
+
- Test migrations in staging before production
|
|
235
|
+
- Add indexes for frequently queried fields
|
|
236
|
+
- Use soft deletes for user content
|
|
237
|
+
- Commit migration files to git
|
|
238
|
+
|
|
239
|
+
### ❌ DON'T:
|
|
240
|
+
- Manually edit database schema
|
|
241
|
+
- Use `prisma:reset` in production
|
|
242
|
+
- Skip migrations and use `db push` in production
|
|
243
|
+
- Delete migration files
|