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.
@@ -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;