go-duck-cli 1.1.44 → 1.1.49

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.44",
3
+ "version": "1.1.49",
4
4
  "description": "The Ultimate Evolutionary Go Microservice Scaffolder.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -23,9 +23,10 @@
23
23
  <h2 class="text-3xl font-black mb-6 text-white m-0">The Social Network of Data</h2>
24
24
  <div class="bg-purple-800/50 backdrop-blur-md rounded-2xl p-8 border border-purple-500/30">
25
25
  <pre class="text-indigo-200 font-mono text-base leading-relaxed overflow-x-auto m-0">
26
- <span class="text-purple-400">// relationship Type Parent to Child</span>
27
- relationship 1:m Customer to Order
28
- relationship m:1 Order to Customer
26
+ <span class="text-purple-400">// relationship Type { Parent to Child }</span>
27
+ relationship OneToMany {
28
+ Customer{orders} to Order{customer}
29
+ }
29
30
  </pre>
30
31
  </div>
31
32
  <p class="mt-8 text-purple-200 text-sm italic font-medium leading-relaxed m-0 border-l-2 border-purple-400 pl-6">
@@ -42,13 +43,17 @@ relationship m:1 Order to Customer
42
43
  <div class="w-12 h-12 bg-purple-100 rounded-xl flex items-center justify-center mb-6 text-2xl group-hover:scale-110 transition-transform">👨‍👩‍👧</div>
43
44
  <h3 class="text-xl font-bold text-slate-900 mb-4 m-0 font-mono">1:m (One-to-Many)</h3>
44
45
  <p class="text-sm text-slate-600 leading-relaxed mb-6 italic">One parent entity owns multiple children. Perfect for Customers having many Orders.</p>
45
- <div class="p-4 bg-slate-50 rounded-xl font-mono text-xs text-purple-700 font-bold border border-slate-100">relationship 1:m Customer to Order</div>
46
+ <div class="p-4 bg-slate-50 rounded-xl font-mono text-xs text-purple-700 font-bold border border-slate-100"><pre class="m-0">relationship OneToMany {
47
+ Customer{orders} to Order{customer}
48
+ }</pre></div>
46
49
  </div>
47
50
  <div class="p-8 bg-white border border-slate-200 rounded-[2rem] shadow-sm hover:border-purple-200 transition-all group">
48
51
  <div class="w-12 h-12 bg-purple-100 rounded-xl flex items-center justify-center mb-6 text-2xl group-hover:scale-110 transition-transform">🔗</div>
49
52
  <h3 class="text-xl font-bold text-slate-900 mb-4 m-0 font-mono">m:1 (Many-to-One)</h3>
50
53
  <p class="text-sm text-slate-600 leading-relaxed mb-6 italic">Multiple entities reference a single shared resource. Multiple Car entities belonging to a single Manufacturer.</p>
51
- <div class="p-4 bg-slate-50 rounded-xl font-mono text-xs text-purple-700 font-bold border border-slate-100">relationship m:1 Car to Manufacturer</div>
54
+ <div class="p-4 bg-slate-50 rounded-xl font-mono text-xs text-purple-700 font-bold border border-slate-100"><pre class="m-0">relationship ManyToOne {
55
+ Car{manufacturer} to Manufacturer
56
+ }</pre></div>
52
57
  </div>
53
58
  </div>
54
59
  </section>
@@ -62,7 +67,9 @@ relationship m:1 Order to Customer
62
67
  <div>
63
68
  <h4 class="text-lg font-bold text-purple-900 mb-2 m-0 font-mono">required</h4>
64
69
  <p class="text-sm text-purple-800 leading-relaxed m-0 italic mb-4">By adding the `required` keyword to any relationship, GO-DUCK enforces strict existence checking at the database and API level.</p>
65
- <code class="px-3 py-1 bg-white text-purple-700 rounded-lg text-xs font-bold border border-purple-200">relationship 1:m Customer to Order required</code>
70
+ <div class="p-4 bg-white rounded-lg font-mono text-xs text-purple-700 font-bold border border-purple-200"><pre class="m-0">relationship OneToMany {
71
+ Customer{orders} to Order{customer} required
72
+ }</pre></div>
66
73
  </div>
67
74
  </div>
68
75
  </div>
@@ -55,7 +55,9 @@ entity Customer {
55
55
  }
56
56
 
57
57
  <span class="text-slate-500">// Define a directional relationship</span>
58
- relationship Customer 1:m Order
58
+ relationship OneToMany {
59
+ Customer{orders} to Order{customer}
60
+ }
59
61
  </pre>
60
62
  </div>
61
63
  </div>
@@ -56,7 +56,7 @@ entity Profile { <span class="text-slate-500">// MongoDB (document)</span>
56
56
  }</code></pre>
57
57
  </div>
58
58
  <p class="text-slate-400 text-sm italic leading-relaxed m-0 border-l border-emerald-500/30 pl-6">
59
- GO-DUCK automatically detects that <code>Profile</code> is NoSQL and <code>User</code> is SQL. It generates a <code>uint64</code> owner ID in MongoDB and a <code>string</code> profile pointer in SQL, handling all casting and <code>gorm:"-"</code> shielding automatically.
59
+ GO-DUCK automatically detects that <code>Profile</code> is NoSQL and <code>User</code> is SQL. To ensure <strong>100% structural parity</strong> between PostgreSQL and MongoDB, the generator now unconditionally injects both <code>gorm:"..."</code> and <code>bson:"..."</code> tags onto every generated Go struct. This allows any model to seamlessly fallback or migrate between engines without any code changes, while still handling dynamic ID typing (<code>string</code> vs <code>uint64</code>) automatically.
60
60
  </p>
