go-duck-cli 1.3.2 → 1.3.3

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.
@@ -5,6 +5,9 @@ export const generateAIDocs = async (config, entities, outputDir, enums, openEnt
5
5
  const aiDocsDir = path.join(outputDir, 'docs', 'ai');
6
6
  await fs.ensureDir(aiDocsDir);
7
7
 
8
+ const apiPrefixRaw = config.server?.rest?.['api-path-prefix'] || '/api';
9
+ const apiPrefix = apiPrefixRaw.endsWith('/') ? apiPrefixRaw.slice(0, -1) : apiPrefixRaw;
10
+
8
11
  // 1. ARCHITECTURE.md
9
12
  const appName = config.name || 'go-duck-app';
10
13
  const hasMongo = config.datasource?.mongodb?.enabled;
@@ -32,7 +35,7 @@ export const generateAIDocs = async (config, entities, outputDir, enums, openEnt
32
35
  archContent += `- **Active**: ${storageActive ? 'Yes' : 'No'}\n`;
33
36
  if (storageActive) {
34
37
  archContent += `- **Enabled Nodes**: ${Object.keys(config.storage).filter(k => config.storage[k]?.enabled).join(', ')}\n`;
35
- archContent += `- **Endpoints**: \n - Upload: \`POST /api/storage/upload?provider=\`\n - Exact Retrieve: \`GET /api/storage/download/*key?provider=\`\n - Cross-Scan Locate: \`GET /api/storage/scan/*key\`\n`;
38
+ archContent += `- **Endpoints**: \n - Upload: \`POST ${apiPrefix}/storage/upload?provider=\`\n - Exact Retrieve: \`GET ${apiPrefix}/storage/download/*key?provider=\`\n - Cross-Scan Locate: \`GET ${apiPrefix}/storage/scan/*key\`\n`;
36
39
  }
37
40
  const bootstrapActive = config.storage?.bootstrap?.enabled;
38
41
  if (bootstrapActive) {
@@ -53,25 +56,25 @@ export const generateAIDocs = async (config, entities, outputDir, enums, openEnt
53
56
 
54
57
  // 2. ENDPOINTS.md
55
58
  let endpointsContent = `# REST & gRPC API Surface\n\n`;
56
- endpointsContent += `## Base Path: \`/api\`\n\n`;
59
+ endpointsContent += `## Base Path: \`${apiPrefix}\`\n\n`;
57
60
  endpointsContent += `### Standard Entity Endpoints\n`;
58
61
  for (const entity of entities) {
59
62
  const routeName = entity.name.toLowerCase() + 's';
60
63
  endpointsContent += `\n#### ${entity.name}\n`;
61
- endpointsContent += `- \`GET /api/${routeName}\` (Pagination & dynamic sorting, e.g. \`?page=1&size=10&eager=true&sort=id,asc\`)\n`;
62
- endpointsContent += `- \`GET /api/${routeName}/:id\`\n`;
63
- endpointsContent += `- \`POST /api/${routeName}\`\n`;
64
- endpointsContent += `- \`PUT /api/${routeName}/:id\`\n`;
65
- endpointsContent += `- \`DELETE /api/${routeName}/:id\`\n`;
66
- endpointsContent += `- \`POST /api/${routeName}/bulk\` (Bulk Create/Update)\n`;
64
+ endpointsContent += `- \`GET ${apiPrefix}/${routeName}\` (Pagination & dynamic sorting, e.g. \`?page=1&size=10&eager=true&sort=id,asc\`)\n`;
65
+ endpointsContent += `- \`GET ${apiPrefix}/${routeName}/:id\`\n`;
66
+ endpointsContent += `- \`POST ${apiPrefix}/${routeName}\`\n`;
67
+ endpointsContent += `- \`PUT ${apiPrefix}/${routeName}/:id\`\n`;
68
+ endpointsContent += `- \`DELETE ${apiPrefix}/${routeName}/:id\`\n`;
69
+ endpointsContent += `- \`POST ${apiPrefix}/${routeName}/bulk\` (Bulk Create/Update)\n`;
67
70
  if (entity.isSearchable) {
68
- endpointsContent += `- \`GET /api/search/${routeName}?q={query}\` (Elasticsearch)\n`;
71
+ endpointsContent += `- \`GET ${apiPrefix}/search/${routeName}?q={query}\` (Elasticsearch)\n`;
69
72
  }
70
73
  }
71
74
 
72
75
  endpointsContent += `\n## Infrastructure & Management APIs (SuperAdmin Protected)\n`;
73
- endpointsContent += `- \`GET /api/admin/audit\` (Fetches Global Delta logs from the Centralized Audit Engine)\n`;
74
- endpointsContent += `- \`POST /api/admin/metering/limit\` (Set SaaS Quota limits)\n`;
76
+ endpointsContent += `- \`GET ${apiPrefix}/admin/audit\` (Fetches Global Delta logs from the Centralized Audit Engine)\n`;
77
+ endpointsContent += `- \`POST ${apiPrefix}/admin/metering/limit\` (Set SaaS Quota limits)\n`;
75
78
  endpointsContent += `- \`POST /management/tenant/assign\` (Dynamically initialize new Tenant DBs)\n`;
76
79
 
77
80
  endpointsContent += `\n## Open APIs (No JWT required)\n`;
@@ -210,10 +210,26 @@ services:
210
210
  - GO_DUCK_DATASOURCE_PASSWORD=${config.datasource?.password || 'password'}
211
211
  - GO_DUCK_DATASOURCE_DATABASE=${config.datasource?.database || 'go_duck_master'}
212
212
  - GO_DUCK_DATASOURCE_PORT=5432
213
+ - GO_DUCK_DATASOURCE_MONGODB_ENABLED=${config.datasource?.mongodb?.enabled ? 'true' : 'false'}
214
+ - GO_DUCK_DATASOURCE_MONGODB_URI=mongodb://mongodb:27017
215
+ - GO_DUCK_DATASOURCE_MONGODB_DATABASE=${config.datasource?.mongodb?.database || 'goduck_document_store'}
213
216
  - GO_DUCK_CACHE_REDIS_HOST=redis:6379
217
+ - GO_DUCK_CACHE_REDIS_PASSWORD=${config.cache?.redis?.password || ''}
214
218
  - GO_DUCK_MESSAGING_MQTT_BROKER=tcp://mosquitto:1883
219
+ - GO_DUCK_MESSAGING_MQTT_USERNAME=${config.messaging?.mqtt?.username || 'dev_user'}
220
+ - GO_DUCK_MESSAGING_MQTT_PASSWORD=${config.messaging?.mqtt?.password || 'dev_password'}
215
221
  - GO_DUCK_TELEMETRY_OTEL_ENDPOINT=otel-collector:4317
216
222
  - GO_DUCK_ELASTICSEARCH_ADDRESSES=http://elasticsearch:9200
223
+ - GO_DUCK_ELASTICSEARCH_USERNAME=${config.elasticsearch?.username || 'elastic'}
224
+ - GO_DUCK_ELASTICSEARCH_PASSWORD=${config.elasticsearch?.password || 'changeme'}
225
+ - GO_DUCK_SECURITY_KEYCLOAK_HOST=http://keycloak:8080
226
+ - GO_DUCK_SECURITY_KEYCLOAK_REALM=${config.security?.['keycloak-realm'] || 'go-duck-realm'}
227
+ - GO_DUCK_SECURITY_KEYCLOAK_APP_CLIENT_ID=${config.security?.['keycloak-app-client-id'] || 'go-duck-app'}
228
+ - GO_DUCK_SECURITY_KEYCLOAK_SERVICE_CLIENT_ID=${config.security?.['keycloak-service-client-id'] || 'go-duck-service'}
229
+ - GO_DUCK_SECURITY_KEYCLOAK_SERVICE_SECRET=${config.security?.['keycloak-service-secret'] || 'service-secret-123'}
230
+ - GO_DUCK_STORAGE_MINIO_ENDPOINT=http://minio:9000
231
+ - GO_DUCK_STORAGE_MINIO_ACCESS_KEY=${config.storage?.minio?.['access-key'] || 'minioadmin'}
232
+ - GO_DUCK_STORAGE_MINIO_SECRET_KEY=${config.storage?.minio?.['secret-key'] || 'minioadmin'}
217
233
  restart: always
218
234
  networks:
219
235
  - go-duck-net
@@ -244,10 +260,26 @@ services:
244
260
  - GO_DUCK_DATASOURCE_PASSWORD=${config.datasource?.password || 'password'}
245
261
  - GO_DUCK_DATASOURCE_DATABASE=${config.datasource?.database || 'go_duck_master'}
246
262
  - GO_DUCK_DATASOURCE_PORT=5432
263
+ - GO_DUCK_DATASOURCE_MONGODB_ENABLED=${config.datasource?.mongodb?.enabled ? 'true' : 'false'}
264
+ - GO_DUCK_DATASOURCE_MONGODB_URI=mongodb://mongodb:27017
265
+ - GO_DUCK_DATASOURCE_MONGODB_DATABASE=${config.datasource?.mongodb?.database || 'goduck_document_store'}
247
266
  - GO_DUCK_CACHE_REDIS_HOST=redis:6379
267
+ - GO_DUCK_CACHE_REDIS_PASSWORD=${config.cache?.redis?.password || ''}
248
268
  - GO_DUCK_MESSAGING_MQTT_BROKER=tcp://mosquitto:1883
269
+ - GO_DUCK_MESSAGING_MQTT_USERNAME=${config.messaging?.mqtt?.username || 'dev_user'}
270
+ - GO_DUCK_MESSAGING_MQTT_PASSWORD=${config.messaging?.mqtt?.password || 'dev_password'}
249
271
  - GO_DUCK_TELEMETRY_OTEL_ENDPOINT=otel-collector:4317
250
272
  - GO_DUCK_ELASTICSEARCH_ADDRESSES=http://elasticsearch:9200
273
+ - GO_DUCK_ELASTICSEARCH_USERNAME=${config.elasticsearch?.username || 'elastic'}
274
+ - GO_DUCK_ELASTICSEARCH_PASSWORD=${config.elasticsearch?.password || 'changeme'}
275
+ - GO_DUCK_SECURITY_KEYCLOAK_HOST=http://keycloak:8080
276
+ - GO_DUCK_SECURITY_KEYCLOAK_REALM=${config.security?.['keycloak-realm'] || 'go-duck-realm'}
277
+ - GO_DUCK_SECURITY_KEYCLOAK_APP_CLIENT_ID=${config.security?.['keycloak-app-client-id'] || 'go-duck-app'}
278
+ - GO_DUCK_SECURITY_KEYCLOAK_SERVICE_CLIENT_ID=${config.security?.['keycloak-service-client-id'] || 'go-duck-service'}
279
+ - GO_DUCK_SECURITY_KEYCLOAK_SERVICE_SECRET=${config.security?.['keycloak-service-secret'] || 'service-secret-123'}
280
+ - GO_DUCK_STORAGE_MINIO_ENDPOINT=http://minio:9000
281
+ - GO_DUCK_STORAGE_MINIO_ACCESS_KEY=${config.storage?.minio?.['access-key'] || 'minioadmin'}
282
+ - GO_DUCK_STORAGE_MINIO_SECRET_KEY=${config.storage?.minio?.['secret-key'] || 'minioadmin'}
251
283
  depends_on:
252
284
  postgres:
253
285
  condition: service_healthy
@@ -467,18 +499,48 @@ spec:
467
499
  value: "${config.datasource?.database || 'go_duck_master'}"
468
500
  - name: GO_DUCK_DATASOURCE_PORT
469
501
  value: "5432"
502
+ - name: GO_DUCK_DATASOURCE_MONGODB_ENABLED
503
+ value: "${config.datasource?.mongodb?.enabled ? 'true' : 'false'}"
504
+ - name: GO_DUCK_DATASOURCE_MONGODB_URI
505
+ value: mongodb://mongodb.${shortName}-mongodb-k8s.svc.cluster.local:27017
506
+ - name: GO_DUCK_DATASOURCE_MONGODB_DATABASE
507
+ value: "${config.datasource?.mongodb?.database || 'goduck_document_store'}"
470
508
  - name: GO_DUCK_CACHE_REDIS_HOST
471
509
  value: redis.${shortName}-redis-k8s.svc.cluster.local:6379
510
+ - name: GO_DUCK_CACHE_REDIS_PASSWORD
511
+ value: "${config.cache?.redis?.password || ''}"
472
512
  - name: GO_DUCK_MESSAGING_MQTT_BROKER
473
513
  value: tcp://mosquitto.${shortName}-mosquitto-k8s.svc.cluster.local:1883
514
+ - name: GO_DUCK_MESSAGING_MQTT_USERNAME
515
+ value: "${config.messaging?.mqtt?.username || 'dev_user'}"
516
+ - name: GO_DUCK_MESSAGING_MQTT_PASSWORD
517
+ value: "${config.messaging?.mqtt?.password || 'dev_password'}"
474
518
  - name: GO_DUCK_MESSAGING_NATS_URL
475
519
  value: nats://nats.${shortName}-nats-k8s.svc.cluster.local:4222
476
520
  - name: GO_DUCK_TELEMETRY_OTEL_ENDPOINT
477
521
  value: otel-collector.${shortName}-otel-collector-k8s.svc.cluster.local:4317
478
522
  - name: GO_DUCK_ELASTICSEARCH_ADDRESSES
479
523
  value: http://elasticsearch.${shortName}-elasticsearch-k8s.svc.cluster.local:9200
524
+ - name: GO_DUCK_ELASTICSEARCH_USERNAME
525
+ value: "${config.elasticsearch?.username || 'elastic'}"
526
+ - name: GO_DUCK_ELASTICSEARCH_PASSWORD
527
+ value: "${config.elasticsearch?.password || 'changeme'}"
480
528
  - name: GO_DUCK_SECURITY_KEYCLOAK_HOST
481
529
  value: http://keycloak.${shortName}-keycloak-k8s.svc.cluster.local:8080
530
+ - name: GO_DUCK_SECURITY_KEYCLOAK_REALM
531
+ value: "${config.security?.['keycloak-realm'] || 'go-duck-realm'}"
532
+ - name: GO_DUCK_SECURITY_KEYCLOAK_APP_CLIENT_ID
533
+ value: "${config.security?.['keycloak-app-client-id'] || 'go-duck-app'}"
534
+ - name: GO_DUCK_SECURITY_KEYCLOAK_SERVICE_CLIENT_ID
535
+ value: "${config.security?.['keycloak-service-client-id'] || 'go-duck-service'}"
536
+ - name: GO_DUCK_SECURITY_KEYCLOAK_SERVICE_SECRET
537
+ value: "${config.security?.['keycloak-service-secret'] || 'service-secret-123'}"
538
+ - name: GO_DUCK_STORAGE_MINIO_ENDPOINT
539
+ value: "http://minio.${shortName}-minio-k8s.svc.cluster.local:9000"
540
+ - name: GO_DUCK_STORAGE_MINIO_ACCESS_KEY
541
+ value: "${config.storage?.minio?.['access-key'] || 'minioadmin'}"
542
+ - name: GO_DUCK_STORAGE_MINIO_SECRET_KEY
543
+ value: "${config.storage?.minio?.['secret-key'] || 'minioadmin'}"
482
544
  ports:
483
545
  - name: http
484
546
  containerPort: ${appPort}
@@ -4,6 +4,9 @@ import chalk from 'chalk';
4
4
 
5
5
  export const generatePostmanCollection = async (config, entities, outputDir, openEntities = []) => {
6
6
  const docsDir = path.join(outputDir, 'docs');
7
+ const apiPrefixRaw = config.server?.rest?.['api-path-prefix'] || '/api';
8
+ const apiPrefix = apiPrefixRaw.endsWith('/') ? apiPrefixRaw.slice(0, -1) : apiPrefixRaw;
9
+ const apiPathArr = apiPrefix.split('/').filter(p => p !== '');
7
10
  await fs.ensureDir(docsDir);
8
11
 
9
12
  const collection = {
@@ -140,11 +143,11 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
140
143
  { key: "X-Tenant-ID", value: "{{tenant}}" }
141
144
  ],
142
145
  url: {
143
- raw: "http://{{host}}:{{port}}/api/admin/audit",
146
+ raw: `http://{{host}}:{{port}}${apiPrefix}/admin/audit`,
144
147
  protocol: "http",
145
148
  host: ["{{host}}"],
146
149
  port: "{{port}}",
147
- path: ["api", "admin", "audit"]
150
+ path: [...apiPathArr, "admin", "audit"]
148
151
  }
149
152
  }
150
153
  },
@@ -157,11 +160,11 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
157
160
  { key: "X-Tenant-ID", value: "{{tenant}}" }
158
161
  ],
159
162
  url: {
160
- raw: "http://{{host}}:{{port}}/api/metering/usage",
163
+ raw: `http://{{host}}:{{port}}${apiPrefix}/metering/usage`,
161
164
  protocol: "http",
162
165
  host: ["{{host}}"],
163
166
  port: "{{port}}",
164
- path: ["api", "metering", "usage"]
167
+ path: [...apiPathArr, "metering", "usage"]
165
168
  }
166
169
  }
167
170
  }
