go-duck-cli 1.1.15 → 1.1.16
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 +5 -0
- package/generators/devops.js +20 -6
- package/generators/docs.js +10 -1
- package/generators/postman.js +7 -7
- package/generators/swagger.js +1 -1
- package/package.json +1 -1
- package/templates/docs/index.html.hbs +9 -9
- package/templates/docs/pages/grpc.hbs +8 -0
- package/templates/docs/pages/integrations.hbs +3 -3
- package/templates/docs/pages/mosquitto.hbs +1 -1
- package/templates/docs/pages/realtime.hbs +2 -2
package/README.md
CHANGED
|
@@ -115,6 +115,11 @@ cd my-app
|
|
|
115
115
|
|
|
116
116
|
Before running the microservice, compile the protobuf contract files using the scaffolded scripts:
|
|
117
117
|
|
|
118
|
+
> [!NOTE]
|
|
119
|
+
> **Do I need to run this?**
|
|
120
|
+
> * **Yes (Local Host Execution)**: You must run the compilation script whenever you first create the microservice, or whenever you modify the GDL schemas and re-run `import-gdl` to generate updated gRPC structures.
|
|
121
|
+
> * **No (Docker Deployment)**: If you compile and run the application via Docker (using `docker build` or `docker-compose`), the multi-stage `devops/Dockerfile` compiles the protobuf definitions internally, so you do not need to run the scripts locally.
|
|
122
|
+
|
|
118
123
|
#### Prerequisites
|
|
119
124
|
1. Ensure the `protoc` compiler is installed and added to your system's `PATH`.
|
|
120
125
|
2. Install the necessary Go compiler plugins:
|
package/generators/devops.js
CHANGED
|
@@ -15,6 +15,20 @@ export const generateDeploymentArtifacts = async (config, projectRootDir) => {
|
|
|
15
15
|
|
|
16
16
|
const appName = config.name || 'go-duck';
|
|
17
17
|
const appPort = config.server?.port || 8080;
|
|
18
|
+
const keycloakHost = config.security?.['keycloak-host'] || 'http://localhost:8180';
|
|
19
|
+
const keycloakPort = keycloakHost.includes(':') ? keycloakHost.split(':').pop() : '8080';
|
|
20
|
+
|
|
21
|
+
const dbPort = config.datasource?.port || 5432;
|
|
22
|
+
|
|
23
|
+
const redisHost = config.cache?.redis?.host || 'localhost:6379';
|
|
24
|
+
const redisPort = redisHost.includes(':') ? redisHost.split(':').pop() : '6379';
|
|
25
|
+
|
|
26
|
+
const mqttBroker = config.messaging?.mqtt?.broker || 'tcp://localhost:1883';
|
|
27
|
+
const mqttPort = mqttBroker.includes(':') ? mqttBroker.split(':').pop() : '1883';
|
|
28
|
+
|
|
29
|
+
const esAddr = config.elasticsearch?.addresses?.[0] || 'http://localhost:9200';
|
|
30
|
+
const esAddrClean = esAddr.replace(/\/+$/, '');
|
|
31
|
+
const esPort = esAddrClean.includes(':') ? esAddrClean.split(':').pop() : '9200';
|
|
18
32
|
|
|
19
33
|
// --- 1. Dockerfile (Multi-stage, lean production image) ---
|
|
20
34
|
const dockerfile = `
|
|
@@ -63,7 +77,7 @@ COPY --from=builder /app/application.yml .
|
|
|
63
77
|
COPY --from=builder /app/application-dev.yml .
|
|
64
78
|
COPY --from=builder /app/application-prod.yml .
|
|
65
79
|
|
|
66
|
-
EXPOSE
|
|
80
|
+
EXPOSE ${appPort}
|
|
67
81
|
ENV GO_PROFILE=prod
|
|
68
82
|
ENTRYPOINT ["/app/server"]
|
|
69
83
|
`;
|
|
@@ -79,7 +93,7 @@ services:
|
|
|
79
93
|
POSTGRES_PASSWORD: ${config.datasource?.password || 'password'}
|
|
80
94
|
POSTGRES_DB: ${config.datasource?.database || 'go_duck_master'}
|
|
81
95
|
ports:
|
|
82
|
-
- "
|
|
96
|
+
- "${dbPort}:5432"
|
|
83
97
|
volumes:
|
|
84
98
|
- postgres_data:/var/lib/postgresql/data
|
|
85
99
|
networks:
|
|
@@ -89,7 +103,7 @@ services:
|
|
|
89
103
|
image: redis:7.2.4-alpine
|
|
90
104
|
container_name: ${appName}-redis
|
|
91
105
|
ports:
|
|
92
|
-
- "
|
|
106
|
+
- "${redisPort}:6379"
|
|
93
107
|
command: redis-server --save 60 1 --loglevel warning
|
|
94
108
|
networks:
|
|
95
109
|
- go-duck-net
|
|
@@ -98,7 +112,7 @@ services:
|
|
|
98
112
|
image: eclipse-mosquitto:2.0.18
|
|
99
113
|
container_name: ${appName}-mqtt
|
|
100
114
|
ports:
|
|
101
|
-
- "
|
|
115
|
+
- "${mqttPort}:1883"
|
|
102
116
|
- "9001:9001"
|
|
103
117
|
volumes:
|
|
104
118
|
- ./k8s/mosquitto.conf:/mosquitto/config/mosquitto.conf
|
|
@@ -136,7 +150,7 @@ services:
|
|
|
136
150
|
- xpack.security.enabled=false
|
|
137
151
|
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
|
|
138
152
|
ports:
|
|
139
|
-
- "
|
|
153
|
+
- "${esPort}:9200"
|
|
140
154
|
networks:
|
|
141
155
|
- go-duck-net
|
|
142
156
|
|
|
@@ -150,7 +164,7 @@ services:
|
|
|
150
164
|
volumes:
|
|
151
165
|
- ./keycloak/realm-config:/opt/keycloak/data/import
|
|
152
166
|
ports:
|
|
153
|
-
- "
|
|
167
|
+
- "${keycloakPort}:8080"
|
|
154
168
|
networks:
|
|
155
169
|
- go-duck-net
|
|
156
170
|
|
package/generators/docs.js
CHANGED
|
@@ -61,11 +61,20 @@ export const generateDocumentation = async (config, entities, outputDir, enums =
|
|
|
61
61
|
{ file: 'serverless', title: 'Serverless Transformation' }
|
|
62
62
|
];
|
|
63
63
|
|
|
64
|
+
const grpcAddr = config.server?.grpc?.addr || ':9000';
|
|
65
|
+
const grpcPort = grpcAddr.includes(':') ? grpcAddr.split(':').pop() : '9000';
|
|
66
|
+
|
|
67
|
+
const mqttBroker = config.messaging?.mqtt?.broker || 'tcp://localhost:1883';
|
|
68
|
+
const mqttPort = mqttBroker.includes(':') ? mqttBroker.split(':').pop() : '1883';
|
|
69
|
+
|
|
64
70
|
const context = {
|
|
65
71
|
appName: config.name || 'GO-DUCK App',
|
|
66
72
|
entities: entities,
|
|
67
73
|
enums: enums,
|
|
68
|
-
openEntities: openEntities
|
|
74
|
+
openEntities: openEntities,
|
|
75
|
+
serverPort: config.server?.port || 8080,
|
|
76
|
+
grpcPort: grpcPort,
|
|
77
|
+
mqttPort: mqttPort
|
|
69
78
|
};
|
|
70
79
|
|
|
71
80
|
for (const page of pages) {
|
package/generators/postman.js
CHANGED
|
@@ -15,16 +15,16 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
|
|
|
15
15
|
item: [],
|
|
16
16
|
variable: [
|
|
17
17
|
{ key: "host", value: "localhost", type: "string" },
|
|
18
|
-
{ key: "port", value:
|
|
18
|
+
{ key: "port", value: String(config.server?.port || 8080), type: "string" },
|
|
19
19
|
{ key: "tenant", value: "tenant_1", type: "string" },
|
|
20
20
|
{ key: "token", value: "", type: "string" },
|
|
21
|
-
{ key: "keycloak_url", value: "http://localhost:8180", type: "string" },
|
|
21
|
+
{ key: "keycloak_url", value: config.security?.['keycloak-host'] || "http://localhost:8180", type: "string" },
|
|
22
22
|
{ key: "realm", value: config.security?.['keycloak-realm'] || 'master', type: "string" },
|
|
23
|
-
{ key: "app_client_id", value: `${config.name}-app`, type: "string" },
|
|
24
|
-
{ key: "service_client_id", value: `${config.name}-service`, type: "string" },
|
|
25
|
-
{ key: "service_secret", value: "service-secret-123", type: "string" },
|
|
26
|
-
{ key: "username", value: "admin", type: "string" },
|
|
27
|
-
{ key: "password", value: "admin", type: "string" }
|
|
23
|
+
{ key: "app_client_id", value: config.security?.['keycloak-app-client-id'] || `${config.name}-app`, type: "string" },
|
|
24
|
+
{ key: "service_client_id", value: config.security?.['keycloak-service-client-id'] || `${config.name}-service`, type: "string" },
|
|
25
|
+
{ key: "service_secret", value: config.security?.['keycloak-service-secret'] || "service-secret-123", type: "string" },
|
|
26
|
+
{ key: "username", value: config.security?.['keycloak-admin-client-id'] || "admin", type: "string" },
|
|
27
|
+
{ key: "password", value: config.security?.['keycloak-admin-secret'] || "admin", type: "string" }
|
|
28
28
|
]
|
|
29
29
|
};
|
|
30
30
|
|
package/generators/swagger.js
CHANGED
|
@@ -14,7 +14,7 @@ export const generateSwaggerDocs = async (config, entities, outputDir, openEntit
|
|
|
14
14
|
description: `Generated documentation for ${config.name} microservice`
|
|
15
15
|
},
|
|
16
16
|
servers: [
|
|
17
|
-
{ url:
|
|
17
|
+
{ url: `http://localhost:${config.server?.port || 8080}`, description: 'Local Development Server' }
|
|
18
18
|
],
|
|
19
19
|
paths: {},
|
|
20
20
|
components: {
|
package/package.json
CHANGED
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
<pre><code class="language-bash"># Start local infrastructure (DB, Redis, MQTT, Keycloak, Jaeger/OTel)
|
|
61
61
|
docker-compose up -d
|
|
62
62
|
|
|
63
|
-
# Run the Go app (starts on port
|
|
63
|
+
# Run the Go app (starts on port {{serverPort}} by default)
|
|
64
64
|
go run main.go</code></pre>
|
|
65
65
|
</section>
|
|
66
66
|
|
|
@@ -77,7 +77,7 @@ DELETE /api/{{#if entities.length}}{{toLowerCase entities.[0].name}}s{{else}}ent
|
|
|
77
77
|
|
|
78
78
|
<h3 class="font-semibold mb-2 mt-6">PostgREST-like Generic Search:</h3>
|
|
79
79
|
<p class="mb-2">We expose a dynamic search endpoint allowing powerful querying directly from the frontend.</p>
|
|
80
|
-
<pre><code class="language-bash">curl -X GET "http://localhost:
|
|
80
|
+
<pre><code class="language-bash">curl -X GET "http://localhost:{{serverPort}}/api/rpc/{{#if entities.length}}{{toLowerCase entities.[0].name}}{{else}}your_table{{/if}}?name=eq.John&age=gt.18" \
|
|
81
81
|
-H "Authorization: Bearer YOUR_JWT" \
|
|
82
82
|
-H "X-Tenant-ID: tenant_1"</code></pre>
|
|
83
83
|
<p class="text-sm text-gray-500 mt-2">Supported operators: <code>eq, neq, gt, gte, lt, lte, like, ilike</code></p>
|
|
@@ -88,10 +88,10 @@ DELETE /api/{{#if entities.length}}{{toLowerCase entities.[0].name}}s{{else}}ent
|
|
|
88
88
|
<h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">Audit & Metering</h2>
|
|
89
89
|
<p class="mb-4">Entity mutations are automatically tracked via the <code>AuditMiddleware</code>. Usage limits per-tenant can be managed via the Metering API.</p>
|
|
90
90
|
<pre><code class="language-bash"># View Audit Logs
|
|
91
|
-
curl -X GET http://localhost:
|
|
91
|
+
curl -X GET http://localhost:{{serverPort}}/api/audit -H "Authorization: Bearer YOUR_JWT"
|
|
92
92
|
|
|
93
93
|
# View Usage for Current Tenant
|
|
94
|
-
curl -X GET http://localhost:
|
|
94
|
+
curl -X GET http://localhost:{{serverPort}}/api/metering/usage -H "Authorization: Bearer YOUR_JWT" -H "X-Tenant-ID: my_tenant"</code></pre>
|
|
95
95
|
</section>
|
|
96
96
|
|
|
97
97
|
<!-- WebSockets -->
|
|
@@ -102,7 +102,7 @@ curl -X GET http://localhost:8080/api/metering/usage -H "Authorization: Bearer Y
|
|
|
102
102
|
<h3 class="font-semibold mb-2">Connecting via JS:</h3>
|
|
103
103
|
<pre><code class="language-javascript">const token = 'YOUR_JWT_TOKEN';
|
|
104
104
|
// Pass token as query param for auth
|
|
105
|
-
const ws = new WebSocket(`ws://localhost:
|
|
105
|
+
const ws = new WebSocket(`ws://localhost:{{serverPort}}/ws?token=${token}`);
|
|
106
106
|
|
|
107
107
|
ws.onopen = () => {
|
|
108
108
|
// Sending a signed Traced Envelope
|
|
@@ -123,14 +123,14 @@ ws.onmessage = (event) => console.log("Received:", event.data);</code></pre>
|
|
|
123
123
|
<h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">MQTT Event Streaming</h2>
|
|
124
124
|
<p class="mb-4">Successful CRUD operations (Create, Update, Delete) are published as JSON payloads to the MQTT broker. This is perfect for webhooks, async processing, or triggering other microservices.</p>
|
|
125
125
|
<pre><code class="language-bash"># Using mosquitto_sub to listen to events
|
|
126
|
-
mosquitto_sub -h localhost -p
|
|
126
|
+
mosquitto_sub -h localhost -p {{mqttPort}} -t "go-duck/events/#" -u dev_user -P dev_password</code></pre>
|
|
127
127
|
<p class="text-sm mt-2">Example topic: <code>go-duck/events/{{#if entities.length}}{{toLowerCase entities.[0].name}}{{else}}entity{{/if}}/CREATE</code></p>
|
|
128
128
|
</section>
|
|
129
129
|
|
|
130
130
|
<!-- Kratos gRPC -->
|
|
131
131
|
<section id="grpc-kratos" class="content-section">
|
|
132
132
|
<h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">Kratos Secured gRPC APIs</h2>
|
|
133
|
-
<p class="mb-4">The application runs a Kratos gRPC server on <code>port
|
|
133
|
+
<p class="mb-4">The application runs a Kratos gRPC server on <code>port {{grpcPort}}</code> by default, sharing the internal repository with Gin. It validates Keycloak JWTs via Kratos middleware.</p>
|
|
134
134
|
<h3 class="font-semibold mb-2">Calling the gRPC Service (Go Client Example):</h3>
|
|
135
135
|
<pre><code class="language-go">import (
|
|
136
136
|
"context"
|
|
@@ -143,7 +143,7 @@ mosquitto_sub -h localhost -p 1883 -t "go-duck/events/#" -u dev_user -P dev_pass
|
|
|
143
143
|
// The JWT must be generated using the Keycloak secret configured in the app
|
|
144
144
|
token := oauth.TokenSource{TokenSource: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: "YOUR_JWT"})}
|
|
145
145
|
|
|
146
|
-
conn, err := grpc.Dial("localhost:
|
|
146
|
+
conn, err := grpc.Dial("localhost:{{grpcPort}}", grpc.WithInsecure(), grpc.WithPerRPCCredentials(token))
|
|
147
147
|
client := pb.New{{#if entities.length}}{{capitalize entities.[0].name}}{{else}}Entity{{/if}}ServiceClient(conn)
|
|
148
148
|
|
|
149
149
|
// Execute Call
|
|
@@ -167,7 +167,7 @@ res, err := client.Get{{#if entities.length}}{{capitalize entities.[0].name}}{{e
|
|
|
167
167
|
}
|
|
168
168
|
}</code></pre>
|
|
169
169
|
<h3 class="font-semibold mb-2 mt-4">Example Request via Fetch:</h3>
|
|
170
|
-
<pre><code class="language-javascript">fetch('http://localhost:
|
|
170
|
+
<pre><code class="language-javascript">fetch('http://localhost:{{serverPort}}/graphql', {
|
|
171
171
|
method: 'POST',
|
|
172
172
|
headers: { 'Content-Type': 'application/json' },
|
|
173
173
|
body: JSON.stringify({ query: 'query { list{{#if entities.length}}{{capitalize entities.[0].name}}s{{else}}Entities{{/if}} { id } }' })
|
|
@@ -52,6 +52,14 @@ service {{capitalize (defaultStr (lookup entities "0.name") "Entity")}}Service {
|
|
|
52
52
|
GO-DUCK scaffolds script files in the root of the generated project to manage the compilation of <code>.proto</code> contracts natively on all operating systems.
|
|
53
53
|
</p>
|
|
54
54
|
|
|
55
|
+
<div class="mb-8 p-6 bg-slate-900 border border-slate-800 rounded-3xl text-white shadow-lg">
|
|
56
|
+
<h4 class="font-bold text-amber-400 mb-2 uppercase text-xs tracking-widest font-mono">⚡ Execution Requirement Guide</h4>
|
|
57
|
+
<ul class="list-disc pl-6 space-y-2 text-slate-300 text-sm leading-relaxed">
|
|
58
|
+
<li><strong>Local Host Execution (Required)</strong>: You must run the compilation script whenever you first create the microservice, or whenever you modify the GDL schemas and re-run <code>import-gdl</code> to generate updated gRPC structures.</li>
|
|
59
|
+
<li><strong>Docker Deployment (Automated)</strong>: If you compile and run the application via Docker (using <code>docker build</code> or <code>docker-compose</code>), the multi-stage <code>devops/Dockerfile</code> compiles the protobuf definitions internally, so you do not need to run the scripts locally.</li>
|
|
60
|
+
</ul>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
55
63
|
<div class="bg-white border border-slate-200 rounded-3xl p-8 shadow-sm mb-8">
|
|
56
64
|
<h3 class="text-xl font-bold text-slate-800 mb-4 flex items-center">
|
|
57
65
|
🛠️ Scaffolded Compiler Scripts
|
|
@@ -25,7 +25,7 @@ export interface {{capitalize (defaultStr (lookup entities "0.name") "Entity")}}
|
|
|
25
25
|
|
|
26
26
|
@Injectable({ providedIn: 'root' })
|
|
27
27
|
export class {{capitalize (defaultStr (lookup entities "0.name") "Entity")}}Service {
|
|
28
|
-
private apiUrl = 'http://localhost:
|
|
28
|
+
private apiUrl = 'http://localhost:{{serverPort}}/api/{{toLowerCase (defaultStr (lookup entities "0.name") "entity")}}s';
|
|
29
29
|
|
|
30
30
|
constructor(private http: HttpClient) {}
|
|
31
31
|
|
|
@@ -40,7 +40,7 @@ export class {{capitalize (defaultStr (lookup entities "0.name") "Entity")}}Serv
|
|
|
40
40
|
// Example: Using the powerful Generic Search RPC Engine
|
|
41
41
|
search(queryField: string, operator: string, value: string): Observable<any> {
|
|
42
42
|
let params = new HttpParams().set(queryField, \`\${operator}.\${value}\`);
|
|
43
|
-
return this.http.get<any>('http://localhost:
|
|
43
|
+
return this.http.get<any>('http://localhost:{{serverPort}}/api/rpc/{{toLowerCase (defaultStr (lookup entities "0.name") "entity")}}', { params });
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
</code></pre>
|
|
@@ -55,7 +55,7 @@ import 'package:http/http.dart' as http;
|
|
|
55
55
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
56
56
|
|
|
57
57
|
class ApiProvider {
|
|
58
|
-
static const String baseUrl = "http://localhost:
|
|
58
|
+
static const String baseUrl = "http://localhost:{{serverPort}}/api/{{toLowerCase (defaultStr (lookup entities "0.name") "entity")}}s";
|
|
59
59
|
final storage = const FlutterSecureStorage();
|
|
60
60
|
|
|
61
61
|
// Retrieve Keycloak Bearer token from secure storage
|
|
@@ -26,7 +26,7 @@ messaging.Publish("events/users", event)</code></pre>
|
|
|
26
26
|
<h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">Local Development</h2>
|
|
27
27
|
<p class="mb-4">Your <code>docker-compose.yml</code> includes a pre-configured Mosquitto image. You can use any MQTT client (like MQTT.fx or mosquitto_pub/sub) to listen to your service events.</p>
|
|
28
28
|
<pre><code class="language-bash"># Subscribe to all audit events locally
|
|
29
|
-
mosquitto_sub -h localhost -p
|
|
29
|
+
mosquitto_sub -h localhost -p {{mqttPort}} -t "audit/logs/#" -v</code></pre>
|
|
30
30
|
</section>
|
|
31
31
|
|
|
32
32
|
<section class="mb-10">
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
<section class="mb-10">
|
|
5
5
|
<h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">1. REST-over-WS Dispatcher</h2>
|
|
6
|
-
<p class="mb-4">Instead of traditional HTTP, you can pass highly efficient "Traced Envelopes" through our WebSocket endpoint at <code>ws://localhost:
|
|
6
|
+
<p class="mb-4">Instead of traditional HTTP, you can pass highly efficient "Traced Envelopes" through our WebSocket endpoint at <code>ws://localhost:{{serverPort}}/ws</code>. The GO-DUCK generated engine unpacks the WS payload and routes it natively through Gin.</p>
|
|
7
7
|
|
|
8
8
|
<div class="bg-indigo-50 border-l-4 border-indigo-500 p-4 mb-6 rounded-r">
|
|
9
9
|
<p class="text-indigo-900"><strong>Note:</strong> Token validation is done via query-param out of necessity due to websocket protocol limitations.</p>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<pre><code class="language-javascript">const token = "eyJhbGciOi..."; // Valid Keycloak Access Token
|
|
14
14
|
|
|
15
15
|
// Phase 1: Authentication happens precisely on the handshake (WSS Handshake via query param)
|
|
16
|
-
const ws = new WebSocket(`ws://localhost:
|
|
16
|
+
const ws = new WebSocket(`ws://localhost:{{serverPort}}/ws?token=${token}`);
|
|
17
17
|
|
|
18
18
|
ws.onopen = () => {
|
|
19
19
|
// Phase 2: Send HMAC-SHA256 Signed Messages
|