go-duck-cli 1.0.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.
Files changed (49) hide show
  1. package/README.md +130 -0
  2. package/generators/cache.js +107 -0
  3. package/generators/config.js +173 -0
  4. package/generators/devops.js +212 -0
  5. package/generators/docs.js +74 -0
  6. package/generators/graphql.js +38 -0
  7. package/generators/kratos.js +157 -0
  8. package/generators/logger.js +68 -0
  9. package/generators/metering.js +143 -0
  10. package/generators/migrations.js +240 -0
  11. package/generators/mqtt.js +87 -0
  12. package/generators/multitenancy.js +130 -0
  13. package/generators/postgrest.js +115 -0
  14. package/generators/repository.js +28 -0
  15. package/generators/resilience.js +69 -0
  16. package/generators/security.js +168 -0
  17. package/generators/swagger.js +145 -0
  18. package/generators/telemetry.js +121 -0
  19. package/generators/websocket.js +162 -0
  20. package/index.js +592 -0
  21. package/package.json +23 -0
  22. package/parser/gdl.js +162 -0
  23. package/templates/application.yml.hbs +18 -0
  24. package/templates/docs/gin_bottle.png +0 -0
  25. package/templates/docs/index.html.hbs +226 -0
  26. package/templates/docs/intro.mp4 +0 -0
  27. package/templates/docs/kratos_mark.png +0 -0
  28. package/templates/docs/layout.hbs +106 -0
  29. package/templates/docs/logo.png +0 -0
  30. package/templates/docs/pages/audit.hbs +39 -0
  31. package/templates/docs/pages/cli.hbs +83 -0
  32. package/templates/docs/pages/gdl.hbs +223 -0
  33. package/templates/docs/pages/graphql.hbs +51 -0
  34. package/templates/docs/pages/grpc.hbs +100 -0
  35. package/templates/docs/pages/index.hbs +181 -0
  36. package/templates/docs/pages/integrations.hbs +83 -0
  37. package/templates/docs/pages/observability.hbs +34 -0
  38. package/templates/docs/pages/realtime.hbs +43 -0
  39. package/templates/docs/pages/rest.hbs +149 -0
  40. package/templates/docs/pages/security.hbs +31 -0
  41. package/templates/go/controller.go.hbs +236 -0
  42. package/templates/go/entity.go.hbs +34 -0
  43. package/templates/go/enum.go.hbs +7 -0
  44. package/templates/go/main.go.hbs +186 -0
  45. package/templates/graphql/resolver.go.hbs +50 -0
  46. package/templates/graphql/schema.graphql.hbs +64 -0
  47. package/templates/kratos/service.go.hbs +104 -0
  48. package/templates/proto/entity.proto.hbs +95 -0
  49. package/test_parser.js +9 -0