@@ -390,7 +393,7 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
390
393
  request: {
391
394
  method: "GET",
392
395
  header: [ { key: "X-Tenant-ID", value: "{{tenant}}" } ],
393
- url: { raw: `http://{{host}}:{{port}}/open/api/${name}s?page=1&size=10&eager=true&sort=id,asc`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: ["open", "api", `${name}s`], query: [ { key: "page", value: "1" }, { key: "size", value: "10" }, { key: "eager", value: "true" }, { key: "sort", value: "id,asc" } ] }
396
+ url: { raw: `http://{{host}}:{{port}}/open${apiPrefix}/${name}s?page=1&size=10&eager=true&sort=id,asc`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: ["open", ...apiPathArr, `${name}s`], query: [ { key: "page", value: "1" }, { key: "size", value: "10" }, { key: "eager", value: "true" }, { key: "sort", value: "id,asc" } ] }
394
397
  }
395
398
  });
396
399
  publicItems.push({
@@ -398,7 +401,7 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
398
401
  request: {
399
402
  method: "GET",
400
403
  header: [ { key: "X-Tenant-ID", value: "{{tenant}}" } ],
401
- url: { raw: `http://{{host}}:{{port}}/open/api/${name}s/1`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: ["open", "api", `${name}s`, "1"] }
404
+ url: { raw: `http://{{host}}:{{port}}/open${apiPrefix}/${name}s/1`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: ["open", ...apiPathArr, `${name}s`, "1"] }
402
405
  }
403
406
  });
