@voyantjs/bookings 0.4.5 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extensions/suppliers.d.ts.map +1 -1
- package/dist/extensions/suppliers.js +3 -2
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -8
- package/dist/route-runtime.d.ts +8 -0
- package/dist/route-runtime.d.ts.map +1 -0
- package/dist/route-runtime.js +17 -0
- package/dist/routes-groups.d.ts +354 -0
- package/dist/routes-groups.d.ts.map +1 -0
- package/dist/routes-groups.js +62 -0
- package/dist/routes-public.d.ts +5 -5
- package/dist/routes-public.d.ts.map +1 -1
- package/dist/routes-public.js +8 -7
- package/dist/routes-shared.d.ts +2 -0
- package/dist/routes-shared.d.ts.map +1 -1
- package/dist/routes.d.ts +413 -257
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +100 -92
- package/dist/schema-core.d.ts +1 -1
- package/dist/schema-groups.d.ts +261 -0
- package/dist/schema-groups.d.ts.map +1 -0
- package/dist/schema-groups.js +34 -0
- package/dist/schema-items.d.ts +2 -2
- package/dist/schema-relations.d.ts +8 -0
- package/dist/schema-relations.d.ts.map +1 -1
- package/dist/schema-relations.js +15 -0
- package/dist/schema.d.ts +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +1 -0
- package/dist/service-groups.d.ts +69 -0
- package/dist/service-groups.d.ts.map +1 -0
- package/dist/service-groups.js +199 -0
- package/dist/service-public.d.ts +14 -14
- package/dist/service.d.ts +22 -22
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +3 -0
- package/dist/validation-public.d.ts +11 -11
- package/dist/validation-shared.d.ts +3 -3
- package/dist/validation.d.ts +59 -12
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +24 -0
- 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":"
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAC7D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAA;AAqM7C,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iDAg3Ba,CAAA;AAEvC,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAA;AAChD,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAA"}
|
package/dist/routes.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ForbiddenApiError, handleApiError, normalizeValidationError, parseJsonBody, parseQuery, requireUserId, UnauthorizedApiError, } from "@voyantjs/hono";
|
|
2
2
|
import { Hono } from "hono";
|
|
3
3
|
import { createBookingPiiService } from "./pii.js";
|
|
4
|
-
import {
|
|
4
|
+
import { BOOKING_ROUTE_RUNTIME_CONTAINER_KEY, buildBookingRouteRuntime, } from "./route-runtime.js";
|
|
5
|
+
import { bookingGroupRoutes } from "./routes-groups.js";
|
|
5
6
|
import { bookingPiiAccessLog } from "./schema.js";
|
|
6
7
|
import { bookingsService } from "./service.js";
|
|
8
|
+
import { bookingGroupsService } from "./service-groups.js";
|
|
7
9
|
import { publicBookingsService } from "./service-public.js";
|
|
8
10
|
import { bookingListQuerySchema, cancelBookingSchema, confirmBookingSchema, convertProductSchema, createBookingSchema, expireBookingSchema, expireStaleBookingsSchema, extendBookingHoldSchema, insertBookingDocumentSchema, insertBookingFulfillmentSchema, insertBookingItemParticipantSchema, insertBookingItemSchema, insertBookingNoteSchema, insertParticipantSchema, insertPassengerSchema, insertSupplierStatusSchema, internalBookingOverviewLookupQuerySchema, recordBookingRedemptionSchema, reserveBookingFromTransactionSchema, reserveBookingSchema, updateBookingFulfillmentSchema, updateBookingItemSchema, updateBookingSchema, updateBookingStatusSchema, updateParticipantSchema, updatePassengerSchema, updateSupplierStatusSchema, upsertParticipantTravelDetailsSchema, } from "./validation.js";
|
|
9
11
|
function hasPiiScope(scopes, action) {
|
|
@@ -41,7 +43,10 @@ async function authorizeBookingPiiAccess(c, input) {
|
|
|
41
43
|
outcome: "denied",
|
|
42
44
|
reason: "missing_user",
|
|
43
45
|
});
|
|
44
|
-
return {
|
|
46
|
+
return {
|
|
47
|
+
allowed: false,
|
|
48
|
+
response: handleApiError(new UnauthorizedApiError(), c),
|
|
49
|
+
};
|
|
45
50
|
}
|
|
46
51
|
const customAuthorizer = c.get("authorizeBookingPii");
|
|
47
52
|
if (customAuthorizer) {
|
|
@@ -60,7 +65,10 @@ async function authorizeBookingPiiAccess(c, input) {
|
|
|
60
65
|
outcome: "denied",
|
|
61
66
|
reason: "custom_policy_denied",
|
|
62
67
|
});
|
|
63
|
-
return {
|
|
68
|
+
return {
|
|
69
|
+
allowed: false,
|
|
70
|
+
response: handleApiError(new ForbiddenApiError(), c),
|
|
71
|
+
};
|
|
64
72
|
}
|
|
65
73
|
return { allowed: true };
|
|
66
74
|
}
|
|
@@ -74,7 +82,10 @@ async function authorizeBookingPiiAccess(c, input) {
|
|
|
74
82
|
reason: "insufficient_scope",
|
|
75
83
|
metadata: { actor: actor ?? null },
|
|
76
84
|
});
|
|
77
|
-
return {
|
|
85
|
+
return {
|
|
86
|
+
allowed: false,
|
|
87
|
+
response: handleApiError(new ForbiddenApiError(), c),
|
|
88
|
+
};
|
|
78
89
|
}
|
|
79
90
|
return { allowed: true };
|
|
80
91
|
}
|
|
@@ -87,6 +98,33 @@ function handleKmsConfigError(c, error) {
|
|
|
87
98
|
}
|
|
88
99
|
return c.json({ error: "Booking PII encryption is not configured" }, 500);
|
|
89
100
|
}
|
|
101
|
+
function getRouteRuntime(c) {
|
|
102
|
+
try {
|
|
103
|
+
return (c.var.container?.resolve(BOOKING_ROUTE_RUNTIME_CONTAINER_KEY) ??
|
|
104
|
+
buildBookingRouteRuntime(c.env));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return buildBookingRouteRuntime(c.env);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function createAuditedBookingPiiService(c, bookingId) {
|
|
111
|
+
const runtime = getRouteRuntime(c);
|
|
112
|
+
return createBookingPiiService({
|
|
113
|
+
kms: runtime.getKmsProvider(),
|
|
114
|
+
onAudit: async (event) => {
|
|
115
|
+
await logBookingPiiAccess(c, {
|
|
116
|
+
bookingId,
|
|
117
|
+
participantId: event.participantId,
|
|
118
|
+
action: event.action === "encrypt"
|
|
119
|
+
? "update"
|
|
120
|
+
: event.action === "decrypt"
|
|
121
|
+
? "read"
|
|
122
|
+
: event.action,
|
|
123
|
+
outcome: "allowed",
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
90
128
|
// ==========================================================================
|
|
91
129
|
// Bookings — method-chained for Hono RPC type inference
|
|
92
130
|
// ==========================================================================
|
|
@@ -96,12 +134,12 @@ export const bookingRoutes = new Hono()
|
|
|
96
134
|
// ==========================================================================
|
|
97
135
|
// 1. GET / — List bookings
|
|
98
136
|
.get("/", async (c) => {
|
|
99
|
-
const query =
|
|
137
|
+
const query = parseQuery(c, bookingListQuerySchema);
|
|
100
138
|
return c.json(await bookingsService.listBookings(c.get("db"), query));
|
|
101
139
|
})
|
|
102
140
|
// 1a. GET /overview — Internal/admin booking overview lookup
|
|
103
141
|
.get("/overview", async (c) => {
|
|
104
|
-
const overview = await publicBookingsService.getOverviewByLookup(c.get("db"),
|
|
142
|
+
const overview = await publicBookingsService.getOverviewByLookup(c.get("db"), parseQuery(c, internalBookingOverviewLookupQuerySchema));
|
|
105
143
|
if (!overview) {
|
|
106
144
|
return c.json({ error: "Booking overview not found" }, 404);
|
|
107
145
|
}
|
|
@@ -117,7 +155,7 @@ export const bookingRoutes = new Hono()
|
|
|
117
155
|
})
|
|
118
156
|
// 3. POST /reserve — Reserve inventory and create on-hold booking
|
|
119
157
|
.post("/reserve", async (c) => {
|
|
120
|
-
const result = await bookingsService.reserveBooking(c.get("db"),
|
|
158
|
+
const result = await bookingsService.reserveBooking(c.get("db"), await parseJsonBody(c, reserveBookingSchema), c.get("userId"));
|
|
121
159
|
if ("booking" in result) {
|
|
122
160
|
return c.json({ data: result.booking }, 201);
|
|
123
161
|
}
|
|
@@ -137,7 +175,7 @@ export const bookingRoutes = new Hono()
|
|
|
137
175
|
})
|
|
138
176
|
// 3a. POST /from-product — Create booking draft from product definition
|
|
139
177
|
.post("/from-product", async (c) => {
|
|
140
|
-
const row = await bookingsService.createBookingFromProduct(c.get("db"),
|
|
178
|
+
const row = await bookingsService.createBookingFromProduct(c.get("db"), await parseJsonBody(c, convertProductSchema), c.get("userId"));
|
|
141
179
|
if (!row) {
|
|
142
180
|
return c.json({ error: "Product or option not found" }, 404);
|
|
143
181
|
}
|
|
@@ -145,7 +183,7 @@ export const bookingRoutes = new Hono()
|
|
|
145
183
|
})
|
|
146
184
|
// 3b. POST /from-offer/:offerId/reserve — Reserve booking from transaction offer
|
|
147
185
|
.post("/from-offer/:offerId/reserve", async (c) => {
|
|
148
|
-
const result = await bookingsService.reserveBookingFromOffer(c.get("db"), c.req.param("offerId"),
|
|
186
|
+
const result = await bookingsService.reserveBookingFromOffer(c.get("db"), c.req.param("offerId"), await parseJsonBody(c, reserveBookingFromTransactionSchema), c.get("userId"));
|
|
149
187
|
if (result.status === "not_found") {
|
|
150
188
|
return c.json({ error: "Offer not found" }, 404);
|
|
151
189
|
}
|
|
@@ -168,7 +206,7 @@ export const bookingRoutes = new Hono()
|
|
|
168
206
|
})
|
|
169
207
|
// 3c. POST /from-order/:orderId/reserve — Reserve booking from transaction order
|
|
170
208
|
.post("/from-order/:orderId/reserve", async (c) => {
|
|
171
|
-
const result = await bookingsService.reserveBookingFromOrder(c.get("db"), c.req.param("orderId"),
|
|
209
|
+
const result = await bookingsService.reserveBookingFromOrder(c.get("db"), c.req.param("orderId"), await parseJsonBody(c, reserveBookingFromTransactionSchema), c.get("userId"));
|
|
172
210
|
if (result.status === "not_found") {
|
|
173
211
|
return c.json({ error: "Order not found" }, 404);
|
|
174
212
|
}
|
|
@@ -191,21 +229,27 @@ export const bookingRoutes = new Hono()
|
|
|
191
229
|
})
|
|
192
230
|
// 4. POST / — Create booking (manual/backoffice only)
|
|
193
231
|
.post("/", async (c) => {
|
|
194
|
-
|
|
195
|
-
const parsed = createBookingSchema.safeParse(payload);
|
|
196
|
-
if (!parsed.success) {
|
|
232
|
+
try {
|
|
197
233
|
return c.json({
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
234
|
+
data: await bookingsService.createBooking(c.get("db"), await parseJsonBody(c, createBookingSchema, {
|
|
235
|
+
invalidBodyMessage: "Invalid booking create payload",
|
|
236
|
+
}), c.get("userId")),
|
|
237
|
+
}, 201);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
const validationError = normalizeValidationError(error);
|
|
241
|
+
if (validationError?.status === 400) {
|
|
242
|
+
return c.json({
|
|
243
|
+
error: validationError.message,
|
|
244
|
+
details: validationError.details?.fields ?? validationError.details,
|
|
245
|
+
}, 400);
|
|
246
|
+
}
|
|
247
|
+
throw error;
|
|
201
248
|
}
|
|
202
|
-
return c.json({
|
|
203
|
-
data: await bookingsService.createBooking(c.get("db"), parsed.data, c.get("userId")),
|
|
204
|
-
}, 201);
|
|
205
249
|
})
|
|
206
250
|
// 5. PATCH /:id — Update booking
|
|
207
251
|
.patch("/:id", async (c) => {
|
|
208
|
-
const row = await bookingsService.updateBooking(c.get("db"), c.req.param("id"),
|
|
252
|
+
const row = await bookingsService.updateBooking(c.get("db"), c.req.param("id"), await parseJsonBody(c, updateBookingSchema));
|
|
209
253
|
if (!row) {
|
|
210
254
|
return c.json({ error: "Booking not found" }, 404);
|
|
211
255
|
}
|
|
@@ -224,7 +268,7 @@ export const bookingRoutes = new Hono()
|
|
|
224
268
|
// ==========================================================================
|
|
225
269
|
// 7. PATCH /:id/status — Change booking status
|
|
226
270
|
.patch("/:id/status", async (c) => {
|
|
227
|
-
const result = await bookingsService.updateBookingStatus(c.get("db"), c.req.param("id"),
|
|
271
|
+
const result = await bookingsService.updateBookingStatus(c.get("db"), c.req.param("id"), await parseJsonBody(c, updateBookingStatusSchema), c.get("userId"));
|
|
228
272
|
if (result.status === "not_found") {
|
|
229
273
|
return c.json({ error: "Booking not found" }, 404);
|
|
230
274
|
}
|
|
@@ -238,7 +282,7 @@ export const bookingRoutes = new Hono()
|
|
|
238
282
|
})
|
|
239
283
|
// 8. POST /:id/confirm — Confirm an on-hold booking
|
|
240
284
|
.post("/:id/confirm", async (c) => {
|
|
241
|
-
const result = await bookingsService.confirmBooking(c.get("db"), c.req.param("id"),
|
|
285
|
+
const result = await bookingsService.confirmBooking(c.get("db"), c.req.param("id"), await parseJsonBody(c, confirmBookingSchema), c.get("userId"));
|
|
242
286
|
if (result.status === "not_found") {
|
|
243
287
|
return c.json({ error: "Booking not found" }, 404);
|
|
244
288
|
}
|
|
@@ -255,7 +299,7 @@ export const bookingRoutes = new Hono()
|
|
|
255
299
|
})
|
|
256
300
|
// 9. POST /:id/extend-hold — Extend booking hold expiry
|
|
257
301
|
.post("/:id/extend-hold", async (c) => {
|
|
258
|
-
const result = await bookingsService.extendBookingHold(c.get("db"), c.req.param("id"),
|
|
302
|
+
const result = await bookingsService.extendBookingHold(c.get("db"), c.req.param("id"), await parseJsonBody(c, extendBookingHoldSchema), c.get("userId"));
|
|
259
303
|
if (result.status === "not_found") {
|
|
260
304
|
return c.json({ error: "Booking not found" }, 404);
|
|
261
305
|
}
|
|
@@ -272,7 +316,7 @@ export const bookingRoutes = new Hono()
|
|
|
272
316
|
})
|
|
273
317
|
// 10. POST /:id/expire — Expire an on-hold booking
|
|
274
318
|
.post("/:id/expire", async (c) => {
|
|
275
|
-
const result = await bookingsService.expireBooking(c.get("db"), c.req.param("id"),
|
|
319
|
+
const result = await bookingsService.expireBooking(c.get("db"), c.req.param("id"), await parseJsonBody(c, expireBookingSchema), c.get("userId"));
|
|
276
320
|
if (result.status === "not_found") {
|
|
277
321
|
return c.json({ error: "Booking not found" }, 404);
|
|
278
322
|
}
|
|
@@ -286,11 +330,11 @@ export const bookingRoutes = new Hono()
|
|
|
286
330
|
})
|
|
287
331
|
// 10b. POST /expire-stale — Expire all stale on-hold bookings up to a cutoff
|
|
288
332
|
.post("/expire-stale", async (c) => {
|
|
289
|
-
return c.json(await bookingsService.expireStaleBookings(c.get("db"),
|
|
333
|
+
return c.json(await bookingsService.expireStaleBookings(c.get("db"), await parseJsonBody(c, expireStaleBookingsSchema), c.get("userId")));
|
|
290
334
|
})
|
|
291
335
|
// 11. POST /:id/cancel — Cancel a booking and release allocations
|
|
292
336
|
.post("/:id/cancel", async (c) => {
|
|
293
|
-
const result = await bookingsService.cancelBooking(c.get("db"), c.req.param("id"),
|
|
337
|
+
const result = await bookingsService.cancelBooking(c.get("db"), c.req.param("id"), await parseJsonBody(c, cancelBookingSchema), c.get("userId"));
|
|
294
338
|
if (result.status === "not_found") {
|
|
295
339
|
return c.json({ error: "Booking not found" }, 404);
|
|
296
340
|
}
|
|
@@ -335,21 +379,7 @@ export const bookingRoutes = new Hono()
|
|
|
335
379
|
return c.json({ error: "Participant not found" }, 404);
|
|
336
380
|
}
|
|
337
381
|
try {
|
|
338
|
-
const pii =
|
|
339
|
-
kms: createKmsProviderFromEnv(getRuntimeEnv(c)),
|
|
340
|
-
onAudit: async (event) => {
|
|
341
|
-
await logBookingPiiAccess(c, {
|
|
342
|
-
bookingId: participant.bookingId,
|
|
343
|
-
participantId: event.participantId,
|
|
344
|
-
action: event.action === "encrypt"
|
|
345
|
-
? "update"
|
|
346
|
-
: event.action === "decrypt"
|
|
347
|
-
? "read"
|
|
348
|
-
: event.action,
|
|
349
|
-
outcome: "allowed",
|
|
350
|
-
});
|
|
351
|
-
},
|
|
352
|
-
});
|
|
382
|
+
const pii = createAuditedBookingPiiService(c, participant.bookingId);
|
|
353
383
|
const details = await pii.getParticipantTravelDetails(c.get("db"), participant.id, c.get("userId"));
|
|
354
384
|
if (!details) {
|
|
355
385
|
await logBookingPiiAccess(c, {
|
|
@@ -369,7 +399,7 @@ export const bookingRoutes = new Hono()
|
|
|
369
399
|
})
|
|
370
400
|
// 9. POST /:id/participants — Add participant
|
|
371
401
|
.post("/:id/participants", async (c) => {
|
|
372
|
-
const row = await bookingsService.createParticipant(c.get("db"), c.req.param("id"),
|
|
402
|
+
const row = await bookingsService.createParticipant(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertParticipantSchema), c.get("userId"));
|
|
373
403
|
if (!row) {
|
|
374
404
|
return c.json({ error: "Booking not found" }, 404);
|
|
375
405
|
}
|
|
@@ -397,22 +427,8 @@ export const bookingRoutes = new Hono()
|
|
|
397
427
|
return c.json({ error: "Participant not found" }, 404);
|
|
398
428
|
}
|
|
399
429
|
try {
|
|
400
|
-
const pii =
|
|
401
|
-
|
|
402
|
-
onAudit: async (event) => {
|
|
403
|
-
await logBookingPiiAccess(c, {
|
|
404
|
-
bookingId: participant.bookingId,
|
|
405
|
-
participantId: event.participantId,
|
|
406
|
-
action: event.action === "encrypt"
|
|
407
|
-
? "update"
|
|
408
|
-
: event.action === "decrypt"
|
|
409
|
-
? "read"
|
|
410
|
-
: event.action,
|
|
411
|
-
outcome: "allowed",
|
|
412
|
-
});
|
|
413
|
-
},
|
|
414
|
-
});
|
|
415
|
-
const row = await pii.upsertParticipantTravelDetails(c.get("db"), participant.id, upsertParticipantTravelDetailsSchema.parse(await c.req.json()), c.get("userId"));
|
|
430
|
+
const pii = createAuditedBookingPiiService(c, participant.bookingId);
|
|
431
|
+
const row = await pii.upsertParticipantTravelDetails(c.get("db"), participant.id, await parseJsonBody(c, upsertParticipantTravelDetailsSchema), c.get("userId"));
|
|
416
432
|
if (!row) {
|
|
417
433
|
return c.json({ error: "Participant not found" }, 404);
|
|
418
434
|
}
|
|
@@ -424,7 +440,7 @@ export const bookingRoutes = new Hono()
|
|
|
424
440
|
})
|
|
425
441
|
// 10. PATCH /:id/participants/:participantId — Update participant
|
|
426
442
|
.patch("/:id/participants/:participantId", async (c) => {
|
|
427
|
-
const row = await bookingsService.updateParticipant(c.get("db"), c.req.param("participantId"),
|
|
443
|
+
const row = await bookingsService.updateParticipant(c.get("db"), c.req.param("participantId"), await parseJsonBody(c, updateParticipantSchema));
|
|
428
444
|
if (!row) {
|
|
429
445
|
return c.json({ error: "Participant not found" }, 404);
|
|
430
446
|
}
|
|
@@ -452,21 +468,7 @@ export const bookingRoutes = new Hono()
|
|
|
452
468
|
return c.json({ error: "Participant not found" }, 404);
|
|
453
469
|
}
|
|
454
470
|
try {
|
|
455
|
-
const pii =
|
|
456
|
-
kms: createKmsProviderFromEnv(getRuntimeEnv(c)),
|
|
457
|
-
onAudit: async (event) => {
|
|
458
|
-
await logBookingPiiAccess(c, {
|
|
459
|
-
bookingId: participant.bookingId,
|
|
460
|
-
participantId: event.participantId,
|
|
461
|
-
action: event.action === "encrypt"
|
|
462
|
-
? "update"
|
|
463
|
-
: event.action === "decrypt"
|
|
464
|
-
? "read"
|
|
465
|
-
: event.action,
|
|
466
|
-
outcome: "allowed",
|
|
467
|
-
});
|
|
468
|
-
},
|
|
469
|
-
});
|
|
471
|
+
const pii = createAuditedBookingPiiService(c, participant.bookingId);
|
|
470
472
|
const row = await pii.deleteParticipantTravelDetails(c.get("db"), participant.id, c.get("userId"));
|
|
471
473
|
if (!row) {
|
|
472
474
|
return c.json({ error: "Participant travel details not found" }, 404);
|
|
@@ -494,7 +496,7 @@ export const bookingRoutes = new Hono()
|
|
|
494
496
|
})
|
|
495
497
|
// 13. POST /:id/passengers — Add passenger
|
|
496
498
|
.post("/:id/passengers", async (c) => {
|
|
497
|
-
const row = await bookingsService.createPassenger(c.get("db"), c.req.param("id"),
|
|
499
|
+
const row = await bookingsService.createPassenger(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertPassengerSchema), c.get("userId"));
|
|
498
500
|
if (!row) {
|
|
499
501
|
return c.json({ error: "Booking not found" }, 404);
|
|
500
502
|
}
|
|
@@ -502,7 +504,7 @@ export const bookingRoutes = new Hono()
|
|
|
502
504
|
})
|
|
503
505
|
// 14. PATCH /:id/passengers/:passengerId — Update passenger
|
|
504
506
|
.patch("/:id/passengers/:passengerId", async (c) => {
|
|
505
|
-
const row = await bookingsService.updatePassenger(c.get("db"), c.req.param("passengerId"),
|
|
507
|
+
const row = await bookingsService.updatePassenger(c.get("db"), c.req.param("passengerId"), await parseJsonBody(c, updatePassengerSchema));
|
|
506
508
|
if (!row) {
|
|
507
509
|
return c.json({ error: "Passenger not found" }, 404);
|
|
508
510
|
}
|
|
@@ -525,7 +527,7 @@ export const bookingRoutes = new Hono()
|
|
|
525
527
|
})
|
|
526
528
|
// 17. POST /:id/items — Add booking item
|
|
527
529
|
.post("/:id/items", async (c) => {
|
|
528
|
-
const row = await bookingsService.createItem(c.get("db"), c.req.param("id"),
|
|
530
|
+
const row = await bookingsService.createItem(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertBookingItemSchema), c.get("userId"));
|
|
529
531
|
if (!row) {
|
|
530
532
|
return c.json({ error: "Booking not found" }, 404);
|
|
531
533
|
}
|
|
@@ -533,7 +535,7 @@ export const bookingRoutes = new Hono()
|
|
|
533
535
|
})
|
|
534
536
|
// 18. PATCH /:id/items/:itemId — Update booking item
|
|
535
537
|
.patch("/:id/items/:itemId", async (c) => {
|
|
536
|
-
const row = await bookingsService.updateItem(c.get("db"), c.req.param("itemId"),
|
|
538
|
+
const row = await bookingsService.updateItem(c.get("db"), c.req.param("itemId"), await parseJsonBody(c, updateBookingItemSchema));
|
|
537
539
|
if (!row) {
|
|
538
540
|
return c.json({ error: "Booking item not found" }, 404);
|
|
539
541
|
}
|
|
@@ -555,7 +557,7 @@ export const bookingRoutes = new Hono()
|
|
|
555
557
|
})
|
|
556
558
|
// 21. POST /:id/items/:itemId/participants — Link participant to item
|
|
557
559
|
.post("/:id/items/:itemId/participants", async (c) => {
|
|
558
|
-
const row = await bookingsService.addItemParticipant(c.get("db"), c.req.param("itemId"),
|
|
560
|
+
const row = await bookingsService.addItemParticipant(c.get("db"), c.req.param("itemId"), await parseJsonBody(c, insertBookingItemParticipantSchema));
|
|
559
561
|
if (!row) {
|
|
560
562
|
return c.json({ error: "Booking item or participant not found" }, 404);
|
|
561
563
|
}
|
|
@@ -578,14 +580,14 @@ export const bookingRoutes = new Hono()
|
|
|
578
580
|
});
|
|
579
581
|
})
|
|
580
582
|
.post("/:id/supplier-statuses", async (c) => {
|
|
581
|
-
const row = await bookingsService.createSupplierStatus(c.get("db"), c.req.param("id"),
|
|
583
|
+
const row = await bookingsService.createSupplierStatus(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertSupplierStatusSchema), c.get("userId"));
|
|
582
584
|
if (!row) {
|
|
583
585
|
return c.json({ error: "Booking not found" }, 404);
|
|
584
586
|
}
|
|
585
587
|
return c.json({ data: row }, 201);
|
|
586
588
|
})
|
|
587
589
|
.patch("/:id/supplier-statuses/:statusId", async (c) => {
|
|
588
|
-
const row = await bookingsService.updateSupplierStatus(c.get("db"), c.req.param("id"), c.req.param("statusId"),
|
|
590
|
+
const row = await bookingsService.updateSupplierStatus(c.get("db"), c.req.param("id"), c.req.param("statusId"), await parseJsonBody(c, updateSupplierStatusSchema), c.get("userId"));
|
|
589
591
|
if (!row) {
|
|
590
592
|
return c.json({ error: "Supplier status not found" }, 404);
|
|
591
593
|
}
|
|
@@ -598,14 +600,14 @@ export const bookingRoutes = new Hono()
|
|
|
598
600
|
return c.json({ data: await bookingsService.listFulfillments(c.get("db"), c.req.param("id")) });
|
|
599
601
|
})
|
|
600
602
|
.post("/:id/fulfillments", async (c) => {
|
|
601
|
-
const row = await bookingsService.issueFulfillment(c.get("db"), c.req.param("id"),
|
|
603
|
+
const row = await bookingsService.issueFulfillment(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertBookingFulfillmentSchema), c.get("userId"));
|
|
602
604
|
if (!row) {
|
|
603
605
|
return c.json({ error: "Booking, item, or participant not found" }, 404);
|
|
604
606
|
}
|
|
605
607
|
return c.json({ data: row }, 201);
|
|
606
608
|
})
|
|
607
609
|
.patch("/:id/fulfillments/:fulfillmentId", async (c) => {
|
|
608
|
-
const row = await bookingsService.updateFulfillment(c.get("db"), c.req.param("id"), c.req.param("fulfillmentId"),
|
|
610
|
+
const row = await bookingsService.updateFulfillment(c.get("db"), c.req.param("id"), c.req.param("fulfillmentId"), await parseJsonBody(c, updateBookingFulfillmentSchema), c.get("userId"));
|
|
609
611
|
if (!row) {
|
|
610
612
|
return c.json({ error: "Fulfillment, item, or participant not found" }, 404);
|
|
611
613
|
}
|
|
@@ -620,7 +622,7 @@ export const bookingRoutes = new Hono()
|
|
|
620
622
|
});
|
|
621
623
|
})
|
|
622
624
|
.post("/:id/redemptions", async (c) => {
|
|
623
|
-
const row = await bookingsService.recordRedemption(c.get("db"), c.req.param("id"),
|
|
625
|
+
const row = await bookingsService.recordRedemption(c.get("db"), c.req.param("id"), await parseJsonBody(c, recordBookingRedemptionSchema), c.get("userId"));
|
|
624
626
|
if (!row) {
|
|
625
627
|
return c.json({ error: "Booking, item, or participant not found" }, 404);
|
|
626
628
|
}
|
|
@@ -632,6 +634,11 @@ export const bookingRoutes = new Hono()
|
|
|
632
634
|
// 26. GET /:id/activity — List activity log
|
|
633
635
|
.get("/:id/activity", async (c) => {
|
|
634
636
|
return c.json({ data: await bookingsService.listActivity(c.get("db"), c.req.param("id")) });
|
|
637
|
+
})
|
|
638
|
+
// 26a. GET /:id/group — Shared-room group membership for this booking (or null)
|
|
639
|
+
.get("/:id/group", async (c) => {
|
|
640
|
+
const result = await bookingGroupsService.getBookingGroupForBooking(c.get("db"), c.req.param("id"));
|
|
641
|
+
return c.json({ data: result ?? null });
|
|
635
642
|
})
|
|
636
643
|
// ==========================================================================
|
|
637
644
|
// Notes
|
|
@@ -642,11 +649,8 @@ export const bookingRoutes = new Hono()
|
|
|
642
649
|
})
|
|
643
650
|
// 28. POST /:id/notes — Add note
|
|
644
651
|
.post("/:id/notes", async (c) => {
|
|
645
|
-
const userId = c
|
|
646
|
-
|
|
647
|
-
return c.json({ error: "User ID required to create notes" }, 400);
|
|
648
|
-
}
|
|
649
|
-
const row = await bookingsService.createNote(c.get("db"), c.req.param("id"), userId, insertBookingNoteSchema.parse(await c.req.json()));
|
|
652
|
+
const userId = requireUserId(c);
|
|
653
|
+
const row = await bookingsService.createNote(c.get("db"), c.req.param("id"), userId, await parseJsonBody(c, insertBookingNoteSchema));
|
|
650
654
|
if (!row) {
|
|
651
655
|
return c.json({ error: "Booking not found" }, 404);
|
|
652
656
|
}
|
|
@@ -661,7 +665,7 @@ export const bookingRoutes = new Hono()
|
|
|
661
665
|
})
|
|
662
666
|
// 30. POST /:id/documents — Add document to booking
|
|
663
667
|
.post("/:id/documents", async (c) => {
|
|
664
|
-
const row = await bookingsService.createDocument(c.get("db"), c.req.param("id"),
|
|
668
|
+
const row = await bookingsService.createDocument(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertBookingDocumentSchema));
|
|
665
669
|
if (!row) {
|
|
666
670
|
return c.json({ error: "Booking not found" }, 404);
|
|
667
671
|
}
|
|
@@ -674,4 +678,8 @@ export const bookingRoutes = new Hono()
|
|
|
674
678
|
return c.json({ error: "Document not found" }, 404);
|
|
675
679
|
}
|
|
676
680
|
return c.json({ success: true }, 200);
|
|
677
|
-
})
|
|
681
|
+
})
|
|
682
|
+
// ==========================================================================
|
|
683
|
+
// Booking Groups (shared-room / split-booking model)
|
|
684
|
+
// ==========================================================================
|
|
685
|
+
.route("/groups", bookingGroupRoutes);
|
package/dist/schema-core.d.ts
CHANGED
|
@@ -92,7 +92,7 @@ export declare const bookings: import("drizzle-orm/pg-core").PgTableWithColumns<
|
|
|
92
92
|
tableName: "bookings";
|
|
93
93
|
dataType: "string";
|
|
94
94
|
columnType: "PgEnumColumn";
|
|
95
|
-
data: "internal" | "
|
|
95
|
+
data: "internal" | "reseller" | "direct" | "manual" | "affiliate" | "ota" | "api_partner";
|
|
96
96
|
driverParam: string;
|
|
97
97
|
notNull: true;
|
|
98
98
|
hasDefault: true;
|