@voyantjs/bookings 0.1.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/LICENSE +109 -0
- package/README.md +42 -0
- package/dist/availability-ref.d.ts +418 -0
- package/dist/availability-ref.d.ts.map +1 -0
- package/dist/availability-ref.js +28 -0
- package/dist/extensions/suppliers.d.ts +3 -0
- package/dist/extensions/suppliers.d.ts.map +1 -0
- package/dist/extensions/suppliers.js +103 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/pii.d.ts +29 -0
- package/dist/pii.d.ts.map +1 -0
- package/dist/pii.js +131 -0
- package/dist/products-ref.d.ts +1043 -0
- package/dist/products-ref.d.ts.map +1 -0
- package/dist/products-ref.js +76 -0
- package/dist/routes.d.ts +2171 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +659 -0
- package/dist/schema/travel-details.d.ts +179 -0
- package/dist/schema/travel-details.d.ts.map +1 -0
- package/dist/schema/travel-details.js +46 -0
- package/dist/schema.d.ts +3180 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +509 -0
- package/dist/service.d.ts +5000 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +2016 -0
- package/dist/tasks/expire-stale-holds.d.ts +12 -0
- package/dist/tasks/expire-stale-holds.d.ts.map +1 -0
- package/dist/tasks/expire-stale-holds.js +7 -0
- package/dist/tasks/index.d.ts +2 -0
- package/dist/tasks/index.d.ts.map +1 -0
- package/dist/tasks/index.js +1 -0
- package/dist/transactions-ref.d.ts +2223 -0
- package/dist/transactions-ref.d.ts.map +1 -0
- package/dist/transactions-ref.js +147 -0
- package/dist/validation.d.ts +643 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +355 -0
- package/package.json +68 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { asc, eq } from "drizzle-orm";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { bookingActivityLog, bookings, bookingSupplierStatuses } from "../schema.js";
|
|
5
|
+
// ---------- validation ----------
|
|
6
|
+
const supplierConfirmationStatusSchema = z.enum(["pending", "confirmed", "rejected", "cancelled"]);
|
|
7
|
+
const supplierStatusCoreSchema = z.object({
|
|
8
|
+
supplierServiceId: z.string().optional().nullable(),
|
|
9
|
+
serviceName: z.string().min(1).max(255),
|
|
10
|
+
status: supplierConfirmationStatusSchema.default("pending"),
|
|
11
|
+
supplierReference: z.string().max(255).optional().nullable(),
|
|
12
|
+
costCurrency: z.string().min(3).max(3),
|
|
13
|
+
costAmountCents: z.number().int().min(0),
|
|
14
|
+
notes: z.string().optional().nullable(),
|
|
15
|
+
});
|
|
16
|
+
const insertSupplierStatusSchema = supplierStatusCoreSchema;
|
|
17
|
+
const updateSupplierStatusSchema = supplierStatusCoreSchema.partial().extend({
|
|
18
|
+
confirmedAt: z.string().optional().nullable(),
|
|
19
|
+
});
|
|
20
|
+
// ---------- service ----------
|
|
21
|
+
const supplierStatusService = {
|
|
22
|
+
listSupplierStatuses(db, bookingId) {
|
|
23
|
+
return db
|
|
24
|
+
.select()
|
|
25
|
+
.from(bookingSupplierStatuses)
|
|
26
|
+
.where(eq(bookingSupplierStatuses.bookingId, bookingId))
|
|
27
|
+
.orderBy(asc(bookingSupplierStatuses.createdAt));
|
|
28
|
+
},
|
|
29
|
+
async createSupplierStatus(db, bookingId, data, userId) {
|
|
30
|
+
const [booking] = await db
|
|
31
|
+
.select({ id: bookings.id })
|
|
32
|
+
.from(bookings)
|
|
33
|
+
.where(eq(bookings.id, bookingId))
|
|
34
|
+
.limit(1);
|
|
35
|
+
if (!booking) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const [row] = await db
|
|
39
|
+
.insert(bookingSupplierStatuses)
|
|
40
|
+
.values({ ...data, bookingId })
|
|
41
|
+
.returning();
|
|
42
|
+
await db.insert(bookingActivityLog).values({
|
|
43
|
+
bookingId,
|
|
44
|
+
actorId: userId ?? "system",
|
|
45
|
+
activityType: "supplier_update",
|
|
46
|
+
description: `Supplier status for "${data.serviceName}" added`,
|
|
47
|
+
});
|
|
48
|
+
return row;
|
|
49
|
+
},
|
|
50
|
+
async updateSupplierStatus(db, bookingId, statusId, data, userId) {
|
|
51
|
+
const updateData = { ...data, updatedAt: new Date() };
|
|
52
|
+
if (data.status === "confirmed" && !data.confirmedAt) {
|
|
53
|
+
updateData.confirmedAt = new Date();
|
|
54
|
+
}
|
|
55
|
+
const [row] = await db
|
|
56
|
+
.update(bookingSupplierStatuses)
|
|
57
|
+
.set(updateData)
|
|
58
|
+
.where(eq(bookingSupplierStatuses.id, statusId))
|
|
59
|
+
.returning();
|
|
60
|
+
if (!row) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (data.status) {
|
|
64
|
+
await db.insert(bookingActivityLog).values({
|
|
65
|
+
bookingId,
|
|
66
|
+
actorId: userId ?? "system",
|
|
67
|
+
activityType: "supplier_update",
|
|
68
|
+
description: `Supplier "${row.serviceName}" status updated to ${data.status}`,
|
|
69
|
+
metadata: { supplierStatusId: statusId, newStatus: data.status },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return row;
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
const supplierStatusRoutes = new Hono()
|
|
76
|
+
.get("/:id/supplier-statuses", async (c) => {
|
|
77
|
+
return c.json({
|
|
78
|
+
data: await supplierStatusService.listSupplierStatuses(c.get("db"), c.req.param("id")),
|
|
79
|
+
});
|
|
80
|
+
})
|
|
81
|
+
.post("/:id/supplier-statuses", async (c) => {
|
|
82
|
+
const row = await supplierStatusService.createSupplierStatus(c.get("db"), c.req.param("id"), insertSupplierStatusSchema.parse(await c.req.json()), c.get("userId"));
|
|
83
|
+
if (!row) {
|
|
84
|
+
return c.json({ error: "Booking not found" }, 404);
|
|
85
|
+
}
|
|
86
|
+
return c.json({ data: row }, 201);
|
|
87
|
+
})
|
|
88
|
+
.patch("/:id/supplier-statuses/:statusId", async (c) => {
|
|
89
|
+
const row = await supplierStatusService.updateSupplierStatus(c.get("db"), c.req.param("id"), c.req.param("statusId"), updateSupplierStatusSchema.parse(await c.req.json()), c.get("userId"));
|
|
90
|
+
if (!row) {
|
|
91
|
+
return c.json({ error: "Supplier status not found" }, 404);
|
|
92
|
+
}
|
|
93
|
+
return c.json({ data: row });
|
|
94
|
+
});
|
|
95
|
+
// ---------- extension export ----------
|
|
96
|
+
const bookingsSupplierExtensionDef = {
|
|
97
|
+
name: "bookings-suppliers",
|
|
98
|
+
module: "bookings",
|
|
99
|
+
};
|
|
100
|
+
export const bookingsSupplierExtension = {
|
|
101
|
+
extension: bookingsSupplierExtensionDef,
|
|
102
|
+
routes: supplierStatusRoutes,
|
|
103
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { LinkableDefinition, Module } from "@voyantjs/core";
|
|
2
|
+
import type { HonoModule } from "@voyantjs/hono/module";
|
|
3
|
+
export { bookingsService } from "./service.js";
|
|
4
|
+
export type { ConvertProductData } from "./service.js";
|
|
5
|
+
export { createBookingPiiService, type BookingPiiAuditEvent, type BookingPiiServiceOptions, type UpsertBookingParticipantTravelDetailInput, } from "./pii.js";
|
|
6
|
+
export { bookingsSupplierExtension } from "./extensions/suppliers.js";
|
|
7
|
+
export { expireStaleBookingHolds, type ExpireStaleBookingHoldsInput, type ExpireStaleBookingHoldsResult, } from "./tasks/index.js";
|
|
8
|
+
export declare const bookingLinkable: LinkableDefinition;
|
|
9
|
+
export declare const bookingsLinkable: {
|
|
10
|
+
booking: LinkableDefinition;
|
|
11
|
+
};
|
|
12
|
+
export declare const bookingsModule: Module;
|
|
13
|
+
export declare const bookingsHonoModule: HonoModule;
|
|
14
|
+
export type { BookingRoutes } from "./routes.js";
|
|
15
|
+
export type { Booking, BookingActivity, BookingAllocation, BookingDocument, BookingFulfillment, BookingItem, BookingItemParticipant, BookingNote, BookingParticipant, BookingPassenger, BookingPiiAccessLog, BookingRedemptionEvent, BookingSupplierStatus, NewBooking, NewBookingActivity, NewBookingAllocation, NewBookingDocument, NewBookingFulfillment, NewBookingItem, NewBookingItemParticipant, NewBookingNote, NewBookingParticipant, NewBookingPassenger, NewBookingPiiAccessLog, NewBookingRedemptionEvent, NewBookingSupplierStatus, } from "./schema.js";
|
|
16
|
+
export { bookingActivityLog, bookingAllocations, bookingDocuments, bookingFulfillments, bookingItemParticipants, bookingItems, bookingNotes, bookingParticipants, bookingPassengers, bookingPiiAccessLog, bookingRedemptionEvents, bookingSupplierStatuses, bookings, } from "./schema.js";
|
|
17
|
+
export type { BookingParticipantDietary, BookingParticipantIdentity, BookingParticipantTravelDetail, DecryptedBookingParticipantTravelDetail, NewBookingParticipantTravelDetail, } from "./schema/travel-details.js";
|
|
18
|
+
export { bookingParticipantDietarySchema, bookingParticipantIdentitySchema, bookingParticipantTravelDetailInsertSchema, bookingParticipantTravelDetails, bookingParticipantTravelDetailSelectSchema, bookingParticipantTravelDetailUpdateSchema, decryptedBookingParticipantTravelDetailSchema, } from "./schema/travel-details.js";
|
|
19
|
+
export { bookingListQuerySchema, cancelBookingSchema, confirmBookingSchema, createBookingSchema, convertProductSchema, expireBookingSchema, expireStaleBookingsSchema, extendBookingHoldSchema, insertBookingAllocationSchema, insertBookingDocumentSchema, insertBookingFulfillmentSchema, insertBookingItemParticipantSchema, insertBookingItemSchema, insertBookingNoteSchema, insertBookingSchema, insertParticipantSchema, insertPassengerSchema, insertSupplierStatusSchema, recordBookingRedemptionSchema, reserveBookingSchema, reserveBookingFromTransactionSchema, upsertParticipantTravelDetailsSchema, updateBookingAllocationSchema, updateBookingFulfillmentSchema, updateBookingItemSchema, updateBookingSchema, updateBookingStatusSchema, updateParticipantSchema, updatePassengerSchema, updateSupplierStatusSchema, } from "./validation.js";
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAChE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAIvD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC9C,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,EACL,uBAAuB,EACvB,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,yCAAyC,GAC/C,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,EACL,uBAAuB,EACvB,KAAK,4BAA4B,EACjC,KAAK,6BAA6B,GACnC,MAAM,kBAAkB,CAAA;AAEzB,eAAO,MAAM,eAAe,EAAE,kBAK7B,CAAA;AAED,eAAO,MAAM,gBAAgB;;CAE5B,CAAA;AAED,eAAO,MAAM,cAAc,EAAE,MAG5B,CAAA;AAED,eAAO,MAAM,kBAAkB,EAAE,UAGhC,CAAA;AAED,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,YAAY,EACV,OAAO,EACP,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,sBAAsB,EACtB,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,UAAU,EACV,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,EACd,yBAAyB,EACzB,cAAc,EACd,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,uBAAuB,EACvB,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,uBAAuB,EACvB,uBAAuB,EACvB,QAAQ,GACT,MAAM,aAAa,CAAA;AACpB,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,8BAA8B,EAC9B,uCAAuC,EACvC,iCAAiC,GAClC,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACL,+BAA+B,EAC/B,gCAAgC,EAChC,0CAA0C,EAC1C,+BAA+B,EAC/B,0CAA0C,EAC1C,0CAA0C,EAC1C,6CAA6C,GAC9C,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,yBAAyB,EACzB,uBAAuB,EACvB,6BAA6B,EAC7B,2BAA2B,EAC3B,8BAA8B,EAC9B,kCAAkC,EAClC,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,EACrB,0BAA0B,EAC1B,6BAA6B,EAC7B,oBAAoB,EACpB,mCAAmC,EACnC,oCAAoC,EACpC,6BAA6B,EAC7B,8BAA8B,EAC9B,uBAAuB,EACvB,mBAAmB,EACnB,yBAAyB,EACzB,uBAAuB,EACvB,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,iBAAiB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { bookingRoutes } from "./routes.js";
|
|
2
|
+
export { bookingsService } from "./service.js";
|
|
3
|
+
export { createBookingPiiService, } from "./pii.js";
|
|
4
|
+
export { bookingsSupplierExtension } from "./extensions/suppliers.js";
|
|
5
|
+
export { expireStaleBookingHolds, } from "./tasks/index.js";
|
|
6
|
+
export const bookingLinkable = {
|
|
7
|
+
module: "bookings",
|
|
8
|
+
entity: "booking",
|
|
9
|
+
table: "bookings",
|
|
10
|
+
idPrefix: "book",
|
|
11
|
+
};
|
|
12
|
+
export const bookingsLinkable = {
|
|
13
|
+
booking: bookingLinkable,
|
|
14
|
+
};
|
|
15
|
+
export const bookingsModule = {
|
|
16
|
+
name: "bookings",
|
|
17
|
+
linkable: bookingsLinkable,
|
|
18
|
+
};
|
|
19
|
+
export const bookingsHonoModule = {
|
|
20
|
+
module: bookingsModule,
|
|
21
|
+
routes: bookingRoutes,
|
|
22
|
+
};
|
|
23
|
+
export { bookingActivityLog, bookingAllocations, bookingDocuments, bookingFulfillments, bookingItemParticipants, bookingItems, bookingNotes, bookingParticipants, bookingPassengers, bookingPiiAccessLog, bookingRedemptionEvents, bookingSupplierStatuses, bookings, } from "./schema.js";
|
|
24
|
+
export { bookingParticipantDietarySchema, bookingParticipantIdentitySchema, bookingParticipantTravelDetailInsertSchema, bookingParticipantTravelDetails, bookingParticipantTravelDetailSelectSchema, bookingParticipantTravelDetailUpdateSchema, decryptedBookingParticipantTravelDetailSchema, } from "./schema/travel-details.js";
|
|
25
|
+
export { bookingListQuerySchema, cancelBookingSchema, confirmBookingSchema, createBookingSchema, convertProductSchema, expireBookingSchema, expireStaleBookingsSchema, extendBookingHoldSchema, insertBookingAllocationSchema, insertBookingDocumentSchema, insertBookingFulfillmentSchema, insertBookingItemParticipantSchema, insertBookingItemSchema, insertBookingNoteSchema, insertBookingSchema, insertParticipantSchema, insertPassengerSchema, insertSupplierStatusSchema, recordBookingRedemptionSchema, reserveBookingSchema, reserveBookingFromTransactionSchema, upsertParticipantTravelDetailsSchema, updateBookingAllocationSchema, updateBookingFulfillmentSchema, updateBookingItemSchema, updateBookingSchema, updateBookingStatusSchema, updateParticipantSchema, updatePassengerSchema, updateSupplierStatusSchema, } from "./validation.js";
|
package/dist/pii.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import type { KmsProvider, KeyRef } from "@voyantjs/utils";
|
|
3
|
+
import { type DecryptedBookingParticipantTravelDetail } from "./schema/travel-details.js";
|
|
4
|
+
export interface UpsertBookingParticipantTravelDetailInput {
|
|
5
|
+
nationality?: string | null;
|
|
6
|
+
passportNumber?: string | null;
|
|
7
|
+
passportExpiry?: string | null;
|
|
8
|
+
dateOfBirth?: string | null;
|
|
9
|
+
dietaryRequirements?: string | null;
|
|
10
|
+
isLeadTraveler?: boolean | null;
|
|
11
|
+
}
|
|
12
|
+
export interface BookingPiiAuditEvent {
|
|
13
|
+
action: "encrypt" | "decrypt" | "delete";
|
|
14
|
+
participantId: string;
|
|
15
|
+
actorId?: string | null;
|
|
16
|
+
}
|
|
17
|
+
export interface BookingPiiServiceOptions {
|
|
18
|
+
kms: KmsProvider;
|
|
19
|
+
keyRef?: KeyRef;
|
|
20
|
+
onAudit?: (event: BookingPiiAuditEvent) => void | Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export declare function createBookingPiiService(options: BookingPiiServiceOptions): {
|
|
23
|
+
getParticipantTravelDetails(db: PostgresJsDatabase, participantId: string, actorId?: string | null): Promise<DecryptedBookingParticipantTravelDetail | null>;
|
|
24
|
+
upsertParticipantTravelDetails(db: PostgresJsDatabase, participantId: string, input: UpsertBookingParticipantTravelDetailInput, actorId?: string | null): Promise<DecryptedBookingParticipantTravelDetail | null>;
|
|
25
|
+
deleteParticipantTravelDetails(db: PostgresJsDatabase, participantId: string, actorId?: string | null): Promise<{
|
|
26
|
+
participantId: string;
|
|
27
|
+
} | null>;
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=pii.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pii.d.ts","sourceRoot":"","sources":["../src/pii.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAO1D,OAAO,EAIL,KAAK,uCAAuC,EAC7C,MAAM,4BAA4B,CAAA;AAEnC,MAAM,WAAW,yCAAyC;IACxD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,cAAc,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CAChC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAA;IACxC,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,GAAG,EAAE,WAAW,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAChE;AAwFD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB;oCAK/D,kBAAkB,iBACP,MAAM,YACX,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,uCAAuC,GAAG,IAAI,CAAC;uCAwCpD,kBAAkB,iBACP,MAAM,SACd,yCAAyC,YACtC,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,uCAAuC,GAAG,IAAI,CAAC;uCAmDpD,kBAAkB,iBACP,MAAM,YACX,MAAM,GAAG,IAAI;;;EAc5B"}
|
package/dist/pii.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { eq } from "drizzle-orm";
|
|
2
|
+
import { decryptOptionalJsonEnvelope, encryptOptionalJsonEnvelope, } from "@voyantjs/utils";
|
|
3
|
+
import { bookingParticipants } from "./schema.js";
|
|
4
|
+
import { bookingParticipantDietarySchema, bookingParticipantIdentitySchema, bookingParticipantTravelDetails, } from "./schema/travel-details.js";
|
|
5
|
+
function buildIdentityPayload(input) {
|
|
6
|
+
const payload = bookingParticipantIdentitySchema.parse({
|
|
7
|
+
nationality: input.nationality ?? null,
|
|
8
|
+
passportNumber: input.passportNumber ?? null,
|
|
9
|
+
passportExpiry: input.passportExpiry ?? null,
|
|
10
|
+
dateOfBirth: input.dateOfBirth ?? null,
|
|
11
|
+
});
|
|
12
|
+
if (!payload.nationality && !payload.passportNumber && !payload.passportExpiry && !payload.dateOfBirth) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return payload;
|
|
16
|
+
}
|
|
17
|
+
function buildDietaryPayload(input) {
|
|
18
|
+
const payload = bookingParticipantDietarySchema.parse({
|
|
19
|
+
dietaryRequirements: input.dietaryRequirements ?? null,
|
|
20
|
+
});
|
|
21
|
+
if (!payload.dietaryRequirements) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return payload;
|
|
25
|
+
}
|
|
26
|
+
async function loadExistingTravelDetails(db, participantId, options, keyRef) {
|
|
27
|
+
const [row] = await db
|
|
28
|
+
.select()
|
|
29
|
+
.from(bookingParticipantTravelDetails)
|
|
30
|
+
.where(eq(bookingParticipantTravelDetails.participantId, participantId))
|
|
31
|
+
.limit(1);
|
|
32
|
+
if (!row) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const identity = await decryptOptionalJsonEnvelope(options.kms, keyRef, row.identityEncrypted, bookingParticipantIdentitySchema);
|
|
36
|
+
const dietary = await decryptOptionalJsonEnvelope(options.kms, keyRef, row.dietaryEncrypted, bookingParticipantDietarySchema);
|
|
37
|
+
return {
|
|
38
|
+
nationality: identity?.nationality ?? null,
|
|
39
|
+
passportNumber: identity?.passportNumber ?? null,
|
|
40
|
+
passportExpiry: identity?.passportExpiry ?? null,
|
|
41
|
+
dateOfBirth: identity?.dateOfBirth ?? null,
|
|
42
|
+
dietaryRequirements: dietary?.dietaryRequirements ?? null,
|
|
43
|
+
isLeadTraveler: row.isLeadTraveler,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function mergeTravelDetailInput(existing, input) {
|
|
47
|
+
return {
|
|
48
|
+
nationality: input.nationality === undefined ? (existing?.nationality ?? null) : input.nationality,
|
|
49
|
+
passportNumber: input.passportNumber === undefined ? (existing?.passportNumber ?? null) : input.passportNumber,
|
|
50
|
+
passportExpiry: input.passportExpiry === undefined ? (existing?.passportExpiry ?? null) : input.passportExpiry,
|
|
51
|
+
dateOfBirth: input.dateOfBirth === undefined ? (existing?.dateOfBirth ?? null) : input.dateOfBirth,
|
|
52
|
+
dietaryRequirements: input.dietaryRequirements === undefined
|
|
53
|
+
? (existing?.dietaryRequirements ?? null)
|
|
54
|
+
: input.dietaryRequirements,
|
|
55
|
+
isLeadTraveler: input.isLeadTraveler === undefined ? (existing?.isLeadTraveler ?? false) : input.isLeadTraveler,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function createBookingPiiService(options) {
|
|
59
|
+
const keyRef = options.keyRef ?? { keyType: "people" };
|
|
60
|
+
return {
|
|
61
|
+
async getParticipantTravelDetails(db, participantId, actorId) {
|
|
62
|
+
const [row] = await db
|
|
63
|
+
.select()
|
|
64
|
+
.from(bookingParticipantTravelDetails)
|
|
65
|
+
.where(eq(bookingParticipantTravelDetails.participantId, participantId))
|
|
66
|
+
.limit(1);
|
|
67
|
+
if (!row) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const identity = await decryptOptionalJsonEnvelope(options.kms, keyRef, row.identityEncrypted, bookingParticipantIdentitySchema);
|
|
71
|
+
const dietary = await decryptOptionalJsonEnvelope(options.kms, keyRef, row.dietaryEncrypted, bookingParticipantDietarySchema);
|
|
72
|
+
await options.onAudit?.({ action: "decrypt", participantId, actorId });
|
|
73
|
+
return {
|
|
74
|
+
participantId: row.participantId,
|
|
75
|
+
nationality: identity?.nationality ?? null,
|
|
76
|
+
passportNumber: identity?.passportNumber ?? null,
|
|
77
|
+
passportExpiry: identity?.passportExpiry ?? null,
|
|
78
|
+
dateOfBirth: identity?.dateOfBirth ?? null,
|
|
79
|
+
dietaryRequirements: dietary?.dietaryRequirements ?? null,
|
|
80
|
+
isLeadTraveler: row.isLeadTraveler,
|
|
81
|
+
createdAt: row.createdAt,
|
|
82
|
+
updatedAt: row.updatedAt,
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
async upsertParticipantTravelDetails(db, participantId, input, actorId) {
|
|
86
|
+
const [participant] = await db
|
|
87
|
+
.select({ id: bookingParticipants.id })
|
|
88
|
+
.from(bookingParticipants)
|
|
89
|
+
.where(eq(bookingParticipants.id, participantId))
|
|
90
|
+
.limit(1);
|
|
91
|
+
if (!participant) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const existing = await loadExistingTravelDetails(db, participantId, options, keyRef);
|
|
95
|
+
const mergedInput = mergeTravelDetailInput(existing, input);
|
|
96
|
+
const identityEncrypted = await encryptOptionalJsonEnvelope(options.kms, keyRef, buildIdentityPayload(mergedInput));
|
|
97
|
+
const dietaryEncrypted = await encryptOptionalJsonEnvelope(options.kms, keyRef, buildDietaryPayload(mergedInput));
|
|
98
|
+
const now = new Date();
|
|
99
|
+
await db
|
|
100
|
+
.insert(bookingParticipantTravelDetails)
|
|
101
|
+
.values({
|
|
102
|
+
participantId,
|
|
103
|
+
identityEncrypted,
|
|
104
|
+
dietaryEncrypted,
|
|
105
|
+
isLeadTraveler: mergedInput.isLeadTraveler ?? false,
|
|
106
|
+
updatedAt: now,
|
|
107
|
+
})
|
|
108
|
+
.onConflictDoUpdate({
|
|
109
|
+
target: bookingParticipantTravelDetails.participantId,
|
|
110
|
+
set: {
|
|
111
|
+
identityEncrypted,
|
|
112
|
+
dietaryEncrypted,
|
|
113
|
+
isLeadTraveler: mergedInput.isLeadTraveler ?? false,
|
|
114
|
+
updatedAt: now,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
await options.onAudit?.({ action: "encrypt", participantId, actorId });
|
|
118
|
+
return this.getParticipantTravelDetails(db, participantId, actorId);
|
|
119
|
+
},
|
|
120
|
+
async deleteParticipantTravelDetails(db, participantId, actorId) {
|
|
121
|
+
const [row] = await db
|
|
122
|
+
.delete(bookingParticipantTravelDetails)
|
|
123
|
+
.where(eq(bookingParticipantTravelDetails.participantId, participantId))
|
|
124
|
+
.returning({ participantId: bookingParticipantTravelDetails.participantId });
|
|
125
|
+
if (row) {
|
|
126
|
+
await options.onAudit?.({ action: "delete", participantId, actorId });
|
|
127
|
+
}
|
|
128
|
+
return row ?? null;
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|