404
407
  }
@@ -409,7 +412,7 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
409
412
  method: "POST",
410
413
  header: [ { key: "X-Tenant-ID", value: "{{tenant}}" }, { key: "Content-Type", value: "application/json" } ],
411
414
  body: { mode: "raw", raw: generateDummyJson(entity.fields) },
412
- url: { raw: `http://{{host}}:{{port}}/open/api/${name}s`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: ["open", "api", `${name}s`] }
415
+ url: { raw: `http://{{host}}:{{port}}/open${apiPrefix}/${name}s`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: ["open", ...apiPathArr, `${name}s`] }
413
416
  }
414
417
  });
415
418
  }
@@ -425,7 +428,7 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
425
428
  request: {
426
429
  method: "GET",
427
430
  header: [ { key: "Authorization", value: "Bearer {{token}}" }, { key: "X-Tenant-ID", value: "{{tenant}}" } ],
428
- url: { raw: `http://{{host}}:{{port}}/api/${name}s?page=1&size=10&eager=true&sort=id,asc`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: ["api", `${name}s`], query: [ { key: "page", value: "1" }, { key: "size", value: "10" }, { key: "eager", value: "true" }, { key: "sort", value: "id,asc" } ] }
431
+ url: { raw: `http://{{host}}:{{port}}${apiPrefix}/${name}s?page=1&size=10&eager=true&sort=id,asc`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: [...apiPathArr, `${name}s`], query: [ { key: "page", value: "1" }, { key: "size", value: "10" }, { key: "eager", value: "true" }, { key: "sort", value: "id,asc" } ] }
429
432
  }
