go-duck-cli 1.1.37 → 1.1.42
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/kratos.js +6 -3
- package/generators/multitenancy.js +10 -6
- package/generators/postman.js +13 -2
- package/generators/swagger.js +64 -2
- package/index.js +48 -0
- package/package.json +1 -1
- package/templates/go/controller.go.hbs +9 -4
- package/templates/go/router.go.hbs +21 -19
- package/templates/kratos/service.go.hbs +149 -24
package/generators/kratos.js
CHANGED
|
@@ -147,6 +147,8 @@ export const generateKratosCode = async (entities, projectRootDir, projectName,
|
|
|
147
147
|
annotation: entity.annotation,
|
|
148
148
|
isDocument: entity.isDocument || false,
|
|
149
149
|
isAudited: entity.isAudited || false,
|
|
150
|
+
isFederated: entity.isFederated || false,
|
|
151
|
+
needFmt: (entity.isDocument || false) || !(entity.isFederated || false),
|
|
150
152
|
projectName,
|
|
151
153
|
enums
|
|
152
154
|
};
|
|
@@ -278,7 +280,8 @@ import (
|
|
|
278
280
|
|
|
279
281
|
func TenantServerInterceptor(conf *config.Config, db *gorm.DB) middleware.Middleware {
|
|
280
282
|
mgr := my_middleware.GetTenantManager(db, conf)
|
|
281
|
-
fallbackDB :=
|
|
283
|
+
fallbackDB := conf.GoDuck.Datasource.Database
|
|
284
|
+
fallbackMongoDB := conf.GoDuck.Datasource.MongoDB.Database
|
|
282
285
|
|
|
283
286
|
return func(handler middleware.Handler) middleware.Handler {
|
|
284
287
|
return func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
@@ -342,9 +345,9 @@ func TenantServerInterceptor(conf *config.Config, db *gorm.DB) middleware.Middle
|
|
|
342
345
|
ctx = context.WithValue(ctx, "tenantDBConn", conn)
|
|
343
346
|
|
|
344
347
|
if conf.GoDuck.Datasource.MongoDB.Enabled {
|
|
345
|
-
mClient, err := mgr.GetMongoClient(
|
|
348
|
+
mClient, err := mgr.GetMongoClient(fallbackMongoDB)
|
|
346
349
|
if err == nil && mClient != nil {
|
|
347
|
-
mDB := mClient.Database(
|
|
350
|
+
mDB := mClient.Database(fallbackMongoDB)
|
|
348
351
|
mongoConnections["fallback"] = mDB
|
|
349
352
|
ctx = context.WithValue(ctx, "tenantMongoDB", mDB)
|
|
350
353
|
}
|
|
@@ -129,7 +129,8 @@ func (m *TenantDBManager) GetMongoClient(dbName string) (*mongo.Client, error) {
|
|
|
129
129
|
|
|
130
130
|
func TenantMiddleware(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
|
131
131
|
mgr := GetTenantManager(db, cfg)
|
|
132
|
-
fallbackDB :=
|
|
132
|
+
fallbackDB := cfg.GoDuck.Datasource.Database
|
|
133
|
+
fallbackMongoDB := cfg.GoDuck.Datasource.MongoDB.Database
|
|
133
134
|
|
|
134
135
|
return func(c *gin.Context) {
|
|
135
136
|
userRolesInterface, exists := c.Get("UserRoles")
|
|
@@ -207,13 +208,13 @@ func TenantMiddleware(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
|
|
207
208
|
c.Set("tenantDBConn", conn)
|
|
208
209
|
|
|
209
210
|
if cfg.GoDuck.Datasource.MongoDB.Enabled {
|
|
210
|
-
mClient, err := mgr.GetMongoClient(
|
|
211
|
+
mClient, err := mgr.GetMongoClient(fallbackMongoDB)
|
|
211
212
|
if err != nil || mClient == nil {
|
|
212
213
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to resolve tenant fallback MongoDB connection"})
|
|
213
214
|
c.Abort()
|
|
214
215
|
return
|
|
215
216
|
}
|
|
216
|
-
mDB := mClient.Database(
|
|
217
|
+
mDB := mClient.Database(fallbackMongoDB)
|
|
217
218
|
mongoConnections["fallback"] = mDB
|
|
218
219
|
c.Set("tenantMongoDB", mDB)
|
|
219
220
|
}
|
|
@@ -285,7 +286,8 @@ func TenantMiddleware(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
|
|
285
286
|
|
|
286
287
|
func PublicTenantMiddleware(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
|
287
288
|
mgr := GetTenantManager(db, cfg)
|
|
288
|
-
fallbackDB :=
|
|
289
|
+
fallbackDB := cfg.GoDuck.Datasource.Database
|
|
290
|
+
fallbackMongoDB := cfg.GoDuck.Datasource.MongoDB.Database
|
|
289
291
|
|
|
290
292
|
return func(c *gin.Context) {
|
|
291
293
|
requestedTenantRaw := strings.ToLower(c.GetHeader("X-Tenant-ID"))
|
|
@@ -308,8 +310,10 @@ func PublicTenantMiddleware(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
|
|
308
310
|
}
|
|
309
311
|
|
|
310
312
|
dbName := fallbackDB
|
|
313
|
+
mongoDbName := fallbackMongoDB
|
|
311
314
|
if len(mappings) > 0 {
|
|
312
315
|
dbName = mappings[0].DBName
|
|
316
|
+
mongoDbName = mappings[0].DBName
|
|
313
317
|
}
|
|
314
318
|
|
|
315
319
|
tenantConn, err := mgr.GetDB(dbName)
|
|
@@ -322,9 +326,9 @@ func PublicTenantMiddleware(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
|
|
322
326
|
c.Set("tenantDBConn", tenantConn)
|
|
323
327
|
|
|
324
328
|
if cfg.GoDuck.Datasource.MongoDB.Enabled {
|
|
325
|
-
mClient, err := mgr.GetMongoClient(
|
|
329
|
+
mClient, err := mgr.GetMongoClient(mongoDbName)
|
|
326
330
|
if err == nil && mClient != nil {
|
|
327
|
-
c.Set("tenantMongoDB", mClient.Database(
|
|
331
|
+
c.Set("tenantMongoDB", mClient.Database(mongoDbName))
|
|
328
332
|
}
|
|
329
333
|
}
|
|
330
334
|
|
package/generators/postman.js
CHANGED
|
@@ -140,11 +140,11 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
|
|
|
140
140
|
{ key: "X-Tenant-ID", value: "{{tenant}}" }
|
|
141
141
|
],
|
|
142
142
|
url: {
|
|
143
|
-
raw: "http://{{host}}:{{port}}/api/audit",
|
|
143
|
+
raw: "http://{{host}}:{{port}}/api/admin/audit",
|
|
144
144
|
protocol: "http",
|
|
145
145
|
host: ["{{host}}"],
|
|
146
146
|
port: "{{port}}",
|
|
147
|
-
path: ["api", "audit"]
|
|
147
|
+
path: ["api", "admin", "audit"]
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
},
|
|
@@ -305,6 +305,17 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
|
|
|
305
305
|
}
|
|
306
306
|
];
|
|
307
307
|
|
|
308
|
+
if (entity.isSearchable && config.elasticsearch?.enabled) {
|
|
309
|
+
privateItems.push({
|
|
310
|
+
name: `Elasticsearch Search (${capitalized})`,
|
|
311
|
+
request: {
|
|
312
|
+
method: "GET",
|
|
313
|
+
header: [ { key: "Authorization", value: "Bearer {{token}}" }, { key: "X-Tenant-ID", value: "{{tenant}}" } ],
|
|
314
|
+
url: { raw: `http://{{host}}:{{port}}/api/search/${name}?q=Sample`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: ["api", "search", name], query: [ { key: "q", value: "Sample" } ] }
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
308
319
|
folder.item.push({ name: "Private (Auth Required) CRUD", item: privateItems });
|
|
309
320
|
restFolder.item.push(folder);
|
|
310
321
|
}
|
package/generators/swagger.js
CHANGED
|
@@ -208,7 +208,7 @@ export const generateSwaggerDocs = async (config, entities, outputDir, openEntit
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
// 2. Add System Paths
|
|
211
|
-
swagger.paths['/rpc/{table}'] = {
|
|
211
|
+
swagger.paths['/api/rpc/{table}'] = {
|
|
212
212
|
get: {
|
|
213
213
|
tags: ['Search Engine'],
|
|
214
214
|
summary: 'Generic PostgREST RPC Engine',
|
|
@@ -240,7 +240,7 @@ export const generateSwaggerDocs = async (config, entities, outputDir, openEntit
|
|
|
240
240
|
}
|
|
241
241
|
};
|
|
242
242
|
|
|
243
|
-
swagger.paths['/audit'] = {
|
|
243
|
+
swagger.paths['/api/admin/audit'] = {
|
|
244
244
|
get: {
|
|
245
245
|
tags: ['Observability'],
|
|
246
246
|
summary: 'Fetch Audit Trail',
|
|
@@ -249,6 +249,68 @@ export const generateSwaggerDocs = async (config, entities, outputDir, openEntit
|
|
|
249
249
|
}
|
|
250
250
|
};
|
|
251
251
|
|
|
252
|
+
swagger.paths['/management/tenant/assign'] = {
|
|
253
|
+
post: {
|
|
254
|
+
tags: ['Management'],
|
|
255
|
+
summary: 'Provision and Assign Tenant Database',
|
|
256
|
+
description: 'Creates a dedicated database for the specified tenant and runs all migrations.',
|
|
257
|
+
requestBody: {
|
|
258
|
+
required: true,
|
|
259
|
+
content: {
|
|
260
|
+
'application/json': {
|
|
261
|
+
schema: {
|
|
262
|
+
type: 'object',
|
|
263
|
+
properties: {
|
|
264
|
+
roleName: { type: 'string', example: 'tenant_1_admin', description: 'The realm role name mapped to this database context' },
|
|
265
|
+
dbName: { type: 'string', example: 'tenant_1', description: 'The physical name of the database to create and provision' }
|
|
266
|
+
},
|
|
267
|
+
required: ['roleName', 'dbName']
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
parameters: [
|
|
273
|
+
{ name: 'X-Tenant-ID', in: 'header', required: true, schema: { type: 'string', default: 'master_internal' }, description: 'SuperAdmin internal master bypass token' }
|
|
274
|
+
],
|
|
275
|
+
responses: {
|
|
276
|
+
200: { description: 'Success' },
|
|
277
|
+
401: { description: 'Unauthorized' },
|
|
278
|
+
403: { description: 'Forbidden (Requires SuperAdmin role)' }
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
if (config.elasticsearch?.enabled) {
|
|
284
|
+
swagger.paths['/api/search/{entity}'] = {
|
|
285
|
+
get: {
|
|
286
|
+
tags: ['Search Engine'],
|
|
287
|
+
summary: 'Elasticsearch Global Search',
|
|
288
|
+
description: 'High-performance full-text search with fuzzy matching across federated silos using Elasticsearch.',
|
|
289
|
+
parameters: [
|
|
290
|
+
...commonHeaders,
|
|
291
|
+
{ name: 'entity', in: 'path', required: true, schema: { type: 'string' }, description: 'The entity name to search (e.g., institute)' },
|
|
292
|
+
{ name: 'q', in: 'query', schema: { type: 'string' }, description: 'The search query (Spring-style fuzzy match)' }
|
|
293
|
+
],
|
|
294
|
+
responses: {
|
|
295
|
+
200: {
|
|
296
|
+
description: 'OK',
|
|
297
|
+
content: {
|
|
298
|
+
'application/json': {
|
|
299
|
+
schema: {
|
|
300
|
+
type: 'object',
|
|
301
|
+
properties: {
|
|
302
|
+
total: { type: 'integer', description: 'Total search hits matching query' },
|
|
303
|
+
hits: { type: 'array', items: { type: 'object' }, description: 'Search hits (matching source documents)' }
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
252
314
|
await fs.writeJson(path.join(docsDir, 'swagger.json'), swagger, { spaces: 2 });
|
|
253
315
|
console.log(chalk.gray(' Generated Swagger Documentation: swagger.json'));
|
|
254
316
|
};
|
package/index.js
CHANGED
|
@@ -642,6 +642,47 @@ program
|
|
|
642
642
|
if (await fs.pathExists(p)) await fs.remove(p);
|
|
643
643
|
}
|
|
644
644
|
|
|
645
|
+
const goModPath = path.join(absoluteOutputDir, 'go.mod');
|
|
646
|
+
if (!await fs.pathExists(goModPath)) {
|
|
647
|
+
const goModContent = `module ${config.name}
|
|
648
|
+
|
|
649
|
+
go 1.24
|
|
650
|
+
|
|
651
|
+
require (
|
|
652
|
+
github.com/gin-gonic/gin v1.10.0
|
|
653
|
+
github.com/go-kratos/kratos/v2 v2.8.2
|
|
654
|
+
github.com/google/uuid v1.6.0
|
|
655
|
+
github.com/spf13/viper v1.19.0
|
|
656
|
+
go.opentelemetry.io/otel v1.26.0
|
|
657
|
+
go.opentelemetry.io/otel/trace v1.26.0
|
|
658
|
+
go.opentelemetry.io/otel/sdk v1.26.0
|
|
659
|
+
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.51.0
|
|
660
|
+
google.golang.org/grpc v1.64.0
|
|
661
|
+
gorm.io/datatypes v1.2.0
|
|
662
|
+
gorm.io/driver/postgres v1.5.7
|
|
663
|
+
gorm.io/gorm v1.25.10
|
|
664
|
+
gorm.io/plugin/opentelemetry v0.1.5
|
|
665
|
+
github.com/golang-jwt/jwt/v5 v5.2.1
|
|
666
|
+
github.com/eclipse/paho.mqtt.golang v1.4.3
|
|
667
|
+
github.com/sony/gobreaker v0.5.0
|
|
668
|
+
github.com/redis/go-redis/v9 v9.5.1
|
|
669
|
+
github.com/gorilla/websocket v1.5.1
|
|
670
|
+
github.com/99designs/gqlgen v0.17.44
|
|
671
|
+
github.com/vektah/gqlparser/v2 v2.5.11
|
|
672
|
+
github.com/aws/aws-lambda-go v1.47.0
|
|
673
|
+
github.com/awslabs/aws-lambda-go-api-proxy v0.16.1
|
|
674
|
+
github.com/GoogleCloudPlatform/functions-framework-go v1.8.1
|
|
675
|
+
go.mongodb.org/mongo-driver v1.17.1
|
|
676
|
+
github.com/gin-contrib/cors v1.7.1
|
|
677
|
+
github.com/go-resty/resty/v2 v2.16.0
|
|
678
|
+
github.com/olivere/elastic/v7 v7.0.32
|
|
679
|
+
github.com/nats-io/nats.go v1.38.0
|
|
680
|
+
)
|
|
681
|
+
`;
|
|
682
|
+
await fs.writeFile(goModPath, goModContent);
|
|
683
|
+
console.log(chalk.green('✅ go.mod file created!'));
|
|
684
|
+
}
|
|
685
|
+
|
|
645
686
|
await generateConfigLoader(absoluteOutputDir);
|
|
646
687
|
await generateLoggerCode(config, absoluteOutputDir);
|
|
647
688
|
await generateMQTTCode(config, absoluteOutputDir);
|
|
@@ -657,6 +698,13 @@ program
|
|
|
657
698
|
await generateGraphQLCode(config, entities, relationships, absoluteOutputDir, enums);
|
|
658
699
|
await generatePostgRESTCode(config, absoluteOutputDir);
|
|
659
700
|
|
|
701
|
+
if (config.multitenancy?.enabled) await generateMultitenancy(config, absoluteOutputDir, entities);
|
|
702
|
+
await generateAuditCode(config, absoluteOutputDir);
|
|
703
|
+
await generateMeteringCode(config, absoluteOutputDir);
|
|
704
|
+
await generateOutbox(absoluteOutputDir, config);
|
|
705
|
+
await generateNATSCode(config, absoluteOutputDir);
|
|
706
|
+
await generateBrokerCode(config, absoluteOutputDir);
|
|
707
|
+
|
|
660
708
|
// Sync Search & Security
|
|
661
709
|
await generateElasticsearchLayer(config, entities, absoluteOutputDir);
|
|
662
710
|
await generateSecurityMiddleware(config, absoluteOutputDir);
|
package/package.json
CHANGED
|
@@ -147,6 +147,11 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
|
|
|
147
147
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "0"))
|
|
148
148
|
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
|
149
149
|
|
|
150
|
+
offset := 0
|
|
151
|
+
if page > 0 {
|
|
152
|
+
offset = (page - 1) * size
|
|
153
|
+
}
|
|
154
|
+
|
|
150
155
|
{{#if isDocument}}
|
|
151
156
|
// 🦆 MongoDB Path
|
|
152
157
|
{{#if isFederated}}
|
|
@@ -163,7 +168,7 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
|
|
|
163
168
|
go func(r string, db *mongo.Database) {
|
|
164
169
|
defer wg.Done()
|
|
165
170
|
var entities []models.{{capitalize name}}
|
|
166
|
-
opts := options.Find().SetSkip(int64(
|
|
171
|
+
opts := options.Find().SetSkip(int64(offset)).SetLimit(int64(size))
|
|
167
172
|
cursor, err := db.Collection("{{toLowerCase name}}s").Find(ctx, bson.M{}, opts)
|
|
168
173
|
if err == nil {
|
|
169
174
|
cursor.All(ctx, &entities)
|
|
@@ -194,7 +199,7 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
|
|
|
194
199
|
c.Header("X-Total-Count", strconv.FormatInt(totalCount, 10))
|
|
195
200
|
|
|
196
201
|
var entities []models.{{capitalize name}}
|
|
197
|
-
opts := options.Find().SetSkip(int64(
|
|
202
|
+
opts := options.Find().SetSkip(int64(offset)).SetLimit(int64(size))
|
|
198
203
|
cursor, err := tenantDB.Collection("{{toLowerCase name}}s").Find(ctx, filter, opts)
|
|
199
204
|
if err != nil {
|
|
200
205
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
@@ -222,7 +227,7 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
|
|
|
222
227
|
go func(r string, db *gorm.DB) {
|
|
223
228
|
defer wg.Done()
|
|
224
229
|
var entities []models.{{capitalize name}}
|
|
225
|
-
if err := db.WithContext(ctx).Offset(
|
|
230
|
+
if err := db.WithContext(ctx).Offset(offset).Limit(size).Find(&entities).Error; err == nil {
|
|
226
231
|
mu.Lock()
|
|
227
232
|
federatedData[r] = entities
|
|
228
233
|
mu.Unlock()
|
|
@@ -243,7 +248,7 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
|
|
|
243
248
|
c.Header("X-Total-Count", strconv.FormatInt(totalCount, 10))
|
|
244
249
|
|
|
245
250
|
var entities []models.{{capitalize name}}
|
|
246
|
-
if err := tenantDB.WithContext(ctx).Offset(
|
|
251
|
+
if err := tenantDB.WithContext(ctx).Offset(offset).Limit(size).Find(&entities).Error; err != nil {
|
|
247
252
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
248
253
|
return
|
|
249
254
|
}
|
|
@@ -5,7 +5,6 @@ import (
|
|
|
5
5
|
"fmt"
|
|
6
6
|
"log"
|
|
7
7
|
"net/http"
|
|
8
|
-
"strings"
|
|
9
8
|
"time"
|
|
10
9
|
|
|
11
10
|
"github.com/gin-gonic/gin"
|
|
@@ -87,29 +86,32 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
|
|
|
87
86
|
logger.Error("Goose Migration failed: %v", err)
|
|
88
87
|
}
|
|
89
88
|
|
|
90
|
-
// 10b. Initialize and Migrate Fallback DB if multi-tenancy is enabled
|
|
89
|
+
// 10b. Initialize and Migrate Fallback DB if multi-tenancy is enabled and different from master DB
|
|
91
90
|
if appConfig.GoDuck.Multitenancy.Enabled {
|
|
92
|
-
fallbackDB :=
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
91
|
+
fallbackDB := appConfig.GoDuck.Datasource.Database
|
|
92
|
+
masterDBName := appConfig.GoDuck.Datasource.Database
|
|
93
|
+
if fallbackDB != masterDBName {
|
|
94
|
+
var exists int
|
|
95
|
+
masterDB.Raw("SELECT 1 FROM pg_database WHERE datname = ?", fallbackDB).Scan(&exists)
|
|
96
|
+
if exists == 0 {
|
|
97
|
+
if err := masterDB.Exec(fmt.Sprintf("CREATE DATABASE \"%s\"", fallbackDB)).Error; err != nil {
|
|
98
|
+
log.Printf("Warning: Failed to create fallback database %s: %v", fallbackDB, err)
|
|
99
|
+
} else {
|
|
100
|
+
log.Printf("✅ Created fallback database: %s", fallbackDB)
|
|
101
|
+
}
|
|
100
102
|
}
|
|
101
|
-
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
// Run tenant migrations on the fallback database
|
|
105
|
+
mgr := middleware.GetTenantManager(masterDB, appConfig)
|
|
106
|
+
if fDB, err := mgr.GetDB(fallbackDB); err == nil {
|
|
107
|
+
if err := migrations.RunGoNativeMigrationsForTenant(fDB); err != nil {
|
|
108
|
+
log.Printf("Warning: Failed to run migrations on fallback database: %v", err)
|
|
109
|
+
} else {
|
|
110
|
+
log.Printf("✅ Migrated fallback database: %s", fallbackDB)
|
|
111
|
+
}
|
|
108
112
|
} else {
|
|
109
|
-
log.Printf("
|
|
113
|
+
log.Printf("Warning: Failed to connect to fallback database for migration: %v", err)
|
|
110
114
|
}
|
|
111
|
-
} else {
|
|
112
|
-
log.Printf("Warning: Failed to connect to fallback database for migration: %v", err)
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
117
|
}
|
|
@@ -2,7 +2,7 @@ package service
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
|
-
{{#if
|
|
5
|
+
{{#if needFmt}}
|
|
6
6
|
"fmt"
|
|
7
7
|
{{/if}}
|
|
8
8
|
{{#if (hasNested fields)}}
|
|
@@ -38,10 +38,19 @@ func New{{capitalize name}}Service(repo *repository.Repository) *{{capitalize na
|
|
|
38
38
|
|
|
39
39
|
func (s *{{capitalize name}}Service) Create{{capitalize name}}(ctx context.Context, req *pb.Create{{capitalize name}}Request) (*pb.{{capitalize name}}Reply, error) {
|
|
40
40
|
{{#if isDocument}}
|
|
41
|
+
{{#if isFederated}}
|
|
42
|
+
siloConns, _ := ctx.Value("tenantMongoConnections").(map[string]*mongo.Database)
|
|
43
|
+
{{else}}
|
|
41
44
|
db, ok := ctx.Value("tenantMongoDB").(*mongo.Database)
|
|
42
45
|
if !ok { return nil, fmt.Errorf("mongo database not found in context") }
|
|
46
|
+
{{/if}}
|
|
43
47
|
{{else}}
|
|
48
|
+
{{#if isFederated}}
|
|
44
49
|
siloConns, _ := ctx.Value("tenantSiloConns").(map[string]*gorm.DB)
|
|
50
|
+
{{else}}
|
|
51
|
+
db, ok := ctx.Value("tenantDBConn").(*gorm.DB)
|
|
52
|
+
if !ok { return nil, fmt.Errorf("database not found in context") }
|
|
53
|
+
{{/if}}
|
|
45
54
|
{{/if}}
|
|
46
55
|
federatedData := make(map[string]*pb.{{capitalize name}})
|
|
47
56
|
|
|
@@ -67,17 +76,33 @@ func (s *{{capitalize name}}Service) Create{{capitalize name}}(ctx context.Conte
|
|
|
67
76
|
{{#each fields}}{{#if isNested}}
|
|
68
77
|
json.Unmarshal([]byte(req.{{toProtoFieldName name}}), &entity.{{capitalize name}})
|
|
69
78
|
{{/if}}{{/each}}
|
|
79
|
+
{{#if isFederated}}
|
|
80
|
+
for role, dbCon := range siloConns {
|
|
81
|
+
res, err := dbCon.Collection("{{toLowerCase name}}s").InsertOne(ctx, entity)
|
|
82
|
+
if err == nil {
|
|
83
|
+
entity.ID = fmt.Sprintf("%v", res.InsertedID)
|
|
84
|
+
federatedData[role] = map{{capitalize name}}ToPb(entity)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
{{else}}
|
|
70
88
|
res, err := db.Collection("{{toLowerCase name}}s").InsertOne(ctx, entity)
|
|
71
89
|
if err == nil {
|
|
72
90
|
entity.ID = fmt.Sprintf("%v", res.InsertedID)
|
|
73
91
|
federatedData["tenant"] = map{{capitalize name}}ToPb(entity)
|
|
74
92
|
}
|
|
93
|
+
{{/if}}
|
|
75
94
|
{{else}}
|
|
76
|
-
|
|
77
|
-
|
|
95
|
+
{{#if isFederated}}
|
|
96
|
+
for role, dbCon := range siloConns {
|
|
97
|
+
if err := dbCon.WithContext(ctx).Create(entity).Error; err == nil {
|
|
78
98
|
federatedData[role] = map{{capitalize name}}ToPb(entity)
|
|
79
99
|
}
|
|
80
100
|
}
|
|
101
|
+
{{else}}
|
|
102
|
+
if err := db.WithContext(ctx).Create(entity).Error; err == nil {
|
|
103
|
+
federatedData["tenant"] = map{{capitalize name}}ToPb(entity)
|
|
104
|
+
}
|
|
105
|
+
{{/if}}
|
|
81
106
|
{{/if}}
|
|
82
107
|
|
|
83
108
|
return &pb.{{capitalize name}}Reply{
|
|
@@ -89,12 +114,25 @@ func (s *{{capitalize name}}Service) Get{{capitalize name}}(ctx context.Context,
|
|
|
89
114
|
federatedData := make(map[string]*pb.{{capitalize name}})
|
|
90
115
|
|
|
91
116
|
{{#if isDocument}}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
117
|
+
{{#if isFederated}}
|
|
118
|
+
siloConns, _ := ctx.Value("tenantMongoConnections").(map[string]*mongo.Database)
|
|
119
|
+
for role, db := range siloConns {
|
|
120
|
+
var entity models.{{capitalize name}}
|
|
121
|
+
if err := db.Collection("{{toLowerCase name}}s").FindOne(ctx, bson.M{"_id": req.Id}).Decode(&entity); err == nil {
|
|
122
|
+
federatedData[role] = map{{capitalize name}}ToPb(&entity)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
{{else}}
|
|
126
|
+
db, ok := ctx.Value("tenantMongoDB").(*mongo.Database)
|
|
127
|
+
if ok {
|
|
128
|
+
var entity models.{{capitalize name}}
|
|
129
|
+
if err := db.Collection("{{toLowerCase name}}s").FindOne(ctx, bson.M{"_id": req.Id}).Decode(&entity); err == nil {
|
|
130
|
+
federatedData["tenant"] = map{{capitalize name}}ToPb(&entity)
|
|
131
|
+
}
|
|
96
132
|
}
|
|
133
|
+
{{/if}}
|
|
97
134
|
{{else}}
|
|
135
|
+
{{#if isFederated}}
|
|
98
136
|
siloConns, _ := ctx.Value("tenantSiloConns").(map[string]*gorm.DB)
|
|
99
137
|
for role, db := range siloConns {
|
|
100
138
|
var entity models.{{capitalize name}}
|
|
@@ -102,6 +140,15 @@ func (s *{{capitalize name}}Service) Get{{capitalize name}}(ctx context.Context,
|
|
|
102
140
|
federatedData[role] = map{{capitalize name}}ToPb(&entity)
|
|
103
141
|
}
|
|
104
142
|
}
|
|
143
|
+
{{else}}
|
|
144
|
+
db, ok := ctx.Value("tenantDBConn").(*gorm.DB)
|
|
145
|
+
if ok {
|
|
146
|
+
var entity models.{{capitalize name}}
|
|
147
|
+
if err := db.WithContext(ctx).First(&entity, req.Id).Error; err == nil {
|
|
148
|
+
federatedData["tenant"] = map{{capitalize name}}ToPb(&entity)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
{{/if}}
|
|
105
152
|
{{/if}}
|
|
106
153
|
|
|
107
154
|
return &pb.{{capitalize name}}Reply{
|
|
@@ -133,17 +180,37 @@ func (s *{{capitalize name}}Service) Update{{capitalize name}}(ctx context.Conte
|
|
|
133
180
|
{{/each}}
|
|
134
181
|
|
|
135
182
|
{{#if isDocument}}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
183
|
+
{{#if isFederated}}
|
|
184
|
+
siloConns, _ := ctx.Value("tenantMongoConnections").(map[string]*mongo.Database)
|
|
185
|
+
for role, db := range siloConns {
|
|
186
|
+
if _, err := db.Collection("{{toLowerCase name}}s").ReplaceOne(ctx, bson.M{"_id": req.Id}, entity); err == nil {
|
|
187
|
+
federatedData[role] = map{{capitalize name}}ToPb(&entity)
|
|
188
|
+
}
|
|
139
189
|
}
|
|
140
190
|
{{else}}
|
|
191
|
+
db, ok := ctx.Value("tenantMongoDB").(*mongo.Database)
|
|
192
|
+
if ok {
|
|
193
|
+
if _, err := db.Collection("{{toLowerCase name}}s").ReplaceOne(ctx, bson.M{"_id": req.Id}, entity); err == nil {
|
|
194
|
+
federatedData["tenant"] = map{{capitalize name}}ToPb(&entity)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
{{/if}}
|
|
198
|
+
{{else}}
|
|
199
|
+
{{#if isFederated}}
|
|
141
200
|
siloConns, _ := ctx.Value("tenantSiloConns").(map[string]*gorm.DB)
|
|
142
201
|
for role, db := range siloConns {
|
|
143
202
|
if err := db.WithContext(ctx).Model(&models.{{capitalize name}}{}).Where("id = ?", req.Id).Save(&entity).Error; err == nil {
|
|
144
203
|
federatedData[role] = map{{capitalize name}}ToPb(&entity)
|
|
145
204
|
}
|
|
146
205
|
}
|
|
206
|
+
{{else}}
|
|
207
|
+
db, ok := ctx.Value("tenantDBConn").(*gorm.DB)
|
|
208
|
+
if ok {
|
|
209
|
+
if err := db.WithContext(ctx).Model(&models.{{capitalize name}}{}).Where("id = ?", req.Id).Save(&entity).Error; err == nil {
|
|
210
|
+
federatedData["tenant"] = map{{capitalize name}}ToPb(&entity)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
{{/if}}
|
|
147
214
|
{{/if}}
|
|
148
215
|
|
|
149
216
|
return &pb.{{capitalize name}}Reply{
|
|
@@ -153,13 +220,29 @@ func (s *{{capitalize name}}Service) Update{{capitalize name}}(ctx context.Conte
|
|
|
153
220
|
|
|
154
221
|
func (s *{{capitalize name}}Service) Delete{{capitalize name}}(ctx context.Context, req *pb.Delete{{capitalize name}}Request) (*pb.Delete{{capitalize name}}Reply, error) {
|
|
155
222
|
{{#if isDocument}}
|
|
156
|
-
|
|
157
|
-
|
|
223
|
+
{{#if isFederated}}
|
|
224
|
+
siloConns, _ := ctx.Value("tenantMongoConnections").(map[string]*mongo.Database)
|
|
225
|
+
for _, db := range siloConns {
|
|
226
|
+
db.Collection("{{toLowerCase name}}s").DeleteOne(ctx, bson.M{"_id": req.Id})
|
|
227
|
+
}
|
|
228
|
+
{{else}}
|
|
229
|
+
db, ok := ctx.Value("tenantMongoDB").(*mongo.Database)
|
|
230
|
+
if ok {
|
|
231
|
+
db.Collection("{{toLowerCase name}}s").DeleteOne(ctx, bson.M{"_id": req.Id})
|
|
232
|
+
}
|
|
233
|
+
{{/if}}
|
|
158
234
|
{{else}}
|
|
235
|
+
{{#if isFederated}}
|
|
159
236
|
siloConns, _ := ctx.Value("tenantSiloConns").(map[string]*gorm.DB)
|
|
160
237
|
for _, db := range siloConns {
|
|
161
238
|
db.WithContext(ctx).Where("id = ?", req.Id).Delete(&models.{{capitalize name}}{})
|
|
162
239
|
}
|
|
240
|
+
{{else}}
|
|
241
|
+
db, ok := ctx.Value("tenantDBConn").(*gorm.DB)
|
|
242
|
+
if ok {
|
|
243
|
+
db.WithContext(ctx).Where("id = ?", req.Id).Delete(&models.{{capitalize name}}{})
|
|
244
|
+
}
|
|
245
|
+
{{/if}}
|
|
163
246
|
{{/if}}
|
|
164
247
|
return &pb.Delete{{capitalize name}}Reply{Message: "Delete completed"}, nil
|
|
165
248
|
}
|
|
@@ -169,21 +252,43 @@ func (s *{{capitalize name}}Service) List{{capitalize name}}(ctx context.Context
|
|
|
169
252
|
var total int64
|
|
170
253
|
|
|
171
254
|
{{#if isDocument}}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
cursor.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
items[
|
|
255
|
+
{{#if isFederated}}
|
|
256
|
+
siloConns, _ := ctx.Value("tenantMongoConnections").(map[string]*mongo.Database)
|
|
257
|
+
for role, db := range siloConns {
|
|
258
|
+
var results []models.{{capitalize name}}
|
|
259
|
+
opts := options.Find().SetSkip(int64((req.Page - 1) * req.PageSize)).SetLimit(int64(req.PageSize))
|
|
260
|
+
cursor, err := db.Collection("{{toLowerCase name}}s").Find(ctx, bson.M{}, opts)
|
|
261
|
+
if err == nil {
|
|
262
|
+
cursor.All(ctx, &results)
|
|
263
|
+
items := make([]*pb.{{capitalize name}}, len(results))
|
|
264
|
+
for i, r := range results {
|
|
265
|
+
items[i] = map{{capitalize name}}ToPb(&r)
|
|
266
|
+
}
|
|
267
|
+
federatedResults[role] = &pb.{{capitalize name}}List{Items: items}
|
|
268
|
+
count, _ := db.Collection("{{toLowerCase name}}s").CountDocuments(ctx, bson.M{})
|
|
269
|
+
total += count
|
|
181
270
|
}
|
|
182
|
-
federatedResults["tenant"] = &pb.{{capitalize name}}List{Items: items}
|
|
183
|
-
count, _ := db.Collection("{{toLowerCase name}}s").CountDocuments(ctx, bson.M{})
|
|
184
|
-
total = count
|
|
185
271
|
}
|
|
186
272
|
{{else}}
|
|
273
|
+
db, ok := ctx.Value("tenantMongoDB").(*mongo.Database)
|
|
274
|
+
if ok {
|
|
275
|
+
var results []models.{{capitalize name}}
|
|
276
|
+
opts := options.Find().SetSkip(int64((req.Page - 1) * req.PageSize)).SetLimit(int64(req.PageSize))
|
|
277
|
+
cursor, err := db.Collection("{{toLowerCase name}}s").Find(ctx, bson.M{}, opts)
|
|
278
|
+
if err == nil {
|
|
279
|
+
cursor.All(ctx, &results)
|
|
280
|
+
items := make([]*pb.{{capitalize name}}, len(results))
|
|
281
|
+
for i, r := range results {
|
|
282
|
+
items[i] = map{{capitalize name}}ToPb(&r)
|
|
283
|
+
}
|
|
284
|
+
federatedResults["tenant"] = &pb.{{capitalize name}}List{Items: items}
|
|
285
|
+
count, _ := db.Collection("{{toLowerCase name}}s").CountDocuments(ctx, bson.M{})
|
|
286
|
+
total = count
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
{{/if}}
|
|
290
|
+
{{else}}
|
|
291
|
+
{{#if isFederated}}
|
|
187
292
|
siloConns, _ := ctx.Value("tenantSiloConns").(map[string]*gorm.DB)
|
|
188
293
|
for role, db := range siloConns {
|
|
189
294
|
var results []models.{{capitalize name}}
|
|
@@ -202,6 +307,26 @@ func (s *{{capitalize name}}Service) List{{capitalize name}}(ctx context.Context
|
|
|
202
307
|
federatedResults[role] = &pb.{{capitalize name}}List{Items: items}
|
|
203
308
|
}
|
|
204
309
|
}
|
|
310
|
+
{{else}}
|
|
311
|
+
db, ok := ctx.Value("tenantDBConn").(*gorm.DB)
|
|
312
|
+
if ok {
|
|
313
|
+
var results []models.{{capitalize name}}
|
|
314
|
+
query := db.WithContext(ctx).Model(&models.{{capitalize name}}{})
|
|
315
|
+
|
|
316
|
+
var count int64
|
|
317
|
+
query.Count(&count)
|
|
318
|
+
total = count
|
|
319
|
+
|
|
320
|
+
offset := (req.Page - 1) * req.PageSize
|
|
321
|
+
if err := query.Limit(int(req.PageSize)).Offset(int(offset)).Find(&results).Error; err == nil {
|
|
322
|
+
items := make([]*pb.{{capitalize name}}, len(results))
|
|
323
|
+
for i, r := range results {
|
|
324
|
+
items[i] = map{{capitalize name}}ToPb(&r)
|
|
325
|
+
}
|
|
326
|
+
federatedResults["tenant"] = &pb.{{capitalize name}}List{Items: items}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
{{/if}}
|
|
205
330
|
{{/if}}
|
|
206
331
|
|
|
207
332
|
return &pb.List{{capitalize name}}Reply{
|