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
@@ -0,0 +1,124 @@
1
+ > **SCOPE**: These rules apply specifically to the **server** directory.
2
+
3
+ # Database & Migrations
4
+
5
+ ## Principles
6
+
7
+ - **Database**: MySQL 8.0+
8
+ - **ORM**: Prisma 6.x (do NOT upgrade to 7.x without testing)
9
+ - **Schema source of truth**: `prisma/schema.prisma`
10
+ - **Dev**: Fast iteration with resets. **Prod**: Safe, forward-only migrations.
11
+
12
+ ---
13
+
14
+ ## Critical Rules
15
+
16
+ | Rule | Detail |
17
+ |------|--------|
18
+ | Schema changes | Prisma migrations ONLY. Never raw DDL. Data changes (INSERT/UPDATE/DELETE) can be raw SQL. |
19
+ | Dev conflicts | Don't fix migration files manually. Run `prisma:reset` then `prisma:seed`. |
20
+ | `db push` | Prototyping only. NEVER in production. |
21
+ | `prisma:reset` | Freely in dev. NEVER in production. |
22
+ | Production deploys | ONLY `prisma:migrate deploy`. Never reset, never manual edits. |
23
+
24
+ ---
25
+
26
+ ## Naming Conventions
27
+
28
+ | Entity | Format | Example |
29
+ |--------|--------|---------|
30
+ | Tables | `snake_case` plural | `users`, `order_items` |
31
+ | Columns | `snake_case` | `user_id`, `created_at` |
32
+ | Foreign keys | `<table_singular>_id` | `user_id`, `category_id` |
33
+ | Prisma models | `PascalCase` singular | `User`, `OrderItem` |
34
+
35
+ ---
36
+
37
+ ## Required Fields
38
+
39
+ Every main entity table MUST have:
40
+ ```prisma
41
+ model EntityName {
42
+ id String @id @default(uuid())
43
+ createdAt DateTime @default(now())
44
+ updatedAt DateTime @updatedAt
45
+ // Optional:
46
+ deletedAt DateTime? // Soft deletes (prefer for user content)
47
+ isActive Boolean @default(true) // Disable without deleting
48
+ }
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Relationships
54
+
55
+ - Use proper foreign keys with `onDelete: Cascade` for dependent data
56
+ - Explicit junction tables for many-to-many (not implicit)
57
+ - Normalize shared lookup data into own tables (no duplicated strings)
58
+ - Polymorphic attachments: use `entityType` + `entityId` with `@@index([entityType, entityId])`
59
+ - **Column renames**: Two-step migration — add new column, migrate data, then drop old column. Renaming directly DROPS data.
60
+
61
+ ---
62
+
63
+ ## Indexing
64
+
65
+ **Always index:** foreign keys, filter columns (WHERE clauses), composite filters, unique constraints.
66
+
67
+ **Never index:** low-cardinality booleans alone, text/blob columns, fields not used in WHERE/ORDER BY.
68
+
69
+ ```prisma
70
+ @@index([userId]) // Foreign key
71
+ @@index([categoryId, isActive]) // Composite filter
72
+ @@unique([email]) // Unique constraint
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Migration Workflow
78
+
79
+ 1. Edit `prisma/schema.prisma`
80
+ 2. Run: `npm run prisma:migrate dev --name descriptive_name`
81
+ 3. If conflict: `npm run prisma:reset` then `npm run prisma:seed`
82
+ 4. Verify generated SQL and use `prisma:studio` to inspect
83
+
84
+ **Adding NOT NULL column**: Must provide a default value or make it optional, otherwise Prisma will error.
85
+
86
+ ---
87
+
88
+ ## Production Deployment
89
+
90
+ **Pre-deploy**: All migrations tested locally, seed runs, migration files committed to git.
91
+
92
+ ```bash
93
+ npm run prisma:migrate deploy # Apply pending migrations (safe)
94
+ npm run prisma:generate # Regenerate client
95
+ npm start # Restart application
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Troubleshooting
101
+
102
+ | Symptom | Fix |
103
+ |---------|-----|
104
+ | `DATABASE_URL` not found | Verify `.env` exists with valid `DATABASE_URL`. If Prisma 7 issue, downgrade to v6. |
105
+ | "Table already exists" | `npm run prisma:reset` (dev only) |
106
+ | "Prisma Client is not generated" | `npm run prisma:generate` |
107
+ | Slow queries | Add missing indexes via schema, use `EXPLAIN`, use `select` to limit fields |
108
+
109
+ ---
110
+
111
+ ## Commands Reference
112
+
113
+ ```bash
114
+ # Development
115
+ npm run prisma:studio # Visual DB browser
116
+ npm run prisma:migrate dev # Create migration
117
+ npm run prisma:reset # Drop all, rerun migrations
118
+ npm run prisma:seed # Repopulate test data
119
+ npm run prisma:generate # Regenerate client
120
+
121
+ # Production
122
+ npm run prisma:migrate deploy # Apply migrations (safe)
123
+ npm run prisma:generate # Regenerate client
124
+ ```
@@ -0,0 +1,150 @@
1
+ > **SCOPE**: These rules apply specifically to the **server** directory.
2
+
3
+ # Project Conventions
4
+
5
+ ## Stack
6
+
7
+ | Technology | Purpose |
8
+ |------------|---------|
9
+ | Node.js 20+ LTS | Runtime |
10
+ | Fastify | HTTP framework (plugins, route prefixes, centralized error handling) |
11
+ | TypeScript (strict) | Language — always type parameters and return types |
12
+ | MySQL 8.0+ | Primary database |
13
+ | Prisma 6.x | ORM, migrations, schema source of truth |
14
+ | Redis | Caching (frequently accessed data, lookups), rate limiting, background task signaling |
15
+ | Zod | Runtime validation and type inference |
16
+ | JWT (HS256/RS256) | Auth — minimal token payload (id, role), role-based access control |
17
+ | PM2 + Nginx | Production deployment (cluster mode) |
18
+ | Jest / Vitest | Testing — deterministic, mock external APIs |
19
+
20
+ ## Package Manager
21
+
22
+ Detect from lockfile: `pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, else → npm.
23
+
24
+ ## Folder Structure
25
+
26
+ ```
27
+ src/
28
+ ├── app.ts # Fastify instance and plugin registration
29
+ ├── server.ts # listen() call only, no app logic
30
+ ├── config/ # env, config, constants
31
+ ├── libs/ # shared libraries (db, redis, logger, auth)
32
+ └── modules/<domain>/ # domain modules
33
+ ├── <domain>.routes.ts
34
+ ├── <domain>.controller.ts
35
+ ├── <domain>.service.ts
36
+ ├── <domain>.repo.ts
37
+ ├── <domain>.schemas.ts
38
+ └── <domain>.types.ts (if needed)
39
+ ```
40
+
41
+ New modules MUST follow `src/modules/<name>/` with the exact naming pattern above.
42
+
43
+ ## API Routes
44
+
45
+ All routes prefixed with `/api/v1`. Grouped by domain: `/api/v1/<domain>/...`
46
+
47
+ Register routes as Fastify plugins in `<domain>.routes.ts`.
48
+
49
+ ## Coding Style
50
+
51
+ - TypeScript `strict` mode ON
52
+ - `async/await` over `.then()`
53
+ - Named exports (except `src/app.ts` which default-exports the Fastify instance)
54
+ - Relative imports within a module (`./user.service`), aliased imports cross-module (`@modules/users/...`)
55
+ - Use `logger` from `src/libs/logger` — never `console.log`
56
+
57
+ ## Layer Responsibilities
58
+
59
+ | Layer | MUST | MUST NOT |
60
+ |-------|------|----------|
61
+ | **Routes** | Register Fastify plugins, define HTTP method + path | Contain any logic |
62
+ | **Controllers** | Validate input (Zod), call services, return via `successResponse`/`paginatedResponse`, throw typed `AppError` | Business logic, direct DB access, manual error JSON, set HTTP status codes for errors |
63
+ | **Services** | All business logic, throw `AppError` subclasses, call repos for DB ops, be stateless | Touch Fastify (request/reply), format responses, return HTTP status codes |
64
+ | **Repositories** | Prisma queries, return raw data | Business logic, error formatting |
65
+
66
+ ## Postman Collection
67
+
68
+ When API endpoints are created or modified, **always update the Postman collection** (`postman/collection.json`). If the collection does not exist, create it.
69
+
70
+ ### Collection Structure
71
+
72
+ ```
73
+ postman/
74
+ ├── collection.json # Postman v2.1 collection
75
+ └── environment.json # Environment variables template
76
+ ```
77
+
78
+ ### Environment Variables
79
+
80
+ Use variables so requests are connected and portable:
81
+
82
+ ```json
83
+ {
84
+ "variables": [
85
+ { "key": "baseUrl", "value": "http://localhost:3000/api/v1" },
86
+ { "key": "accessToken", "value": "" },
87
+ { "key": "refreshToken", "value": "" },
88
+ { "key": "userId", "value": "" }
89
+ ]
90
+ }
91
+ ```
92
+
93
+ - **`{{baseUrl}}`** — all request URLs use this prefix, never hardcoded hosts.
94
+ - **`{{accessToken}}`** / **`{{refreshToken}}`** — set automatically via login/register test scripts.
95
+ - **`{{userId}}`** and other IDs — captured from responses so subsequent requests can reference them.
96
+
97
+ ### Auto-set Tokens via Test Scripts
98
+
99
+ The **Login** and **Register** requests MUST include a `Tests` script that stores tokens and user data into collection variables:
100
+
101
+ ```javascript
102
+ if (pm.response.code === 200 || pm.response.code === 201) {
103
+ const res = pm.response.json();
104
+ pm.collectionVariables.set("accessToken", res.data.tokens.accessToken);
105
+ pm.collectionVariables.set("refreshToken", res.data.tokens.refreshToken);
106
+ pm.collectionVariables.set("userId", res.data.user.id);
107
+ }
108
+ ```
109
+
110
+ ### Auth Header
111
+
112
+ All authenticated requests use a collection-level or folder-level **Bearer Token** auth set to `{{accessToken}}`. Do not duplicate the auth header on every request — inherit from the parent folder.
113
+
114
+ ### Folder Organization
115
+
116
+ Organize requests into folders matching domain modules:
117
+
118
+ ```
119
+ Collection Root (Bearer Token: {{accessToken}})
120
+ ├── Auth (No Auth)
121
+ │ ├── Register → POST {{baseUrl}}/auth/register
122
+ │ ├── Login → POST {{baseUrl}}/auth/login [sets tokens]
123
+ │ ├── Refresh Token → POST {{baseUrl}}/auth/refresh
124
+ │ └── Logout → POST {{baseUrl}}/auth/logout
125
+ ├── Users
126
+ │ ├── Get Me → GET {{baseUrl}}/users/me
127
+ │ ├── Update Me → PATCH {{baseUrl}}/users/me
128
+ │ └── Delete Me → DELETE {{baseUrl}}/users/me
129
+ └── <Domain> → One folder per module
130
+ ├── List → GET {{baseUrl}}/<domain>
131
+ ├── Get by ID → GET {{baseUrl}}/<domain>/{{<domain>Id}}
132
+ ├── Create → POST {{baseUrl}}/<domain>
133
+ ├── Update → PATCH {{baseUrl}}/<domain>/{{<domain>Id}}
134
+ └── Delete → DELETE {{baseUrl}}/<domain>/{{<domain>Id}}
135
+ ```
136
+
137
+ ### Rules
138
+
139
+ 1. **Every route gets a request.** No endpoint should exist without a matching Postman request.
140
+ 2. **Include example request bodies** for POST/PATCH/PUT with realistic placeholder data.
141
+ 3. **Capture IDs from create responses** — add a `Tests` script that sets `{{<domain>Id}}` so Get/Update/Delete requests work without manual copy-paste.
142
+ 4. **Auth folder uses "No Auth"** — login and register don't need tokens. All other folders inherit Bearer Token from the collection root.
143
+ 5. **Keep it importable** — the collection must be valid Postman v2.1 JSON that anyone can import and run immediately after setting `baseUrl`.
144
+
145
+ ## Security
146
+
147
+ - Validate all inputs with Zod schemas
148
+ - Never interpolate raw values into SQL — always use Prisma query builder
149
+ - Rate limit public endpoints
150
+ - Avoid N+1 queries — prefer joins or batched queries
@@ -0,0 +1,144 @@
1
+ > **SCOPE**: These rules apply specifically to the **server** directory.
2
+
3
+ # Response Handling
4
+
5
+ All API responses follow a unified contract. No exceptions.
6
+
7
+ ---
8
+
9
+ ## Success Response
10
+
11
+ ```json
12
+ {
13
+ "success": true,
14
+ "message": "Human-readable message",
15
+ "data": {}
16
+ }
17
+ ```
18
+
19
+ - `data` can be an object, array, or null
20
+ - Controllers MUST use the shared `successResponse()` helper
21
+
22
+ ```typescript
23
+ // Allowed
24
+ return reply.send(successResponse("Item created successfully", item));
25
+
26
+ // Forbidden — never send raw values or custom shapes
27
+ reply.send(item);
28
+ reply.send({ data: item });
29
+ reply.send({ success: true, item });
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Error Response
35
+
36
+ ```json
37
+ {
38
+ "success": false,
39
+ "error": {
40
+ "code": "MACHINE_READABLE_CODE",
41
+ "message": "Human-readable, user-safe message"
42
+ }
43
+ }
44
+ ```
45
+
46
+ - `code`: stable machine-readable string (e.g. `RESOURCE_NOT_FOUND`, `VALIDATION_FAILED`)
47
+ - Never expose internal details, stack traces, or SQL errors
48
+ - Controllers MUST NOT manually construct error responses
49
+
50
+ ---
51
+
52
+ ## Paginated Response
53
+
54
+ ```json
55
+ {
56
+ "success": true,
57
+ "message": "string",
58
+ "data": {
59
+ "items": [],
60
+ "pagination": {
61
+ "page": 1,
62
+ "limit": 10,
63
+ "totalItems": 237,
64
+ "totalPages": 24,
65
+ "hasNextPage": true,
66
+ "hasPreviousPage": false
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ Controllers MUST use the shared `paginatedResponse()` helper:
73
+
74
+ ```typescript
75
+ function paginatedResponse<T>(message: string, items: T[], page: number, limit: number, totalItems: number)
76
+
77
+ // Allowed
78
+ return reply.send(paginatedResponse("Items retrieved successfully", items, page, limit, totalCount));
79
+ ```
80
+
81
+ ### Pagination Input Validation
82
+
83
+ All paginated endpoints MUST validate with this shared Zod schema:
84
+
85
+ ```typescript
86
+ const PaginationSchema = z.object({
87
+ page: z.coerce.number().int().min(1).default(1),
88
+ limit: z.coerce.number().int().min(1).max(100).default(10)
89
+ });
90
+ ```
91
+
92
+ ### Service Return Pattern for Pagination
93
+
94
+ Services return `{ items, totalItems }`. Controllers build the pagination metadata.
95
+
96
+ ```typescript
97
+ // Service returns:
98
+ return { items, totalItems: count };
99
+ // Offset calculation: (page - 1) * limit
100
+ ```
101
+
102
+ ### Filters with Pagination
103
+
104
+ - Apply filters/sorting BEFORE `limit` and `offset`
105
+ - Count total items AFTER filters but BEFORE pagination
106
+ - Pages start at 1 (never 0)
107
+
108
+ ---
109
+
110
+ ## Error Architecture
111
+
112
+ ONE global Fastify error handler via `fastify.setErrorHandler(...)`:
113
+ - Maps `AppError` subclasses to HTTP status codes
114
+ - Formats the unified error JSON structure
115
+ - Logs internal details server-side only — never sends to client
116
+
117
+ ### AppError Subclasses
118
+
119
+ All errors MUST extend `AppError` (which defines `code`, `message`, `statusCode`). Only throw these typed subclasses — never raw `Error`, strings, or plain objects.
120
+
121
+ | Class | Status | Example Code |
122
+ |-------|--------|--------------|
123
+ | `BadRequestError` | 400 | `INVALID_INPUT` |
124
+ | `ValidationError` | 422 | `VALIDATION_FAILED` |
125
+ | `UnauthorizedError` | 401 | `UNAUTHORIZED` |
126
+ | `ForbiddenError` | 403 | `FORBIDDEN` |
127
+ | `NotFoundError` | 404 | `RESOURCE_NOT_FOUND` |
128
+ | `ConflictError` | 409 | `EMAIL_ALREADY_EXISTS` |
129
+ | `InternalError` | 500 | `INTERNAL_ERROR` |
130
+
131
+ ---
132
+
133
+ ## Controller Response Rules
134
+
135
+ **ALWAYS:**
136
+ - Use `successResponse()` or `paginatedResponse()` for all responses
137
+ - Validate input with Zod before calling services
138
+ - Throw typed `AppError` subclasses on failure
139
+
140
+ **NEVER:**
141
+ - Send raw values or custom JSON shapes
142
+ - Set HTTP status codes for errors (global handler does this)
143
+ - Catch errors unless rethrowing as typed `AppError`
144
+ - Manually construct error response JSON
@@ -0,0 +1,5 @@
1
+ # Public API URL - points to the server's base URL
2
+ NEXT_PUBLIC_API_BASE_URL=http://localhost:8000/api/v1
3
+
4
+ # App name displayed in the UI
5
+ NEXT_PUBLIC_APP_NAME={{PROJECT_DISPLAY_NAME}}
@@ -0,0 +1,36 @@
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
+
3
+ ## Getting Started
4
+
5
+ First, run the development server:
6
+
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
+
19
+ You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20
+
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
+
23
+ ## Learn More
24
+
25
+ To learn more about Next.js, take a look at the following resources:
26
+
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
+
32
+ ## Deploy on Vercel
33
+
34
+ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
+
36
+ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "rtl": false,
15
+ "aliases": {
16
+ "components": "@/components",
17
+ "utils": "@/lib/utils",
18
+ "ui": "@/components/ui",
19
+ "lib": "@/lib",
20
+ "hooks": "@/hooks"
21
+ },
22
+ "registries": {}
23
+ }
@@ -0,0 +1,18 @@
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTs from "eslint-config-next/typescript";
4
+
5
+ const eslintConfig = defineConfig([
6
+ ...nextVitals,
7
+ ...nextTs,
8
+ // Override default ignores of eslint-config-next.
9
+ globalIgnores([
10
+ // Default ignores of eslint-config-next:
11
+ ".next/**",
12
+ "out/**",
13
+ "build/**",
14
+ "next-env.d.ts",
15
+ ]),
16
+ ]);
17
+
18
+ export default eslintConfig;
@@ -0,0 +1,34 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ async headers() {
5
+ return [
6
+ {
7
+ source: "/(.*)",
8
+ headers: [
9
+ { key: "X-Frame-Options", value: "DENY" },
10
+ { key: "X-Content-Type-Options", value: "nosniff" },
11
+ { key: "Referrer-Policy", value: "origin-when-cross-origin" },
12
+ { key: "X-XSS-Protection", value: "1; mode=block" },
13
+ {
14
+ key: "Content-Security-Policy",
15
+ value: [
16
+ "default-src 'self'",
17
+ "script-src 'self' 'unsafe-eval' 'unsafe-inline'",
18
+ "style-src 'self' 'unsafe-inline'",
19
+ "img-src 'self' blob: data: https:",
20
+ "font-src 'self'",
21
+ "object-src 'none'",
22
+ "base-uri 'self'",
23
+ "form-action 'self'",
24
+ "frame-ancestors 'none'",
25
+ `connect-src 'self' ${process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"}`,
26
+ ].join("; "),
27
+ },
28
+ ],
29
+ },
30
+ ];
31
+ },
32
+ };
33
+
34
+ export default nextConfig;
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}-client",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "eslint src/"
10
+ },
11
+ "dependencies": {
12
+ "@hookform/resolvers": "^5.2.2",
13
+ "@reduxjs/toolkit": "^2.11.2",
14
+ "@tanstack/react-query": "^5.90.21",
15
+ "axios": "^1.13.5",
16
+ "class-variance-authority": "^0.7.1",
17
+ "clsx": "^2.1.1",
18
+ "lucide-react": "^0.563.0",
19
+ "next": "16.1.6",
20
+ "next-themes": "^0.4.6",
21
+ "radix-ui": "^1.4.3",
22
+ "react": "19.2.3",
23
+ "react-dom": "19.2.3",
24
+ "react-hook-form": "^7.71.1",
25
+ "react-redux": "^9.2.0",
26
+ "sonner": "^2.0.7",
27
+ "tailwind-merge": "^3.4.0",
28
+ "tailwindcss-animate": "^1.0.7",
29
+ "zod": "^4.3.6"
30
+ },
31
+ "devDependencies": {
32
+ "@tailwindcss/postcss": "^4",
33
+ "@tanstack/react-query-devtools": "^5.91.3",
34
+ "@types/node": "^20",
35
+ "@types/react": "^19",
36
+ "@types/react-dom": "^19",
37
+ "eslint": "^9",
38
+ "eslint-config-next": "16.1.6",
39
+ "shadcn": "^3.8.4",
40
+ "tailwindcss": "^4",
41
+ "tw-animate-css": "^1.4.0",
42
+ "typescript": "^5"
43
+ }
44
+ }
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1,18 @@
1
+ import type React from 'react';
2
+
3
+ import { APP_NAME } from '@/lib/constants/app.constants';
4
+
5
+ export default function AuthLayout({
6
+ children,
7
+ }: {
8
+ children: React.ReactNode;
9
+ }): React.ReactElement {
10
+ return (
11
+ <div className="flex min-h-dvh flex-col items-center justify-center px-4 py-12">
12
+ <span className="mb-8 text-xl font-semibold tracking-tight text-foreground">
13
+ {APP_NAME}
14
+ </span>
15
+ {children}
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,13 @@
1
+ import type React from 'react';
2
+
3
+ import { LoginForm } from '@/features/auth/components/LoginForm';
4
+
5
+ import type { Metadata } from 'next';
6
+
7
+ export const metadata: Metadata = {
8
+ title: 'Sign in',
9
+ };
10
+
11
+ export default function LoginPage(): React.ReactElement {
12
+ return <LoginForm />;
13
+ }
@@ -0,0 +1,13 @@
1
+ import type React from 'react';
2
+
3
+ import { RegisterForm } from '@/features/auth/components/RegisterForm';
4
+
5
+ import type { Metadata } from 'next';
6
+
7
+ export const metadata: Metadata = {
8
+ title: 'Create account',
9
+ };
10
+
11
+ export default function RegisterPage(): React.ReactElement {
12
+ return <RegisterForm />;
13
+ }
@@ -0,0 +1,22 @@
1
+ import type React from 'react';
2
+
3
+ import type { Metadata } from 'next';
4
+
5
+ export const metadata: Metadata = {
6
+ title: 'Dashboard',
7
+ };
8
+
9
+ export default function DashboardPage(): React.ReactElement {
10
+ return (
11
+ <div className="container mx-auto px-4 py-12 md:px-6 lg:px-8">
12
+ <div className="space-y-6">
13
+ <div>
14
+ <h1 className="text-3xl font-bold tracking-tight">Dashboard</h1>
15
+ <p className="mt-2 text-muted-foreground">
16
+ Welcome to your dashboard. This is where you&apos;ll manage everything.
17
+ </p>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ );
22
+ }
@@ -0,0 +1,11 @@
1
+ import type React from 'react';
2
+
3
+ import { MainLayout } from '@/components/layout/MainLayout';
4
+
5
+ export default function MainGroupLayout({
6
+ children,
7
+ }: {
8
+ children: React.ReactNode;
9
+ }): React.ReactElement {
10
+ return <MainLayout>{children}</MainLayout>;
11
+ }