@voyant-travel/cruises 0.118.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/LICENSE +201 -0
- package/README.md +50 -0
- package/dist/adapters/connect-compat.d.ts +20 -0
- package/dist/adapters/connect-compat.d.ts.map +1 -0
- package/dist/adapters/connect-compat.js +71 -0
- package/dist/adapters/contract-fixture.d.ts +32 -0
- package/dist/adapters/contract-fixture.d.ts.map +1 -0
- package/dist/adapters/contract-fixture.js +152 -0
- package/dist/adapters/index.d.ts +331 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +16 -0
- package/dist/adapters/memoize.d.ts +28 -0
- package/dist/adapters/memoize.d.ts.map +1 -0
- package/dist/adapters/memoize.js +131 -0
- package/dist/adapters/mock.d.ts +44 -0
- package/dist/adapters/mock.d.ts.map +1 -0
- package/dist/adapters/mock.js +192 -0
- package/dist/adapters/registry.d.ts +26 -0
- package/dist/adapters/registry.d.ts.map +1 -0
- package/dist/adapters/registry.js +42 -0
- package/dist/adapters/source-adapter-shim.d.ts +80 -0
- package/dist/adapters/source-adapter-shim.d.ts.map +1 -0
- package/dist/adapters/source-adapter-shim.js +390 -0
- package/dist/booking-engine/handler.d.ts +108 -0
- package/dist/booking-engine/handler.d.ts.map +1 -0
- package/dist/booking-engine/handler.js +225 -0
- package/dist/booking-engine/index.d.ts +9 -0
- package/dist/booking-engine/index.d.ts.map +1 -0
- package/dist/booking-engine/index.js +8 -0
- package/dist/booking-extension.d.ts +1179 -0
- package/dist/booking-extension.d.ts.map +1 -0
- package/dist/booking-extension.js +342 -0
- package/dist/cabin-features.d.ts +8 -0
- package/dist/cabin-features.d.ts.map +1 -0
- package/dist/cabin-features.js +7 -0
- package/dist/catalog-policy-cabins.d.ts +18 -0
- package/dist/catalog-policy-cabins.d.ts.map +1 -0
- package/dist/catalog-policy-cabins.js +96 -0
- package/dist/catalog-policy-core.d.ts +3 -0
- package/dist/catalog-policy-core.d.ts.map +1 -0
- package/dist/catalog-policy-core.js +247 -0
- package/dist/catalog-policy-structure.d.ts +3 -0
- package/dist/catalog-policy-structure.d.ts.map +1 -0
- package/dist/catalog-policy-structure.js +387 -0
- package/dist/catalog-policy.d.ts +15 -0
- package/dist/catalog-policy.d.ts.map +1 -0
- package/dist/catalog-policy.js +19 -0
- package/dist/content-shape.d.ts +5 -0
- package/dist/content-shape.d.ts.map +1 -0
- package/dist/content-shape.js +13 -0
- package/dist/draft-shape.d.ts +59 -0
- package/dist/draft-shape.d.ts.map +1 -0
- package/dist/draft-shape.js +98 -0
- package/dist/events.d.ts +21 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +21 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/lib/key.d.ts +41 -0
- package/dist/lib/key.d.ts.map +1 -0
- package/dist/lib/key.js +100 -0
- package/dist/routes-booking-payloads.d.ts +133 -0
- package/dist/routes-booking-payloads.d.ts.map +1 -0
- package/dist/routes-booking-payloads.js +142 -0
- package/dist/routes-content.d.ts +53 -0
- package/dist/routes-content.d.ts.map +1 -0
- package/dist/routes-content.js +158 -0
- package/dist/routes-core.d.ts +4 -0
- package/dist/routes-core.d.ts.map +1 -0
- package/dist/routes-core.js +68 -0
- package/dist/routes-detail.d.ts +4 -0
- package/dist/routes-detail.d.ts.map +1 -0
- package/dist/routes-detail.js +261 -0
- package/dist/routes-env.d.ts +13 -0
- package/dist/routes-env.d.ts.map +1 -0
- package/dist/routes-env.js +1 -0
- package/dist/routes-keying.d.ts +28 -0
- package/dist/routes-keying.d.ts.map +1 -0
- package/dist/routes-keying.js +70 -0
- package/dist/routes-public.d.ts +911 -0
- package/dist/routes-public.d.ts.map +1 -0
- package/dist/routes-public.js +252 -0
- package/dist/routes-sailings-prices.d.ts +4 -0
- package/dist/routes-sailings-prices.d.ts.map +1 -0
- package/dist/routes-sailings-prices.js +278 -0
- package/dist/routes-search-index.d.ts +4 -0
- package/dist/routes-search-index.d.ts.map +1 -0
- package/dist/routes-search-index.js +25 -0
- package/dist/routes-ships.d.ts +4 -0
- package/dist/routes-ships.d.ts.map +1 -0
- package/dist/routes-ships.js +147 -0
- package/dist/routes-voyage-groups.d.ts +4 -0
- package/dist/routes-voyage-groups.d.ts.map +1 -0
- package/dist/routes-voyage-groups.js +85 -0
- package/dist/routes.d.ts +5 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +14 -0
- package/dist/schema-cabins.d.ts +1098 -0
- package/dist/schema-cabins.d.ts.map +1 -0
- package/dist/schema-cabins.js +105 -0
- package/dist/schema-content.d.ts +577 -0
- package/dist/schema-content.d.ts.map +1 -0
- package/dist/schema-content.js +63 -0
- package/dist/schema-core.d.ts +1790 -0
- package/dist/schema-core.d.ts.map +1 -0
- package/dist/schema-core.js +171 -0
- package/dist/schema-itinerary.d.ts +556 -0
- package/dist/schema-itinerary.d.ts.map +1 -0
- package/dist/schema-itinerary.js +50 -0
- package/dist/schema-pricing.d.ts +633 -0
- package/dist/schema-pricing.d.ts.map +1 -0
- package/dist/schema-pricing.js +73 -0
- package/dist/schema-search.d.ts +611 -0
- package/dist/schema-search.d.ts.map +1 -0
- package/dist/schema-search.js +64 -0
- package/dist/schema-shared.d.ts +23 -0
- package/dist/schema-shared.d.ts.map +1 -0
- package/dist/schema-shared.js +107 -0
- package/dist/schema-sourced-content.d.ts +247 -0
- package/dist/schema-sourced-content.d.ts.map +1 -0
- package/dist/schema-sourced-content.js +38 -0
- package/dist/schema.d.ts +10 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +9 -0
- package/dist/service-booking-helpers.d.ts +12 -0
- package/dist/service-booking-helpers.d.ts.map +1 -0
- package/dist/service-booking-helpers.js +94 -0
- package/dist/service-booking-types.d.ts +101 -0
- package/dist/service-booking-types.d.ts.map +1 -0
- package/dist/service-booking-types.js +1 -0
- package/dist/service-bookings.d.ts +46 -0
- package/dist/service-bookings.d.ts.map +1 -0
- package/dist/service-bookings.js +420 -0
- package/dist/service-catalog-plane-cabins.d.ts +24 -0
- package/dist/service-catalog-plane-cabins.d.ts.map +1 -0
- package/dist/service-catalog-plane-cabins.js +90 -0
- package/dist/service-catalog-plane.d.ts +74 -0
- package/dist/service-catalog-plane.d.ts.map +1 -0
- package/dist/service-catalog-plane.js +194 -0
- package/dist/service-content-synthesizer.d.ts +42 -0
- package/dist/service-content-synthesizer.d.ts.map +1 -0
- package/dist/service-content-synthesizer.js +144 -0
- package/dist/service-content.d.ts +74 -0
- package/dist/service-content.d.ts.map +1 -0
- package/dist/service-content.js +315 -0
- package/dist/service-core.d.ts +134 -0
- package/dist/service-core.d.ts.map +1 -0
- package/dist/service-core.js +257 -0
- package/dist/service-detach.d.ts +18 -0
- package/dist/service-detach.d.ts.map +1 -0
- package/dist/service-detach.js +199 -0
- package/dist/service-enrichment.d.ts +11 -0
- package/dist/service-enrichment.d.ts.map +1 -0
- package/dist/service-enrichment.js +47 -0
- package/dist/service-external-refresh.d.ts +39 -0
- package/dist/service-external-refresh.d.ts.map +1 -0
- package/dist/service-external-refresh.js +47 -0
- package/dist/service-itinerary.d.ts +22 -0
- package/dist/service-itinerary.d.ts.map +1 -0
- package/dist/service-itinerary.js +34 -0
- package/dist/service-prices.d.ts +46 -0
- package/dist/service-prices.d.ts.map +1 -0
- package/dist/service-prices.js +89 -0
- package/dist/service-pricing.d.ts +97 -0
- package/dist/service-pricing.d.ts.map +1 -0
- package/dist/service-pricing.js +198 -0
- package/dist/service-sailings.d.ts +48 -0
- package/dist/service-sailings.d.ts.map +1 -0
- package/dist/service-sailings.js +145 -0
- package/dist/service-search-types.d.ts +54 -0
- package/dist/service-search-types.d.ts.map +1 -0
- package/dist/service-search-types.js +1 -0
- package/dist/service-search.d.ts +65 -0
- package/dist/service-search.d.ts.map +1 -0
- package/dist/service-search.js +467 -0
- package/dist/service-shared.d.ts +22 -0
- package/dist/service-shared.d.ts.map +1 -0
- package/dist/service-shared.js +22 -0
- package/dist/service-ships.d.ts +47 -0
- package/dist/service-ships.d.ts.map +1 -0
- package/dist/service-ships.js +156 -0
- package/dist/service.d.ts +255 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +12 -0
- package/dist/validation-cabins.d.ts +267 -0
- package/dist/validation-cabins.d.ts.map +1 -0
- package/dist/validation-cabins.js +77 -0
- package/dist/validation-content.d.ts +123 -0
- package/dist/validation-content.d.ts.map +1 -0
- package/dist/validation-content.js +40 -0
- package/dist/validation-core.d.ts +393 -0
- package/dist/validation-core.d.ts.map +1 -0
- package/dist/validation-core.js +162 -0
- package/dist/validation-itinerary.d.ts +123 -0
- package/dist/validation-itinerary.d.ts.map +1 -0
- package/dist/validation-itinerary.js +47 -0
- package/dist/validation-pricing.d.ts +137 -0
- package/dist/validation-pricing.d.ts.map +1 -0
- package/dist/validation-pricing.js +49 -0
- package/dist/validation-search.d.ts +118 -0
- package/dist/validation-search.d.ts.map +1 -0
- package/dist/validation-search.js +60 -0
- package/dist/validation-shared.d.ts +123 -0
- package/dist/validation-shared.d.ts.map +1 -0
- package/dist/validation-shared.js +103 -0
- package/dist/validation.d.ts +8 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +7 -0
- package/package.json +146 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { and, asc, count, desc, eq, ilike, or, sql } from "drizzle-orm";
|
|
2
|
+
import { CRUISE_CREATED_EVENT, CRUISE_DELETED_EVENT, CRUISE_UPDATED_EVENT, emitCruiseLifecycleEvent, } from "./events.js";
|
|
3
|
+
import { cruiseSailings, cruises, cruiseVoyageGroupSegments, cruiseVoyageGroups, } from "./schema-core.js";
|
|
4
|
+
import { cruiseDays } from "./schema-itinerary.js";
|
|
5
|
+
import { cruisePrices } from "./schema-pricing.js";
|
|
6
|
+
import { paginate, reprojectIfPossible, setUpdated, } from "./service-shared.js";
|
|
7
|
+
export const cruiseCoreService = {
|
|
8
|
+
async listVoyageGroups(db, query) {
|
|
9
|
+
const conditions = [];
|
|
10
|
+
if (query.groupKind)
|
|
11
|
+
conditions.push(eq(cruiseVoyageGroups.groupKind, query.groupKind));
|
|
12
|
+
if (query.status)
|
|
13
|
+
conditions.push(eq(cruiseVoyageGroups.status, query.status));
|
|
14
|
+
if (query.lineSupplierId) {
|
|
15
|
+
conditions.push(eq(cruiseVoyageGroups.lineSupplierId, query.lineSupplierId));
|
|
16
|
+
}
|
|
17
|
+
if (query.region) {
|
|
18
|
+
conditions.push(
|
|
19
|
+
// agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
20
|
+
sql `${cruiseVoyageGroups.regions} @> ${JSON.stringify([query.region])}::jsonb`);
|
|
21
|
+
}
|
|
22
|
+
if (query.search) {
|
|
23
|
+
const term = `%${query.search}%`;
|
|
24
|
+
conditions.push(or(ilike(cruiseVoyageGroups.name, term), ilike(cruiseVoyageGroups.description, term)));
|
|
25
|
+
}
|
|
26
|
+
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
|
27
|
+
const { limit, offset } = paginate(query);
|
|
28
|
+
const [rows, totalRows] = await Promise.all([
|
|
29
|
+
db
|
|
30
|
+
.select()
|
|
31
|
+
.from(cruiseVoyageGroups)
|
|
32
|
+
.where(where)
|
|
33
|
+
.orderBy(desc(cruiseVoyageGroups.createdAt))
|
|
34
|
+
.limit(limit)
|
|
35
|
+
.offset(offset),
|
|
36
|
+
db.select({ value: count() }).from(cruiseVoyageGroups).where(where),
|
|
37
|
+
]);
|
|
38
|
+
return { data: rows, total: totalRows[0]?.value ?? 0, limit, offset };
|
|
39
|
+
},
|
|
40
|
+
async getVoyageGroupById(db, id, options = {}) {
|
|
41
|
+
const [row] = await db
|
|
42
|
+
.select()
|
|
43
|
+
.from(cruiseVoyageGroups)
|
|
44
|
+
.where(eq(cruiseVoyageGroups.id, id))
|
|
45
|
+
.limit(1);
|
|
46
|
+
if (!row)
|
|
47
|
+
return null;
|
|
48
|
+
const out = { ...row };
|
|
49
|
+
if (options.withSegments) {
|
|
50
|
+
out.segments = await db
|
|
51
|
+
.select()
|
|
52
|
+
.from(cruiseVoyageGroupSegments)
|
|
53
|
+
.where(eq(cruiseVoyageGroupSegments.voyageGroupId, id))
|
|
54
|
+
.orderBy(asc(cruiseVoyageGroupSegments.sortOrder));
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
},
|
|
58
|
+
async createVoyageGroup(db, data) {
|
|
59
|
+
const [row] = await db
|
|
60
|
+
.insert(cruiseVoyageGroups)
|
|
61
|
+
.values(data)
|
|
62
|
+
.returning();
|
|
63
|
+
if (!row)
|
|
64
|
+
throw new Error("Failed to create voyage group");
|
|
65
|
+
return row;
|
|
66
|
+
},
|
|
67
|
+
async updateVoyageGroup(db, id, data) {
|
|
68
|
+
const [row] = await db
|
|
69
|
+
.update(cruiseVoyageGroups)
|
|
70
|
+
.set({ ...data, ...setUpdated })
|
|
71
|
+
.where(eq(cruiseVoyageGroups.id, id))
|
|
72
|
+
.returning();
|
|
73
|
+
return row ?? null;
|
|
74
|
+
},
|
|
75
|
+
async archiveVoyageGroup(db, id) {
|
|
76
|
+
const [row] = await db
|
|
77
|
+
.update(cruiseVoyageGroups)
|
|
78
|
+
.set({ status: "archived", ...setUpdated })
|
|
79
|
+
.where(eq(cruiseVoyageGroups.id, id))
|
|
80
|
+
.returning();
|
|
81
|
+
return row ?? null;
|
|
82
|
+
},
|
|
83
|
+
async listVoyageGroupSegments(db, query) {
|
|
84
|
+
const conditions = [];
|
|
85
|
+
if (query.voyageGroupId) {
|
|
86
|
+
conditions.push(eq(cruiseVoyageGroupSegments.voyageGroupId, query.voyageGroupId));
|
|
87
|
+
}
|
|
88
|
+
if (query.cruiseId)
|
|
89
|
+
conditions.push(eq(cruiseVoyageGroupSegments.cruiseId, query.cruiseId));
|
|
90
|
+
if (query.sailingId)
|
|
91
|
+
conditions.push(eq(cruiseVoyageGroupSegments.sailingId, query.sailingId));
|
|
92
|
+
if (query.segmentKind) {
|
|
93
|
+
conditions.push(eq(cruiseVoyageGroupSegments.segmentKind, query.segmentKind));
|
|
94
|
+
}
|
|
95
|
+
if (query.segmentRole) {
|
|
96
|
+
conditions.push(eq(cruiseVoyageGroupSegments.segmentRole, query.segmentRole));
|
|
97
|
+
}
|
|
98
|
+
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
|
99
|
+
const { limit, offset } = paginate(query);
|
|
100
|
+
const [rows, totalRows] = await Promise.all([
|
|
101
|
+
db
|
|
102
|
+
.select()
|
|
103
|
+
.from(cruiseVoyageGroupSegments)
|
|
104
|
+
.where(where)
|
|
105
|
+
.orderBy(asc(cruiseVoyageGroupSegments.voyageGroupId), asc(cruiseVoyageGroupSegments.sortOrder))
|
|
106
|
+
.limit(limit)
|
|
107
|
+
.offset(offset),
|
|
108
|
+
db.select({ value: count() }).from(cruiseVoyageGroupSegments).where(where),
|
|
109
|
+
]);
|
|
110
|
+
return { data: rows, total: totalRows[0]?.value ?? 0, limit, offset };
|
|
111
|
+
},
|
|
112
|
+
async createVoyageGroupSegment(db, data) {
|
|
113
|
+
const [row] = await db
|
|
114
|
+
.insert(cruiseVoyageGroupSegments)
|
|
115
|
+
.values(data)
|
|
116
|
+
.returning();
|
|
117
|
+
if (!row)
|
|
118
|
+
throw new Error("Failed to create voyage group segment");
|
|
119
|
+
return row;
|
|
120
|
+
},
|
|
121
|
+
async updateVoyageGroupSegment(db, id, data) {
|
|
122
|
+
const [row] = await db
|
|
123
|
+
.update(cruiseVoyageGroupSegments)
|
|
124
|
+
.set({ ...data, ...setUpdated })
|
|
125
|
+
.where(eq(cruiseVoyageGroupSegments.id, id))
|
|
126
|
+
.returning();
|
|
127
|
+
return row ?? null;
|
|
128
|
+
},
|
|
129
|
+
async deleteVoyageGroupSegment(db, id) {
|
|
130
|
+
const rows = await db
|
|
131
|
+
.delete(cruiseVoyageGroupSegments)
|
|
132
|
+
.where(eq(cruiseVoyageGroupSegments.id, id))
|
|
133
|
+
.returning({ id: cruiseVoyageGroupSegments.id });
|
|
134
|
+
return rows.length > 0;
|
|
135
|
+
},
|
|
136
|
+
async listCruises(db, query) {
|
|
137
|
+
const conditions = [];
|
|
138
|
+
if (query.cruiseType)
|
|
139
|
+
conditions.push(eq(cruises.cruiseType, query.cruiseType));
|
|
140
|
+
if (query.status)
|
|
141
|
+
conditions.push(eq(cruises.status, query.status));
|
|
142
|
+
if (query.lineSupplierId)
|
|
143
|
+
conditions.push(eq(cruises.lineSupplierId, query.lineSupplierId));
|
|
144
|
+
if (query.region) {
|
|
145
|
+
// agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
146
|
+
conditions.push(sql `${cruises.regions} @> ${JSON.stringify([query.region])}::jsonb`);
|
|
147
|
+
}
|
|
148
|
+
if (query.search) {
|
|
149
|
+
const term = `%${query.search}%`;
|
|
150
|
+
conditions.push(or(ilike(cruises.name, term), ilike(cruises.description, term)));
|
|
151
|
+
}
|
|
152
|
+
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
|
153
|
+
const { limit, offset } = paginate(query);
|
|
154
|
+
const [rows, totalRows] = await Promise.all([
|
|
155
|
+
db
|
|
156
|
+
.select()
|
|
157
|
+
.from(cruises)
|
|
158
|
+
.where(where)
|
|
159
|
+
.orderBy(desc(cruises.createdAt))
|
|
160
|
+
.limit(limit)
|
|
161
|
+
.offset(offset),
|
|
162
|
+
db.select({ value: count() }).from(cruises).where(where),
|
|
163
|
+
]);
|
|
164
|
+
return { data: rows, total: totalRows[0]?.value ?? 0, limit, offset };
|
|
165
|
+
},
|
|
166
|
+
async getCruiseById(db, id, options = {}) {
|
|
167
|
+
const [row] = await db.select().from(cruises).where(eq(cruises.id, id)).limit(1);
|
|
168
|
+
if (!row)
|
|
169
|
+
return null;
|
|
170
|
+
const out = { ...row };
|
|
171
|
+
if (options.withSailings) {
|
|
172
|
+
out.sailings = await db
|
|
173
|
+
.select()
|
|
174
|
+
.from(cruiseSailings)
|
|
175
|
+
.where(eq(cruiseSailings.cruiseId, id))
|
|
176
|
+
.orderBy(asc(cruiseSailings.departureDate));
|
|
177
|
+
}
|
|
178
|
+
if (options.withDays) {
|
|
179
|
+
out.days = await db
|
|
180
|
+
.select()
|
|
181
|
+
.from(cruiseDays)
|
|
182
|
+
.where(eq(cruiseDays.cruiseId, id))
|
|
183
|
+
.orderBy(asc(cruiseDays.dayNumber));
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
},
|
|
187
|
+
async createCruise(db, data, runtime = {}) {
|
|
188
|
+
const [row] = await db
|
|
189
|
+
.insert(cruises)
|
|
190
|
+
.values(data)
|
|
191
|
+
.returning();
|
|
192
|
+
if (!row)
|
|
193
|
+
throw new Error("Failed to create cruise");
|
|
194
|
+
await reprojectIfPossible(db, row.id);
|
|
195
|
+
await emitCruiseLifecycleEvent(runtime.eventBus, CRUISE_CREATED_EVENT, { id: row.id });
|
|
196
|
+
return row;
|
|
197
|
+
},
|
|
198
|
+
async updateCruise(db, id, data, runtime = {}) {
|
|
199
|
+
const [row] = await db
|
|
200
|
+
.update(cruises)
|
|
201
|
+
.set({ ...data, ...setUpdated })
|
|
202
|
+
.where(eq(cruises.id, id))
|
|
203
|
+
.returning();
|
|
204
|
+
if (row) {
|
|
205
|
+
await reprojectIfPossible(db, row.id);
|
|
206
|
+
await emitCruiseLifecycleEvent(runtime.eventBus, CRUISE_UPDATED_EVENT, { id: row.id });
|
|
207
|
+
}
|
|
208
|
+
return row ?? null;
|
|
209
|
+
},
|
|
210
|
+
async archiveCruise(db, id, runtime = {}) {
|
|
211
|
+
const [row] = await db
|
|
212
|
+
.update(cruises)
|
|
213
|
+
.set({ status: "archived", ...setUpdated })
|
|
214
|
+
.where(eq(cruises.id, id))
|
|
215
|
+
.returning();
|
|
216
|
+
if (row) {
|
|
217
|
+
await reprojectIfPossible(db, row.id);
|
|
218
|
+
await emitCruiseLifecycleEvent(runtime.eventBus, CRUISE_DELETED_EVENT, { id: row.id });
|
|
219
|
+
}
|
|
220
|
+
return row ?? null;
|
|
221
|
+
},
|
|
222
|
+
async recomputeCruiseAggregates(db, cruiseId) {
|
|
223
|
+
// Lowest available price across all of this cruise's sailings × cabin categories × occupancies.
|
|
224
|
+
const [priceAgg] = await db
|
|
225
|
+
.select({
|
|
226
|
+
lowest: sql `MIN(${cruisePrices.pricePerPerson}::numeric)::text`,
|
|
227
|
+
currency: sql `(ARRAY_AGG(${cruisePrices.currency} ORDER BY ${cruisePrices.pricePerPerson}::numeric ASC))[1]`,
|
|
228
|
+
})
|
|
229
|
+
.from(cruisePrices)
|
|
230
|
+
.innerJoin(cruiseSailings, eq(cruisePrices.sailingId, cruiseSailings.id))
|
|
231
|
+
.where(
|
|
232
|
+
// agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
233
|
+
and(eq(cruiseSailings.cruiseId, cruiseId), sql `${cruisePrices.availability} <> 'sold_out'`));
|
|
234
|
+
const [dateAgg] = await db
|
|
235
|
+
.select({
|
|
236
|
+
earliest: sql `MIN(${cruiseSailings.departureDate})`,
|
|
237
|
+
latest: sql `MAX(${cruiseSailings.departureDate})`,
|
|
238
|
+
})
|
|
239
|
+
.from(cruiseSailings)
|
|
240
|
+
.where(eq(cruiseSailings.cruiseId, cruiseId));
|
|
241
|
+
const [row] = await db
|
|
242
|
+
.update(cruises)
|
|
243
|
+
.set({
|
|
244
|
+
lowestPriceCached: priceAgg?.lowest ?? null,
|
|
245
|
+
lowestPriceCurrencyCached: priceAgg?.currency ?? null,
|
|
246
|
+
earliestDepartureCached: dateAgg?.earliest ?? null,
|
|
247
|
+
latestDepartureCached: dateAgg?.latest ?? null,
|
|
248
|
+
...setUpdated,
|
|
249
|
+
})
|
|
250
|
+
.where(eq(cruises.id, cruiseId))
|
|
251
|
+
.returning();
|
|
252
|
+
if (row)
|
|
253
|
+
await reprojectIfPossible(db, row.id);
|
|
254
|
+
return row ?? null;
|
|
255
|
+
},
|
|
256
|
+
// ---------- sailings ----------
|
|
257
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import type { CruiseAdapter, SourceRef } from "./adapters/index.js";
|
|
3
|
+
import { type Cruise } from "./schema-core.js";
|
|
4
|
+
/**
|
|
5
|
+
* One-way conversion of an external cruise into a local cruise.
|
|
6
|
+
*
|
|
7
|
+
* Fetches the cruise + ship + cabin categories + sailings (with itineraries)
|
|
8
|
+
* from the adapter and inserts them as local rows in one transaction. After
|
|
9
|
+
* detach the operator owns the data and can edit it freely; the upstream
|
|
10
|
+
* link is severed (future calls to the original external key still work
|
|
11
|
+
* upstream-side, but the local DB has its own snapshot).
|
|
12
|
+
*
|
|
13
|
+
* Returns the newly-created local cruise row.
|
|
14
|
+
*/
|
|
15
|
+
export declare function detachExternalCruise(db: PostgresJsDatabase, adapter: CruiseAdapter, sourceRef: SourceRef, options?: {
|
|
16
|
+
slugSuffix?: string;
|
|
17
|
+
}): Promise<Cruise>;
|
|
18
|
+
//# sourceMappingURL=service-detach.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-detach.d.ts","sourceRoot":"","sources":["../src/service-detach.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAEnE,OAAO,EAAE,KAAK,MAAM,EAA2B,MAAM,kBAAkB,CAAA;AAIvE;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,SAAS,EACpB,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GACpC,OAAO,CAAC,MAAM,CAAC,CAsKjB"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { eq } from "drizzle-orm";
|
|
2
|
+
import { cruiseCabinCategories, cruiseDecks, cruiseShips } from "./schema-cabins.js";
|
|
3
|
+
import { cruiseSailings, cruises } from "./schema-core.js";
|
|
4
|
+
import { cruiseSailingDays } from "./schema-itinerary.js";
|
|
5
|
+
import { cruisesService } from "./service.js";
|
|
6
|
+
/**
|
|
7
|
+
* One-way conversion of an external cruise into a local cruise.
|
|
8
|
+
*
|
|
9
|
+
* Fetches the cruise + ship + cabin categories + sailings (with itineraries)
|
|
10
|
+
* from the adapter and inserts them as local rows in one transaction. After
|
|
11
|
+
* detach the operator owns the data and can edit it freely; the upstream
|
|
12
|
+
* link is severed (future calls to the original external key still work
|
|
13
|
+
* upstream-side, but the local DB has its own snapshot).
|
|
14
|
+
*
|
|
15
|
+
* Returns the newly-created local cruise row.
|
|
16
|
+
*/
|
|
17
|
+
export async function detachExternalCruise(db, adapter, sourceRef, options = {}) {
|
|
18
|
+
const externalCruise = await adapter.fetchCruise(sourceRef);
|
|
19
|
+
if (!externalCruise) {
|
|
20
|
+
throw new Error(`Adapter '${adapter.name}' returned no cruise for ref ${sourceRef.externalId}`);
|
|
21
|
+
}
|
|
22
|
+
return db.transaction(async (tx) => {
|
|
23
|
+
let localShipId = null;
|
|
24
|
+
// 1. Snapshot ship + decks + cabin categories if the cruise references a ship.
|
|
25
|
+
if (externalCruise.defaultShipRef) {
|
|
26
|
+
const externalShip = await adapter.fetchShip(externalCruise.defaultShipRef);
|
|
27
|
+
if (externalShip) {
|
|
28
|
+
const slug = await ensureUniqueShipSlug(tx, externalShip.slug, options.slugSuffix);
|
|
29
|
+
const [shipRow] = await tx
|
|
30
|
+
.insert(cruiseShips)
|
|
31
|
+
.values({
|
|
32
|
+
slug,
|
|
33
|
+
name: externalShip.name,
|
|
34
|
+
shipType: externalShip.shipType,
|
|
35
|
+
capacityGuests: externalShip.capacityGuests ?? null,
|
|
36
|
+
capacityCrew: externalShip.capacityCrew ?? null,
|
|
37
|
+
cabinCount: externalShip.cabinCount ?? null,
|
|
38
|
+
deckCount: externalShip.deckCount ?? null,
|
|
39
|
+
lengthMeters: externalShip.lengthMeters ?? null,
|
|
40
|
+
cruisingSpeedKnots: externalShip.cruisingSpeedKnots ?? null,
|
|
41
|
+
yearBuilt: externalShip.yearBuilt ?? null,
|
|
42
|
+
yearRefurbished: externalShip.yearRefurbished ?? null,
|
|
43
|
+
imo: externalShip.imo ?? null,
|
|
44
|
+
description: externalShip.description ?? null,
|
|
45
|
+
deckPlanUrl: externalShip.deckPlanUrl ?? null,
|
|
46
|
+
gallery: externalShip.gallery ?? [],
|
|
47
|
+
amenities: (externalShip.amenities ?? {}),
|
|
48
|
+
externalRefs: { [adapter.name]: externalShip.sourceRef.externalId },
|
|
49
|
+
isActive: true,
|
|
50
|
+
})
|
|
51
|
+
.returning();
|
|
52
|
+
if (!shipRow)
|
|
53
|
+
throw new Error("Failed to insert detached ship");
|
|
54
|
+
localShipId = shipRow.id;
|
|
55
|
+
if (externalShip.decks) {
|
|
56
|
+
for (const deck of externalShip.decks) {
|
|
57
|
+
await tx.insert(cruiseDecks).values({
|
|
58
|
+
shipId: shipRow.id,
|
|
59
|
+
name: deck.name,
|
|
60
|
+
level: deck.level ?? null,
|
|
61
|
+
planImageUrl: deck.planImageUrl ?? null,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (externalShip.categories) {
|
|
66
|
+
for (const cat of externalShip.categories) {
|
|
67
|
+
await tx.insert(cruiseCabinCategories).values({
|
|
68
|
+
shipId: shipRow.id,
|
|
69
|
+
code: cat.code,
|
|
70
|
+
name: cat.name,
|
|
71
|
+
roomType: cat.roomType,
|
|
72
|
+
description: cat.description ?? null,
|
|
73
|
+
minOccupancy: cat.minOccupancy,
|
|
74
|
+
maxOccupancy: cat.maxOccupancy,
|
|
75
|
+
squareFeet: cat.squareFeet ?? null,
|
|
76
|
+
wheelchairAccessible: cat.wheelchairAccessible ?? false,
|
|
77
|
+
amenities: cat.amenities ?? [],
|
|
78
|
+
featureCodes: cat.featureCodes ?? [],
|
|
79
|
+
bedConfigurations: cat.bedConfigurations ?? [],
|
|
80
|
+
accessibilityFeatures: cat.accessibilityFeatures ?? [],
|
|
81
|
+
viewType: cat.viewType ?? null,
|
|
82
|
+
images: cat.images ?? [],
|
|
83
|
+
floorplanImages: cat.floorplanImages ?? [],
|
|
84
|
+
gradeCodes: cat.gradeCodes ?? [],
|
|
85
|
+
externalRefs: { [adapter.name]: cat.sourceRef.externalId },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// 2. Insert the cruise row.
|
|
92
|
+
const slug = await ensureUniqueCruiseSlug(tx, externalCruise.slug, options.slugSuffix);
|
|
93
|
+
const [cruiseRow] = await tx
|
|
94
|
+
.insert(cruises)
|
|
95
|
+
.values({
|
|
96
|
+
slug,
|
|
97
|
+
name: externalCruise.name,
|
|
98
|
+
cruiseType: externalCruise.cruiseType,
|
|
99
|
+
defaultShipId: localShipId,
|
|
100
|
+
nights: externalCruise.nights,
|
|
101
|
+
embarkPortCanonicalPlaceId: externalCruise.embarkPortCanonicalPlaceId ?? null,
|
|
102
|
+
disembarkPortCanonicalPlaceId: externalCruise.disembarkPortCanonicalPlaceId ?? null,
|
|
103
|
+
description: externalCruise.description ?? null,
|
|
104
|
+
shortDescription: externalCruise.shortDescription ?? null,
|
|
105
|
+
highlights: externalCruise.highlights ?? [],
|
|
106
|
+
inclusionsHtml: externalCruise.inclusionsHtml ?? null,
|
|
107
|
+
exclusionsHtml: externalCruise.exclusionsHtml ?? null,
|
|
108
|
+
regionIds: externalCruise.regionIds ?? [],
|
|
109
|
+
waterwayIds: externalCruise.waterwayIds ?? [],
|
|
110
|
+
portIds: externalCruise.portIds ?? [],
|
|
111
|
+
countryIso: externalCruise.countryIso ?? [],
|
|
112
|
+
regions: externalCruise.regions ?? [],
|
|
113
|
+
waterways: externalCruise.waterways ?? [],
|
|
114
|
+
ports: externalCruise.ports ?? [],
|
|
115
|
+
countries: externalCruise.countries ?? [],
|
|
116
|
+
themes: externalCruise.themes ?? [],
|
|
117
|
+
heroImageUrl: externalCruise.heroImageUrl ?? null,
|
|
118
|
+
mapImageUrl: externalCruise.mapImageUrl ?? null,
|
|
119
|
+
status: externalCruise.status ?? "draft",
|
|
120
|
+
externalRefs: { [`${adapter.name}-detach-source`]: externalCruise.sourceRef.externalId },
|
|
121
|
+
})
|
|
122
|
+
.returning();
|
|
123
|
+
if (!cruiseRow)
|
|
124
|
+
throw new Error("Failed to insert detached cruise");
|
|
125
|
+
// 3. Snapshot sailings with their itinerary day overrides. v1 falls back to
|
|
126
|
+
// the default ship for every sailing when an external sailing references
|
|
127
|
+
// a different ship — multi-ship sailing detach can be added later.
|
|
128
|
+
if (localShipId) {
|
|
129
|
+
const sailings = await adapter.listSailingsForCruise(externalCruise.sourceRef);
|
|
130
|
+
for (const sailing of sailings) {
|
|
131
|
+
const [sailingRow] = await tx
|
|
132
|
+
.insert(cruiseSailings)
|
|
133
|
+
.values({
|
|
134
|
+
cruiseId: cruiseRow.id,
|
|
135
|
+
shipId: localShipId,
|
|
136
|
+
departureDate: sailing.departureDate,
|
|
137
|
+
returnDate: sailing.returnDate,
|
|
138
|
+
embarkPortFacilityId: null,
|
|
139
|
+
embarkPortCanonicalPlaceId: sailing.embarkPortCanonicalPlaceId ?? null,
|
|
140
|
+
disembarkPortFacilityId: null,
|
|
141
|
+
disembarkPortCanonicalPlaceId: sailing.disembarkPortCanonicalPlaceId ?? null,
|
|
142
|
+
direction: sailing.direction ?? null,
|
|
143
|
+
availabilityNote: sailing.availabilityNote ?? null,
|
|
144
|
+
isCharter: sailing.isCharter ?? false,
|
|
145
|
+
salesStatus: sailing.salesStatus ?? "open",
|
|
146
|
+
externalRefs: { [`${adapter.name}-detach-source`]: sailing.sourceRef.externalId },
|
|
147
|
+
lastSyncedAt: new Date(),
|
|
148
|
+
})
|
|
149
|
+
.returning();
|
|
150
|
+
if (!sailingRow)
|
|
151
|
+
throw new Error("Failed to insert detached sailing");
|
|
152
|
+
const days = await adapter.fetchSailingItinerary(sailing.sourceRef);
|
|
153
|
+
for (const day of days) {
|
|
154
|
+
await tx.insert(cruiseSailingDays).values({
|
|
155
|
+
sailingId: sailingRow.id,
|
|
156
|
+
dayNumber: day.dayNumber,
|
|
157
|
+
title: day.title ?? null,
|
|
158
|
+
description: day.description ?? null,
|
|
159
|
+
portFacilityId: null,
|
|
160
|
+
portCanonicalPlaceId: day.portCanonicalPlaceId ?? null,
|
|
161
|
+
arrivalTime: day.arrivalTime ?? null,
|
|
162
|
+
departureTime: day.departureTime ?? null,
|
|
163
|
+
isOvernight: day.isOvernight ?? null,
|
|
164
|
+
isSeaDay: day.isSeaDay ?? null,
|
|
165
|
+
isExpeditionLanding: day.isExpeditionLanding ?? null,
|
|
166
|
+
isSkipped: false,
|
|
167
|
+
meals: day.meals ?? null,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// 4. Recompute aggregates so the new cruise reports correct cached fields.
|
|
173
|
+
await cruisesService.recomputeCruiseAggregates(tx, cruiseRow.id);
|
|
174
|
+
const [refreshed] = await tx.select().from(cruises).where(eq(cruises.id, cruiseRow.id)).limit(1);
|
|
175
|
+
return refreshed ?? cruiseRow;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
async function ensureUniqueCruiseSlug(tx, baseSlug, suffix) {
|
|
179
|
+
const candidate = suffix ? `${baseSlug}-${suffix}` : baseSlug;
|
|
180
|
+
const [exists] = await tx
|
|
181
|
+
.select({ id: cruises.id })
|
|
182
|
+
.from(cruises)
|
|
183
|
+
.where(eq(cruises.slug, candidate))
|
|
184
|
+
.limit(1);
|
|
185
|
+
if (!exists)
|
|
186
|
+
return candidate;
|
|
187
|
+
return `${candidate}-${Math.random().toString(36).slice(2, 6)}`;
|
|
188
|
+
}
|
|
189
|
+
async function ensureUniqueShipSlug(tx, baseSlug, suffix) {
|
|
190
|
+
const candidate = suffix ? `${baseSlug}-${suffix}` : baseSlug;
|
|
191
|
+
const [exists] = await tx
|
|
192
|
+
.select({ id: cruiseShips.id })
|
|
193
|
+
.from(cruiseShips)
|
|
194
|
+
.where(eq(cruiseShips.slug, candidate))
|
|
195
|
+
.limit(1);
|
|
196
|
+
if (!exists)
|
|
197
|
+
return candidate;
|
|
198
|
+
return `${candidate}-${Math.random().toString(36).slice(2, 6)}`;
|
|
199
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import type { CruiseEnrichmentProgram } from "./schema-content.js";
|
|
3
|
+
import type { InsertEnrichmentProgram, ReplaceEnrichmentPrograms, UpdateEnrichmentProgram } from "./validation-content.js";
|
|
4
|
+
export declare const cruiseEnrichmentService: {
|
|
5
|
+
listEnrichmentPrograms(db: PostgresJsDatabase, cruiseId: string): Promise<CruiseEnrichmentProgram[]>;
|
|
6
|
+
createEnrichmentProgram(db: PostgresJsDatabase, data: InsertEnrichmentProgram): Promise<CruiseEnrichmentProgram>;
|
|
7
|
+
updateEnrichmentProgram(db: PostgresJsDatabase, id: string, data: UpdateEnrichmentProgram): Promise<CruiseEnrichmentProgram | null>;
|
|
8
|
+
deleteEnrichmentProgram(db: PostgresJsDatabase, id: string): Promise<boolean>;
|
|
9
|
+
replaceEnrichmentPrograms(db: PostgresJsDatabase, payload: ReplaceEnrichmentPrograms): Promise<CruiseEnrichmentProgram[]>;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=service-enrichment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-enrichment.d.ts","sourceRoot":"","sources":["../src/service-enrichment.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA;AAGlE,OAAO,KAAK,EACV,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,EACxB,MAAM,yBAAyB,CAAA;AAEhC,eAAO,MAAM,uBAAuB;+BAE5B,kBAAkB,YACZ,MAAM,GACf,OAAO,CAAC,uBAAuB,EAAE,CAAC;gCAS/B,kBAAkB,QAChB,uBAAuB,GAC5B,OAAO,CAAC,uBAAuB,CAAC;gCAO7B,kBAAkB,MAClB,MAAM,QACJ,uBAAuB,GAC5B,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC;gCASR,kBAAkB,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;kCAS7E,kBAAkB,WACb,yBAAyB,GACjC,OAAO,CAAC,uBAAuB,EAAE,CAAC;CAatC,CAAA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { asc, eq } from "drizzle-orm";
|
|
2
|
+
import { cruiseEnrichmentPrograms } from "./schema-content.js";
|
|
3
|
+
import { setUpdated } from "./service-shared.js";
|
|
4
|
+
export const cruiseEnrichmentService = {
|
|
5
|
+
async listEnrichmentPrograms(db, cruiseId) {
|
|
6
|
+
return db
|
|
7
|
+
.select()
|
|
8
|
+
.from(cruiseEnrichmentPrograms)
|
|
9
|
+
.where(eq(cruiseEnrichmentPrograms.cruiseId, cruiseId))
|
|
10
|
+
.orderBy(asc(cruiseEnrichmentPrograms.sortOrder), asc(cruiseEnrichmentPrograms.name));
|
|
11
|
+
},
|
|
12
|
+
async createEnrichmentProgram(db, data) {
|
|
13
|
+
const [row] = await db.insert(cruiseEnrichmentPrograms).values(data).returning();
|
|
14
|
+
if (!row)
|
|
15
|
+
throw new Error("Failed to create enrichment program");
|
|
16
|
+
return row;
|
|
17
|
+
},
|
|
18
|
+
async updateEnrichmentProgram(db, id, data) {
|
|
19
|
+
const [row] = await db
|
|
20
|
+
.update(cruiseEnrichmentPrograms)
|
|
21
|
+
.set({ ...data, ...setUpdated })
|
|
22
|
+
.where(eq(cruiseEnrichmentPrograms.id, id))
|
|
23
|
+
.returning();
|
|
24
|
+
return row ?? null;
|
|
25
|
+
},
|
|
26
|
+
async deleteEnrichmentProgram(db, id) {
|
|
27
|
+
const result = await db
|
|
28
|
+
.delete(cruiseEnrichmentPrograms)
|
|
29
|
+
.where(eq(cruiseEnrichmentPrograms.id, id))
|
|
30
|
+
.returning({ id: cruiseEnrichmentPrograms.id });
|
|
31
|
+
return result.length > 0;
|
|
32
|
+
},
|
|
33
|
+
async replaceEnrichmentPrograms(db, payload) {
|
|
34
|
+
return db.transaction(async (tx) => {
|
|
35
|
+
await tx
|
|
36
|
+
.delete(cruiseEnrichmentPrograms)
|
|
37
|
+
.where(eq(cruiseEnrichmentPrograms.cruiseId, payload.cruiseId));
|
|
38
|
+
if (payload.programs.length === 0)
|
|
39
|
+
return [];
|
|
40
|
+
const inserted = await tx
|
|
41
|
+
.insert(cruiseEnrichmentPrograms)
|
|
42
|
+
.values(payload.programs.map((p) => ({ ...p, cruiseId: payload.cruiseId })))
|
|
43
|
+
.returning();
|
|
44
|
+
return inserted;
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-agnostic external cruise catalog refresh.
|
|
3
|
+
*
|
|
4
|
+
* Reconciles both local browse/search projections (`cruise_search_index`) and,
|
|
5
|
+
* when catalog runtime dependencies are supplied, catalog sourced entries plus
|
|
6
|
+
* catalog search slices. The service never imports a concrete provider.
|
|
7
|
+
*/
|
|
8
|
+
import type { DocumentBuilder, FieldPolicyRegistry, IndexerService } from "@voyant-travel/catalog";
|
|
9
|
+
import { type SourceAdapterRegistry, type SyncProgressEvent, type SyncSourcesSummary } from "@voyant-travel/catalog/booking-engine";
|
|
10
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
11
|
+
import { type ExternalAdapterRefreshResult } from "./service-search.js";
|
|
12
|
+
export interface ExternalCruiseCatalogRefreshOptions {
|
|
13
|
+
db: PostgresJsDatabase;
|
|
14
|
+
/**
|
|
15
|
+
* Optional catalog source registry. Supplying it lets the refresh update
|
|
16
|
+
* `catalog_sourced_entries` and catalog search slices from cruise shims.
|
|
17
|
+
*/
|
|
18
|
+
sourceAdapterRegistry?: SourceAdapterRegistry;
|
|
19
|
+
indexerService?: IndexerService;
|
|
20
|
+
fieldPolicyRegistries?: ReadonlyMap<string, FieldPolicyRegistry>;
|
|
21
|
+
wrapCatalogBuilder?: (builder: DocumentBuilder) => DocumentBuilder;
|
|
22
|
+
onCatalogProgress?: (event: SyncProgressEvent) => void;
|
|
23
|
+
}
|
|
24
|
+
export interface ExternalCruiseCatalogRefreshResult {
|
|
25
|
+
cruiseSearchIndex: {
|
|
26
|
+
adapters: Array<{
|
|
27
|
+
adapter: string;
|
|
28
|
+
} & ExternalAdapterRefreshResult>;
|
|
29
|
+
upserted: number;
|
|
30
|
+
removed: number;
|
|
31
|
+
errors: Array<{
|
|
32
|
+
adapter: string;
|
|
33
|
+
error: string;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
catalog?: SyncSourcesSummary;
|
|
37
|
+
}
|
|
38
|
+
export declare function refreshExternalCruiseCatalog(options: ExternalCruiseCatalogRefreshOptions): Promise<ExternalCruiseCatalogRefreshResult>;
|
|
39
|
+
//# sourceMappingURL=service-external-refresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-external-refresh.d.ts","sourceRoot":"","sources":["../src/service-external-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAClG,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EAExB,MAAM,uCAAuC,CAAA;AAC9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAGjE,OAAO,EAAwB,KAAK,4BAA4B,EAAE,MAAM,qBAAqB,CAAA;AAE7F,MAAM,WAAW,mCAAmC;IAClD,EAAE,EAAE,kBAAkB,CAAA;IACtB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAA;IAC7C,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B,qBAAqB,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;IAChE,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,eAAe,CAAA;IAClE,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAA;CACvD;AAED,MAAM,WAAW,kCAAkC;IACjD,iBAAiB,EAAE;QACjB,QAAQ,EAAE,KAAK,CACb;YACE,OAAO,EAAE,MAAM,CAAA;SAChB,GAAG,4BAA4B,CACjC,CAAA;QACD,QAAQ,EAAE,MAAM,CAAA;QAChB,OAAO,EAAE,MAAM,CAAA;QACf,MAAM,EAAE,KAAK,CAAC;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAClD,CAAA;IACD,OAAO,CAAC,EAAE,kBAAkB,CAAA;CAC7B;AAED,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,mCAAmC,GAC3C,OAAO,CAAC,kCAAkC,CAAC,CAsC7C"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-agnostic external cruise catalog refresh.
|
|
3
|
+
*
|
|
4
|
+
* Reconciles both local browse/search projections (`cruise_search_index`) and,
|
|
5
|
+
* when catalog runtime dependencies are supplied, catalog sourced entries plus
|
|
6
|
+
* catalog search slices. The service never imports a concrete provider.
|
|
7
|
+
*/
|
|
8
|
+
import { syncSources, } from "@voyant-travel/catalog/booking-engine";
|
|
9
|
+
import { listCruiseAdapters } from "./adapters/registry.js";
|
|
10
|
+
import { cruisesSearchService } from "./service-search.js";
|
|
11
|
+
export async function refreshExternalCruiseCatalog(options) {
|
|
12
|
+
const adapters = listCruiseAdapters();
|
|
13
|
+
const cruiseSearchIndex = {
|
|
14
|
+
adapters: [],
|
|
15
|
+
upserted: 0,
|
|
16
|
+
removed: 0,
|
|
17
|
+
errors: [],
|
|
18
|
+
};
|
|
19
|
+
for (const adapter of adapters) {
|
|
20
|
+
try {
|
|
21
|
+
const result = await cruisesSearchService.refreshExternalForAdapter(options.db, adapter);
|
|
22
|
+
cruiseSearchIndex.adapters.push({ adapter: adapter.name, ...result });
|
|
23
|
+
cruiseSearchIndex.upserted += result.upserted;
|
|
24
|
+
cruiseSearchIndex.removed += result.removed;
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
cruiseSearchIndex.errors.push({
|
|
28
|
+
adapter: adapter.name,
|
|
29
|
+
error: err instanceof Error ? err.message : String(err),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
let catalog;
|
|
34
|
+
if (options.sourceAdapterRegistry && options.indexerService && options.fieldPolicyRegistries) {
|
|
35
|
+
catalog = await syncSources({
|
|
36
|
+
registry: options.sourceAdapterRegistry,
|
|
37
|
+
indexerService: options.indexerService,
|
|
38
|
+
fieldPolicyRegistries: options.fieldPolicyRegistries,
|
|
39
|
+
db: options.db,
|
|
40
|
+
verticals: ["cruises"],
|
|
41
|
+
pruneMissing: true,
|
|
42
|
+
...(options.wrapCatalogBuilder ? { wrapBuilder: options.wrapCatalogBuilder } : {}),
|
|
43
|
+
...(options.onCatalogProgress ? { onProgress: options.onCatalogProgress } : {}),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return { cruiseSearchIndex, ...(catalog ? { catalog } : {}) };
|
|
47
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CruiseDay, CruiseSailingDay } from "./schema-itinerary.js";
|
|
2
|
+
export type EffectiveItineraryDay = {
|
|
3
|
+
dayNumber: number;
|
|
4
|
+
title: string | null;
|
|
5
|
+
description: string | null;
|
|
6
|
+
portFacilityId: string | null;
|
|
7
|
+
portCanonicalPlaceId: string | null;
|
|
8
|
+
arrivalTime: string | null;
|
|
9
|
+
departureTime: string | null;
|
|
10
|
+
isOvernight: boolean;
|
|
11
|
+
isSeaDay: boolean;
|
|
12
|
+
isExpeditionLanding: boolean;
|
|
13
|
+
isSkipped: boolean;
|
|
14
|
+
meals: {
|
|
15
|
+
breakfast?: boolean;
|
|
16
|
+
lunch?: boolean;
|
|
17
|
+
dinner?: boolean;
|
|
18
|
+
};
|
|
19
|
+
hasOverride: boolean;
|
|
20
|
+
};
|
|
21
|
+
export declare function mergeDay(base: CruiseDay, override: CruiseSailingDay | undefined): EffectiveItineraryDay;
|
|
22
|
+
//# sourceMappingURL=service-itinerary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-itinerary.d.ts","sourceRoot":"","sources":["../src/service-itinerary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAExE,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,WAAW,EAAE,OAAO,CAAA;IACpB,QAAQ,EAAE,OAAO,CAAA;IACjB,mBAAmB,EAAE,OAAO,CAAA;IAC5B,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;IACjE,WAAW,EAAE,OAAO,CAAA;CACrB,CAAA;AAED,wBAAgB,QAAQ,CACtB,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,gBAAgB,GAAG,SAAS,GACrC,qBAAqB,CAiCvB"}
|