go-gin-cli 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 +287 -0
- package/git-manager.sh +495 -0
- package/package.json +36 -0
- package/prompt.md +6 -0
- package/src/bin/index.js +410 -0
- package/src/lib/constants/index.js +24 -0
- package/src/lib/shares/createDir.js +22 -0
- package/src/lib/shares/createFile.js +23 -0
- package/src/lib/utils/add-auth-to-resource.js +225 -0
- package/src/lib/utils/create-auth.js +937 -0
- package/src/lib/utils/create-resource.js +1426 -0
- package/src/lib/utils/create-service.js +456 -0
- package/src/lib/utils/display.js +19 -0
- package/src/lib/utils/help.js +93 -0
- package/src/lib/utils/remove-module.js +146 -0
- package/src/lib/utils/setup.js +1626 -0
|
@@ -0,0 +1,1426 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs").promises;
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const createDir = require("../shares/createDir");
|
|
6
|
+
const createFile = require("../shares/createFile");
|
|
7
|
+
const { COLORS } = require("../constants/index");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ModuleGenerator - Generates Gin HTTP resource files following Clean Architecture pattern
|
|
11
|
+
*
|
|
12
|
+
* Structure:
|
|
13
|
+
* src/
|
|
14
|
+
* ├── domain/
|
|
15
|
+
* │ ├── dtos/{module}.go
|
|
16
|
+
* │ ├── models/{module}.go
|
|
17
|
+
* │ └── repositories/{module}.go
|
|
18
|
+
* ├── infrastructure/
|
|
19
|
+
* │ ├── controllers/{module}/
|
|
20
|
+
* │ │ └── {module}.controller.go
|
|
21
|
+
* │ ├── entities/{module}.go
|
|
22
|
+
* │ ├── repositories/{module}/
|
|
23
|
+
* │ │ ├── create{Module}/
|
|
24
|
+
* │ │ │ ├── action.go
|
|
25
|
+
* │ │ │ └── validation.go
|
|
26
|
+
* │ │ ├── delete{Module}/
|
|
27
|
+
* │ │ ├── getAll{Module}/
|
|
28
|
+
* │ │ ├── load{Module}ById/
|
|
29
|
+
* │ │ ├── loadAll{Module}/
|
|
30
|
+
* │ │ ├── update{Module}/
|
|
31
|
+
* │ │ └── {module}.repository.go
|
|
32
|
+
* └── usecases/{module}/
|
|
33
|
+
*/
|
|
34
|
+
class ModuleGenerator {
|
|
35
|
+
constructor(moduleNameLower, moduleNamePascal, moduleNameCamel, moduleNameSnake) {
|
|
36
|
+
// For Go packages, use camelCase (no hyphens allowed)
|
|
37
|
+
// e.g., "user-role" -> "userRole" for package name
|
|
38
|
+
this.moduleNameLower = moduleNameCamel.charAt(0).toLowerCase() + moduleNameCamel.slice(1); // userRole
|
|
39
|
+
this.moduleNamePascal = moduleNamePascal; // UserRole
|
|
40
|
+
this.moduleNameCamel = moduleNameCamel; // userRole
|
|
41
|
+
this.moduleNameSnake = moduleNameSnake; // user_role
|
|
42
|
+
this.moduleNameKebab = moduleNameLower; // user-role (original input, for display only)
|
|
43
|
+
|
|
44
|
+
// Paths matching Clean Architecture structure
|
|
45
|
+
this.srcDir = "src";
|
|
46
|
+
this.domainDir = path.join(this.srcDir, "domain");
|
|
47
|
+
this.infrastructureDir = path.join(this.srcDir, "infrastructure");
|
|
48
|
+
this.usecasesDir = path.join(this.srcDir, "usecases");
|
|
49
|
+
|
|
50
|
+
// Get go module name from go.mod
|
|
51
|
+
this.goModule = this.getGoModuleName();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getGoModuleName() {
|
|
55
|
+
try {
|
|
56
|
+
const goModPath = path.join(process.cwd(), "go.mod");
|
|
57
|
+
const content = require("fs").readFileSync(goModPath, "utf8");
|
|
58
|
+
const match = content.match(/module\s+(.+)/);
|
|
59
|
+
return match ? match[1].trim() : "github.com/example/project";
|
|
60
|
+
} catch {
|
|
61
|
+
return "github.com/example/project";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async execute() {
|
|
66
|
+
try {
|
|
67
|
+
console.log(`${COLORS.YELLOW}Starting Gin HTTP resource generation...${COLORS.NC}`);
|
|
68
|
+
|
|
69
|
+
await this.setupDirectories();
|
|
70
|
+
await this.generateDomainFiles();
|
|
71
|
+
await this.generateInfrastructureFiles();
|
|
72
|
+
await this.generateUsecaseFiles();
|
|
73
|
+
await this.updateModules();
|
|
74
|
+
await this.runPostGeneration();
|
|
75
|
+
|
|
76
|
+
console.log(`${COLORS.GREEN}Resource generation completed successfully!${COLORS.NC}`);
|
|
77
|
+
this.printSummary();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(`${COLORS.RED}Error during generation:${COLORS.NC} ${error.message}`);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async setupDirectories() {
|
|
85
|
+
// Repository action directories
|
|
86
|
+
const repoActions = [
|
|
87
|
+
`create${this.moduleNamePascal}`,
|
|
88
|
+
`delete${this.moduleNamePascal}`,
|
|
89
|
+
`getAll${this.moduleNamePascal}`,
|
|
90
|
+
`load${this.moduleNamePascal}ById`,
|
|
91
|
+
`loadAll${this.moduleNamePascal}`,
|
|
92
|
+
`update${this.moduleNamePascal}`,
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const dirs = [
|
|
96
|
+
// Repository directories with action folders
|
|
97
|
+
...repoActions.map(action =>
|
|
98
|
+
path.join(this.infrastructureDir, "repositories", this.moduleNameLower, action)
|
|
99
|
+
),
|
|
100
|
+
// Controller directory
|
|
101
|
+
path.join(this.infrastructureDir, "controllers", this.moduleNameLower),
|
|
102
|
+
// Usecase directory
|
|
103
|
+
path.join(this.usecasesDir, this.moduleNameLower),
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
for (const dir of dirs) {
|
|
107
|
+
await createDir(dir);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async generateDomainFiles() {
|
|
112
|
+
// DTOs
|
|
113
|
+
await createFile(
|
|
114
|
+
path.join(this.domainDir, "dtos", `${this.moduleNameLower}.go`),
|
|
115
|
+
this.getDtoContent()
|
|
116
|
+
);
|
|
117
|
+
// Model
|
|
118
|
+
await createFile(
|
|
119
|
+
path.join(this.domainDir, "models", `${this.moduleNameLower}.go`),
|
|
120
|
+
this.getModelContent()
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Repository Interface
|
|
124
|
+
await createFile(
|
|
125
|
+
path.join(this.domainDir, "repositories", `${this.moduleNameLower}.go`),
|
|
126
|
+
this.getRepositoryInterfaceContent()
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async generateInfrastructureFiles() {
|
|
131
|
+
// Entity
|
|
132
|
+
await createFile(
|
|
133
|
+
path.join(this.infrastructureDir, "entities", `${this.moduleNameLower}.go`),
|
|
134
|
+
this.getEntityContent()
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Repository action files
|
|
138
|
+
await this.generateRepositoryActions();
|
|
139
|
+
|
|
140
|
+
// Main repository file
|
|
141
|
+
await createFile(
|
|
142
|
+
path.join(this.infrastructureDir, "repositories", this.moduleNameLower, `${this.moduleNameLower}.repository.go`),
|
|
143
|
+
this.getMainRepositoryContent()
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// Gin Controller
|
|
147
|
+
await createFile(
|
|
148
|
+
path.join(this.infrastructureDir, "controllers", this.moduleNameLower, `${this.moduleNameLower}.controller.go`),
|
|
149
|
+
this.getControllerContent()
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async generateRepositoryActions() {
|
|
154
|
+
// Create action
|
|
155
|
+
await createFile(
|
|
156
|
+
path.join(this.infrastructureDir, "repositories", this.moduleNameLower, `create${this.moduleNamePascal}`, "action.go"),
|
|
157
|
+
this.getCreateActionContent()
|
|
158
|
+
);
|
|
159
|
+
await createFile(
|
|
160
|
+
path.join(this.infrastructureDir, "repositories", this.moduleNameLower, `create${this.moduleNamePascal}`, "validation.go"),
|
|
161
|
+
this.getCreateValidationContent()
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Delete action
|
|
165
|
+
await createFile(
|
|
166
|
+
path.join(this.infrastructureDir, "repositories", this.moduleNameLower, `delete${this.moduleNamePascal}`, "action.go"),
|
|
167
|
+
this.getDeleteActionContent()
|
|
168
|
+
);
|
|
169
|
+
await createFile(
|
|
170
|
+
path.join(this.infrastructureDir, "repositories", this.moduleNameLower, `delete${this.moduleNamePascal}`, "validation.go"),
|
|
171
|
+
this.getDeleteValidationContent()
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// GetAll action
|
|
175
|
+
await createFile(
|
|
176
|
+
path.join(this.infrastructureDir, "repositories", this.moduleNameLower, `getAll${this.moduleNamePascal}`, "action.go"),
|
|
177
|
+
this.getGetAllActionContent()
|
|
178
|
+
);
|
|
179
|
+
await createFile(
|
|
180
|
+
path.join(this.infrastructureDir, "repositories", this.moduleNameLower, `getAll${this.moduleNamePascal}`, "validation.go"),
|
|
181
|
+
this.getGetAllValidationContent()
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// LoadById action
|
|
185
|
+
await createFile(
|
|
186
|
+
path.join(this.infrastructureDir, "repositories", this.moduleNameLower, `load${this.moduleNamePascal}ById`, "action.go"),
|
|
187
|
+
this.getLoadByIdActionContent()
|
|
188
|
+
);
|
|
189
|
+
await createFile(
|
|
190
|
+
path.join(this.infrastructureDir, "repositories", this.moduleNameLower, `load${this.moduleNamePascal}ById`, "validation.go"),
|
|
191
|
+
this.getLoadByIdValidationContent()
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// LoadAll action
|
|
195
|
+
await createFile(
|
|
196
|
+
path.join(this.infrastructureDir, "repositories", this.moduleNameLower, `loadAll${this.moduleNamePascal}`, "action.go"),
|
|
197
|
+
this.getLoadAllActionContent()
|
|
198
|
+
);
|
|
199
|
+
await createFile(
|
|
200
|
+
path.join(this.infrastructureDir, "repositories", this.moduleNameLower, `loadAll${this.moduleNamePascal}`, "validation.go"),
|
|
201
|
+
this.getLoadAllValidationContent()
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// Update action
|
|
205
|
+
await createFile(
|
|
206
|
+
path.join(this.infrastructureDir, "repositories", this.moduleNameLower, `update${this.moduleNamePascal}`, "action.go"),
|
|
207
|
+
this.getUpdateActionContent()
|
|
208
|
+
);
|
|
209
|
+
await createFile(
|
|
210
|
+
path.join(this.infrastructureDir, "repositories", this.moduleNameLower, `update${this.moduleNamePascal}`, "validation.go"),
|
|
211
|
+
this.getUpdateValidationContent()
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async generateUsecaseFiles() {
|
|
216
|
+
// Main usecase file
|
|
217
|
+
await createFile(
|
|
218
|
+
path.join(this.usecasesDir, this.moduleNameLower, `${this.moduleNameLower}.usecase.go`),
|
|
219
|
+
this.getUsecaseContent()
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async updateModules() {
|
|
224
|
+
// Update repositories module
|
|
225
|
+
await this.updateRepositoriesModule();
|
|
226
|
+
// Update usecases proxy
|
|
227
|
+
await this.updateUsecasesProxy();
|
|
228
|
+
// Update router with new routes
|
|
229
|
+
await this.updateRouter();
|
|
230
|
+
// Update main.go with auto-migration
|
|
231
|
+
await this.updateMainMigration();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async updateRepositoriesModule() {
|
|
235
|
+
const filePath = path.join(this.infrastructureDir, "repositories", "repositories.go");
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
let content = await fs.readFile(filePath, "utf8");
|
|
239
|
+
|
|
240
|
+
if (content.includes(`${this.moduleNamePascal}Repository`)) {
|
|
241
|
+
console.log(
|
|
242
|
+
`${COLORS.YELLOW}⚠ Repositories module already contains ${this.moduleNamePascal}${COLORS.NC}`,
|
|
243
|
+
);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Add import before gorm.io/gorm (alphabetical order)
|
|
248
|
+
const importLine = `\t"${this.goModule}/src/infrastructure/repositories/${this.moduleNameLower}"`;
|
|
249
|
+
content = content.replace(/import \(\n/, `import (\n${importLine}\n`);
|
|
250
|
+
|
|
251
|
+
// Add repository field to struct after "Add your repositories here" comment
|
|
252
|
+
const structFieldLine = `\t${this.moduleNamePascal}Repository *${this.moduleNameLower}.Repository`;
|
|
253
|
+
content = content.replace(
|
|
254
|
+
/\/\/ Add your repositories here\n/,
|
|
255
|
+
`// Add your repositories here\n${structFieldLine}\n`,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// Add initialization after "Initialize your repositories here" comment
|
|
259
|
+
const initLine = `\t\t${this.moduleNamePascal}Repository: ${this.moduleNameLower}.NewRepository(db),`;
|
|
260
|
+
content = content.replace(
|
|
261
|
+
/\/\/ Initialize your repositories here\n/,
|
|
262
|
+
`// Initialize your repositories here\n${initLine}\n`,
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
266
|
+
console.log(`${COLORS.GREEN}✔ Updated ${filePath}${COLORS.NC}`);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.error(`${COLORS.YELLOW}⚠ Could not update repositories module: ${error.message}${COLORS.NC}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async updateUsecasesProxy() {
|
|
273
|
+
const filePath = path.join(this.infrastructureDir, "usecases-proxy", "usecases_proxy.go");
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
let content = await fs.readFile(filePath, "utf8");
|
|
277
|
+
|
|
278
|
+
if (content.includes(`${this.moduleNamePascal}Usecase *${this.moduleNameCamel}Usecase.Usecase`)) {
|
|
279
|
+
console.log(`${COLORS.YELLOW}⚠ Usecases proxy already contains ${this.moduleNamePascal}${COLORS.NC}`);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Add import
|
|
284
|
+
const importLine = `\t${this.moduleNameCamel}Usecase "${this.goModule}/src/usecases/${this.moduleNameLower}"`;
|
|
285
|
+
content = content.replace(
|
|
286
|
+
/import \(([\s\S]*?)\)/,
|
|
287
|
+
(match, imports) => `import (${imports}${importLine}\n)`
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
// Remove placeholder comments if present
|
|
291
|
+
content = content.replace(/\t\/\/ Add your usecases here\n\t\/\/ UserUsecase \*usecases\.UserUsecase\n/g, '');
|
|
292
|
+
content = content.replace(/\t\t\/\/ Initialize your usecases here\n\t\t\/\/ UserUsecase: usecases\.NewUserUsecase\(repos\.UserRepository, logger\),\n/g, '');
|
|
293
|
+
|
|
294
|
+
// Add usecase field to struct - find the struct and add before closing brace
|
|
295
|
+
content = content.replace(
|
|
296
|
+
/(type UsecasesProxy struct \{[\s\S]*?)(})/,
|
|
297
|
+
(match, structContent, closeBrace) => {
|
|
298
|
+
if (structContent.includes(`${this.moduleNamePascal}Usecase`)) {
|
|
299
|
+
return match;
|
|
300
|
+
}
|
|
301
|
+
return `${structContent}\t${this.moduleNamePascal}Usecase *${this.moduleNameCamel}Usecase.Usecase\n${closeBrace}`;
|
|
302
|
+
}
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Add initialization - find NewUsecasesProxy return and add before closing brace
|
|
306
|
+
content = content.replace(
|
|
307
|
+
/(return &UsecasesProxy\{[\s\S]*?)([\t ]*\})/,
|
|
308
|
+
(match, initContent, closeBrace) => {
|
|
309
|
+
if (initContent.includes(`${this.moduleNamePascal}Usecase:`)) {
|
|
310
|
+
return match;
|
|
311
|
+
}
|
|
312
|
+
return `${initContent}\t\t${this.moduleNamePascal}Usecase: ${this.moduleNameCamel}Usecase.NewUsecase(repos.${this.moduleNamePascal}Repository, logger),\n${closeBrace}`;
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
317
|
+
console.log(`${COLORS.GREEN}✔ Updated ${filePath}${COLORS.NC}`);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.error(`${COLORS.YELLOW}⚠ Could not update usecases proxy: ${error.message}${COLORS.NC}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async updateRouter() {
|
|
324
|
+
const filePath = path.join(this.infrastructureDir, "controllers", "router.go");
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
let content = await fs.readFile(filePath, "utf8");
|
|
328
|
+
|
|
329
|
+
if (content.includes(`${this.moduleNameCamel}Controller`)) {
|
|
330
|
+
console.log(`${COLORS.YELLOW}⚠ Router already contains ${this.moduleNamePascal}${COLORS.NC}`);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Add import
|
|
335
|
+
const importLine = `\t${this.moduleNameCamel}Controller "${this.goModule}/src/infrastructure/controllers/${this.moduleNameLower}"`;
|
|
336
|
+
content = content.replace(
|
|
337
|
+
/import \(([\s\S]*?)\)/,
|
|
338
|
+
(match, imports) => `import (${imports}${importLine}\n)`
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
// Remove placeholder line if present
|
|
342
|
+
content = content.replace(/\t\t_ = v1 \/\/ Remove this line when adding routes\n/, '');
|
|
343
|
+
|
|
344
|
+
// Add route registration
|
|
345
|
+
const routeRegistration = `\n\t\t// ${this.moduleNamePascal} routes\n\t\t${this.moduleNameCamel}Ctrl := ${this.moduleNameCamel}Controller.NewController(r.usecasesProxy.${this.moduleNamePascal}Usecase, r.logger)\n\t\t${this.moduleNameLower}s := v1.Group("/${this.moduleNameSnake}s")\n\t\t{\n\t\t\t${this.moduleNameLower}s.GET("", ${this.moduleNameCamel}Ctrl.GetAll)\n\t\t\t${this.moduleNameLower}s.GET("/:id", ${this.moduleNameCamel}Ctrl.GetByID)\n\t\t\t${this.moduleNameLower}s.POST("", ${this.moduleNameCamel}Ctrl.Create)\n\t\t\t${this.moduleNameLower}s.PUT("/:id", ${this.moduleNameCamel}Ctrl.Update)\n\t\t\t${this.moduleNameLower}s.DELETE("/:id", ${this.moduleNameCamel}Ctrl.Delete)\n\t\t}`;
|
|
346
|
+
|
|
347
|
+
content = content.replace(
|
|
348
|
+
/(v1 := r\.engine\.Group\("\/api\/v1"\)\n\t\{)/,
|
|
349
|
+
`$1${routeRegistration}`
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
353
|
+
console.log(`${COLORS.GREEN}✔ Updated ${filePath}${COLORS.NC}`);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
console.error(`${COLORS.YELLOW}⚠ Could not update router: ${error.message}${COLORS.NC}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async updateMainMigration() {
|
|
360
|
+
const filePath = path.join(this.srcDir, "main.go");
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
let content = await fs.readFile(filePath, "utf8");
|
|
364
|
+
|
|
365
|
+
// Check if entity is already in migration
|
|
366
|
+
if (content.includes(`&entities.${this.moduleNamePascal}{}`)) {
|
|
367
|
+
console.log(`${COLORS.YELLOW}⚠ main.go already contains ${this.moduleNamePascal} migration${COLORS.NC}`);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Add entities import if not present
|
|
372
|
+
if (!content.includes(`"${this.goModule}/src/infrastructure/entities"`)) {
|
|
373
|
+
content = content.replace(
|
|
374
|
+
/import \(([\s\S]*?)\)/,
|
|
375
|
+
(match, imports) => `import (${imports}\t"${this.goModule}/src/infrastructure/entities"\n)`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Find AutoMigrate call and add the new entity
|
|
380
|
+
if (content.includes("database.AutoMigrate(db,")) {
|
|
381
|
+
// Add to existing AutoMigrate call - find the closing parenthesis
|
|
382
|
+
content = content.replace(
|
|
383
|
+
/(database\.AutoMigrate\(db,)([\s\S]*?)(\n\t\))/,
|
|
384
|
+
`$1$2\n\t\t&entities.${this.moduleNamePascal}{},$3`
|
|
385
|
+
);
|
|
386
|
+
} else if (content.includes("db.AutoMigrate(")) {
|
|
387
|
+
// Alternative: db.AutoMigrate pattern
|
|
388
|
+
content = content.replace(
|
|
389
|
+
/(db\.AutoMigrate\()([\s\S]*?)(\))/,
|
|
390
|
+
`$1$2, &entities.${this.moduleNamePascal}{}$3`
|
|
391
|
+
);
|
|
392
|
+
} else {
|
|
393
|
+
// Add AutoMigrate before "// Initialize and run application" or before NewApp
|
|
394
|
+
if (content.includes("// Initialize and run application")) {
|
|
395
|
+
content = content.replace(
|
|
396
|
+
/(\/\/ Initialize and run application)/,
|
|
397
|
+
`// Auto-migrate entities\n\tdatabase.AutoMigrate(db,\n\t\t&entities.${this.moduleNamePascal}{},\n\t)\n\n\t$1`
|
|
398
|
+
);
|
|
399
|
+
} else {
|
|
400
|
+
// Add before NewApp call
|
|
401
|
+
content = content.replace(
|
|
402
|
+
/(\n\t)(app := NewApp)/,
|
|
403
|
+
`$1// Auto-migrate entities\n\tdatabase.AutoMigrate(db,\n\t\t&entities.${this.moduleNamePascal}{},\n\t)\n\n\t$2`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
409
|
+
console.log(`${COLORS.GREEN}✔ Updated ${filePath} with auto-migration${COLORS.NC}`);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
console.error(`${COLORS.YELLOW}⚠ Could not update main.go migration: ${error.message}${COLORS.NC}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async runPostGeneration() {
|
|
416
|
+
const { execSync } = require("child_process");
|
|
417
|
+
|
|
418
|
+
console.log(`\n${COLORS.CYAN}🔄 Running post-generation tasks...${COLORS.NC}`);
|
|
419
|
+
|
|
420
|
+
try {
|
|
421
|
+
// Run go mod tidy
|
|
422
|
+
console.log(`${COLORS.YELLOW}Running go mod tidy...${COLORS.NC}`);
|
|
423
|
+
execSync("go mod tidy", { stdio: "inherit" });
|
|
424
|
+
console.log(`${COLORS.GREEN}✔ go mod tidy completed${COLORS.NC}`);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
console.error(`${COLORS.YELLOW}⚠ go mod tidy failed: ${error.message}${COLORS.NC}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
printSummary() {
|
|
431
|
+
console.log(`
|
|
432
|
+
${COLORS.CYAN}📁 Generated files:${COLORS.NC}
|
|
433
|
+
${COLORS.GREEN}✓${COLORS.NC} src/domain/dtos/${this.moduleNameLower}.go
|
|
434
|
+
${COLORS.GREEN}✓${COLORS.NC} src/domain/models/${this.moduleNameLower}.go
|
|
435
|
+
${COLORS.GREEN}✓${COLORS.NC} src/domain/repositories/${this.moduleNameLower}.go
|
|
436
|
+
${COLORS.GREEN}✓${COLORS.NC} src/infrastructure/entities/${this.moduleNameLower}.go
|
|
437
|
+
${COLORS.GREEN}✓${COLORS.NC} src/infrastructure/controllers/${this.moduleNameLower}/${this.moduleNameLower}.controller.go
|
|
438
|
+
${COLORS.GREEN}✓${COLORS.NC} src/infrastructure/repositories/${this.moduleNameLower}/
|
|
439
|
+
├── create${this.moduleNamePascal}/ (action.go, validation.go)
|
|
440
|
+
├── delete${this.moduleNamePascal}/ (action.go, validation.go)
|
|
441
|
+
├── getAll${this.moduleNamePascal}/ (action.go, validation.go)
|
|
442
|
+
├── load${this.moduleNamePascal}ById/ (action.go, validation.go)
|
|
443
|
+
├── loadAll${this.moduleNamePascal}/ (action.go, validation.go)
|
|
444
|
+
├── update${this.moduleNamePascal}/ (action.go, validation.go)
|
|
445
|
+
└── ${this.moduleNameLower}.repository.go
|
|
446
|
+
${COLORS.GREEN}✓${COLORS.NC} src/usecases/${this.moduleNameLower}/${this.moduleNameLower}.usecase.go
|
|
447
|
+
|
|
448
|
+
${COLORS.CYAN}📝 Updated files:${COLORS.NC}
|
|
449
|
+
${COLORS.GREEN}✓${COLORS.NC} src/infrastructure/repositories/repositories.go
|
|
450
|
+
${COLORS.GREEN}✓${COLORS.NC} src/infrastructure/usecases-proxy/usecases_proxy.go
|
|
451
|
+
${COLORS.GREEN}✓${COLORS.NC} src/infrastructure/controllers/router.go
|
|
452
|
+
${COLORS.GREEN}✓${COLORS.NC} src/main.go (auto-migration added)
|
|
453
|
+
|
|
454
|
+
${COLORS.CYAN}🔄 Auto-completed:${COLORS.NC}
|
|
455
|
+
${COLORS.GREEN}✓${COLORS.NC} go mod tidy
|
|
456
|
+
|
|
457
|
+
${COLORS.YELLOW}⚠ Don't forget to:${COLORS.NC}
|
|
458
|
+
1. Customize the entity fields in ${COLORS.CYAN}src/infrastructure/entities/${this.moduleNameLower}.go${COLORS.NC}
|
|
459
|
+
2. Update DTOs in ${COLORS.CYAN}src/domain/dtos/${this.moduleNameLower}.go${COLORS.NC}
|
|
460
|
+
3. Run ${COLORS.CYAN}make run${COLORS.NC} or ${COLORS.CYAN}make dev${COLORS.NC} to start the server
|
|
461
|
+
`);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ============================================
|
|
465
|
+
// Content Generators - DTOs
|
|
466
|
+
// ============================================
|
|
467
|
+
|
|
468
|
+
getDtoContent() {
|
|
469
|
+
return `package dtos
|
|
470
|
+
|
|
471
|
+
// Create${this.moduleNamePascal}Request represents the create request
|
|
472
|
+
type Create${this.moduleNamePascal}Request struct {
|
|
473
|
+
Name string \`json:"name" binding:"required\"\`
|
|
474
|
+
Description string \`json:"description"\`
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Update${this.moduleNamePascal}Request represents the update request
|
|
478
|
+
type Update${this.moduleNamePascal}Request struct {
|
|
479
|
+
Name string \`json:"name" binding:"required"\`
|
|
480
|
+
Description string \`json:"description"\`
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ${this.moduleNamePascal}Response represents the response
|
|
484
|
+
type ${this.moduleNamePascal}Response struct {
|
|
485
|
+
ID string \`json:"id"\`
|
|
486
|
+
Name string \`json:"name"\`
|
|
487
|
+
Description string \`json:"description"\`
|
|
488
|
+
IsActive bool \`json:"is_active"\`
|
|
489
|
+
CreatedAt string \`json:"created_at"\`
|
|
490
|
+
UpdatedAt string \`json:"updated_at"\`
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// ${this.moduleNamePascal}QueryParams represents query parameters for listing
|
|
494
|
+
type ${this.moduleNamePascal}QueryParams struct {
|
|
495
|
+
Page int \`form:"page" binding:"omitempty,min=1"\`
|
|
496
|
+
PageSize int \`form:"page_size" binding:"omitempty,min=1,max=100"\`
|
|
497
|
+
Search string \`form:"search"\`
|
|
498
|
+
SortBy string \`form:"sort_by"\`
|
|
499
|
+
SortOrder string \`form:"sort_order" binding:"omitempty,oneof=asc desc"\`
|
|
500
|
+
}
|
|
501
|
+
`;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ============================================
|
|
505
|
+
// Content Generators - Domain
|
|
506
|
+
// ============================================
|
|
507
|
+
|
|
508
|
+
getModelContent() {
|
|
509
|
+
return `package models
|
|
510
|
+
|
|
511
|
+
import (
|
|
512
|
+
"github.com/google/uuid"
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
// ${this.moduleNamePascal}Model represents the ${this.moduleNameLower} domain model
|
|
516
|
+
type ${this.moduleNamePascal}Model struct {
|
|
517
|
+
BaseModel
|
|
518
|
+
Name string \`json:"name"\`
|
|
519
|
+
Description string \`json:"description"\`
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// New${this.moduleNamePascal}Model creates a new ${this.moduleNamePascal} model
|
|
523
|
+
func New${this.moduleNamePascal}Model(name, description string) *${this.moduleNamePascal}Model {
|
|
524
|
+
return &${this.moduleNamePascal}Model{
|
|
525
|
+
BaseModel: NewBaseModel(),
|
|
526
|
+
Name: name,
|
|
527
|
+
Description: description,
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// ${this.moduleNamePascal}Filter represents filter options for ${this.moduleNameLower}
|
|
532
|
+
type ${this.moduleNamePascal}Filter struct {
|
|
533
|
+
QueryParams
|
|
534
|
+
Name string \`json:"name" form:"name"\`
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// ToMap converts model to map
|
|
538
|
+
func (m *${this.moduleNamePascal}Model) ToMap() map[string]interface{} {
|
|
539
|
+
return map[string]interface{}{
|
|
540
|
+
"id": m.ID,
|
|
541
|
+
"name": m.Name,
|
|
542
|
+
"description": m.Description,
|
|
543
|
+
"is_active": m.IsActive,
|
|
544
|
+
"created_at": m.CreatedAt,
|
|
545
|
+
"updated_at": m.UpdatedAt,
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// ${this.moduleNamePascal}FromID creates a model reference by ID
|
|
550
|
+
func ${this.moduleNamePascal}FromID(id uuid.UUID) *${this.moduleNamePascal}Model {
|
|
551
|
+
return &${this.moduleNamePascal}Model{
|
|
552
|
+
BaseModel: BaseModel{ID: id},
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
`;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
getRepositoryInterfaceContent() {
|
|
559
|
+
return `package repositories
|
|
560
|
+
|
|
561
|
+
import (
|
|
562
|
+
"context"
|
|
563
|
+
|
|
564
|
+
"${this.goModule}/src/domain/models"
|
|
565
|
+
"github.com/google/uuid"
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
// ${this.moduleNamePascal}Repository defines the ${this.moduleNameLower} repository interface
|
|
569
|
+
type ${this.moduleNamePascal}Repository interface {
|
|
570
|
+
Create(ctx context.Context, ${this.moduleNameCamel} *models.${this.moduleNamePascal}Model) error
|
|
571
|
+
Update(ctx context.Context, ${this.moduleNameCamel} *models.${this.moduleNamePascal}Model) error
|
|
572
|
+
Delete(ctx context.Context, id uuid.UUID) error
|
|
573
|
+
GetById(ctx context.Context, id uuid.UUID) (*models.${this.moduleNamePascal}Model, error)
|
|
574
|
+
GetAll(ctx context.Context, filter *models.${this.moduleNamePascal}Filter) ([]models.${this.moduleNamePascal}Model, int64, error)
|
|
575
|
+
LoadById(ctx context.Context, id uuid.UUID) (*models.${this.moduleNamePascal}Model, error)
|
|
576
|
+
LoadAll(ctx context.Context, filter *models.${this.moduleNamePascal}Filter) ([]models.${this.moduleNamePascal}Model, int64, error)
|
|
577
|
+
}
|
|
578
|
+
`;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// ============================================
|
|
582
|
+
// Content Generators - Entity
|
|
583
|
+
// ============================================
|
|
584
|
+
|
|
585
|
+
getEntityContent() {
|
|
586
|
+
return `package entities
|
|
587
|
+
|
|
588
|
+
import (
|
|
589
|
+
"time"
|
|
590
|
+
|
|
591
|
+
"${this.goModule}/src/domain/models"
|
|
592
|
+
"github.com/google/uuid"
|
|
593
|
+
"gorm.io/gorm"
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
// ${this.moduleNamePascal} represents the ${this.moduleNameLower} entity
|
|
597
|
+
type ${this.moduleNamePascal} struct {
|
|
598
|
+
ID uuid.UUID \`gorm:"type:uuid;primary_key" json:"id"\`
|
|
599
|
+
Name string \`gorm:"type:varchar(255);not null" json:"name"\`
|
|
600
|
+
Description string \`gorm:"type:text" json:"description"\`
|
|
601
|
+
IsActive bool \`gorm:"default:true" json:"is_active"\`
|
|
602
|
+
CreatedAt time.Time \`gorm:"autoCreateTime" json:"created_at"\`
|
|
603
|
+
UpdatedAt time.Time \`gorm:"autoUpdateTime" json:"updated_at"\`
|
|
604
|
+
DeletedAt gorm.DeletedAt \`gorm:"index" json:"deleted_at,omitempty"\`
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// TableName returns the table name for ${this.moduleNamePascal}
|
|
608
|
+
func (${this.moduleNamePascal}) TableName() string {
|
|
609
|
+
return "${this.moduleNameSnake}s"
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// BeforeCreate will set a UUID rather than numeric ID
|
|
613
|
+
func (e *${this.moduleNamePascal}) BeforeCreate(tx *gorm.DB) error {
|
|
614
|
+
if e.ID == uuid.Nil {
|
|
615
|
+
e.ID = uuid.New()
|
|
616
|
+
}
|
|
617
|
+
return nil
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// ToModel converts entity to domain model
|
|
621
|
+
func (e *${this.moduleNamePascal}) ToModel() *models.${this.moduleNamePascal}Model {
|
|
622
|
+
var deletedAt *time.Time
|
|
623
|
+
if e.DeletedAt.Valid {
|
|
624
|
+
deletedAt = &e.DeletedAt.Time
|
|
625
|
+
}
|
|
626
|
+
return &models.${this.moduleNamePascal}Model{
|
|
627
|
+
BaseModel: models.BaseModel{
|
|
628
|
+
ID: e.ID,
|
|
629
|
+
CreatedAt: e.CreatedAt,
|
|
630
|
+
UpdatedAt: e.UpdatedAt,
|
|
631
|
+
DeletedAt: deletedAt,
|
|
632
|
+
IsActive: e.IsActive,
|
|
633
|
+
},
|
|
634
|
+
Name: e.Name,
|
|
635
|
+
Description: e.Description,
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// FromModel creates entity from domain model
|
|
640
|
+
func ${this.moduleNamePascal}FromModel(m *models.${this.moduleNamePascal}Model) *${this.moduleNamePascal} {
|
|
641
|
+
return &${this.moduleNamePascal}{
|
|
642
|
+
ID: m.ID,
|
|
643
|
+
Name: m.Name,
|
|
644
|
+
Description: m.Description,
|
|
645
|
+
IsActive: m.IsActive,
|
|
646
|
+
CreatedAt: m.CreatedAt,
|
|
647
|
+
UpdatedAt: m.UpdatedAt,
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
`;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// ============================================
|
|
654
|
+
// Content Generators - Repository Actions
|
|
655
|
+
// ============================================
|
|
656
|
+
|
|
657
|
+
getCreateActionContent() {
|
|
658
|
+
return `package create${this.moduleNamePascal}
|
|
659
|
+
|
|
660
|
+
import (
|
|
661
|
+
"context"
|
|
662
|
+
|
|
663
|
+
"${this.goModule}/src/domain/models"
|
|
664
|
+
"${this.goModule}/src/infrastructure/entities"
|
|
665
|
+
|
|
666
|
+
"gorm.io/gorm"
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
// Action performs the create operation
|
|
670
|
+
func Action(ctx context.Context, db *gorm.DB, model *models.${this.moduleNamePascal}Model) error {
|
|
671
|
+
entity := entities.${this.moduleNamePascal}FromModel(model)
|
|
672
|
+
if err := db.WithContext(ctx).Create(entity).Error; err != nil {
|
|
673
|
+
return err
|
|
674
|
+
}
|
|
675
|
+
// Update model with generated values
|
|
676
|
+
*model = *entity.ToModel()
|
|
677
|
+
return nil
|
|
678
|
+
}
|
|
679
|
+
`;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
getCreateValidationContent() {
|
|
683
|
+
return `package create${this.moduleNamePascal}
|
|
684
|
+
|
|
685
|
+
import (
|
|
686
|
+
"errors"
|
|
687
|
+
|
|
688
|
+
"${this.goModule}/src/domain/models"
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
// Validate validates the create request
|
|
692
|
+
func Validate(model *models.${this.moduleNamePascal}Model) error {
|
|
693
|
+
if model.Name == "" {
|
|
694
|
+
return errors.New("name is required")
|
|
695
|
+
}
|
|
696
|
+
return nil
|
|
697
|
+
}
|
|
698
|
+
`;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
getDeleteActionContent() {
|
|
702
|
+
return `package delete${this.moduleNamePascal}
|
|
703
|
+
|
|
704
|
+
import (
|
|
705
|
+
"context"
|
|
706
|
+
|
|
707
|
+
"${this.goModule}/src/infrastructure/entities"
|
|
708
|
+
|
|
709
|
+
"github.com/google/uuid"
|
|
710
|
+
"gorm.io/gorm"
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
// Action performs the delete operation (soft delete)
|
|
714
|
+
func Action(ctx context.Context, db *gorm.DB, id uuid.UUID) error {
|
|
715
|
+
return db.WithContext(ctx).
|
|
716
|
+
Model(&entities.${this.moduleNamePascal}{}).
|
|
717
|
+
Where("id = ?", id).
|
|
718
|
+
Update("is_active", false).Error
|
|
719
|
+
}
|
|
720
|
+
`;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
getDeleteValidationContent() {
|
|
724
|
+
return `package delete${this.moduleNamePascal}
|
|
725
|
+
|
|
726
|
+
import (
|
|
727
|
+
"errors"
|
|
728
|
+
|
|
729
|
+
"github.com/google/uuid"
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
// Validate validates the delete request
|
|
733
|
+
func Validate(id uuid.UUID) error {
|
|
734
|
+
if id == uuid.Nil {
|
|
735
|
+
return errors.New("id is required")
|
|
736
|
+
}
|
|
737
|
+
return nil
|
|
738
|
+
}
|
|
739
|
+
`;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
getGetAllActionContent() {
|
|
743
|
+
return `package getAll${this.moduleNamePascal}
|
|
744
|
+
|
|
745
|
+
import (
|
|
746
|
+
"context"
|
|
747
|
+
|
|
748
|
+
"${this.goModule}/src/domain/models"
|
|
749
|
+
"${this.goModule}/src/infrastructure/entities"
|
|
750
|
+
|
|
751
|
+
"gorm.io/gorm"
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
// Action performs the getAll operation with filtering
|
|
755
|
+
func Action(ctx context.Context, db *gorm.DB, filter *models.${this.moduleNamePascal}Filter) ([]models.${this.moduleNamePascal}Model, int64, error) {
|
|
756
|
+
var entityList []entities.${this.moduleNamePascal}
|
|
757
|
+
var total int64
|
|
758
|
+
|
|
759
|
+
query := db.WithContext(ctx).Model(&entities.${this.moduleNamePascal}{}).Where("is_active = ?", true)
|
|
760
|
+
|
|
761
|
+
// Apply filters
|
|
762
|
+
if filter != nil {
|
|
763
|
+
if filter.Name != "" {
|
|
764
|
+
query = query.Where("name ILIKE ?", "%"+filter.Name+"%")
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if filter.Search != nil && filter.Search.Q != "" {
|
|
768
|
+
query = query.Where("name ILIKE ? OR description ILIKE ?", "%"+filter.Search.Q+"%", "%"+filter.Search.Q+"%")
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Count total
|
|
773
|
+
if err := query.Count(&total).Error; err != nil {
|
|
774
|
+
return nil, 0, err
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Apply sorting
|
|
778
|
+
if filter != nil && filter.Sort != 0 {
|
|
779
|
+
if filter.Sort == 1 {
|
|
780
|
+
query = query.Order("created_at ASC")
|
|
781
|
+
} else {
|
|
782
|
+
query = query.Order("created_at DESC")
|
|
783
|
+
}
|
|
784
|
+
} else {
|
|
785
|
+
query = query.Order("created_at DESC")
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Apply pagination
|
|
789
|
+
if filter != nil && filter.Paginate != nil {
|
|
790
|
+
offset := filter.Paginate.GetOffset()
|
|
791
|
+
limit := filter.Paginate.GetLimit()
|
|
792
|
+
query = query.Offset(offset).Limit(limit)
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if err := query.Find(&entityList).Error; err != nil {
|
|
796
|
+
return nil, 0, err
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Convert to models
|
|
800
|
+
result := make([]models.${this.moduleNamePascal}Model, len(entityList))
|
|
801
|
+
for i, entity := range entityList {
|
|
802
|
+
result[i] = *entity.ToModel()
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
return result, total, nil
|
|
806
|
+
}
|
|
807
|
+
`;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
getGetAllValidationContent() {
|
|
811
|
+
return `package getAll${this.moduleNamePascal}
|
|
812
|
+
|
|
813
|
+
import (
|
|
814
|
+
"${this.goModule}/src/domain/models"
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
// Validate validates the getAll request
|
|
818
|
+
func Validate(filter *models.${this.moduleNamePascal}Filter) error {
|
|
819
|
+
// Add validation logic if needed
|
|
820
|
+
return nil
|
|
821
|
+
}
|
|
822
|
+
`;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
getLoadByIdActionContent() {
|
|
826
|
+
return `package load${this.moduleNamePascal}ById
|
|
827
|
+
|
|
828
|
+
import (
|
|
829
|
+
"context"
|
|
830
|
+
|
|
831
|
+
"${this.goModule}/src/domain/models"
|
|
832
|
+
"${this.goModule}/src/infrastructure/entities"
|
|
833
|
+
|
|
834
|
+
"github.com/google/uuid"
|
|
835
|
+
"gorm.io/gorm"
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
// Action performs the loadById operation
|
|
839
|
+
func Action(ctx context.Context, db *gorm.DB, id uuid.UUID) (*models.${this.moduleNamePascal}Model, error) {
|
|
840
|
+
var entity entities.${this.moduleNamePascal}
|
|
841
|
+
err := db.WithContext(ctx).Where("id = ? AND is_active = ?", id, true).First(&entity).Error
|
|
842
|
+
if err != nil {
|
|
843
|
+
return nil, err
|
|
844
|
+
}
|
|
845
|
+
return entity.ToModel(), nil
|
|
846
|
+
}
|
|
847
|
+
`;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
getLoadByIdValidationContent() {
|
|
851
|
+
return `package load${this.moduleNamePascal}ById
|
|
852
|
+
|
|
853
|
+
import (
|
|
854
|
+
"errors"
|
|
855
|
+
|
|
856
|
+
"github.com/google/uuid"
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
// Validate validates the loadById request
|
|
860
|
+
func Validate(id uuid.UUID) error {
|
|
861
|
+
if id == uuid.Nil {
|
|
862
|
+
return errors.New("id is required")
|
|
863
|
+
}
|
|
864
|
+
return nil
|
|
865
|
+
}
|
|
866
|
+
`;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
getLoadAllActionContent() {
|
|
870
|
+
return `package loadAll${this.moduleNamePascal}
|
|
871
|
+
|
|
872
|
+
import (
|
|
873
|
+
"context"
|
|
874
|
+
|
|
875
|
+
"${this.goModule}/src/domain/models"
|
|
876
|
+
"${this.goModule}/src/infrastructure/entities"
|
|
877
|
+
|
|
878
|
+
"gorm.io/gorm"
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
// Action performs the loadAll operation (without pagination)
|
|
882
|
+
func Action(ctx context.Context, db *gorm.DB, filter *models.${this.moduleNamePascal}Filter) ([]models.${this.moduleNamePascal}Model, int64, error) {
|
|
883
|
+
var entityList []entities.${this.moduleNamePascal}
|
|
884
|
+
var total int64
|
|
885
|
+
|
|
886
|
+
query := db.WithContext(ctx).Model(&entities.${this.moduleNamePascal}{}).Where("is_active = ?", true)
|
|
887
|
+
|
|
888
|
+
// Apply filters
|
|
889
|
+
if filter != nil {
|
|
890
|
+
if filter.Name != "" {
|
|
891
|
+
query = query.Where("name ILIKE ?", "%"+filter.Name+"%")
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Count total
|
|
896
|
+
if err := query.Count(&total).Error; err != nil {
|
|
897
|
+
return nil, 0, err
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Order by created_at DESC
|
|
901
|
+
query = query.Order("created_at DESC")
|
|
902
|
+
|
|
903
|
+
if err := query.Find(&entityList).Error; err != nil {
|
|
904
|
+
return nil, 0, err
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Convert to models
|
|
908
|
+
result := make([]models.${this.moduleNamePascal}Model, len(entityList))
|
|
909
|
+
for i, entity := range entityList {
|
|
910
|
+
result[i] = *entity.ToModel()
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
return result, total, nil
|
|
914
|
+
}
|
|
915
|
+
`;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
getLoadAllValidationContent() {
|
|
919
|
+
return `package loadAll${this.moduleNamePascal}
|
|
920
|
+
|
|
921
|
+
import (
|
|
922
|
+
"${this.goModule}/src/domain/models"
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
// Validate validates the loadAll request
|
|
926
|
+
func Validate(filter *models.${this.moduleNamePascal}Filter) error {
|
|
927
|
+
// Add validation logic if needed
|
|
928
|
+
return nil
|
|
929
|
+
}
|
|
930
|
+
`;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
getUpdateActionContent() {
|
|
934
|
+
return `package update${this.moduleNamePascal}
|
|
935
|
+
|
|
936
|
+
import (
|
|
937
|
+
"context"
|
|
938
|
+
|
|
939
|
+
"${this.goModule}/src/domain/models"
|
|
940
|
+
"${this.goModule}/src/infrastructure/entities"
|
|
941
|
+
|
|
942
|
+
"gorm.io/gorm"
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
// Action performs the update operation
|
|
946
|
+
func Action(ctx context.Context, db *gorm.DB, model *models.${this.moduleNamePascal}Model) error {
|
|
947
|
+
entity := entities.${this.moduleNamePascal}FromModel(model)
|
|
948
|
+
if err := db.WithContext(ctx).Save(entity).Error; err != nil {
|
|
949
|
+
return err
|
|
950
|
+
}
|
|
951
|
+
*model = *entity.ToModel()
|
|
952
|
+
return nil
|
|
953
|
+
}
|
|
954
|
+
`;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
getUpdateValidationContent() {
|
|
958
|
+
return `package update${this.moduleNamePascal}
|
|
959
|
+
|
|
960
|
+
import (
|
|
961
|
+
"errors"
|
|
962
|
+
|
|
963
|
+
"${this.goModule}/src/domain/models"
|
|
964
|
+
"github.com/google/uuid"
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
// Validate validates the update request
|
|
968
|
+
func Validate(model *models.${this.moduleNamePascal}Model) error {
|
|
969
|
+
if model.ID == uuid.Nil {
|
|
970
|
+
return errors.New("id is required")
|
|
971
|
+
}
|
|
972
|
+
if model.Name == "" {
|
|
973
|
+
return errors.New("name is required")
|
|
974
|
+
}
|
|
975
|
+
return nil
|
|
976
|
+
}
|
|
977
|
+
`;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// ============================================
|
|
981
|
+
// Content Generators - Main Repository
|
|
982
|
+
// ============================================
|
|
983
|
+
|
|
984
|
+
getMainRepositoryContent() {
|
|
985
|
+
return `package ${this.moduleNameLower}
|
|
986
|
+
|
|
987
|
+
import (
|
|
988
|
+
"context"
|
|
989
|
+
|
|
990
|
+
"${this.goModule}/src/domain/models"
|
|
991
|
+
domainRepo "${this.goModule}/src/domain/repositories"
|
|
992
|
+
"${this.goModule}/src/infrastructure/repositories/${this.moduleNameLower}/create${this.moduleNamePascal}"
|
|
993
|
+
"${this.goModule}/src/infrastructure/repositories/${this.moduleNameLower}/delete${this.moduleNamePascal}"
|
|
994
|
+
"${this.goModule}/src/infrastructure/repositories/${this.moduleNameLower}/getAll${this.moduleNamePascal}"
|
|
995
|
+
"${this.goModule}/src/infrastructure/repositories/${this.moduleNameLower}/load${this.moduleNamePascal}ById"
|
|
996
|
+
"${this.goModule}/src/infrastructure/repositories/${this.moduleNameLower}/loadAll${this.moduleNamePascal}"
|
|
997
|
+
"${this.goModule}/src/infrastructure/repositories/${this.moduleNameLower}/update${this.moduleNamePascal}"
|
|
998
|
+
|
|
999
|
+
"github.com/google/uuid"
|
|
1000
|
+
"gorm.io/gorm"
|
|
1001
|
+
)
|
|
1002
|
+
|
|
1003
|
+
// Repository implements ${this.moduleNamePascal}Repository interface
|
|
1004
|
+
type Repository struct {
|
|
1005
|
+
db *gorm.DB
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// NewRepository creates a new ${this.moduleNameLower} repository
|
|
1009
|
+
func NewRepository(db *gorm.DB) *Repository {
|
|
1010
|
+
return &Repository{db: db}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// Ensure Repository implements interface
|
|
1014
|
+
var _ domainRepo.${this.moduleNamePascal}Repository = (*Repository)(nil)
|
|
1015
|
+
|
|
1016
|
+
func (r *Repository) Create(ctx context.Context, model *models.${this.moduleNamePascal}Model) error {
|
|
1017
|
+
if err := create${this.moduleNamePascal}.Validate(model); err != nil {
|
|
1018
|
+
return err
|
|
1019
|
+
}
|
|
1020
|
+
return create${this.moduleNamePascal}.Action(ctx, r.db, model)
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
func (r *Repository) Update(ctx context.Context, model *models.${this.moduleNamePascal}Model) error {
|
|
1024
|
+
if err := update${this.moduleNamePascal}.Validate(model); err != nil {
|
|
1025
|
+
return err
|
|
1026
|
+
}
|
|
1027
|
+
return update${this.moduleNamePascal}.Action(ctx, r.db, model)
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
func (r *Repository) Delete(ctx context.Context, id uuid.UUID) error {
|
|
1031
|
+
if err := delete${this.moduleNamePascal}.Validate(id); err != nil {
|
|
1032
|
+
return err
|
|
1033
|
+
}
|
|
1034
|
+
return delete${this.moduleNamePascal}.Action(ctx, r.db, id)
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
func (r *Repository) GetById(ctx context.Context, id uuid.UUID) (*models.${this.moduleNamePascal}Model, error) {
|
|
1038
|
+
if err := load${this.moduleNamePascal}ById.Validate(id); err != nil {
|
|
1039
|
+
return nil, err
|
|
1040
|
+
}
|
|
1041
|
+
return load${this.moduleNamePascal}ById.Action(ctx, r.db, id)
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
func (r *Repository) GetAll(ctx context.Context, filter *models.${this.moduleNamePascal}Filter) ([]models.${this.moduleNamePascal}Model, int64, error) {
|
|
1045
|
+
if err := getAll${this.moduleNamePascal}.Validate(filter); err != nil {
|
|
1046
|
+
return nil, 0, err
|
|
1047
|
+
}
|
|
1048
|
+
return getAll${this.moduleNamePascal}.Action(ctx, r.db, filter)
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
func (r *Repository) LoadById(ctx context.Context, id uuid.UUID) (*models.${this.moduleNamePascal}Model, error) {
|
|
1052
|
+
if err := load${this.moduleNamePascal}ById.Validate(id); err != nil {
|
|
1053
|
+
return nil, err
|
|
1054
|
+
}
|
|
1055
|
+
return load${this.moduleNamePascal}ById.Action(ctx, r.db, id)
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
func (r *Repository) LoadAll(ctx context.Context, filter *models.${this.moduleNamePascal}Filter) ([]models.${this.moduleNamePascal}Model, int64, error) {
|
|
1059
|
+
if err := loadAll${this.moduleNamePascal}.Validate(filter); err != nil {
|
|
1060
|
+
return nil, 0, err
|
|
1061
|
+
}
|
|
1062
|
+
return loadAll${this.moduleNamePascal}.Action(ctx, r.db, filter)
|
|
1063
|
+
}
|
|
1064
|
+
`;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// ============================================
|
|
1068
|
+
// Content Generators - Usecase
|
|
1069
|
+
// ============================================
|
|
1070
|
+
|
|
1071
|
+
getUsecaseContent() {
|
|
1072
|
+
return `package ${this.moduleNameLower}
|
|
1073
|
+
|
|
1074
|
+
import (
|
|
1075
|
+
"context"
|
|
1076
|
+
"errors"
|
|
1077
|
+
|
|
1078
|
+
"${this.goModule}/src/domain/logger"
|
|
1079
|
+
"${this.goModule}/src/domain/models"
|
|
1080
|
+
"${this.goModule}/src/domain/repositories"
|
|
1081
|
+
|
|
1082
|
+
"github.com/google/uuid"
|
|
1083
|
+
"gorm.io/gorm"
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1086
|
+
// Usecase handles ${this.moduleNameLower} business logic
|
|
1087
|
+
type Usecase struct {
|
|
1088
|
+
repo repositories.${this.moduleNamePascal}Repository
|
|
1089
|
+
logger logger.Logger
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// NewUsecase creates a new ${this.moduleNameLower} usecase
|
|
1093
|
+
func NewUsecase(repo repositories.${this.moduleNamePascal}Repository, logger logger.Logger) *Usecase {
|
|
1094
|
+
return &Usecase{
|
|
1095
|
+
repo: repo,
|
|
1096
|
+
logger: logger,
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Create creates a new ${this.moduleNameLower}
|
|
1101
|
+
func (u *Usecase) Create(ctx context.Context, name, description string) (*models.${this.moduleNamePascal}Model, error) {
|
|
1102
|
+
model := models.New${this.moduleNamePascal}Model(name, description)
|
|
1103
|
+
|
|
1104
|
+
if err := u.repo.Create(ctx, model); err != nil {
|
|
1105
|
+
u.logger.Error("${this.moduleNamePascal}Usecase", "Failed to create ${this.moduleNameLower}", err)
|
|
1106
|
+
return nil, err
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
u.logger.Info("${this.moduleNamePascal}Usecase", "${this.moduleNamePascal} created: "+model.ID.String())
|
|
1110
|
+
return model, nil
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// Update updates an existing ${this.moduleNameLower}
|
|
1114
|
+
func (u *Usecase) Update(ctx context.Context, id uuid.UUID, name, description string) (*models.${this.moduleNamePascal}Model, error) {
|
|
1115
|
+
model, err := u.repo.LoadById(ctx, id)
|
|
1116
|
+
if err != nil {
|
|
1117
|
+
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
1118
|
+
return nil, errors.New("${this.moduleNameLower} not found")
|
|
1119
|
+
}
|
|
1120
|
+
u.logger.Error("${this.moduleNamePascal}Usecase", "Failed to find ${this.moduleNameLower}", err)
|
|
1121
|
+
return nil, err
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
model.Name = name
|
|
1125
|
+
model.Description = description
|
|
1126
|
+
|
|
1127
|
+
if err := u.repo.Update(ctx, model); err != nil {
|
|
1128
|
+
u.logger.Error("${this.moduleNamePascal}Usecase", "Failed to update ${this.moduleNameLower}", err)
|
|
1129
|
+
return nil, err
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
u.logger.Info("${this.moduleNamePascal}Usecase", "${this.moduleNamePascal} updated: "+id.String())
|
|
1133
|
+
return model, nil
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// Delete soft deletes a ${this.moduleNameLower}
|
|
1137
|
+
func (u *Usecase) Delete(ctx context.Context, id uuid.UUID) error {
|
|
1138
|
+
if err := u.repo.Delete(ctx, id); err != nil {
|
|
1139
|
+
u.logger.Error("${this.moduleNamePascal}Usecase", "Failed to delete ${this.moduleNameLower}", err)
|
|
1140
|
+
return err
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
u.logger.Info("${this.moduleNamePascal}Usecase", "${this.moduleNamePascal} deleted: "+id.String())
|
|
1144
|
+
return nil
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// GetById gets a ${this.moduleNameLower} by ID
|
|
1148
|
+
func (u *Usecase) GetById(ctx context.Context, id uuid.UUID) (*models.${this.moduleNamePascal}Model, error) {
|
|
1149
|
+
model, err := u.repo.GetById(ctx, id)
|
|
1150
|
+
if err != nil {
|
|
1151
|
+
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
1152
|
+
return nil, errors.New("${this.moduleNameLower} not found")
|
|
1153
|
+
}
|
|
1154
|
+
u.logger.Error("${this.moduleNamePascal}Usecase", "Failed to find ${this.moduleNameLower}", err)
|
|
1155
|
+
return nil, err
|
|
1156
|
+
}
|
|
1157
|
+
return model, nil
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// GetAll gets all ${this.moduleNameLower}s with filtering and pagination
|
|
1161
|
+
func (u *Usecase) GetAll(ctx context.Context, filter *models.${this.moduleNamePascal}Filter) ([]models.${this.moduleNamePascal}Model, int64, error) {
|
|
1162
|
+
items, total, err := u.repo.GetAll(ctx, filter)
|
|
1163
|
+
if err != nil {
|
|
1164
|
+
u.logger.Error("${this.moduleNamePascal}Usecase", "Failed to get ${this.moduleNameLower}s", err)
|
|
1165
|
+
return nil, 0, err
|
|
1166
|
+
}
|
|
1167
|
+
return items, total, nil
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// LoadById loads a ${this.moduleNameLower} by ID
|
|
1171
|
+
func (u *Usecase) LoadById(ctx context.Context, id uuid.UUID) (*models.${this.moduleNamePascal}Model, error) {
|
|
1172
|
+
return u.repo.LoadById(ctx, id)
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// LoadAll loads all ${this.moduleNameLower}s without pagination
|
|
1176
|
+
func (u *Usecase) LoadAll(ctx context.Context, filter *models.${this.moduleNamePascal}Filter) ([]models.${this.moduleNamePascal}Model, int64, error) {
|
|
1177
|
+
return u.repo.LoadAll(ctx, filter)
|
|
1178
|
+
}
|
|
1179
|
+
`;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// ============================================
|
|
1183
|
+
// Content Generators - Gin Controller
|
|
1184
|
+
// ============================================
|
|
1185
|
+
|
|
1186
|
+
getControllerContent() {
|
|
1187
|
+
return `package ${this.moduleNameLower}
|
|
1188
|
+
|
|
1189
|
+
import (
|
|
1190
|
+
"net/http"
|
|
1191
|
+
"strconv"
|
|
1192
|
+
|
|
1193
|
+
"${this.goModule}/src/domain/dtos"
|
|
1194
|
+
"${this.goModule}/src/domain/logger"
|
|
1195
|
+
"${this.goModule}/src/domain/models"
|
|
1196
|
+
"${this.goModule}/src/infrastructure/common/response"
|
|
1197
|
+
"${this.goModule}/src/shared/utils"
|
|
1198
|
+
${this.moduleNameCamel}Usecase "${this.goModule}/src/usecases/${this.moduleNameLower}"
|
|
1199
|
+
|
|
1200
|
+
"github.com/gin-gonic/gin"
|
|
1201
|
+
"github.com/google/uuid"
|
|
1202
|
+
)
|
|
1203
|
+
|
|
1204
|
+
const controllerName = "${this.moduleNamePascal}Controller"
|
|
1205
|
+
|
|
1206
|
+
// Controller handles ${this.moduleNameLower} HTTP requests
|
|
1207
|
+
type Controller struct {
|
|
1208
|
+
usecase *${this.moduleNameCamel}Usecase.Usecase
|
|
1209
|
+
logger logger.Logger
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// NewController creates a new ${this.moduleNameLower} controller
|
|
1213
|
+
func NewController(usecase *${this.moduleNameCamel}Usecase.Usecase, logger logger.Logger) *Controller {
|
|
1214
|
+
return &Controller{
|
|
1215
|
+
usecase: usecase,
|
|
1216
|
+
logger: logger,
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// Create creates a new ${this.moduleNameLower}
|
|
1221
|
+
// @Summary Create a new ${this.moduleNameLower}
|
|
1222
|
+
// @Tags ${this.moduleNamePascal}
|
|
1223
|
+
// @Accept json
|
|
1224
|
+
// @Produce json
|
|
1225
|
+
// @Param request body dtos.Create${this.moduleNamePascal}Request true "Create ${this.moduleNamePascal} Request"
|
|
1226
|
+
// @Success 201 {object} response.Response{data=dtos.${this.moduleNamePascal}Response}
|
|
1227
|
+
// @Failure 400 {object} response.Response
|
|
1228
|
+
// @Failure 500 {object} response.Response
|
|
1229
|
+
// @Router /api/v1/${this.moduleNameSnake}s [post]
|
|
1230
|
+
func (c *Controller) Create(ctx *gin.Context) {
|
|
1231
|
+
var req dtos.Create${this.moduleNamePascal}Request
|
|
1232
|
+
if err := ctx.ShouldBindJSON(&req); err != nil {
|
|
1233
|
+
response.ValidationError(ctx, err.Error())
|
|
1234
|
+
return
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
model, err := c.usecase.Create(ctx.Request.Context(), req.Name, req.Description)
|
|
1238
|
+
if err != nil {
|
|
1239
|
+
c.logger.Error(controllerName, "Failed to create ${this.moduleNameLower}", err)
|
|
1240
|
+
response.Error(ctx, http.StatusInternalServerError, err.Error())
|
|
1241
|
+
return
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
response.Success(ctx, http.StatusCreated, modelToResponse(model))
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// GetByID gets a ${this.moduleNameLower} by ID
|
|
1248
|
+
// @Summary Get a ${this.moduleNameLower} by ID
|
|
1249
|
+
// @Tags ${this.moduleNamePascal}
|
|
1250
|
+
// @Produce json
|
|
1251
|
+
// @Param id path string true "${this.moduleNamePascal} ID"
|
|
1252
|
+
// @Success 200 {object} response.Response{data=dtos.${this.moduleNamePascal}Response}
|
|
1253
|
+
// @Failure 400 {object} response.Response
|
|
1254
|
+
// @Failure 404 {object} response.Response
|
|
1255
|
+
// @Router /api/v1/${this.moduleNameSnake}s/{id} [get]
|
|
1256
|
+
func (c *Controller) GetByID(ctx *gin.Context) {
|
|
1257
|
+
id, err := uuid.Parse(ctx.Param("id"))
|
|
1258
|
+
if err != nil {
|
|
1259
|
+
response.Error(ctx, http.StatusBadRequest, "Invalid ID format")
|
|
1260
|
+
return
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
model, err := c.usecase.GetById(ctx.Request.Context(), id)
|
|
1264
|
+
if err != nil {
|
|
1265
|
+
if appErr, ok := utils.IsAppError(err); ok {
|
|
1266
|
+
response.Error(ctx, appErr.Code, appErr.Message)
|
|
1267
|
+
return
|
|
1268
|
+
}
|
|
1269
|
+
response.Error(ctx, http.StatusNotFound, "${this.moduleNamePascal} not found")
|
|
1270
|
+
return
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
response.Success(ctx, http.StatusOK, modelToResponse(model))
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// GetAll gets all ${this.moduleNameLower}s with pagination
|
|
1277
|
+
// @Summary Get all ${this.moduleNameLower}s
|
|
1278
|
+
// @Tags ${this.moduleNamePascal}
|
|
1279
|
+
// @Produce json
|
|
1280
|
+
// @Param page query int false "Page number" default(1)
|
|
1281
|
+
// @Param page_size query int false "Page size" default(10)
|
|
1282
|
+
// @Param search query string false "Search term"
|
|
1283
|
+
// @Success 200 {object} response.Response{data=[]dtos.${this.moduleNamePascal}Response}
|
|
1284
|
+
// @Failure 500 {object} response.Response
|
|
1285
|
+
// @Router /api/v1/${this.moduleNameSnake}s [get]
|
|
1286
|
+
func (c *Controller) GetAll(ctx *gin.Context) {
|
|
1287
|
+
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
|
|
1288
|
+
pageSize, _ := strconv.Atoi(ctx.DefaultQuery("page_size", "10"))
|
|
1289
|
+
search := ctx.Query("search")
|
|
1290
|
+
|
|
1291
|
+
filter := &models.${this.moduleNamePascal}Filter{
|
|
1292
|
+
QueryParams: models.QueryParams{
|
|
1293
|
+
Paginate: &models.Paginate{
|
|
1294
|
+
Page: page,
|
|
1295
|
+
Limit: pageSize,
|
|
1296
|
+
},
|
|
1297
|
+
},
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
if search != "" {
|
|
1301
|
+
filter.Search = &models.Search{
|
|
1302
|
+
SearchField: []string{"name", "description"},
|
|
1303
|
+
Q: search,
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
items, total, err := c.usecase.GetAll(ctx.Request.Context(), filter)
|
|
1308
|
+
if err != nil {
|
|
1309
|
+
c.logger.Error(controllerName, "Failed to get ${this.moduleNameLower}s", err)
|
|
1310
|
+
response.Error(ctx, http.StatusInternalServerError, err.Error())
|
|
1311
|
+
return
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
totalPage := int(total) / pageSize
|
|
1315
|
+
if int(total)%pageSize > 0 {
|
|
1316
|
+
totalPage++
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
response.SuccessWithMeta(ctx, http.StatusOK, modelsToResponse(items), &response.Meta{
|
|
1320
|
+
Page: page,
|
|
1321
|
+
PageSize: pageSize,
|
|
1322
|
+
Total: total,
|
|
1323
|
+
TotalPage: totalPage,
|
|
1324
|
+
})
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// Update updates an existing ${this.moduleNameLower}
|
|
1328
|
+
// @Summary Update a ${this.moduleNameLower}
|
|
1329
|
+
// @Tags ${this.moduleNamePascal}
|
|
1330
|
+
// @Accept json
|
|
1331
|
+
// @Produce json
|
|
1332
|
+
// @Param id path string true "${this.moduleNamePascal} ID"
|
|
1333
|
+
// @Param request body dtos.Update${this.moduleNamePascal}Request true "Update ${this.moduleNamePascal} Request"
|
|
1334
|
+
// @Success 200 {object} response.Response{data=dtos.${this.moduleNamePascal}Response}
|
|
1335
|
+
// @Failure 400 {object} response.Response
|
|
1336
|
+
// @Failure 404 {object} response.Response
|
|
1337
|
+
// @Failure 500 {object} response.Response
|
|
1338
|
+
// @Router /api/v1/${this.moduleNameSnake}s/{id} [put]
|
|
1339
|
+
func (c *Controller) Update(ctx *gin.Context) {
|
|
1340
|
+
id, err := uuid.Parse(ctx.Param("id"))
|
|
1341
|
+
if err != nil {
|
|
1342
|
+
response.Error(ctx, http.StatusBadRequest, "Invalid ID format")
|
|
1343
|
+
return
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
var req dtos.Update${this.moduleNamePascal}Request
|
|
1347
|
+
if err := ctx.ShouldBindJSON(&req); err != nil {
|
|
1348
|
+
response.ValidationError(ctx, err.Error())
|
|
1349
|
+
return
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
model, err := c.usecase.Update(ctx.Request.Context(), id, req.Name, req.Description)
|
|
1353
|
+
if err != nil {
|
|
1354
|
+
if appErr, ok := utils.IsAppError(err); ok {
|
|
1355
|
+
response.Error(ctx, appErr.Code, appErr.Message)
|
|
1356
|
+
return
|
|
1357
|
+
}
|
|
1358
|
+
c.logger.Error(controllerName, "Failed to update ${this.moduleNameLower}", err)
|
|
1359
|
+
response.Error(ctx, http.StatusInternalServerError, err.Error())
|
|
1360
|
+
return
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
response.Success(ctx, http.StatusOK, modelToResponse(model))
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// Delete deletes a ${this.moduleNameLower}
|
|
1367
|
+
// @Summary Delete a ${this.moduleNameLower}
|
|
1368
|
+
// @Tags ${this.moduleNamePascal}
|
|
1369
|
+
// @Produce json
|
|
1370
|
+
// @Param id path string true "${this.moduleNamePascal} ID"
|
|
1371
|
+
// @Success 200 {object} response.Response
|
|
1372
|
+
// @Failure 400 {object} response.Response
|
|
1373
|
+
// @Failure 500 {object} response.Response
|
|
1374
|
+
// @Router /api/v1/${this.moduleNameSnake}s/{id} [delete]
|
|
1375
|
+
func (c *Controller) Delete(ctx *gin.Context) {
|
|
1376
|
+
id, err := uuid.Parse(ctx.Param("id"))
|
|
1377
|
+
if err != nil {
|
|
1378
|
+
response.Error(ctx, http.StatusBadRequest, "Invalid ID format")
|
|
1379
|
+
return
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
if err := c.usecase.Delete(ctx.Request.Context(), id); err != nil {
|
|
1383
|
+
if appErr, ok := utils.IsAppError(err); ok {
|
|
1384
|
+
response.Error(ctx, appErr.Code, appErr.Message)
|
|
1385
|
+
return
|
|
1386
|
+
}
|
|
1387
|
+
c.logger.Error(controllerName, "Failed to delete ${this.moduleNameLower}", err)
|
|
1388
|
+
response.Error(ctx, http.StatusInternalServerError, err.Error())
|
|
1389
|
+
return
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
response.SuccessWithMessage(ctx, http.StatusOK, "${this.moduleNamePascal} deleted successfully")
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// ============================================================================
|
|
1396
|
+
// Converters
|
|
1397
|
+
// ============================================================================
|
|
1398
|
+
|
|
1399
|
+
const timeFormat = "2006-01-02T15:04:05Z07:00"
|
|
1400
|
+
|
|
1401
|
+
func modelToResponse(m *models.${this.moduleNamePascal}Model) *dtos.${this.moduleNamePascal}Response {
|
|
1402
|
+
if m == nil {
|
|
1403
|
+
return nil
|
|
1404
|
+
}
|
|
1405
|
+
return &dtos.${this.moduleNamePascal}Response{
|
|
1406
|
+
ID: m.ID.String(),
|
|
1407
|
+
Name: m.Name,
|
|
1408
|
+
Description: m.Description,
|
|
1409
|
+
IsActive: m.IsActive,
|
|
1410
|
+
CreatedAt: m.CreatedAt.Format(timeFormat),
|
|
1411
|
+
UpdatedAt: m.UpdatedAt.Format(timeFormat),
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
func modelsToResponse(items []models.${this.moduleNamePascal}Model) []*dtos.${this.moduleNamePascal}Response {
|
|
1416
|
+
result := make([]*dtos.${this.moduleNamePascal}Response, len(items))
|
|
1417
|
+
for i := range items {
|
|
1418
|
+
result[i] = modelToResponse(&items[i])
|
|
1419
|
+
}
|
|
1420
|
+
return result
|
|
1421
|
+
}
|
|
1422
|
+
`;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
module.exports = ModuleGenerator;
|