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.
- package/generators/multitenancy.js +11 -5
- package/generators/outbox.js +28 -6
- package/index.js +33 -11
- package/package.json +1 -1
- package/templates/docs/pages/gdl-relationships.hbs +13 -6
- package/templates/docs/pages/gdl.hbs +3 -1
- package/templates/docs/pages/hybrid-store.hbs +1 -1
- package/templates/docs/pages/index.hbs +3 -3
- package/templates/docs/pages/realtime.hbs +6 -6
- package/templates/docs/pages/wizard.hbs +1 -1
- package/templates/go/controller.go.hbs +33 -0
- package/templates/go/entity.go.hbs +12 -16
- package/templates/kratos/service.go.hbs +28 -2
|
@@ -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:"
|
|
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
|
}
|
package/generators/outbox.js
CHANGED
|
@@ -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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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}} \`
|
|
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
|
@@ -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
|
|
28
|
-
|
|
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
|
|
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
|
|
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
|
-
<
|
|
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>
|
|
@@ -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.
|
|
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
|
|
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
|
|
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 • THE
|
|
419
|
+
GO-DUCK • THE 380% ELITE MILESTONE • 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>
|
|
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/
|
|
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>
|
|
65
|
-
<pre class="text-xs bg-slate-900 text-white p-3 rounded"><code># Listen to any Car mutations
|
|
66
|
-
nats sub "
|
|
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">
|
|
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 `
|
|
19
|
-
CreatedDate time.Time `
|
|
20
|
-
LastModifiedBy string `
|
|
21
|
-
LastModifiedDate time.Time `
|
|
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 `
|
|
24
|
-
UpdatedAt time.Time `
|
|
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}} `
|
|
31
|
-
{{#unless
|
|
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
|
|
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
|
|
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}}
|