@sundaysf/cli-v2 1.0.0 → 1.0.3

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 (108) hide show
  1. package/README.md +178 -178
  2. package/dist/README.md +178 -178
  3. package/dist/bin/generators/class.js.map +1 -1
  4. package/dist/bin/generators/postman.js.map +1 -1
  5. package/dist/bin/index.js +1 -1
  6. package/dist/bin/index.js.map +1 -1
  7. package/dist/templates/backend/.claude/agents/knex-table-implementer.md +113 -113
  8. package/dist/templates/backend/.claude/agents/sundays-backend-builder.md +70 -70
  9. package/dist/templates/backend/.claude/settings.local.json +13 -13
  10. package/dist/templates/backend/.env.example +13 -13
  11. package/dist/templates/backend/.prettierignore +2 -2
  12. package/dist/templates/backend/.prettierrc +9 -9
  13. package/dist/templates/backend/CLAUDE.md +348 -348
  14. package/dist/templates/backend/Dockerfile +14 -14
  15. package/dist/templates/backend/README.md +18 -18
  16. package/dist/templates/backend/eslint.config.js +20 -20
  17. package/dist/templates/backend/src/app.ts +34 -34
  18. package/dist/templates/backend/src/common/config/origins/origins.config.ts +11 -11
  19. package/dist/templates/backend/src/common/utils/environment.resolver.ts +3 -3
  20. package/dist/templates/backend/src/common/utils/version.resolver.ts +4 -4
  21. package/dist/templates/backend/src/controllers/health/health.controller.ts +23 -23
  22. package/dist/templates/backend/src/middlewares/error/error.middleware.ts +21 -21
  23. package/dist/templates/backend/src/routes/health/health.router.ts +16 -16
  24. package/dist/templates/backend/src/routes/index.ts +57 -57
  25. package/dist/templates/backend/src/server.ts +16 -16
  26. package/dist/templates/backend/src/types.d.ts +10 -10
  27. package/dist/templates/backend/tsconfig.json +16 -16
  28. package/dist/templates/backend-db-sql/.claude/agents/knex-table-implementer.md +114 -114
  29. package/dist/templates/backend-db-sql/.claude/agents/sundays-backend-builder.md +70 -70
  30. package/dist/templates/backend-db-sql/.claude/settings.local.json +19 -19
  31. package/dist/templates/backend-db-sql/.env.example +14 -13
  32. package/dist/templates/backend-db-sql/.prettierignore +2 -2
  33. package/dist/templates/backend-db-sql/.prettierrc +9 -9
  34. package/dist/templates/backend-db-sql/CLAUDE.md +374 -374
  35. package/dist/templates/backend-db-sql/Dockerfile +17 -17
  36. package/dist/templates/backend-db-sql/README.md +34 -34
  37. package/dist/templates/backend-db-sql/db/knexfile.ts +33 -34
  38. package/dist/templates/backend-db-sql/db/migrations/001_create_sundays_package_version.ts +12 -12
  39. package/dist/templates/backend-db-sql/db/seeds/001_sundays_package_version_seed.ts +10 -10
  40. package/dist/templates/backend-db-sql/db/src/KnexConnection.ts +74 -74
  41. package/dist/templates/backend-db-sql/db/src/d.types.ts +18 -18
  42. package/dist/templates/backend-db-sql/db/src/dao/sundays-package-version/sundays-package-version.dao.ts +71 -71
  43. package/dist/templates/backend-db-sql/db/src/index.ts +9 -9
  44. package/dist/templates/backend-db-sql/db/src/interfaces/sundays-package-version/sundays-package-version.interfaces.ts +6 -6
  45. package/dist/templates/backend-db-sql/db/tsconfig.json +16 -16
  46. package/dist/templates/backend-db-sql/eslint.config.js +20 -20
  47. package/dist/templates/backend-db-sql/src/app.ts +34 -34
  48. package/dist/templates/backend-db-sql/src/common/config/origins/origins.config.ts +11 -11
  49. package/dist/templates/backend-db-sql/src/common/utils/environment.resolver.ts +3 -3
  50. package/dist/templates/backend-db-sql/src/common/utils/version.resolver.ts +4 -4
  51. package/dist/templates/backend-db-sql/src/controllers/health/health.controller.ts +23 -23
  52. package/dist/templates/backend-db-sql/src/middlewares/error/error.middleware.ts +21 -21
  53. package/dist/templates/backend-db-sql/src/routes/health/health.router.ts +16 -16
  54. package/dist/templates/backend-db-sql/src/routes/index.ts +57 -57
  55. package/dist/templates/backend-db-sql/src/server.ts +18 -18
  56. package/dist/templates/backend-db-sql/src/types.d.ts +10 -10
  57. package/dist/templates/backend-db-sql/tsconfig.json +16 -16
  58. package/dist/templates/backend-embedded-db-sql/.claude/agents/knex-table-implementer.md +116 -0
  59. package/dist/templates/backend-embedded-db-sql/.claude/agents/sundays-backend-builder.md +70 -0
  60. package/dist/templates/backend-embedded-db-sql/.claude/settings.local.json +18 -0
  61. package/dist/templates/backend-embedded-db-sql/.env.example +14 -0
  62. package/dist/templates/backend-embedded-db-sql/.prettierignore +3 -0
  63. package/dist/templates/backend-embedded-db-sql/.prettierrc +9 -0
  64. package/dist/templates/backend-embedded-db-sql/CLAUDE.md +371 -0
  65. package/dist/templates/backend-embedded-db-sql/Dockerfile +14 -0
  66. package/dist/templates/backend-embedded-db-sql/README.md +32 -0
  67. package/dist/templates/backend-embedded-db-sql/eslint.config.js +20 -0
  68. package/dist/templates/backend-embedded-db-sql/knexfile.ts +37 -0
  69. package/dist/templates/backend-embedded-db-sql/migrations/.gitkeep +0 -0
  70. package/dist/templates/backend-embedded-db-sql/migrations/001_create_sundays_package_version.ts +13 -0
  71. package/dist/templates/backend-embedded-db-sql/seeds/001_sundays_package_version_seed.ts +11 -0
  72. package/dist/templates/backend-embedded-db-sql/src/app.ts +35 -0
  73. package/dist/templates/backend-embedded-db-sql/src/common/config/origins/origins.config.ts +11 -0
  74. package/dist/templates/backend-embedded-db-sql/src/common/utils/environment.resolver.ts +4 -0
  75. package/dist/templates/backend-embedded-db-sql/src/common/utils/version.resolver.ts +5 -0
  76. package/dist/templates/backend-embedded-db-sql/src/controllers/health/health.controller.ts +24 -0
  77. package/dist/templates/backend-embedded-db-sql/src/db/KnexConnection.ts +74 -0
  78. package/dist/templates/backend-embedded-db-sql/src/db/d.types.ts +18 -0
  79. package/dist/templates/backend-embedded-db-sql/src/db/dao/sundays-package-version/sundays-package-version.dao.ts +71 -0
  80. package/dist/templates/backend-embedded-db-sql/src/db/index.ts +9 -0
  81. package/dist/templates/backend-embedded-db-sql/src/db/interfaces/sundays-package-version/sundays-package-version.interfaces.ts +6 -0
  82. package/dist/templates/backend-embedded-db-sql/src/middlewares/error/error.middleware.ts +21 -0
  83. package/dist/templates/backend-embedded-db-sql/src/routes/health/health.router.ts +17 -0
  84. package/dist/templates/backend-embedded-db-sql/src/routes/index.ts +57 -0
  85. package/dist/templates/backend-embedded-db-sql/src/server.ts +18 -0
  86. package/dist/templates/backend-embedded-db-sql/src/types.d.ts +10 -0
  87. package/dist/templates/backend-embedded-db-sql/tsconfig.json +16 -0
  88. package/dist/templates/db-sql/.claude/agents/knex-table-implementer.md +113 -113
  89. package/dist/templates/db-sql/.claude/agents/sundays-backend-builder.md +70 -70
  90. package/dist/templates/db-sql/.claude/settings.local.json +10 -10
  91. package/dist/templates/db-sql/.env.example +8 -8
  92. package/dist/templates/db-sql/CLAUDE.md +105 -105
  93. package/dist/templates/db-sql/knexfile.ts +33 -33
  94. package/dist/templates/db-sql/migrations/001_create_sundays_package_version.ts +12 -12
  95. package/dist/templates/db-sql/seeds/001_sundays_package_version_seed.ts +10 -10
  96. package/dist/templates/db-sql/src/KnexConnection.ts +74 -74
  97. package/dist/templates/db-sql/src/d.types.ts +18 -18
  98. package/dist/templates/db-sql/src/dao/sundays-package-version/sundays-package-version.dao.ts +71 -71
  99. package/dist/templates/db-sql/src/index.ts +9 -9
  100. package/dist/templates/db-sql/src/interfaces/sundays-package-version/sundays-package-version.interfaces.ts +6 -6
  101. package/dist/templates/db-sql/tsconfig.json +16 -16
  102. package/dist/templates/module/.claude/agents/knex-table-implementer.md +113 -113
  103. package/dist/templates/module/.claude/agents/sundays-backend-builder.md +70 -70
  104. package/dist/templates/module/.claude/settings.local.json +10 -10
  105. package/dist/templates/module/CLAUDE.md +158 -158
  106. package/dist/templates/module/src/index.ts +9 -9
  107. package/dist/templates/module/tsconfig.json +19 -19
  108. package/package.json +40 -40