package/parser/gdl.js ADDED
@@ -0,0 +1,162 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * GO-DUCK GDL Parser
6
+ *
7
+ * Supported field syntax:
8
+ * fieldName Type [required] [unique] [text]
9
+ *
10
+ * Supported types:
11
+ * String, Text, Integer, Long, Float, BigDecimal, Boolean, LocalDate, Instant, JSON, JSONB
12
+ *
13
+ * Type overrides in GDL:
14
+ * - `Text` maps to TEXT in DB and string in Go
15
+ * - `String(512)` maps to VARCHAR(512) in DB
16
+ *
17
+ * Examples:
18
+ * email String required unique
19
+ * bio Text
20
+ * name String required
21
+ */
22
+
23
+ export const parseGDL = async (filePath) => {
24
+ const content = await fs.readFile(filePath, 'utf8');
25
+ const entities = [];
26
+ const relationships = [];
27
+ const enums = [];
28
+
29
+ // Parse enum blocks
30
+ const enumRegex = /enum\s+(\w+)\s*\{([\s\S]*?)\}/g;
31
+ let match;
32
+ while ((match = enumRegex.exec(content)) !== null) {
33
+ const name = match[1];
34
+ const values = match[2].split(',').map(v => v.trim().replace(/['"]/g, '')).filter(v => v.length > 0);
35
+ enums.push({ name, values });
36
+ }
37
+
38
+ // Parse entity blocks
39
+ const entityRegex = /(@\w+\s+)?entity\s+(\w+)\s*\{([\s\S]*?)\}/g;
40
+ while ((match = entityRegex.exec(content)) !== null) {
41
+ const annotation = match[1]?.trim();
42
+ const name = match[2];
43
+ const fieldBlock = match[3];
44
+
45
+ if (name === 'relationship' || name === 'enum') continue;
46
+
47
+ const fields = [];
48
+ const fieldLines = fieldBlock.split('\n').map(l => l.trim()).filter(l => l.length > 0);
49
+
50
+ for (const line of fieldLines) {
51
+ const parts = line.split(/\s+/);
52
+ if (parts.length < 2) continue;
53
+
54
+ const fieldName = parts[0];
55
+ let rawType = parts[1];
56
+
57
+ // Support String(N) for custom VARCHAR sizes
58
+ let varcharSize = 255;
59
+ const sizeMatch = rawType.match(/^String\((\d+)\)$/);
60
+ if (sizeMatch) {
61
+ varcharSize = parseInt(sizeMatch[1], 10);
62
+ rawType = 'String';
63
+ }
64
+
65
+ const required = line.includes('required');
66
+ const unique = line.includes('unique');
67
+ const isText = rawType === 'Text';
68
+
69
+ // Check if type is an Enum
70
+ const isEnum = enums.some(e => e.name === rawType);
71
+
72
+ fields.push({
73
+ name: fieldName,
74
+ type: isText ? 'Text' : rawType,
75
+ required,
76
+ unique,
77
+ isEnum,
78
+ varcharSize: rawType === 'String' ? varcharSize : null,
79
+ });
80
+ }
81
+
82
+ entities.push({ name, annotation, fields });
83
+ }
84
+
85
+ // Parse relationship blocks
86
+ const relRegex = /relationship\s+(\w+)\s*\{([\s\S]*?)\n\}/g;
87
+ while ((match = relRegex.exec(content)) !== null) {
88
+ const type = match[1];
89
+ const relBlock = match[2];
90
+ const relLines = relBlock.split('\n').map(l => l.trim()).filter(l => l.length > 0);
91
+
92
+ for (const line of relLines) {
93
+ const relParts = line.split(/\s+to\s+/);
94
+ if (relParts.length !== 2) continue;
95
+
96
+ const parseRelPart = (p) => {
97
+ const m = p.match(/(\w+)\s*\{\s*(\w+)\s*\}/);
98
+ return m ? { entity: m[1], field: m[2] } : null;
99
+ };
100
+
101
+ // Support required FK: "required" keyword anywhere in the line
102
+ const fkRequired = line.includes('required');
103
+
104
+ const from = parseRelPart(relParts[0]);
105
+ const to = parseRelPart(relParts[1]);
106
+
107
+ if (from && to) {
108
+ relationships.push({ type, from, to, required: fkRequired });
109
+ }
110
+ }
111
+ }
112
+
113
+ return { entities, relationships, enums };
114
+ };
115
+
116
+ /**
117
+ * Maps GDL type to Go type
118
+ */
119
+ export const toGoType = (type, enums = []) => {
120
+ const isEnum = enums.some(e => e.name === type);
121
+ if (isEnum) return type;
122
+
123
+ const map = {
124
+ String: 'string',
125
+ Text: 'string',
126
+ Integer: 'int',
127
+ Long: 'int64',
128
+ Float: 'float64',
129
+ BigDecimal: 'float64',
130
+ Boolean: 'bool',
131
+ LocalDate: 'time.Time',
132
+ Instant: 'time.Time',
133
+ JSON: 'datatypes.JSON',
134
+ JSONB: 'datatypes.JSON',
135
+ };
136
+ return map[type] || 'interface{}';
137
+ };
138
+
139
+ /**
140
+ * Maps GDL type to Liquibase/SQL type
141
+ */
142
+ export const toLiquibaseType = (field, enums = []) => {
143
+ if (field.type === 'Text') return 'TEXT';
144
+ if (field.type === 'String' && field.varcharSize) return `VARCHAR(${field.varcharSize})`;
145
+
146
+ // Enums are stored as VARCHAR with a check constraint or just as strings
147
+ if (field.isEnum) return 'VARCHAR(50)';
148
+
149
+ const map = {
150
+ String: 'VARCHAR(255)',
151
+ Integer: 'INT',
152
+ Long: 'BIGINT',
153
+ Float: 'DECIMAL',
154
+ BigDecimal: 'DECIMAL',
155
+ Boolean: 'BOOLEAN',
156
+ LocalDate: 'DATE',
157
+ Instant: 'TIMESTAMP',
158
+ JSON: 'JSON',
159
+ JSONB: 'JSONB',
160
+ };
161
+ return map[field.type] || 'VARCHAR(255)';
162
+ };
@@ -0,0 +1,18 @@
1
+ spring:
2
+ profiles:
3
+ active: @spring.profiles.active@
4
+ datasource:
5
+ url: jdbc:postgresql://localhost:5432/{{name}}
6
+ username: {{security.keycloak-client-id}}
7
+ password: {{security.keycloak-secret}}
8
+ driver-class-name: org.postgresql.Driver
9
+
10
+ keycloak:
11
+ auth-server-url: {{security.keycloak-host}}
12
+ realm: {{security.keycloak-realm}}
13
+ resource: {{security.keycloak-client-id}}
14
+ credentials:
15
+ secret: {{security.keycloak-secret}}
16
+
17
+ multitenancy:
18
+ enabled: {{multitenancy.enabled}}
Binary file
@@ -0,0 +1,226 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{appName}} - Developer Guide</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
10
+ <script>hljs.highlightAll();</script>
11
+ <style>
12
+ html { scroll-behavior: smooth; }
13
+ .sidebar { height: calc(100vh - 4rem); }
14
+ .content-section { margin-bottom: 4rem; padding-top: 4rem; margin-top: -4rem; }
15
+ </style>
16
+ </head>
17
+ <body class="bg-gray-50 text-gray-800 font-sans antialiased">
18
+ <!-- Navbar -->
19
+ <nav class="bg-indigo-600 text-white shadow-lg fixed w-full z-10 top-0 h-16 flex items-center px-6">
20
+ <div class="text-xl font-bold">{{appName}} <span class="text-indigo-200 text-sm font-normal">Developer Guide</span></div>
21
+ </nav>
22
+
23
+ <div class="flex max-w-8xl mx-auto pt-16">
24
+ <!-- Sidebar -->
25
+ <aside class="w-64 fixed sidebar overflow-y-auto bg-white border-r border-gray-200 p-6 hidden md:block">
26
+ <h3 class="font-bold text-gray-400 uppercase text-xs tracking-wider mb-4">Table of Contents</h3>
27
+ <ul class="space-y-2">
28
+ <li><a href="#quick-start" class="text-gray-600 hover:text-indigo-600 hover:font-medium transition-colors">Quick Start</a></li>
29
+ <li class="pt-2 pb-1"><span class="font-semibold text-gray-800">1. Core & Generic Layers</span></li>
30
+ <li><a href="#rest-api" class="text-gray-600 hover:text-indigo-600 hover:font-medium transition-colors pl-4 block">REST APIs & Search</a></li>
31
+ <li><a href="#audit-metering" class="text-gray-600 hover:text-indigo-600 hover:font-medium transition-colors pl-4 block">Audit & Metering</a></li>
32
+ <li class="pt-2 pb-1"><span class="font-semibold text-gray-800">2. Real-Time & Kratos</span></li>
33
+ <li><a href="#websockets" class="text-gray-600 hover:text-indigo-600 hover:font-medium transition-colors pl-4 block">WebSockets (REST-over-WS)</a></li>
34
+ <li><a href="#mqtt" class="text-gray-600 hover:text-indigo-600 hover:font-medium transition-colors pl-4 block">MQTT Event Streaming</a></li>
35
+ <li><a href="#grpc-kratos" class="text-gray-600 hover:text-indigo-600 hover:font-medium transition-colors pl-4 block">Kratos gRPC APIs</a></li>
36
+ <li class="pt-2 pb-1"><span class="font-semibold text-gray-800">3. GraphQL</span></li>
37
+ <li><a href="#graphql" class="text-gray-600 hover:text-indigo-600 hover:font-medium transition-colors pl-4 block">GraphQL Integration</a></li>
38
+ <li class="pt-2 pb-1"><span class="font-semibold text-gray-800">4. Resilience & Security</span></li>
39
+ <li><a href="#resilience-cache" class="text-gray-600 hover:text-indigo-600 hover:font-medium transition-colors pl-4 block">Caching & Circuit Breakers</a></li>
40
+ <li><a href="#security" class="text-gray-600 hover:text-indigo-600 hover:font-medium transition-colors pl-4 block">Keycloak OIDC & Multi-tenancy</a></li>
41
+ <li class="pt-2 pb-1"><span class="font-semibold text-gray-800">5. Observability</span></li>
42
+ <li><a href="#observability" class="text-gray-600 hover:text-indigo-600 hover:font-medium transition-colors pl-4 block">OTel & Datadog</a></li>
43
+ <li class="pt-2 pb-1"><span class="font-semibold text-gray-800">6. Migrations</span></li>
44
+ <li><a href="#liquibase" class="text-gray-600 hover:text-indigo-600 hover:font-medium transition-colors pl-4 block">Liquibase & GDL</a></li>
45
+ </ul>
46
+ </aside>
47
+
48
+ <!-- Main Content -->
49
+ <main class="flex-1 md:ml-64 p-8 lg:p-12 max-w-5xl">
50
+ <h1 class="text-4xl font-extrabold text-gray-900 mb-4">{{appName}} API & Integration Guide</h1>
51
+ <p class="text-lg text-gray-600 mb-8">Welcome to the developer documentation for your GO-DUCK generated microservice. This guide covers how to connect, authenticate, and integrate with all the implemented features (The 200% Milestone).</p>
52
+
53
+ <!-- Quick Start -->
54
+ <section id="quick-start" class="content-section">
55
+ <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">Quick Start</h2>
56
+ <div class="bg-indigo-50 border-l-4 border-indigo-500 p-4 mb-6 rounded-r">
57
+ <p class="text-indigo-900"><strong>Note:</strong> Most APIs are secured by Keycloak JWT. You will need a valid token to access them.</p>
58
+ </div>
59
+ <h3 class="font-semibold mb-2">Running the application:</h3>
60
+ <pre><code class="language-bash"># Start local infrastructure (DB, Redis, MQTT, Keycloak, Jaeger/OTel)
61
+ docker-compose up -d
62
+
63
+ # Run the Go app (starts on port 8080 by default)
64
+ go run main.go</code></pre>
65
+ </section>
66
+
67
+ <!-- REST & Search -->
68
+ <section id="rest-api" class="content-section">
69
+ <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">1. REST APIs & Generic Search</h2>
70
+ <p class="mb-4">The application provides standard RESTful CRUD endpoints for all generated entities (e.g., {{#if entities.length}}{{entities.[0].name}}{{else}}Entity{{/if}}).</p>
71
+
72
+ <h3 class="font-semibold mb-2">Standard CRUD:</h3>
73
+ <pre><code class="language-http">GET /api/{{#if entities.length}}{{toLowerCase entities.[0].name}}s{{else}}entities{{/if}}?page=1&pageSize=10
74
+ POST /api/{{#if entities.length}}{{toLowerCase entities.[0].name}}s{{else}}entities{{/if}}
75
+ PUT /api/{{#if entities.length}}{{toLowerCase entities.[0].name}}s{{else}}entities{{/if}}/:id
76
+ DELETE /api/{{#if entities.length}}{{toLowerCase entities.[0].name}}s{{else}}entities{{/if}}/:id</code></pre>
77
+
78
+ <h3 class="font-semibold mb-2 mt-6">PostgREST-like Generic Search:</h3>
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:8080/api/rpc/{{#if entities.length}}{{toLowerCase entities.[0].name}}{{else}}your_table{{/if}}?name=eq.John&age=gt.18" \
81
+ -H "Authorization: Bearer YOUR_JWT" \
82
+ -H "X-Tenant-ID: tenant_1"</code></pre>
83
+ <p class="text-sm text-gray-500 mt-2">Supported operators: <code>eq, neq, gt, gte, lt, lte, like, ilike</code></p>
84
+ </section>
85
+
86
+ <!-- Audit & Metering -->
87
+ <section id="audit-metering" class="content-section">
88
+ <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">Audit & Metering</h2>
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
+ <pre><code class="language-bash"># View Audit Logs
91
+ curl -X GET http://localhost:8080/api/audit -H "Authorization: Bearer YOUR_JWT"
92
+
93
+ # View Usage for Current Tenant
94
+ curl -X GET http://localhost:8080/api/metering/usage -H "Authorization: Bearer YOUR_JWT" -H "X-Tenant-ID: my_tenant"</code></pre>
95
+ </section>
96
+
97
+ <!-- WebSockets -->
98
+ <section id="websockets" class="content-section">
99
+ <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">2. WebSockets (REST-over-WS)</h2>
100
+ <p class="mb-4">A high-performance WebSocket dispatcher is available at <code>/ws</code>. It supports JWT authentication during connection and requires HMAC-SHA256 signatures for message integrity.</p>
101
+
102
+ <h3 class="font-semibold mb-2">Connecting via JS:</h3>
103
+ <pre><code class="language-javascript">const token = 'YOUR_JWT_TOKEN';
104
+ // Pass token as query param for auth
105
+ const ws = new WebSocket(`ws://localhost:8080/ws?token=${token}`);
106
+
107
+ ws.onopen = () => {
108
+ // Sending a signed Traced Envelope
109
+ ws.send(JSON.stringify({
110
+ TraceID: "trace-123",
111
+ Method: "GET",
112
+ Path: "/api/{{#if entities.length}}{{toLowerCase entities.[0].name}}s{{else}}entities{{/if}}",
113
+ Payload: "{}",
114
+ Signature: "hmac_sha256_hash_of_payload" // Verified on server
115
+ }));
116
+ };
117
+
118
+ ws.onmessage = (event) => console.log("Received:", event.data);</code></pre>
119
+ </section>
120
+
121
+ <!-- MQTT -->
122
+ <section id="mqtt" class="content-section">
123
+ <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">MQTT Event Streaming</h2>
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
+ <pre><code class="language-bash"># Using mosquitto_sub to listen to events
126
+ mosquitto_sub -h localhost -p 1883 -t "go-duck/events/#" -u dev_user -P dev_password</code></pre>
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
+ </section>
129
+
130
+ <!-- Kratos gRPC -->
131
+ <section id="grpc-kratos" class="content-section">
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 9000</code> by default, sharing the internal repository with Gin. It validates Keycloak JWTs via Kratos middleware.</p>
134
+ <h3 class="font-semibold mb-2">Calling the gRPC Service (Go Client Example):</h3>
135
+ <pre><code class="language-go">import (
136
+ "context"
137
+ "google.golang.org/grpc"
138
+ "golang.org/x/oauth2"
139
+ "google.golang.org/grpc/credentials/oauth"
140
+ pb "{{appName}}/api/v1"
141
+ )
142
+
143
+ // The JWT must be generated using the Keycloak secret configured in the app
144
+ token := oauth.TokenSource{TokenSource: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: "YOUR_JWT"})}
145
+
146
+ conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure(), grpc.WithPerRPCCredentials(token))
147
+ client := pb.New{{#if entities.length}}{{capitalize entities.[0].name}}{{else}}Entity{{/if}}ServiceClient(conn)
148
+
149
+ // Execute Call
150
+ res, err := client.Get{{#if entities.length}}{{capitalize entities.[0].name}}{{else}}Entity{{/if}}(context.Background(), &pb.Get{{#if entities.length}}{{capitalize entities.[0].name}}{{else}}Entity{{/if}}Request{Id: 1})
151
+ </code></pre>
152
+ </section>
153
+
154
+ <!-- GraphQL -->
155
+ <section id="graphql" class="content-section">
156
+ <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">3. GraphQL Integration</h2>
157
+ <p class="mb-4">A single unified GraphQL endpoint is available at <code>POST /graphql</code>. It integrates with GORM for efficient queries.</p>
158
+ <h3 class="font-semibold mb-2">Example Query:</h3>
159
+ <pre><code class="language-graphql">query {
160
+ get{{#if entities.length}}{{capitalize entities.[0].name}}{{else}}Entity{{/if}}(id: 1) {
161
+ id
162
+ {{#if entities.length}}
163
+ {{#if entities.[0].fields.length}}
164
+ {{entities.[0].fields.[0].name}}
165
+ {{/if}}
166
+ {{/if}}
167
+ }
168
+ }</code></pre>
169
+ <h3 class="font-semibold mb-2 mt-4">Example Request via Fetch:</h3>
170
+ <pre><code class="language-javascript">fetch('http://localhost:8080/graphql', {
171
+ method: 'POST',
172
+ headers: { 'Content-Type': 'application/json' },
173
+ body: JSON.stringify({ query: 'query { list{{#if entities.length}}{{capitalize entities.[0].name}}s{{else}}Entities{{/if}} { id } }' })
174
+ }).then(res => res.json()).then(console.log);</code></pre>
175
+ </section>
176
+
177
+ <!-- Caching & Circuit Breakers -->
178
+ <section id="resilience-cache" class="content-section">
179
+ <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">4. Resilience & Caching</h2>
180
+ <h3 class="font-semibold mb-2">Distributed Redis Caching</h3>
181
+ <p class="mb-4">The application uses Cache-Aside logic built on top of Redis. The <code>cache.GetOrSet</code> function handles tenant-prefixed caching to prevent data spillage between tenants.</p>
182
+
183
+ <h3 class="font-semibold mb-2">Circuit Breakers (Sony/Gobreaker)</h3>
184
+ <p class="mb-4">Failing network calls (like to the Database or Redis) are wrapped in Circuit Breakers. If the failure threshold is crossed, the circuit "opens" and immediately fails fast to prevent cascading failures.</p>
185
+ </section>
186
+
187
+ <!-- Security & Multi-tenancy -->
188
+ <section id="security" class="content-section">
189
+ <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">Security, OIDC & Multi-tenancy</h2>
190
+ <p class="mb-4">All `/api` endpoints are protected by <code>JWTMiddleware</code>.</p>
191
+ <ul class="list-disc pl-6 space-y-2 mb-4">
192
+ <li>Tokens are verified against the Keycloak Secret (defined in <code>application.yml</code>).</li>
193
+ <li><code>TenantMiddleware</code> reads the <code>X-Tenant-ID</code> header and dynamically routes the GORM connection to the tenant's specific database schema (if Multi-tenancy is fully implemented) or injects the Tenant ID into the request context.</li>
194
+ <li>Rate Limiting protects burst traffic (configurable in YAML).</li>
195
+ </ul>
196
+ </section>
197
+
198
+ <!-- Observability -->
199
+ <section id="observability" class="content-section">
200
+ <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">5. Observability (OTel & Datadog)</h2>
201
+ <p class="mb-4">The application uses OpenTelemetry for full-stack tracing (HTTP -> Gin Controller -> GORM DB Plugin -> PostgREST search).</p>
202
+ <ul class="list-disc pl-6 space-y-2 mb-4">
203
+ <li>Traces are exported over gRPC to the local <code>otel-collector</code> on port <code>4317</code>.</li>
204
+ <li>You can visualize these traces locally via Jaeger (<code>http://localhost:16686</code>).</li>
205
+ <li>Datadog logging is implemented in the <code>logger</code> package and can be enabled via <code>application-prod.yml</code>.</li>
206
+ </ul>
207
+ </section>
208
+
209
+ <!-- Liquibase & GDL -->
210
+ <section id="liquibase" class="content-section">
211
+ <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">6. Advanced Migrations & GDL</h2>
212
+ <p class="mb-4">Running <code>go-duck import-gdl</code> calculates stateful differences using the `.go-duck/` snapshot folder. It generates atomic, timestamped Liquibase XML changelogs in <code>migrations/liquibase/changelogs/</code>.</p>
213
+ <ul class="list-disc pl-6 space-y-2">
214
+ <li>The application auto-generates <code>master.xml</code> dynamically without "ghost" references.</li>
215
+ <li>Includes smart nullability and automatic indexing for Foreign Keys.</li>
216
+ <li><strong>Needle Support:</strong> We inject `// go-duck-needle-*` markers in <code>main.go</code> and <code>grpc.go</code> for safe evolutionary code additions without destroying manual updates.</li>
217
+ </ul>
218
+ </section>
219
+
220
+ <footer class="mt-12 pt-6 border-t border-gray-200 text-center text-gray-500 text-sm pb-12">
221
+ Generated with ❤️ by GO-DUCK CLI
222
+ </footer>
223
+ </main>
224
+ </div>
225
+ </body>
226
+ </html>
Binary file
Binary file
@@ -0,0 +1,106 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{appName}} - {{title}}</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/vs2015.min.css">
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
11
+ <script>
12
+ tailwind.config = {
13
+ theme: {
14
+ extend: {
15
+ fontFamily: { sans: ['Inter', 'sans-serif'] },
16
+ }
17
+ }
18
+ }
19
+ </script>
20
+ <script>hljs.highlightAll();</script>
21
+ <style>
22
+ .gradient-text { background: linear-gradient(90deg, #818cf8, #c084fc); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
23
+ ::-webkit-scrollbar { width: 6px; }
24
+ ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
25
+ </style>
26
+ </head>
27
+ <body class="bg-slate-50 text-slate-800 font-sans antialiased selection:bg-indigo-300 selection:text-indigo-900">
28
+ <!-- Navbar -->
29
+ <nav class="bg-slate-900/95 backdrop-blur-md shadow-lg fixed w-full z-30 top-0 h-16 flex items-center px-6 border-b border-slate-800">
30
+ <div class="flex items-center">
31
+ <img src="logo.png" alt="GO-DUCK Logo" class="h-10 w-auto mr-4 rounded-lg shadow-sm">
32
+ <div class="text-xl font-bold text-white tracking-tight">GO-DUCK <span class="text-indigo-300 text-xs font-semibold ml-2 inline-flex items-center px-2 py-0.5 rounded-full bg-indigo-500/20 border border-indigo-500/30 uppercase tracking-wider hidden sm:inline-flex">Developer Guide</span></div>
33
+ </div>
34
+ </nav>
35
+
36
+ <div class="flex max-w-[90rem] mx-auto pt-16">
37
+ <!-- Sidebar -->
38
+ <aside class="w-72 fixed h-[calc(100vh-4rem)] overflow-y-auto bg-slate-50 border-r border-slate-200 p-6 hidden lg:block scroll-smooth">
39
+ <h3 class="font-bold text-slate-400 uppercase text-[10px] tracking-widest mb-4 flex items-center">
40
+ <svg class="w-3 h-3 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7"></path></svg>
41
+ Table of Contents
42
+ </h3>
43
+ <ul class="space-y-1 text-sm font-medium text-slate-600">
44
+ <li><a href="index.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'index')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">Home & Quick Start</a></li>
45
+
46
+ <li class="pt-5 pb-1 px-3"><span class="font-bold text-slate-900 uppercase text-[10px] tracking-widest">1. Core Concepts</span></li>
47
+ <li><a href="gdl.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'gdl')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">GDL & Framework</a></li>
48
+ <li><a href="cli.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'cli')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">CLI & Code Injection</a></li>
49
+
50
+ <li class="pt-5 pb-1 px-3"><span class="font-bold text-slate-900 uppercase text-[10px] tracking-widest">2. API Endpoints</span></li>
51
+ <li><a href="rest.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'rest')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">REST & Generic Search</a></li>
52
+ <li><a href="graphql.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'graphql')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">GraphQL Integration</a></li>
53
+ <li><a href="grpc.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'grpc')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">Secured gRPC (Kratos)</a></li>
54
+
55
+ <li class="pt-5 pb-1 px-3"><span class="font-bold text-slate-900 uppercase text-[10px] tracking-widest">3. Real-Time & Audit</span></li>
56
+ <li><a href="realtime.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'realtime')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">WebSockets & MQTT</a></li>
57
+ <li><a href="audit.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'audit')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">Audit & Metering</a></li>
58
+
59
+ <li class="pt-5 pb-1 px-3"><span class="font-bold text-slate-900 uppercase text-[10px] tracking-widest">4. Infrastructure</span></li>
60
+ <li><a href="security.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'security')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">Security & Resilience</a></li>
61
+ <li><a href="observability.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'observability')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">Observability</a></li>
62
+
63
+ <li class="pt-5 pb-1 px-3"><span class="font-bold text-slate-900 uppercase text-[10px] tracking-widest">5. References</span></li>
64
+ <li><a href="integrations.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'integrations')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">Client Integrations</a></li>
65
+ </ul>
66
+ </aside>
67
+
68
+ <main class="flex-1 lg:ml-72 p-6 md:p-10 max-w-4xl xl:max-w-5xl">
69
+ <!-- White Content Card -->
70
+ <div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-8 md:p-12 mb-10 relative overflow-hidden">
71
+ <!-- Background Decoration -->
72
+ <div class="absolute top-0 right-0 w-64 h-64 bg-slate-50 rounded-bl-full -z-0"></div>
73
+
74
+ <!-- Content injected here (raised z-index) -->
75
+ <div class="relative z-10">
76
+ {{{body}}}
77
+ </div>
78
+ </div>
79
+
80
+ <footer class="mt-8 text-center text-slate-400 text-sm mb-8 flex justify-center items-center space-x-2">
81
+ <span>Generated with</span>
82
+ <span class="text-lg">🦆</span>
83
+ <span>by <strong>GO-DUCK CLI</strong></span>
84
+ </footer>
85
+ </main>
86
+ </div>
87
+ <!-- Lightbox Overlay -->
88
+ <div id="lightbox" class="fixed inset-0 bg-slate-900/90 backdrop-blur-sm z-[100] hidden flex items-center justify-center p-4 md:p-10 cursor-zoom-out" onclick="this.classList.add('hidden')">
89
+ <div class="relative max-w-5xl w-full h-full flex items-center justify-center">
90
+ <img id="lightbox-img" src="" alt="Enlarged View" class="max-w-full max-h-full object-contain rounded-xl shadow-2xl transition-all duration-300">
91
+ <button class="absolute top-0 right-0 m-4 text-white hover:text-indigo-300 transition-colors">
92
+ <svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
93
+ </button>
94
+ </div>
95
+ </div>
96
+
97
+ <script>
98
+ function openLightbox(src) {
99
+ const lb = document.getElementById('lightbox');
100
+ const img = document.getElementById('lightbox-img');
101
+ img.src = src;
102
+ lb.classList.remove('hidden');
103
+ }
104
+ </script>
105
+ </body>
106
+ </html>
Binary file
@@ -0,0 +1,39 @@
1
+ <h1 class="text-4xl font-extrabold text-gray-900 mb-4">Audit Trails & Tenant Metering</h1>
2
+ <p class="text-lg text-gray-600 mb-8">Maintain strict compliance and automatically track API consumption rates for multi-tenant applications.</p>
3
+
4
+ <section class="mb-10">
5
+ <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">The `@Audited` Engine (Dual Layer)</h2>
6
+ <p class="mb-4">By tagging an entity with <code>@Audited</code> inside your <code>GDL</code> file, GO-DUCK injects an advanced two-tier auditing strategy automatically into your microservice.</p>
7
+
8
+ <div class="bg-indigo-50 border-l-4 border-indigo-500 p-4 mb-6 rounded-r">
9
+ <p class="text-indigo-900"><strong>Note:</strong> All Audit Logs are automatically driven completely via Keycloak JWT Token Extraction!</p>
10
+ </div>
11
+
12
+ <h3 class="font-semibold mb-2 mt-6">Layer 1: Inline Entity Columns</h3>
13
+ <p class="mb-4 text-gray-700">Instead of generic <code>created_at</code> and <code>updated_at</code> timestamps, an <code>@Audited</code> object inherently gains these columns directly on the database row:</p>
14
+ <ul class="list-disc pl-6 space-y-2 mb-4 text-gray-700 font-mono text-sm bg-gray-100 p-4 rounded">
15
+ <li>created_by (String - e.g., "John Doe")</li>
16
+ <li>created_date (TIMESTAMP)</li>
17
+ <li>last_modified_by (String - e.g., "Jane Doe")</li>
18
+ <li>last_modified_date (TIMESTAMP)</li>
19
+ <li>last_modified_user_id (String - Global Auth Provider UUID)</li>
20
+ </ul>
21
+
22
+ <h3 class="font-semibold mb-2 mt-8">Layer 2: The Central `audit_log` Tracker Table</h3>
23
+ <p class="mb-4 text-gray-700">A global <code>audit_log</code> timeline is maintained across your database. For every mutable Action (CREATE, UPDATE, DELETE), the middleware diffs the pre-mutation HTTP object and the post-mutation object. It stores the explicit JSON payload changes asynchronously!</p>
24
+ <pre><code class="language-bash"># Quickly view all changes securely over REST
25
+ curl -X GET "http://localhost:8080/api/audit?entityName={{capitalize (defaultStr (lookup entities "0.name") "Entity")}}" \
26
+ -H "Authorization: Bearer YOUR_JWT"
27
+ </code></pre>
28
+ </section>
29
+
30
+ <section class="mb-10">
31
+ <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">Multi-Tenant Metering</h2>
32
+ <p class="mb-4">API consumption tracking is inherently built in. When fully enabled, this records usage hits explicitly tracked against the <code>X-Tenant-ID</code> attached to the requests over time. This metric is ideal for setting up a usage-based billing logic or preventing internal abuse.</p>
33
+
34
+ <pre><code class="language-bash"># The Tenant retrieves their own API request footprints:
35
+ curl -X GET "http://localhost:8080/api/metering/usage" \
36
+ -H "Authorization: Bearer YOUR_JWT" \
37
+ -H "X-Tenant-ID: tenant_B_stripe"
38
+ </code></pre>
39
+ </section>