go-duck-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.
Files changed (49) hide show
  1. package/README.md +130 -0
  2. package/generators/cache.js +107 -0
  3. package/generators/config.js +173 -0
  4. package/generators/devops.js +212 -0
  5. package/generators/docs.js +74 -0
  6. package/generators/graphql.js +38 -0
  7. package/generators/kratos.js +157 -0
  8. package/generators/logger.js +68 -0
  9. package/generators/metering.js +143 -0
  10. package/generators/migrations.js +240 -0
  11. package/generators/mqtt.js +87 -0
  12. package/generators/multitenancy.js +130 -0
  13. package/generators/postgrest.js +115 -0
  14. package/generators/repository.js +28 -0
  15. package/generators/resilience.js +69 -0
  16. package/generators/security.js +168 -0
  17. package/generators/swagger.js +145 -0
  18. package/generators/telemetry.js +121 -0
  19. package/generators/websocket.js +162 -0
  20. package/index.js +592 -0
  21. package/package.json +23 -0
  22. package/parser/gdl.js +162 -0
  23. package/templates/application.yml.hbs +18 -0
  24. package/templates/docs/gin_bottle.png +0 -0
  25. package/templates/docs/index.html.hbs +226 -0
  26. package/templates/docs/intro.mp4 +0 -0
  27. package/templates/docs/kratos_mark.png +0 -0
  28. package/templates/docs/layout.hbs +106 -0
  29. package/templates/docs/logo.png +0 -0
  30. package/templates/docs/pages/audit.hbs +39 -0
  31. package/templates/docs/pages/cli.hbs +83 -0
  32. package/templates/docs/pages/gdl.hbs +223 -0
  33. package/templates/docs/pages/graphql.hbs +51 -0
  34. package/templates/docs/pages/grpc.hbs +100 -0
  35. package/templates/docs/pages/index.hbs +181 -0
  36. package/templates/docs/pages/integrations.hbs +83 -0
  37. package/templates/docs/pages/observability.hbs +34 -0
  38. package/templates/docs/pages/realtime.hbs +43 -0
  39. package/templates/docs/pages/rest.hbs +149 -0
  40. package/templates/docs/pages/security.hbs +31 -0
  41. package/templates/go/controller.go.hbs +236 -0
  42. package/templates/go/entity.go.hbs +34 -0
  43. package/templates/go/enum.go.hbs +7 -0
  44. package/templates/go/main.go.hbs +186 -0
  45. package/templates/graphql/resolver.go.hbs +50 -0
  46. package/templates/graphql/schema.graphql.hbs +64 -0
  47. package/templates/kratos/service.go.hbs +104 -0
  48. package/templates/proto/entity.proto.hbs +95 -0
  49. package/test_parser.js +9 -0