@@ -1,348 +1,348 @@
1
- # CLAUDE.md
2
-
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## Commands
6
-
7
- ### Development
8
-
9
- - `npm run start:dev` - Start development server with auto-reload (uses nodemon)
10
- - `npm run build` - Compile TypeScript to JavaScript (output to dist/)
11
- - `npm start` - Run production server from compiled dist/
12
-
13
- ### Code Quality
14
-
15
- - `npm run format` - Format code with Prettier
16
- - No linting command configured - consider using ESLint with the installed configuration
17
-
18
- ### Testing
19
-
20
- - `npm test` - Run tests with Jest (no test files found yet)
21
-
22
- ### Utilities
23
-
24
- - `npm run create:controller` - Generate new controller using Sundays Framework CLI
25
-
26
- ## Architecture
27
-
28
- This is a Sundays Framework backend built with Express.js and TypeScript following a modular MVC pattern.
29
-
30
- ### Core Structure
31
-
32
- - **Entry Points**: `src/server.ts` initializes the server, `src/app.ts` configures Express middleware
33
- - **Routing**: Dynamic route loading system in `src/routes/index.ts` that automatically discovers and mounts routers from subdirectories
34
- - **Controllers**: Business logic separated into controller classes (e.g., `HealthController`, `LogsController`)
35
- - **Middleware**: Error handling middleware in `src/middlewares/error/`
36
- - **Configuration**: CORS origins managed in `src/common/config/origins/`
37
-
38
- ### Key Patterns
39
-
40
- 1. **Router Auto-Discovery**: The `IndexRouter` class scans the routes directory and automatically mounts any `*.router.ts` files found in subdirectories. Routes are mounted at `/{folder-name}`.
41
-
42
- 2. **Controller Pattern**: Each router has a corresponding controller class that handles the business logic. Controllers are bound to router methods using `.bind()` to maintain proper context.
43
-
44
- 3. **Environment Configuration**: Uses dotenv for environment variables. Server port defaults to 3005 if not specified in .env.
45
-
46
- 4. **CORS Configuration**: Dynamic CORS origin configuration via `getAllowedOrigins()` function.
47
-
48
- 5. **Dependencies**:
49
- - Sundays Framework utilities (`@sundaysf/utils`) for pagination and validation
50
- - Standard Express middleware (cors, morgan, etc.)
51
-
52
- ### TypeScript Configuration
53
-
54
- - Strict mode enabled with all strict checks
55
- - Compiles to ES2016, CommonJS modules
56
- - Source in `./src`, output to `./dist`
57
- - Unused locals will cause compilation errors
58
-
59
- ## Controller Implementation Guide
60
-
61
- When creating new controllers, follow the established pattern demonstrated in the CompanyController:
62
-
63
- ### 1. Controller Structure
64
- ```typescript
65
- import { Request, Response, NextFunction } from "express";
66
- import { IBaseController } from "../../types";
67
- import { inputValidator, IInputValidator, paginationHelper } from "@sundaysf/utils";
68
- // Import your DAO and interfaces from your db-sql module
69
- // import { [Entity]DAO, I[Entity], IDataPaginator } from "your-db-module";
70
- import { [Entity]CreateInputDTO } from "../../dto/input/[entity]/[entity].create.dto";
71
- import { [Entity]UpdateInputDTO } from "../../dto/input/[entity]/[entity].update.dto";
72
- import { v4 as uuidv4 } from 'uuid';
73
-
74
- export class [Entity]Controller implements IBaseController {
75
- private _[entity]DAO: [Entity]DAO = new [Entity]DAO();
76
-
77
- // Implement CRUD methods
78
- }
79
- ```
80
-
81
- ### 2. API Response Format
82
-
83
- All API responses must follow a consistent format:
84
-
85
- **Success Response**:
86
- ```json
87
- {
88
- "success": true,
89
- "data": {...} // or "message": "Action completed successfully" for operations without data
90
- }
91
- ```
92
-
93
- **Error Response**:
94
- ```json
95
- {
96
- "success": false,
97
- "message": "Error description"
98
- }
99
- ```
100
-
101
- **Paginated Response** (from IDataPaginator):
102
- ```json
103
- {
104
- "success": true,
105
- "data": [...],
106
- "page": 1,
107
- "limit": 10,
108
- "count": 10,
109
- "totalCount": 100,
110
- "totalPages": 10
111
- }
112
- ```
113
-
114
- Note: The `IDataPaginator` interface already includes the standard response format with `success` and `data` fields, so responses from paginated endpoints should be returned directly without additional wrapping.
115
-
116
- ### 3. Standard CRUD Methods
117
-
118
- **getAll** - List with pagination:
119
- ```typescript
120
- public async getAll(req: Request, res: Response, next: NextFunction): Promise<void> {
121
- try {
122
- const {page, limit} = paginationHelper(req);
123
- const result: IDataPaginator<I[Entity]> = await this._[entity]DAO.getAll(page, limit);
124
- res.status(200).json(result); // IDataPaginator already includes success and data fields
125
- } catch (err: any) {
126
- next(err);
127
- }
128
- }
129
- ```
130
-
131
- **getByUuid** - Get single resource:
132
- ```typescript
133
- public async getByUuid(req: Request, res: Response, next: NextFunction): Promise<void> {
134
- try {
135
- const { uuid } = req.params;
136
- const result = await this._[entity]DAO.getByUuid(uuid);
137
- if (!result) {
138
- res.status(404).json({ success: false, message: "[Entity] not found" });
139
- return;
140
- }
141
- res.status(200).json({
142
- success: true,
143
- data: result
144
- });
145
- } catch (err: any) {
146
- next(err);
147
- }
148
- }
149
- ```
150
-
151
- **create** - Create new resource with DTO validation:
152
- ```typescript
153
- public async create(req: Request, res: Response, next: NextFunction): Promise<void> {
154
- try {
155
- const data = req.body;
156
- const inputDTO = new [Entity]CreateInputDTO(data).build();
157
- const validation: IInputValidator = await inputValidator(inputDTO);
158
- if (!validation.success) {
159
- req.statusCode = 400;
160
- return next(new Error(validation.message));
161
- }
162
- const dataToCreate = {...inputDTO, uuid: uuidv4()};
163
- const result = await this._[entity]DAO.create(dataToCreate);
164
- res.status(201).json({
165
- success: true,
166
- data: result
167
- });
168
- } catch (err: any) {
169
- next(err);
170
- }
171
- }
172
- ```
173
-
174
- **update** - Update existing resource with DTO validation:
175
- ```typescript
176
- public async update(req: Request, res: Response, next: NextFunction): Promise<void> {
177
- try {
178
- const { uuid } = req.params;
179
- const data = req.body;
180
-
181
- // First get the entity by UUID to find its ID
182
- const existing = await this._[entity]DAO.getByUuid(uuid);
183
- if (!existing || !existing.id) {
184
- res.status(404).json({ success: false, message: "[Entity] not found" });
185
- return;
186
- }
187
-
188
- const inputDTO = new [Entity]UpdateInputDTO(data).build();
189
- const validation: IInputValidator = await inputValidator(inputDTO);
190
- if (!validation.success) {
191
- req.statusCode = 400;
192
- return next(new Error(validation.message));
193
- }
194
-
195
- const result = await this._[entity]DAO.update(existing.id, inputDTO);
196
- res.status(200).json({
197
- success: true,
198
- data: result
199
- });
200
- } catch (err: any) {
201
- next(err);
202
- }
203
- }
204
- ```
205
-
206
- **delete** - Delete resource:
207
- ```typescript
208
- public async delete(req: Request, res: Response, next: NextFunction): Promise<void> {
209
- try {
210
- const { uuid } = req.params;
211
-
212
- // First get the entity by UUID to find its ID
213
- const existing = await this._[entity]DAO.getByUuid(uuid);
214
- if (!existing || !existing.id) {
215
- res.status(404).json({ success: false, message: "[Entity] not found" });
216
- return;
217
- }
218
-
219
- const result = await this._[entity]DAO.delete(existing.id);
220
- if (result) {
221
- res.status(200).json({ success: true, message: "[Entity] deleted successfully" });
222
- } else {
223
- res.status(404).json({ success: false, message: "Failed to delete [entity]" });
224
- }
225
- } catch (err: any) {
226
- next(err);
227
- }
228
- }
229
- ```
230
-
231
- ### 4. Router Implementation
232
- Create a corresponding router in `src/routes/[entity]/[entity].router.ts`:
233
-
234
- ```typescript
235
- import { Router } from "express";
236
- import { [Entity]Controller } from "../../controllers/[entity]/[entity].controller";
237
-
238
- export class [Entity]Router {
239
- private _router: Router;
240
- private _[entity]Controller = new [Entity]Controller();
241
-
242
- constructor() {
243
- this._router = Router();
244
- this.initRoutes();
245
- }
246
-
247
- private initRoutes(): void {
248
- this._router.get("/", this._[entity]Controller.getAll.bind(this._[entity]Controller));
249
- this._router.get("/:uuid", this._[entity]Controller.getByUuid.bind(this._[entity]Controller));
250
- this._router.post("/", this._[entity]Controller.create.bind(this._[entity]Controller));
251
- this._router.put("/:uuid", this._[entity]Controller.update.bind(this._[entity]Controller));
252
- this._router.delete("/:uuid", this._[entity]Controller.delete.bind(this._[entity]Controller));
253
- }
254
-
255
- public get router(): Router {
256
- return this._router;
257
- }
258
- }
259
- ```
260
-
261
- ### 5. DTO Implementation
262
-
263
- Create DTOs to validate and sanitize input data:
264
-
265
- **Create DTO** (`src/dto/input/[entity]/[entity].create.dto.ts`):
266
- ```typescript
267
- export class [Entity]CreateInputDTO {
268
- // Define only allowed properties
269
- property1: type;
270
- property2: type;
271
-
272
- constructor(data: any){
273
- this.property1 = data.property1;
274
- this.property2 = data.property2;
275
- // Set defaults for optional properties
276
- }
277
-
278
- public build(): this {
279
- return this;
280
- }
281
- }
282
- ```
283
-
284
- **Update DTO** (`src/dto/input/[entity]/[entity].update.dto.ts`):
285
- ```typescript
286
- export class [Entity]UpdateInputDTO {
287
- // Define optional properties
288
- property1?: type;
289
- property2?: type;
290
-
291
- constructor(data: any){
292
- // Only set properties that are present in the input
293
- if (data.property1 !== undefined) this.property1 = data.property1;
294
- if (data.property2 !== undefined) this.property2 = data.property2;
295
- }
296
-
297
- public build(): this {
298
- // Remove any properties that weren't set
299
- const cleanData: any = {};
300
- if (this.property1 !== undefined) cleanData.property1 = this.property1;
301
- if (this.property2 !== undefined) cleanData.property2 = this.property2;
302
-
303
- // Clear all properties and reassign only the allowed ones
304
- Object.keys(this).forEach(key => delete (this as any)[key]);
305
- Object.assign(this, cleanData);
306
-
307
- return this;
308
- }
309
- }
310
- ```
311
-
312
- ### 6. Working with Related Entities
313
-
314
- When your entity has foreign key relationships, DAOs can include related entities using PostgreSQL's `to_jsonb()` function. For example:
315
-
316
- - `UserDAO` methods return users with their associated `company` object
317
- - The join is handled at the database level for optimal performance
318
- - No additional API logic is needed to include related data
319
-
320
- Example response structure:
321
- ```json
322
- {
323
- "id": 1,
324
- "uuid": "...",
325
- "name": "John",
326
- "lastName": "Doe",
327
- "companyId": 1,
328
- "company": {
329
- "id": 1,
330
- "uuid": "...",
331
- "name": "Company Name",
332
- // ... other company fields
333
- }
334
- }
335
- ```
336
-
337
- ### 7. Important Notes
338
- - Always use `.bind()` when assigning controller methods to router to maintain proper context
339
- - Use UUID for public-facing endpoints (params) but convert to ID for internal DAO operations
340
- - Return appropriate HTTP status codes (200, 201, 404, etc.)
341
- - Pass errors to the next() function for centralized error handling
342
- - Use paginationHelper from @sundaysf/utils for consistent pagination
343
- - Use DTOs to validate and sanitize input data, preventing unwanted fields from being processed
344
- - DTOs ensure only allowed fields are passed to the DAO layer
345
- - Update DTOs should handle partial updates properly
346
- - Always generate UUID in the controller for new resources (not in DTO or client-side)
347
- - All API responses must follow the standard format: `{success: boolean, data?: any, message?: string}`
348
- - Use status 200 for successful DELETE operations (not 204) to include success message
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ ### Development
8
+
9
+ - `npm run start:dev` - Start development server with auto-reload (uses nodemon)
10
+ - `npm run build` - Compile TypeScript to JavaScript (output to dist/)
11
+ - `npm start` - Run production server from compiled dist/
12
+
13
+ ### Code Quality
14
+
15
+ - `npm run format` - Format code with Prettier
16
+ - No linting command configured - consider using ESLint with the installed configuration
17
+
18
+ ### Testing
19
+
20
+ - `npm test` - Run tests with Jest (no test files found yet)
21
+
22
+ ### Utilities
23
+
24
+ - `npm run create:controller` - Generate new controller using Sundays Framework CLI
25
+
26
+ ## Architecture
27
+
28
+ This is a Sundays Framework backend built with Express.js and TypeScript following a modular MVC pattern.
29
+
30
+ ### Core Structure
31
+
32
+ - **Entry Points**: `src/server.ts` initializes the server, `src/app.ts` configures Express middleware
33
+ - **Routing**: Dynamic route loading system in `src/routes/index.ts` that automatically discovers and mounts routers from subdirectories
34
+ - **Controllers**: Business logic separated into controller classes (e.g., `HealthController`, `LogsController`)
35
+ - **Middleware**: Error handling middleware in `src/middlewares/error/`
36
+ - **Configuration**: CORS origins managed in `src/common/config/origins/`
37
+
38
+ ### Key Patterns
39
+
40
+ 1. **Router Auto-Discovery**: The `IndexRouter` class scans the routes directory and automatically mounts any `*.router.ts` files found in subdirectories. Routes are mounted at `/{folder-name}`.
41
+
42
+ 2. **Controller Pattern**: Each router has a corresponding controller class that handles the business logic. Controllers are bound to router methods using `.bind()` to maintain proper context.
43
+
44
+ 3. **Environment Configuration**: Uses dotenv for environment variables. Server port defaults to 3005 if not specified in .env.
45
+
46
+ 4. **CORS Configuration**: Dynamic CORS origin configuration via `getAllowedOrigins()` function.
47
+
48
+ 5. **Dependencies**:
49
+ - Sundays Framework utilities (`@sundaysf/utils`) for pagination and validation
50
+ - Standard Express middleware (cors, morgan, etc.)
51
+
52
+ ### TypeScript Configuration
53
+
54
+ - Strict mode enabled with all strict checks
55
+ - Compiles to ES2016, CommonJS modules
56
+ - Source in `./src`, output to `./dist`
57
+ - Unused locals will cause compilation errors
58
+
59
+ ## Controller Implementation Guide
60
+
61
+ When creating new controllers, follow the established pattern demonstrated in the CompanyController:
62
+
63
+ ### 1. Controller Structure
64
+ ```typescript
65
+ import { Request, Response, NextFunction } from "express";
66
+ import { IBaseController } from "../../types";
67
+ import { inputValidator, IInputValidator, paginationHelper } from "@sundaysf/utils";
68
+ // Import your DAO and interfaces from your db-sql module
69
+ // import { [Entity]DAO, I[Entity], IDataPaginator } from "your-db-module";
70
+ import { [Entity]CreateInputDTO } from "../../dto/input/[entity]/[entity].create.dto";
71
+ import { [Entity]UpdateInputDTO } from "../../dto/input/[entity]/[entity].update.dto";
72
+ import { v4 as uuidv4 } from 'uuid';
73
+
74
+ export class [Entity]Controller implements IBaseController {
75
+ private _[entity]DAO: [Entity]DAO = new [Entity]DAO();
76
+
77
+ // Implement CRUD methods
78
+ }
79
+ ```
80
+
81
+ ### 2. API Response Format
82
+
83
+ All API responses must follow a consistent format:
84
+
85
+ **Success Response**:
86
+ ```json
87
+ {
88
+ "success": true,
89
+ "data": {...} // or "message": "Action completed successfully" for operations without data
90
+ }
91
+ ```
92
+
93
+ **Error Response**:
94
+ ```json
95
+ {
96
+ "success": false,
97
+ "message": "Error description"
98
+ }
99
+ ```
100
+
101
+ **Paginated Response** (from IDataPaginator):
102
+ ```json
103
+ {
104
+ "success": true,
105
+ "data": [...],
106
+ "page": 1,
107
+ "limit": 10,
108
+ "count": 10,
109
+ "totalCount": 100,
110
+ "totalPages": 10
111
+ }
112
+ ```
113
+
114
+ Note: The `IDataPaginator` interface already includes the standard response format with `success` and `data` fields, so responses from paginated endpoints should be returned directly without additional wrapping.
115
+
116
+ ### 3. Standard CRUD Methods
117
+
118
+ **getAll** - List with pagination:
119
+ ```typescript
120
+ public async getAll(req: Request, res: Response, next: NextFunction): Promise<void> {
121
+ try {
122
+ const {page, limit} = paginationHelper(req);
123
+ const result: IDataPaginator<I[Entity]> = await this._[entity]DAO.getAll(page, limit);
124
+ res.status(200).json(result); // IDataPaginator already includes success and data fields
125
+ } catch (err: any) {
126
+ next(err);
127
+ }
128
+ }
129
+ ```
130
+
131
+ **getByUuid** - Get single resource:
132
+ ```typescript
133
+ public async getByUuid(req: Request, res: Response, next: NextFunction): Promise<void> {
134
+ try {
135
+ const { uuid } = req.params;
136
+ const result = await this._[entity]DAO.getByUuid(uuid);
137
+ if (!result) {
138
+ res.status(404).json({ success: false, message: "[Entity] not found" });
139
+ return;
140
+ }
141
+ res.status(200).json({
142
+ success: true,
143
+ data: result
144
+ });
145
+ } catch (err: any) {
146
+ next(err);
147
+ }
148
+ }
149
+ ```
150
+
151
+ **create** - Create new resource with DTO validation:
152
+ ```typescript
153
+ public async create(req: Request, res: Response, next: NextFunction): Promise<void> {
154
+ try {
155
+ const data = req.body;
156
+ const inputDTO = new [Entity]CreateInputDTO(data).build();
157
+ const validation: IInputValidator = await inputValidator(inputDTO);
158
+ if (!validation.success) {
159
+ req.statusCode = 400;
160
+ return next(new Error(validation.message));
161
+ }
162
+ const dataToCreate = {...inputDTO, uuid: uuidv4()};
163
+ const result = await this._[entity]DAO.create(dataToCreate);
164
+ res.status(201).json({
165
+ success: true,
166
+ data: result
167
+ });
168
+ } catch (err: any) {
169
+ next(err);
170
+ }
171
+ }
172
+ ```
173
+
174
+ **update** - Update existing resource with DTO validation:
175
+ ```typescript
176
+ public async update(req: Request, res: Response, next: NextFunction): Promise<void> {
177
+ try {
178
+ const { uuid } = req.params;
179
+ const data = req.body;
180
+
181
+ // First get the entity by UUID to find its ID
182
+ const existing = await this._[entity]DAO.getByUuid(uuid);
183
+ if (!existing || !existing.id) {
184
+ res.status(404).json({ success: false, message: "[Entity] not found" });
185
+ return;
186
+ }
187
+
188
+ const inputDTO = new [Entity]UpdateInputDTO(data).build();
189
+ const validation: IInputValidator = await inputValidator(inputDTO);
190
+ if (!validation.success) {
191
+ req.statusCode = 400;
192
+ return next(new Error(validation.message));
193
+ }
194
+
195
+ const result = await this._[entity]DAO.update(existing.id, inputDTO);
196
+ res.status(200).json({
197
+ success: true,
198
+ data: result
199
+ });
200
+ } catch (err: any) {
201
+ next(err);
202
+ }
203
+ }
204
+ ```
205
+
206
+ **delete** - Delete resource:
207
+ ```typescript
208
+ public async delete(req: Request, res: Response, next: NextFunction): Promise<void> {
209
+ try {
210
+ const { uuid } = req.params;
211
+
212
+ // First get the entity by UUID to find its ID
213
+ const existing = await this._[entity]DAO.getByUuid(uuid);
214
+ if (!existing || !existing.id) {
215
+ res.status(404).json({ success: false, message: "[Entity] not found" });
216
+ return;
217
+ }
218
+
219
+ const result = await this._[entity]DAO.delete(existing.id);
220
+ if (result) {
221
+ res.status(200).json({ success: true, message: "[Entity] deleted successfully" });
222
+ } else {
223
+ res.status(404).json({ success: false, message: "Failed to delete [entity]" });
224
+ }
225
+ } catch (err: any) {
226
+ next(err);
227
+ }
228
+ }
229
+ ```
230
+
231
+ ### 4. Router Implementation
232
+ Create a corresponding router in `src/routes/[entity]/[entity].router.ts`:
233
+
234
+ ```typescript
235
+ import { Router } from "express";
236
+ import { [Entity]Controller } from "../../controllers/[entity]/[entity].controller";
237
+
238
+ export class [Entity]Router {
239
+ private _router: Router;
240
+ private _[entity]Controller = new [Entity]Controller();
241
+
242
+ constructor() {
243
+ this._router = Router();
244
+ this.initRoutes();
245
+ }
246
+
247
+ private initRoutes(): void {
248
+ this._router.get("/", this._[entity]Controller.getAll.bind(this._[entity]Controller));
249
+ this._router.get("/:uuid", this._[entity]Controller.getByUuid.bind(this._[entity]Controller));
250
+ this._router.post("/", this._[entity]Controller.create.bind(this._[entity]Controller));
251
+ this._router.put("/:uuid", this._[entity]Controller.update.bind(this._[entity]Controller));
252
+ this._router.delete("/:uuid", this._[entity]Controller.delete.bind(this._[entity]Controller));
253
+ }
254
+
255
+ public get router(): Router {
256
+ return this._router;
257
+ }
258
+ }
259
+ ```
260
+
261
+ ### 5. DTO Implementation
262
+
263
+ Create DTOs to validate and sanitize input data:
264
+
265
+ **Create DTO** (`src/dto/input/[entity]/[entity].create.dto.ts`):
266
+ ```typescript
267
+ export class [Entity]CreateInputDTO {
268
+ // Define only allowed properties
269
+ property1: type;
270
+ property2: type;
271
+
272
+ constructor(data: any){
273
+ this.property1 = data.property1;
274
+ this.property2 = data.property2;
275
+ // Set defaults for optional properties
276
+ }
277
+
278
+ public build(): this {
279
+ return this;
280
+ }
281
+ }
282
+ ```
283
+
284
+ **Update DTO** (`src/dto/input/[entity]/[entity].update.dto.ts`):
285
+ ```typescript
286
+ export class [Entity]UpdateInputDTO {
287
+ // Define optional properties
288
+ property1?: type;
289
+ property2?: type;
290
+
291
+ constructor(data: any){
292
+ // Only set properties that are present in the input
293
+ if (data.property1 !== undefined) this.property1 = data.property1;
294
+ if (data.property2 !== undefined) this.property2 = data.property2;
295
+ }
296
+
297
+ public build(): this {
298
+ // Remove any properties that weren't set
299
+ const cleanData: any = {};
300
+ if (this.property1 !== undefined) cleanData.property1 = this.property1;
301
+ if (this.property2 !== undefined) cleanData.property2 = this.property2;
302
+
303
+ // Clear all properties and reassign only the allowed ones
304
+ Object.keys(this).forEach(key => delete (this as any)[key]);
305
+ Object.assign(this, cleanData);
306
+
307
+ return this;
308
+ }
309
+ }
310
+ ```
311
+
312
+ ### 6. Working with Related Entities
313
+
314
+ When your entity has foreign key relationships, DAOs can include related entities using PostgreSQL's `to_jsonb()` function. For example:
315
+
316
+ - `UserDAO` methods return users with their associated `company` object
317
+ - The join is handled at the database level for optimal performance
318
+ - No additional API logic is needed to include related data
319
+
320
+ Example response structure:
321
+ ```json
322
+ {
323
+ "id": 1,
324
+ "uuid": "...",
325
+ "name": "John",
326
+ "lastName": "Doe",
327
+ "companyId": 1,
328
+ "company": {
329
+ "id": 1,
330
+ "uuid": "...",
331
+ "name": "Company Name",
332
+ // ... other company fields
333
+ }
334
+ }
335
+ ```
336
+
337
+ ### 7. Important Notes
338
+ - Always use `.bind()` when assigning controller methods to router to maintain proper context
339
+ - Use UUID for public-facing endpoints (params) but convert to ID for internal DAO operations
340
+ - Return appropriate HTTP status codes (200, 201, 404, etc.)
341
+ - Pass errors to the next() function for centralized error handling
342
+ - Use paginationHelper from @sundaysf/utils for consistent pagination
343
+ - Use DTOs to validate and sanitize input data, preventing unwanted fields from being processed
344
+ - DTOs ensure only allowed fields are passed to the DAO layer
345
+ - Update DTOs should handle partial updates properly
346
+ - Always generate UUID in the controller for new resources (not in DTO or client-side)
347
+ - All API responses must follow the standard format: `{success: boolean, data?: any, message?: string}`
348
+ - Use status 200 for successful DELETE operations (not 204) to include success message