@voyantjs/cruises 0.41.0 → 0.41.2
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/dist/routes.d.ts +1420 -1420
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +249 -247
- package/package.json +6 -6
package/dist/routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAE9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AA8CjE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAA;AAE7E,KAAK,GAAG,GAAG;IACT,SAAS,EAAE;QACT,EAAE,EAAE,kBAAkB,CAAA;QACtB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,QAAQ,CAAC,EAAE,QAAQ,CAAA;QACnB;;;;;;;;;;;;WAYG;QACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAA;KAC9C,CAAA;CACF,CAAA;AAuND,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAyBhB,UAAU;oCACF,MAAM;;;;;;yBAEjB,MAAM;;;;;;;6BAGyB,MAAM;2BAAS,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAE9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AA8CjE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAA;AAE7E,KAAK,GAAG,GAAG;IACT,SAAS,EAAE;QACT,EAAE,EAAE,kBAAkB,CAAA;QACtB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,QAAQ,CAAC,EAAE,QAAQ,CAAA;QACnB;;;;;;;;;;;;WAYG;QACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAA;KAC9C,CAAA;CACF,CAAA;AAuND,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAyBhB,UAAU;oCACF,MAAM;;;;;;yBAEjB,MAAM;;;;;;;6BAGyB,MAAM;2BAAS,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAysB3D,CAAA;AAEJ,MAAM,MAAM,iBAAiB,GAAG,OAAO,iBAAiB,CAAA"}
|
package/dist/routes.js
CHANGED
|
@@ -256,253 +256,6 @@ export const cruiseAdminRoutes = new Hono()
|
|
|
256
256
|
eventBus: c.get("eventBus"),
|
|
257
257
|
});
|
|
258
258
|
return c.json({ data: row }, 201);
|
|
259
|
-
})
|
|
260
|
-
// --- per-cruise (parses unified key, dispatches local or external) ---
|
|
261
|
-
// External branch dispatches through the catalog content service
|
|
262
|
-
// (cache-first, SWR refresh, synthesizer fallback) — flipped from
|
|
263
|
-
// ad-hoc adapter.fetchCruise() per the catalog-sourced-content
|
|
264
|
-
// migration. Returns the rich CruiseContent shape; templates that
|
|
265
|
-
// need backwards-compatible ExternalCruise can post-process the
|
|
266
|
-
// response.
|
|
267
|
-
.get("/:key", async (c) => {
|
|
268
|
-
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
269
|
-
if (parsed.kind === "invalid")
|
|
270
|
-
return c.json(invalidKey(parsed.raw), 400);
|
|
271
|
-
if (parsed.kind === "external") {
|
|
272
|
-
const registry = c.get("sourceAdapterRegistry");
|
|
273
|
-
if (!registry)
|
|
274
|
-
return c.json(registryNotConfigured(), 503);
|
|
275
|
-
const entityId = entityIdFromExternal(parsed);
|
|
276
|
-
const result = await getCruiseContent(c.get("db"), entityId, readContentScope(c), {
|
|
277
|
-
registry,
|
|
278
|
-
});
|
|
279
|
-
if (!result) {
|
|
280
|
-
return c.json({
|
|
281
|
-
error: "not_found",
|
|
282
|
-
detail: `No sourced-entry row for cruise ${parsed.provider}:${parsed.ref} (entity ${entityId}). Run discovery first or check that an adapter is registered for "${parsed.provider}".`,
|
|
283
|
-
}, 404);
|
|
284
|
-
}
|
|
285
|
-
return c.json({
|
|
286
|
-
data: {
|
|
287
|
-
source: "external",
|
|
288
|
-
sourceProvider: parsed.provider,
|
|
289
|
-
sourceRef: parsed.ref,
|
|
290
|
-
entityId,
|
|
291
|
-
content: result.content,
|
|
292
|
-
servedLocale: result.resolution.served_locale,
|
|
293
|
-
matchKind: result.resolution.match_kind,
|
|
294
|
-
contentSource: result.source,
|
|
295
|
-
servedStale: result.served_stale,
|
|
296
|
-
synthesized: result.synthesized,
|
|
297
|
-
machineTranslated: result.machine_translated,
|
|
298
|
-
},
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
const includeRaw = c.req.query("include") ?? "";
|
|
302
|
-
const includes = new Set(includeRaw
|
|
303
|
-
.split(",")
|
|
304
|
-
.map((s) => s.trim())
|
|
305
|
-
.filter(Boolean));
|
|
306
|
-
const row = await cruisesService.getCruiseById(c.get("db"), parsed.id, {
|
|
307
|
-
withSailings: includes.has("sailings"),
|
|
308
|
-
withDays: includes.has("days"),
|
|
309
|
-
});
|
|
310
|
-
if (!row)
|
|
311
|
-
return c.json({ error: "not_found" }, 404);
|
|
312
|
-
return c.json({
|
|
313
|
-
data: {
|
|
314
|
-
source: "local",
|
|
315
|
-
sourceProvider: null,
|
|
316
|
-
sourceRef: null,
|
|
317
|
-
cruise: row,
|
|
318
|
-
},
|
|
319
|
-
});
|
|
320
|
-
})
|
|
321
|
-
.put("/:key", async (c) => {
|
|
322
|
-
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
323
|
-
if (parsed.kind === "external") {
|
|
324
|
-
return c.json({
|
|
325
|
-
error: "external_cruise_read_only",
|
|
326
|
-
detail: `External cruise from '${parsed.provider}' cannot be edited locally. Edit at the upstream system, or POST /:key/detach to convert to a local cruise first.`,
|
|
327
|
-
}, 409);
|
|
328
|
-
}
|
|
329
|
-
if (parsed.kind === "invalid")
|
|
330
|
-
return c.json(invalidKey(parsed.raw), 400);
|
|
331
|
-
const data = await parseJsonBody(c, updateCruiseSchema);
|
|
332
|
-
const row = await cruisesService.updateCruise(c.get("db"), parsed.id, data, {
|
|
333
|
-
eventBus: c.get("eventBus"),
|
|
334
|
-
});
|
|
335
|
-
if (!row)
|
|
336
|
-
return c.json({ error: "not_found" }, 404);
|
|
337
|
-
return c.json({ data: row });
|
|
338
|
-
})
|
|
339
|
-
.delete("/:key", async (c) => {
|
|
340
|
-
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
341
|
-
if (parsed.kind === "external") {
|
|
342
|
-
return c.json({
|
|
343
|
-
error: "external_cruise_read_only",
|
|
344
|
-
detail: "External cruises can't be deleted locally.",
|
|
345
|
-
}, 409);
|
|
346
|
-
}
|
|
347
|
-
if (parsed.kind === "invalid")
|
|
348
|
-
return c.json(invalidKey(parsed.raw), 400);
|
|
349
|
-
const row = await cruisesService.archiveCruise(c.get("db"), parsed.id, {
|
|
350
|
-
eventBus: c.get("eventBus"),
|
|
351
|
-
});
|
|
352
|
-
if (!row)
|
|
353
|
-
return c.json({ error: "not_found" }, 404);
|
|
354
|
-
return c.json({ data: row });
|
|
355
|
-
})
|
|
356
|
-
.post("/:key/aggregates/recompute", async (c) => {
|
|
357
|
-
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
358
|
-
if (parsed.kind === "external") {
|
|
359
|
-
return c.json({ error: "external_cruise_read_only", detail: "Aggregates only apply to local cruises." }, 409);
|
|
360
|
-
}
|
|
361
|
-
if (parsed.kind === "invalid")
|
|
362
|
-
return c.json(invalidKey(parsed.raw), 400);
|
|
363
|
-
const row = await cruisesService.recomputeCruiseAggregates(c.get("db"), parsed.id);
|
|
364
|
-
if (!row)
|
|
365
|
-
return c.json({ error: "not_found" }, 404);
|
|
366
|
-
return c.json({ data: row });
|
|
367
|
-
})
|
|
368
|
-
.get("/:key/sailings", async (c) => {
|
|
369
|
-
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
370
|
-
if (parsed.kind === "invalid")
|
|
371
|
-
return c.json(invalidKey(parsed.raw), 400);
|
|
372
|
-
if (parsed.kind === "external") {
|
|
373
|
-
const ext = resolveExternal(parsed);
|
|
374
|
-
if (!ext)
|
|
375
|
-
return c.json(adapterNotRegistered(parsed.provider), 501);
|
|
376
|
-
const sailings = await ext.adapter.listSailingsForCruise(ext.sourceRef);
|
|
377
|
-
return c.json({
|
|
378
|
-
data: sailings.map((s) => ({
|
|
379
|
-
source: "external",
|
|
380
|
-
sourceProvider: ext.adapter.name,
|
|
381
|
-
key: makeExternalKey(ext.adapter, s.sourceRef),
|
|
382
|
-
sailing: s,
|
|
383
|
-
})),
|
|
384
|
-
total: sailings.length,
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
const result = await cruisesService.listSailings(c.get("db"), {
|
|
388
|
-
cruiseId: parsed.id,
|
|
389
|
-
limit: 100,
|
|
390
|
-
offset: 0,
|
|
391
|
-
});
|
|
392
|
-
return c.json(result);
|
|
393
|
-
})
|
|
394
|
-
.put("/:key/days/bulk", async (c) => {
|
|
395
|
-
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
396
|
-
if (parsed.kind === "external") {
|
|
397
|
-
return c.json({ error: "external_cruise_read_only" }, 409);
|
|
398
|
-
}
|
|
399
|
-
if (parsed.kind === "invalid")
|
|
400
|
-
return c.json(invalidKey(parsed.raw), 400);
|
|
401
|
-
const payload = await parseJsonBody(c, replaceCruiseDaysSchema.omit({ cruiseId: true }));
|
|
402
|
-
const days = await cruisesService.replaceCruiseDays(c.get("db"), {
|
|
403
|
-
cruiseId: parsed.id,
|
|
404
|
-
days: payload.days,
|
|
405
|
-
});
|
|
406
|
-
return c.json({ data: days });
|
|
407
|
-
})
|
|
408
|
-
// --- external-only operations ---
|
|
409
|
-
// Refresh dispatches through the catalog content service. The
|
|
410
|
-
// invalidator marks the cache row stale; the subsequent
|
|
411
|
-
// getCruiseContent call sees the staleness and triggers a SWR
|
|
412
|
-
// refresh. Templates that need synchronous "force fresh from
|
|
413
|
-
// upstream" semantics should call adapter.getContent() directly
|
|
414
|
-
// — this route's contract is "best effort refresh, eventually
|
|
415
|
-
// consistent."
|
|
416
|
-
.post("/:key/refresh", async (c) => {
|
|
417
|
-
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
418
|
-
if (parsed.kind !== "external")
|
|
419
|
-
return c.json({ error: "local_cruise_no_refresh" }, 400);
|
|
420
|
-
const registry = c.get("sourceAdapterRegistry");
|
|
421
|
-
if (!registry)
|
|
422
|
-
return c.json(registryNotConfigured(), 503);
|
|
423
|
-
const entityId = entityIdFromExternal(parsed);
|
|
424
|
-
const { invalidateCruiseContentOnDrift } = await import("./service-content.js");
|
|
425
|
-
await invalidateCruiseContentOnDrift(c.get("db"), {
|
|
426
|
-
id: `cnde_refresh_${Date.now()}`,
|
|
427
|
-
entity_module: "cruises",
|
|
428
|
-
entity_id: entityId,
|
|
429
|
-
kind: "content_invalidated",
|
|
430
|
-
detected_at: new Date(),
|
|
431
|
-
});
|
|
432
|
-
const result = await getCruiseContent(c.get("db"), entityId, readContentScope(c), {
|
|
433
|
-
registry,
|
|
434
|
-
});
|
|
435
|
-
if (!result) {
|
|
436
|
-
return c.json({
|
|
437
|
-
error: "not_found",
|
|
438
|
-
detail: `No sourced-entry row for cruise ${parsed.provider}:${parsed.ref} (entity ${entityId}).`,
|
|
439
|
-
}, 404);
|
|
440
|
-
}
|
|
441
|
-
return c.json({
|
|
442
|
-
data: {
|
|
443
|
-
source: "external",
|
|
444
|
-
sourceProvider: parsed.provider,
|
|
445
|
-
sourceRef: parsed.ref,
|
|
446
|
-
entityId,
|
|
447
|
-
content: result.content,
|
|
448
|
-
contentSource: result.source,
|
|
449
|
-
servedStale: result.served_stale,
|
|
450
|
-
refreshedAt: new Date().toISOString(),
|
|
451
|
-
},
|
|
452
|
-
});
|
|
453
|
-
})
|
|
454
|
-
.post("/:key/detach", async (c) => {
|
|
455
|
-
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
456
|
-
if (parsed.kind !== "external")
|
|
457
|
-
return c.json({ error: "local_cruise_no_detach" }, 400);
|
|
458
|
-
const ext = resolveExternal(parsed);
|
|
459
|
-
if (!ext)
|
|
460
|
-
return c.json(adapterNotRegistered(parsed.provider), 501);
|
|
461
|
-
const cruise = await detachExternalCruise(c.get("db"), ext.adapter, ext.sourceRef);
|
|
462
|
-
return c.json({ data: cruise }, 201);
|
|
463
|
-
})
|
|
464
|
-
// --- enrichment programs (expedition-focused; local cruises only) ---
|
|
465
|
-
.get("/:key/enrichment", async (c) => {
|
|
466
|
-
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
467
|
-
if (parsed.kind === "external") {
|
|
468
|
-
const ext = resolveExternal(parsed);
|
|
469
|
-
if (!ext)
|
|
470
|
-
return c.json(adapterNotRegistered(parsed.provider), 501);
|
|
471
|
-
// Adapters surface enrichment via the rich cruise detail; we return an
|
|
472
|
-
// empty list here for shape compatibility. Templates that need richer
|
|
473
|
-
// external enrichment should read from adapter.fetchCruise() directly.
|
|
474
|
-
return c.json({ data: [] });
|
|
475
|
-
}
|
|
476
|
-
if (parsed.kind === "invalid")
|
|
477
|
-
return c.json(invalidKey(parsed.raw), 400);
|
|
478
|
-
const programs = await cruisesService.listEnrichmentPrograms(c.get("db"), parsed.id);
|
|
479
|
-
return c.json({ data: programs });
|
|
480
|
-
})
|
|
481
|
-
.post("/:key/enrichment", async (c) => {
|
|
482
|
-
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
483
|
-
if (parsed.kind === "external")
|
|
484
|
-
return c.json({ error: "external_cruise_read_only" }, 409);
|
|
485
|
-
if (parsed.kind === "invalid")
|
|
486
|
-
return c.json(invalidKey(parsed.raw), 400);
|
|
487
|
-
const data = await parseJsonBody(c, insertEnrichmentProgramSchema.omit({ cruiseId: true }));
|
|
488
|
-
const row = await cruisesService.createEnrichmentProgram(c.get("db"), {
|
|
489
|
-
...data,
|
|
490
|
-
cruiseId: parsed.id,
|
|
491
|
-
});
|
|
492
|
-
return c.json({ data: row }, 201);
|
|
493
|
-
})
|
|
494
|
-
.put("/:key/enrichment/bulk", async (c) => {
|
|
495
|
-
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
496
|
-
if (parsed.kind === "external")
|
|
497
|
-
return c.json({ error: "external_cruise_read_only" }, 409);
|
|
498
|
-
if (parsed.kind === "invalid")
|
|
499
|
-
return c.json(invalidKey(parsed.raw), 400);
|
|
500
|
-
const payload = await parseJsonBody(c, replaceEnrichmentProgramsSchema.omit({ cruiseId: true }));
|
|
501
|
-
const rows = await cruisesService.replaceEnrichmentPrograms(c.get("db"), {
|
|
502
|
-
cruiseId: parsed.id,
|
|
503
|
-
programs: payload.programs,
|
|
504
|
-
});
|
|
505
|
-
return c.json({ data: rows });
|
|
506
259
|
})
|
|
507
260
|
.put("/enrichment/:programId", async (c) => {
|
|
508
261
|
const data = await parseJsonBody(c, updateEnrichmentProgramSchema);
|
|
@@ -920,4 +673,253 @@ export const cruiseAdminRoutes = new Hono()
|
|
|
920
673
|
.post("/search-index/rebuild", async (c) => {
|
|
921
674
|
const result = await cruisesSearchService.rebuildAll(c.get("db"));
|
|
922
675
|
return c.json({ data: result });
|
|
676
|
+
})
|
|
677
|
+
// --- per-cruise (parses unified key, dispatches local or external) ---
|
|
678
|
+
// Keep wildcard key routes after static admin subresources so reserved
|
|
679
|
+
// segments such as /sailings, /ships, and /prices reach their handlers.
|
|
680
|
+
// External branch dispatches through the catalog content service
|
|
681
|
+
// (cache-first, SWR refresh, synthesizer fallback) — flipped from
|
|
682
|
+
// ad-hoc adapter.fetchCruise() per the catalog-sourced-content
|
|
683
|
+
// migration. Returns the rich CruiseContent shape; templates that
|
|
684
|
+
// need backwards-compatible ExternalCruise can post-process the
|
|
685
|
+
// response.
|
|
686
|
+
.get("/:key", async (c) => {
|
|
687
|
+
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
688
|
+
if (parsed.kind === "invalid")
|
|
689
|
+
return c.json(invalidKey(parsed.raw), 400);
|
|
690
|
+
if (parsed.kind === "external") {
|
|
691
|
+
const registry = c.get("sourceAdapterRegistry");
|
|
692
|
+
if (!registry)
|
|
693
|
+
return c.json(registryNotConfigured(), 503);
|
|
694
|
+
const entityId = entityIdFromExternal(parsed);
|
|
695
|
+
const result = await getCruiseContent(c.get("db"), entityId, readContentScope(c), {
|
|
696
|
+
registry,
|
|
697
|
+
});
|
|
698
|
+
if (!result) {
|
|
699
|
+
return c.json({
|
|
700
|
+
error: "not_found",
|
|
701
|
+
detail: `No sourced-entry row for cruise ${parsed.provider}:${parsed.ref} (entity ${entityId}). Run discovery first or check that an adapter is registered for "${parsed.provider}".`,
|
|
702
|
+
}, 404);
|
|
703
|
+
}
|
|
704
|
+
return c.json({
|
|
705
|
+
data: {
|
|
706
|
+
source: "external",
|
|
707
|
+
sourceProvider: parsed.provider,
|
|
708
|
+
sourceRef: parsed.ref,
|
|
709
|
+
entityId,
|
|
710
|
+
content: result.content,
|
|
711
|
+
servedLocale: result.resolution.served_locale,
|
|
712
|
+
matchKind: result.resolution.match_kind,
|
|
713
|
+
contentSource: result.source,
|
|
714
|
+
servedStale: result.served_stale,
|
|
715
|
+
synthesized: result.synthesized,
|
|
716
|
+
machineTranslated: result.machine_translated,
|
|
717
|
+
},
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
const includeRaw = c.req.query("include") ?? "";
|
|
721
|
+
const includes = new Set(includeRaw
|
|
722
|
+
.split(",")
|
|
723
|
+
.map((s) => s.trim())
|
|
724
|
+
.filter(Boolean));
|
|
725
|
+
const row = await cruisesService.getCruiseById(c.get("db"), parsed.id, {
|
|
726
|
+
withSailings: includes.has("sailings"),
|
|
727
|
+
withDays: includes.has("days"),
|
|
728
|
+
});
|
|
729
|
+
if (!row)
|
|
730
|
+
return c.json({ error: "not_found" }, 404);
|
|
731
|
+
return c.json({
|
|
732
|
+
data: {
|
|
733
|
+
source: "local",
|
|
734
|
+
sourceProvider: null,
|
|
735
|
+
sourceRef: null,
|
|
736
|
+
cruise: row,
|
|
737
|
+
},
|
|
738
|
+
});
|
|
739
|
+
})
|
|
740
|
+
.put("/:key", async (c) => {
|
|
741
|
+
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
742
|
+
if (parsed.kind === "external") {
|
|
743
|
+
return c.json({
|
|
744
|
+
error: "external_cruise_read_only",
|
|
745
|
+
detail: `External cruise from '${parsed.provider}' cannot be edited locally. Edit at the upstream system, or POST /:key/detach to convert to a local cruise first.`,
|
|
746
|
+
}, 409);
|
|
747
|
+
}
|
|
748
|
+
if (parsed.kind === "invalid")
|
|
749
|
+
return c.json(invalidKey(parsed.raw), 400);
|
|
750
|
+
const data = await parseJsonBody(c, updateCruiseSchema);
|
|
751
|
+
const row = await cruisesService.updateCruise(c.get("db"), parsed.id, data, {
|
|
752
|
+
eventBus: c.get("eventBus"),
|
|
753
|
+
});
|
|
754
|
+
if (!row)
|
|
755
|
+
return c.json({ error: "not_found" }, 404);
|
|
756
|
+
return c.json({ data: row });
|
|
757
|
+
})
|
|
758
|
+
.delete("/:key", async (c) => {
|
|
759
|
+
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
760
|
+
if (parsed.kind === "external") {
|
|
761
|
+
return c.json({
|
|
762
|
+
error: "external_cruise_read_only",
|
|
763
|
+
detail: "External cruises can't be deleted locally.",
|
|
764
|
+
}, 409);
|
|
765
|
+
}
|
|
766
|
+
if (parsed.kind === "invalid")
|
|
767
|
+
return c.json(invalidKey(parsed.raw), 400);
|
|
768
|
+
const row = await cruisesService.archiveCruise(c.get("db"), parsed.id, {
|
|
769
|
+
eventBus: c.get("eventBus"),
|
|
770
|
+
});
|
|
771
|
+
if (!row)
|
|
772
|
+
return c.json({ error: "not_found" }, 404);
|
|
773
|
+
return c.json({ data: row });
|
|
774
|
+
})
|
|
775
|
+
.post("/:key/aggregates/recompute", async (c) => {
|
|
776
|
+
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
777
|
+
if (parsed.kind === "external") {
|
|
778
|
+
return c.json({ error: "external_cruise_read_only", detail: "Aggregates only apply to local cruises." }, 409);
|
|
779
|
+
}
|
|
780
|
+
if (parsed.kind === "invalid")
|
|
781
|
+
return c.json(invalidKey(parsed.raw), 400);
|
|
782
|
+
const row = await cruisesService.recomputeCruiseAggregates(c.get("db"), parsed.id);
|
|
783
|
+
if (!row)
|
|
784
|
+
return c.json({ error: "not_found" }, 404);
|
|
785
|
+
return c.json({ data: row });
|
|
786
|
+
})
|
|
787
|
+
.get("/:key/sailings", async (c) => {
|
|
788
|
+
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
789
|
+
if (parsed.kind === "invalid")
|
|
790
|
+
return c.json(invalidKey(parsed.raw), 400);
|
|
791
|
+
if (parsed.kind === "external") {
|
|
792
|
+
const ext = resolveExternal(parsed);
|
|
793
|
+
if (!ext)
|
|
794
|
+
return c.json(adapterNotRegistered(parsed.provider), 501);
|
|
795
|
+
const sailings = await ext.adapter.listSailingsForCruise(ext.sourceRef);
|
|
796
|
+
return c.json({
|
|
797
|
+
data: sailings.map((s) => ({
|
|
798
|
+
source: "external",
|
|
799
|
+
sourceProvider: ext.adapter.name,
|
|
800
|
+
key: makeExternalKey(ext.adapter, s.sourceRef),
|
|
801
|
+
sailing: s,
|
|
802
|
+
})),
|
|
803
|
+
total: sailings.length,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
const result = await cruisesService.listSailings(c.get("db"), {
|
|
807
|
+
cruiseId: parsed.id,
|
|
808
|
+
limit: 100,
|
|
809
|
+
offset: 0,
|
|
810
|
+
});
|
|
811
|
+
return c.json(result);
|
|
812
|
+
})
|
|
813
|
+
.put("/:key/days/bulk", async (c) => {
|
|
814
|
+
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
815
|
+
if (parsed.kind === "external") {
|
|
816
|
+
return c.json({ error: "external_cruise_read_only" }, 409);
|
|
817
|
+
}
|
|
818
|
+
if (parsed.kind === "invalid")
|
|
819
|
+
return c.json(invalidKey(parsed.raw), 400);
|
|
820
|
+
const payload = await parseJsonBody(c, replaceCruiseDaysSchema.omit({ cruiseId: true }));
|
|
821
|
+
const days = await cruisesService.replaceCruiseDays(c.get("db"), {
|
|
822
|
+
cruiseId: parsed.id,
|
|
823
|
+
days: payload.days,
|
|
824
|
+
});
|
|
825
|
+
return c.json({ data: days });
|
|
826
|
+
})
|
|
827
|
+
// --- external-only operations ---
|
|
828
|
+
// Refresh dispatches through the catalog content service. The
|
|
829
|
+
// invalidator marks the cache row stale; the subsequent
|
|
830
|
+
// getCruiseContent call sees the staleness and triggers a SWR
|
|
831
|
+
// refresh. Templates that need synchronous "force fresh from
|
|
832
|
+
// upstream" semantics should call adapter.getContent() directly
|
|
833
|
+
// — this route's contract is "best effort refresh, eventually
|
|
834
|
+
// consistent."
|
|
835
|
+
.post("/:key/refresh", async (c) => {
|
|
836
|
+
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
837
|
+
if (parsed.kind !== "external")
|
|
838
|
+
return c.json({ error: "local_cruise_no_refresh" }, 400);
|
|
839
|
+
const registry = c.get("sourceAdapterRegistry");
|
|
840
|
+
if (!registry)
|
|
841
|
+
return c.json(registryNotConfigured(), 503);
|
|
842
|
+
const entityId = entityIdFromExternal(parsed);
|
|
843
|
+
const { invalidateCruiseContentOnDrift } = await import("./service-content.js");
|
|
844
|
+
await invalidateCruiseContentOnDrift(c.get("db"), {
|
|
845
|
+
id: `cnde_refresh_${Date.now()}`,
|
|
846
|
+
entity_module: "cruises",
|
|
847
|
+
entity_id: entityId,
|
|
848
|
+
kind: "content_invalidated",
|
|
849
|
+
detected_at: new Date(),
|
|
850
|
+
});
|
|
851
|
+
const result = await getCruiseContent(c.get("db"), entityId, readContentScope(c), {
|
|
852
|
+
registry,
|
|
853
|
+
});
|
|
854
|
+
if (!result) {
|
|
855
|
+
return c.json({
|
|
856
|
+
error: "not_found",
|
|
857
|
+
detail: `No sourced-entry row for cruise ${parsed.provider}:${parsed.ref} (entity ${entityId}).`,
|
|
858
|
+
}, 404);
|
|
859
|
+
}
|
|
860
|
+
return c.json({
|
|
861
|
+
data: {
|
|
862
|
+
source: "external",
|
|
863
|
+
sourceProvider: parsed.provider,
|
|
864
|
+
sourceRef: parsed.ref,
|
|
865
|
+
entityId,
|
|
866
|
+
content: result.content,
|
|
867
|
+
contentSource: result.source,
|
|
868
|
+
servedStale: result.served_stale,
|
|
869
|
+
refreshedAt: new Date().toISOString(),
|
|
870
|
+
},
|
|
871
|
+
});
|
|
872
|
+
})
|
|
873
|
+
.post("/:key/detach", async (c) => {
|
|
874
|
+
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
875
|
+
if (parsed.kind !== "external")
|
|
876
|
+
return c.json({ error: "local_cruise_no_detach" }, 400);
|
|
877
|
+
const ext = resolveExternal(parsed);
|
|
878
|
+
if (!ext)
|
|
879
|
+
return c.json(adapterNotRegistered(parsed.provider), 501);
|
|
880
|
+
const cruise = await detachExternalCruise(c.get("db"), ext.adapter, ext.sourceRef);
|
|
881
|
+
return c.json({ data: cruise }, 201);
|
|
882
|
+
})
|
|
883
|
+
// --- enrichment programs (expedition-focused; local cruises only) ---
|
|
884
|
+
.get("/:key/enrichment", async (c) => {
|
|
885
|
+
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
886
|
+
if (parsed.kind === "external") {
|
|
887
|
+
const ext = resolveExternal(parsed);
|
|
888
|
+
if (!ext)
|
|
889
|
+
return c.json(adapterNotRegistered(parsed.provider), 501);
|
|
890
|
+
// Adapters surface enrichment via the rich cruise detail; we return an
|
|
891
|
+
// empty list here for shape compatibility. Templates that need richer
|
|
892
|
+
// external enrichment should read from adapter.fetchCruise() directly.
|
|
893
|
+
return c.json({ data: [] });
|
|
894
|
+
}
|
|
895
|
+
if (parsed.kind === "invalid")
|
|
896
|
+
return c.json(invalidKey(parsed.raw), 400);
|
|
897
|
+
const programs = await cruisesService.listEnrichmentPrograms(c.get("db"), parsed.id);
|
|
898
|
+
return c.json({ data: programs });
|
|
899
|
+
})
|
|
900
|
+
.post("/:key/enrichment", async (c) => {
|
|
901
|
+
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
902
|
+
if (parsed.kind === "external")
|
|
903
|
+
return c.json({ error: "external_cruise_read_only" }, 409);
|
|
904
|
+
if (parsed.kind === "invalid")
|
|
905
|
+
return c.json(invalidKey(parsed.raw), 400);
|
|
906
|
+
const data = await parseJsonBody(c, insertEnrichmentProgramSchema.omit({ cruiseId: true }));
|
|
907
|
+
const row = await cruisesService.createEnrichmentProgram(c.get("db"), {
|
|
908
|
+
...data,
|
|
909
|
+
cruiseId: parsed.id,
|
|
910
|
+
});
|
|
911
|
+
return c.json({ data: row }, 201);
|
|
912
|
+
})
|
|
913
|
+
.put("/:key/enrichment/bulk", async (c) => {
|
|
914
|
+
const parsed = parseUnifiedKey(c.req.param("key"));
|
|
915
|
+
if (parsed.kind === "external")
|
|
916
|
+
return c.json({ error: "external_cruise_read_only" }, 409);
|
|
917
|
+
if (parsed.kind === "invalid")
|
|
918
|
+
return c.json(invalidKey(parsed.raw), 400);
|
|
919
|
+
const payload = await parseJsonBody(c, replaceEnrichmentProgramsSchema.omit({ cruiseId: true }));
|
|
920
|
+
const rows = await cruisesService.replaceEnrichmentPrograms(c.get("db"), {
|
|
921
|
+
cruiseId: parsed.id,
|
|
922
|
+
programs: payload.programs,
|
|
923
|
+
});
|
|
924
|
+
return c.json({ data: rows });
|
|
923
925
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyantjs/cruises",
|
|
3
|
-
"version": "0.41.
|
|
3
|
+
"version": "0.41.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -89,11 +89,11 @@
|
|
|
89
89
|
"drizzle-orm": "^0.45.2",
|
|
90
90
|
"hono": "^4.12.10",
|
|
91
91
|
"zod": "^4.3.6",
|
|
92
|
-
"@voyantjs/bookings": "0.41.
|
|
93
|
-
"@voyantjs/core": "0.41.
|
|
94
|
-
"@voyantjs/db": "0.41.
|
|
95
|
-
"@voyantjs/hono": "0.41.
|
|
96
|
-
"@voyantjs/catalog": "0.41.
|
|
92
|
+
"@voyantjs/bookings": "0.41.2",
|
|
93
|
+
"@voyantjs/core": "0.41.2",
|
|
94
|
+
"@voyantjs/db": "0.41.2",
|
|
95
|
+
"@voyantjs/hono": "0.41.2",
|
|
96
|
+
"@voyantjs/catalog": "0.41.2"
|
|
97
97
|
},
|
|
98
98
|
"devDependencies": {
|
|
99
99
|
"typescript": "^6.0.2",
|