430
433
  },
431
434
  {
@@ -434,7 +437,7 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
434
437
  method: "POST",
435
438
  header: [ { key: "Authorization", value: "Bearer {{token}}" }, { key: "X-Tenant-ID", value: "{{tenant}}" }, { key: "Content-Type", value: "application/json" } ],
436
439
  body: { mode: "raw", raw: generateDummyJson(entity.fields) },
437
- url: { raw: `http://{{host}}:{{port}}/api/${name}s`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: ["api", `${name}s`] }
440
+ url: { raw: `http://{{host}}:{{port}}${apiPrefix}/${name}s`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: [...apiPathArr, `${name}s`] }
438
441
  }
439
442
  },
440
443
  {
@@ -442,7 +445,7 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
442
445
  request: {
443
446
  method: "GET",
444
447
  header: [ { key: "Authorization", value: "Bearer {{token}}" }, { key: "X-Tenant-ID", value: "{{tenant}}" } ],
445
- url: { raw: `http://{{host}}:{{port}}/api/${name}s/1`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: ["api", `${name}s`, "1"] }
448
+ url: { raw: `http://{{host}}:{{port}}${apiPrefix}/${name}s/1`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: [...apiPathArr, `${name}s`, "1"] }
446
449
  }
447
450
  },
448
451
  {
@@ -450,7 +453,7 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
450
453
  request: {
451
454
  method: "GET",
452
455
  header: [ { key: "Authorization", value: "Bearer {{token}}" }, { key: "X-Tenant-ID", value: "{{tenant}}" } ],
453
- url: { raw: `http://{{host}}:{{port}}/api/rpc/${name}?id=gt.0`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: ["api", "rpc", name], query: [ { key: "id", value: "gt.0" } ] }
456
+ url: { raw: `http://{{host}}:{{port}}${apiPrefix}/rpc/${name}?id=gt.0`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: [...apiPathArr, "rpc", name], query: [ { key: "id", value: "gt.0" } ] }
454
457
  }
455
458
  }
