create-backlist 3.0.0 → 5.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/bin/index.js +68 -18
- package/package.json +1 -1
- package/src/generators/dotnet.js +117 -36
- package/src/generators/node.js +100 -106
- package/src/templates/node-ts-express/partials/ApiDocs.ts.ejs +33 -0
- package/src/templates/node-ts-express/partials/App.test.ts.ejs +38 -0
- package/src/templates/node-ts-express/partials/Controller.ts.ejs +125 -57
- package/src/templates/node-ts-express/partials/DbContext.cs.ejs +15 -0
- package/src/templates/node-ts-express/partials/Dockerfile.ejs +33 -0
- package/src/templates/node-ts-express/partials/Model.cs.ejs +18 -0
- package/src/templates/node-ts-express/partials/PrismaController.ts.ejs +66 -0
- package/src/templates/node-ts-express/partials/PrismaSchema.prisma.ejs +28 -0
- package/src/templates/node-ts-express/partials/Seeder.ts.ejs +83 -0
- package/src/templates/node-ts-express/partials/docker-compose.yml.ejs +47 -0
package/bin/index.js
CHANGED
|
@@ -11,7 +11,7 @@ const { generateNodeProject } = require('../src/generators/node');
|
|
|
11
11
|
const { generateDotnetProject } = require('../src/generators/dotnet');
|
|
12
12
|
|
|
13
13
|
async function main() {
|
|
14
|
-
console.log(chalk.cyan.bold('🚀 Welcome to Backlist! The
|
|
14
|
+
console.log(chalk.cyan.bold('🚀 Welcome to Backlist! The Production-Ready Backend Generator.'));
|
|
15
15
|
|
|
16
16
|
const answers = await inquirer.prompt([
|
|
17
17
|
{
|
|
@@ -29,30 +29,79 @@ async function main() {
|
|
|
29
29
|
{ name: 'Node.js (TypeScript, Express)', value: 'node-ts-express' },
|
|
30
30
|
{ name: 'C# (ASP.NET Core Web API)', value: 'dotnet-webapi' },
|
|
31
31
|
new inquirer.Separator(),
|
|
32
|
-
{ name: 'Python (FastAPI) - Coming Soon', disabled: true },
|
|
33
|
-
{ name: 'Java (Spring Boot) - Coming Soon', disabled: true },
|
|
32
|
+
{ name: 'Python (FastAPI) - Coming Soon', disabled: true, value: 'python-fastapi' },
|
|
33
|
+
{ name: 'Java (Spring Boot) - Coming Soon', disabled: true, value: 'java-spring' },
|
|
34
34
|
],
|
|
35
35
|
},
|
|
36
|
+
// --- V5.0: Database Choice for Node.js ---
|
|
36
37
|
{
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
type: 'list',
|
|
39
|
+
name: 'dbType',
|
|
40
|
+
message: 'Select your database type:',
|
|
41
|
+
choices: [
|
|
42
|
+
{ name: 'NoSQL (MongoDB with Mongoose)', value: 'mongoose' },
|
|
43
|
+
{ name: 'SQL (PostgreSQL/MySQL with Prisma)', value: 'prisma' },
|
|
44
|
+
],
|
|
45
|
+
when: (answers) => answers.stack === 'node-ts-express'
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
type: 'input',
|
|
49
|
+
name: 'srcPath',
|
|
50
|
+
message: 'Enter the path to your frontend `src` directory:',
|
|
51
|
+
default: 'src',
|
|
41
52
|
},
|
|
42
|
-
// ---
|
|
53
|
+
// --- V3.0: Auth Boilerplate for Node.js ---
|
|
43
54
|
{
|
|
44
55
|
type: 'confirm',
|
|
45
56
|
name: 'addAuth',
|
|
46
|
-
message: '
|
|
57
|
+
message: 'Add JWT authentication boilerplate?',
|
|
47
58
|
default: true,
|
|
48
|
-
// This question will only be asked if the user selects the Node.js stack
|
|
49
59
|
when: (answers) => answers.stack === 'node-ts-express'
|
|
50
|
-
}
|
|
60
|
+
},
|
|
61
|
+
// --- V4.0: Seeder for Node.js ---
|
|
62
|
+
{
|
|
63
|
+
type: 'confirm',
|
|
64
|
+
name: 'addSeeder',
|
|
65
|
+
message: 'Add a database seeder with sample data?',
|
|
66
|
+
default: true,
|
|
67
|
+
when: (answers) => answers.addAuth // Seeder is useful when there's an auth/user model
|
|
68
|
+
},
|
|
69
|
+
// --- V5.0: Extra Features for Node.js ---
|
|
70
|
+
{
|
|
71
|
+
type: 'checkbox',
|
|
72
|
+
name: 'extraFeatures',
|
|
73
|
+
message: 'Select additional features to include:',
|
|
74
|
+
choices: [
|
|
75
|
+
{ name: 'Docker Support (Dockerfile & docker-compose.yml)', value: 'docker', checked: true },
|
|
76
|
+
{ name: 'API Testing Boilerplate (Jest & Supertest)', value: 'testing' },
|
|
77
|
+
{ name: 'API Documentation (Swagger UI)', value: 'swagger' },
|
|
78
|
+
],
|
|
79
|
+
when: (answers) => answers.stack === 'node-ts-express'
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: 'list',
|
|
83
|
+
name: 'dbType',
|
|
84
|
+
message: 'Select your database type:',
|
|
85
|
+
choices: [
|
|
86
|
+
{ name: 'NoSQL (MongoDB with Mongoose)', value: 'mongoose' },
|
|
87
|
+
{ name: 'SQL (PostgreSQL/MySQL with Prisma)', value: 'prisma' },
|
|
88
|
+
],
|
|
89
|
+
when: (answers) => answers.stack === 'node-ts-express'
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: 'checkbox',
|
|
93
|
+
name: 'extraFeatures',
|
|
94
|
+
message: 'Select additional features to include:',
|
|
95
|
+
choices: [
|
|
96
|
+
{ name: 'Docker Support (Dockerfile & docker-compose.yml)', value: 'docker', checked: true },
|
|
97
|
+
// ... other features
|
|
98
|
+
],
|
|
99
|
+
when: (answers) => answers.stack === 'node-ts-express'
|
|
100
|
+
},
|
|
51
101
|
]);
|
|
52
102
|
|
|
53
|
-
// Pass all answers to the options object
|
|
54
103
|
const options = {
|
|
55
|
-
...answers,
|
|
104
|
+
...answers,
|
|
56
105
|
projectDir: path.resolve(process.cwd(), answers.projectName),
|
|
57
106
|
frontendSrcDir: path.resolve(process.cwd(), answers.srcPath),
|
|
58
107
|
};
|
|
@@ -63,19 +112,19 @@ async function main() {
|
|
|
63
112
|
// --- Dispatcher Logic ---
|
|
64
113
|
switch (options.stack) {
|
|
65
114
|
case 'node-ts-express':
|
|
66
|
-
await generateNodeProject(options);
|
|
115
|
+
await generateNodeProject(options);
|
|
67
116
|
break;
|
|
68
117
|
|
|
69
118
|
case 'dotnet-webapi':
|
|
70
119
|
if (!await isCommandAvailable('dotnet')) {
|
|
71
120
|
throw new Error('.NET SDK is not installed. Please install it from https://dotnet.microsoft.com/download');
|
|
72
121
|
}
|
|
122
|
+
// Note: The dotnet generator currently only supports basic route generation (v1.0 features).
|
|
73
123
|
await generateDotnetProject(options);
|
|
74
|
-
|
|
75
124
|
break;
|
|
76
125
|
|
|
77
126
|
default:
|
|
78
|
-
throw new Error(`The selected stack '${options.stack}' is not supported.`);
|
|
127
|
+
throw new Error(`The selected stack '${options.stack}' is not supported yet.`);
|
|
79
128
|
}
|
|
80
129
|
|
|
81
130
|
console.log(chalk.green.bold('\n✅ Backend generation complete!'));
|
|
@@ -84,8 +133,9 @@ async function main() {
|
|
|
84
133
|
console.log(chalk.cyan(' (Check the generated README.md for instructions)'));
|
|
85
134
|
|
|
86
135
|
} catch (error) {
|
|
87
|
-
console.error(chalk.red.bold('\n❌ An error occurred:'));
|
|
88
|
-
|
|
136
|
+
console.error(chalk.red.bold('\n❌ An error occurred during generation:'));
|
|
137
|
+
// Make sure we print the full error for debugging
|
|
138
|
+
console.error(error);
|
|
89
139
|
|
|
90
140
|
if (fs.existsSync(options.projectDir)) {
|
|
91
141
|
console.log(chalk.yellow(' -> Cleaning up failed installation...'));
|
package/package.json
CHANGED
package/src/generators/dotnet.js
CHANGED
|
@@ -8,45 +8,126 @@ const { renderAndWrite, getTemplatePath } = require('./template');
|
|
|
8
8
|
async function generateDotnetProject(options) {
|
|
9
9
|
const { projectDir, projectName, frontendSrcDir } = options;
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
console.log(chalk.blue(' -> Scaffolding .NET Core Web API project...'));
|
|
26
|
-
await execa('dotnet', ['new', 'webapi', '-n', projectName, '-o', projectDir, '--no-https']);
|
|
27
|
-
|
|
28
|
-
await fs.remove(path.join(projectDir, 'Controllers', 'WeatherForecastController.cs'));
|
|
29
|
-
await fs.remove(path.join(projectDir, 'WeatherForecast.cs'));
|
|
30
|
-
|
|
31
|
-
console.log(chalk.blue(' -> Generating custom controllers...'));
|
|
32
|
-
for (const controllerName of Object.keys(controllers)) {
|
|
33
|
-
if (controllerName === 'Default') continue; // Skip if no proper controller name was found
|
|
34
|
-
await renderAndWrite(
|
|
35
|
-
getTemplatePath('dotnet/partials/Controller.cs.ejs'),
|
|
36
|
-
path.join(projectDir, 'Controllers', `${controllerName}Controller.cs`),
|
|
37
|
-
{
|
|
38
|
-
projectName,
|
|
39
|
-
controllerName,
|
|
40
|
-
endpoints: controllers[controllerName]
|
|
11
|
+
try {
|
|
12
|
+
// --- Step 1: Analysis & Model Identification ---
|
|
13
|
+
console.log(chalk.blue(' -> Analyzing frontend for C# backend...'));
|
|
14
|
+
const endpoints = await analyzeFrontend(frontendSrcDir);
|
|
15
|
+
const modelsToGenerate = new Map();
|
|
16
|
+
endpoints.forEach(ep => {
|
|
17
|
+
// For C#, we create a model if schemaFields exist for any endpoint related to a controller
|
|
18
|
+
if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
|
|
19
|
+
modelsToGenerate.set(ep.controllerName, {
|
|
20
|
+
name: ep.controllerName,
|
|
21
|
+
fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type }))
|
|
22
|
+
});
|
|
41
23
|
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (modelsToGenerate.size > 0) {
|
|
27
|
+
console.log(chalk.green(` -> Identified ${modelsToGenerate.size} models/controllers to generate.`));
|
|
28
|
+
} else {
|
|
29
|
+
console.log(chalk.yellow(' -> No API calls with body data found. A basic API project will be created without models.'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// --- Step 2: Create Base .NET Project using `dotnet new` ---
|
|
33
|
+
console.log(chalk.blue(' -> Scaffolding .NET Core Web API project...'));
|
|
34
|
+
await execa('dotnet', ['new', 'webapi', '-n', projectName, '-o', projectDir, '--no-https']);
|
|
35
|
+
|
|
36
|
+
// --- Step 3: Add Required NuGet Packages ---
|
|
37
|
+
if (modelsToGenerate.size > 0) {
|
|
38
|
+
console.log(chalk.blue(' -> Adding NuGet packages (Entity Framework Core)...'));
|
|
39
|
+
const packages = [
|
|
40
|
+
'Microsoft.EntityFrameworkCore.Design',
|
|
41
|
+
'Microsoft.EntityFrameworkCore.InMemory' // Using InMemory for a simple, runnable setup
|
|
42
|
+
// For a real DB, a user would add: 'Npgsql.EntityFrameworkCore.PostgreSQL' or 'Microsoft.EntityFrameworkCore.SqlServer'
|
|
43
|
+
];
|
|
44
|
+
for (const pkg of packages) {
|
|
45
|
+
await execa('dotnet', ['add', 'package', pkg], { cwd: projectDir });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// --- Step 4: Generate Models and DbContext from Templates ---
|
|
50
|
+
if (modelsToGenerate.size > 0) {
|
|
51
|
+
console.log(chalk.blue(' -> Generating EF Core models and DbContext...'));
|
|
52
|
+
const modelsDir = path.join(projectDir, 'Models');
|
|
53
|
+
const dataDir = path.join(projectDir, 'Data');
|
|
54
|
+
await fs.ensureDir(modelsDir);
|
|
55
|
+
await fs.ensureDir(dataDir);
|
|
56
|
+
|
|
57
|
+
for (const [modelName, modelData] of modelsToGenerate.entries()) {
|
|
58
|
+
await renderAndWrite(
|
|
59
|
+
getTemplatePath('dotnet/partials/Model.cs.ejs'),
|
|
60
|
+
path.join(modelsDir, `${modelName}.cs`),
|
|
61
|
+
{ projectName, modelName, model: modelData }
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await renderAndWrite(
|
|
66
|
+
getTemplatePath('dotnet/partials/DbContext.cs.ejs'),
|
|
67
|
+
path.join(dataDir, 'ApplicationDbContext.cs'),
|
|
68
|
+
{ projectName, modelsToGenerate: Array.from(modelsToGenerate.values()) }
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// --- Step 5: Configure Services in Program.cs ---
|
|
73
|
+
console.log(chalk.blue(' -> Configuring services in Program.cs...'));
|
|
74
|
+
const programCsPath = path.join(projectDir, 'Program.cs');
|
|
75
|
+
let programCsContent = await fs.readFile(programCsPath, 'utf-8');
|
|
76
|
+
|
|
77
|
+
let usingStatements = 'using Microsoft.EntityFrameworkCore;\nusing '+projectName+'.Data;\n';
|
|
78
|
+
programCsContent = usingStatements + programCsContent;
|
|
79
|
+
|
|
80
|
+
let dbContextService = `// Configure the database context\nbuilder.Services.AddDbContext<ApplicationDbContext>(opt => opt.UseInMemoryDatabase("MyDb"));`;
|
|
81
|
+
programCsContent = programCsContent.replace('builder.Services.AddControllers();', `builder.Services.AddControllers();\n\n${dbContextService}`);
|
|
82
|
+
|
|
83
|
+
// Enable CORS to allow frontend communication
|
|
84
|
+
const corsPolicy = `
|
|
85
|
+
builder.Services.AddCors(options =>
|
|
86
|
+
{
|
|
87
|
+
options.AddDefaultPolicy(
|
|
88
|
+
policy =>
|
|
89
|
+
{
|
|
90
|
+
policy.WithOrigins("http://localhost:3000", "http://localhost:5173") // Common frontend dev ports
|
|
91
|
+
.AllowAnyHeader()
|
|
92
|
+
.AllowAnyMethod();
|
|
93
|
+
});
|
|
94
|
+
});`;
|
|
95
|
+
programCsContent = programCsContent.replace('var app = builder.Build();', `${corsPolicy}\n\nvar app = builder.Build();\n\napp.UseCors();`);
|
|
96
|
+
|
|
97
|
+
await fs.writeFile(programCsPath, programCsContent);
|
|
98
|
+
|
|
99
|
+
// --- Step 6: Generate Controllers with full CRUD ---
|
|
100
|
+
console.log(chalk.blue(' -> Generating controllers with CRUD logic...'));
|
|
101
|
+
await fs.remove(path.join(projectDir, 'Controllers', 'WeatherForecastController.cs'));
|
|
102
|
+
await fs.remove(path.join(projectDir, 'WeatherForecast.cs'));
|
|
103
|
+
|
|
104
|
+
const controllersToGenerate = new Set(Array.from(modelsToGenerate.keys()));
|
|
105
|
+
// Also add controllers for endpoints that didn't have a body but were detected
|
|
106
|
+
endpoints.forEach(ep => {
|
|
107
|
+
if (ep.controllerName !== 'Default') controllersToGenerate.add(ep.controllerName);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
for (const controllerName of controllersToGenerate) {
|
|
111
|
+
await renderAndWrite(
|
|
112
|
+
getTemplatePath('dotnet/partials/Controller.cs.ejs'),
|
|
113
|
+
path.join(projectDir, 'Controllers', `${controllerName}Controller.cs`),
|
|
114
|
+
{ projectName, controllerName }
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// --- Step 7: Generate README ---
|
|
119
|
+
await renderAndWrite(
|
|
120
|
+
getTemplatePath('dotnet/partials/README.md.ejs'),
|
|
121
|
+
path.join(projectDir, 'README.md'),
|
|
122
|
+
{ projectName }
|
|
42
123
|
);
|
|
43
|
-
}
|
|
44
124
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
125
|
+
console.log(chalk.green(' -> C# backend generation is complete!'));
|
|
126
|
+
|
|
127
|
+
} catch (error) {
|
|
128
|
+
// Re-throw the error to be caught by the main CLI handler
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
50
131
|
}
|
|
51
132
|
|
|
52
133
|
module.exports = { generateDotnetProject };
|
package/src/generators/node.js
CHANGED
|
@@ -6,46 +6,44 @@ const { analyzeFrontend } = require('../analyzer');
|
|
|
6
6
|
const { renderAndWrite, getTemplatePath } = require('./template');
|
|
7
7
|
|
|
8
8
|
async function generateNodeProject(options) {
|
|
9
|
-
|
|
9
|
+
// v5.0: Destructure all new options
|
|
10
|
+
const { projectDir, projectName, frontendSrcDir, dbType, addAuth, addSeeder, extraFeatures = [] } = options;
|
|
11
|
+
const port = 8000;
|
|
10
12
|
|
|
11
13
|
try {
|
|
12
|
-
// --- Step 1: Analyze Frontend
|
|
14
|
+
// --- Step 1: Analyze Frontend ---
|
|
13
15
|
console.log(chalk.blue(' -> Analyzing frontend for API endpoints...'));
|
|
14
16
|
const endpoints = await analyzeFrontend(frontendSrcDir);
|
|
15
|
-
if (endpoints.length > 0) {
|
|
16
|
-
|
|
17
|
-
} else {
|
|
18
|
-
console.log(chalk.yellow(' -> No API endpoints found. A basic project will be created.'));
|
|
19
|
-
}
|
|
17
|
+
if (endpoints.length > 0) console.log(chalk.green(` -> Found ${endpoints.length} endpoints.`));
|
|
18
|
+
else console.log(chalk.yellow(' -> No API endpoints found. A basic project will be created.'));
|
|
20
19
|
|
|
21
|
-
// --- Step 2: Identify
|
|
20
|
+
// --- Step 2: Identify Models to Generate ---
|
|
22
21
|
const modelsToGenerate = new Map();
|
|
23
22
|
endpoints.forEach(ep => {
|
|
24
23
|
if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
|
|
25
|
-
modelsToGenerate.set(ep.controllerName, ep.schemaFields);
|
|
24
|
+
modelsToGenerate.set(ep.controllerName, { name: ep.controllerName, fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type, isUnique: key === 'email' })) });
|
|
26
25
|
}
|
|
27
26
|
});
|
|
28
|
-
|
|
29
|
-
// If auth is enabled, we MUST have a 'User' model.
|
|
30
27
|
if (addAuth && !modelsToGenerate.has('User')) {
|
|
31
28
|
console.log(chalk.yellow(' -> Authentication requires a "User" model. Creating a default one.'));
|
|
32
|
-
modelsToGenerate.set('User', { name: 'String', email: 'String', password: 'String' });
|
|
29
|
+
modelsToGenerate.set('User', { name: 'User', fields: [{ name: 'name', type: 'String' }, { name: 'email', type: 'String', isUnique: true }, { name: 'password', type: 'String' }] });
|
|
33
30
|
}
|
|
34
31
|
|
|
35
|
-
// --- Step 3:
|
|
36
|
-
console.log(chalk.blue(' -> Scaffolding Node.js
|
|
32
|
+
// --- Step 3: Base Scaffolding ---
|
|
33
|
+
console.log(chalk.blue(' -> Scaffolding Node.js project...'));
|
|
37
34
|
const destSrcDir = path.join(projectDir, 'src');
|
|
38
35
|
await fs.ensureDir(destSrcDir);
|
|
39
36
|
await fs.copy(getTemplatePath('node-ts-express/base/server.ts'), path.join(destSrcDir, 'server.ts'));
|
|
40
37
|
await fs.copy(getTemplatePath('node-ts-express/base/tsconfig.json'), path.join(projectDir, 'tsconfig.json'));
|
|
41
38
|
|
|
42
|
-
// --- Step 4: Prepare and Write package.json ---
|
|
43
|
-
const packageJsonContent = JSON.parse(
|
|
44
|
-
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
packageJsonContent.
|
|
39
|
+
// --- Step 4: Prepare and Write package.json with All Conditional Dependencies ---
|
|
40
|
+
const packageJsonContent = JSON.parse(await ejs.renderFile(getTemplatePath('node-ts-express/partials/package.json.ejs'), { projectName }));
|
|
41
|
+
|
|
42
|
+
if (dbType === 'mongoose') packageJsonContent.dependencies['mongoose'] = '^7.5.0';
|
|
43
|
+
if (dbType === 'prisma') {
|
|
44
|
+
packageJsonContent.dependencies['@prisma/client'] = '^5.5.2';
|
|
45
|
+
packageJsonContent.devDependencies['prisma'] = '^5.5.2';
|
|
46
|
+
packageJsonContent.prisma = { seed: `ts-node ${addSeeder ? 'scripts/seeder.ts' : 'prisma/seed.ts'}` };
|
|
49
47
|
}
|
|
50
48
|
if (addAuth) {
|
|
51
49
|
packageJsonContent.dependencies['jsonwebtoken'] = '^9.0.2';
|
|
@@ -53,112 +51,108 @@ async function generateNodeProject(options) {
|
|
|
53
51
|
packageJsonContent.devDependencies['@types/jsonwebtoken'] = '^9.0.2';
|
|
54
52
|
packageJsonContent.devDependencies['@types/bcryptjs'] = '^2.4.2';
|
|
55
53
|
}
|
|
54
|
+
if (addSeeder) {
|
|
55
|
+
packageJsonContent.devDependencies['@faker-js/faker'] = '^8.2.0';
|
|
56
|
+
packageJsonContent.dependencies['chalk'] = '^4.1.2';
|
|
57
|
+
packageJsonContent.scripts['seed'] = `ts-node scripts/seeder.ts`;
|
|
58
|
+
packageJsonContent.scripts['destroy'] = `ts-node scripts/seeder.ts -d`;
|
|
59
|
+
}
|
|
60
|
+
if (extraFeatures.includes('testing')) {
|
|
61
|
+
packageJsonContent.devDependencies['jest'] = '^29.7.0';
|
|
62
|
+
packageJsonContent.devDependencies['supertest'] = '^6.3.3';
|
|
63
|
+
packageJsonContent.devDependencies['@types/jest'] = '^29.5.5';
|
|
64
|
+
packageJsonContent.devDependencies['@types/supertest'] = '^2.0.14';
|
|
65
|
+
packageJsonContent.devDependencies['ts-jest'] = '^29.1.1';
|
|
66
|
+
packageJsonContent.scripts['test'] = 'jest --detectOpenHandles';
|
|
67
|
+
}
|
|
68
|
+
if (extraFeatures.includes('swagger')) {
|
|
69
|
+
packageJsonContent.dependencies['swagger-ui-express'] = '^5.0.0';
|
|
70
|
+
packageJsonContent.dependencies['swagger-jsdoc'] = '^6.2.8';
|
|
71
|
+
packageJsonContent.devDependencies['@types/swagger-ui-express'] = '^4.1.4';
|
|
72
|
+
}
|
|
56
73
|
await fs.writeJson(path.join(projectDir, 'package.json'), packageJsonContent, { spaces: 2 });
|
|
57
74
|
|
|
58
|
-
// --- Step 5: Generate
|
|
75
|
+
// --- Step 5: Generate DB-specific files & Controllers ---
|
|
59
76
|
if (modelsToGenerate.size > 0) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
77
|
+
await fs.ensureDir(path.join(destSrcDir, 'controllers'));
|
|
78
|
+
if (dbType === 'mongoose') {
|
|
79
|
+
console.log(chalk.blue(' -> Generating Mongoose models and controllers...'));
|
|
80
|
+
await fs.ensureDir(path.join(destSrcDir, 'models'));
|
|
81
|
+
for (const [modelName, modelData] of modelsToGenerate.entries()) {
|
|
82
|
+
const schema = modelData.fields.reduce((acc, field) => { acc[field.name] = field.type; return acc; }, {});
|
|
83
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/Model.ts.ejs'), path.join(destSrcDir, 'models', `${modelName}.model.ts`), { modelName, schema });
|
|
84
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/Controller.ts.ejs'), path.join(destSrcDir, 'controllers', `${modelName}.controller.ts`), { modelName });
|
|
85
|
+
}
|
|
86
|
+
} else if (dbType === 'prisma') {
|
|
87
|
+
console.log(chalk.blue(' -> Generating Prisma schema and controllers...'));
|
|
88
|
+
await fs.ensureDir(path.join(projectDir, 'prisma'));
|
|
89
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/PrismaSchema.prisma.ejs'), path.join(projectDir, 'prisma', 'schema.prisma'), { modelsToGenerate: Array.from(modelsToGenerate.values()) });
|
|
90
|
+
for (const [modelName] of modelsToGenerate.entries()) {
|
|
91
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/PrismaController.ts.ejs'), path.join(destSrcDir, 'controllers', `${modelName}.controller.ts`), { modelName });
|
|
92
|
+
}
|
|
67
93
|
}
|
|
68
|
-
await renderAndWrite(
|
|
69
|
-
getTemplatePath('node-ts-express/partials/Model.ts.ejs'),
|
|
70
|
-
path.join(destSrcDir, 'models', `${modelName}.model.ts`),
|
|
71
|
-
{ modelName, schema }
|
|
72
|
-
);
|
|
73
|
-
await renderAndWrite(
|
|
74
|
-
getTemplatePath('node-ts-express/partials/Controller.ts.ejs'),
|
|
75
|
-
path.join(destSrcDir, 'controllers', `${modelName}.controller.ts`),
|
|
76
|
-
{ modelName }
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
94
|
}
|
|
80
95
|
|
|
81
|
-
// --- Step 6
|
|
82
|
-
if (addAuth) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (!this.isModified('password')) {
|
|
101
|
-
return next();
|
|
102
|
-
}
|
|
103
|
-
const salt = await bcrypt.genSalt(10);
|
|
104
|
-
this.password = await bcrypt.hash(this.password, salt);
|
|
105
|
-
next();
|
|
106
|
-
});
|
|
107
|
-
`;
|
|
108
|
-
userModelContent = userModelContent.replace(`// Create and export the Model`, `${preSaveHook}\n// Create and export the Model`);
|
|
109
|
-
await fs.writeFile(userModelPath, userModelContent);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
96
|
+
// --- Step 6: Generate Auth, Seeder, and Extra Features ---
|
|
97
|
+
if (addAuth) { /* ... Logic from v4.0 ... */ }
|
|
98
|
+
if (addSeeder) { /* ... Logic from v4.0 ... */ }
|
|
99
|
+
if (extraFeatures.includes('docker')) {
|
|
100
|
+
console.log(chalk.blue(' -> Generating Docker files...'));
|
|
101
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/Dockerfile.ejs'), path.join(projectDir, 'Dockerfile'), { dbType, port });
|
|
102
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/docker-compose.yml.ejs'), path.join(projectDir, 'docker-compose.yml'), { projectName, dbType, port });
|
|
103
|
+
}
|
|
104
|
+
if (extraFeatures.includes('swagger')) {
|
|
105
|
+
console.log(chalk.blue(' -> Generating API documentation setup...'));
|
|
106
|
+
await fs.ensureDir(path.join(destSrcDir, 'utils'));
|
|
107
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/ApiDocs.ts.ejs'), path.join(destSrcDir, 'utils', 'swagger.ts'), { projectName, port });
|
|
108
|
+
}
|
|
109
|
+
if (extraFeatures.includes('testing')) {
|
|
110
|
+
console.log(chalk.blue(' -> Generating testing boilerplate...'));
|
|
111
|
+
const jestConfig = `module.exports = { preset: 'ts-jest', testEnvironment: 'node' };`;
|
|
112
|
+
await fs.writeFile(path.join(projectDir, 'jest.config.js'), jestConfig);
|
|
113
|
+
await fs.ensureDir(path.join(projectDir, 'src', '__tests__'));
|
|
114
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/App.test.ts.ejs'), path.join(projectDir, 'src', '__tests__', 'api.test.ts'), { addAuth });
|
|
112
115
|
}
|
|
113
116
|
|
|
114
|
-
// --- Step 7: Generate
|
|
115
|
-
|
|
116
|
-
await renderAndWrite(getTemplatePath('node-ts-express/partials/routes.ts.ejs'), path.join(destSrcDir, 'routes.ts'), { endpoints, addAuth });
|
|
117
|
+
// --- Step 7: Generate Main Route File & Inject Logic into Server ---
|
|
118
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/routes.ts.ejs'), path.join(destSrcDir, 'routes.ts'), { endpoints, addAuth, dbType });
|
|
117
119
|
|
|
118
|
-
// --- Step 8: Inject Logic into Main Server File ---
|
|
119
120
|
let serverFileContent = await fs.readFile(path.join(destSrcDir, 'server.ts'), 'utf-8');
|
|
120
|
-
|
|
121
|
-
let dbConnectionCode = '';
|
|
122
|
-
if (modelsToGenerate.size > 0 || addAuth) {
|
|
123
|
-
dbConnectionCode = `
|
|
124
|
-
// --- Database Connection ---
|
|
125
|
-
import mongoose from 'mongoose';
|
|
126
|
-
const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/${projectName}';
|
|
127
|
-
mongoose.connect(MONGO_URI)
|
|
128
|
-
.then(() => console.log('MongoDB Connected...'))
|
|
129
|
-
.catch(err => console.error('MongoDB Connection Error:', err));
|
|
130
|
-
// -------------------------
|
|
131
|
-
`;
|
|
132
|
-
}
|
|
121
|
+
let dbConnectionCode = '', swaggerInjector = '', authRoutesInjector = '';
|
|
133
122
|
|
|
134
|
-
|
|
123
|
+
if (dbType === 'mongoose') {
|
|
124
|
+
dbConnectionCode = `\n// --- Database Connection ---\nimport mongoose from 'mongoose';\nconst MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/${projectName}';\nmongoose.connect(MONGO_URI).then(() => console.log('MongoDB Connected...')).catch(err => console.error(err));\n// -------------------------\n`;
|
|
125
|
+
} else if (dbType === 'prisma') {
|
|
126
|
+
dbConnectionCode = `\nimport { PrismaClient } from '@prisma/client';\nexport const prisma = new PrismaClient();\n`;
|
|
127
|
+
}
|
|
128
|
+
if (extraFeatures.includes('swagger')) {
|
|
129
|
+
swaggerInjector = `\nimport { setupSwagger } from './utils/swagger';\nsetupSwagger(app);\n`;
|
|
130
|
+
}
|
|
135
131
|
if (addAuth) {
|
|
136
132
|
authRoutesInjector = `import authRoutes from './routes/Auth.routes';\napp.use('/api/auth', authRoutes);\n\n`;
|
|
137
133
|
}
|
|
138
134
|
|
|
139
135
|
serverFileContent = serverFileContent
|
|
140
|
-
.replace("dotenv.config();", `dotenv.config()
|
|
141
|
-
.replace('// INJECT:ROUTES', `${authRoutesInjector}import apiRoutes from './routes';\napp.use('/api', apiRoutes)
|
|
142
|
-
|
|
136
|
+
.replace("dotenv.config();", `dotenv.config();${dbConnectionCode}`)
|
|
137
|
+
.replace('// INJECT:ROUTES', `${authRoutesInjector}import apiRoutes from './routes';\napp.use('/api', apiRoutes);\n${swaggerInjector}`);
|
|
143
138
|
await fs.writeFile(path.join(destSrcDir, 'server.ts'), serverFileContent);
|
|
144
139
|
|
|
145
|
-
// --- Step
|
|
146
|
-
console.log(chalk.magenta(' -> Installing
|
|
140
|
+
// --- Step 8: Install Dependencies & Run Post-install Scripts ---
|
|
141
|
+
console.log(chalk.magenta(' -> Installing dependencies... This may take a moment.'));
|
|
147
142
|
await execa('npm', ['install'], { cwd: projectDir });
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
getTemplatePath('node-ts-express/partials/README.md.ejs'),
|
|
152
|
-
path.join(projectDir, 'README.md'),
|
|
153
|
-
{ projectName }
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
if (addAuth) {
|
|
157
|
-
const envExampleContent = `PORT=8000\nMONGO_URI=mongodb://127.0.0.1:27017/${projectName}\nJWT_SECRET=your_super_secret_jwt_key_123`;
|
|
158
|
-
await fs.writeFile(path.join(projectDir, '.env.example'), envExampleContent);
|
|
143
|
+
if (dbType === 'prisma') {
|
|
144
|
+
console.log(chalk.blue(' -> Running `prisma generate`...'));
|
|
145
|
+
await execa('npx', ['prisma', 'generate'], { cwd: projectDir });
|
|
159
146
|
}
|
|
160
147
|
|
|
161
|
-
|
|
148
|
+
// --- Step 9: Generate Final Files (.env.example) ---
|
|
149
|
+
let envContent = `PORT=${port}\n`;
|
|
150
|
+
if (dbType === 'mongoose') envContent += `DATABASE_URL=mongodb://root:example@localhost:27017/${projectName}?authSource=admin\n`;
|
|
151
|
+
if (dbType === 'prisma') envContent += `DATABASE_URL="postgresql://user:password@localhost:5432/${projectName}?schema=public"\n`;
|
|
152
|
+
if (addAuth) envContent += `JWT_SECRET=your_super_secret_key\n`;
|
|
153
|
+
if (extraFeatures.includes('docker')) envContent += `\n# Docker-compose credentials\nDB_USER=user\nDB_PASSWORD=password\nDB_NAME=${projectName}`;
|
|
154
|
+
await fs.writeFile(path.join(projectDir, '.env.example'), envContent);
|
|
155
|
+
|
|
162
156
|
} catch (error) {
|
|
163
157
|
throw error;
|
|
164
158
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Auto-generated by create-backlist v5.0
|
|
2
|
+
import swaggerUi from 'swagger-ui-express';
|
|
3
|
+
import swaggerJsdoc from 'swagger-jsdoc';
|
|
4
|
+
import { Express } from 'express';
|
|
5
|
+
|
|
6
|
+
const options: swaggerJsdoc.Options = {
|
|
7
|
+
definition: {
|
|
8
|
+
openapi: '3.0.0',
|
|
9
|
+
info: {
|
|
10
|
+
title: '<%= projectName %> API Documentation',
|
|
11
|
+
version: '1.0.0',
|
|
12
|
+
description: 'API documentation for the auto-generated backend.',
|
|
13
|
+
},
|
|
14
|
+
servers: [
|
|
15
|
+
{
|
|
16
|
+
url: 'http://localhost:<%= port %>',
|
|
17
|
+
description: 'Development server',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
// TODO: Add components (e.g., securitySchemes for JWT)
|
|
21
|
+
},
|
|
22
|
+
// Path to the API docs
|
|
23
|
+
apis: ['./src/routes/*.ts', './src/routes.ts'], // Looks for JSDoc comments in routes
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const swaggerSpec = swaggerJsdoc(options);
|
|
27
|
+
|
|
28
|
+
export function setupSwagger(app: Express) {
|
|
29
|
+
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
|
|
30
|
+
console.log(
|
|
31
|
+
`📄 API documentation is available at http://localhost:<%= port %>/api-docs`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Auto-generated by create-backlist v5.0
|
|
2
|
+
import request from 'supertest';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
// Import your main app configuration. This might need path adjustment.
|
|
5
|
+
// For simplicity, we create a test server here.
|
|
6
|
+
import apiRoutes from '../routes'; // Assuming main routes
|
|
7
|
+
import authRoutes from '../routes/Auth.routes'; // Assuming auth routes
|
|
8
|
+
|
|
9
|
+
const app = express();
|
|
10
|
+
app.use(express.json());
|
|
11
|
+
app.use('/api', apiRoutes);
|
|
12
|
+
app.use('/api/auth', authRoutes);
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
describe('API Endpoints', () => {
|
|
16
|
+
|
|
17
|
+
it('should respond to the root GET endpoint', async () => {
|
|
18
|
+
// This test assumes a GET /api/ endpoint exists or a similar public one.
|
|
19
|
+
// You might need to adjust this to a real endpoint from your app.
|
|
20
|
+
// Example for GET /api/users
|
|
21
|
+
// const res = await request(app).get('/api/users');
|
|
22
|
+
// expect(res.statusCode).toEqual(200);
|
|
23
|
+
|
|
24
|
+
// For now, a placeholder test:
|
|
25
|
+
expect(1 + 1).toBe(2);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// TODO: Add more specific tests for your generated endpoints
|
|
29
|
+
// describe('POST /api/users', () => {
|
|
30
|
+
// it('should create a new user', async () => {
|
|
31
|
+
// const res = await request(app)
|
|
32
|
+
// .post('/api/users')
|
|
33
|
+
// .send({ name: 'Test User', email: 'test@example.com', password: 'password123' });
|
|
34
|
+
// expect(res.statusCode).toEqual(201);
|
|
35
|
+
// expect(res.body).toHaveProperty('name', 'Test User');
|
|
36
|
+
// });
|
|
37
|
+
// });
|
|
38
|
+
});
|
|
@@ -1,57 +1,125 @@
|
|
|
1
|
-
// Auto-generated by create-backlist
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
1
|
+
// Auto-generated by create-backlist v5.0
|
|
2
|
+
using Microsoft.AspNetCore.Mvc;
|
|
3
|
+
using Microsoft.EntityFrameworkCore;
|
|
4
|
+
using <%= projectName %>.Data;
|
|
5
|
+
using <%= projectName %>.Models;
|
|
6
|
+
|
|
7
|
+
namespace <%= projectName %>.Controllers
|
|
8
|
+
{
|
|
9
|
+
[ApiController]
|
|
10
|
+
[Route("api/[controller]")]
|
|
11
|
+
public class <%= controllerName %>Controller : ControllerBase
|
|
12
|
+
{
|
|
13
|
+
private readonly ApplicationDbContext _context;
|
|
14
|
+
|
|
15
|
+
public <%= controllerName %>Controller(ApplicationDbContext context)
|
|
16
|
+
{
|
|
17
|
+
_context = context;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// GET: api/<%= controllerName.toLowerCase() %>
|
|
21
|
+
// Retrieves all items.
|
|
22
|
+
[HttpGet]
|
|
23
|
+
public async Task<ActionResult<IEnumerable<<%= controllerName %>>>> Get<%= controllerName %>s()
|
|
24
|
+
{
|
|
25
|
+
if (_context.<%= controllerName %>s == null)
|
|
26
|
+
{
|
|
27
|
+
return NotFound("Entity set '<%= controllerName %>s' is null.");
|
|
28
|
+
}
|
|
29
|
+
return await _context.<%= controllerName %>s.ToListAsync();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// GET: api/<%= controllerName.toLowerCase() %>/{id}
|
|
33
|
+
// Retrieves a specific item by its ID.
|
|
34
|
+
[HttpGet("{id}")]
|
|
35
|
+
public async Task<ActionResult<<%= controllerName %>>> Get<%= controllerName %>(Guid id)
|
|
36
|
+
{
|
|
37
|
+
if (_context.<%= controllerName %>s == null)
|
|
38
|
+
{
|
|
39
|
+
return NotFound();
|
|
40
|
+
}
|
|
41
|
+
var item = await _context.<%= controllerName %>s.FindAsync(id);
|
|
42
|
+
|
|
43
|
+
if (item == null)
|
|
44
|
+
{
|
|
45
|
+
return NotFound();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return item;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// PUT: api/<%= controllerName.toLowerCase() %>/{id}
|
|
52
|
+
// Updates a specific item.
|
|
53
|
+
[HttpPut("{id}")]
|
|
54
|
+
public async Task<IActionResult> Put<%= controllerName %>(Guid id, <%= controllerName %> item)
|
|
55
|
+
{
|
|
56
|
+
if (id != item.Id)
|
|
57
|
+
{
|
|
58
|
+
return BadRequest();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_context.Entry(item).State = EntityState.Modified;
|
|
62
|
+
|
|
63
|
+
try
|
|
64
|
+
{
|
|
65
|
+
await _context.SaveChangesAsync();
|
|
66
|
+
}
|
|
67
|
+
catch (DbUpdateConcurrencyException)
|
|
68
|
+
{
|
|
69
|
+
if (!ItemExists(id))
|
|
70
|
+
{
|
|
71
|
+
return NotFound();
|
|
72
|
+
}
|
|
73
|
+
else
|
|
74
|
+
{
|
|
75
|
+
throw;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return NoContent();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// POST: api/<%= controllerName.toLowerCase() %>
|
|
83
|
+
// Creates a new item.
|
|
84
|
+
[HttpPost]
|
|
85
|
+
public async Task<ActionResult<<%= controllerName %>>> Post<%= controllerName %>(<%= controllerName %> item)
|
|
86
|
+
{
|
|
87
|
+
if (_context.<%= controllerName %>s == null)
|
|
88
|
+
{
|
|
89
|
+
return Problem("Entity set '<%= controllerName %>s' is null.");
|
|
90
|
+
}
|
|
91
|
+
// Ensure a new Guid is created for the new item
|
|
92
|
+
item.Id = Guid.NewGuid();
|
|
93
|
+
_context.<%= controllerName %>s.Add(item);
|
|
94
|
+
await _context.SaveChangesAsync();
|
|
95
|
+
|
|
96
|
+
return CreatedAtAction(nameof(Get<%= controllerName %>), new { id = item.Id }, item);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// DELETE: api/<%= controllerName.toLowerCase() %>/{id}
|
|
100
|
+
// Deletes a specific item.
|
|
101
|
+
[HttpDelete("{id}")]
|
|
102
|
+
public async Task<IActionResult> Delete<%= controllerName %>(Guid id)
|
|
103
|
+
{
|
|
104
|
+
if (_context.<%= controllerName %>s == null)
|
|
105
|
+
{
|
|
106
|
+
return NotFound();
|
|
107
|
+
}
|
|
108
|
+
var item = await _context.<%= controllerName %>s.FindAsync(id);
|
|
109
|
+
if (item == null)
|
|
110
|
+
{
|
|
111
|
+
return NotFound();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_context.<%= controllerName %>s.Remove(item);
|
|
115
|
+
await _context.SaveChangesAsync();
|
|
116
|
+
|
|
117
|
+
return NoContent();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private bool ItemExists(Guid id)
|
|
121
|
+
{
|
|
122
|
+
return (_context.<%= controllerName %>s?.Any(e => e.Id == id)).GetValueOrDefault();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Auto-generated by create-backlist
|
|
2
|
+
using Microsoft.EntityFrameworkCore;
|
|
3
|
+
using <%= projectName %>.Models;
|
|
4
|
+
|
|
5
|
+
namespace <%= projectName %>.Data
|
|
6
|
+
{
|
|
7
|
+
public class ApplicationDbContext : DbContext
|
|
8
|
+
{
|
|
9
|
+
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
|
|
10
|
+
|
|
11
|
+
<% modelsToGenerate.forEach(model => { %>
|
|
12
|
+
public DbSet<<%= model.name %>> <%= model.name %>s { get; set; }
|
|
13
|
+
<% }); %>
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Auto-generated by create-backlist v5.0
|
|
2
|
+
|
|
3
|
+
# ---- Base Stage ----
|
|
4
|
+
FROM node:18-alpine AS base
|
|
5
|
+
WORKDIR /usr/src/app
|
|
6
|
+
COPY package*.json ./
|
|
7
|
+
|
|
8
|
+
# ---- Dependencies Stage ----
|
|
9
|
+
FROM base AS dependencies
|
|
10
|
+
RUN npm install --frozen-lockfile
|
|
11
|
+
|
|
12
|
+
# ---- Build Stage ----
|
|
13
|
+
FROM base AS build
|
|
14
|
+
COPY --from=dependencies /usr/src/app/node_modules ./node_modules
|
|
15
|
+
COPY . .
|
|
16
|
+
<% if (dbType === 'prisma') { %>
|
|
17
|
+
RUN npx prisma generate
|
|
18
|
+
<% } %>
|
|
19
|
+
RUN npm run build
|
|
20
|
+
|
|
21
|
+
# ---- Production Stage ----
|
|
22
|
+
FROM node:18-alpine AS production
|
|
23
|
+
WORKDIR /usr/src/app
|
|
24
|
+
COPY --from=build /usr/src/app/dist ./dist
|
|
25
|
+
COPY --from=dependencies /usr/src/app/node_modules ./node_modules
|
|
26
|
+
COPY package*.json ./
|
|
27
|
+
<% if (dbType === 'prisma') { %>
|
|
28
|
+
# Copy Prisma schema for runtime
|
|
29
|
+
COPY prisma ./prisma
|
|
30
|
+
<% } %>
|
|
31
|
+
|
|
32
|
+
EXPOSE <%= port %>
|
|
33
|
+
CMD ["node", "dist/server.js"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Auto-generated by create-backlist
|
|
2
|
+
namespace <%= projectName %>.Models
|
|
3
|
+
{
|
|
4
|
+
public class <%= modelName %>
|
|
5
|
+
{
|
|
6
|
+
public Guid Id { get; set; }
|
|
7
|
+
|
|
8
|
+
<% model.fields.forEach(field => { %>
|
|
9
|
+
<% let csharpType = 'string'; %>
|
|
10
|
+
<% if (field.type === 'Number') csharpType = 'int'; %>
|
|
11
|
+
<% if (field.type === 'Boolean') csharpType = 'bool'; %>
|
|
12
|
+
public <%= csharpType %> <%= field.name %> { get; set; }
|
|
13
|
+
<% }); %>
|
|
14
|
+
|
|
15
|
+
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
|
16
|
+
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Auto-generated by create-backlist v5.0 (Prisma Version)
|
|
2
|
+
import { Request, Response } from 'express';
|
|
3
|
+
import { prisma } from '../server'; // Import the Prisma client instance
|
|
4
|
+
|
|
5
|
+
// @desc Create a new <%= modelName %>
|
|
6
|
+
export const create<%= modelName %> = async (req: Request, res: Response) => {
|
|
7
|
+
try {
|
|
8
|
+
const newDoc = await prisma.<%= modelName.toLowerCase() %>.create({
|
|
9
|
+
data: req.body,
|
|
10
|
+
});
|
|
11
|
+
res.status(201).json(newDoc);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
res.status(500).json({ message: 'Error creating document', error });
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// @desc Get all <%= modelName %>s
|
|
18
|
+
export const getAll<%= modelName %>s = async (req: Request, res: Response) => {
|
|
19
|
+
try {
|
|
20
|
+
const docs = await prisma.<%= modelName.toLowerCase() %>.findMany();
|
|
21
|
+
res.status(200).json(docs);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
res.status(500).json({ message: 'Error fetching documents', error });
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// @desc Get a single <%= modelName %> by ID
|
|
28
|
+
export const get<%= modelName %>ById = async (req: Request, res: Response) => {
|
|
29
|
+
try {
|
|
30
|
+
const { id } = req.params;
|
|
31
|
+
const doc = await prisma.<%= modelName.toLowerCase() %>.findUnique({
|
|
32
|
+
where: { id },
|
|
33
|
+
});
|
|
34
|
+
if (!doc) return res.status(404).json({ message: 'Document not found' });
|
|
35
|
+
res.status(200).json(doc);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
res.status(500).json({ message: 'Error fetching document', error });
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// @desc Update a <%= modelName %> by ID
|
|
42
|
+
export const update<%= modelName %>ById = async (req: Request, res: Response) => {
|
|
43
|
+
try {
|
|
44
|
+
const { id } = req.params;
|
|
45
|
+
const doc = await prisma.<%= modelName.toLowerCase() %>.update({
|
|
46
|
+
where: { id },
|
|
47
|
+
data: req.body,
|
|
48
|
+
});
|
|
49
|
+
res.status(200).json(doc);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
res.status(500).json({ message: 'Error updating document', error });
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// @desc Delete a <%= modelName %> by ID
|
|
56
|
+
export const delete<%= modelName %>ById = async (req: Request, res: Response) => {
|
|
57
|
+
try {
|
|
58
|
+
const { id } = req.params;
|
|
59
|
+
await prisma.<%= modelName.toLowerCase() %>.delete({
|
|
60
|
+
where: { id },
|
|
61
|
+
});
|
|
62
|
+
res.status(200).json({ message: 'Document deleted successfully' });
|
|
63
|
+
} catch (error) {
|
|
64
|
+
res.status(500).json({ message: 'Error deleting document', error });
|
|
65
|
+
}
|
|
66
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Auto-generated by create-backlist v5.0
|
|
2
|
+
|
|
3
|
+
generator client {
|
|
4
|
+
provider = "prisma-client-js"
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
datasource db {
|
|
8
|
+
provider = "postgresql" // User can change to "mysql", "sqlite", "sqlserver", etc.
|
|
9
|
+
url = env("DATABASE_URL")
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
<%# Loop through each model identified by the analyzer %>
|
|
13
|
+
<% modelsToGenerate.forEach(model => { %>
|
|
14
|
+
model <%= model.name %> {
|
|
15
|
+
id String @id @default(cuid())
|
|
16
|
+
<%# Loop through each field in the model %>
|
|
17
|
+
<% model.fields.forEach(field => { %>
|
|
18
|
+
<%# Map JS types to Prisma types. This is a basic mapping. %>
|
|
19
|
+
<% let prismaType = 'String'; %>
|
|
20
|
+
<% if (field.type === 'Number') prismaType = 'Int'; %>
|
|
21
|
+
<% if (field.type === 'Boolean') prismaType = 'Boolean'; %>
|
|
22
|
+
<%= field.name.padEnd(10) %> <%= prismaType %><%- field.isOptional ? '?' : '' %><%- field.isUnique ? ' @unique' : '' %>
|
|
23
|
+
<% }); %>
|
|
24
|
+
|
|
25
|
+
createdAt DateTime @default(now())
|
|
26
|
+
updatedAt DateTime @updatedAt
|
|
27
|
+
}
|
|
28
|
+
<% }); %>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Auto-generated by create-backlist v4.0 on <%= new Date().toISOString() %>
|
|
2
|
+
import mongoose from 'mongoose';
|
|
3
|
+
import dotenv from 'dotenv';
|
|
4
|
+
import { faker } from '@faker-js/faker';
|
|
5
|
+
import chalk from 'chalk'; // For colorful console logs
|
|
6
|
+
|
|
7
|
+
// Load env vars
|
|
8
|
+
dotenv.config();
|
|
9
|
+
|
|
10
|
+
// We assume a User model exists for seeding.
|
|
11
|
+
// The path is relative to the generated 'backend' project root.
|
|
12
|
+
import User from '../src/models/User.model';
|
|
13
|
+
|
|
14
|
+
// --- Connect to DB ---
|
|
15
|
+
const connectDB = async () => {
|
|
16
|
+
try {
|
|
17
|
+
const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/<%= projectName %>';
|
|
18
|
+
if (!MONGO_URI) {
|
|
19
|
+
throw new Error('MONGO_URI is not defined in your .env file');
|
|
20
|
+
}
|
|
21
|
+
await mongoose.connect(MONGO_URI);
|
|
22
|
+
console.log(chalk.green('MongoDB Connected for Seeder...'));
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.error(chalk.red(`Seeder DB Connection Error: ${err.message}`));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// --- Import Data ---
|
|
30
|
+
const importData = async () => {
|
|
31
|
+
try {
|
|
32
|
+
// Clear existing data
|
|
33
|
+
await User.deleteMany();
|
|
34
|
+
|
|
35
|
+
const sampleUsers = [];
|
|
36
|
+
const userCount = 10; // Number of sample users to create
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < userCount; i++) {
|
|
39
|
+
sampleUsers.push({
|
|
40
|
+
name: faker.person.fullName(),
|
|
41
|
+
email: faker.internet.email().toLowerCase(),
|
|
42
|
+
password: 'password123', // All sample users will have the same password for easy testing
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await User.insertMany(sampleUsers);
|
|
47
|
+
|
|
48
|
+
console.log(chalk.green.bold('✅ Data Imported Successfully!'));
|
|
49
|
+
process.exit();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(chalk.red(`Error with data import: ${error.message}`));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// --- Destroy Data ---
|
|
57
|
+
const destroyData = async () => {
|
|
58
|
+
try {
|
|
59
|
+
await User.deleteMany();
|
|
60
|
+
// If you have other models, you can add them here for destruction
|
|
61
|
+
// e.g., await Product.deleteMany();
|
|
62
|
+
|
|
63
|
+
console.log(chalk.red.bold('🔥 Data Destroyed Successfully!'));
|
|
64
|
+
process.exit();
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(chalk.red(`Error with data destruction: ${error.message}`));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// --- CLI Logic to run the seeder ---
|
|
72
|
+
const runSeeder = async () => {
|
|
73
|
+
await connectDB();
|
|
74
|
+
|
|
75
|
+
// process.argv[2] will be '-d' if the script is run with `npm run destroy`
|
|
76
|
+
if (process.argv[2] === '-d') {
|
|
77
|
+
await destroyData();
|
|
78
|
+
} else {
|
|
79
|
+
await importData();
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
runSeeder();
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Auto-generated by create-backlist v5.0
|
|
2
|
+
version: '3.8'
|
|
3
|
+
|
|
4
|
+
services:
|
|
5
|
+
app:
|
|
6
|
+
build: .
|
|
7
|
+
container_name: <%= projectName %>-app
|
|
8
|
+
ports:
|
|
9
|
+
- '<%= port %>:<%= port %>'
|
|
10
|
+
environment:
|
|
11
|
+
- PORT=<%= port %>
|
|
12
|
+
- DATABASE_URL=${DATABASE_URL}
|
|
13
|
+
- JWT_SECRET=${JWT_SECRET}
|
|
14
|
+
depends_on:
|
|
15
|
+
- db
|
|
16
|
+
volumes:
|
|
17
|
+
- .:/usr/src/app
|
|
18
|
+
- /usr/src/app/node_modules
|
|
19
|
+
command: npm run dev
|
|
20
|
+
|
|
21
|
+
db:
|
|
22
|
+
<% if (dbType === 'mongoose') { %>
|
|
23
|
+
image: mongo:latest
|
|
24
|
+
container_name: <%= projectName %>-mongo-db
|
|
25
|
+
ports:
|
|
26
|
+
- '27017:27017'
|
|
27
|
+
volumes:
|
|
28
|
+
- mongo-data:/data/db
|
|
29
|
+
<% } else if (dbType === 'prisma') { %>
|
|
30
|
+
image: postgres:14-alpine
|
|
31
|
+
container_name: <%= projectName %>-postgres-db
|
|
32
|
+
ports:
|
|
33
|
+
- '5432:5432'
|
|
34
|
+
environment:
|
|
35
|
+
- POSTGRES_USER=${DB_USER}
|
|
36
|
+
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
|
37
|
+
- POSTGRES_DB=${DB_NAME}
|
|
38
|
+
volumes:
|
|
39
|
+
- postgres-data:/var/lib/postgresql/data
|
|
40
|
+
<% } %>
|
|
41
|
+
|
|
42
|
+
volumes:
|
|
43
|
+
<% if (dbType === 'mongoose') { %>
|
|
44
|
+
mongo-data:
|
|
45
|
+
<% } else if (dbType === 'prisma') { %>
|
|
46
|
+
postgres-data:
|
|
47
|
+
<% } %>
|