@@ -0,0 +1,236 @@
1
+ package controllers
2
+
3
+ import (
4
+ "fmt"
5
+ "net/http"
6
+ "strconv"
7
+
8
+ "{{app_name}}/config"
9
+ "{{app_name}}/messaging"
10
+ "{{app_name}}/models"
11
+ "{{app_name}}/cache"
12
+ "{{app_name}}/resilience"
13
+
14
+ "github.com/gin-gonic/gin"
15
+ "gorm.io/gorm"
16
+ )
17
+
18
+ type {{capitalize name}}Controller struct {
19
+ DB *gorm.DB
20
+ Config *config.Config
21
+ }
22
+
23
+ // Create{{capitalize name}}
24
+ func (ctrl *{{capitalize name}}Controller) Create(c *gin.Context) {
25
+ tenant, _ := c.Get("tenantDB")
26
+ tenantStr := fmt.Sprintf("%v", tenant)
27
+ ctx := c.Request.Context()
28
+
29
+ var entity models.{{capitalize name}}
30
+ if err := c.ShouldBindJSON(&entity); err != nil {
31
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
32
+ return
33
+ }
34
+ if err := ctrl.DB.WithContext(ctx).Create(&entity).Error; err != nil {
35
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
36
+ return
37
+ }
38
+
39
+ // Dynamic Cache Invalidation (Tenant Aware)
40
+ cache.ClearPattern(tenantStr + ":{{capitalize name}}*")
41
+
42
+ // MQTT Event (Resilient)
43
+ resilience.Execute(func() (interface{}, error) {
44
+ messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, "CREATE", "{{capitalize name}}", entity, nil)
45
+ return nil, nil
46
+ })
47
+
48
+ c.JSON(http.StatusCreated, entity)
49
+ }
50
+
51
+ // GetAll{{capitalize name}}s (with filtering, pagination, and lazy/eager loading)
52
+ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
53
+ var entities []models.{{capitalize name}}
54
+ ctx := c.Request.Context()
55
+ query := ctrl.DB.WithContext(ctx)
56
+
57
+ // 1. Pagination
58
+ page, _ := strconv.Atoi(c.DefaultQuery("page", "0"))
59
+ size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
60
+ query = query.Offset(page * size).Limit(size)
61
+
62
+ // 2. Eager Loading (Full Bodied) vs Lazy (IDs Only)
63
+ eager := c.Query("eager") == "true"
64
+ if eager {
65
+ {{#each relationships}}
66
+ query = query.Preload("{{capitalize from.field}}")
67
+ query = query.Preload("{{capitalize to.field}}")
68
+ {{/each}}
69
+ }
70
+
71
+ // 3. Simple Filtering (Optimized for CRUD)
72
+ // Example: ?name=eq.John
73
+ for key, values := range c.Request.URL.Query() {
74
+ if key == "page" || key == "size" || key == "eager" || key == "sort" {
75
+ continue
76
+ }
77
+ for _, val := range values {
78
+ query = query.Where(key+" = ?", val)
79
+ }
80
+ }
81
+
82
+ if err := query.Find(&entities).Error; err != nil {
83
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
84
+ return
85
+ }
86
+ c.JSON(http.StatusOK, entities)
87
+ }
88
+
89
+ // GetByID
90
+ func (ctrl *{{capitalize name}}Controller) GetByID(c *gin.Context) {
91
+ id := c.Param("id")
92
+ tenant, _ := c.Get("tenantDB")
93
+ tenantStr := fmt.Sprintf("%v", tenant)
94
+ ctx := c.Request.Context()
95
+
96
+ // Tenant-Aware Cache Key
97
+ cacheKey := fmt.Sprintf("%s:{{capitalize name}}:%s", tenantStr, id)
98
+
99
+ var entity models.{{capitalize name}}
100
+
101
+ // 1. Check Distributed Cache (Redis)
102
+ if cache.Get(cacheKey, &entity) {
103
+ c.JSON(http.StatusOK, entity)
104
+ return
105
+ }
106
+
107
+ // 2. Fallback to DB (With Context for Tracing)
108
+ query := ctrl.DB.WithContext(ctx)
109
+ if c.Query("eager") == "true" {
110
+ {{#each relationships}}
111
+ query = query.Preload("{{capitalize from.field}}")
112
+ query = query.Preload("{{capitalize to.field}}")
113
+ {{/each}}
114
+ }
115
+
116
+ if err := query.First(&entity, id).Error; err != nil {
117
+ c.JSON(http.StatusNotFound, gin.H{"error": "Resource not found"})
118
+ return
119
+ }
120
+
121
+ // 3. Update Cache (Resilient)
122
+ resilience.Execute(func() (interface{}, error) {
123
+ cache.Set(cacheKey, entity, ctrl.Config.GoDuck.Cache.Redis.TTL)
124
+ return nil, nil
125
+ })
126
+
127
+ c.JSON(http.StatusOK, entity)
128
+ }
129
+
130
+ // Update (PUT) - Full Update
131
+ func (ctrl *{{capitalize name}}Controller) Update(c *gin.Context) {
132
+ id := c.Param("id")
133
+ tenant, _ := c.Get("tenantDB")
134
+ tenantStr := fmt.Sprintf("%v", tenant)
135
+ ctx := c.Request.Context()
136
+
137
+ var entity models.{{capitalize name}}
138
+ if err := ctrl.DB.WithContext(ctx).First(&entity, id).Error; err != nil {
139
+ c.JSON(http.StatusNotFound, gin.H{"error": "Resource not found"})
140
+ return
141
+ }
142
+
143
+ // Capture Previous State
144
+ prev := entity
145
+
146
+ if err := c.ShouldBindJSON(&entity); err != nil {
147
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
148
+ return
149
+ }
150
+
151
+ if err := ctrl.DB.WithContext(ctx).Save(&entity).Error; err != nil {
152
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
153
+ return
154
+ }
155
+
156
+ // Cache Invalidation (Tenant Aware)
157
+ cache.Delete(fmt.Sprintf("%s:{{capitalize name}}:%s", tenantStr, id))
158
+
159
+ // MQTT Event (Resilient)
160
+ resilience.Execute(func() (interface{}, error) {
161
+ messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, "UPDATE", "{{capitalize name}}", entity, prev)
162
+ return nil, nil
163
+ })
164
+
165
+ c.JSON(http.StatusOK, entity)
166
+ }
167
+
168
+ // Patch (PATCH) - Partial Update
169
+ func (ctrl *{{capitalize name}}Controller) Patch(c *gin.Context) {
170
+ id := c.Param("id")
171
+ tenant, _ := c.Get("tenantDB")
172
+ tenantStr := fmt.Sprintf("%v", tenant)
173
+ ctx := c.Request.Context()
174
+
175
+ var entity models.{{capitalize name}}
176
+ if err := ctrl.DB.WithContext(ctx).First(&entity, id).Error; err != nil {
177
+ c.JSON(http.StatusNotFound, gin.H{"error": "Resource not found"})
178
+ return
179
+ }
180
+ prev := entity
181
+
182
+ var updates map[string]interface{}
183
+ if err := c.ShouldBindJSON(&updates); err != nil {
184
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
185
+ return
186
+ }
187
+
188
+ if err := ctrl.DB.WithContext(ctx).Model(&entity).Updates(updates).Error; err != nil {
189
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
190
+ return
191
+ }
192
+
193
+ // Fetch updated
194
+ ctrl.DB.WithContext(ctx).First(&entity, id)
195
+
196
+ // Cache Invalidation (Tenant Aware)
197
+ cache.Delete(fmt.Sprintf("%s:{{capitalize name}}:%s", tenantStr, id))
198
+
199
+ // MQTT Event (Resilient)
200
+ resilience.Execute(func() (interface{}, error) {
201
+ messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, "PATCH", "{{capitalize name}}", entity, prev)
202
+ return nil, nil
203
+ })
204
+
205
+ c.JSON(http.StatusOK, gin.H{"message": "Updated successfully", "data": entity})
206
+ }
207
+
208
+ // Delete
209
+ func (ctrl *{{capitalize name}}Controller) Delete(c *gin.Context) {
210
+ id := c.Param("id")
211
+ tenant, _ := c.Get("tenantDB")
212
+ tenantStr := fmt.Sprintf("%v", tenant)
213
+ ctx := c.Request.Context()
214
+
215
+ var entity models.{{capitalize name}}
216
+ if err := ctrl.DB.WithContext(ctx).First(&entity, id).Error; err != nil {
217
+ c.JSON(http.StatusNotFound, gin.H{"error": "Resource not found"})
218
+ return
219
+ }
220
+
221
+ if err := ctrl.DB.WithContext(ctx).Delete(&entity).Error; err != nil {
222
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
223
+ return
224
+ }
225
+
226
+ // Cache Invalidation (Tenant Aware)
227
+ cache.Delete(fmt.Sprintf("%s:{{capitalize name}}:%s", tenantStr, id))
228
+
229
+ // MQTT Event (Resilient)
230
+ resilience.Execute(func() (interface{}, error) {
231
+ messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, "DELETE", "{{capitalize name}}", entity, nil)
232
+ return nil, nil
233
+ })
234
+
235
+ c.JSON(http.StatusNoContent, nil)
236
+ }
@@ -0,0 +1,34 @@
1
+ package models
2
+
3
+ import (
4
+ "time"
5
+ {{#if (hasJson fields)}}
6
+ "gorm.io/datatypes"
7
+ {{/if}}
8
+ )
9
+
10
+ type {{name}} struct {
11
+ ID uint `gorm:"primaryKey" json:"id"`
12
+ {{#each fields}}
13
+ {{capitalize name}} {{toGoType type}} `{{#if unique}}gorm:"uniqueIndex" {{/if}}{{#if (isJson type)}}gorm:"type:{{toLowerCase type}};serializer:json" {{/if}}json:"{{name}}" {{#if required}}binding:"required"{{/if}}`
14
+ {{/each}}
15
+ {{#if (eq annotation "@Audited")}}
16
+ CreatedBy string `gorm:"column:created_by" json:"createdBy"`
17
+ CreatedDate time.Time `gorm:"column:created_date" json:"createdDate"`
18
+ LastModifiedBy string `gorm:"column:last_modified_by" json:"lastModifiedBy"`
19
+ LastModifiedDate time.Time `gorm:"column:last_modified_date" json:"lastModifiedDate"`
20
+ LastModifiedUserID string `gorm:"column:last_modified_user_id" json:"lastModifiedUserId"`
21
+ {{else}}
22
+ CreatedAt time.Time `gorm:"autoCreateTime" json:"createdAt"`
23
+ UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updatedAt"`
24
+ {{/if}}
25
+ {{#each relationships}}
26
+ {{#if (eq from.entity ../name)}}
27
+ {{capitalize from.field}} []{{capitalize to.entity}} `gorm:"foreignKey:{{capitalize to.field}}ID" json:"{{from.field}}"`
28
+ {{else}}
29
+ {{capitalize to.field}}ID *uint `gorm:"column:{{to.field}}_id;index" json:"{{to.field}}Id"`
30
+ {{capitalize to.field}} *{{capitalize from.entity}} `gorm:"foreignKey:{{capitalize to.field}}ID"
31
+ json:"{{to.field}},omitempty"`
32
+ {{/if}}
33
+ {{/each}}
34
+ }
@@ -0,0 +1,7 @@
1
+ type {{name}} string
2
+
3
+ const (
4
+ {{#each values}}
5
+ {{../name}}_{{this}} {{../name}} = "{{this}}"
6
+ {{/each}}
7
+ )
@@ -0,0 +1,186 @@
1
+ package main
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "log"
7
+ "net/http"
8
+
9
+ "github.com/gin-gonic/gin"
10
+ "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
11
+ "gorm.io/driver/postgres"
12
+ "gorm.io/gorm"
13
+ "gorm.io/plugin/opentelemetry/tracing"
14
+ "{{app_name}}/management"
15
+ "{{app_name}}/middleware"
16
+ "{{app_name}}/controllers"
17
+ "{{app_name}}/graph"
18
+ "{{app_name}}/ws"
19
+ "{{app_name}}/config"
20
+ "{{app_name}}/logger"
21
+ "{{app_name}}/messaging"
22
+ "{{app_name}}/cache"
23
+ "{{app_name}}/resilience"
24
+ "{{app_name}}/internal/telemetry"
25
+ "{{app_name}}/internal/repository"
26
+ "{{app_name}}/internal/server"
27
+ k_grpc "github.com/go-kratos/kratos/v2/transport/grpc"
28
+ // go-duck-needle-add-import
29
+ )
30
+
31
+ func main() {
32
+ // 1. Load Configuration
33
+ appConfig, err := config.LoadConfig()
34
+ if err != nil {
35
+ log.Fatalf("Failed to load configuration: %v", err)
36
+ }
37
+
38
+ // 2. Initialize Logging & Observability (Datadog)
39
+ logger.InitLogger(appConfig)
40
+ logger.Info("Starting %s version %s...", appConfig.GoDuck.Name, appConfig.GoDuck.Version)
41
+
42
+ // 3. Initialize OpenTelemetry Tracing
43
+ shutdown, err := telemetry.InitTelemetry(appConfig)
44
+ if err != nil {
45
+ log.Printf("Warning: Failed to initialize OpenTelemetry: %v", err)
46
+ }
47
+ defer shutdown(context.Background())
48
+
49
+ // 4. Initialize Resilience Layer (Circuit Breaker)
50
+ resilience.InitResilience(appConfig)
51
+
52
+ // 5. Initialize MQTT Messaging (for Webhooks/Audit)
53
+ messaging.InitMQTT(appConfig)
54
+
55
+ // 6. Initialize Distributed Caching (Redis)
56
+ cache.InitCache(appConfig)
57
+
58
+ // 7. Initialize master DB connection with Pool Tuning & Tracing
59
+ masterDB, err := gorm.Open(postgres.Open(appConfig.GetDSN()), &gorm.Config{})
60
+ if err != nil {
61
+ log.Fatalf("Failed to connect to master database: %v", err)
62
+ }
63
+ // go-duck-needle-add-init-server
64
+
65
+ // Inject GORM OTel Plugin
66
+ if err := masterDB.Use(tracing.NewPlugin()); err != nil {
67
+ log.Printf("Warning: Failed to inject GORM OTel plugin: %v", err)
68
+ }
69
+
70
+ sqlDB, _ := masterDB.DB()
71
+ sqlDB.SetMaxOpenConns(appConfig.GoDuck.Datasource.MaxOpenConns)
72
+ sqlDB.SetMaxIdleConns(appConfig.GoDuck.Datasource.MaxIdleConns)
73
+ sqlDB.SetConnMaxLifetime(appConfig.GoDuck.Datasource.ConnMaxLifetime)
74
+
75
+ // 8. Initialize Repository
76
+ repo := repository.NewRepository(masterDB)
77
+ // go-duck-needle-add-init-repository
78
+
79
+ // 9. Initialize & Start Kratos gRPC Server (in background)
80
+ go func() {
81
+ grpcSrv := server.NewGRPCServer(appConfig, repo)
82
+ logger.Info("Starting Kratos gRPC server on %s", appConfig.GoDuck.Server.GRPC.Addr)
83
+ if err := grpcSrv.Start(context.Background()); err != nil {
84
+ logger.Error("Failed to start Kratos gRPC server: %v", err)
85
+ }
86
+ }()
87
+ // go-duck-needle-add-grpc-start
88
+
89
+ r := gin.Default()
90
+
91
+ // 8. Global Middleware (OTel, Rate Limit & CORS)
92
+ if appConfig.GoDuck.Telemetry.OTel.Enabled {
93
+ r.Use(otelgin.Middleware(appConfig.GoDuck.Name))
94
+ }
95
+ r.Use(middleware.RateLimitMiddleware(appConfig))
96
+ r.Use(middleware.CORSMiddleware(appConfig))
97
+
98
+ // Health Check
99
+ r.GET("/health", func(c *gin.Context) {
100
+ c.JSON(http.StatusOK, gin.H{"status": "UP"})
101
+ })
102
+
103
+ // Swagger Docs & UI
104
+ r.StaticFile("/swagger.json", "./docs/swagger.json")
105
+ r.GET("/swagger", func(c *gin.Context) {
106
+ c.Header("Content-Type", "text/html; charset=utf-8")
107
+ c.String(http.StatusOK, \`
108
+ <!DOCTYPE html>
109
+ <html lang="en">
110
+
111
+ <head>
112
+ <meta charset="UTF-8">
113
+ <title>Swagger UI</title>
114
+ <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@3/swagger-ui.css">
115
+ </head>
116
+
117
+ <body>
118
+ <div id="swagger-ui"></div>
119
+ <script src="https://unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"> </script>
120
+ <script>
121
+ window.onload = function () {
122
+ window.ui = SwaggerUIBundle({
123
+ url: "/swagger.json",
124
+ dom_id: '#swagger-ui',
125
+ deepLinking: true,
126
+ presets: [SwaggerUIBundle.presets.apis],
127
+ layout: "BaseLayout"
128
+ });
129
+ };
130
+ </script>
131
+ </body>
132
+
133
+ </html>
134
+ \`)
135
+ })
136
+
137
+ // Management APIs (Run-time DB creation)
138
+ mgmt := r.Group("/management")
139
+ {
140
+ mgmt.POST("/db/create", management.CreateDatabaseAndMigrate(masterDB))
141
+ }
142
+
143
+ // 9. Secured Application APIs
144
+ api := r.Group("/api")
145
+ api.Use(middleware.JWTMiddleware())
146
+ api.Use(middleware.TenantMiddleware(masterDB))
147
+ api.Use(middleware.AuditMiddleware(masterDB))
148
+ api.Use(middleware.MeteringMiddleware(masterDB))
149
+ {
150
+ // Observability
151
+ auditCtrl := controllers.AuditController{DB: masterDB}
152
+ api.GET("/audit", auditCtrl.GetLogs)
153
+
154
+ meteringCtrl := controllers.MeteringController{DB: masterDB}
155
+ api.POST("/metering/limit", meteringCtrl.SetLimit)
156
+ api.GET("/metering/usage", meteringCtrl.GetUsage)
157
+
158
+ // Search
159
+ searchCtrl := controllers.SearchController{DB: masterDB}
160
+ api.GET("/rpc/:table", searchCtrl.GenericSearch)
161
+
162
+ {{#each entities}}
163
+ // {{name}} Routes
164
+ {{toLowerCase name}}Ctrl := controllers.{{capitalize name}}Controller{DB: masterDB, Config: appConfig}
165
+ api.POST("/{{toLowerCase name}}s", {{toLowerCase name}}Ctrl.Create)
166
+ api.GET("/{{toLowerCase name}}s", {{toLowerCase name}}Ctrl.GetAll)
167
+ api.GET("/{{toLowerCase name}}s/:id", {{toLowerCase name}}Ctrl.GetByID)
168
+ api.PUT("/{{toLowerCase name}}s/:id", {{toLowerCase name}}Ctrl.Update)
169
+ api.PATCH("/{{toLowerCase name}}s/:id", {{toLowerCase name}}Ctrl.Patch)
170
+ api.DELETE("/{{toLowerCase name}}s/:id", {{toLowerCase name}}Ctrl.Delete)
171
+ {{/each}}
172
+ // go-duck-needle-add-route
173
+ }
174
+
175
+ // 10. GraphQL
176
+ r.POST("/graphql", func(c *gin.Context) {
177
+ graph.HandleGraphQLRequest(masterDB, c)
178
+ })
179
+
180
+ // 11. WebSockets
181
+ wsDispatcher := ws.NewDispatcher(masterDB)
182
+ r.GET("/ws", middleware.JWTMiddleware(), wsDispatcher.HandleConnection)
183
+
184
+ port := fmt.Sprintf(":%d", appConfig.GoDuck.Server.Port)
185
+ r.Run(port)
186
+ }
@@ -0,0 +1,50 @@
1
+ package graph
2
+
3
+ import (
4
+ "encoding/json"
5
+ "net/http"
6
+ "{{app_name}}/models"
7
+
8
+ "github.com/gin-gonic/gin"
9
+ "gorm.io/gorm"
10
+ )
11
+
12
+ // HandleGraphQLRequest is the main bridge for the Gin web framework.
13
+ // In a production app, we would use a more robust engine (like gqlgen).
14
+ func HandleGraphQLRequest(db *gorm.DB, c *gin.Context) {
15
+ var input struct {
16
+ Query string \`json:"query"\`
17
+ Variables map[string]interface{} \`json:"variables"\`
18
+ }
19
+
20
+ if err := c.BindJSON(&input); err != nil {
21
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid GraphQL request"})
22
+ return
23
+ }
24
+
25
+ // We'll perform basic routing for the POC.
26
+ // For production, this should integrate with a real schema loader.
27
+ c.JSON(http.StatusOK, gin.H{
28
+ "data": "GraphQL Handler Integrated Successfully!",
29
+ "note": "Ready for schema execution.",
30
+ })
31
+ }
32
+
33
+ // Resolver for each entity
34
+ {{#each entities}}
35
+ func Resolve{{capitalize name}}(db *gorm.DB, id uint) (*models.{{capitalize name}}, error) {
36
+ var e models.{{capitalize name}}
37
+ if err := db.First(&e, id).Error; err != nil {
38
+ return nil, err
39
+ }
40
+ return &e, nil
41
+ }
42
+
43
+ func ResolveAll{{capitalize name}}s(db *gorm.DB) ([]models.{{capitalize name}}, error) {
44
+ var list []models.{{capitalize name}}
45
+ if err := db.Find(&list).Error; err != nil {
46
+ return nil, err
47
+ }
48
+ return list, nil
49
+ }
50
+ {{/each}}
@@ -0,0 +1,64 @@
1
+ {{#each entities}}
2
+ type {{name}} {
3
+ id: ID!
4
+ {{#each fields}}
5
+ {{name}}: {{gql_type type}}{{#if required}}!{{/if}}
6
+ {{/each}}
7
+ {{#if (eq annotation "@Audited")}}
8
+ createdBy: String
9
+ createdDate: String
10
+ lastModifiedBy: String
11
+ lastModifiedDate: String
12
+ lastModifiedUserId: String
13
+ {{/if}}
14
+ {{#each relationships}}
15
+ {{#if (eq from.entity ../name)}}
16
+ {{from.field}}: [{{capitalize to.entity}}]
17
+ {{else}}
18
+ {{to.field}}: {{capitalize from.entity}}
19
+ {{/if}}
20
+ {{/each}}
21
+ createdAt: String
22
+ updatedAt: String
23
+ }
24
+
25
+ input Create{{name}}Input {
26
+ {{#each fields}}
27
+ {{name}}: {{gql_type type}}{{#if required}}!{{/if}}
28
+ {{/each}}
29
+ {{#each relationships}}
30
+ {{#if (eq to.entity ../name)}}
31
+ {{to.field}}Id: ID!
32
+ {{/if}}
33
+ {{/each}}
34
+ }
35
+
36
+ input Update{{name}}Input {
37
+ {{#each fields}}
38
+ {{name}}: {{gql_type type}}
39
+ {{/each}}
40
+ }
41
+ {{/each}}
42
+
43
+ {{#each enums}}
44
+ enum {{name}} {
45
+ {{#each values}}
46
+ {{this}}
47
+ {{/each}}
48
+ }
49
+ {{/each}}
50
+
51
+ type Query {
52
+ {{#each entities}}
53
+ get{{capitalize name}}(id: ID!): {{name}}
54
+ list{{capitalize name}}s(page: Int, size: Int): [{{name}}]
55
+ {{/each}}
56
+ }
57
+
58
+ type Mutation {
59
+ {{#each entities}}
60
+ create{{capitalize name}}(input: Create{{name}}Input!): {{name}}
61
+ update{{capitalize name}}(id: ID!, input: Update{{name}}Input!): {{name}}
62
+ delete{{capitalize name}}(id: ID!): Boolean
63
+ {{/each}}
64
+ }
@@ -0,0 +1,104 @@
1
+ package service
2
+
3
+ import (
4
+ "context"
5
+ pb "{{projectName}}/api/v1"
6
+ "{{projectName}}/internal/repository"
7
+ "{{projectName}}/models"
8
+ {{#if (hasJson fields)}}
9
+ "gorm.io/datatypes"
10
+ {{/if}}
11
+ "google.golang.org/protobuf/types/known/timestamppb"
12
+ )
13
+
14
+ type {{capitalize name}}Service struct {
15
+ pb.Unimplemented{{capitalize name}}ServiceServer
16
+ repo *repository.Repository
17
+ }
18
+
19
+ func New{{capitalize name}}Service(repo *repository.Repository) *{{capitalize name}}Service {
20
+ return &{{capitalize name}}Service{repo: repo}
21
+ }
22
+
23
+ func (s *{{capitalize name}}Service) Create{{capitalize name}}(ctx context.Context, req *pb.Create{{capitalize name}}Request) (*pb.{{capitalize name}}Reply, error) {
24
+ entity := &models.{{capitalize name}}{
25
+ {{#each fields}}
26
+ {{capitalize name}}: {{#if (isJson type)}}datatypes.JSON(req.{{capitalize name}}){{else}}{{#if (toGoCast type)}}{{toGoCast type}}(req.{{capitalize name}}){{else}}req.{{capitalize name}}{{/if}}{{/if}},
27
+ {{/each}}
28
+ }
29
+ if err := s.repo.DB.WithContext(ctx).Create(entity).Error; err != nil {
30
+ return nil, err
31
+ }
32
+ return &pb.{{capitalize name}}Reply{
33
+ Data: map{{capitalize name}}ToPb(entity),
34
+ }, nil
35
+ }
36
+
37
+ func (s *{{capitalize name}}Service) Get{{capitalize name}}(ctx context.Context, req *pb.Get{{capitalize name}}Request) (*pb.{{capitalize name}}Reply, error) {
38
+ var entity models.{{capitalize name}}
39
+ if err := s.repo.DB.WithContext(ctx).First(&entity, req.Id).Error; err != nil {
40
+ return nil, err
41
+ }
42
+ return &pb.{{capitalize name}}Reply{
43
+ Data: map{{capitalize name}}ToPb(&entity),
44
+ }, nil
45
+ }
46
+
47
+ func (s *{{capitalize name}}Service) Update{{capitalize name}}(ctx context.Context, req *pb.Update{{capitalize name}}Request) (*pb.{{capitalize name}}Reply, error) {
48
+ var entity models.{{capitalize name}}
49
+ if err := s.repo.DB.WithContext(ctx).First(&entity, req.Id).Error; err != nil {
50
+ return nil, err
51
+ }
52
+
53
+ {{#each fields}}
54
+ entity.{{capitalize name}} = {{#if (isJson type)}}datatypes.JSON(req.{{capitalize name}}){{else}}{{#if (toGoCast type)}}{{toGoCast type}}(req.{{capitalize name}}){{else}}req.{{capitalize name}}{{/if}}{{/if}}
55
+ {{/each}}
56
+
57
+ if err := s.repo.DB.WithContext(ctx).Save(&entity).Error; err != nil {
58
+ return nil, err
59
+ }
60
+ return &pb.{{capitalize name}}Reply{
61
+ Data: map{{capitalize name}}ToPb(&entity),
62
+ }, nil
63
+ }
64
+
65
+ func (s *{{capitalize name}}Service) Delete{{capitalize name}}(ctx context.Context, req *pb.Delete{{capitalize name}}Request) (*pb.Delete{{capitalize name}}Reply, error) {
66
+ if err := s.repo.DB.WithContext(ctx).Delete(&models.{{capitalize name}}{}, req.Id).Error; err != nil {
67
+ return nil, err
68
+ }
69
+ return &pb.Delete{{capitalize name}}Reply{Message: "Success"}, nil
70
+ }
71
+
72
+ func (s *{{capitalize name}}Service) List{{capitalize name}}(ctx context.Context, req *pb.List{{capitalize name}}Request) (*pb.List{{capitalize name}}Reply, error) {
73
+ var results []models.{{capitalize name}}
74
+ var total int64
75
+
76
+ db := s.repo.DB.WithContext(ctx).Model(&models.{{capitalize name}}{})
77
+ db.Count(&total)
78
+
79
+ offset := (req.Page - 1) * req.PageSize
80
+ if err := db.Limit(int(req.PageSize)).Offset(int(offset)).Find(&results).Error; err != nil {
81
+ return nil, err
82
+ }
83
+
84
+ pbResults := make([]*pb.{{capitalize name}}, len(results))
85
+ for i, r := range results {
86
+ pbResults[i] = map{{capitalize name}}ToPb(&r)
87
+ }
88
+
89
+ return &pb.List{{capitalize name}}Reply{
90
+ Results: pbResults,
91
+ Total: total,
92
+ }, nil
93
+ }
94
+
95
+ func map{{capitalize name}}ToPb(m *models.{{capitalize name}}) *pb.{{capitalize name}} {
96
+ return &pb.{{capitalize name}}{
97
+ Id: uint64(m.ID),
98
+ {{#each fields}}
99
+ {{capitalize name}}: {{#if (isJson type)}}string(m.{{capitalize name}}){{else}}{{#if (toProtoCast type)}}{{toProtoCast type}}(m.{{capitalize name}}){{else}}m.{{capitalize name}}{{/if}}{{/if}},
100
+ {{/each}}
101
+ CreatedAt: timestamppb.New(m.CreatedAt),
102
+ UpdatedAt: timestamppb.New(m.UpdatedAt),
103
+ }
104
+ }