456
459
  ];
@@ -461,7 +464,7 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
461
464
  request: {
462
465
  method: "GET",
463
466
  header: [ { key: "Authorization", value: "Bearer {{token}}" }, { key: "X-Tenant-ID", value: "{{tenant}}" } ],
464
- 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" } ] }
467
+ url: { raw: `http://{{host}}:{{port}}${apiPrefix}/search/${name}?q=Sample`, protocol: "http", host: ["{{host}}"], port: "{{port}}", path: [...apiPathArr, "search", name], query: [ { key: "q", value: "Sample" } ] }
465
468
  }
466
469
  });
467
470
  }
@@ -513,11 +516,11 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
513
516
  ]
514
517
  },
515
518
  url: {
516
- raw: "http://{{host}}:{{port}}/api/storage/upload?provider=sftp",
519
+ raw: `http://{{host}}:{{port}}${apiPrefix}/storage/upload?provider=sftp`,
517
520
  protocol: "http",
518
521
  host: ["{{host}}"],
519
522
  port: "{{port}}",
520
- path: ["api", "storage", "upload"],
523
+ path: [...apiPathArr, "storage", "upload"],
521
524
  query: [{ key: "provider", value: "sftp" }]
522
525
  }
523
526
  }
@@ -531,11 +534,11 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
531
534
  { key: "X-Tenant-ID", value: "{{tenant}}" }
