create-tigra 3.0.5 → 3.1.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.
- package/README.md +4 -2
- package/bin/create-tigra.js +5 -19
- package/modules/email-verification/client/hooks/useVerification.ts +3 -3
- package/package.json +1 -1
- package/template/.agents/skills/security-audit/AI-AND-LLM.md +83 -0
- package/template/.agents/skills/security-audit/ATTACK-CLASSES.md +130 -0
- package/template/.agents/skills/security-audit/CLIENT-SIDE.md +83 -0
- package/template/.agents/skills/security-audit/CLOUD-AND-DEPLOYMENT.md +86 -0
- package/template/.agents/skills/security-audit/DATA-ISOLATION-AND-LIFECYCLE.md +84 -0
- package/template/.agents/skills/security-audit/DESKTOP-MOBILE-AND-LOCAL-IPC.md +89 -0
- package/template/.agents/skills/security-audit/HUNTING.md +251 -0
- package/template/.agents/skills/security-audit/LICENSE +21 -0
- package/template/.agents/skills/security-audit/MEMORY-SAFETY-AND-BINARY.md +101 -0
- package/template/.agents/skills/security-audit/PROTOCOLS-RPC-AND-MESSAGING.md +81 -0
- package/template/.agents/skills/security-audit/RECONNAISSANCE.md +156 -0
- package/template/.agents/skills/security-audit/RESOURCE-EXHAUSTION-AND-AVAILABILITY.md +78 -0
- package/template/.agents/skills/security-audit/SKILL.md +192 -0
- package/template/.agents/skills/security-audit/SOURCE.md +5 -0
- package/template/.agents/skills/security-audit/SUPPLY-CHAIN-AND-RELEASE.md +73 -0
- package/template/.agents/skills/security-audit/VALIDATION-AND-REPORTING.md +186 -0
- package/template/.agents/skills/security-audit/WEB-PROTOCOL-AND-AUTH.md +105 -0
- package/template/.agents/skills/security-audit/report-schema.json +461 -0
- package/template/.agents/skills/security-audit/validate-coverage-ledger.cjs +872 -0
- package/template/.agents/skills/security-audit/validate-coverage-ledger.test.cjs +740 -0
- package/template/.agents/skills/security-audit/validate-findings.cjs +773 -0
- package/template/.agents/skills/security-audit/validate-findings.test.cjs +652 -0
- package/template/AGENTS.md +46 -0
- package/template/client/AGENTS.md +23 -0
- package/template/client/package-lock.json +410 -324
- package/template/client/package.json +3 -3
- package/template/client/src/app/(auth)/layout.tsx +9 -0
- package/template/client/src/app/(auth)/loading.tsx +7 -0
- package/template/client/src/app/(main)/layout.tsx +11 -0
- package/template/client/src/app/(main)/loading.tsx +7 -0
- package/template/client/src/app/globals.css +4 -0
- package/template/client/src/app/layout.tsx +9 -2
- package/template/client/src/app/loading.tsx +2 -6
- package/template/client/src/app/not-found.tsx +2 -3
- package/template/client/src/app/providers.tsx +6 -3
- package/template/client/src/components/common/AppLink.tsx +84 -0
- package/template/client/src/components/common/EmptyState.tsx +2 -2
- package/template/client/src/components/common/Pagination.tsx +3 -2
- package/template/client/src/components/common/RouteLoadingShell.tsx +21 -0
- package/template/client/src/components/common/SmoothNavigationProvider.tsx +151 -0
- package/template/client/src/components/layout/Header.tsx +12 -12
- package/template/client/src/features/admin/hooks/useAdminSessions.ts +2 -2
- package/template/client/src/features/admin/hooks/useAdminUsers.ts +3 -3
- package/template/client/src/features/auth/components/AuthInitializer.tsx +3 -2
- package/template/client/src/features/auth/components/LoginForm.tsx +3 -3
- package/template/client/src/features/auth/components/RegisterForm.tsx +3 -3
- package/template/client/src/features/auth/hooks/useAuth.ts +2 -2
- package/template/client/src/features/auth/hooks/usePasswordReset.ts +2 -2
- package/template/client/src/hooks/useAppRouter.ts +40 -0
- package/template/client/src/styles/fonts/inter-jetbrains.css +4 -2
- package/template/client/src/styles/themes/default.css +1 -1
- package/template/gitignore +0 -6
- package/template/server/AGENTS.md +28 -0
- package/template/server/package-lock.json +671 -522
- package/template/server/package.json +8 -8
- package/template/_claude/QUICK_REFERENCE.md +0 -193
- package/template/_claude/README.md +0 -53
- package/template/_claude/commands/create-client.md +0 -878
- package/template/_claude/commands/create-server.md +0 -388
- package/template/_claude/hooks/restrict-paths.sh +0 -51
- package/template/_claude/rules/client/01-project-structure.md +0 -147
- package/template/_claude/rules/client/02-components-and-types.md +0 -146
- package/template/_claude/rules/client/03-data-and-state.md +0 -195
- package/template/_claude/rules/client/04-design-system.md +0 -408
- package/template/_claude/rules/client/05-security.md +0 -55
- package/template/_claude/rules/client/06-ux-checklist.md +0 -111
- package/template/_claude/rules/client/07-deployment.md +0 -99
- package/template/_claude/rules/client/08-lockfile-cross-platform.md +0 -79
- package/template/_claude/rules/client/core.md +0 -46
- package/template/_claude/rules/global/completion-reports.md +0 -178
- package/template/_claude/rules/global/core.md +0 -104
- package/template/_claude/rules/global/investigation-before-conclusions.md +0 -57
- package/template/_claude/rules/server/core.md +0 -52
- package/template/_claude/rules/server/database.md +0 -124
- package/template/_claude/rules/server/deployment.md +0 -78
- package/template/_claude/rules/server/project-conventions.md +0 -254
- package/template/_claude/rules/server/response-handling.md +0 -144
- package/template/_claude/settings.json +0 -15
- package/template/_claude/skills/clean-ui/SKILL.md +0 -63
- package/template/_claude/skills/role/SKILL.md +0 -39
- package/template/_claude/skills/theme/SKILL.md +0 -109
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
# Create Server Project
|
|
2
|
-
|
|
3
|
-
You are scaffolding a new Fastify + TypeScript backend project. The project name is: **$ARGUMENTS**
|
|
4
|
-
|
|
5
|
-
If no project name is provided, ask the user for one before proceeding.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Instructions
|
|
10
|
-
|
|
11
|
-
Create a complete, production-ready Fastify server project following the architecture and rules defined in `.claude/rules/server/`. Read those rules before generating any code.
|
|
12
|
-
|
|
13
|
-
### Step 1: Project Root Setup
|
|
14
|
-
|
|
15
|
-
Create the project directory named `$ARGUMENTS` with the following root files:
|
|
16
|
-
|
|
17
|
-
#### `package.json`
|
|
18
|
-
```json
|
|
19
|
-
{
|
|
20
|
-
"name": "$ARGUMENTS",
|
|
21
|
-
"version": "1.0.0",
|
|
22
|
-
"description": "",
|
|
23
|
-
"main": "dist/server.js",
|
|
24
|
-
"scripts": {
|
|
25
|
-
"dev": "tsx watch src/server.ts",
|
|
26
|
-
"build": "tsc && tsc-alias",
|
|
27
|
-
"start": "node dist/server.js",
|
|
28
|
-
"prisma:generate": "prisma generate",
|
|
29
|
-
"prisma:migrate:dev": "prisma migrate dev",
|
|
30
|
-
"prisma:migrate:deploy": "prisma migrate deploy",
|
|
31
|
-
"prisma:reset": "prisma migrate reset",
|
|
32
|
-
"prisma:seed": "prisma db seed",
|
|
33
|
-
"prisma:studio": "prisma studio",
|
|
34
|
-
"lint": "eslint src/",
|
|
35
|
-
"docker:up": "docker compose up -d",
|
|
36
|
-
"docker:down": "docker compose down",
|
|
37
|
-
"docker:logs": "docker compose logs -f"
|
|
38
|
-
},
|
|
39
|
-
"prisma": {
|
|
40
|
-
"seed": "tsx prisma/seed.ts"
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Install these dependencies (use npm):
|
|
46
|
-
```
|
|
47
|
-
# Dependencies
|
|
48
|
-
npm install fastify @fastify/cors @fastify/helmet @fastify/rate-limit @fastify/cookie @fastify/jwt
|
|
49
|
-
npm install @prisma/client zod dotenv pino pino-pretty ioredis bcryptjs uuid
|
|
50
|
-
|
|
51
|
-
# Dev dependencies
|
|
52
|
-
npm install -D typescript tsx tsc-alias prisma @types/node @types/bcryptjs @types/uuid eslint @eslint/js typescript-eslint
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
#### `tsconfig.json`
|
|
56
|
-
```json
|
|
57
|
-
{
|
|
58
|
-
"compilerOptions": {
|
|
59
|
-
"target": "ES2020",
|
|
60
|
-
"module": "NodeNext",
|
|
61
|
-
"moduleResolution": "NodeNext",
|
|
62
|
-
"strict": true,
|
|
63
|
-
"esModuleInterop": true,
|
|
64
|
-
"skipLibCheck": true,
|
|
65
|
-
"forceConsistentCasingInFileNames": true,
|
|
66
|
-
"resolveJsonModule": true,
|
|
67
|
-
"declaration": true,
|
|
68
|
-
"declarationMap": true,
|
|
69
|
-
"sourceMap": true,
|
|
70
|
-
"outDir": "dist",
|
|
71
|
-
"rootDir": "src",
|
|
72
|
-
"baseUrl": ".",
|
|
73
|
-
"paths": {
|
|
74
|
-
"@/*": ["./src/*"],
|
|
75
|
-
"@modules/*": ["./src/modules/*"],
|
|
76
|
-
"@libs/*": ["./src/libs/*"],
|
|
77
|
-
"@config/*": ["./src/config/*"],
|
|
78
|
-
"@shared/*": ["./src/shared/*"]
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
"include": ["src/**/*"],
|
|
82
|
-
"exclude": ["node_modules", "dist"]
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
> **Note**: The `tsc-alias` package resolves path aliases at build time. `tsx` handles paths natively in development.
|
|
87
|
-
|
|
88
|
-
#### `eslint.config.mjs`
|
|
89
|
-
```js
|
|
90
|
-
import eslint from '@eslint/js';
|
|
91
|
-
import tseslint from 'typescript-eslint';
|
|
92
|
-
|
|
93
|
-
export default tseslint.config(
|
|
94
|
-
eslint.configs.recommended,
|
|
95
|
-
...tseslint.configs.recommended,
|
|
96
|
-
{
|
|
97
|
-
rules: {
|
|
98
|
-
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
|
99
|
-
'@typescript-eslint/explicit-function-return-type': 'warn',
|
|
100
|
-
'@typescript-eslint/no-explicit-any': 'error',
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
ignores: ['dist/', 'node_modules/', 'prisma/'],
|
|
105
|
-
}
|
|
106
|
-
);
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
#### `.env` and `.env.example`
|
|
110
|
-
```env
|
|
111
|
-
# App
|
|
112
|
-
NODE_ENV=development
|
|
113
|
-
PORT=3000
|
|
114
|
-
HOST=0.0.0.0
|
|
115
|
-
|
|
116
|
-
# Database
|
|
117
|
-
DATABASE_URL="mysql://root:rootpassword@localhost:3306/$ARGUMENTS"
|
|
118
|
-
|
|
119
|
-
# Redis
|
|
120
|
-
REDIS_URL="redis://localhost:6379"
|
|
121
|
-
|
|
122
|
-
# JWT
|
|
123
|
-
JWT_SECRET="change-this-to-a-secure-random-string"
|
|
124
|
-
JWT_ACCESS_EXPIRY="15m"
|
|
125
|
-
JWT_REFRESH_EXPIRY="7d"
|
|
126
|
-
|
|
127
|
-
# CORS
|
|
128
|
-
CORS_ORIGIN="http://localhost:3001"
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
#### `.gitignore`
|
|
132
|
-
Include: node_modules, dist, .env, .env.local, .env*.local, *.log, .prisma
|
|
133
|
-
|
|
134
|
-
#### `docker-compose.yml`
|
|
135
|
-
Create a Docker Compose file with these services:
|
|
136
|
-
1. **mysql** - MySQL 8.0, port 3306, database name = `$ARGUMENTS`, root password = `rootpassword`, volume for data persistence, healthcheck
|
|
137
|
-
2. **phpmyadmin** - Latest, port 8080, linked to mysql, behind the `tools` profile
|
|
138
|
-
3. **redis** - Redis 7 Alpine, port 6379, volume for data persistence, healthcheck
|
|
139
|
-
4. **redis-commander** - Redis Commander UI, port 8081, linked to redis, behind the `tools` profile
|
|
140
|
-
|
|
141
|
-
Use a named network for all services. Add restart policies.
|
|
142
|
-
|
|
143
|
-
**Security requirements (dev attack surface):**
|
|
144
|
-
- Bind ALL published ports to localhost (`127.0.0.1:<port>:<container-port>`), never `0.0.0.0` — these are dev credentials (root password, unpassworded Redis).
|
|
145
|
-
- Put the admin UIs (**phpmyadmin**, **redis-commander**) behind `profiles: ["tools"]` so they do NOT start by default:
|
|
146
|
-
- `docker compose up -d` → MySQL + Redis only
|
|
147
|
-
- `docker compose --profile tools up -d` → also starts phpMyAdmin + Redis Commander
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
|
|
151
|
-
### Step 2: Source Code Structure
|
|
152
|
-
|
|
153
|
-
Create the following directory structure under `src/`:
|
|
154
|
-
|
|
155
|
-
```
|
|
156
|
-
src/
|
|
157
|
-
├── app.ts # Fastify instance, plugin registration
|
|
158
|
-
├── server.ts # listen() call only
|
|
159
|
-
├── config/
|
|
160
|
-
│ └── env.ts # Zod-validated environment variables
|
|
161
|
-
├── libs/
|
|
162
|
-
│ ├── prisma.ts # Prisma client singleton
|
|
163
|
-
│ ├── redis.ts # Redis client singleton
|
|
164
|
-
│ ├── logger.ts # Pino logger
|
|
165
|
-
│ └── auth.ts # JWT helpers (sign, verify, middleware)
|
|
166
|
-
├── shared/
|
|
167
|
-
│ ├── errors/
|
|
168
|
-
│ │ ├── AppError.ts # Base error class
|
|
169
|
-
│ │ └── errors.ts # All typed error subclasses
|
|
170
|
-
│ ├── responses/
|
|
171
|
-
│ │ ├── successResponse.ts
|
|
172
|
-
│ │ └── paginatedResponse.ts
|
|
173
|
-
│ ├── schemas/
|
|
174
|
-
│ │ └── pagination.schema.ts # Shared Zod pagination schema
|
|
175
|
-
│ └── types/
|
|
176
|
-
│ └── index.ts # Shared TypeScript types
|
|
177
|
-
└── modules/
|
|
178
|
-
└── auth/ # Auth module
|
|
179
|
-
├── auth.routes.ts
|
|
180
|
-
├── auth.controller.ts
|
|
181
|
-
├── auth.service.ts
|
|
182
|
-
├── auth.repo.ts
|
|
183
|
-
└── auth.schemas.ts
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
---
|
|
187
|
-
|
|
188
|
-
### Step 3: Core Files Implementation
|
|
189
|
-
|
|
190
|
-
#### `src/config/env.ts`
|
|
191
|
-
- Use Zod to validate ALL environment variables at startup
|
|
192
|
-
- Export typed `env` object
|
|
193
|
-
- Crash immediately with clear message if validation fails
|
|
194
|
-
|
|
195
|
-
#### `src/libs/logger.ts`
|
|
196
|
-
- Use Pino logger
|
|
197
|
-
- Pretty print in development, JSON in production
|
|
198
|
-
- Export singleton `logger`
|
|
199
|
-
|
|
200
|
-
#### `src/libs/prisma.ts`
|
|
201
|
-
- Singleton Prisma client
|
|
202
|
-
- Handle graceful shutdown (disconnect on process exit)
|
|
203
|
-
|
|
204
|
-
#### `src/libs/redis.ts`
|
|
205
|
-
- Singleton ioredis client
|
|
206
|
-
- Configure from `REDIS_URL` env
|
|
207
|
-
- Handle connection errors with logger
|
|
208
|
-
- Handle graceful shutdown
|
|
209
|
-
|
|
210
|
-
#### `src/libs/auth.ts`
|
|
211
|
-
- JWT sign/verify using @fastify/jwt
|
|
212
|
-
- Fastify `authenticate` preHandler decorator
|
|
213
|
-
- Fastify `optionalAuth` preHandler decorator
|
|
214
|
-
- Role-based `authorize(...roles)` preHandler
|
|
215
|
-
|
|
216
|
-
#### `src/shared/errors/AppError.ts`
|
|
217
|
-
- Base `AppError` class extending `Error` with: `code`, `statusCode`, `message`
|
|
218
|
-
|
|
219
|
-
#### `src/shared/errors/errors.ts`
|
|
220
|
-
- Export all subclasses: `BadRequestError`, `ValidationError`, `UnauthorizedError`, `ForbiddenError`, `NotFoundError`, `ConflictError`, `InternalError`
|
|
221
|
-
- Each with appropriate default status code and code string
|
|
222
|
-
|
|
223
|
-
#### `src/shared/responses/successResponse.ts`
|
|
224
|
-
```typescript
|
|
225
|
-
export function successResponse<T>(message: string, data: T) {
|
|
226
|
-
return { success: true as const, message, data };
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
#### `src/shared/responses/paginatedResponse.ts`
|
|
231
|
-
```typescript
|
|
232
|
-
export function paginatedResponse<T>(
|
|
233
|
-
message: string,
|
|
234
|
-
items: T[],
|
|
235
|
-
page: number,
|
|
236
|
-
limit: number,
|
|
237
|
-
totalItems: number
|
|
238
|
-
) {
|
|
239
|
-
const totalPages = Math.ceil(totalItems / limit);
|
|
240
|
-
return {
|
|
241
|
-
success: true as const,
|
|
242
|
-
message,
|
|
243
|
-
data: {
|
|
244
|
-
items,
|
|
245
|
-
pagination: {
|
|
246
|
-
page,
|
|
247
|
-
limit,
|
|
248
|
-
totalItems,
|
|
249
|
-
totalPages,
|
|
250
|
-
hasNextPage: page < totalPages,
|
|
251
|
-
hasPreviousPage: page > 1,
|
|
252
|
-
},
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
#### `src/shared/schemas/pagination.schema.ts`
|
|
259
|
-
```typescript
|
|
260
|
-
import { z } from 'zod';
|
|
261
|
-
|
|
262
|
-
export const PaginationSchema = z.object({
|
|
263
|
-
page: z.coerce.number().int().min(1).default(1),
|
|
264
|
-
limit: z.coerce.number().int().min(1).max(100).default(10),
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
export type PaginationInput = z.infer<typeof PaginationSchema>;
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
#### `src/app.ts`
|
|
271
|
-
- Create and configure Fastify instance
|
|
272
|
-
- Register plugins: cors, helmet, rate-limit, cookie, jwt
|
|
273
|
-
- Register health check route: `GET /api/v1/health` — returns `successResponse('Server is healthy', { status: 'ok', timestamp: new Date().toISOString() })`
|
|
274
|
-
- Register global error handler that maps `AppError` → proper response format
|
|
275
|
-
- Register routes with `/api/v1` prefix
|
|
276
|
-
- Export the app instance
|
|
277
|
-
|
|
278
|
-
The global error handler MUST:
|
|
279
|
-
- Check if error is `AppError` → use its code, message, statusCode
|
|
280
|
-
- Check if error is Zod validation error → 422 with validation details
|
|
281
|
-
- Check if error is Fastify validation error → 400
|
|
282
|
-
- Default to 500 Internal Server Error
|
|
283
|
-
- Log internal errors with logger
|
|
284
|
-
- NEVER expose stack traces or internal details to client
|
|
285
|
-
|
|
286
|
-
#### `src/server.ts`
|
|
287
|
-
- Import app from `app.ts`
|
|
288
|
-
- Import env config
|
|
289
|
-
- Call `app.listen({ port, host })`
|
|
290
|
-
- Log startup message with port and environment
|
|
291
|
-
- Handle graceful shutdown (SIGINT, SIGTERM) — close Fastify, disconnect Prisma, disconnect Redis
|
|
292
|
-
|
|
293
|
-
---
|
|
294
|
-
|
|
295
|
-
### Step 4: Auth Module
|
|
296
|
-
|
|
297
|
-
Create a working auth module with these endpoints:
|
|
298
|
-
- `POST /api/v1/auth/register` - Register new user
|
|
299
|
-
- `POST /api/v1/auth/login` - Login with email/password
|
|
300
|
-
- `POST /api/v1/auth/logout` - Logout (invalidate refresh token)
|
|
301
|
-
- `POST /api/v1/auth/refresh` - Refresh access token
|
|
302
|
-
- `GET /api/v1/auth/me` - Get current user (protected)
|
|
303
|
-
|
|
304
|
-
Follow the layered architecture:
|
|
305
|
-
- **auth.schemas.ts** - Zod schemas for register, login, refresh input
|
|
306
|
-
- **auth.controller.ts** - Validate input, call service, return `successResponse()`
|
|
307
|
-
- **auth.service.ts** - Business logic, bcrypt hashing, JWT token generation, throw typed errors
|
|
308
|
-
- **auth.repo.ts** - Prisma queries for users and refresh tokens
|
|
309
|
-
- **auth.routes.ts** - Fastify plugin registering routes with appropriate preHandlers
|
|
310
|
-
|
|
311
|
-
---
|
|
312
|
-
|
|
313
|
-
### Step 5: Prisma Setup
|
|
314
|
-
|
|
315
|
-
#### `prisma/schema.prisma`
|
|
316
|
-
```prisma
|
|
317
|
-
generator client {
|
|
318
|
-
provider = "prisma-client-js"
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
datasource db {
|
|
322
|
-
provider = "mysql"
|
|
323
|
-
url = env("DATABASE_URL")
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
model User {
|
|
327
|
-
id String @id @default(uuid())
|
|
328
|
-
email String @unique
|
|
329
|
-
password String
|
|
330
|
-
firstName String
|
|
331
|
-
lastName String
|
|
332
|
-
role String @default("USER")
|
|
333
|
-
isActive Boolean @default(true)
|
|
334
|
-
deletedAt DateTime?
|
|
335
|
-
createdAt DateTime @default(now())
|
|
336
|
-
updatedAt DateTime @updatedAt
|
|
337
|
-
|
|
338
|
-
refreshTokens RefreshToken[]
|
|
339
|
-
|
|
340
|
-
@@map("users")
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
model RefreshToken {
|
|
344
|
-
id String @id @default(uuid())
|
|
345
|
-
token String @unique @db.VarChar(500)
|
|
346
|
-
userId String
|
|
347
|
-
expiresAt DateTime
|
|
348
|
-
createdAt DateTime @default(now())
|
|
349
|
-
|
|
350
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
351
|
-
|
|
352
|
-
@@index([userId])
|
|
353
|
-
@@index([token])
|
|
354
|
-
@@map("refresh_tokens")
|
|
355
|
-
}
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
#### `prisma/seed.ts`
|
|
359
|
-
- Create a basic seed file that creates an admin user and a test user
|
|
360
|
-
- Use bcrypt to hash passwords
|
|
361
|
-
- Wrap in try/catch with proper logging
|
|
362
|
-
|
|
363
|
-
---
|
|
364
|
-
|
|
365
|
-
### Step 6: Final Verification
|
|
366
|
-
|
|
367
|
-
After creating all files:
|
|
368
|
-
1. Run `npm install` to install dependencies
|
|
369
|
-
2. Run `docker compose up -d` to start services (if Docker is available, otherwise remind the user)
|
|
370
|
-
3. Wait for MySQL to be healthy, then run `npx prisma migrate dev --name init`
|
|
371
|
-
4. Run `npx prisma generate`
|
|
372
|
-
5. Run `npx prisma db seed`
|
|
373
|
-
6. Verify the project starts with `npm run dev`
|
|
374
|
-
7. Test health check: `curl http://localhost:3000/api/v1/health`
|
|
375
|
-
|
|
376
|
-
If Docker is not running, inform the user they need to run `docker compose up -d` before running migrations.
|
|
377
|
-
|
|
378
|
-
---
|
|
379
|
-
|
|
380
|
-
## IMPORTANT RULES
|
|
381
|
-
|
|
382
|
-
- Follow ALL rules from `.claude/rules/server/` and `.claude/rules/global/`
|
|
383
|
-
- Use the EXACT response format from `response-handling.md` (includes pagination contract)
|
|
384
|
-
- All routes prefixed with `/api/v1`
|
|
385
|
-
- TypeScript strict mode
|
|
386
|
-
- No `console.log` — use logger
|
|
387
|
-
- No raw `Error` throws — use AppError subclasses
|
|
388
|
-
- Named exports everywhere except `app.ts`
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Restrict Claude's WRITE access based on the .developer-role file.
|
|
3
|
-
# Claude can always READ any file for full project context.
|
|
4
|
-
# Only Edit/Write operations are blocked on the other side's directory.
|
|
5
|
-
#
|
|
6
|
-
# NOTE: settings.json matcher already filters for Edit|Write only,
|
|
7
|
-
# so this script does NOT need to check the tool name.
|
|
8
|
-
#
|
|
9
|
-
# To switch roles, either:
|
|
10
|
-
# 1. Edit .developer-role and type: frontend, backend, or fullstack
|
|
11
|
-
# 2. Use /role command in Claude
|
|
12
|
-
#
|
|
13
|
-
# fullstack (or empty/missing file) = no restrictions
|
|
14
|
-
|
|
15
|
-
ROLE_FILE="$CLAUDE_PROJECT_DIR/.developer-role"
|
|
16
|
-
|
|
17
|
-
# Read role from first non-comment, non-empty line
|
|
18
|
-
if [ -f "$ROLE_FILE" ]; then
|
|
19
|
-
ROLE=$(grep -v '^\s*#' "$ROLE_FILE" | grep -v '^\s*$' | head -1 | tr -d '[:space:]')
|
|
20
|
-
else
|
|
21
|
-
ROLE=""
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
# No file, empty, or fullstack = full access
|
|
25
|
-
if [ -z "$ROLE" ] || [ "$ROLE" = "fullstack" ]; then
|
|
26
|
-
exit 0
|
|
27
|
-
fi
|
|
28
|
-
|
|
29
|
-
# Read the full JSON input from Claude Code
|
|
30
|
-
INPUT=$(cat)
|
|
31
|
-
|
|
32
|
-
deny_with_reason() {
|
|
33
|
-
cat <<DENY_EOF
|
|
34
|
-
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"$1"}}
|
|
35
|
-
DENY_EOF
|
|
36
|
-
exit 0
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
# Simple path check against the raw JSON input.
|
|
40
|
-
# No JSON parsing needed — just check if the input contains server/ or client/ paths.
|
|
41
|
-
if [ "$ROLE" = "frontend" ]; then
|
|
42
|
-
if echo "$INPUT" | grep -qiE "server[/\\\\]"; then
|
|
43
|
-
deny_with_reason "BLOCKED: Role is set to frontend. You can read server/ files but cannot modify them. Only the backend developer can edit server/ code."
|
|
44
|
-
fi
|
|
45
|
-
elif [ "$ROLE" = "backend" ]; then
|
|
46
|
-
if echo "$INPUT" | grep -qiE "client[/\\\\]"; then
|
|
47
|
-
deny_with_reason "BLOCKED: Role is set to backend. You can read client/ files but cannot modify them. Only the frontend developer can edit client/ code."
|
|
48
|
-
fi
|
|
49
|
-
fi
|
|
50
|
-
|
|
51
|
-
exit 0
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
> **SCOPE**: These rules apply specifically to the **client** directory (Next.js App Router).
|
|
2
|
-
|
|
3
|
-
# Project Structure
|
|
4
|
-
|
|
5
|
-
## Folder Structure
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
src/
|
|
9
|
-
├── app/ # Next.js App Router
|
|
10
|
-
│ ├── layout.tsx # Root layout
|
|
11
|
-
│ ├── page.tsx # Home page
|
|
12
|
-
│ ├── providers.tsx # Client providers (Redux, React Query)
|
|
13
|
-
│ ├── globals.css # Global styles
|
|
14
|
-
│ ├── (auth)/ # Auth route group
|
|
15
|
-
│ │ ├── login/page.tsx
|
|
16
|
-
│ │ ├── register/page.tsx
|
|
17
|
-
│ │ └── layout.tsx
|
|
18
|
-
│ ├── (main)/ # Main route group
|
|
19
|
-
│ │ ├── layout.tsx # Header/Footer layout
|
|
20
|
-
│ │ └── <domain>/ # Domain-specific routes
|
|
21
|
-
│ ├── dashboard/ # Protected routes
|
|
22
|
-
│ └── admin/ # Admin routes
|
|
23
|
-
├── components/
|
|
24
|
-
│ ├── ui/ # shadcn/ui components
|
|
25
|
-
│ ├── layout/ # Header, Footer, Sidebar, MainLayout
|
|
26
|
-
│ └── common/ # LoadingSpinner, ErrorBoundary, Pagination, EmptyState
|
|
27
|
-
├── features/ # Feature modules (domain-driven)
|
|
28
|
-
│ └── <domain>/
|
|
29
|
-
│ ├── components/
|
|
30
|
-
│ ├── hooks/
|
|
31
|
-
│ ├── services/ # <domain>.service.ts
|
|
32
|
-
│ ├── store/ # <domain>Slice.ts (if needed)
|
|
33
|
-
│ ├── types/ # <domain>.types.ts
|
|
34
|
-
│ └── actions/ # <domain>.actions.ts (Server Actions)
|
|
35
|
-
├── styles/
|
|
36
|
-
│ ├── themes/ # Color theme (light/dark mode values)
|
|
37
|
-
│ │ └── default.css # Claude-inspired warm palette (HEX)
|
|
38
|
-
│ └── fonts/ # Font presets (switch in globals.css import)
|
|
39
|
-
│ └── inter-jetbrains.css # Default — Inter + JetBrains Mono
|
|
40
|
-
├── hooks/ # Global hooks (useDebounce, useLocalStorage, useMediaQuery)
|
|
41
|
-
├── lib/
|
|
42
|
-
│ ├── api/ # axios.config.ts, api.types.ts
|
|
43
|
-
│ ├── constants/ # routes.ts, api-endpoints.ts, app.constants.ts
|
|
44
|
-
│ └── utils/ # format.ts, validation.ts, error.ts, cn() helper
|
|
45
|
-
├── store/ # Redux store (index.ts, hooks.ts)
|
|
46
|
-
├── types/ # Global types
|
|
47
|
-
└── middleware.ts # Auth route protection
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## File Naming
|
|
51
|
-
|
|
52
|
-
| Type | Pattern | Example |
|
|
53
|
-
|---|---|---|
|
|
54
|
-
| Component | `PascalCase.tsx` | `ProductCard.tsx` |
|
|
55
|
-
| Page | `folder/page.tsx` | `products/page.tsx` |
|
|
56
|
-
| Hook | `use<Name>.ts` | `useAuth.ts` |
|
|
57
|
-
| Service | `<domain>.service.ts` | `product.service.ts` |
|
|
58
|
-
| Types | `<domain>.types.ts` | `product.types.ts` |
|
|
59
|
-
| Redux slice | `<domain>Slice.ts` | `authSlice.ts` |
|
|
60
|
-
| Server Action | `<domain>.actions.ts` | `product.actions.ts` |
|
|
61
|
-
| Page exports | `default export` | Required by Next.js |
|
|
62
|
-
| Everything else | Named exports | `export const ProductCard` |
|
|
63
|
-
|
|
64
|
-
## Import Order
|
|
65
|
-
|
|
66
|
-
1. React / Next.js (`useState`, `useRouter`, `Image`, `Link`)
|
|
67
|
-
2. Third-party (`@tanstack/react-query`, `sonner`)
|
|
68
|
-
3. UI components (`@/components/ui/*`)
|
|
69
|
-
4. Local components
|
|
70
|
-
5. Hooks
|
|
71
|
-
6. Services
|
|
72
|
-
7. Types (always `import type`)
|
|
73
|
-
8. Utils (`cn`, `formatDate`)
|
|
74
|
-
|
|
75
|
-
## Constants
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
// lib/constants/api-endpoints.ts
|
|
79
|
-
export const API_ENDPOINTS = {
|
|
80
|
-
AUTH: {
|
|
81
|
-
REGISTER: '/auth/register',
|
|
82
|
-
LOGIN: '/auth/login',
|
|
83
|
-
LOGOUT: '/auth/logout',
|
|
84
|
-
REFRESH: '/auth/refresh',
|
|
85
|
-
ME: '/auth/me',
|
|
86
|
-
REQUEST_PASSWORD_RESET: '/auth/request-password-reset',
|
|
87
|
-
RESET_PASSWORD: '/auth/reset-password',
|
|
88
|
-
},
|
|
89
|
-
USERS: {
|
|
90
|
-
ME: '/users/me',
|
|
91
|
-
UPDATE_ME: '/users/me',
|
|
92
|
-
DELETE_ME: '/users/me',
|
|
93
|
-
},
|
|
94
|
-
// Add domain-specific endpoints following this pattern:
|
|
95
|
-
// <DOMAIN>: {
|
|
96
|
-
// LIST: '/<domain>',
|
|
97
|
-
// CREATE: '/<domain>',
|
|
98
|
-
// GET: (id: string) => `/<domain>/${id}`,
|
|
99
|
-
// UPDATE: (id: string) => `/<domain>/${id}`,
|
|
100
|
-
// DELETE: (id: string) => `/<domain>/${id}`,
|
|
101
|
-
// },
|
|
102
|
-
} as const;
|
|
103
|
-
|
|
104
|
-
// lib/constants/routes.ts
|
|
105
|
-
export const ROUTES = {
|
|
106
|
-
HOME: '/',
|
|
107
|
-
LOGIN: '/login',
|
|
108
|
-
REGISTER: '/register',
|
|
109
|
-
RESET_PASSWORD: '/reset-password',
|
|
110
|
-
DASHBOARD: '/dashboard',
|
|
111
|
-
PROFILE: '/profile',
|
|
112
|
-
// Add domain-specific routes following this pattern:
|
|
113
|
-
// <DOMAIN>: {
|
|
114
|
-
// LIST: '/<domain>',
|
|
115
|
-
// DETAILS: (id: string) => `/<domain>/${id}`,
|
|
116
|
-
// CREATE: '/<domain>/create',
|
|
117
|
-
// EDIT: (id: string) => `/<domain>/${id}/edit`,
|
|
118
|
-
// },
|
|
119
|
-
} as const;
|
|
120
|
-
|
|
121
|
-
// lib/constants/app.constants.ts
|
|
122
|
-
export const APP_NAME = 'My App';
|
|
123
|
-
export const PAGINATION = { DEFAULT_PAGE: 1, DEFAULT_LIMIT: 10, MAX_LIMIT: 100 } as const;
|
|
124
|
-
export const USER_ROLES = { USER: 'USER', ADMIN: 'ADMIN' } as const;
|
|
125
|
-
export const CURRENCIES = { USD: 'USD', EUR: 'EUR' } as const;
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## App Providers
|
|
129
|
-
|
|
130
|
-
`app/providers.tsx` wraps the app with: **ReduxProvider** (store) → **QueryClientProvider** (React Query) → **Toaster** (sonner, position: top-right).
|
|
131
|
-
|
|
132
|
-
## Middleware
|
|
133
|
-
|
|
134
|
-
Protected paths: `/dashboard`, `/profile`, `/admin` — redirect to `/login` if no token.
|
|
135
|
-
Auth paths: `/login`, `/register` — redirect to `/dashboard` if already authenticated.
|
|
136
|
-
|
|
137
|
-
When creating a new page, always add it to `protectedPaths` and `config.matcher` in `middleware.ts` if it requires authentication. If you are unsure whether a page should be protected, **ask the user** — do not guess.
|
|
138
|
-
|
|
139
|
-
### Deleting a Route
|
|
140
|
-
|
|
141
|
-
When removing a page/route, you MUST update **all three** locations:
|
|
142
|
-
|
|
143
|
-
1. **Delete the page file**: `app/<route>/page.tsx` (and the folder if empty)
|
|
144
|
-
2. **Remove from `routes.ts`**: Delete the entry in `lib/constants/routes.ts`
|
|
145
|
-
3. **Remove from `middleware.ts`**: Delete from `protectedPaths` array AND `config.matcher` array
|
|
146
|
-
|
|
147
|
-
Missing any of these leaves dead references or broken middleware matches.
|