go-duck-cli 1.3.3 → 1.3.31

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "go-duck-cli",
3
- "version": "1.3.3",
3
+ "version": "1.3.31",
4
4
  "description": "The Ultimate Evolutionary Go Microservice Scaffolder.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -5,6 +5,7 @@ import (
5
5
  "fmt"
6
6
  "log"
7
7
  "net/http"
8
+ "strings"
8
9
  "time"
9
10
 
10
11
  "github.com/gin-gonic/gin"
@@ -164,39 +165,228 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
164
165
  // Swagger Docs & UI
165
166
  r.StaticFile("/swagger.json", "./docs/swagger.json")
166
167
  r.GET("/swagger", func(c *gin.Context) {
167
- c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(`<!DOCTYPE html>
168
+ swaggerHTML := `<!DOCTYPE html>
168
169
  <html lang="en">
169
170
  <head>
170
171
  <meta charset="UTF-8">
171
- <title>Swagger UI</title>
172
+ <title>Swagger UI (Secured)</title>
172
173
  <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui.css" />
174
+ <script src="https://cdn.jsdelivr.net/npm/keycloak-js@24.0.4/dist/keycloak.min.js"></script>
173
175
  <style>
174
176
  html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; }
175
177
  *, *:before, *:after { box-sizing: inherit; }
176
- body { margin:0; background: #fafafa; }
178
+ body { margin:0; background: #fafafa; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
179
+
180
+ .top-nav {
181
+ background: rgba(255, 255, 255, 0.8);
182
+ backdrop-filter: blur(10px);
183
+ -webkit-backdrop-filter: blur(10px);
184
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
185
+ padding: 12px 24px;
186
+ display: flex;
187
+ justify-content: space-between;
188
+ align-items: center;
189
+ position: sticky;
190
+ top: 0;
191
+ z-index: 1000;
192
+ box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05);
193
+ }
194
+
195
+ .nav-brand {
196
+ font-weight: 700;
197
+ font-size: 1.2rem;
198
+ color: #333;
199
+ display: flex;
200
+ align-items: center;
201
+ gap: 10px;
202
+ }
203
+
204
+ .nav-controls {
205
+ display: flex;
206
+ align-items: center;
207
+ gap: 16px;
208
+ }
209
+
210
+ .tenant-input {
211
+ padding: 8px 12px;
212
+ border: 1px solid #ddd;
213
+ border-radius: 6px;
214
+ font-size: 0.9rem;
215
+ outline: none;
216
+ transition: border-color 0.2s;
217
+ }
218
+
219
+ .tenant-input:focus {
220
+ border-color: #4a90e2;
221
+ }
222
+
223
+ .btn {
224
+ padding: 8px 16px;
225
+ border-radius: 6px;
226
+ border: none;
227
+ font-weight: 600;
228
+ cursor: pointer;
229
+ transition: all 0.2s;
230
+ font-size: 0.9rem;
231
+ }
232
+
233
+ .btn-login {
234
+ background: #4a90e2;
235
+ color: white;
236
+ }
237
+
238
+ .btn-login:hover {
239
+ background: #357abd;
240
+ }
241
+
242
+ .btn-logout {
243
+ background: #e74c3c;
244
+ color: white;
245
+ display: none;
246
+ }
247
+
248
+ .btn-logout:hover {
249
+ background: #c0392b;
250
+ }
251
+
252
+ .status-indicator {
253
+ display: flex;
254
+ align-items: center;
255
+ gap: 6px;
256
+ font-size: 0.85rem;
257
+ color: #666;
258
+ }
259
+
260
+ .dot {
261
+ width: 8px;
262
+ height: 8px;
263
+ border-radius: 50%;
264
+ background: #ccc;
265
+ }
266
+
267
+ .dot.active {
268
+ background: #2ecc71;
269
+ box-shadow: 0 0 8px #2ecc71;
270
+ }
177
271
  </style>
178
272
  </head>
179
273
  <body>
274
+ <div class="top-nav">
275
+ <div class="nav-brand">
276
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
277
+ GO-DUCK API Explorer
278
+ </div>
279
+ <div class="nav-controls">
280
+ <div class="status-indicator">
281
+ <div class="dot" id="auth-dot"></div>
282
+ <span id="auth-status">Unauthenticated</span>
283
+ </div>
284
+ <input type="text" id="tenant-input" class="tenant-input" placeholder="X-Tenant-ID" value="tenant_1" title="Multi-tenant DB Target" />
285
+ <button id="login-btn" class="btn btn-login">Login with Keycloak</button>
286
+ <button id="logout-btn" class="btn btn-logout">Logout</button>
287
+ </div>
288
+ </div>
289
+
180
290
  <div id="swagger-ui"></div>
291
+
181
292
  <script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-bundle.js"></script>
182
293
  <script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-standalone-preset.js"></script>
183
294
  <script>
295
+ const KEYCLOAK_URL = 'KEYCLOAK_URL_PLACEHOLDER';
296
+ const KEYCLOAK_REALM = 'KEYCLOAK_REALM_PLACEHOLDER';
297
+ const KEYCLOAK_CLIENT = 'KEYCLOAK_CLIENT_PLACEHOLDER';
298
+
299
+ let keycloak = null;
300
+
301
+ function updateUI(authenticated) {
302
+ if (authenticated) {
303
+ document.getElementById('login-btn').style.display = 'none';
304
+ document.getElementById('logout-btn').style.display = 'block';
305
+ document.getElementById('auth-dot').classList.add('active');
306
+ document.getElementById('auth-status').innerText = 'Authenticated (' + (keycloak.tokenParsed?.preferred_username || 'User') + ')';
307
+ } else {
308
+ document.getElementById('login-btn').style.display = 'block';
309
+ document.getElementById('logout-btn').style.display = 'none';
310
+ document.getElementById('auth-dot').classList.remove('active');
311
+ document.getElementById('auth-status').innerText = 'Unauthenticated';
312
+ }
313
+ }
314
+
184
315
  window.onload = function() {
185
- const ui = SwaggerUIBundle({
186
- url: "/swagger.json",
187
- dom_id: '#swagger-ui',
188
- deepLinking: true,
189
- presets: [
190
- SwaggerUIBundle.presets.apis,
191
- SwaggerUIStandalonePreset
192
- ],
193
- layout: "StandaloneLayout"
316
+ // Initialize Keycloak
317
+ keycloak = new Keycloak({
318
+ url: KEYCLOAK_URL,
319
+ realm: KEYCLOAK_REALM,
320
+ clientId: KEYCLOAK_CLIENT
321
+ });
322
+
323
+ keycloak.init({ onLoad: 'check-sso', checkLoginIframe: false }).then(authenticated => {
324
+ updateUI(authenticated);
325
+
326
+ document.getElementById('login-btn').onclick = () => keycloak.login();
327
+ document.getElementById('logout-btn').onclick = () => keycloak.logout();
328
+
329
+ if (authenticated) {
330
+ // Auto-refresh token logic
331
+ setInterval(() => {
332
+ keycloak.updateToken(70).then(refreshed => {
333
+ if (refreshed) {
334
+ console.log('Token refreshed automatically');
335
+ }
336
+ }).catch(() => {
337
+ console.error('Failed to refresh token');
338
+ updateUI(false);
339
+ });
340
+ }, 60000); // Check every minute
341
+ }
342
+
343
+ // Initialize Swagger UI with Interceptor
344
+ window.ui = SwaggerUIBundle({
345
+ url: "/swagger.json",
346
+ dom_id: '#swagger-ui',
347
+ deepLinking: true,
348
+ presets: [
349
+ SwaggerUIBundle.presets.apis,
350
+ SwaggerUIStandalonePreset
351
+ ],
352
+ layout: "StandaloneLayout",
353
+ requestInterceptor: (req) => {
354
+ if (keycloak && keycloak.token) {
355
+ req.headers['Authorization'] = 'Bearer ' + keycloak.token;
356
+ }
357
+ const tenantId = document.getElementById('tenant-input').value;
358
+ if (tenantId) {
359
+ req.headers['X-Tenant-ID'] = tenantId;
360
+ }
361
+ return req;
362
+ }
363
+ });
364
+ }).catch(err => {
365
+ console.error("Keycloak initialization failed", err);
366
+ document.getElementById('auth-status').innerText = 'Keycloak init failed';
367
+
368
+ // Still load swagger without auth interceptor
369
+ window.ui = SwaggerUIBundle({
370
+ url: "/swagger.json",
371
+ dom_id: '#swagger-ui',
372
+ deepLinking: true,
373
+ presets: [
374
+ SwaggerUIBundle.presets.apis,
375
+ SwaggerUIStandalonePreset
376
+ ],
377
+ layout: "StandaloneLayout"
378
+ });
194
379
  });
195
- window.ui = ui;
196
380
  };
197
381
  </script>
198
382
  </body>
199
- </html>`))
383
+ </html>`
384
+
385
+ htmlStr := strings.ReplaceAll(swaggerHTML, "KEYCLOAK_URL_PLACEHOLDER", appConfig.GoDuck.Security.KeycloakHost)
386
+ htmlStr = strings.ReplaceAll(htmlStr, "KEYCLOAK_REALM_PLACEHOLDER", appConfig.GoDuck.Security.KeycloakRealm)
387
+ htmlStr = strings.ReplaceAll(htmlStr, "KEYCLOAK_CLIENT_PLACEHOLDER", appConfig.GoDuck.Security.KeycloakAppClientID)
388
+
389
+ c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(htmlStr))
200
390
  })
201
391
 
202
392
  // Management APIs (Run-time DB onboarding)