@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
@@ -0,0 +1,14 @@
1
+ # Server
2
+ PORT=3098
3
+ ENVIRONMENT=local
4
+
5
+ # CORS (comma-separated origins with protocol)
6
+ CORS_ALLOWED_ORIGINS=http://localhost:3098
7
+
8
+ # Database
9
+ SQL_HOST=localhost
10
+ SQL_PORT=5432
11
+ SQL_USER=postgres
12
+ SQL_PASSWORD=
13
+ SQL_DB_NAME=mydb
14
+ SQL_REJECT_UNAUTHORIZED=false
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ dist/
3
+ package-lock.json
@@ -0,0 +1,9 @@
1
+ {
2
+ "tabWidth": 2,
3
+ "semi": true,
4
+ "singleQuote": true,
5
+ "trailingComma": "es5",
6
+ "bracketSpacing": true,
7
+ "bracketSameLine": true,
8
+ "arrowParens": "always"
9
+ }
@@ -0,0 +1,371 @@
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
+ ### Database
27
+
28
+ - `npm run db:migrate` - Apply pending migrations
29
+ - `npm run db:rollback` - Rollback the last batch of migrations
30
+ - `npm run db:seed` - Run seed files
31
+ - `npm run db:make-migration` - Create a new migration file
32
+
33
+ ## Architecture
34
+
35
+ This is a Sundays Framework backend built with Express.js and TypeScript following a modular MVC pattern, with the Knex.js database layer embedded in `src/db/`.
36
+
37
+ ### Core Structure
38
+
39
+ - **Entry Points**: `src/server.ts` initializes the server and connects to the database, `src/app.ts` configures Express middleware
40
+ - **Routing**: Dynamic route loading system in `src/routes/index.ts` that automatically discovers and mounts routers from subdirectories
41
+ - **Controllers**: Business logic separated into controller classes (e.g., `HealthController`)
42
+ - **Middleware**: Error handling middleware in `src/middlewares/error/`
43
+ - **Configuration**: CORS origins managed in `src/common/config/origins/`
44
+ - **Database**: `src/db/` contains the Knex.js module with DAOs, interfaces, and the KnexManager singleton
45
+
46
+ ### Key Patterns
47
+
48
+ 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}`.
49
+
50
+ 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.
51
+
52
+ 3. **Environment Configuration**: Uses dotenv for environment variables. Server port defaults to 3005 if not specified in .env.
53
+
54
+ 4. **CORS Configuration**: Dynamic CORS origin configuration via `getAllowedOrigins()` function.
55
+
56
+ 5. **Database Connection**: `src/server.ts` calls `KnexManager.connect()` before starting the Express server. The KnexManager singleton manages connection pooling.
57
+
58
+ 6. **Dependencies**:
59
+ - Sundays Framework utilities (`@sundaysf/utils`) for pagination and validation
60
+ - Standard Express middleware (cors, morgan, etc.)
61
+ - Knex.js + PostgreSQL for database access
62
+
63
+ ### Database Module (`src/db/`)
64
+
65
+ The `src/db/` directory contains the Knex.js database layer, compiled together with the rest of the application:
66
+
67
+ - **KnexManager** (`src/db/KnexConnection.ts`): Singleton for managing database connections with pooling and SSL support
68
+ - **DAO Pattern**: Data Access Objects implement `IBaseDAO<T>` with CRUD + pagination
69
+ - **Migrations**: `migrations/` (root level) - Knex migration files in TypeScript
70
+ - **Seeds**: `seeds/` (root level) - Seed data files
71
+ - **Interfaces**: `src/db/interfaces/` - Entity type definitions
72
+ - **Configuration**: `knexfile.ts` (root level) - Knex configuration for all environments
73
+
74
+ #### File Structure Conventions
75
+
76
+ **Interfaces**: `src/db/interfaces/{entity}/{entity}.interfaces.ts`
77
+ **DAOs**: `src/db/dao/{entity}/{entity}.dao.ts`
78
+
79
+ #### Environment Variables (Database)
80
+
81
+ - `SQL_HOST` - PostgreSQL host
82
+ - `SQL_PORT` - Database port (defaults to 5432)
83
+ - `SQL_USER` - Database user
84
+ - `SQL_PASSWORD` - Database password
85
+ - `SQL_DB_NAME` - Database name
86
+ - `SQL_REJECT_UNAUTHORIZED` - Set to 'false' to disable SSL certificate verification
87
+
88
+ ### TypeScript Configuration
89
+
90
+ - **Single tsconfig.json**: Strict mode, ES2022, CommonJS. Source in `./src` (including `src/db/`), output to `./dist`.
91
+
92
+ ## Controller Implementation Guide
93
+
94
+ When creating new controllers, follow the established pattern demonstrated in the CompanyController:
95
+
96
+ ### 1. Controller Structure
97
+ ```typescript
98
+ import { Request, Response, NextFunction } from "express";
99
+ import { IBaseController } from "../../types";
100
+ import { inputValidator, IInputValidator, paginationHelper } from "@sundaysf/utils";
101
+ import { [Entity]DAO, I[Entity], IDataPaginator } from "../../db";
102
+ import { [Entity]CreateInputDTO } from "../../dto/input/[entity]/[entity].create.dto";
103
+ import { [Entity]UpdateInputDTO } from "../../dto/input/[entity]/[entity].update.dto";
104
+ import { v4 as uuidv4 } from 'uuid';
105
+
106
+ export class [Entity]Controller implements IBaseController {
107
+ private _[entity]DAO: [Entity]DAO = new [Entity]DAO();
108
+
109
+ // Implement CRUD methods
110
+ }
111
+ ```
112
+
113
+ ### 2. API Response Format
114
+
115
+ All API responses must follow a consistent format:
116
+
117
+ **Success Response**:
118
+ ```json
119
+ {
120
+ "success": true,
121
+ "data": {...} // or "message": "Action completed successfully" for operations without data
122
+ }
123
+ ```
124
+
125
+ **Error Response**:
126
+ ```json
127
+ {
128
+ "success": false,
129
+ "message": "Error description"
130
+ }
131
+ ```
132
+
133
+ **Paginated Response** (from IDataPaginator):
134
+ ```json
135
+ {
136
+ "success": true,
137
+ "data": [...],
138
+ "page": 1,
139
+ "limit": 10,
140
+ "count": 10,
141
+ "totalCount": 100,
142
+ "totalPages": 10
143
+ }
144
+ ```
145
+
146
+ 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.
147
+
148
+ ### 3. Standard CRUD Methods
149
+
150
+ **getAll** - List with pagination:
151
+ ```typescript
152
+ public async getAll(req: Request, res: Response, next: NextFunction): Promise<void> {
153
+ try {
154
+ const {page, limit} = paginationHelper(req);
155
+ const result: IDataPaginator<I[Entity]> = await this._[entity]DAO.getAll(page, limit);
156
+ res.status(200).json(result); // IDataPaginator already includes success and data fields
157
+ } catch (err: any) {
158
+ next(err);
159
+ }
160
+ }
161
+ ```
162
+
163
+ **getByUuid** - Get single resource:
164
+ ```typescript
165
+ public async getByUuid(req: Request, res: Response, next: NextFunction): Promise<void> {
166
+ try {
167
+ const { uuid } = req.params;
168
+ const result = await this._[entity]DAO.getByUuid(uuid);
169
+ if (!result) {
170
+ res.status(404).json({ success: false, message: "[Entity] not found" });
171
+ return;
172
+ }
173
+ res.status(200).json({
174
+ success: true,
175
+ data: result
176
+ });
177
+ } catch (err: any) {
178
+ next(err);
179
+ }
180
+ }
181
+ ```
182
+
183
+ **create** - Create new resource with DTO validation:
184
+ ```typescript
185
+ public async create(req: Request, res: Response, next: NextFunction): Promise<void> {
186
+ try {
187
+ const data = req.body;
188
+ const inputDTO = new [Entity]CreateInputDTO(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
+ const dataToCreate = {...inputDTO, uuid: uuidv4()};
195
+ const result = await this._[entity]DAO.create(dataToCreate);
196
+ res.status(201).json({
197
+ success: true,
198
+ data: result
199
+ });
200
+ } catch (err: any) {
201
+ next(err);
202
+ }
203
+ }
204
+ ```
205
+
206
+ **update** - Update existing resource with DTO validation:
207
+ ```typescript
208
+ public async update(req: Request, res: Response, next: NextFunction): Promise<void> {
209
+ try {
210
+ const { uuid } = req.params;
211
+ const data = req.body;
212
+
213
+ // First get the entity by UUID to find its ID
214
+ const existing = await this._[entity]DAO.getByUuid(uuid);
215
+ if (!existing || !existing.id) {
216
+ res.status(404).json({ success: false, message: "[Entity] not found" });
217
+ return;
218
+ }
219
+
220
+ const inputDTO = new [Entity]UpdateInputDTO(data).build();
221
+ const validation: IInputValidator = await inputValidator(inputDTO);
222
+ if (!validation.success) {
223
+ req.statusCode = 400;
224
+ return next(new Error(validation.message));
225
+ }
226
+
227
+ const result = await this._[entity]DAO.update(existing.id, inputDTO);
228
+ res.status(200).json({
229
+ success: true,
230
+ data: result
231
+ });
232
+ } catch (err: any) {
233
+ next(err);
234
+ }
235
+ }
236
+ ```
237
+
238
+ **delete** - Delete resource:
239
+ ```typescript
240
+ public async delete(req: Request, res: Response, next: NextFunction): Promise<void> {
241
+ try {
242
+ const { uuid } = req.params;
243
+
244
+ // First get the entity by UUID to find its ID
245
+ const existing = await this._[entity]DAO.getByUuid(uuid);
246
+ if (!existing || !existing.id) {
247
+ res.status(404).json({ success: false, message: "[Entity] not found" });
248
+ return;
249
+ }
250
+
251
+ const result = await this._[entity]DAO.delete(existing.id);
252
+ if (result) {
253
+ res.status(200).json({ success: true, message: "[Entity] deleted successfully" });
254
+ } else {
255
+ res.status(404).json({ success: false, message: "Failed to delete [entity]" });
256
+ }
257
+ } catch (err: any) {
258
+ next(err);
259
+ }
260
+ }
261
+ ```
262
+
263
+ ### 4. Router Implementation
264
+ Create a corresponding router in `src/routes/[entity]/[entity].router.ts`:
265
+
266
+ ```typescript
267
+ import { Router } from "express";
268
+ import { [Entity]Controller } from "../../controllers/[entity]/[entity].controller";
269
+
270
+ export class [Entity]Router {
271
+ private _router: Router;
272
+ private _[entity]Controller = new [Entity]Controller();
273
+
274
+ constructor() {
275
+ this._router = Router();
276
+ this.initRoutes();
277
+ }
278
+
279
+ private initRoutes(): void {
280
+ this._router.get("/", this._[entity]Controller.getAll.bind(this._[entity]Controller));
281
+ this._router.get("/:uuid", this._[entity]Controller.getByUuid.bind(this._[entity]Controller));
282
+ this._router.post("/", this._[entity]Controller.create.bind(this._[entity]Controller));
283
+ this._router.put("/:uuid", this._[entity]Controller.update.bind(this._[entity]Controller));
284
+ this._router.delete("/:uuid", this._[entity]Controller.delete.bind(this._[entity]Controller));
285
+ }
286
+
287
+ public get router(): Router {
288
+ return this._router;
289
+ }
290
+ }
291
+ ```
292
+
293
+ ### 5. DTO Implementation
294
+
295
+ Create DTOs to validate and sanitize input data:
296
+
297
+ **Create DTO** (`src/dto/input/[entity]/[entity].create.dto.ts`):
298
+ ```typescript
299
+ export class [Entity]CreateInputDTO {
300
+ // Define only allowed properties
301
+ property1: type;
302
+ property2: type;
303
+
304
+ constructor(data: any){
305
+ this.property1 = data.property1;
306
+ this.property2 = data.property2;
307
+ // Set defaults for optional properties
308
+ }
309
+
310
+ public build(): this {
311
+ return this;
312
+ }
313
+ }
314
+ ```
315
+
316
+ **Update DTO** (`src/dto/input/[entity]/[entity].update.dto.ts`):
317
+ ```typescript
318
+ export class [Entity]UpdateInputDTO {
319
+ // Define optional properties
320
+ property1?: type;
321
+ property2?: type;
322
+
323
+ constructor(data: any){
324
+ // Only set properties that are present in the input
325
+ if (data.property1 !== undefined) this.property1 = data.property1;
326
+ if (data.property2 !== undefined) this.property2 = data.property2;
327
+ }
328
+
329
+ public build(): this {
330
+ // Remove any properties that weren't set
331
+ const cleanData: any = {};
332
+ if (this.property1 !== undefined) cleanData.property1 = this.property1;
333
+ if (this.property2 !== undefined) cleanData.property2 = this.property2;
334
+
335
+ // Clear all properties and reassign only the allowed ones
336
+ Object.keys(this).forEach(key => delete (this as any)[key]);
337
+ Object.assign(this, cleanData);
338
+
339
+ return this;
340
+ }
341
+ }
342
+ ```
343
+
344
+ ### 6. Working with Related Entities
345
+
346
+ When your entity has foreign key relationships, DAOs can include related entities using PostgreSQL's `to_jsonb()` function:
347
+
348
+ ```typescript
349
+ async getById(id: number): Promise<IEntity | null> {
350
+ const result = await this._knex("entity as e")
351
+ .leftJoin("related as r", "e.relatedId", "r.id")
352
+ .select("e.*", this._knex.raw("to_jsonb(r.*) as related"))
353
+ .where("e.id", id)
354
+ .first();
355
+ return result || null;
356
+ }
357
+ ```
358
+
359
+ ### 7. Important Notes
360
+ - Always use `.bind()` when assigning controller methods to router to maintain proper context
361
+ - Use UUID for public-facing endpoints (params) but convert to ID for internal DAO operations
362
+ - Return appropriate HTTP status codes (200, 201, 404, etc.)
363
+ - Pass errors to the next() function for centralized error handling
364
+ - Use paginationHelper from @sundaysf/utils for consistent pagination
365
+ - Use DTOs to validate and sanitize input data, preventing unwanted fields from being processed
366
+ - DTOs ensure only allowed fields are passed to the DAO layer
367
+ - Update DTOs should handle partial updates properly
368
+ - Always generate UUID in the controller for new resources (not in DTO or client-side)
369
+ - All API responses must follow the standard format: `{success: boolean, data?: any, message?: string}`
370
+ - Use status 200 for successful DELETE operations (not 204) to include success message
371
+ - Import DAOs and interfaces from `../db` (relative path from controllers to src/db/index.ts)
@@ -0,0 +1,14 @@
1
+ FROM node:22-alpine AS builder
2
+ WORKDIR /var/api
3
+ COPY package*.json ./
4
+ RUN npm ci
5
+ COPY . .
6
+ RUN npm run build
7
+
8
+ FROM node:22-alpine
9
+ WORKDIR /var/api
10
+ COPY package*.json ./
11
+ RUN npm ci --omit=dev
12
+ COPY --from=builder /var/api/dist ./dist
13
+ EXPOSE 3098
14
+ CMD ["node", "dist/server.js"]
@@ -0,0 +1,32 @@
1
+ # Sundays Framework Project
2
+
3
+ This directory contains the starter backend with an embedded database module generated by the CLI.
4
+
5
+ ## Quick start
6
+
7
+ 1. Install dependencies:
8
+ ```
9
+ npm install
10
+ ```
11
+ 2. Copy `.env.example` to `.env` and set your environment variables (especially the `SQL_*` database variables).
12
+ 3. Run database migrations:
13
+ ```
14
+ npm run db:migrate
15
+ ```
16
+ 4. Start the development server:
17
+ ```
18
+ npm run start:dev
19
+ ```
20
+
21
+ The server will run on the port specified in your `.env` file.
22
+
23
+ ## Database
24
+
25
+ The database layer lives in `src/db/` and is compiled together with the rest of the application.
26
+
27
+ ### Database commands
28
+
29
+ - `npm run db:migrate` - Apply pending migrations
30
+ - `npm run db:rollback` - Rollback the last batch of migrations
31
+ - `npm run db:seed` - Run seed files
32
+ - `npm run db:make-migration` - Create a new migration file
@@ -0,0 +1,20 @@
1
+ const eslint = require('@eslint/js');
2
+ const tseslint = require('typescript-eslint');
3
+
4
+ module.exports = tseslint.config(
5
+ eslint.configs.recommended,
6
+ ...tseslint.configs.recommended,
7
+ {
8
+ languageOptions: {
9
+ parserOptions: {
10
+ project: './tsconfig.json',
11
+ },
12
+ },
13
+ rules: {},
14
+ },
15
+ {
16
+ ignores: ['dist/', 'node_modules/'],
17
+ }
18
+ );
19
+
20
+ //# sourceMappingURL=eslint.config.js.map
@@ -0,0 +1,37 @@
1
+ import type { Knex } from "knex";
2
+ import dotenv from "dotenv";
3
+ dotenv.config();
4
+
5
+ const isLocalhost = process.env.SQL_HOST === 'localhost' || process.env.SQL_HOST === '127.0.0.1';
6
+ const rejectUnauthorized = process.env.SQL_REJECT_UNAUTHORIZED !== 'false';
7
+
8
+ const sharedConfig: Knex.Config = {
9
+ client: "postgresql",
10
+ connection: {
11
+ database: process.env.SQL_DB_NAME,
12
+ user: process.env.SQL_USER,
13
+ password: process.env.SQL_PASSWORD,
14
+ host: process.env.SQL_HOST,
15
+ port: process.env.SQL_PORT ? +process.env.SQL_PORT : 5432,
16
+ ssl: isLocalhost ? false : { rejectUnauthorized },
17
+ },
18
+ pool: {
19
+ min: 2,
20
+ max: 10,
21
+ },
22
+ migrations: {
23
+ tableName: "knex_migrations",
24
+ directory: "./migrations",
25
+ },
26
+ seeds: {
27
+ directory: "./seeds",
28
+ },
29
+ };
30
+
31
+ const config: { [key: string]: Knex.Config } = {
32
+ development: sharedConfig,
33
+ staging: sharedConfig,
34
+ production: sharedConfig,
35
+ };
36
+
37
+ export default config;
@@ -0,0 +1,13 @@
1
+ import type { Knex } from "knex";
2
+
3
+ export async function up(knex: Knex): Promise<void> {
4
+ await knex.schema.createTable("sundays_package_version", (table) => {
5
+ table.increments("id").primary();
6
+ table.string("versionName").notNullable();
7
+ table.timestamps(true, true); // created_at, updated_at
8
+ });
9
+ }
10
+
11
+ export async function down(knex: Knex): Promise<void> {
12
+ await knex.schema.dropTableIfExists("sundays_package_version");
13
+ }
@@ -0,0 +1,11 @@
1
+ import { Knex } from "knex";
2
+
3
+ export async function seed(knex: Knex): Promise<void> {
4
+ // Deletes ALL existing entries
5
+ await knex("sundays_package_version").del();
6
+
7
+ // Inserts seed entries
8
+ await knex("sundays_package_version").insert([
9
+ { versionName: "1.0.0" }
10
+ ]);
11
+ }
@@ -0,0 +1,35 @@
1
+ import dotenv from 'dotenv';
2
+ import express, { type Express } from 'express';
3
+ import logger from 'morgan';
4
+ import cors from 'cors';
5
+ import { IndexRouter } from './routes/index';
6
+ import { errorMiddleware } from './middlewares/error/error.middleware';
7
+ import { getAllowedOrigins } from './common/config/origins/origins.config';
8
+ dotenv.config();
9
+
10
+ const app: Express = express();
11
+
12
+ app.use(
13
+ logger('tiny', {
14
+ skip: (req, _res) => {
15
+ return req.originalUrl.startsWith('/api/health');
16
+ },
17
+ })
18
+ );
19
+
20
+ app.use(
21
+ cors({
22
+ origin: getAllowedOrigins(),
23
+ credentials: true,
24
+ allowedHeaders: ['Content-Type', 'Authorization'],
25
+ })
26
+ );
27
+
28
+ app.use(express.urlencoded({ extended: true }));
29
+ app.use(express.json());
30
+
31
+ app.use('/api', new IndexRouter().router);
32
+
33
+ app.use(errorMiddleware);
34
+
35
+ export default app;
@@ -0,0 +1,11 @@
1
+ export const getAllowedOrigins = (): string[] => {
2
+ let origins: string = process.env.CORS_ALLOWED_ORIGINS || 'http://localhost:3098';
3
+ origins = origins
4
+ .split('\n')
5
+ .join('')
6
+ .split('\r')
7
+ .join('')
8
+ .split(' ')
9
+ .join('');
10
+ return origins.split(',');
11
+ };
@@ -0,0 +1,4 @@
1
+ export const getServiceEnvironment = (): string => {
2
+ const serEnv: string = process.env.ENVIRONMENT || 'undefined';
3
+ return serEnv.charAt(0).toUpperCase() + serEnv.slice(1);
4
+ };
@@ -0,0 +1,5 @@
1
+ const pjson = require('../../../package.json');
2
+
3
+ export const getServiceVersion = (): string => {
4
+ return pjson['version'];
5
+ };
@@ -0,0 +1,24 @@
1
+ import { NextFunction, Request, Response } from 'express';
2
+ import { getServiceVersion } from '../../common/utils/version.resolver';
3
+ import { getServiceEnvironment } from '../../common/utils/environment.resolver';
4
+
5
+ export class HealthController {
6
+ public async getHealthStatus(
7
+ _req: Request,
8
+ res: Response,
9
+ next: NextFunction
10
+ ): Promise<void> {
11
+ try {
12
+ const version: string = await getServiceVersion();
13
+ const environment: string = await getServiceEnvironment();
14
+ res.status(200).json({
15
+ success: true,
16
+ health: 'Up!',
17
+ version,
18
+ environment,
19
+ });
20
+ } catch (err: any) {
21
+ next(err);
22
+ }
23
+ }
24
+ }