express-genix 4.6.0 ā 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/index.js +16 -1
- package/lib/features.js +42 -0
- package/lib/scaffold.js +165 -0
- package/package.json +1 -1
- package/templates/core/docker-compose.yml.ejs +16 -3
- package/templates/infra/Makefile.ejs +30 -0
- package/templates/infra/nginx.conf.ejs +25 -0
- package/templates/scaffold/controller.js.ejs +94 -0
- package/templates/scaffold/route.js.ejs +88 -0
- package/templates/scaffold/service.js.ejs +77 -0
package/README.md
CHANGED
|
@@ -112,6 +112,7 @@ npm run dev
|
|
|
112
112
|
|
|
113
113
|
```bash
|
|
114
114
|
cd my-express-app
|
|
115
|
+
express-genix add infra # Adds Production-Ready Infra (Docker, CI/CD, Nginx, Makefile)
|
|
115
116
|
express-genix add docker # Adds Dockerfile, docker-compose.yml, .dockerignore
|
|
116
117
|
express-genix add cicd # Adds GitHub Actions CI workflow
|
|
117
118
|
express-genix add auth # Adds JWT auth, controllers, routes, middleware
|
|
@@ -119,6 +120,23 @@ express-genix add websocket # Adds Socket.io setup
|
|
|
119
120
|
express-genix add prisma # Adds Prisma schema, client config, migrations
|
|
120
121
|
```
|
|
121
122
|
|
|
123
|
+
## Interactive Resource Scaffolding
|
|
124
|
+
|
|
125
|
+
Generate a complete resource (Controller, Service, and Routes) inside an existing Express Genix project.
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
cd my-express-app
|
|
129
|
+
express-genix generate product
|
|
130
|
+
# or use the alias
|
|
131
|
+
express-genix g product
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
This command automatically:
|
|
135
|
+
1. Creates `src/controllers/productController.js` (with CRUD operations)
|
|
136
|
+
2. Creates `src/services/productService.js` (with business logic structure)
|
|
137
|
+
3. Creates `src/routes/productRoutes.js` (with Swagger annotations and auth middleware if enabled)
|
|
138
|
+
4. **Auto-injects** the new route into `src/routes/index.js` (`router.use('/products', productRoutes);`)
|
|
139
|
+
|
|
122
140
|
### Generate from natural language (AI)
|
|
123
141
|
|
|
124
142
|
Requires `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` in your environment.
|
package/index.js
CHANGED
|
@@ -267,7 +267,7 @@ Configuration:
|
|
|
267
267
|
.command('add <feature>')
|
|
268
268
|
.description('Add a feature to an existing express-genix project')
|
|
269
269
|
.action(async (feature) => {
|
|
270
|
-
const supportedFeatures = ['auth', 'websocket', 'docker', 'cicd', 'prisma'];
|
|
270
|
+
const supportedFeatures = ['auth', 'websocket', 'docker', 'cicd', 'prisma', 'infra'];
|
|
271
271
|
if (!supportedFeatures.includes(feature)) {
|
|
272
272
|
console.error(`ā Unknown feature: "${feature}"`);
|
|
273
273
|
console.log(`Supported features: ${supportedFeatures.join(', ')}`);
|
|
@@ -296,6 +296,21 @@ Configuration:
|
|
|
296
296
|
|
|
297
297
|
|
|
298
298
|
|
|
299
|
+
program
|
|
300
|
+
.command('generate <resource>')
|
|
301
|
+
.alias('g')
|
|
302
|
+
.description('Generate a new resource (controller, service, routes)')
|
|
303
|
+
.action(async (resource) => {
|
|
304
|
+
try {
|
|
305
|
+
const { generateResource } = require('./lib/scaffold');
|
|
306
|
+
await generateResource(resource, process.cwd());
|
|
307
|
+
console.log(`\nš Resource "${resource}" generated successfully!`);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.error(`\nā Failed to generate resource: ${error.message}`);
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
299
314
|
await program.parseAsync(process.argv);
|
|
300
315
|
}
|
|
301
316
|
|
package/lib/features.js
CHANGED
|
@@ -11,6 +11,7 @@ const addFeature = async (feature, projectDir) => {
|
|
|
11
11
|
const handlers = {
|
|
12
12
|
docker: () => addDocker(projectDir, templatesDir, pkg),
|
|
13
13
|
cicd: () => addCicd(projectDir, templatesDir, pkg),
|
|
14
|
+
infra: () => addInfra(projectDir, templatesDir, pkg),
|
|
14
15
|
auth: () => addAuth(projectDir, templatesDir, pkg),
|
|
15
16
|
websocket: () => addWebsocket(projectDir, templatesDir, pkg),
|
|
16
17
|
prisma: () => addPrisma(projectDir, templatesDir, pkg),
|
|
@@ -62,6 +63,47 @@ const addCicd = (projectDir, templatesDir, pkg) => {
|
|
|
62
63
|
console.log(' Created .github/workflows/ci.yml');
|
|
63
64
|
};
|
|
64
65
|
|
|
66
|
+
const addInfra = (projectDir, templatesDir, pkg) => {
|
|
67
|
+
const config = inferConfig(projectDir, pkg);
|
|
68
|
+
config.hasNginx = true; // Enable Nginx in docker-compose
|
|
69
|
+
|
|
70
|
+
// 1. Add Docker files
|
|
71
|
+
const dockerFiles = [
|
|
72
|
+
{ template: 'core/Dockerfile.ejs', output: 'Dockerfile' },
|
|
73
|
+
{ template: 'core/docker-compose.yml.ejs', output: 'docker-compose.yml' },
|
|
74
|
+
{ template: 'core/dockerignore.ejs', output: '.dockerignore' },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
for (const file of dockerFiles) {
|
|
78
|
+
const content = renderTemplate(templatesDir, file.template, config);
|
|
79
|
+
fs.writeFileSync(path.join(projectDir, file.output), content);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 2. Add CI/CD files
|
|
83
|
+
const workflowDir = path.join(projectDir, '.github', 'workflows');
|
|
84
|
+
fs.mkdirSync(workflowDir, { recursive: true });
|
|
85
|
+
const ciContent = renderTemplate(templatesDir, 'cicd/github-actions.yml.ejs', config);
|
|
86
|
+
fs.writeFileSync(path.join(workflowDir, 'ci.yml'), ciContent);
|
|
87
|
+
|
|
88
|
+
// 3. Add Infra specific files (Makefile, Nginx)
|
|
89
|
+
const infraFiles = [
|
|
90
|
+
{ template: 'infra/Makefile.ejs', output: 'Makefile' },
|
|
91
|
+
{ template: 'infra/nginx.conf.ejs', output: 'nginx/nginx.conf' },
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
for (const file of infraFiles) {
|
|
95
|
+
const outputPath = path.join(projectDir, file.output);
|
|
96
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
97
|
+
const content = renderTemplate(templatesDir, file.template, config);
|
|
98
|
+
fs.writeFileSync(outputPath, content);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log(' ā
Production-Ready Infrastructure added!');
|
|
102
|
+
console.log(' Created Dockerfile, docker-compose.yml, .dockerignore');
|
|
103
|
+
console.log(' Created .github/workflows/ci.yml');
|
|
104
|
+
console.log(' Created Makefile and nginx/nginx.conf');
|
|
105
|
+
};
|
|
106
|
+
|
|
65
107
|
const addAuth = (projectDir, templatesDir, pkg) => {
|
|
66
108
|
const config = inferConfig(projectDir, pkg);
|
|
67
109
|
|
package/lib/scaffold.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const ejs = require('ejs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Capitalize first letter
|
|
7
|
+
*/
|
|
8
|
+
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Convert to camelCase
|
|
12
|
+
*/
|
|
13
|
+
const toCamelCase = (str) => {
|
|
14
|
+
return str.replace(/[-_](.)/g, (_, c) => c.toUpperCase());
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Convert to PascalCase
|
|
19
|
+
*/
|
|
20
|
+
const toPascalCase = (str) => {
|
|
21
|
+
const camel = toCamelCase(str);
|
|
22
|
+
return capitalize(camel);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Simple pluralize (adds 's' if not ending in 's')
|
|
27
|
+
*/
|
|
28
|
+
const pluralize = (str) => {
|
|
29
|
+
if (str.endsWith('s')) return str;
|
|
30
|
+
if (str.endsWith('y')) return str.slice(0, -1) + 'ies';
|
|
31
|
+
return str + 's';
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generate a resource (controller, service, route)
|
|
36
|
+
*/
|
|
37
|
+
const generateResource = async (resourceName, projectDir) => {
|
|
38
|
+
const packageJsonPath = path.join(projectDir, 'package.json');
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
41
|
+
throw new Error('No package.json found. Run this command from your express-genix project root.');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
45
|
+
|
|
46
|
+
// Check if it's an express-genix project (or at least has the structure)
|
|
47
|
+
const srcDir = path.join(projectDir, 'src');
|
|
48
|
+
if (!fs.existsSync(srcDir)) {
|
|
49
|
+
throw new Error('No "src" directory found. Are you in an express-genix project?');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Determine features from package.json dependencies
|
|
53
|
+
const hasAuth = !!pkg.dependencies['jsonwebtoken'];
|
|
54
|
+
|
|
55
|
+
const nameCamel = toCamelCase(resourceName);
|
|
56
|
+
const namePascal = toPascalCase(resourceName);
|
|
57
|
+
const namePlural = pluralize(nameCamel);
|
|
58
|
+
|
|
59
|
+
const templateData = {
|
|
60
|
+
resourceName: resourceName.toLowerCase(),
|
|
61
|
+
resourceNameCamel: nameCamel,
|
|
62
|
+
resourceNamePascal: namePascal,
|
|
63
|
+
resourceNamePlural: namePlural,
|
|
64
|
+
hasAuth,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const templatesDir = path.join(__dirname, '..', 'templates', 'scaffold');
|
|
68
|
+
|
|
69
|
+
const filesToGenerate = [
|
|
70
|
+
{
|
|
71
|
+
template: 'controller.js.ejs',
|
|
72
|
+
dest: path.join(srcDir, 'controllers', `${nameCamel}Controller.js`),
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
template: 'service.js.ejs',
|
|
76
|
+
dest: path.join(srcDir, 'services', `${nameCamel}Service.js`),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
template: 'route.js.ejs',
|
|
80
|
+
dest: path.join(srcDir, 'routes', `${nameCamel}Routes.js`),
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
console.log(`\nGenerating resource: ${namePascal}...`);
|
|
85
|
+
|
|
86
|
+
for (const file of filesToGenerate) {
|
|
87
|
+
if (fs.existsSync(file.dest)) {
|
|
88
|
+
console.log(`ā ļø Skipping ${path.basename(file.dest)} (already exists)`);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const templatePath = path.join(templatesDir, file.template);
|
|
93
|
+
const templateContent = fs.readFileSync(templatePath, 'utf8');
|
|
94
|
+
const rendered = ejs.render(templateContent, templateData);
|
|
95
|
+
|
|
96
|
+
// Ensure directory exists
|
|
97
|
+
fs.mkdirSync(path.dirname(file.dest), { recursive: true });
|
|
98
|
+
|
|
99
|
+
fs.writeFileSync(file.dest, rendered);
|
|
100
|
+
console.log(`ā
Created ${path.relative(projectDir, file.dest)}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Inject route into src/routes/index.js
|
|
104
|
+
injectRoute(srcDir, nameCamel, namePlural);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Inject the new route into src/routes/index.js
|
|
109
|
+
*/
|
|
110
|
+
const injectRoute = (srcDir, nameCamel, namePlural) => {
|
|
111
|
+
const routesIndexPath = path.join(srcDir, 'routes', 'index.js');
|
|
112
|
+
|
|
113
|
+
if (!fs.existsSync(routesIndexPath)) {
|
|
114
|
+
console.log(`ā ļø Could not find src/routes/index.js to inject the route.`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let content = fs.readFileSync(routesIndexPath, 'utf8');
|
|
119
|
+
|
|
120
|
+
const requireStatement = `const ${nameCamel}Routes = require('./${nameCamel}Routes');`;
|
|
121
|
+
const useStatement = `router.use('/${namePlural}', ${nameCamel}Routes);`;
|
|
122
|
+
|
|
123
|
+
if (content.includes(requireStatement) || content.includes(useStatement)) {
|
|
124
|
+
console.log(`ā ļø Route already injected in src/routes/index.js`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Find the last require statement
|
|
129
|
+
const requireRegex = /const .* = require\('.*'\);/g;
|
|
130
|
+
let lastRequireMatch;
|
|
131
|
+
let match;
|
|
132
|
+
while ((match = requireRegex.exec(content)) !== null) {
|
|
133
|
+
lastRequireMatch = match;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (lastRequireMatch) {
|
|
137
|
+
const insertPos = lastRequireMatch.index + lastRequireMatch[0].length;
|
|
138
|
+
content = content.slice(0, insertPos) + '\n' + requireStatement + content.slice(insertPos);
|
|
139
|
+
} else {
|
|
140
|
+
// Fallback: put it after express require
|
|
141
|
+
content = content.replace(/const express = require\('express'\);/, `const express = require('express');\n${requireStatement}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Find the last router.use statement
|
|
145
|
+
const useRegex = /router\.use\('.*', .*\);/g;
|
|
146
|
+
let lastUseMatch;
|
|
147
|
+
while ((match = useRegex.exec(content)) !== null) {
|
|
148
|
+
lastUseMatch = match;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (lastUseMatch) {
|
|
152
|
+
const insertPos = lastUseMatch.index + lastUseMatch[0].length;
|
|
153
|
+
content = content.slice(0, insertPos) + '\n' + useStatement + content.slice(insertPos);
|
|
154
|
+
} else {
|
|
155
|
+
// Fallback: put it before router.get('/', ...)
|
|
156
|
+
content = content.replace(/router\.get\('\/'/, `${useStatement}\n\nrouter.get('/'`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
fs.writeFileSync(routesIndexPath, content);
|
|
160
|
+
console.log(`ā
Injected /${namePlural} route into src/routes/index.js`);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
module.exports = {
|
|
164
|
+
generateResource,
|
|
165
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-genix",
|
|
3
|
-
"version": "4.6.
|
|
3
|
+
"version": "4.6.1",
|
|
4
4
|
"description": "Production-grade CLI to generate Express apps with JWT, RBAC, GraphQL, TypeScript, Prisma, MongoDB, PostgreSQL, file uploads, email, background jobs, and more",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
# templates/core/docker-compose.yml.ejs
|
|
2
2
|
version: '3.8'
|
|
3
3
|
|
|
4
|
-
services
|
|
4
|
+
services:<% if (typeof hasNginx !== 'undefined' && hasNginx) { %>
|
|
5
|
+
nginx:
|
|
6
|
+
image: nginx:alpine
|
|
7
|
+
ports:
|
|
8
|
+
- "80:80"
|
|
9
|
+
volumes:
|
|
10
|
+
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
11
|
+
depends_on:
|
|
12
|
+
- app
|
|
13
|
+
restart: unless-stopped
|
|
14
|
+
networks:
|
|
15
|
+
- app-network
|
|
16
|
+
<% } %>
|
|
5
17
|
app:
|
|
6
18
|
build: .
|
|
7
|
-
ports
|
|
8
|
-
- "3000
|
|
19
|
+
ports:<% if (typeof hasNginx !== 'undefined' && hasNginx) { %>
|
|
20
|
+
- "3000" # Exposed to Nginx, not host directly<% } else { %>
|
|
21
|
+
- "3000:3000"<% } %>
|
|
9
22
|
environment:
|
|
10
23
|
- NODE_ENV=production
|
|
11
24
|
env_file:
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.PHONY: build up down logs test lint clean
|
|
2
|
+
|
|
3
|
+
# Build the Docker image
|
|
4
|
+
build:
|
|
5
|
+
docker-compose build
|
|
6
|
+
|
|
7
|
+
# Start the application in detached mode
|
|
8
|
+
up:
|
|
9
|
+
docker-compose up -d
|
|
10
|
+
|
|
11
|
+
# Stop the application
|
|
12
|
+
down:
|
|
13
|
+
docker-compose down
|
|
14
|
+
|
|
15
|
+
# View logs
|
|
16
|
+
logs:
|
|
17
|
+
docker-compose logs -f app
|
|
18
|
+
|
|
19
|
+
# Run tests
|
|
20
|
+
test:
|
|
21
|
+
npm test
|
|
22
|
+
|
|
23
|
+
# Run linter
|
|
24
|
+
lint:
|
|
25
|
+
npm run lint
|
|
26
|
+
|
|
27
|
+
# Clean up Docker resources
|
|
28
|
+
clean:
|
|
29
|
+
docker-compose down -v --remove-orphans
|
|
30
|
+
docker system prune -f
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
events {
|
|
2
|
+
worker_connections 1024;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
http {
|
|
6
|
+
upstream app_servers {
|
|
7
|
+
server app:3000;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
server {
|
|
11
|
+
listen 80;
|
|
12
|
+
server_name localhost;
|
|
13
|
+
|
|
14
|
+
location / {
|
|
15
|
+
proxy_pass http://app_servers;
|
|
16
|
+
proxy_http_version 1.1;
|
|
17
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
18
|
+
proxy_set_header Connection 'upgrade';
|
|
19
|
+
proxy_set_header Host $host;
|
|
20
|
+
proxy_cache_bypass $http_upgrade;
|
|
21
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
22
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const <%= resourceNameCamel %>Service = require('../services/<%= resourceNameCamel %>Service');
|
|
2
|
+
const { AppError } = require('../utils/errors');
|
|
3
|
+
const { success, created, paginated } = require('../utils/response');
|
|
4
|
+
const { createLogger } = require('../utils/logger');
|
|
5
|
+
|
|
6
|
+
const logger = createLogger('<%= resourceNamePascal %>Controller');
|
|
7
|
+
|
|
8
|
+
const getAll<%= resourceNamePascal %>s = async (req, res, next) => {
|
|
9
|
+
try {
|
|
10
|
+
const page = Math.max(1, parseInt(req.query.page, 10) || 1);
|
|
11
|
+
const limit = Math.min(100, Math.max(1, parseInt(req.query.limit, 10) || 10));
|
|
12
|
+
|
|
13
|
+
logger.info('Fetching <%= resourceNamePlural %>', { page, limit });
|
|
14
|
+
const result = await <%= resourceNameCamel %>Service.getAll<%= resourceNamePascal %>s(page, limit);
|
|
15
|
+
|
|
16
|
+
return paginated(res, result.<%= resourceNamePlural %>, {
|
|
17
|
+
page,
|
|
18
|
+
limit,
|
|
19
|
+
total: result.total,
|
|
20
|
+
totalPages: result.totalPages,
|
|
21
|
+
});
|
|
22
|
+
} catch (error) {
|
|
23
|
+
logger.error('Error fetching <%= resourceNamePlural %>', { error: error.message });
|
|
24
|
+
next(error);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const get<%= resourceNamePascal %>ById = async (req, res, next) => {
|
|
29
|
+
try {
|
|
30
|
+
const { id } = req.params;
|
|
31
|
+
const <%= resourceNameCamel %> = await <%= resourceNameCamel %>Service.get<%= resourceNamePascal %>ById(id);
|
|
32
|
+
|
|
33
|
+
if (!<%= resourceNameCamel %>) {
|
|
34
|
+
throw new AppError('<%= resourceNamePascal %> not found', 404);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return success(res, <%= resourceNameCamel %>);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
next(error);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const create<%= resourceNamePascal %> = async (req, res, next) => {
|
|
44
|
+
try {
|
|
45
|
+
const data = req.body;
|
|
46
|
+
|
|
47
|
+
logger.info('Creating <%= resourceNameCamel %>', { data });
|
|
48
|
+
const <%= resourceNameCamel %> = await <%= resourceNameCamel %>Service.create<%= resourceNamePascal %>(data);
|
|
49
|
+
|
|
50
|
+
return created(res, <%= resourceNameCamel %>);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
next(error);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const update<%= resourceNamePascal %> = async (req, res, next) => {
|
|
57
|
+
try {
|
|
58
|
+
const { id } = req.params;
|
|
59
|
+
const data = req.body;
|
|
60
|
+
|
|
61
|
+
const <%= resourceNameCamel %> = await <%= resourceNameCamel %>Service.update<%= resourceNamePascal %>(id, data);
|
|
62
|
+
|
|
63
|
+
if (!<%= resourceNameCamel %>) {
|
|
64
|
+
throw new AppError('<%= resourceNamePascal %> not found', 404);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return success(res, <%= resourceNameCamel %>);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
next(error);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const delete<%= resourceNamePascal %> = async (req, res, next) => {
|
|
74
|
+
try {
|
|
75
|
+
const { id } = req.params;
|
|
76
|
+
const deleted = await <%= resourceNameCamel %>Service.delete<%= resourceNamePascal %>(id);
|
|
77
|
+
|
|
78
|
+
if (!deleted) {
|
|
79
|
+
throw new AppError('<%= resourceNamePascal %> not found', 404);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return success(res, { message: '<%= resourceNamePascal %> deleted successfully' });
|
|
83
|
+
} catch (error) {
|
|
84
|
+
next(error);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
module.exports = {
|
|
89
|
+
getAll<%= resourceNamePascal %>s,
|
|
90
|
+
get<%= resourceNamePascal %>ById,
|
|
91
|
+
create<%= resourceNamePascal %>,
|
|
92
|
+
update<%= resourceNamePascal %>,
|
|
93
|
+
delete<%= resourceNamePascal %>
|
|
94
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const <%= resourceNameCamel %>Controller = require('../controllers/<%= resourceNameCamel %>Controller');
|
|
3
|
+
<% if (hasAuth) { %>const { authenticate } = require('../middleware/auth');<% } %>
|
|
4
|
+
|
|
5
|
+
const router = express.Router();
|
|
6
|
+
|
|
7
|
+
<% if (hasAuth) { %>// Apply authentication middleware to all routes
|
|
8
|
+
router.use(authenticate);
|
|
9
|
+
<% } %>
|
|
10
|
+
/**
|
|
11
|
+
* @swagger
|
|
12
|
+
* /<%= resourceNamePlural %>:
|
|
13
|
+
* get:
|
|
14
|
+
* summary: Get all <%= resourceNamePlural %>
|
|
15
|
+
* tags: [<%= resourceNamePascal %>]
|
|
16
|
+
* responses:
|
|
17
|
+
* 200:
|
|
18
|
+
* description: List of <%= resourceNamePlural %>
|
|
19
|
+
*/
|
|
20
|
+
router.get('/', <%= resourceNameCamel %>Controller.getAll<%= resourceNamePascal %>s);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @swagger
|
|
24
|
+
* /<%= resourceNamePlural %>/{id}:
|
|
25
|
+
* get:
|
|
26
|
+
* summary: Get a <%= resourceNameCamel %> by ID
|
|
27
|
+
* tags: [<%= resourceNamePascal %>]
|
|
28
|
+
* parameters:
|
|
29
|
+
* - in: path
|
|
30
|
+
* name: id
|
|
31
|
+
* required: true
|
|
32
|
+
* schema:
|
|
33
|
+
* type: string
|
|
34
|
+
* responses:
|
|
35
|
+
* 200:
|
|
36
|
+
* description: <%= resourceNamePascal %> details
|
|
37
|
+
*/
|
|
38
|
+
router.get('/:id', <%= resourceNameCamel %>Controller.get<%= resourceNamePascal %>ById);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @swagger
|
|
42
|
+
* /<%= resourceNamePlural %>:
|
|
43
|
+
* post:
|
|
44
|
+
* summary: Create a new <%= resourceNameCamel %>
|
|
45
|
+
* tags: [<%= resourceNamePascal %>]
|
|
46
|
+
* responses:
|
|
47
|
+
* 201:
|
|
48
|
+
* description: Created <%= resourceNameCamel %>
|
|
49
|
+
*/
|
|
50
|
+
router.post('/', <%= resourceNameCamel %>Controller.create<%= resourceNamePascal %>);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @swagger
|
|
54
|
+
* /<%= resourceNamePlural %>/{id}:
|
|
55
|
+
* put:
|
|
56
|
+
* summary: Update a <%= resourceNameCamel %>
|
|
57
|
+
* tags: [<%= resourceNamePascal %>]
|
|
58
|
+
* parameters:
|
|
59
|
+
* - in: path
|
|
60
|
+
* name: id
|
|
61
|
+
* required: true
|
|
62
|
+
* schema:
|
|
63
|
+
* type: string
|
|
64
|
+
* responses:
|
|
65
|
+
* 200:
|
|
66
|
+
* description: Updated <%= resourceNameCamel %>
|
|
67
|
+
*/
|
|
68
|
+
router.put('/:id', <%= resourceNameCamel %>Controller.update<%= resourceNamePascal %>);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @swagger
|
|
72
|
+
* /<%= resourceNamePlural %>/{id}:
|
|
73
|
+
* delete:
|
|
74
|
+
* summary: Delete a <%= resourceNameCamel %>
|
|
75
|
+
* tags: [<%= resourceNamePascal %>]
|
|
76
|
+
* parameters:
|
|
77
|
+
* - in: path
|
|
78
|
+
* name: id
|
|
79
|
+
* required: true
|
|
80
|
+
* schema:
|
|
81
|
+
* type: string
|
|
82
|
+
* responses:
|
|
83
|
+
* 200:
|
|
84
|
+
* description: <%= resourceNamePascal %> deleted successfully
|
|
85
|
+
*/
|
|
86
|
+
router.delete('/:id', <%= resourceNameCamel %>Controller.delete<%= resourceNamePascal %>);
|
|
87
|
+
|
|
88
|
+
module.exports = router;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const { createLogger } = require('../utils/logger');
|
|
2
|
+
|
|
3
|
+
const logger = createLogger('<%= resourceNamePascal %>Service');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get all <%= resourceNamePlural %> with pagination
|
|
7
|
+
*/
|
|
8
|
+
const getAll<%= resourceNamePascal %>s = async (page = 1, limit = 10) => {
|
|
9
|
+
logger.info('Fetching all <%= resourceNamePlural %>', { page, limit });
|
|
10
|
+
|
|
11
|
+
// TODO: Implement database logic here
|
|
12
|
+
// Example (Prisma): return prisma.<%= resourceNameCamel %>.findMany({ skip: (page - 1) * limit, take: limit });
|
|
13
|
+
// Example (Mongoose): return <%= resourceNamePascal %>.find().skip((page - 1) * limit).limit(limit);
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
<%= resourceNamePlural %>: [],
|
|
17
|
+
total: 0,
|
|
18
|
+
totalPages: 0,
|
|
19
|
+
currentPage: page,
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get <%= resourceNameCamel %> by ID
|
|
25
|
+
*/
|
|
26
|
+
const get<%= resourceNamePascal %>ById = async (id) => {
|
|
27
|
+
logger.info('Fetching <%= resourceNameCamel %> by ID', { id });
|
|
28
|
+
|
|
29
|
+
// TODO: Implement database logic here
|
|
30
|
+
// Example (Prisma): return prisma.<%= resourceNameCamel %>.findUnique({ where: { id } });
|
|
31
|
+
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create new <%= resourceNameCamel %>
|
|
37
|
+
*/
|
|
38
|
+
const create<%= resourceNamePascal %> = async (data) => {
|
|
39
|
+
logger.info('Creating new <%= resourceNameCamel %>');
|
|
40
|
+
|
|
41
|
+
// TODO: Implement database logic here
|
|
42
|
+
// Example (Prisma): return prisma.<%= resourceNameCamel %>.create({ data });
|
|
43
|
+
|
|
44
|
+
return { id: 'new-id', ...data };
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Update <%= resourceNameCamel %> by ID
|
|
49
|
+
*/
|
|
50
|
+
const update<%= resourceNamePascal %> = async (id, data) => {
|
|
51
|
+
logger.info('Updating <%= resourceNameCamel %>', { id });
|
|
52
|
+
|
|
53
|
+
// TODO: Implement database logic here
|
|
54
|
+
// Example (Prisma): return prisma.<%= resourceNameCamel %>.update({ where: { id }, data });
|
|
55
|
+
|
|
56
|
+
return { id, ...data };
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Delete <%= resourceNameCamel %> by ID
|
|
61
|
+
*/
|
|
62
|
+
const delete<%= resourceNamePascal %> = async (id) => {
|
|
63
|
+
logger.info('Deleting <%= resourceNameCamel %>', { id });
|
|
64
|
+
|
|
65
|
+
// TODO: Implement database logic here
|
|
66
|
+
// Example (Prisma): await prisma.<%= resourceNameCamel %>.delete({ where: { id } });
|
|
67
|
+
|
|
68
|
+
return true;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
getAll<%= resourceNamePascal %>s,
|
|
73
|
+
get<%= resourceNamePascal %>ById,
|
|
74
|
+
create<%= resourceNamePascal %>,
|
|
75
|
+
update<%= resourceNamePascal %>,
|
|
76
|
+
delete<%= resourceNamePascal %>,
|
|
77
|
+
};
|