61
61
  </div>
62
62
  </div>
@@ -16,7 +16,7 @@
16
16
  <span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
17
17
  <span class="relative inline-flex rounded-full h-3 w-3 bg-emerald-500"></span>
18
18
  </span>
19
- <p class="text-[11px] font-black text-indigo-900 uppercase tracking-[0.25em]">The 361% Elite Milestone Surpassed</p>
19
+ <p class="text-[11px] font-black text-indigo-900 uppercase tracking-[0.25em]">The 380% Elite Milestone Surpassed</p>
20
20
  </div>
21
21
 
22
22
  <!-- Heroic Logo Anchor (Enhanced) -->
@@ -59,7 +59,7 @@
59
59
  <div class="relative z-10 flex flex-col lg:flex-row items-center justify-between gap-12">
60
60
  <div class="max-w-xl text-center lg:text-left">
61
61
  <h3 class="text-[11px] font-black text-indigo-500 uppercase tracking-[0.4em] mb-4 text-center lg:text-left">Operational Readiness</h3>
62
- <h2 class="text-5xl font-black text-slate-900 tracking-tighter mb-6 italic">The 361% Paradigm Status.</h2>
62
+ <h2 class="text-5xl font-black text-slate-900 tracking-tighter mb-6 italic">The 380% Paradigm Status.</h2>
63
63
  <p class="text-slate-500 leading-relaxed font-medium">GO-DUCK has evolved from a simple generator into a high-performance distributed orchestrator, achieving industrial compliance across all silo ecosystems.</p>
64
64
  </div>
65
65
  <div class="grid grid-cols-2 gap-8 text-center bg-slate-50 p-8 rounded-[2rem] border border-slate-100 shadow-inner">
@@ -416,7 +416,7 @@
416
416
  </div>
417
417
 
418
418
  <div class="mt-24 text-slate-500 font-mono text-[10px] uppercase tracking-[0.6em] font-black relative z-10">
419
- GO-DUCK &bull; THE 361% ELITE MILESTONE &bull; SOVEREIGN CODE ORCHESTRATION
419
+ GO-DUCK &bull; THE 380% ELITE MILESTONE &bull; SOVEREIGN CODE ORCHESTRATION
420
420
  </div>
421
421
  </div>
422
422
  </section>
@@ -55,15 +55,15 @@ ws.onmessage = (event) => console.log("Received via WS:", event.data);</code></p
55
55
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
56
56
  <div class="bg-gray-50 p-6 rounded-xl border border-gray-200">
57
57
  <h4 class="font-bold text-blue-600 mb-2 font-mono">MQTT (Real-time UI)</h4>
58
- <p class="text-sm text-gray-600 mb-4">Topic Pattern: <code>go-duck/events/{entity}/{action}</code></p>
59
- <pre class="text-xs bg-slate-900 text-white p-3 rounded"><code># Listen to Car Deletions
60
- mosquitto_sub -t "go-duck/events/car/DELETE"</code></pre>
58
+ <p class="text-sm text-gray-600 mb-4">Topic Pattern: <code>{topicPrefix}/{tenantDB}/{entity}/{action}</code></p>
59
+ <pre class="text-xs bg-slate-900 text-white p-3 rounded"><code># Listen to Car Deletions in the tokyo tenant
60
+ mosquitto_sub -t "go-duck/events/tokyo_silo/Car/DELETE"</code></pre>
61
61
  </div>
62
62
  <div class="bg-gray-50 p-6 rounded-xl border border-gray-200">
63
63
  <h4 class="font-bold text-green-600 mb-2 font-mono">NATS (High-Perf CQRS)</h4>
64
- <p class="text-sm text-gray-600 mb-4">Subject Pattern: <code>go.duck.events.{entity}.{action}</code></p>
65
- <pre class="text-xs bg-slate-900 text-white p-3 rounded"><code># Listen to any Car mutations
66
- nats sub "go.duck.events.car.>"</code></pre>
64
+ <p class="text-sm text-gray-600 mb-4">Subject Pattern: <code>events.{tenantDB}.{entity}.{action}</code></p>
65
+ <pre class="text-xs bg-slate-900 text-white p-3 rounded"><code># Listen to any Car mutations across all silos
66
+ nats sub "events.*.Car.>"</code></pre>
67
67
  </div>
68
68
  </div>
69
69
  </section>
@@ -62,7 +62,7 @@
62
62
  <div class="flex-grow h-1.5 bg-slate-100 rounded-full overflow-hidden">
63
63
  <div class="w-[88%] h-full bg-indigo-500 shadow-[0_0_8px_rgba(99,102,241,0.5)]"></div>
64
64
  </div>
65
- <span class="text-[8px] font-black text-indigo-600 uppercase">361%</span>
65
+ <span class="text-[8px] font-black text-indigo-600 uppercase">380%</span>
66
66
  </div>
67
67
  </div>
68
68
  </div>
@@ -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}}