532
535
  ],
533
536
  url: {
534
- raw: "http://{{host}}:{{port}}/api/storage/download/farm/animals/photo.jpg?provider=sftp",
537
+ raw: `http://{{host}}:{{port}}${apiPrefix}/storage/download/farm/animals/photo.jpg?provider=sftp`,
535
538
  protocol: "http",
536
539
  host: ["{{host}}"],
537
540
  port: "{{port}}",
538
- path: ["api", "storage", "download", "farm", "animals", "photo.jpg"],
541
+ path: [...apiPathArr, "storage", "download", "farm", "animals", "photo.jpg"],
539
542
  query: [{ key: "provider", value: "sftp" }]
540
543
  }
541
544
  }
@@ -549,11 +552,11 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
549
552
  { key: "X-Tenant-ID", value: "{{tenant}}" }
550
553
  ],
551
554
  url: {
552
- raw: "http://{{host}}:{{port}}/api/storage/scan/farm/animals/photo.jpg",
555
+ raw: `http://{{host}}:{{port}}${apiPrefix}/storage/scan/farm/animals/photo.jpg`,
553
556
  protocol: "http",
554
557
  host: ["{{host}}"],
555
558
  port: "{{port}}",
556
- path: ["api", "storage", "scan", "farm", "animals", "photo.jpg"]
559
+ path: [...apiPathArr, "storage", "scan", "farm", "animals", "photo.jpg"]
557
560
  }
