go-duck-cli 1.1.48 → 1.2.0
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/config.js +12 -0
- package/generators/wso2.js +156 -0
- package/index.js +14 -0
- 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 +13 -3
- package/templates/docs/pages/integrations.hbs +21 -0
- package/templates/docs/pages/realtime.hbs +6 -6
- package/templates/docs/pages/wizard.hbs +1 -1
- package/templates/go/main.go.hbs +7 -1
- package/templates/go/router.go.hbs +6 -0
package/generators/config.js
CHANGED
|
@@ -207,6 +207,15 @@ type Config struct {
|
|
|
207
207
|
Enabled bool \`mapstructure:"enabled"\`
|
|
208
208
|
Provider string \`mapstructure:"provider"\`
|
|
209
209
|
} \`mapstructure:"serverless"\`
|
|
210
|
+
Integrations struct {
|
|
211
|
+
WSO2 struct {
|
|
212
|
+
Enabled bool \`mapstructure:"enabled"\`
|
|
213
|
+
PublisherURL string \`mapstructure:"publisher-url"\`
|
|
214
|
+
ClientID string \`mapstructure:"client-id"\`
|
|
215
|
+
ClientSecret string \`mapstructure:"client-secret"\`
|
|
216
|
+
GatewayEnvironments []string \`mapstructure:"gateway-environments"\`
|
|
217
|
+
} \`mapstructure:"wso2"\`
|
|
218
|
+
} \`mapstructure:"integrations"\`
|
|
210
219
|
} \`mapstructure:"go-duck"\`
|
|
211
220
|
Environment struct {
|
|
212
221
|
ActiveProfile string \`mapstructure:"active_profile"\`
|
|
@@ -267,6 +276,9 @@ func LoadConfig() (*Config, error) {
|
|
|
267
276
|
v.SetDefault("go-duck.serverless.provider", "aws")
|
|
268
277
|
v.SetDefault("go-duck.datasource.mongodb.enabled", false)
|
|
269
278
|
v.SetDefault("go-duck.datasource.mongodb.uri", "mongodb://localhost:27017")
|
|
279
|
+
v.SetDefault("go-duck.integrations.wso2.enabled", false)
|
|
280
|
+
v.SetDefault("go-duck.integrations.wso2.publisher-url", "https://localhost:9443/api/am/publisher/v3")
|
|
281
|
+
v.SetDefault("go-duck.integrations.wso2.gateway-environments", []string{"Production", "Sandbox"})
|
|
270
282
|
|
|
271
283
|
v.AutomaticEnv()
|
|
272
284
|
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
export const generateWSO2Integration = async (outputDir, config) => {
|
|
6
|
+
const integrationsDir = path.join(outputDir, 'integrations', 'wso2');
|
|
7
|
+
await fs.ensureDir(integrationsDir);
|
|
8
|
+
|
|
9
|
+
const wso2Go = `
|
|
10
|
+
package wso2
|
|
11
|
+
|
|
12
|
+
import (
|
|
13
|
+
"bytes"
|
|
14
|
+
"encoding/json"
|
|
15
|
+
"fmt"
|
|
16
|
+
"io"
|
|
17
|
+
"mime/multipart"
|
|
18
|
+
"net/http"
|
|
19
|
+
"os"
|
|
20
|
+
"path/filepath"
|
|
21
|
+
"time"
|
|
22
|
+
|
|
23
|
+
"${config.name || 'go-duck'}/config"
|
|
24
|
+
"${config.name || 'go-duck'}/logger"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
type TokenResponse struct {
|
|
28
|
+
AccessToken string \`json:"access_token"\`
|
|
29
|
+
Scope string \`json:"scope"\`
|
|
30
|
+
TokenType string \`json:"token_type"\`
|
|
31
|
+
ExpiresIn int \`json:"expires_in"\`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func RegisterAPI(appConfig *config.Config) error {
|
|
35
|
+
wso2Config := appConfig.GoDuck.Integrations.WSO2
|
|
36
|
+
if !wso2Config.Enabled {
|
|
37
|
+
return nil
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
logger.Info("Starting WSO2 APIM Publisher Registration...")
|
|
41
|
+
|
|
42
|
+
// 1. Authenticate to get OAuth2 Access Token
|
|
43
|
+
token, err := getAccessToken(wso2Config.PublisherURL, wso2Config.ClientID, wso2Config.ClientSecret)
|
|
44
|
+
if err != nil {
|
|
45
|
+
return fmt.Errorf("failed to authenticate with WSO2: %v", err)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 2. Locate swagger.json
|
|
49
|
+
swaggerPath := filepath.Join("docs", "swagger.json")
|
|
50
|
+
if _, err := os.Stat(swaggerPath); os.IsNotExist(err) {
|
|
51
|
+
return fmt.Errorf("swagger.json not found at %s. Cannot register with WSO2", swaggerPath)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 3. Register via /apis/import-openapi
|
|
55
|
+
err = importOpenAPI(wso2Config.PublisherURL, token, swaggerPath)
|
|
56
|
+
if err != nil {
|
|
57
|
+
return fmt.Errorf("failed to import OpenAPI to WSO2: %v", err)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
logger.Info("Successfully registered API with WSO2 Publisher!")
|
|
61
|
+
return nil
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func getAccessToken(publisherURL, clientID, clientSecret string) (string, error) {
|
|
65
|
+
// WSO2 DCR/Token endpoint is typically exposed at /oauth2/token on the Gateway or KM
|
|
66
|
+
// If the PublisherURL is https://localhost:9443/api/am/publisher/v3,
|
|
67
|
+
// Token URL is typically https://localhost:9443/oauth2/token
|
|
68
|
+
baseURL := publisherURL[:len(publisherURL)-len("/api/am/publisher/v3")]
|
|
69
|
+
tokenURL := baseURL + "/oauth2/token"
|
|
70
|
+
|
|
71
|
+
data := "grant_type=client_credentials&scope=apim:api_create apim:api_publish"
|
|
72
|
+
req, err := http.NewRequest("POST", tokenURL, bytes.NewBufferString(data))
|
|
73
|
+
if err != nil {
|
|
74
|
+
return "", err
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
78
|
+
req.SetBasicAuth(clientID, clientSecret)
|
|
79
|
+
|
|
80
|
+
client := &http.Client{Timeout: 10 * time.Second}
|
|
81
|
+
resp, err := client.Do(req)
|
|
82
|
+
if err != nil {
|
|
83
|
+
return "", err
|
|
84
|
+
}
|
|
85
|
+
defer resp.Body.Close()
|
|
86
|
+
|
|
87
|
+
if resp.StatusCode != http.StatusOK {
|
|
88
|
+
bodyBytes, _ := io.ReadAll(resp.Body)
|
|
89
|
+
return "", fmt.Errorf("token request failed with status %d: %s", resp.StatusCode, string(bodyBytes))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
var tokenRes TokenResponse
|
|
93
|
+
if err := json.NewDecoder(resp.Body).Decode(&tokenRes); err != nil {
|
|
94
|
+
return "", err
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return tokenRes.AccessToken, nil
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
func importOpenAPI(publisherURL, token, swaggerPath string) error {
|
|
101
|
+
importURL := publisherURL + "/apis/import-openapi"
|
|
102
|
+
|
|
103
|
+
file, err := os.Open(swaggerPath)
|
|
104
|
+
if err != nil {
|
|
105
|
+
return err
|
|
106
|
+
}
|
|
107
|
+
defer file.Close()
|
|
108
|
+
|
|
109
|
+
body := &bytes.Buffer{}
|
|
110
|
+
writer := multipart.NewWriter(body)
|
|
111
|
+
|
|
112
|
+
// Additional data fields
|
|
113
|
+
_ = writer.WriteField("additionalProperties", \`{"visibility":"PUBLIC"}\`)
|
|
114
|
+
|
|
115
|
+
// File field
|
|
116
|
+
part, err := writer.CreateFormFile("file", filepath.Base(swaggerPath))
|
|
117
|
+
if err != nil {
|
|
118
|
+
return err
|
|
119
|
+
}
|
|
120
|
+
_, err = io.Copy(part, file)
|
|
121
|
+
if err != nil {
|
|
122
|
+
return err
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
err = writer.Close()
|
|
126
|
+
if err != nil {
|
|
127
|
+
return err
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
req, err := http.NewRequest("POST", importURL, body)
|
|
131
|
+
if err != nil {
|
|
132
|
+
return err
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
req.Header.Set("Authorization", "Bearer "+token)
|
|
136
|
+
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
137
|
+
|
|
138
|
+
client := &http.Client{Timeout: 30 * time.Second}
|
|
139
|
+
resp, err := client.Do(req)
|
|
140
|
+
if err != nil {
|
|
141
|
+
return err
|
|
142
|
+
}
|
|
143
|
+
defer resp.Body.Close()
|
|
144
|
+
|
|
145
|
+
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
|
146
|
+
bodyBytes, _ := io.ReadAll(resp.Body)
|
|
147
|
+
return fmt.Errorf("import failed with status %d: %s", resp.StatusCode, string(bodyBytes))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return nil
|
|
151
|
+
}
|
|
152
|
+
`;
|
|
153
|
+
|
|
154
|
+
await fs.writeFile(path.join(integrationsDir, 'wso2.go'), wso2Go);
|
|
155
|
+
console.log(chalk.gray(' Generated WSO2 API Manager Integration client: integrations/wso2/wso2.go'));
|
|
156
|
+
};
|
package/index.js
CHANGED
|
@@ -46,6 +46,7 @@ import { generateElasticsearchLayer } from './generators/elasticsearch.js';
|
|
|
46
46
|
import { generateServerlessHandler } from './generators/serverless.js';
|
|
47
47
|
import { generateRouterCode } from './generators/router.js';
|
|
48
48
|
import { generateAIDocs } from './generators/ai_docs.js';
|
|
49
|
+
import { generateWSO2Integration } from './generators/wso2.js';
|
|
49
50
|
|
|
50
51
|
export const generateAuditCode = async (config, outputDir) => {
|
|
51
52
|
const middlewareDir = path.join(outputDir, 'middleware');
|
|
@@ -575,6 +576,10 @@ program
|
|
|
575
576
|
await generateAIDocs(config, entities, absoluteOutputDir, enums, openEntities);
|
|
576
577
|
console.log(chalk.green('🤖 System-level AI Blueprint Documentation generated!'));
|
|
577
578
|
|
|
579
|
+
// 8.7 Generate WSO2 API Manager Integration
|
|
580
|
+
await generateWSO2Integration(absoluteOutputDir, config);
|
|
581
|
+
console.log(chalk.green('🌐 WSO2 API Manager Integration Module generated!'));
|
|
582
|
+
|
|
578
583
|
// 9. Generate main.go
|
|
579
584
|
const mainTemplatePath = path.resolve(__dirname, 'templates/go/main.go.hbs');
|
|
580
585
|
if (await fs.pathExists(mainTemplatePath)) {
|
|
@@ -920,6 +925,15 @@ const generateYAMLConfigs = async (config, outputDir) => {
|
|
|
920
925
|
enabled: cleanConfig.serverless?.enabled || false,
|
|
921
926
|
provider: cleanConfig.serverless?.provider || 'aws'
|
|
922
927
|
},
|
|
928
|
+
integrations: {
|
|
929
|
+
wso2: {
|
|
930
|
+
enabled: cleanConfig.integrations?.wso2?.enabled || false,
|
|
931
|
+
'publisher-url': cleanConfig.integrations?.wso2?.['publisher-url'] || 'https://localhost:9443/api/am/publisher/v3',
|
|
932
|
+
'client-id': cleanConfig.integrations?.wso2?.['client-id'] || '',
|
|
933
|
+
'client-secret': cleanConfig.integrations?.wso2?.['client-secret'] || '',
|
|
934
|
+
'gateway-environments': cleanConfig.integrations?.wso2?.['gateway-environments'] || ['Production', 'Sandbox']
|
|
935
|
+
}
|
|
936
|
+
},
|
|
923
937
|
multitenancy: {
|
|
924
938
|
enabled: cleanConfig.multitenancy?.enabled || false,
|
|
925
939
|
'hide-silo-names': cleanConfig.multitenancy?.['hide-silo-names'] || false
|
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 395% 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 395% 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">
|
|
@@ -110,6 +110,16 @@
|
|
|
110
110
|
<span class="px-4 py-1.5 bg-blue-100 text-blue-700 text-[9px] font-black rounded-lg">ZERO-LEAK PROXY</span>
|
|
111
111
|
</div>
|
|
112
112
|
</div>
|
|
113
|
+
|
|
114
|
+
<div class="lg:col-span-12 bg-white p-10 rounded-[2.5rem] border border-orange-100 shadow-sm hover:shadow-2xl transition-all group relative overflow-hidden cursor-crosshair bg-gradient-to-br from-white to-orange-50/30">
|
|
115
|
+
<p class="text-[9px] font-bold text-orange-600 uppercase tracking-widest mb-4">Elite Extension (+15%)</p>
|
|
116
|
+
<h4 class="text-2xl font-black text-slate-900 mb-3 tracking-tight italic">WSO2 API Gateway Integration</h4>
|
|
117
|
+
<p class="text-slate-600 leading-relaxed mb-8">Natively orchestrates with WSO2 API Manager using automated OAuth2 Client Credentials Flow, pushing dynamic OpenAPI specifications to Gateways at boot time.</p>
|
|
118
|
+
<div class="flex gap-3">
|
|
119
|
+
<span class="px-4 py-1.5 bg-orange-100 text-orange-700 text-[9px] font-black rounded-lg">ZERO-TOUCH PROVISIONING</span>
|
|
120
|
+
<span class="px-4 py-1.5 bg-orange-100 text-orange-700 text-[9px] font-black rounded-lg">WSO2 REST PROXY</span>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
113
123
|
</div>
|
|
114
124
|
</div>
|
|
115
125
|
</section>
|
|
@@ -416,7 +426,7 @@
|
|
|
416
426
|
</div>
|
|
417
427
|
|
|
418
428
|
<div class="mt-24 text-slate-500 font-mono text-[10px] uppercase tracking-[0.6em] font-black relative z-10">
|
|
419
|
-
GO-DUCK • THE
|
|
429
|
+
GO-DUCK • THE 395% ELITE MILESTONE • SOVEREIGN CODE ORCHESTRATION
|
|
420
430
|
</div>
|
|
421
431
|
</div>
|
|
422
432
|
</section>
|
|
@@ -81,3 +81,24 @@ class ApiProvider {
|
|
|
81
81
|
}
|
|
82
82
|
</code></pre>
|
|
83
83
|
</section>
|
|
84
|
+
|
|
85
|
+
<section class="mb-10">
|
|
86
|
+
<h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">3. WSO2 API Manager Integration</h2>
|
|
87
|
+
<p class="mb-4">GO-DUCK generated APIs can automatically register themselves with a <strong>WSO2 API Manager</strong> instance on startup. This uses the WSO2 Publisher REST API to import your dynamically generated OpenAPI 3.0 specs.</p>
|
|
88
|
+
|
|
89
|
+
<div class="bg-blue-50 border-l-4 border-blue-500 p-4 mb-6 rounded-r">
|
|
90
|
+
<p class="text-blue-900"><strong>Configuration:</strong> Enable this by adding the <code>wso2</code> block to your <code>config.yaml</code>.</p>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<pre><code class="language-yaml">go-duck:
|
|
94
|
+
integrations:
|
|
95
|
+
wso2:
|
|
96
|
+
enabled: true
|
|
97
|
+
publisher-url: "https://localhost:9443/api/am/publisher/v3"
|
|
98
|
+
client-id: "YOUR_WSO2_CLIENT_ID"
|
|
99
|
+
client-secret: "YOUR_WSO2_CLIENT_SECRET"
|
|
100
|
+
gateway-environments:
|
|
101
|
+
- "Production"
|
|
102
|
+
- "Sandbox"
|
|
103
|
+
</code></pre>
|
|
104
|
+
</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">395%</span>
|
|
66
66
|
</div>
|
|
67
67
|
</div>
|
|
68
68
|
</div>
|
package/templates/go/main.go.hbs
CHANGED
|
@@ -11,6 +11,7 @@ import (
|
|
|
11
11
|
"{{app_name}}/internal/server"
|
|
12
12
|
"{{app_name}}/internal/worker"
|
|
13
13
|
"{{app_name}}/internal/repository"
|
|
14
|
+
"{{app_name}}/integrations/wso2"
|
|
14
15
|
"gorm.io/driver/postgres"
|
|
15
16
|
"gorm.io/gorm"
|
|
16
17
|
)
|
|
@@ -49,7 +50,12 @@ func main() {
|
|
|
49
50
|
go outboxWorker.Start(context.Background())
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
// 5.
|
|
53
|
+
// 5. Register with WSO2 API Manager (if enabled)
|
|
54
|
+
if err := wso2.RegisterAPI(appConfig); err != nil {
|
|
55
|
+
logger.Error("WSO2 API Manager Registration failed: %v", err)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 6. Start HTTP Server
|
|
53
59
|
port := fmt.Sprintf(":%d", appConfig.GoDuck.Server.Port)
|
|
54
60
|
logger.Info("Starting HTTP server on %s", port)
|
|
55
61
|
r.Run(port)
|
|
@@ -14,7 +14,9 @@ import (
|
|
|
14
14
|
"gorm.io/driver/postgres"
|
|
15
15
|
"gorm.io/gorm"
|
|
16
16
|
"gorm.io/plugin/opentelemetry/tracing"
|
|
17
|
+
{{#if multitenancy_enabled}}
|
|
17
18
|
"{{app_name}}/management"
|
|
19
|
+
{{/if}}
|
|
18
20
|
"{{app_name}}/middleware"
|
|
19
21
|
"{{app_name}}/controllers"
|
|
20
22
|
"{{app_name}}/models"
|
|
@@ -183,12 +185,14 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
|
|
|
183
185
|
})
|
|
184
186
|
|
|
185
187
|
// Management APIs (Run-time DB onboarding)
|
|
188
|
+
{{#if multitenancy_enabled}}
|
|
186
189
|
mgmt := r.Group("/management")
|
|
187
190
|
mgmt.Use(middleware.JWTMiddleware())
|
|
188
191
|
mgmt.Use(middleware.SuperAdminRoleMiddleware(appConfig))
|
|
189
192
|
{
|
|
190
193
|
mgmt.POST("/tenant/assign", management.CreateDatabaseAndMigrate(masterDB))
|
|
191
194
|
}
|
|
195
|
+
{{/if}}
|
|
192
196
|
|
|
193
197
|
// Open Application APIs (No Auth)
|
|
194
198
|
openApi := r.Group("/open/api")
|
|
@@ -241,7 +245,9 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
|
|
|
241
245
|
api.Use(middleware.MeteringMiddleware(masterDB))
|
|
242
246
|
{
|
|
243
247
|
// Silo Portfolio
|
|
248
|
+
{{#if multitenancy_enabled}}
|
|
244
249
|
api.GET("/silos/me", management.GetMySilos(masterDB))
|
|
250
|
+
{{/if}}
|
|
245
251
|
|
|
246
252
|
// Business Reporting APIs
|
|
247
253
|
meteringCtrl := controllers.MeteringController{DB: masterDB}
|