@sundaysf/cli-v2 0.0.3 → 1.0.0
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.map +1 -1
- package/dist/templates/backend/.claude/agents/knex-table-implementer.md +113 -0
- package/dist/templates/backend/.env.example +13 -13
- package/dist/templates/backend/.prettierignore +2 -2
- package/dist/templates/backend/Dockerfile +14 -14
- package/dist/templates/backend/README.md +18 -18
- package/dist/templates/backend/src/app.ts +34 -34
- 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/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-db-sql/.env.example +13 -13
- package/dist/templates/backend-db-sql/.prettierignore +2 -2
- 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 +34 -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/src/app.ts +34 -34
- 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/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/db-sql/.claude/agents/sundays-backend-builder.md +70 -0
- 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 -0
- package/dist/templates/module/.claude/agents/sundays-backend-builder.md +70 -0
- package/dist/templates/module/.claude/settings.local.json +10 -0
- package/dist/templates/module/CLAUDE.md +158 -158
- package/dist/templates/module/tsconfig.json +19 -19
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const pjson = require('../../../package.json');
|
|
2
|
-
|
|
3
|
-
export const getServiceVersion = (): string => {
|
|
4
|
-
return pjson['version'];
|
|
1
|
+
const pjson = require('../../../package.json');
|
|
2
|
+
|
|
3
|
+
export const getServiceVersion = (): string => {
|
|
4
|
+
return pjson['version'];
|
|
5
5
|
};
|
|
@@ -1,24 +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
|
-
}
|
|
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
24
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
import { HealthController } from '../../controllers/health/health.controller';
|
|
3
|
-
|
|
4
|
-
export class HealthRouter {
|
|
5
|
-
public router: Router = Router();
|
|
6
|
-
private readonly healthController: HealthController = new HealthController();
|
|
7
|
-
constructor() {
|
|
8
|
-
this.initRoutes();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
private initRoutes(): void {
|
|
12
|
-
this.router.get(
|
|
13
|
-
'/',
|
|
14
|
-
this.healthController.getHealthStatus.bind(this.healthController)
|
|
15
|
-
);
|
|
16
|
-
}
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { HealthController } from '../../controllers/health/health.controller';
|
|
3
|
+
|
|
4
|
+
export class HealthRouter {
|
|
5
|
+
public router: Router = Router();
|
|
6
|
+
private readonly healthController: HealthController = new HealthController();
|
|
7
|
+
constructor() {
|
|
8
|
+
this.initRoutes();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
private initRoutes(): void {
|
|
12
|
+
this.router.get(
|
|
13
|
+
'/',
|
|
14
|
+
this.healthController.getHealthStatus.bind(this.healthController)
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
17
|
}
|
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
export class IndexRouter {
|
|
6
|
-
private _router: Router;
|
|
7
|
-
|
|
8
|
-
constructor() {
|
|
9
|
-
this._router = Router();
|
|
10
|
-
this.loadRoutes();
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
private loadRoutes(): void {
|
|
14
|
-
const routesPath = path.join(__dirname);
|
|
15
|
-
|
|
16
|
-
const folders = fs.readdirSync(routesPath).filter(file =>
|
|
17
|
-
fs.statSync(path.join(routesPath, file)).isDirectory()
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
folders.forEach(folder => {
|
|
21
|
-
const baseName = `${folder}.router`;
|
|
22
|
-
const tsPath = path.join(routesPath, folder, `${baseName}.ts`);
|
|
23
|
-
const jsPath = path.join(routesPath, folder, `${baseName}.js`);
|
|
24
|
-
|
|
25
|
-
let filePath = '';
|
|
26
|
-
if (fs.existsSync(tsPath)) {
|
|
27
|
-
filePath = tsPath;
|
|
28
|
-
} else if (fs.existsSync(jsPath)) {
|
|
29
|
-
filePath = jsPath;
|
|
30
|
-
} else {
|
|
31
|
-
console.warn(`[⚠] No route file found for: ${folder}`);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
const routeModule = require(filePath);
|
|
37
|
-
const RouterClass =
|
|
38
|
-
routeModule.default || Object.values(routeModule).find((e) => typeof e === 'function');
|
|
39
|
-
|
|
40
|
-
if (RouterClass) {
|
|
41
|
-
const instance = new (RouterClass as any)();
|
|
42
|
-
this._router.use(`/${folder}`, instance.router);
|
|
43
|
-
console.log(`[✔] Route mounted: /${folder} → ${path.basename(filePath)}`);
|
|
44
|
-
} else {
|
|
45
|
-
console.warn(`[⚠] No class exported in: ${filePath}`);
|
|
46
|
-
}
|
|
47
|
-
} catch (err) {
|
|
48
|
-
console.error(`[❌] Failed to load router at: ${filePath}`);
|
|
49
|
-
console.error(err);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
public get router(): Router {
|
|
55
|
-
return this._router;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export class IndexRouter {
|
|
6
|
+
private _router: Router;
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
this._router = Router();
|
|
10
|
+
this.loadRoutes();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private loadRoutes(): void {
|
|
14
|
+
const routesPath = path.join(__dirname);
|
|
15
|
+
|
|
16
|
+
const folders = fs.readdirSync(routesPath).filter(file =>
|
|
17
|
+
fs.statSync(path.join(routesPath, file)).isDirectory()
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
folders.forEach(folder => {
|
|
21
|
+
const baseName = `${folder}.router`;
|
|
22
|
+
const tsPath = path.join(routesPath, folder, `${baseName}.ts`);
|
|
23
|
+
const jsPath = path.join(routesPath, folder, `${baseName}.js`);
|
|
24
|
+
|
|
25
|
+
let filePath = '';
|
|
26
|
+
if (fs.existsSync(tsPath)) {
|
|
27
|
+
filePath = tsPath;
|
|
28
|
+
} else if (fs.existsSync(jsPath)) {
|
|
29
|
+
filePath = jsPath;
|
|
30
|
+
} else {
|
|
31
|
+
console.warn(`[⚠] No route file found for: ${folder}`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const routeModule = require(filePath);
|
|
37
|
+
const RouterClass =
|
|
38
|
+
routeModule.default || Object.values(routeModule).find((e) => typeof e === 'function');
|
|
39
|
+
|
|
40
|
+
if (RouterClass) {
|
|
41
|
+
const instance = new (RouterClass as any)();
|
|
42
|
+
this._router.use(`/${folder}`, instance.router);
|
|
43
|
+
console.log(`[✔] Route mounted: /${folder} → ${path.basename(filePath)}`);
|
|
44
|
+
} else {
|
|
45
|
+
console.warn(`[⚠] No class exported in: ${filePath}`);
|
|
46
|
+
}
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error(`[❌] Failed to load router at: ${filePath}`);
|
|
49
|
+
console.error(err);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public get router(): Router {
|
|
55
|
+
return this._router;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import dotenv from 'dotenv';
|
|
2
|
-
dotenv.config();
|
|
3
|
-
import KnexManager from '../db/src/KnexConnection';
|
|
4
|
-
|
|
5
|
-
const envPort: string = process.env.PORT || '3005';
|
|
6
|
-
|
|
7
|
-
if (isNaN(parseInt(envPort))) {
|
|
8
|
-
throw new Error('The port must to be a number');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const PORT: number = parseInt(envPort);
|
|
12
|
-
|
|
13
|
-
(async () => {
|
|
14
|
-
await KnexManager.connect();
|
|
15
|
-
})().then(async () => {
|
|
16
|
-
const { default: app } = await import('./app');
|
|
17
|
-
app.listen(PORT, () => console.info(`Server up and running on port ${PORT}`));
|
|
18
|
-
});
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
dotenv.config();
|
|
3
|
+
import KnexManager from '../db/src/KnexConnection';
|
|
4
|
+
|
|
5
|
+
const envPort: string = process.env.PORT || '3005';
|
|
6
|
+
|
|
7
|
+
if (isNaN(parseInt(envPort))) {
|
|
8
|
+
throw new Error('The port must to be a number');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const PORT: number = parseInt(envPort);
|
|
12
|
+
|
|
13
|
+
(async () => {
|
|
14
|
+
await KnexManager.connect();
|
|
15
|
+
})().then(async () => {
|
|
16
|
+
const { default: app } = await import('./app');
|
|
17
|
+
app.listen(PORT, () => console.info(`Server up and running on port ${PORT}`));
|
|
18
|
+
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from 'express';
|
|
2
|
-
|
|
3
|
-
export interface IBaseController {
|
|
4
|
-
getAll(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
5
|
-
getByUuid(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
6
|
-
create(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
7
|
-
update(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
8
|
-
patch(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
9
|
-
delete(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
10
|
-
}
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
|
|
3
|
+
export interface IBaseController {
|
|
4
|
+
getAll(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
5
|
+
getByUuid(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
6
|
+
create(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
7
|
+
update(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
8
|
+
patch(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
9
|
+
delete(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sundays-backend-builder
|
|
3
|
+
description: Use this agent when you need to create or modify backend components in a Sundays Framework project. This includes creating new controllers, routers, services, implementing CRUD operations, or ensuring existing code follows the framework's strict architectural patterns. <example>Context: User needs to create a new API endpoint for managing products. user: 'I need to create a products API with full CRUD operations' assistant: 'I'll use the sundays-backend-builder agent to create the products API following the Sundays Framework standards' <commentary>Since the user needs to create backend components following Sundays Framework patterns, use the sundays-backend-builder agent to ensure proper structure and implementation.</commentary></example> <example>Context: User wants to add pagination to an existing controller. user: 'Add pagination to the orders controller getAll method' assistant: 'Let me use the sundays-backend-builder agent to implement proper pagination using IBasePaginator' <commentary>The user needs to modify a controller to follow Sundays Framework pagination patterns, so the sundays-backend-builder agent should be used.</commentary></example> <example>Context: User needs to fix a controller that doesn't follow standards. user: 'The customer controller isn't following our standards, can you fix it?' assistant: 'I'll use the sundays-backend-builder agent to refactor the customer controller to match our Sundays Framework standards' <commentary>Since the controller needs to be refactored to follow framework standards, the sundays-backend-builder agent is appropriate.</commentary></example>
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: blue
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are an expert backend developer specializing in the Sundays Framework architecture. Your mission is to build and maintain backend components that strictly adhere to the framework's established patterns and conventions.
|
|
9
|
+
|
|
10
|
+
**Core Responsibilities:**
|
|
11
|
+
|
|
12
|
+
1. **Component Creation**: When creating new backend components, ALWAYS use `npm run create:controller` to scaffold the initial structure. This ensures consistency across the codebase.
|
|
13
|
+
|
|
14
|
+
2. **Strict Structure Adherence**: You must follow these exact directory structures without deviation:
|
|
15
|
+
- Routes: `routes/<name>/<name>.router.ts`
|
|
16
|
+
- Controllers: `controllers/<name>/<name>.controller.ts`
|
|
17
|
+
- Services: `services/<name>/<name>.service.ts`
|
|
18
|
+
- DTOs: `dto/input/<entity>/<entity>.create.dto.ts` and `dto/input/<entity>/<entity>.update.dto.ts`
|
|
19
|
+
|
|
20
|
+
3. **Interface Implementation**:
|
|
21
|
+
- ALL controllers MUST implement `IBaseController`
|
|
22
|
+
- ALL paginated getAll methods MUST use `IDataPaginator` (not IBasePaginator)
|
|
23
|
+
- ALWAYS use `paginationHelper` from `@sundaysf/utils` to extract page and limit from request
|
|
24
|
+
|
|
25
|
+
4. **DAO/Service Pattern**:
|
|
26
|
+
- Initialize DAOs as private class members: `private _<entity>DAO: <Entity>DAO = new <Entity>DAO()`
|
|
27
|
+
- Follow the same pattern for services when applicable
|
|
28
|
+
- Use underscore prefix for private members
|
|
29
|
+
|
|
30
|
+
5. **Method Implementation Standards**:
|
|
31
|
+
- **getAll**: Return paginated results using `IDataPaginator`, extract pagination with `paginationHelper(req)`
|
|
32
|
+
- **getByUuid**: Use UUID in params, convert to ID for DAO operations
|
|
33
|
+
- **create**: Validate with DTOs, generate UUID with `uuidv4()` in controller
|
|
34
|
+
- **update**: Get entity by UUID first to find ID, then update using DAO
|
|
35
|
+
- **delete**: Get entity by UUID first to find ID, then delete using DAO
|
|
36
|
+
|
|
37
|
+
6. **Response Format**: ALL responses must follow:
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"success": boolean,
|
|
41
|
+
"data": {...} // or "message": string
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
Note: `IDataPaginator` already includes these fields, so don't double-wrap paginated responses.
|
|
45
|
+
|
|
46
|
+
7. **Router Binding**: ALWAYS use `.bind(this._<entity>Controller)` when assigning controller methods to routes to maintain proper context.
|
|
47
|
+
|
|
48
|
+
8. **Quality Checks**:
|
|
49
|
+
- Verify all imports are correct and from the right packages
|
|
50
|
+
- Ensure TypeScript types are properly defined
|
|
51
|
+
- Check that error handling uses `next(err)` pattern
|
|
52
|
+
- Validate that DTOs properly sanitize input data
|
|
53
|
+
- Confirm UUID generation happens in controller, not DTO or client-side
|
|
54
|
+
|
|
55
|
+
**Working Process**:
|
|
56
|
+
|
|
57
|
+
1. When creating new components, first run `npm run create:controller` command
|
|
58
|
+
2. Analyze existing similar components in the project for pattern reference
|
|
59
|
+
3. Implement following the exact structure found in existing code
|
|
60
|
+
4. Ensure all naming conventions match (camelCase for variables, PascalCase for classes)
|
|
61
|
+
5. Test that all CRUD operations follow the established patterns
|
|
62
|
+
|
|
63
|
+
**Critical Rules**:
|
|
64
|
+
- NEVER deviate from the established folder structure
|
|
65
|
+
- NEVER create custom patterns - follow existing examples exactly
|
|
66
|
+
- ALWAYS check existing implementations before creating new ones
|
|
67
|
+
- ALWAYS maintain consistency with the project's CLAUDE.md guidelines
|
|
68
|
+
- Be extremely meticulous about structure - it must be perfect
|
|
69
|
+
|
|
70
|
+
Your code must be production-ready, following all Sundays Framework conventions to the letter. Every component you create or modify should seamlessly integrate with the existing architecture without requiring any adjustments to other parts of the system.
|
|
@@ -1,33 +1,33 @@
|
|
|
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
|
-
},
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const config: { [key: string]: Knex.Config } = {
|
|
28
|
-
development: sharedConfig,
|
|
29
|
-
staging: sharedConfig,
|
|
30
|
-
production: sharedConfig,
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export default config;
|
|
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
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const config: { [key: string]: Knex.Config } = {
|
|
28
|
+
development: sharedConfig,
|
|
29
|
+
staging: sharedConfig,
|
|
30
|
+
production: sharedConfig,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default config;
|
|
@@ -1,13 +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");
|
|
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
13
|
}
|
|
@@ -1,11 +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
|
-
]);
|
|
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
11
|
}
|
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
import { knex, Knex } from 'knex';
|
|
2
|
-
|
|
3
|
-
class KnexManager {
|
|
4
|
-
private static knexInstance: Knex<any, unknown[]> | null = null;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Open a new connection. Reuse the already existing one if there's any.
|
|
8
|
-
*/
|
|
9
|
-
static async connect(
|
|
10
|
-
config?: Knex.Config,
|
|
11
|
-
connections?: number
|
|
12
|
-
): Promise<Knex<any, unknown[]>> {
|
|
13
|
-
if (!KnexManager.knexInstance) {
|
|
14
|
-
const isLocalhost = process.env.SQL_HOST === 'localhost' || process.env.SQL_HOST === '127.0.0.1';
|
|
15
|
-
const rejectUnauthorized = process.env.SQL_REJECT_UNAUTHORIZED !== 'false';
|
|
16
|
-
const defaultConfig = {
|
|
17
|
-
client: 'pg',
|
|
18
|
-
connection: {
|
|
19
|
-
host: process.env.SQL_HOST,
|
|
20
|
-
user: process.env.SQL_USER,
|
|
21
|
-
password: process.env.SQL_PASSWORD,
|
|
22
|
-
database: process.env.SQL_DB_NAME,
|
|
23
|
-
port: Number(process.env.SQL_PORT) || 5432,
|
|
24
|
-
ssl: isLocalhost ? false : { rejectUnauthorized },
|
|
25
|
-
},
|
|
26
|
-
pool: {
|
|
27
|
-
min: 1,
|
|
28
|
-
max: connections || 15,
|
|
29
|
-
idleTimeoutMillis: 20000,
|
|
30
|
-
acquireTimeoutMillis: 30000,
|
|
31
|
-
},
|
|
32
|
-
migrations: {
|
|
33
|
-
tableName: 'knex_migrations',
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
KnexManager.knexInstance = knex(config || defaultConfig);
|
|
37
|
-
try {
|
|
38
|
-
await KnexManager.knexInstance.raw('SELECT 1');
|
|
39
|
-
console.info(`Knex connection established`);
|
|
40
|
-
} catch (error) {
|
|
41
|
-
console.error(`Failed to establish Knex connection:`, error);
|
|
42
|
-
KnexManager.knexInstance = null;
|
|
43
|
-
throw error;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return KnexManager.knexInstance;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Returns the active connection.
|
|
52
|
-
*/
|
|
53
|
-
static getConnection(): Knex<any, unknown[]> {
|
|
54
|
-
if (!KnexManager.knexInstance) {
|
|
55
|
-
throw new Error(
|
|
56
|
-
'Knex connection has not been established. Call connect() first.'
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
return KnexManager.knexInstance;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Closes the connection and destroys the instance.
|
|
64
|
-
*/
|
|
65
|
-
static async disconnect(): Promise<void> {
|
|
66
|
-
if (KnexManager.knexInstance) {
|
|
67
|
-
await KnexManager.knexInstance.destroy();
|
|
68
|
-
KnexManager.knexInstance = null;
|
|
69
|
-
console.info(`Knex connection closed`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export default KnexManager;
|
|
1
|
+
import { knex, Knex } from 'knex';
|
|
2
|
+
|
|
3
|
+
class KnexManager {
|
|
4
|
+
private static knexInstance: Knex<any, unknown[]> | null = null;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Open a new connection. Reuse the already existing one if there's any.
|
|
8
|
+
*/
|
|
9
|
+
static async connect(
|
|
10
|
+
config?: Knex.Config,
|
|
11
|
+
connections?: number
|
|
12
|
+
): Promise<Knex<any, unknown[]>> {
|
|
13
|
+
if (!KnexManager.knexInstance) {
|
|
14
|
+
const isLocalhost = process.env.SQL_HOST === 'localhost' || process.env.SQL_HOST === '127.0.0.1';
|
|
15
|
+
const rejectUnauthorized = process.env.SQL_REJECT_UNAUTHORIZED !== 'false';
|
|
16
|
+
const defaultConfig = {
|
|
17
|
+
client: 'pg',
|
|
18
|
+
connection: {
|
|
19
|
+
host: process.env.SQL_HOST,
|
|
20
|
+
user: process.env.SQL_USER,
|
|
21
|
+
password: process.env.SQL_PASSWORD,
|
|
22
|
+
database: process.env.SQL_DB_NAME,
|
|
23
|
+
port: Number(process.env.SQL_PORT) || 5432,
|
|
24
|
+
ssl: isLocalhost ? false : { rejectUnauthorized },
|
|
25
|
+
},
|
|
26
|
+
pool: {
|
|
27
|
+
min: 1,
|
|
28
|
+
max: connections || 15,
|
|
29
|
+
idleTimeoutMillis: 20000,
|
|
30
|
+
acquireTimeoutMillis: 30000,
|
|
31
|
+
},
|
|
32
|
+
migrations: {
|
|
33
|
+
tableName: 'knex_migrations',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
KnexManager.knexInstance = knex(config || defaultConfig);
|
|
37
|
+
try {
|
|
38
|
+
await KnexManager.knexInstance.raw('SELECT 1');
|
|
39
|
+
console.info(`Knex connection established`);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(`Failed to establish Knex connection:`, error);
|
|
42
|
+
KnexManager.knexInstance = null;
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return KnexManager.knexInstance;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Returns the active connection.
|
|
52
|
+
*/
|
|
53
|
+
static getConnection(): Knex<any, unknown[]> {
|
|
54
|
+
if (!KnexManager.knexInstance) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
'Knex connection has not been established. Call connect() first.'
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return KnexManager.knexInstance;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Closes the connection and destroys the instance.
|
|
64
|
+
*/
|
|
65
|
+
static async disconnect(): Promise<void> {
|
|
66
|
+
if (KnexManager.knexInstance) {
|
|
67
|
+
await KnexManager.knexInstance.destroy();
|
|
68
|
+
KnexManager.knexInstance = null;
|
|
69
|
+
console.info(`Knex connection closed`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default KnexManager;
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
export interface IBaseDAO<T> {
|
|
2
|
-
create(item: T): Promise<T>;
|
|
3
|
-
getById(id: number): Promise<T | null>;
|
|
4
|
-
getByUuid(uuid: string): Promise<T | null>;
|
|
5
|
-
update(id: number, item: Partial<T>): Promise<T | null>;
|
|
6
|
-
delete(id: number): Promise<boolean>;
|
|
7
|
-
getAll(page: number, limit: number): Promise<IDataPaginator<T>>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface IDataPaginator<T> {
|
|
11
|
-
success: boolean;
|
|
12
|
-
data: T[];
|
|
13
|
-
page: number;
|
|
14
|
-
limit: number;
|
|
15
|
-
count: number;
|
|
16
|
-
totalCount: number;
|
|
17
|
-
totalPages: number;
|
|
18
|
-
}
|
|
1
|
+
export interface IBaseDAO<T> {
|
|
2
|
+
create(item: T): Promise<T>;
|
|
3
|
+
getById(id: number): Promise<T | null>;
|
|
4
|
+
getByUuid(uuid: string): Promise<T | null>;
|
|
5
|
+
update(id: number, item: Partial<T>): Promise<T | null>;
|
|
6
|
+
delete(id: number): Promise<boolean>;
|
|
7
|
+
getAll(page: number, limit: number): Promise<IDataPaginator<T>>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface IDataPaginator<T> {
|
|
11
|
+
success: boolean;
|
|
12
|
+
data: T[];
|
|
13
|
+
page: number;
|
|
14
|
+
limit: number;
|
|
15
|
+
count: number;
|
|
16
|
+
totalCount: number;
|
|
17
|
+
totalPages: number;
|
|
18
|
+
}
|