558
561
  }
559
562
  }
@@ -6,6 +6,9 @@ export const generateSwaggerDocs = async (config, entities, outputDir, openEntit
6
6
  const docsDir = path.join(outputDir, 'docs');
7
7
  await fs.ensureDir(docsDir);
8
8
 
9
+ const apiPrefixRaw = config.server?.rest?.['api-path-prefix'] || '/api';
10
+ const apiPrefix = apiPrefixRaw.endsWith('/') ? apiPrefixRaw.slice(0, -1) : apiPrefixRaw;
11
+
9
12
  const swagger = {
10
13
  openapi: '3.0.0',
11
14
  info: {
@@ -201,14 +204,14 @@ export const generateSwaggerDocs = async (config, entities, outputDir, openEntit
201
204
  };
202
205
 
203
206
  // 1a. Secured Paths
204
- addEntityOperations('/api', false);
207
+ addEntityOperations(apiPrefix, false);
205
208
 
206
209
  // 1b. Public Paths (if marked as open)
207
- addEntityOperations('/open/api', true);
210
+ addEntityOperations('/open' + apiPrefix, true);
208
211
  }
209
212
 
210
213
  // 2. Add System Paths
211
- swagger.paths['/api/rpc/{table}'] = {
214
+ swagger.paths[`${apiPrefix}/rpc/{table}`] = {
212
215
  get: {
213
216
  tags: ['Search Engine'],
214
217
  summary: 'Generic PostgREST RPC Engine',
@@ -240,7 +243,7 @@ export const generateSwaggerDocs = async (config, entities, outputDir, openEntit
240
243
  }
241
244
  };
242
245
 
243
- swagger.paths['/api/admin/audit'] = {
246
+ swagger.paths[`${apiPrefix}/admin/audit`] = {
244
247
  get: {
245
248
  tags: ['Observability'],
246
249
  summary: 'Fetch Audit Trail',
@@ -281,7 +284,7 @@ export const generateSwaggerDocs = async (config, entities, outputDir, openEntit
281
284
  };
282
285
 
283
286
  if (config.elasticsearch?.enabled) {
284
- swagger.paths['/api/search/{entity}'] = {
287
+ swagger.paths[`${apiPrefix}/search/{entity}`] = {
285
288
  get: {
286
289
  tags: ['Search Engine'],
287
290
  summary: 'Elasticsearch Global Search',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "go-duck-cli",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "The Ultimate Evolutionary Go Microservice Scaffolder.",
5
5
  "main": "index.js",
6
6
  "type": "module",