go-duck-cli 1.1.43 → 1.1.48

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.
@@ -405,15 +405,21 @@ func CreateDatabaseAndMigrate(masterDB *gorm.DB) gin.HandlerFunc {
405
405
  if req.TenantID != "" {
406
406
  existing.TenantID = req.TenantID
407
407
  }
408
- masterDB.Save(&existing)
408
+ if saveErr := masterDB.Save(&existing).Error; saveErr != nil {
409
+ c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to update role silo: %v", saveErr)})
410
+ return
411
+ }
409
412
  tenantID = existing.TenantID
410
413
  } else {
411
- masterDB.Create(&models.TenantRole{
414
+ if createErr := masterDB.Create(&models.TenantRole{
412
415
  TenantID: tenantID,
413
416
  RoleName: req.RoleName,
414
417
  DBName: req.DBName,
415
418
  IsPrimary: isPrimary,
416
- })
419
+ }).Error; createErr != nil {
420
+ c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to provision role silo: %v", createErr)})
421
+ return
422
+ }
417
423
  }
418
424
 
419
425
  // 4. Migrate
@@ -480,8 +486,8 @@ package models
480
486
  type TenantRole struct {
481
487
  ID uint \`gorm:"primaryKey" json:"id"\`
482
488
  TenantID string \`json:"tenantId" gorm:"unique;not null;index"\`
483
- RoleName string \`json:"roleName" gorm:"unique;not null"\`
484
- DBName string \`json:"dbName" gorm:"not null"\`
489
+ RoleName string \`json:"roleName" gorm:"not null;uniqueIndex:idx_role_db"\`
490
+ DBName string \`json:"dbName" gorm:"not null;uniqueIndex:idx_role_db"\`
485
491
  MongoDBName string \`json:"mongoDbName"\`
486
492
  IsPrimary bool \`json:"isPrimary" gorm:"default:false"\`
487
493
  }
@@ -42,8 +42,8 @@ import (
42
42
  "{{app_name}}/models"
43
43
  "{{app_name}}/config"
44
44
  "{{app_name}}/middleware"
45
- "gorm.io/gorm"
46
45
  "gorm.io/gorm/clause"
46
+ "gorm.io/gorm/schema"
47
47
  )
48
48
 
49
49
  type OutboxWorker struct {
@@ -122,11 +122,18 @@ func (w *OutboxWorker) markFailed(db *gorm.DB, item *models.DistributedOutbox, r
122
122
  }
123
123
 
124
124
  func (w *OutboxWorker) relayMutation(targetDB *gorm.DB, item models.DistributedOutbox) error {
125
+ var rawData map[string]interface{}
125
126
  var data map[string]interface{}
126
- if item.Payload != "" && item.MutationType != "DELETE" {
127
- if err := json.Unmarshal([]byte(item.Payload), &data); err != nil {
127
+ ns := schema.NamingStrategy{}
128
+
129
+ if item.Payload != "" && item.MutationType != "DELETE" && item.MutationType != "BULK_CREATE" && item.MutationType != "BULK_UPDATE" && item.MutationType != "BULK_PATCH" {
130
+ if err := json.Unmarshal([]byte(item.Payload), &rawData); err != nil {
128
131
  return fmt.Errorf("payload unmarshal failed: %v", err)
129
132
  }
133
+ data = make(map[string]interface{})
134
+ for k, v := range rawData {
135
+ data[ns.ColumnName("", k)] = v
136
+ }
130
137
  }
131
138
 
132
139
  table := strings.ToLower(item.EntityName)
@@ -139,15 +146,30 @@ func (w *OutboxWorker) relayMutation(targetDB *gorm.DB, item models.DistributedO
139
146
  case "DELETE":
140
147
  return targetDB.Table(table).Where("id = ?", item.EntityID).Delete(nil).Error
141
148
  case "BULK_CREATE":
149
+ var rawList []map[string]interface{}
150
+ json.Unmarshal([]byte(item.Payload), &rawList)
142
151
  var list []map[string]interface{}
143
- json.Unmarshal([]byte(item.Payload), &list)
152
+ for _, row := range rawList {
153
+ mappedRow := make(map[string]interface{})
154
+ for k, v := range row {
155
+ mappedRow[ns.ColumnName("", k)] = v
156
+ }
157
+ list = append(list, mappedRow)
158
+ }
144
159
  return targetDB.Table(table).Clauses(clause.OnConflict{DoNothing: true}).Create(list).Error
145
160
  case "BULK_UPDATE", "BULK_PATCH":
161
+ var rawList []map[string]interface{}
162
+ json.Unmarshal([]byte(item.Payload), &rawList)
146
163
  var list []map[string]interface{}
147
- json.Unmarshal([]byte(item.Payload), &list)
164
+ for _, row := range rawList {
165
+ mappedRow := make(map[string]interface{})
166
+ for k, v := range row {
167
+ mappedRow[ns.ColumnName("", k)] = v
168
+ }
169
+ list = append(list, mappedRow)
170
+ }
148
171
  err := targetDB.Transaction(func(tx *gorm.DB) error {
149
172
  for _, row := range list {
150
- // Identify ID for updates in bulk
151
173
  id := row["id"]
152
174
  if id == nil {
153
175
  id = row["ID"]
package/index.js CHANGED
@@ -82,7 +82,9 @@ type AuditLog struct {
82
82
  package middleware
83
83
 
84
84
  import (
85
+ "bytes"
85
86
  "fmt"
87
+ "io"
86
88
  "net/http"
87
89
  "time"
88
90
 
@@ -98,6 +100,12 @@ func AuditMiddleware(db *gorm.DB) gin.HandlerFunc {
98
100
  return
99
101
  }
100
102
 
103
+ var bodyBytes []byte
104
+ if c.Request.Body != nil {
105
+ bodyBytes, _ = io.ReadAll(c.Request.Body)
106
+ c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
107
+ }
108
+
101
109
  // Simplified auditing logic
102
110
  method := c.Request.Method
103
111
  path := c.Request.URL.Path
@@ -125,16 +133,30 @@ func AuditMiddleware(db *gorm.DB) gin.HandlerFunc {
125
133
  c.Next()
126
134
 
127
135
  // Logic to capture entity ID and snapshot values would go here...
128
- auditEntry := models.AuditLog{
129
- EntityName: path,
130
- TenantDB: tenantStr,
131
- Action: action,
132
- ModifiedBy: emailStr,
133
- KeycloakID: kidStr,
134
- ModifiedAt: time.Now(),
135
- ClientIP: clientIP,
136
- }
137
- db.Create(&auditEntry)
136
+ entityID := c.Param("id")
137
+ // If ID not in path param, try query param "id"
138
+ if entityID == "" {
139
+ entityID = c.Query("id")
140
+ }
141
+ var newValue string
142
+ newValue = string(bodyBytes)
143
+ // For CREATE actions, previous value is empty; for UPDATE/DELETE we could fetch old value later (placeholder)
144
+ if action == "UPDATE" || action == "DELETE" {
145
+ // Attempt to fetch previous record snapshot (basic placeholder implementation)
146
+ // This could be improved with model-specific hooks.
147
+ }
148
+ auditEntry := models.AuditLog{
149
+ EntityName: path,
150
+ EntityID: entityID,
151
+ TenantDB: tenantStr,
152
+ Action: action,
153
+ ModifiedBy: emailStr,
154
+ KeycloakID: kidStr,
155
+ ModifiedAt: time.Now(),
156
+ ClientIP: clientIP,
157
+ NewValue: newValue,
158
+ }
159
+ db.Create(&auditEntry)
138
160
  }
139
161
  }
140
162
  `;
@@ -287,7 +309,7 @@ Handlebars.registerPartial('renderFields', `
287
309
  {{> renderFields fields=children isDocument=../isDocument}}
288
310
  } \`json:"{{name}}"{{#if ../isDocument}} bson:"{{name}}"{{/if}}\`
289
311
  {{else}}
290
- {{capitalize name}} {{toGoType type}} \`{{#if ../isDocument}}bson:"{{#if (eq name "id")}}_id{{else}}{{name}}{{/if}}{{#if (eq name "id")}},omitempty{{/if}}"{{else}}{{#if (eq name "id")}}gorm:"primaryKey"{{else}}{{#if unique}}gorm:"uniqueIndex"{{/if}}{{/if}}{{/if}} {{#if (isJson type)}}gorm:"type:{{toLowerCase type}};serializer:json"{{/if}} json:"{{name}}"{{#if required}} binding:"required"{{/if}}\`
312
+ {{capitalize name}} {{toGoType type}} \`bson:"{{#if (eq name "id")}}_id{{else}}{{name}}{{/if}}{{#if (eq name "id")}},omitempty{{/if}}" {{#if (eq name "id")}}gorm:"primaryKey;column:id"{{else}}{{#if unique}}gorm:"uniqueIndex"{{/if}}{{/if}} {{#if (isJson type)}}gorm:"type:{{toLowerCase type}};serializer:json"{{/if}} json:"{{name}}"{{#if required}} binding:"required"{{/if}}\`
291
313
  {{/if}}
292
314
  {{/each}}
293
315
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "go-duck-cli",
3
- "version": "1.1.43",
3
+ "version": "1.1.48",
4
4
  "description": "The Ultimate Evolutionary Go Microservice Scaffolder.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -309,6 +309,17 @@ func (ctrl *{{capitalize name}}Controller) Update(c *gin.Context) {
309
309
  {{/if}}
310
310
 
311
311
  cache.ClearPattern("*:{{capitalize name}}:" + id)
312
+
313
+ {{#if isFederated}}
314
+ rolesInterface, _ := c.Get("tenantRoles")
315
+ roles := rolesInterface.([]string)
316
+ firstRole := "tenant"
317
+ if len(roles) > 0 { firstRole = roles[0] }
318
+ messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, firstRole, "UPDATE", "{{capitalize name}}", entity, nil)
319
+ {{else}}
320
+ messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, "tenant", "UPDATE", "{{capitalize name}}", entity, nil)
321
+ {{/if}}
322
+
312
323
  c.JSON(http.StatusOK, entity)
313
324
  }
314
325
 
@@ -327,6 +338,17 @@ func (ctrl *{{capitalize name}}Controller) Delete(c *gin.Context) {
327
338
  {{/if}}
328
339
 
329
340
  cache.ClearPattern("*:{{capitalize name}}:" + id)
341
+
342
+ {{#if isFederated}}
343
+ rolesInterface, _ := c.Get("tenantRoles")
344
+ roles := rolesInterface.([]string)
345
+ firstRole := "tenant"
346
+ if len(roles) > 0 { firstRole = roles[0] }
347
+ messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, firstRole, "DELETE", "{{capitalize name}}", map[string]string{"id": id}, nil)
348
+ {{else}}
349
+ messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, "tenant", "DELETE", "{{capitalize name}}", map[string]string{"id": id}, nil)
350
+ {{/if}}
351
+
330
352
  c.JSON(http.StatusNoContent, nil)
331
353
  }
332
354
 
@@ -350,6 +372,17 @@ func (ctrl *{{capitalize name}}Controller) Patch(c *gin.Context) {
350
372
  {{/if}}
351
373
 
352
374
  cache.ClearPattern("*:{{capitalize name}}:" + id)
375
+
376
+ {{#if isFederated}}
377
+ rolesInterface, _ := c.Get("tenantRoles")
378
+ roles := rolesInterface.([]string)
379
+ firstRole := "tenant"
380
+ if len(roles) > 0 { firstRole = roles[0] }
381
+ messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, firstRole, "PATCH", "{{capitalize name}}", updates, nil)
382
+ {{else}}
383
+ messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, "tenant", "PATCH", "{{capitalize name}}", updates, nil)
384
+ {{/if}}
385
+
353
386
  c.JSON(http.StatusOK, gin.H{"message": "Patch successful"})
354
387
  }
355
388
 
@@ -9,34 +9,30 @@ import (
9
9
 
10
10
  type {{name}} struct {
11
11
  {{#if isDocument}}
12
- ID string `json:"id" bson:"_id,omitempty"`
12
+ ID string `gorm:"primaryKey;column:id" json:"id" bson:"_id,omitempty"`
13
13
  {{else}}
14
- ID uint `gorm:"primaryKey" json:"id"`
14
+ ID uint `gorm:"primaryKey;column:id" json:"id" bson:"_id,omitempty"`
15
15
  {{/if}}
16
16
  {{> renderFields fields=fields isDocument=isDocument}}
17
17
  {{#if isAudited}}
18
- CreatedBy string `{{#if isDocument}}bson:"created_by"{{else}}gorm:"column:created_by"{{/if}} json:"createdBy"`
19
- CreatedDate time.Time `{{#if isDocument}}bson:"created_date"{{else}}gorm:"column:created_date"{{/if}} json:"createdDate"`
20
- LastModifiedBy string `{{#if isDocument}}bson:"last_modified_by"{{else}}gorm:"column:last_modified_by"{{/if}} json:"lastModifiedBy"`
21
- LastModifiedDate time.Time `{{#if isDocument}}bson:"last_modified_date"{{else}}gorm:"column:last_modified_date"{{/if}} json:"lastModifiedDate"`
18
+ CreatedBy string `gorm:"column:created_by" bson:"created_by" json:"createdBy"`
19
+ CreatedDate time.Time `gorm:"column:created_date" bson:"created_date" json:"createdDate"`
20
+ LastModifiedBy string `gorm:"column:last_modified_by" bson:"last_modified_by" json:"lastModifiedBy"`
21
+ LastModifiedDate time.Time `gorm:"column:last_modified_date" bson:"last_modified_date" json:"lastModifiedDate"`
22
22
  {{else}}
23
- CreatedAt time.Time `{{#if isDocument}}bson:"created_at"{{else}}gorm:"autoCreateTime"{{/if}} json:"createdAt"`
24
- UpdatedAt time.Time `{{#if isDocument}}bson:"updated_at"{{else}}gorm:"autoUpdateTime"{{/if}} json:"updatedAt"`
23
+ CreatedAt time.Time `gorm:"autoCreateTime;column:created_at" bson:"created_at" json:"createdAt"`
24
+ UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at" bson:"updated_at" json:"updatedAt"`
25
25
  {{/if}}
26
26
  {{#each relationships}}
27
27
  {{#if (eq from.entity ../name)}}
28
- {{capitalize from.field}} []{{capitalize to.entity}} `{{#unless ../isDocument}}{{#unless toIsDocument}}gorm:"foreignKey:{{capitalize to.field}}ID"{{else}}gorm:"-"{{/unless}}{{/unless}} json:"{{from.field}}" bson:"{{from.field}}"`
28
+ {{capitalize from.field}} []{{capitalize to.entity}} `{{#unless ../isDocument}}{{#unless toIsDocument}}gorm:"foreignKey:{{capitalize to.field}}ID"{{else}}gorm:"-"{{/unless}}{{else}}gorm:"-"{{/unless}} json:"{{from.field}}" bson:"{{from.field}}"`
29
29
  {{else}}
30
- {{capitalize to.field}}ID {{#if ../isDocument}}string{{else}}{{#if fromIsDocument}}string{{else}}*uint{{/if}}{{/if}} `{{#if ../isDocument}}bson:"{{toLowerCase to.field}}_id"{{else}}gorm:"column:{{to.field}}_id;index"{{/if}} json:"{{to.field}}Id"`
31
- {{#unless ../isDocument}}
32
- {{capitalize to.field}} *{{capitalize from.entity}} `{{#unless fromIsDocument}}gorm:"foreignKey:{{capitalize to.field}}ID"{{else}}gorm:"-"{{/unless}} json:"{{to.field}},omitempty"`
33
- {{/unless}}
30
+ {{capitalize to.field}}ID {{#if ../isDocument}}string{{else}}{{#if fromIsDocument}}string{{else}}*uint{{/if}}{{/if}} `gorm:"column:{{toLowerCase to.field}}_id;index" bson:"{{toLowerCase to.field}}_id" json:"{{to.field}}Id"`
31
+ {{capitalize to.field}} *{{capitalize from.entity}} `{{#unless fromIsDocument}}gorm:"foreignKey:{{capitalize to.field}}ID"{{else}}gorm:"-"{{/unless}} json:"{{to.field}},omitempty" bson:"-"`
34
32
  {{/if}}
35
33
  {{/each}}
36
34
  }
37
35
 
38
- {{#unless isDocument}}
39
36
  func ({{name}}) TableName() string {
40
37
  return "{{toLowerCase name}}"
41
- }
42
- {{/unless}}
38
+ }
@@ -12,7 +12,9 @@ import (
12
12
  "time"
13
13
  {{/if}}
14
14
  pb "{{projectName}}/api/v1"
15
+ "{{projectName}}/config"
15
16
  "{{projectName}}/internal/repository"
17
+ "{{projectName}}/messaging"
16
18
  "{{projectName}}/models"
17
19
  {{#if (hasJson fields)}}
18
20
  "gorm.io/datatypes"
@@ -105,6 +107,12 @@ func (s *{{capitalize name}}Service) Create{{capitalize name}}(ctx context.Conte
105
107
  {{/if}}
106
108
  {{/if}}
107
109
 
110
+ for role, pbEntity := range federatedData {
111
+ if config.AppConfig != nil {
112
+ messaging.PublishEvent(config.AppConfig.GoDuck.Messaging.MQTT.TopicPrefix, role, "CREATE", "{{capitalize name}}", pbEntity, nil)
113
+ }
114
+ }
115
+
108
116
  return &pb.{{capitalize name}}Reply{
109
117
  FederatedData: federatedData,
110
118
  }, nil
@@ -213,6 +221,12 @@ func (s *{{capitalize name}}Service) Update{{capitalize name}}(ctx context.Conte
213
221
  {{/if}}
214
222
  {{/if}}
215
223
 
224
+ for role, pbEntity := range federatedData {
225
+ if config.AppConfig != nil {
226
+ messaging.PublishEvent(config.AppConfig.GoDuck.Messaging.MQTT.TopicPrefix, role, "UPDATE", "{{capitalize name}}", pbEntity, nil)
227
+ }
228
+ }
229
+
216
230
  return &pb.{{capitalize name}}Reply{
217
231
  FederatedData: federatedData,
218
232
  }, nil
@@ -222,25 +236,37 @@ func (s *{{capitalize name}}Service) Delete{{capitalize name}}(ctx context.Conte
222
236
  {{#if isDocument}}
223
237
  {{#if isFederated}}
224
238
  siloConns, _ := ctx.Value("tenantMongoConnections").(map[string]*mongo.Database)
225
- for _, db := range siloConns {
239
+ for role, db := range siloConns {
226
240
  db.Collection("{{toLowerCase name}}s").DeleteOne(ctx, bson.M{"_id": req.Id})
241
+ if config.AppConfig != nil {
242
+ messaging.PublishEvent(config.AppConfig.GoDuck.Messaging.MQTT.TopicPrefix, role, "DELETE", "{{capitalize name}}", map[string]interface{}{"id": req.Id}, nil)
243
+ }
227
244
  }
228
245
  {{else}}
229
246
  db, ok := ctx.Value("tenantMongoDB").(*mongo.Database)
230
247
  if ok {
231
248
  db.Collection("{{toLowerCase name}}s").DeleteOne(ctx, bson.M{"_id": req.Id})
249
+ if config.AppConfig != nil {
250
+ messaging.PublishEvent(config.AppConfig.GoDuck.Messaging.MQTT.TopicPrefix, "tenant", "DELETE", "{{capitalize name}}", map[string]interface{}{"id": req.Id}, nil)
251
+ }
232
252
  }
233
253
  {{/if}}
234
254
  {{else}}
235
255
  {{#if isFederated}}
236
256
  siloConns, _ := ctx.Value("tenantSiloConns").(map[string]*gorm.DB)
237
- for _, db := range siloConns {
257
+ for role, db := range siloConns {
238
258
  db.WithContext(ctx).Where("id = ?", req.Id).Delete(&models.{{capitalize name}}{})
259
+ if config.AppConfig != nil {
260
+ messaging.PublishEvent(config.AppConfig.GoDuck.Messaging.MQTT.TopicPrefix, role, "DELETE", "{{capitalize name}}", map[string]interface{}{"id": req.Id}, nil)
261
+ }
239
262
  }
240
263
  {{else}}
241
264
  db, ok := ctx.Value("tenantDBConn").(*gorm.DB)
242
265
  if ok {
243
266
  db.WithContext(ctx).Where("id = ?", req.Id).Delete(&models.{{capitalize name}}{})
267
+ if config.AppConfig != nil {
268
+ messaging.PublishEvent(config.AppConfig.GoDuck.Messaging.MQTT.TopicPrefix, "tenant", "DELETE", "{{capitalize name}}", map[string]interface{}{"id": req.Id}, nil)
269
+ }
244
270
  }
245
271
  {{/if}}
246
272
  {{/if}}