go-duck-cli 1.0.9 → 1.1.1
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 +30 -15
- package/generators/ai_docs.js +130 -0
- package/generators/broker.js +63 -0
- package/generators/config.js +149 -7
- package/generators/devops.js +210 -43
- package/generators/docs.js +23 -4
- package/generators/elasticsearch.js +263 -0
- package/generators/kratos.js +229 -41
- package/generators/metering.js +280 -48
- package/generators/migrations.js +92 -198
- package/generators/mqtt.js +2 -39
- package/generators/multitenancy.js +274 -71
- package/generators/nats.js +39 -0
- package/generators/outbox.js +171 -0
- package/generators/postgrest.js +7 -3
- package/generators/postman.js +405 -0
- package/generators/repository.js +27 -0
- package/generators/router.js +27 -0
- package/generators/security.js +95 -14
- package/generators/serverless.js +147 -0
- package/generators/storage.js +589 -0
- package/generators/swagger.js +84 -60
- package/generators/telemetry.js +23 -32
- package/generators/websocket.js +55 -21
- package/index.js +481 -116
- package/package.json +6 -4
- package/parser/gdl.js +163 -24
- package/templates/docs/index.html.hbs +5 -5
- package/templates/docs/layout.hbs +221 -62
- package/templates/docs/pages/audit.hbs +83 -35
- package/templates/docs/pages/cli.hbs +18 -0
- package/templates/docs/pages/configuration.hbs +241 -0
- package/templates/docs/pages/datadog.hbs +46 -0
- package/templates/docs/pages/elasticsearch.hbs +121 -0
- package/templates/docs/pages/federation.hbs +241 -0
- package/templates/docs/pages/gdl-advanced.hbs +91 -0
- package/templates/docs/pages/gdl-annotations.hbs +137 -0
- package/templates/docs/pages/gdl-entities.hbs +134 -0
- package/templates/docs/pages/gdl-relationships.hbs +80 -0
- package/templates/docs/pages/gdl.hbs +60 -204
- package/templates/docs/pages/graphql.hbs +58 -44
- package/templates/docs/pages/grpc.hbs +53 -90
- package/templates/docs/pages/hybrid-store.hbs +127 -0
- package/templates/docs/pages/index.hbs +418 -149
- package/templates/docs/pages/keycloak.hbs +43 -0
- package/templates/docs/pages/legend.hbs +116 -0
- package/templates/docs/pages/mosquitto.hbs +39 -0
- package/templates/docs/pages/multitenancy.hbs +139 -71
- package/templates/docs/pages/otel.hbs +40 -0
- package/templates/docs/pages/realtime.hbs +38 -12
- package/templates/docs/pages/redis.hbs +40 -0
- package/templates/docs/pages/rest.hbs +120 -202
- package/templates/docs/pages/saga.hbs +94 -0
- package/templates/docs/pages/security.hbs +150 -44
- package/templates/docs/pages/serverless.hbs +157 -0
- package/templates/docs/pages/storage.hbs +127 -0
- package/templates/docs/pages/wizard.hbs +683 -0
- package/templates/docs/triple_identity_registry.png +0 -0
- package/templates/go/controller.go.hbs +287 -283
- package/templates/go/entity.go.hbs +17 -15
- package/templates/go/main.go.hbs +47 -180
- package/templates/go/migrator.go.hbs +65 -0
- package/templates/go/router.go.hbs +272 -0
- package/templates/graphql/resolver.go.hbs +53 -34
- package/templates/graphql/schema.graphql.hbs +17 -5
- package/templates/kratos/service.go.hbs +169 -34
- package/templates/proto/entity.proto.hbs +10 -14
- package/test_nested.gdl +21 -0
- package/templates/docs/intro.mp4 +0 -0
- package/test_parser.js +0 -9
|
Binary file
|
|
@@ -1,339 +1,343 @@
|
|
|
1
1
|
package controllers
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
4
|
+
"net/http"
|
|
5
|
+
"strconv"
|
|
6
|
+
{{#if isSearchable}}
|
|
7
|
+
"context"
|
|
8
|
+
{{/if}}
|
|
9
|
+
{{#if (or isDocument isFederated)}}
|
|
10
|
+
"fmt"
|
|
11
|
+
{{/if}}
|
|
12
|
+
{{#if isFederated}}
|
|
13
|
+
"encoding/json"
|
|
14
|
+
"sync"
|
|
15
|
+
{{/if}}
|
|
16
|
+
|
|
17
|
+
"{{app_name}}/config"
|
|
18
|
+
"{{app_name}}/messaging"
|
|
19
|
+
"{{app_name}}/models"
|
|
20
|
+
"{{app_name}}/cache"
|
|
21
|
+
{{#if isSearchable}}
|
|
22
|
+
"{{app_name}}/internal/search"
|
|
23
|
+
{{/if}}
|
|
24
|
+
"github.com/gin-gonic/gin"
|
|
25
|
+
{{#if isDocument}}
|
|
26
|
+
"go.mongodb.org/mongo-driver/bson"
|
|
27
|
+
"go.mongodb.org/mongo-driver/mongo"
|
|
28
|
+
"go.mongodb.org/mongo-driver/mongo/options"
|
|
29
|
+
{{else}}
|
|
30
|
+
"gorm.io/gorm"
|
|
31
|
+
{{/if}}
|
|
16
32
|
)
|
|
17
33
|
|
|
18
34
|
type {{capitalize name}}Controller struct {
|
|
19
|
-
|
|
20
|
-
|
|
35
|
+
{{#if isDocument}}
|
|
36
|
+
MongoClient *mongo.Client
|
|
37
|
+
{{else}}
|
|
38
|
+
DB *gorm.DB
|
|
39
|
+
{{/if}}
|
|
40
|
+
Config *config.Config
|
|
21
41
|
}
|
|
22
42
|
|
|
23
|
-
// Create{{capitalize name}}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
db = tdb.(*gorm.DB)
|
|
27
|
-
}
|
|
28
|
-
|
|
43
|
+
// Create handles creating a new {{capitalize name}}
|
|
44
|
+
func (ctrl *{{capitalize name}}Controller) Create(c *gin.Context) {
|
|
45
|
+
ctx := c.Request.Context()
|
|
29
46
|
var entity models.{{capitalize name}}
|
|
30
47
|
if err := c.ShouldBindJSON(&entity); err != nil {
|
|
31
48
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
32
49
|
return
|
|
33
50
|
}
|
|
34
|
-
if err := 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
51
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
{{#if isDocument}}
|
|
53
|
+
// 🦆 MongoDB Path
|
|
54
|
+
db, _ := c.Get("tenantMongoDB")
|
|
55
|
+
tenantDB := db.(*mongo.Database)
|
|
56
|
+
collection := tenantDB.Collection("{{toLowerCase name}}s")
|
|
57
|
+
|
|
58
|
+
{{#if isFederated}}
|
|
59
|
+
// 🦆 Federated MongoDB Path
|
|
60
|
+
siloConns, _ := c.Get("tenantMongoConnections")
|
|
61
|
+
conns := siloConns.(map[string]*mongo.Database)
|
|
62
|
+
primaryRole, _ := c.Get("primaryRole")
|
|
63
|
+
firstRole := primaryRole.(string)
|
|
64
|
+
|
|
65
|
+
// Since MongoDB doesn't do cross-DB transactions easily, we record the Outbox in the Primary Postgres Silo
|
|
66
|
+
// (If Postgres is disabled, we'd use a Mongo-native outbox pool)
|
|
67
|
+
masterDB, _ := c.Get("tenantDBConn")
|
|
68
|
+
postgresDB := masterDB.(*gorm.DB)
|
|
69
|
+
|
|
70
|
+
err := postgresDB.Transaction(func(tx *gorm.DB) error {
|
|
71
|
+
res, err := collection.InsertOne(ctx, entity)
|
|
72
|
+
if err != nil { return err }
|
|
73
|
+
entity.ID = fmt.Sprintf("%v", res.InsertedID)
|
|
74
|
+
|
|
75
|
+
payload, _ := json.Marshal(entity)
|
|
76
|
+
for role := range conns {
|
|
77
|
+
if role == firstRole { continue }
|
|
78
|
+
tx.Create(&models.DistributedOutbox{
|
|
79
|
+
MutationType: "CREATE",
|
|
80
|
+
EntityName: "{{capitalize name}}",
|
|
81
|
+
EntityID: entity.ID,
|
|
82
|
+
Payload: string(payload),
|
|
83
|
+
TargetSilo: role,
|
|
84
|
+
SourceSilo: firstRole,
|
|
85
|
+
Status: "PENDING",
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
return nil
|
|
89
|
+
})
|
|
90
|
+
if err != nil {
|
|
91
|
+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Federated Mongo write failed: " + err.Error()})
|
|
92
|
+
return
|
|
56
93
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
|
64
|
-
query = query.Offset(page * size).Limit(size)
|
|
65
|
-
|
|
66
|
-
// 2. Eager Loading (Full Bodied) vs Lazy (IDs Only)
|
|
67
|
-
eager := c.Query("eager") == "true"
|
|
68
|
-
if eager {
|
|
69
|
-
{{#each relationships}}
|
|
70
|
-
query = query.Preload("{{capitalize from.field}}")
|
|
71
|
-
query = query.Preload("{{capitalize to.field}}")
|
|
72
|
-
{{/each}}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 3. Simple Filtering (Optimized for CRUD)
|
|
76
|
-
// Example: ?name=eq.John
|
|
77
|
-
for key, values := range c.Request.URL.Query() {
|
|
78
|
-
if key == "page" || key == "size" || key == "eager" || key == "sort" {
|
|
79
|
-
continue
|
|
80
|
-
}
|
|
81
|
-
for _, val := range values {
|
|
82
|
-
query = query.Where(key+" = ?", val)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if err := query.Find(&entities).Error; err != nil {
|
|
87
|
-
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
c.JSON(http.StatusOK, entities)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// GetByID
|
|
94
|
-
func (ctrl *{{capitalize name}}Controller) GetByID(c *gin.Context) {
|
|
95
|
-
id := c.Param("id")
|
|
96
|
-
tenant, _ := c.Get("tenantDB")
|
|
97
|
-
tenantStr := fmt.Sprintf("%v", tenant)
|
|
98
|
-
ctx := c.Request.Context()
|
|
99
|
-
|
|
100
|
-
// Tenant-Aware Cache Key
|
|
101
|
-
cacheKey := fmt.Sprintf("%s:{{capitalize name}}:%s", tenantStr, id)
|
|
102
|
-
|
|
103
|
-
var entity models.{{capitalize name}}
|
|
104
|
-
|
|
105
|
-
// 1. Check Distributed Cache (Redis)
|
|
106
|
-
if cache.Get(cacheKey, &entity) {
|
|
107
|
-
c.JSON(http.StatusOK, entity)
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// 2. Fallback to DB (With Context for Tracing)
|
|
112
|
-
db := ctrl.DB
|
|
113
|
-
if tdb, exists := c.Get("tenantDBConn"); exists {
|
|
114
|
-
db = tdb.(*gorm.DB)
|
|
94
|
+
messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, firstRole, "CREATE", "{{capitalize name}}", entity, nil)
|
|
95
|
+
{{else}}
|
|
96
|
+
res, err := collection.InsertOne(ctx, entity)
|
|
97
|
+
if err != nil {
|
|
98
|
+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
99
|
+
return
|
|
115
100
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
{{
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
101
|
+
entity.ID = fmt.Sprintf("%v", res.InsertedID)
|
|
102
|
+
messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, "tenant", "CREATE", "{{capitalize name}}", entity, nil)
|
|
103
|
+
{{/if}}
|
|
104
|
+
{{else}}
|
|
105
|
+
// 🦆 GORM Path
|
|
106
|
+
db, _ := c.Get("tenantDBConn")
|
|
107
|
+
tenantDB := db.(*gorm.DB)
|
|
108
|
+
{{#if isFederated}}
|
|
109
|
+
siloConns, _ := c.Get("tenantSiloConns")
|
|
110
|
+
conns := siloConns.(map[string]*gorm.DB)
|
|
111
|
+
primaryRole, _ := c.Get("primaryRole")
|
|
112
|
+
firstRole := primaryRole.(string)
|
|
113
|
+
|
|
114
|
+
err := tenantDB.Transaction(func(tx *gorm.DB) error {
|
|
115
|
+
if err := tx.WithContext(ctx).Create(&entity).Error; err != nil { return err }
|
|
116
|
+
payload, _ := json.Marshal(entity)
|
|
117
|
+
for role := range conns {
|
|
118
|
+
if role == firstRole { continue }
|
|
119
|
+
tx.Create(&models.DistributedOutbox{ MutationType: "CREATE", EntityName: "{{capitalize name}}", EntityID: fmt.Sprintf("%v", entity.ID), Payload: string(payload), TargetSilo: role, SourceSilo: firstRole, Status: "PENDING" })
|
|
120
|
+
}
|
|
121
|
+
return nil
|
|
122
|
+
})
|
|
123
|
+
if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}); return }
|
|
124
|
+
messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, firstRole, "CREATE", "{{capitalize name}}", entity, nil)
|
|
125
|
+
{{else}}
|
|
126
|
+
if err := tenantDB.WithContext(ctx).Create(&entity).Error; err != nil {
|
|
127
|
+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
128
|
+
return
|
|
142
129
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
prev := entity
|
|
152
|
-
|
|
153
|
-
if err := c.ShouldBindJSON(&entity); err != nil {
|
|
154
|
-
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if err := db.WithContext(ctx).Save(&entity).Error; err != nil {
|
|
159
|
-
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
160
|
-
return
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Cache Invalidation (Tenant Aware)
|
|
164
|
-
cache.Delete(fmt.Sprintf("%s:{{capitalize name}}:%s", tenantStr, id))
|
|
165
|
-
|
|
166
|
-
// MQTT Event (Resilient)
|
|
167
|
-
resilience.Execute(func() (interface{}, error) {
|
|
168
|
-
messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, "UPDATE", "{{capitalize name}}", entity, prev)
|
|
169
|
-
return nil, nil
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
c.JSON(http.StatusOK, entity)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Patch (PATCH) - Partial Update
|
|
176
|
-
db := ctrl.DB
|
|
177
|
-
if tdb, exists := c.Get("tenantDBConn"); exists {
|
|
178
|
-
db = tdb.(*gorm.DB)
|
|
130
|
+
messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, "tenant", "CREATE", "{{capitalize name}}", entity, nil)
|
|
131
|
+
{{/if}}
|
|
132
|
+
{{/if}}
|
|
133
|
+
|
|
134
|
+
cache.ClearPattern("*:{{capitalize name}}*")
|
|
135
|
+
{{#if isSearchable}}
|
|
136
|
+
if ctrl.Config.GoDuck.Elasticsearch.AutoSync {
|
|
137
|
+
go search.SyncToElasticsearch(context.Background(), "{{capitalize name}}", entity.ID, entity, ctrl.Config)
|
|
179
138
|
}
|
|
139
|
+
{{/if}}
|
|
180
140
|
|
|
181
|
-
|
|
182
|
-
if err := db.WithContext(ctx).First(&entity, id).Error; err != nil {
|
|
183
|
-
c.JSON(http.StatusNotFound, gin.H{"error": "Resource not found"})
|
|
184
|
-
return
|
|
185
|
-
}
|
|
186
|
-
prev := entity
|
|
187
|
-
|
|
188
|
-
var updates map[string]interface{}
|
|
189
|
-
if err := c.ShouldBindJSON(&updates); err != nil {
|
|
190
|
-
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if err := db.WithContext(ctx).Model(&entity).Updates(updates).Error; err != nil {
|
|
195
|
-
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
196
|
-
return
|
|
141
|
+
c.JSON(http.StatusCreated, entity)
|
|
197
142
|
}
|
|
198
143
|
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
144
|
+
// GetAll handles parallel read aggregation for both SQL and Mongo
|
|
145
|
+
func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
|
|
146
|
+
ctx := c.Request.Context()
|
|
147
|
+
page, _ := strconv.Atoi(c.DefaultQuery("page", "0"))
|
|
148
|
+
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
|
149
|
+
|
|
150
|
+
{{#if isDocument}}
|
|
151
|
+
// 🦆 MongoDB Path
|
|
152
|
+
{{#if isFederated}}
|
|
153
|
+
if c.DefaultQuery("federated", "false") == "true" {
|
|
154
|
+
siloConns, _ := c.Get("tenantMongoConnections")
|
|
155
|
+
conns := siloConns.(map[string]*mongo.Database)
|
|
156
|
+
federatedData := make(map[string][]models.{{capitalize name}})
|
|
157
|
+
|
|
158
|
+
var wg sync.WaitGroup
|
|
159
|
+
var mu sync.Mutex
|
|
160
|
+
|
|
161
|
+
for role, siloDB := range conns {
|
|
162
|
+
wg.Add(1)
|
|
163
|
+
go func(r string, db *mongo.Database) {
|
|
164
|
+
defer wg.Done()
|
|
165
|
+
var entities []models.{{capitalize name}}
|
|
166
|
+
opts := options.Find().SetSkip(int64(page * size)).SetLimit(int64(size))
|
|
167
|
+
cursor, err := db.Collection("{{toLowerCase name}}s").Find(ctx, bson.M{}, opts)
|
|
168
|
+
if err == nil {
|
|
169
|
+
cursor.All(ctx, &entities)
|
|
170
|
+
mu.Lock()
|
|
171
|
+
federatedData[r] = entities
|
|
172
|
+
mu.Unlock()
|
|
173
|
+
}
|
|
174
|
+
}(role, siloDB)
|
|
175
|
+
}
|
|
176
|
+
wg.Wait()
|
|
177
|
+
c.JSON(http.StatusOK, federatedData)
|
|
178
|
+
return
|
|
218
179
|
}
|
|
180
|
+
{{/if}}
|
|
219
181
|
|
|
182
|
+
db, _ := c.Get("tenantMongoDB")
|
|
183
|
+
tenantDB := db.(*mongo.Database)
|
|
220
184
|
var entities []models.{{capitalize name}}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
185
|
+
opts := options.Find().SetSkip(int64(page * size)).SetLimit(int64(size))
|
|
186
|
+
|
|
187
|
+
// PostgREST-lite Filter Translation (Simple eq check for now)
|
|
188
|
+
filter := bson.M{}
|
|
189
|
+
for k, v := range c.Request.URL.Query() {
|
|
190
|
+
if k != "page" && k != "size" && k != "federated" {
|
|
191
|
+
filter[k] = v[0]
|
|
192
|
+
}
|
|
224
193
|
}
|
|
225
194
|
|
|
226
|
-
|
|
195
|
+
cursor, err := tenantDB.Collection("{{toLowerCase name}}s").Find(ctx, filter, opts)
|
|
196
|
+
if err != nil {
|
|
227
197
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
228
198
|
return
|
|
229
199
|
}
|
|
200
|
+
cursor.All(ctx, &entities)
|
|
201
|
+
c.JSON(http.StatusOK, entities)
|
|
230
202
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
203
|
+
{{else}}
|
|
204
|
+
// 🦆 GORM Path
|
|
205
|
+
db, _ := c.Get("tenantDBConn")
|
|
206
|
+
tenantDB := db.(*gorm.DB)
|
|
207
|
+
|
|
208
|
+
{{#if isFederated}}
|
|
209
|
+
if c.DefaultQuery("federated", "false") == "true" {
|
|
210
|
+
siloConns, _ := c.Get("tenantSiloConns")
|
|
211
|
+
conns := siloConns.(map[string]*gorm.DB)
|
|
212
|
+
federatedData := make(map[string][]models.{{capitalize name}})
|
|
213
|
+
|
|
214
|
+
var wg sync.WaitGroup
|
|
215
|
+
var mu sync.Mutex
|
|
216
|
+
|
|
217
|
+
for role, siloDB := range conns {
|
|
218
|
+
wg.Add(1)
|
|
219
|
+
go func(r string, db *gorm.DB) {
|
|
220
|
+
defer wg.Done()
|
|
221
|
+
var entities []models.{{capitalize name}}
|
|
222
|
+
if err := db.WithContext(ctx).Offset(page * size).Limit(size).Find(&entities).Error; err == nil {
|
|
223
|
+
mu.Lock()
|
|
224
|
+
federatedData[r] = entities
|
|
225
|
+
mu.Unlock()
|
|
226
|
+
}
|
|
227
|
+
}(role, siloDB)
|
|
228
|
+
}
|
|
229
|
+
wg.Wait()
|
|
230
|
+
c.JSON(http.StatusOK, federatedData)
|
|
252
231
|
return
|
|
253
232
|
}
|
|
233
|
+
{{/if}}
|
|
254
234
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if err := tx.Save(&e).Error; err != nil {
|
|
258
|
-
return err
|
|
259
|
-
}
|
|
260
|
-
cache.Delete(fmt.Sprintf("%s:{{capitalize name}}:%d", tenantStr, e.ID))
|
|
261
|
-
}
|
|
262
|
-
return nil
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
if err != nil {
|
|
235
|
+
var entities []models.{{capitalize name}}
|
|
236
|
+
if err := tenantDB.WithContext(ctx).Offset(page * size).Limit(size).Find(&entities).Error; err != nil {
|
|
266
237
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
267
238
|
return
|
|
268
239
|
}
|
|
269
|
-
|
|
270
|
-
// MQTT Event (Resilient)
|
|
271
|
-
resilience.Execute(func() (interface{}, error) {
|
|
272
|
-
messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, "BULK_UPDATE", "{{capitalize name}}", entities, nil)
|
|
273
|
-
return nil, nil
|
|
274
|
-
})
|
|
275
|
-
|
|
276
240
|
c.JSON(http.StatusOK, entities)
|
|
241
|
+
{{/if}}
|
|
277
242
|
}
|
|
278
243
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
244
|
+
func (ctrl *{{capitalize name}}Controller) GetByID(c *gin.Context) {
|
|
245
|
+
ctx := c.Request.Context()
|
|
246
|
+
id := c.Param("id")
|
|
247
|
+
|
|
248
|
+
{{#if isDocument}}
|
|
249
|
+
db, _ := c.Get("tenantMongoDB")
|
|
250
|
+
tenantDB := db.(*mongo.Database)
|
|
251
|
+
var entity models.{{capitalize name}}
|
|
252
|
+
err := tenantDB.Collection("{{toLowerCase name}}s").FindOne(ctx, bson.M{"_id": id}).Decode(&entity)
|
|
253
|
+
if err != nil {
|
|
254
|
+
c.JSON(http.StatusNotFound, gin.H{"error": "Not found"})
|
|
255
|
+
return
|
|
283
256
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
257
|
+
c.JSON(http.StatusOK, entity)
|
|
258
|
+
{{else}}
|
|
259
|
+
db, _ := c.Get("tenantDBConn")
|
|
260
|
+
tenantDB := db.(*gorm.DB)
|
|
261
|
+
var entity models.{{capitalize name}}
|
|
262
|
+
if err := tenantDB.WithContext(ctx).First(&entity, id).Error; err != nil {
|
|
263
|
+
c.JSON(http.StatusNotFound, gin.H{"error": "Not found"})
|
|
264
|
+
return
|
|
288
265
|
}
|
|
289
|
-
|
|
266
|
+
c.JSON(http.StatusOK, entity)
|
|
267
|
+
{{/if}}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
func (ctrl *{{capitalize name}}Controller) Update(c *gin.Context) {
|
|
271
|
+
ctx := c.Request.Context()
|
|
272
|
+
id := c.Param("id")
|
|
273
|
+
var entity models.{{capitalize name}}
|
|
274
|
+
if err := c.ShouldBindJSON(&entity); err != nil {
|
|
290
275
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
291
276
|
return
|
|
292
277
|
}
|
|
293
278
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
cache.Delete(fmt.Sprintf("%s:{{capitalize name}}:%d", tenantStr, u.ID))
|
|
300
|
-
}
|
|
301
|
-
return nil
|
|
302
|
-
})
|
|
303
|
-
|
|
279
|
+
{{#if isDocument}}
|
|
280
|
+
db, _ := c.Get("tenantMongoDB")
|
|
281
|
+
tenantDB := db.(*mongo.Database)
|
|
282
|
+
_, err := tenantDB.Collection("{{toLowerCase name}}s").ReplaceOne(ctx, bson.M{"_id": id}, entity)
|
|
304
283
|
if err != nil {
|
|
305
284
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
306
285
|
return
|
|
307
286
|
}
|
|
308
|
-
|
|
309
|
-
c.
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if tdb, exists := c.Get("tenantDBConn"); exists {
|
|
315
|
-
db = tdb.(*gorm.DB)
|
|
287
|
+
{{else}}
|
|
288
|
+
db, _ := c.Get("tenantDBConn")
|
|
289
|
+
tenantDB := db.(*gorm.DB)
|
|
290
|
+
if err := tenantDB.WithContext(ctx).Model(&models.{{capitalize name}}{}).Where("id = ?", id).Save(&entity).Error; err != nil {
|
|
291
|
+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
292
|
+
return
|
|
316
293
|
}
|
|
294
|
+
{{/if}}
|
|
317
295
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
c.JSON(http.StatusNotFound, gin.H{"error": "Resource not found"})
|
|
321
|
-
return
|
|
296
|
+
cache.ClearPattern("*:{{capitalize name}}:" + id)
|
|
297
|
+
c.JSON(http.StatusOK, entity)
|
|
322
298
|
}
|
|
323
299
|
|
|
324
|
-
|
|
325
|
-
c.
|
|
326
|
-
|
|
300
|
+
func (ctrl *{{capitalize name}}Controller) Delete(c *gin.Context) {
|
|
301
|
+
ctx := c.Request.Context()
|
|
302
|
+
id := c.Param("id")
|
|
303
|
+
|
|
304
|
+
{{#if isDocument}}
|
|
305
|
+
db, _ := c.Get("tenantMongoDB")
|
|
306
|
+
tenantDB := db.(*mongo.Database)
|
|
307
|
+
tenantDB.Collection("{{toLowerCase name}}s").DeleteOne(ctx, bson.M{"_id": id})
|
|
308
|
+
{{else}}
|
|
309
|
+
db, _ := c.Get("tenantDBConn")
|
|
310
|
+
tenantDB := db.(*gorm.DB)
|
|
311
|
+
tenantDB.WithContext(ctx).Where("id = ?", id).Delete(&models.{{capitalize name}}{})
|
|
312
|
+
{{/if}}
|
|
313
|
+
|
|
314
|
+
cache.ClearPattern("*:{{capitalize name}}:" + id)
|
|
315
|
+
c.JSON(http.StatusNoContent, nil)
|
|
327
316
|
}
|
|
328
317
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
return
|
|
336
|
-
}
|
|
318
|
+
func (ctrl *{{capitalize name}}Controller) Patch(c *gin.Context) {
|
|
319
|
+
ctx := c.Request.Context()
|
|
320
|
+
id := c.Param("id")
|
|
321
|
+
var updates map[string]interface{}
|
|
322
|
+
if err := c.ShouldBindJSON(&updates); err != nil {
|
|
323
|
+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
324
|
+
return
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
{{#if isDocument}}
|
|
328
|
+
db, _ := c.Get("tenantMongoDB")
|
|
329
|
+
tenantDB := db.(*mongo.Database)
|
|
330
|
+
tenantDB.Collection("{{toLowerCase name}}s").UpdateOne(ctx, bson.M{"_id": id}, bson.M{"$set": updates})
|
|
331
|
+
{{else}}
|
|
332
|
+
db, _ := c.Get("tenantDBConn")
|
|
333
|
+
tenantDB := db.(*gorm.DB)
|
|
334
|
+
tenantDB.WithContext(ctx).Model(&models.{{capitalize name}}{}).Where("id = ?", id).Updates(updates)
|
|
335
|
+
{{/if}}
|
|
336
|
+
|
|
337
|
+
cache.ClearPattern("*:{{capitalize name}}:" + id)
|
|
338
|
+
c.JSON(http.StatusOK, gin.H{"message": "Patch successful"})
|
|
339
|
+
}
|
|
337
340
|
|
|
338
|
-
c.JSON(http.
|
|
339
|
-
}
|
|
341
|
+
func (ctrl *{{capitalize name}}Controller) BulkCreate(c *gin.Context) { c.JSON(http.StatusNotImplemented, gin.H{"error": "Bulk operations use primary silo isolation by default."}) }
|
|
342
|
+
func (ctrl *{{capitalize name}}Controller) BulkUpdate(c *gin.Context) { c.JSON(http.StatusNotImplemented, gin.H{"error": "Bulk operations use primary silo isolation by default."}) }
|
|
343
|
+
func (ctrl *{{capitalize name}}Controller) BulkPatch(c *gin.Context) { c.JSON(http.StatusNotImplemented, gin.H{"error": "Bulk operations use primary silo isolation by default."}) }
|