@sundaysf/cli-v2 1.0.1 → 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.
- package/README.md +178 -178
- package/dist/README.md +178 -178
- package/dist/bin/generators/class.js.map +1 -1
- package/dist/bin/generators/postman.js.map +1 -1
- package/dist/bin/index.js +1 -1
- package/dist/bin/index.js.map +1 -1
- package/dist/templates/backend/.claude/agents/knex-table-implementer.md +113 -113
- package/dist/templates/backend/.claude/agents/sundays-backend-builder.md +70 -70
- package/dist/templates/backend/.claude/settings.local.json +13 -13
- package/dist/templates/backend/.env.example +13 -13
- package/dist/templates/backend/.prettierignore +2 -2
- package/dist/templates/backend/.prettierrc +9 -9
- package/dist/templates/backend/CLAUDE.md +348 -348
- package/dist/templates/backend/Dockerfile +14 -14
- package/dist/templates/backend/README.md +18 -18
- package/dist/templates/backend/eslint.config.js +20 -20
- package/dist/templates/backend/src/app.ts +34 -34
- package/dist/templates/backend/src/common/config/origins/origins.config.ts +11 -11
- package/dist/templates/backend/src/common/utils/environment.resolver.ts +3 -3
- package/dist/templates/backend/src/common/utils/version.resolver.ts +4 -4
- package/dist/templates/backend/src/controllers/health/health.controller.ts +23 -23
- package/dist/templates/backend/src/middlewares/error/error.middleware.ts +21 -21
- package/dist/templates/backend/src/routes/health/health.router.ts +16 -16
- package/dist/templates/backend/src/routes/index.ts +57 -57
- package/dist/templates/backend/src/server.ts +16 -16
- package/dist/templates/backend/src/types.d.ts +10 -10
- package/dist/templates/backend/tsconfig.json +16 -16
- package/dist/templates/backend-db-sql/.claude/agents/knex-table-implementer.md +114 -114
- package/dist/templates/backend-db-sql/.claude/agents/sundays-backend-builder.md +70 -70
- package/dist/templates/backend-db-sql/.claude/settings.local.json +19 -19
- package/dist/templates/backend-db-sql/.env.example +13 -13
- package/dist/templates/backend-db-sql/.prettierignore +2 -2
- package/dist/templates/backend-db-sql/.prettierrc +9 -9
- package/dist/templates/backend-db-sql/CLAUDE.md +374 -374
- package/dist/templates/backend-db-sql/Dockerfile +17 -17
- package/dist/templates/backend-db-sql/README.md +34 -34
- package/dist/templates/backend-db-sql/db/knexfile.ts +33 -33
- package/dist/templates/backend-db-sql/db/migrations/001_create_sundays_package_version.ts +12 -12
- package/dist/templates/backend-db-sql/db/seeds/001_sundays_package_version_seed.ts +10 -10
- package/dist/templates/backend-db-sql/db/src/KnexConnection.ts +74 -74
- package/dist/templates/backend-db-sql/db/src/d.types.ts +18 -18
- package/dist/templates/backend-db-sql/db/src/dao/sundays-package-version/sundays-package-version.dao.ts +71 -71
- package/dist/templates/backend-db-sql/db/src/index.ts +9 -9
- package/dist/templates/backend-db-sql/db/src/interfaces/sundays-package-version/sundays-package-version.interfaces.ts +6 -6
- package/dist/templates/backend-db-sql/db/tsconfig.json +16 -16
- package/dist/templates/backend-db-sql/eslint.config.js +20 -20
- package/dist/templates/backend-db-sql/src/app.ts +34 -34
- package/dist/templates/backend-db-sql/src/common/config/origins/origins.config.ts +11 -11
- package/dist/templates/backend-db-sql/src/common/utils/environment.resolver.ts +3 -3
- package/dist/templates/backend-db-sql/src/common/utils/version.resolver.ts +4 -4
- package/dist/templates/backend-db-sql/src/controllers/health/health.controller.ts +23 -23
- package/dist/templates/backend-db-sql/src/middlewares/error/error.middleware.ts +21 -21
- package/dist/templates/backend-db-sql/src/routes/health/health.router.ts +16 -16
- package/dist/templates/backend-db-sql/src/routes/index.ts +57 -57
- package/dist/templates/backend-db-sql/src/server.ts +18 -18
- package/dist/templates/backend-db-sql/src/types.d.ts +10 -10
- package/dist/templates/backend-db-sql/tsconfig.json +16 -16
- package/dist/templates/backend-embedded-db-sql/.claude/agents/knex-table-implementer.md +116 -0
- package/dist/templates/backend-embedded-db-sql/.claude/agents/sundays-backend-builder.md +70 -0
- package/dist/templates/backend-embedded-db-sql/.claude/settings.local.json +18 -0
- package/dist/templates/backend-embedded-db-sql/.env.example +14 -0
- package/dist/templates/backend-embedded-db-sql/.prettierignore +3 -0
- package/dist/templates/backend-embedded-db-sql/.prettierrc +9 -0
- package/dist/templates/backend-embedded-db-sql/CLAUDE.md +371 -0
- package/dist/templates/backend-embedded-db-sql/Dockerfile +14 -0
- package/dist/templates/backend-embedded-db-sql/README.md +32 -0
- package/dist/templates/backend-embedded-db-sql/eslint.config.js +20 -0
- package/dist/templates/backend-embedded-db-sql/knexfile.ts +37 -0
- package/dist/templates/backend-embedded-db-sql/migrations/.gitkeep +0 -0
- package/dist/templates/backend-embedded-db-sql/migrations/001_create_sundays_package_version.ts +13 -0
- package/dist/templates/backend-embedded-db-sql/seeds/001_sundays_package_version_seed.ts +11 -0
- package/dist/templates/backend-embedded-db-sql/src/app.ts +35 -0
- package/dist/templates/backend-embedded-db-sql/src/common/config/origins/origins.config.ts +11 -0
- package/dist/templates/backend-embedded-db-sql/src/common/utils/environment.resolver.ts +4 -0
- package/dist/templates/backend-embedded-db-sql/src/common/utils/version.resolver.ts +5 -0
- package/dist/templates/backend-embedded-db-sql/src/controllers/health/health.controller.ts +24 -0
- package/dist/templates/backend-embedded-db-sql/src/db/KnexConnection.ts +74 -0
- package/dist/templates/backend-embedded-db-sql/src/db/d.types.ts +18 -0
- package/dist/templates/backend-embedded-db-sql/src/db/dao/sundays-package-version/sundays-package-version.dao.ts +71 -0
- package/dist/templates/backend-embedded-db-sql/src/db/index.ts +9 -0
- package/dist/templates/backend-embedded-db-sql/src/db/interfaces/sundays-package-version/sundays-package-version.interfaces.ts +6 -0
- package/dist/templates/backend-embedded-db-sql/src/middlewares/error/error.middleware.ts +21 -0
- package/dist/templates/backend-embedded-db-sql/src/routes/health/health.router.ts +17 -0
- package/dist/templates/backend-embedded-db-sql/src/routes/index.ts +57 -0
- package/dist/templates/backend-embedded-db-sql/src/server.ts +18 -0
- package/dist/templates/backend-embedded-db-sql/src/types.d.ts +10 -0
- package/dist/templates/backend-embedded-db-sql/tsconfig.json +16 -0
- package/dist/templates/db-sql/.claude/agents/knex-table-implementer.md +113 -113
- package/dist/templates/db-sql/.claude/agents/sundays-backend-builder.md +70 -70
- package/dist/templates/db-sql/.claude/settings.local.json +10 -10
- package/dist/templates/db-sql/.env.example +8 -8
- package/dist/templates/db-sql/CLAUDE.md +105 -105
- package/dist/templates/db-sql/knexfile.ts +33 -33
- package/dist/templates/db-sql/migrations/001_create_sundays_package_version.ts +12 -12
- package/dist/templates/db-sql/seeds/001_sundays_package_version_seed.ts +10 -10
- package/dist/templates/db-sql/src/KnexConnection.ts +74 -74
- package/dist/templates/db-sql/src/d.types.ts +18 -18
- package/dist/templates/db-sql/src/dao/sundays-package-version/sundays-package-version.dao.ts +71 -71
- package/dist/templates/db-sql/src/index.ts +9 -9
- package/dist/templates/db-sql/src/interfaces/sundays-package-version/sundays-package-version.interfaces.ts +6 -6
- package/dist/templates/db-sql/tsconfig.json +16 -16
- package/dist/templates/module/.claude/agents/knex-table-implementer.md +113 -113
- package/dist/templates/module/.claude/agents/sundays-backend-builder.md +70 -70
- package/dist/templates/module/.claude/settings.local.json +10 -10
- package/dist/templates/module/CLAUDE.md +158 -158
- package/dist/templates/module/src/index.ts +9 -9
- package/dist/templates/module/tsconfig.json +19 -19
- 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
|