go-duck-cli 1.2.1 → 1.2.3
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 +74 -1
- package/generators/ai_docs.js +22 -8
- package/generators/config.js +13 -7
- package/generators/devops.js +591 -1
- package/generators/elasticsearch.js +3 -4
- package/generators/postman.js +156 -6
- package/generators/security.js +2 -2
- package/index.js +62 -7
- package/package.json +1 -1
- package/parser/gdl.js +2 -0
- package/templates/docs/index.html.hbs +24 -1
- package/templates/docs/pages/cli.hbs +20 -4
- package/templates/docs/pages/gdl-annotations.hbs +17 -0
- package/templates/docs/pages/rest.hbs +21 -0
- package/templates/go/controller.go.hbs +107 -20
- package/templates/go/main.go.hbs +16 -2
- package/templates/kratos/service.go.hbs +25 -2
- package/templates/proto/entity.proto.hbs +1 -0
|
@@ -3,6 +3,7 @@ package controllers
|
|
|
3
3
|
import (
|
|
4
4
|
"net/http"
|
|
5
5
|
"strconv"
|
|
6
|
+
"strings"
|
|
6
7
|
{{#if isSearchable}}
|
|
7
8
|
"context"
|
|
8
9
|
{{/if}}
|
|
@@ -22,6 +23,8 @@ import (
|
|
|
22
23
|
"{{app_name}}/internal/search"
|
|
23
24
|
{{/if}}
|
|
24
25
|
"github.com/gin-gonic/gin"
|
|
26
|
+
"github.com/gin-gonic/gin/binding"
|
|
27
|
+
"github.com/gin-gonic/gin/render"
|
|
25
28
|
{{#if isDocument}}
|
|
26
29
|
"go.mongodb.org/mongo-driver/bson"
|
|
27
30
|
"go.mongodb.org/mongo-driver/mongo"
|
|
@@ -44,9 +47,16 @@ type {{capitalize name}}Controller struct {
|
|
|
44
47
|
func (ctrl *{{capitalize name}}Controller) Create(c *gin.Context) {
|
|
45
48
|
ctx := c.Request.Context()
|
|
46
49
|
var entity models.{{capitalize name}}
|
|
47
|
-
if
|
|
48
|
-
c.
|
|
49
|
-
|
|
50
|
+
if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
|
|
51
|
+
if err := c.ShouldBindWith(&entity, binding.MsgPack); err != nil {
|
|
52
|
+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
if err := c.ShouldBindJSON(&entity); err != nil {
|
|
57
|
+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
58
|
+
return
|
|
59
|
+
}
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
{{#if isDocument}}
|
|
@@ -138,7 +148,11 @@ func (ctrl *{{capitalize name}}Controller) Create(c *gin.Context) {
|
|
|
138
148
|
}
|
|
139
149
|
{{/if}}
|
|
140
150
|
|
|
141
|
-
|
|
151
|
+
if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
|
|
152
|
+
c.Render(http.StatusCreated, render.MsgPack{Data: entity})
|
|
153
|
+
} else {
|
|
154
|
+
c.JSON(http.StatusCreated, entity)
|
|
155
|
+
}
|
|
142
156
|
}
|
|
143
157
|
|
|
144
158
|
// GetAll handles parallel read aggregation for both SQL and Mongo
|
|
@@ -146,6 +160,7 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
|
|
|
146
160
|
ctx := c.Request.Context()
|
|
147
161
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "0"))
|
|
148
162
|
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
|
163
|
+
sortParam := c.DefaultQuery("sort", "")
|
|
149
164
|
|
|
150
165
|
offset := 0
|
|
151
166
|
if page > 0 {
|
|
@@ -169,6 +184,15 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
|
|
|
169
184
|
defer wg.Done()
|
|
170
185
|
var entities []models.{{capitalize name}}
|
|
171
186
|
opts := options.Find().SetSkip(int64(offset)).SetLimit(int64(size))
|
|
187
|
+
if sortParam != "" {
|
|
188
|
+
parts := strings.Split(sortParam, ",")
|
|
189
|
+
field := parts[0]
|
|
190
|
+
order := 1
|
|
191
|
+
if len(parts) > 1 && strings.ToLower(parts[1]) == "desc" {
|
|
192
|
+
order = -1
|
|
193
|
+
}
|
|
194
|
+
opts.SetSort(bson.D{bson.E{Key: field, Value: order}})
|
|
195
|
+
}
|
|
172
196
|
cursor, err := db.Collection("{{toLowerCase name}}s").Find(ctx, bson.M{}, opts)
|
|
173
197
|
if err == nil {
|
|
174
198
|
cursor.All(ctx, &entities)
|
|
@@ -179,7 +203,11 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
|
|
|
179
203
|
}(role, siloDB)
|
|
180
204
|
}
|
|
181
205
|
wg.Wait()
|
|
182
|
-
|
|
206
|
+
if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
|
|
207
|
+
c.Render(http.StatusOK, render.MsgPack{Data: federatedData})
|
|
208
|
+
} else {
|
|
209
|
+
c.JSON(http.StatusOK, federatedData)
|
|
210
|
+
}
|
|
183
211
|
return
|
|
184
212
|
}
|
|
185
213
|
{{/if}}
|
|
@@ -200,13 +228,26 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
|
|
|
200
228
|
|
|
201
229
|
var entities []models.{{capitalize name}}
|
|
202
230
|
opts := options.Find().SetSkip(int64(offset)).SetLimit(int64(size))
|
|
231
|
+
if sortParam != "" {
|
|
232
|
+
parts := strings.Split(sortParam, ",")
|
|
233
|
+
field := parts[0]
|
|
234
|
+
order := 1
|
|
235
|
+
if len(parts) > 1 && strings.ToLower(parts[1]) == "desc" {
|
|
236
|
+
order = -1
|
|
237
|
+
}
|
|
238
|
+
opts.SetSort(bson.D{bson.E{Key: field, Value: order}})
|
|
239
|
+
}
|
|
203
240
|
cursor, err := tenantDB.Collection("{{toLowerCase name}}s").Find(ctx, filter, opts)
|
|
204
241
|
if err != nil {
|
|
205
242
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
206
243
|
return
|
|
207
244
|
}
|
|
208
245
|
cursor.All(ctx, &entities)
|
|
209
|
-
|
|
246
|
+
if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
|
|
247
|
+
c.Render(http.StatusOK, render.MsgPack{Data: entities})
|
|
248
|
+
} else {
|
|
249
|
+
c.JSON(http.StatusOK, entities)
|
|
250
|
+
}
|
|
210
251
|
|
|
211
252
|
{{else}}
|
|
212
253
|
// 🦆 GORM Path
|
|
@@ -227,7 +268,11 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
|
|
|
227
268
|
go func(r string, db *gorm.DB) {
|
|
228
269
|
defer wg.Done()
|
|
229
270
|
var entities []models.{{capitalize name}}
|
|
230
|
-
|
|
271
|
+
query := db.WithContext(ctx)
|
|
272
|
+
if sortParam != "" {
|
|
273
|
+
query = query.Order(strings.ReplaceAll(sortParam, ",", " "))
|
|
274
|
+
}
|
|
275
|
+
if err := query.Offset(offset).Limit(size).Find(&entities).Error; err == nil {
|
|
231
276
|
mu.Lock()
|
|
232
277
|
federatedData[r] = entities
|
|
233
278
|
mu.Unlock()
|
|
@@ -235,7 +280,11 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
|
|
|
235
280
|
}(role, siloDB)
|
|
236
281
|
}
|
|
237
282
|
wg.Wait()
|
|
238
|
-
|
|
283
|
+
if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
|
|
284
|
+
c.Render(http.StatusOK, render.MsgPack{Data: federatedData})
|
|
285
|
+
} else {
|
|
286
|
+
c.JSON(http.StatusOK, federatedData)
|
|
287
|
+
}
|
|
239
288
|
return
|
|
240
289
|
}
|
|
241
290
|
{{/if}}
|
|
@@ -248,11 +297,19 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
|
|
|
248
297
|
c.Header("X-Total-Count", strconv.FormatInt(totalCount, 10))
|
|
249
298
|
|
|
250
299
|
var entities []models.{{capitalize name}}
|
|
251
|
-
|
|
300
|
+
query := tenantDB.WithContext(ctx)
|
|
301
|
+
if sortParam != "" {
|
|
302
|
+
query = query.Order(strings.ReplaceAll(sortParam, ",", " "))
|
|
303
|
+
}
|
|
304
|
+
if err := query.Offset(offset).Limit(size).Find(&entities).Error; err != nil {
|
|
252
305
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
253
306
|
return
|
|
254
307
|
}
|
|
255
|
-
|
|
308
|
+
if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
|
|
309
|
+
c.Render(http.StatusOK, render.MsgPack{Data: entities})
|
|
310
|
+
} else {
|
|
311
|
+
c.JSON(http.StatusOK, entities)
|
|
312
|
+
}
|
|
256
313
|
{{/if}}
|
|
257
314
|
}
|
|
258
315
|
|
|
@@ -269,7 +326,11 @@ func (ctrl *{{capitalize name}}Controller) GetByID(c *gin.Context) {
|
|
|
269
326
|
c.JSON(http.StatusNotFound, gin.H{"error": "Not found"})
|
|
270
327
|
return
|
|
271
328
|
}
|
|
272
|
-
|
|
329
|
+
if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
|
|
330
|
+
c.Render(http.StatusOK, render.MsgPack{Data: entity})
|
|
331
|
+
} else {
|
|
332
|
+
c.JSON(http.StatusOK, entity)
|
|
333
|
+
}
|
|
273
334
|
{{else}}
|
|
274
335
|
db, _ := c.Get("tenantDBConn")
|
|
275
336
|
tenantDB := db.(*gorm.DB)
|
|
@@ -278,7 +339,11 @@ func (ctrl *{{capitalize name}}Controller) GetByID(c *gin.Context) {
|
|
|
278
339
|
c.JSON(http.StatusNotFound, gin.H{"error": "Not found"})
|
|
279
340
|
return
|
|
280
341
|
}
|
|
281
|
-
|
|
342
|
+
if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
|
|
343
|
+
c.Render(http.StatusOK, render.MsgPack{Data: entity})
|
|
344
|
+
} else {
|
|
345
|
+
c.JSON(http.StatusOK, entity)
|
|
346
|
+
}
|
|
282
347
|
{{/if}}
|
|
283
348
|
}
|
|
284
349
|
|
|
@@ -286,9 +351,16 @@ func (ctrl *{{capitalize name}}Controller) Update(c *gin.Context) {
|
|
|
286
351
|
ctx := c.Request.Context()
|
|
287
352
|
id := c.Param("id")
|
|
288
353
|
var entity models.{{capitalize name}}
|
|
289
|
-
if
|
|
290
|
-
c.
|
|
291
|
-
|
|
354
|
+
if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
|
|
355
|
+
if err := c.ShouldBindWith(&entity, binding.MsgPack); err != nil {
|
|
356
|
+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
if err := c.ShouldBindJSON(&entity); err != nil {
|
|
361
|
+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
362
|
+
return
|
|
363
|
+
}
|
|
292
364
|
}
|
|
293
365
|
|
|
294
366
|
{{#if isDocument}}
|
|
@@ -320,7 +392,11 @@ func (ctrl *{{capitalize name}}Controller) Update(c *gin.Context) {
|
|
|
320
392
|
messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, "tenant", "UPDATE", "{{capitalize name}}", entity, nil)
|
|
321
393
|
{{/if}}
|
|
322
394
|
|
|
323
|
-
|
|
395
|
+
if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
|
|
396
|
+
c.Render(http.StatusOK, render.MsgPack{Data: entity})
|
|
397
|
+
} else {
|
|
398
|
+
c.JSON(http.StatusOK, entity)
|
|
399
|
+
}
|
|
324
400
|
}
|
|
325
401
|
|
|
326
402
|
func (ctrl *{{capitalize name}}Controller) Delete(c *gin.Context) {
|
|
@@ -356,9 +432,16 @@ func (ctrl *{{capitalize name}}Controller) Patch(c *gin.Context) {
|
|
|
356
432
|
ctx := c.Request.Context()
|
|
357
433
|
id := c.Param("id")
|
|
358
434
|
var updates map[string]interface{}
|
|
359
|
-
if
|
|
360
|
-
c.
|
|
361
|
-
|
|
435
|
+
if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
|
|
436
|
+
if err := c.ShouldBindWith(&updates, binding.MsgPack); err != nil {
|
|
437
|
+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
438
|
+
return
|
|
439
|
+
}
|
|
440
|
+
} else {
|
|
441
|
+
if err := c.ShouldBindJSON(&updates); err != nil {
|
|
442
|
+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
443
|
+
return
|
|
444
|
+
}
|
|
362
445
|
}
|
|
363
446
|
|
|
364
447
|
{{#if isDocument}}
|
|
@@ -383,7 +466,11 @@ func (ctrl *{{capitalize name}}Controller) Patch(c *gin.Context) {
|
|
|
383
466
|
messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, "tenant", "PATCH", "{{capitalize name}}", updates, nil)
|
|
384
467
|
{{/if}}
|
|
385
468
|
|
|
386
|
-
|
|
469
|
+
if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
|
|
470
|
+
c.Render(http.StatusOK, render.MsgPack{Data: gin.H{"message": "Patch successful"}})
|
|
471
|
+
} else {
|
|
472
|
+
c.JSON(http.StatusOK, gin.H{"message": "Patch successful"})
|
|
473
|
+
}
|
|
387
474
|
}
|
|
388
475
|
|
|
389
476
|
func (ctrl *{{capitalize name}}Controller) BulkCreate(c *gin.Context) { c.JSON(http.StatusNotImplemented, gin.H{"error": "Bulk operations use primary silo isolation by default."}) }
|
package/templates/go/main.go.hbs
CHANGED
|
@@ -14,6 +14,8 @@ import (
|
|
|
14
14
|
"{{app_name}}/integrations/wso2"
|
|
15
15
|
"gorm.io/driver/postgres"
|
|
16
16
|
"gorm.io/gorm"
|
|
17
|
+
"net/http"
|
|
18
|
+
"github.com/improbable-eng/grpc-web/go/grpcweb"
|
|
17
19
|
)
|
|
18
20
|
|
|
19
21
|
func main() {
|
|
@@ -40,6 +42,18 @@ func main() {
|
|
|
40
42
|
go func() {
|
|
41
43
|
grpcSrv := server.NewGRPCServer(appConfig, repo)
|
|
42
44
|
logger.Info("Starting Kratos gRPC server on %s", appConfig.GoDuck.Server.GRPC.Addr)
|
|
45
|
+
|
|
46
|
+
if appConfig.GoDuck.Server.GRPC.WebEnabled {
|
|
47
|
+
wrappedGrpc := grpcweb.WrapServer(grpcSrv.Server)
|
|
48
|
+
go func() {
|
|
49
|
+
webPort := fmt.Sprintf(":%d", appConfig.GoDuck.Server.GRPC.WebPort)
|
|
50
|
+
logger.Info("Starting gRPC-Web Proxy on %s", webPort)
|
|
51
|
+
if err := http.ListenAndServe(webPort, wrappedGrpc); err != nil {
|
|
52
|
+
logger.Error("Failed to start gRPC-Web Proxy: %v", err)
|
|
53
|
+
}
|
|
54
|
+
}()
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
if err := grpcSrv.Start(context.Background()); err != nil {
|
|
44
58
|
logger.Error("Failed to start Kratos gRPC server: %v", err)
|
|
45
59
|
}
|
|
@@ -56,7 +70,7 @@ func main() {
|
|
|
56
70
|
}
|
|
57
71
|
|
|
58
72
|
// 6. Start HTTP Server
|
|
59
|
-
port := fmt.Sprintf(":%d", appConfig.GoDuck.Server.Port)
|
|
60
|
-
logger.Info("Starting HTTP server on %s", port)
|
|
73
|
+
port := fmt.Sprintf(":%d", appConfig.GoDuck.Server.REST.Port)
|
|
74
|
+
logger.Info("Starting HTTP REST server on %s", port)
|
|
61
75
|
r.Run(port)
|
|
62
76
|
}
|
|
@@ -2,6 +2,7 @@ package service
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
|
+
"strings"
|
|
5
6
|
{{#if needFmt}}
|
|
6
7
|
"fmt"
|
|
7
8
|
{{/if}}
|
|
@@ -283,6 +284,15 @@ func (s *{{capitalize name}}Service) List{{capitalize name}}(ctx context.Context
|
|
|
283
284
|
for role, db := range siloConns {
|
|
284
285
|
var results []models.{{capitalize name}}
|
|
285
286
|
opts := options.Find().SetSkip(int64((req.Page - 1) * req.PageSize)).SetLimit(int64(req.PageSize))
|
|
287
|
+
if req.Sort != "" {
|
|
288
|
+
parts := strings.Split(req.Sort, ",")
|
|
289
|
+
field := parts[0]
|
|
290
|
+
order := 1
|
|
291
|
+
if len(parts) > 1 && strings.ToLower(parts[1]) == "desc" {
|
|
292
|
+
order = -1
|
|
293
|
+
}
|
|
294
|
+
opts.SetSort(bson.D{bson.E{Key: field, Value: order}})
|
|
295
|
+
}
|
|
286
296
|
cursor, err := db.Collection("{{toLowerCase name}}s").Find(ctx, bson.M{}, opts)
|
|
287
297
|
if err == nil {
|
|
288
298
|
cursor.All(ctx, &results)
|
|
@@ -300,6 +310,15 @@ func (s *{{capitalize name}}Service) List{{capitalize name}}(ctx context.Context
|
|
|
300
310
|
if ok {
|
|
301
311
|
var results []models.{{capitalize name}}
|
|
302
312
|
opts := options.Find().SetSkip(int64((req.Page - 1) * req.PageSize)).SetLimit(int64(req.PageSize))
|
|
313
|
+
if req.Sort != "" {
|
|
314
|
+
parts := strings.Split(req.Sort, ",")
|
|
315
|
+
field := parts[0]
|
|
316
|
+
order := 1
|
|
317
|
+
if len(parts) > 1 && strings.ToLower(parts[1]) == "desc" {
|
|
318
|
+
order = -1
|
|
319
|
+
}
|
|
320
|
+
opts.SetSort(bson.D{bson.E{Key: field, Value: order}})
|
|
321
|
+
}
|
|
303
322
|
cursor, err := db.Collection("{{toLowerCase name}}s").Find(ctx, bson.M{}, opts)
|
|
304
323
|
if err == nil {
|
|
305
324
|
cursor.All(ctx, &results)
|
|
@@ -319,7 +338,9 @@ func (s *{{capitalize name}}Service) List{{capitalize name}}(ctx context.Context
|
|
|
319
338
|
for role, db := range siloConns {
|
|
320
339
|
var results []models.{{capitalize name}}
|
|
321
340
|
query := db.WithContext(ctx).Model(&models.{{capitalize name}}{})
|
|
322
|
-
|
|
341
|
+
if req.Sort != "" {
|
|
342
|
+
query = query.Order(strings.ReplaceAll(req.Sort, ",", " "))
|
|
343
|
+
}
|
|
323
344
|
var count int64
|
|
324
345
|
query.Count(&count)
|
|
325
346
|
total += count
|
|
@@ -338,7 +359,9 @@ func (s *{{capitalize name}}Service) List{{capitalize name}}(ctx context.Context
|
|
|
338
359
|
if ok {
|
|
339
360
|
var results []models.{{capitalize name}}
|
|
340
361
|
query := db.WithContext(ctx).Model(&models.{{capitalize name}}{})
|
|
341
|
-
|
|
362
|
+
if req.Sort != "" {
|
|
363
|
+
query = query.Order(strings.ReplaceAll(req.Sort, ",", " "))
|
|
364
|
+
}
|
|
342
365
|
var count int64
|
|
343
366
|
query.Count(&count)
|
|
344
367
|
total = count
|