@voyantjs/customer-portal 0.26.0 → 0.26.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.
@@ -1 +1 @@
1
- {"version":3,"file":"routes-public.d.ts","sourceRoot":"","sources":["../src/routes-public.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"routes-public.d.ts","sourceRoot":"","sources":["../src/routes-public.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAErD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAoBjE,KAAK,GAAG,GAAG;IACT,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,SAAS,EAAE;QACT,SAAS,CAAC,EAAE,eAAe,CAAA;QAC3B,EAAE,EAAE,kBAAkB,CAAA;QACtB,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAA;CACF,CAAA;AAoBD,MAAM,WAAW,gCAAgC;IAC/C,0BAA0B,CAAC,EAAE,CAC3B,QAAQ,EAAE,OAAO,EACjB,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAA;CAC5C;AAED,wBAAgB,gCAAgC,CAAC,OAAO,GAAE,gCAAqC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gDAqP9F;AAED,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+CAAqC,CAAA;AAE5E,MAAM,MAAM,0BAA0B,GAAG,OAAO,0BAA0B,CAAA"}
@@ -3,7 +3,7 @@ import { Hono } from "hono";
3
3
  import { buildPublicCustomerPortalRouteRuntime, CUSTOMER_PORTAL_ROUTE_RUNTIME_CONTAINER_KEY, } from "./route-runtime.js";
4
4
  import { customerPortalRoutes } from "./routes.js";
5
5
  import { publicCustomerPortalService } from "./service-public.js";
6
- import { bootstrapCustomerPortalSchema, createCustomerPortalCompanionSchema, importCustomerPortalBookingTravelersSchema, updateCustomerPortalCompanionSchema, updateCustomerPortalProfileSchema, } from "./validation-public.js";
6
+ import { bootstrapCustomerPortalSchema, createCustomerPortalCompanionSchema, createCustomerPortalProfileDocumentSchema, importCustomerPortalBookingTravelersSchema, updateCustomerPortalCompanionSchema, updateCustomerPortalProfileDocumentSchema, updateCustomerPortalProfileSchema, } from "./validation-public.js";
7
7
  function notFound(c, error) {
8
8
  return c.json({ error }, 404);
9
9
  }
@@ -38,6 +38,45 @@ export function createPublicCustomerPortalRoutes(options = {}) {
38
38
  return c.json({ error: "Customer record is not linked to this account" }, 409);
39
39
  }
40
40
  return c.json({ data: result.profile });
41
+ })
42
+ .get("/me/documents", async (c) => {
43
+ const userId = requireUserId(c);
44
+ const data = await publicCustomerPortalService.listMyDocuments(c.get("db"), userId, {
45
+ kms: resolveOptionalKms(c),
46
+ });
47
+ return c.json({ data });
48
+ })
49
+ .post("/me/documents", async (c) => {
50
+ const userId = requireUserId(c);
51
+ const input = await parseJsonBody(c, createCustomerPortalProfileDocumentSchema);
52
+ const row = await publicCustomerPortalService.createMyDocument(c.get("db"), userId, input, {
53
+ kms: resolveOptionalKms(c),
54
+ });
55
+ if (!row)
56
+ return notFound(c, "Customer profile not found");
57
+ return c.json({ data: row }, 201);
58
+ })
59
+ .patch("/me/documents/:id", async (c) => {
60
+ const userId = requireUserId(c);
61
+ const input = await parseJsonBody(c, updateCustomerPortalProfileDocumentSchema);
62
+ const row = await publicCustomerPortalService.updateMyDocument(c.get("db"), userId, c.req.param("id"), input, { kms: resolveOptionalKms(c) });
63
+ if (!row)
64
+ return notFound(c, "Document not found");
65
+ return c.json({ data: row });
66
+ })
67
+ .delete("/me/documents/:id", async (c) => {
68
+ const userId = requireUserId(c);
69
+ const result = await publicCustomerPortalService.deleteMyDocument(c.get("db"), userId, c.req.param("id"));
70
+ if (!result)
71
+ return notFound(c, "Document not found");
72
+ return c.json({ success: true });
73
+ })
74
+ .post("/me/documents/:id/set-primary", async (c) => {
75
+ const userId = requireUserId(c);
76
+ const row = await publicCustomerPortalService.setPrimaryMyDocument(c.get("db"), userId, c.req.param("id"), { kms: resolveOptionalKms(c) });
77
+ if (!row)
78
+ return notFound(c, "Document not found");
79
+ return c.json({ data: row });
41
80
  })
42
81
  .post("/bootstrap", async (c) => {
43
82
  const userId = requireUserId(c);
@@ -5,6 +5,7 @@ interface CustomerPortalServiceOptions {
5
5
  kms?: KmsProvider | null;
6
6
  resolveDocumentDownloadUrl?: (storageKey: string) => Promise<string | null> | string | null;
7
7
  }
8
+ type WireDocumentType = "passport" | "id_card" | "visa" | "drivers_license" | "other";
8
9
  export declare const publicCustomerPortalService: {
9
10
  contactExists(db: PostgresJsDatabase, email: string): Promise<CustomerPortalContactExistsResult>;
10
11
  phoneContactExists(db: PostgresJsDatabase, phone: string): Promise<CustomerPortalPhoneContactExistsResult>;
@@ -52,6 +53,85 @@ export declare const publicCustomerPortalService: {
52
53
  address1: string | null;
53
54
  postal: string | null;
54
55
  } | null>;
56
+ listMyDocuments(db: PostgresJsDatabase, userId: string, options?: CustomerPortalServiceOptions): Promise<{
57
+ id: string;
58
+ type: WireDocumentType;
59
+ number: string | null;
60
+ issuingAuthority: string | null;
61
+ issuingCountry: string | null;
62
+ issueDate: string | null;
63
+ expiryDate: string | null;
64
+ attachmentId: string | null;
65
+ isPrimary: boolean;
66
+ notes: string | null;
67
+ createdAt: string;
68
+ updatedAt: string;
69
+ }[]>;
70
+ createMyDocument(db: PostgresJsDatabase, userId: string, input: {
71
+ type: WireDocumentType;
72
+ number?: string | null;
73
+ issuingAuthority?: string | null;
74
+ issuingCountry?: string | null;
75
+ issueDate?: string | null;
76
+ expiryDate?: string | null;
77
+ attachmentId?: string | null;
78
+ isPrimary?: boolean;
79
+ notes?: string | null;
80
+ }, options?: CustomerPortalServiceOptions): Promise<{
81
+ id: string;
82
+ type: WireDocumentType;
83
+ number: string | null;
84
+ issuingAuthority: string | null;
85
+ issuingCountry: string | null;
86
+ issueDate: string | null;
87
+ expiryDate: string | null;
88
+ attachmentId: string | null;
89
+ isPrimary: boolean;
90
+ notes: string | null;
91
+ createdAt: string;
92
+ updatedAt: string;
93
+ } | null>;
94
+ updateMyDocument(db: PostgresJsDatabase, userId: string, documentId: string, input: {
95
+ type?: WireDocumentType;
96
+ number?: string | null;
97
+ issuingAuthority?: string | null;
98
+ issuingCountry?: string | null;
99
+ issueDate?: string | null;
100
+ expiryDate?: string | null;
101
+ attachmentId?: string | null;
102
+ isPrimary?: boolean;
103
+ notes?: string | null;
104
+ }, options?: CustomerPortalServiceOptions): Promise<{
105
+ id: string;
106
+ type: WireDocumentType;
107
+ number: string | null;
108
+ issuingAuthority: string | null;
109
+ issuingCountry: string | null;
110
+ issueDate: string | null;
111
+ expiryDate: string | null;
112
+ attachmentId: string | null;
113
+ isPrimary: boolean;
114
+ notes: string | null;
115
+ createdAt: string;
116
+ updatedAt: string;
117
+ } | null>;
118
+ deleteMyDocument(db: PostgresJsDatabase, userId: string, documentId: string): Promise<{
119
+ id: string;
120
+ } | null>;
121
+ setPrimaryMyDocument(db: PostgresJsDatabase, userId: string, documentId: string, options?: CustomerPortalServiceOptions): Promise<{
122
+ id: string;
123
+ type: WireDocumentType;
124
+ number: string | null;
125
+ issuingAuthority: string | null;
126
+ issuingCountry: string | null;
127
+ issueDate: string | null;
128
+ expiryDate: string | null;
129
+ attachmentId: string | null;
130
+ isPrimary: boolean;
131
+ notes: string | null;
132
+ createdAt: string;
133
+ updatedAt: string;
134
+ } | null>;
55
135
  };
56
136
  export {};
57
137
  //# sourceMappingURL=service-public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service-public.d.ts","sourceRoot":"","sources":["../src/service-public.ts"],"names":[],"mappings":"AAqBA,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,iBAAiB,CAAA;AAExB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,kCAAkC,EAGlC,2BAA2B,EAK3B,4BAA4B,EAE5B,uBAAuB,EACvB,iCAAiC,EACjC,sCAAsC,EACtC,qBAAqB,EACrB,yCAAyC,EACzC,0CAA0C,EAE1C,kCAAkC,EAClC,gCAAgC,EACjC,MAAM,wBAAwB,CAAA;AAQ/B,UAAU,4BAA4B;IACpC,GAAG,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;IACxB,0BAA0B,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAA;CAC5F;AAwvCD,eAAO,MAAM,2BAA2B;sBAEhC,kBAAkB,SACf,MAAM,GACZ,OAAO,CAAC,iCAAiC,CAAC;2BAuBvC,kBAAkB,SACf,MAAM,GACZ,OAAO,CAAC,sCAAsC,CAAC;mBAa7B,kBAAkB,UAAU,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;8BAKzF,kBAAkB,UACd,MAAM,YACJ,4BAA4B,GACrC,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;sBA+ClC,kBAAkB,UACd,MAAM,SACP,gCAAgC,GACtC,OAAO,CACR;QAAE,OAAO,EAAE,qBAAqB,CAAA;KAAE,GAAG;QAAE,KAAK,EAAE,WAAW,GAAG,0BAA0B,CAAA;KAAE,CACzF;iCAKK,kBAAkB,UACd,MAAM,SACP,gCAAgC,YAC7B,4BAA4B,GACrC,OAAO,CACR;QAAE,OAAO,EAAE,qBAAqB,CAAA;KAAE,GAAG;QAAE,KAAK,EAAE,WAAW,GAAG,0BAA0B,CAAA;KAAE,CACzF;kBA6KK,kBAAkB,UACd,MAAM,SACP,4BAA4B,GAClC,OAAO,CACN,6BAA6B,GAC7B;QAAE,KAAK,EAAE,WAAW,GAAG,2BAA2B,GAAG,yBAAyB,CAAA;KAAE,CACnF;uBA0JwB,kBAAkB,UAAU,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC;2CAiB1F,kBAAkB,UACd,MAAM,SACP,yCAAyC,GAC/C,OAAO,CAAC,0CAA0C,GAAG,IAAI,CAAC;8CA2HvD,kBAAkB,UACd,MAAM,SACP,yCAAyC,GAC/C,OAAO,CAAC,0CAA0C,GAAG,IAAI,CAAC;wBAKvD,kBAAkB,UACd,MAAM,SACP,kCAAkC,GACxC,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC;wBA2BpC,kBAAkB,UACd,MAAM,eACD,MAAM,SACZ,kCAAkC,GACxC,OAAO,CAAC,uBAAuB,GAAG,IAAI,GAAG,WAAW,CAAC;wBA2ClD,kBAAkB,UACd,MAAM,eACD,MAAM,GAClB,OAAO,CAAC,SAAS,GAAG,WAAW,GAAG,WAAW,CAAC;qBAyB3C,kBAAkB,UACd,MAAM,GACb,OAAO,CAAC,4BAA4B,EAAE,GAAG,IAAI,CAAC;mBAmG3C,kBAAkB,UACd,MAAM,aACH,MAAM,YACR,4BAA4B,GACpC,OAAO,CAAC,2BAA2B,GAAG,IAAI,CAAC;6BA2BxC,kBAAkB,UACd,MAAM,aACH,MAAM,YACR,4BAA4B;;;;;;;;;;iCAMJ,kBAAkB,UAAU,MAAM,aAAa,MAAM;;;;;;;;;;;CAyBzF,CAAA"}
1
+ {"version":3,"file":"service-public.d.ts","sourceRoot":"","sources":["../src/service-public.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { bookingDocuments, bookingFulfillments, bookingItems, bookingItemTravelers, bookingSessionStates, bookingStaffAssignments, bookings, bookingTravelers, } from "@voyantjs/bookings/schema";
2
- import { crmService, people } from "@voyantjs/crm";
3
- import { authUser, travelDocumentSchema, userProfilesTable, } from "@voyantjs/db/schema/iam";
2
+ import { crmService, people, personDocumentNumberPlaintextSchema, personPiiBlobPlaintextSchema, } from "@voyantjs/crm";
3
+ import { authUser, userProfilesTable } from "@voyantjs/db/schema/iam";
4
4
  import { invoiceRenditions, invoices, payments } from "@voyantjs/finance/schema";
5
5
  import { identityContactPoints } from "@voyantjs/identity/schema";
6
6
  import { identityService } from "@voyantjs/identity/service";
@@ -82,11 +82,11 @@ function deriveMiddleName(fullName, firstName, lastName) {
82
82
  }
83
83
  return working.length > 0 ? working : null;
84
84
  }
85
- function toStoredProfileDocumentType(type) {
86
- return type === "id_card" ? "national_id" : type;
85
+ function toCrmDocumentType(type) {
86
+ return type === "drivers_license" ? "driver_license" : type;
87
87
  }
88
- function toPublicProfileDocumentType(type) {
89
- return type === "national_id" ? "id_card" : type;
88
+ function toWireDocumentType(type) {
89
+ return type === "driver_license" ? "drivers_license" : type;
90
90
  }
91
91
  function getMetadataRecord(value) {
92
92
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -517,7 +517,6 @@ async function getAuthProfileRow(db, userId) {
517
517
  locale: userProfilesTable.locale,
518
518
  timezone: userProfilesTable.timezone,
519
519
  seatingPreference: userProfilesTable.seatingPreference,
520
- documentsEncrypted: userProfilesTable.documentsEncrypted,
521
520
  marketingConsent: userProfilesTable.marketingConsent,
522
521
  marketingConsentAt: userProfilesTable.marketingConsentAt,
523
522
  marketingConsentSource: userProfilesTable.marketingConsentSource,
@@ -530,20 +529,102 @@ async function getAuthProfileRow(db, userId) {
530
529
  .limit(1);
531
530
  return row ?? null;
532
531
  }
533
- async function getProfileDocuments(authProfile, options) {
534
- if (!authProfile?.documentsEncrypted || !options?.kms) {
532
+ async function decryptProfileBlob(envelope, options) {
533
+ if (!envelope || !options?.kms) {
534
+ return null;
535
+ }
536
+ const decrypted = await decryptOptionalJsonEnvelope(options.kms, peopleKeyRef, envelope, personPiiBlobPlaintextSchema);
537
+ return decrypted?.text ?? null;
538
+ }
539
+ async function encryptProfileBlob(value, options) {
540
+ if (!options?.kms) {
541
+ return undefined;
542
+ }
543
+ if (value === null) {
544
+ return null;
545
+ }
546
+ return encryptOptionalJsonEnvelope(options.kms, peopleKeyRef, { text: value });
547
+ }
548
+ async function decryptDocumentNumber(envelope, options) {
549
+ if (!envelope || !options?.kms) {
550
+ return null;
551
+ }
552
+ const decrypted = await decryptOptionalJsonEnvelope(options.kms, peopleKeyRef, envelope, personDocumentNumberPlaintextSchema);
553
+ return decrypted?.number ?? null;
554
+ }
555
+ async function encryptDocumentNumber(value, options) {
556
+ if (!options?.kms) {
557
+ return undefined;
558
+ }
559
+ if (value == null) {
560
+ return null;
561
+ }
562
+ return encryptOptionalJsonEnvelope(options.kms, peopleKeyRef, { number: value });
563
+ }
564
+ async function projectPersonDocumentToWire(row, options) {
565
+ return {
566
+ id: row.id,
567
+ type: toWireDocumentType(row.type),
568
+ number: await decryptDocumentNumber(row.numberEncrypted, options),
569
+ issuingAuthority: row.issuingAuthority ?? null,
570
+ issuingCountry: row.issuingCountry ?? null,
571
+ issueDate: row.issueDate ?? null,
572
+ expiryDate: row.expiryDate ?? null,
573
+ attachmentId: row.attachmentId ?? null,
574
+ isPrimary: row.isPrimary,
575
+ notes: row.notes ?? null,
576
+ createdAt: row.createdAt instanceof Date ? row.createdAt.toISOString() : String(row.createdAt),
577
+ updatedAt: row.updatedAt instanceof Date ? row.updatedAt.toISOString() : String(row.updatedAt),
578
+ };
579
+ }
580
+ async function getLinkedPersonPiiRow(db, userId) {
581
+ const [row] = await db
582
+ .select({
583
+ id: people.id,
584
+ accessibilityEncrypted: people.accessibilityEncrypted,
585
+ dietaryEncrypted: people.dietaryEncrypted,
586
+ loyaltyEncrypted: people.loyaltyEncrypted,
587
+ insuranceEncrypted: people.insuranceEncrypted,
588
+ })
589
+ .from(people)
590
+ .where(and(eq(people.source, linkedCustomerSource), eq(people.sourceRef, userId)))
591
+ .limit(1);
592
+ return row ?? null;
593
+ }
594
+ async function getLinkedPersonDocuments(db, userId, options) {
595
+ const linked = await resolveLinkedCustomerRecordId(db, userId);
596
+ if (!linked) {
535
597
  return [];
536
598
  }
537
- const decrypted = (await decryptOptionalJsonEnvelope(options.kms, peopleKeyRef, authProfile.documentsEncrypted, travelDocumentSchema.array())) ?? [];
538
- return decrypted.map((document) => ({
539
- type: toPublicProfileDocumentType(document.type),
540
- number: document.number,
541
- issuingAuthority: document.issuingAuthority ?? null,
542
- issuingCountry: document.issuingCountry,
543
- nationality: document.nationality ?? null,
544
- expiryDate: document.expiryDate,
545
- issueDate: document.issueDate ?? null,
546
- }));
599
+ const rows = await crmService.listPersonDocuments(db, linked);
600
+ return Promise.all(rows.map((row) => projectPersonDocumentToWire(row, options)));
601
+ }
602
+ /**
603
+ * Resolves the `crm.people` row linked to this auth user, creating
604
+ * it on first PII write if missing. The seed values mirror what the
605
+ * bootstrap path already produces — first/last name from the auth
606
+ * profile, source/sourceRef pinned to `auth.user`/`userId` so future
607
+ * reads find the same row.
608
+ */
609
+ async function ensureLinkedPerson(db, userId, authProfile) {
610
+ const existing = await resolveLinkedCustomerRecordId(db, userId);
611
+ if (existing)
612
+ return existing;
613
+ const fallbackFirst = authProfile.firstName ?? authProfile.name.split(" ")[0]?.trim() ?? "Customer";
614
+ const fallbackLast = authProfile.lastName ?? (authProfile.name.split(" ").slice(1).join(" ").trim() || "");
615
+ const created = await crmService.createPerson(db, {
616
+ firstName: fallbackFirst,
617
+ lastName: fallbackLast,
618
+ tags: [],
619
+ status: "active",
620
+ source: linkedCustomerSource,
621
+ sourceRef: userId,
622
+ website: null,
623
+ });
624
+ if (!created) {
625
+ throw new Error("Failed to create linked customer record");
626
+ }
627
+ return created.id;
547
628
  }
548
629
  async function resolveLinkedCustomerRecordId(db, userId) {
549
630
  const [row] = await db
@@ -686,7 +767,7 @@ async function upsertCustomerBillingAddress(db, personId, input) {
686
767
  }
687
768
  async function getAccessibleBookingIds(db, params) {
688
769
  const linkedPersonId = await resolveLinkedCustomerRecordId(db, params.userId);
689
- const email = params.email.trim().toLowerCase();
770
+ const email = params.email?.trim().toLowerCase() ?? null;
690
771
  const [directBookingRows, participantPersonRows, participantEmailRows] = await Promise.all([
691
772
  linkedPersonId
692
773
  ? db
@@ -700,18 +781,27 @@ async function getAccessibleBookingIds(db, params) {
700
781
  .from(bookingTravelers)
701
782
  .where(eq(bookingTravelers.personId, linkedPersonId))
702
783
  : Promise.resolve([]),
703
- db
704
- .select({ bookingId: bookingTravelers.bookingId })
705
- .from(bookingTravelers)
706
- .where(sql `lower(${bookingTravelers.email}) = ${email}`),
784
+ // Phone-only users have no email to match on — fall back to linked-person matching only.
785
+ email
786
+ ? db
787
+ .select({ bookingId: bookingTravelers.bookingId })
788
+ .from(bookingTravelers)
789
+ .where(sql `lower(${bookingTravelers.email}) = ${email}`)
790
+ : Promise.resolve([]),
707
791
  ]);
708
792
  return Array.from(new Set([...directBookingRows, ...participantPersonRows, ...participantEmailRows].map((row) => row.bookingId)));
709
793
  }
710
794
  async function hasBookingAccess(params) {
711
- const ownershipConditions = [sql `lower(${bookingTravelers.email}) = ${params.authEmail}`];
795
+ const ownershipConditions = [];
796
+ if (params.authEmail) {
797
+ ownershipConditions.push(sql `lower(${bookingTravelers.email}) = ${params.authEmail}`);
798
+ }
712
799
  if (params.linkedPersonId) {
713
800
  ownershipConditions.push(eq(bookingTravelers.personId, params.linkedPersonId));
714
801
  }
802
+ if (ownershipConditions.length === 0) {
803
+ return false;
804
+ }
715
805
  const [participantMatch, bookingMatch] = await Promise.all([
716
806
  params.db
717
807
  .select({ bookingId: bookingTravelers.bookingId })
@@ -962,7 +1052,13 @@ export const publicCustomerPortalService = {
962
1052
  if (!authProfile) {
963
1053
  return null;
964
1054
  }
965
- const documents = await getProfileDocuments(authProfile, options);
1055
+ const linkedPerson = await getLinkedPersonPiiRow(db, userId);
1056
+ const [accessibility, dietary, loyalty, insurance] = await Promise.all([
1057
+ decryptProfileBlob(linkedPerson?.accessibilityEncrypted, options),
1058
+ decryptProfileBlob(linkedPerson?.dietaryEncrypted, options),
1059
+ decryptProfileBlob(linkedPerson?.loyaltyEncrypted, options),
1060
+ decryptProfileBlob(linkedPerson?.insuranceEncrypted, options),
1061
+ ]);
966
1062
  const billingAddress = customerRecord?.billingAddress ?? null;
967
1063
  return {
968
1064
  userId: authProfile.id,
@@ -986,7 +1082,10 @@ export const publicCustomerPortalService = {
986
1082
  addressLine2: billingAddress.line2,
987
1083
  }
988
1084
  : null,
989
- documents,
1085
+ accessibility,
1086
+ dietary,
1087
+ loyalty,
1088
+ insurance,
990
1089
  marketingConsent: authProfile.marketingConsent ?? false,
991
1090
  marketingConsentAt: normalizeDateTime(authProfile.marketingConsentAt),
992
1091
  marketingConsentSource: authProfile.marketingConsentSource ?? null,
@@ -1035,24 +1134,12 @@ export const publicCustomerPortalService = {
1035
1134
  },
1036
1135
  }
1037
1136
  : undefined;
1038
- const documentsEncrypted = input.documents !== undefined && options?.kms
1039
- ? await encryptOptionalJsonEnvelope(options.kms, peopleKeyRef, input.documents.map((document) => ({
1040
- type: toStoredProfileDocumentType(document.type),
1041
- number: document.number,
1042
- issuingAuthority: document.issuingAuthority ?? undefined,
1043
- issuingCountry: document.issuingCountry,
1044
- nationality: document.nationality ?? undefined,
1045
- expiryDate: document.expiryDate,
1046
- issueDate: document.issueDate ?? undefined,
1047
- })))
1048
- : undefined;
1049
1137
  await db
1050
1138
  .insert(userProfilesTable)
1051
1139
  .values({
1052
1140
  id: userId,
1053
1141
  firstName: nextFirstName,
1054
1142
  lastName: nextLastName,
1055
- ...(documentsEncrypted !== undefined ? { documentsEncrypted } : {}),
1056
1143
  avatarUrl: input.avatarUrl ?? authProfile.avatarUrl ?? authProfile.image ?? null,
1057
1144
  locale: input.locale ?? authProfile.locale ?? "en",
1058
1145
  timezone: input.timezone !== undefined ? input.timezone : (authProfile.timezone ?? null),
@@ -1074,7 +1161,6 @@ export const publicCustomerPortalService = {
1074
1161
  set: {
1075
1162
  firstName: nextFirstName,
1076
1163
  lastName: nextLastName,
1077
- ...(documentsEncrypted !== undefined ? { documentsEncrypted } : {}),
1078
1164
  avatarUrl: input.avatarUrl ?? authProfile.avatarUrl ?? authProfile.image ?? null,
1079
1165
  locale: input.locale ?? authProfile.locale ?? "en",
1080
1166
  timezone: input.timezone !== undefined ? input.timezone : (authProfile.timezone ?? null),
@@ -1093,6 +1179,34 @@ export const publicCustomerPortalService = {
1093
1179
  updatedAt: new Date(),
1094
1180
  },
1095
1181
  });
1182
+ const piiUpdates = {};
1183
+ if (input.accessibility !== undefined) {
1184
+ const enc = await encryptProfileBlob(input.accessibility, options);
1185
+ if (enc !== undefined)
1186
+ piiUpdates.accessibilityEncrypted = enc;
1187
+ }
1188
+ if (input.dietary !== undefined) {
1189
+ const enc = await encryptProfileBlob(input.dietary, options);
1190
+ if (enc !== undefined)
1191
+ piiUpdates.dietaryEncrypted = enc;
1192
+ }
1193
+ if (input.loyalty !== undefined) {
1194
+ const enc = await encryptProfileBlob(input.loyalty, options);
1195
+ if (enc !== undefined)
1196
+ piiUpdates.loyaltyEncrypted = enc;
1197
+ }
1198
+ if (input.insurance !== undefined) {
1199
+ const enc = await encryptProfileBlob(input.insurance, options);
1200
+ if (enc !== undefined)
1201
+ piiUpdates.insuranceEncrypted = enc;
1202
+ }
1203
+ if (Object.keys(piiUpdates).length > 0) {
1204
+ const personId = await ensureLinkedPerson(db, userId, authProfile);
1205
+ await db
1206
+ .update(people)
1207
+ .set({ ...piiUpdates, updatedAt: new Date() })
1208
+ .where(eq(people.id, personId));
1209
+ }
1096
1210
  await db
1097
1211
  .update(authUser)
1098
1212
  .set({
@@ -1151,7 +1265,10 @@ export const publicCustomerPortalService = {
1151
1265
  candidates: [],
1152
1266
  };
1153
1267
  }
1154
- const normalizedEmail = normalizeEmail(authProfile.email);
1268
+ // Phone-only signups have no email; email-keyed candidate
1269
+ // matching simply finds zero candidates and the path falls
1270
+ // through to creating a fresh `crm.people` row when allowed.
1271
+ const normalizedEmail = authProfile.email ? normalizeEmail(authProfile.email) : null;
1155
1272
  const nextFirstName = input.firstName ?? authProfile.firstName ?? authProfile.name.split(" ")[0] ?? "Customer";
1156
1273
  const nextLastName = input.lastName ?? authProfile.lastName ?? authProfile.name.split(" ").slice(1).join(" ") ?? "";
1157
1274
  if (input.marketingConsent !== undefined || input.marketingConsentSource !== undefined) {
@@ -1219,7 +1336,9 @@ export const publicCustomerPortalService = {
1219
1336
  candidates: [],
1220
1337
  };
1221
1338
  }
1222
- const customerCandidates = await listCustomerRecordCandidatesByEmail(db, normalizedEmail);
1339
+ const customerCandidates = normalizedEmail
1340
+ ? await listCustomerRecordCandidatesByEmail(db, normalizedEmail)
1341
+ : [];
1223
1342
  const selectableCandidates = customerCandidates.filter((candidate) => !candidate.claimedByAnotherUser);
1224
1343
  if (selectableCandidates.length > 0) {
1225
1344
  return {
@@ -1547,7 +1666,7 @@ export const publicCustomerPortalService = {
1547
1666
  resolveLinkedCustomerRecordId(db, userId),
1548
1667
  getCustomerRecord(db, userId),
1549
1668
  ]);
1550
- const authEmail = authProfile.email.trim().toLowerCase();
1669
+ const authEmail = authProfile.email?.trim().toLowerCase() ?? null;
1551
1670
  const canAccess = await hasBookingAccess({
1552
1671
  db,
1553
1672
  bookingId,
@@ -1577,7 +1696,7 @@ export const publicCustomerPortalService = {
1577
1696
  db,
1578
1697
  bookingId,
1579
1698
  userId,
1580
- authEmail: authProfile.email.trim().toLowerCase(),
1699
+ authEmail: authProfile.email?.trim().toLowerCase() ?? null,
1581
1700
  linkedPersonId,
1582
1701
  });
1583
1702
  if (!canAccess) {
@@ -1585,4 +1704,83 @@ export const publicCustomerPortalService = {
1585
1704
  }
1586
1705
  return getBookingBillingContact(db, bookingId, customerRecord);
1587
1706
  },
1707
+ // ── Identity documents ────────────────────────────────────────────────
1708
+ // CRUD over `crm.person_documents` scoped to the auth user's linked
1709
+ // person. Auto-creates the linked person row on first write so
1710
+ // phone-only / metadata-light customers can save documents without
1711
+ // a separate bootstrap step.
1712
+ async listMyDocuments(db, userId, options) {
1713
+ return getLinkedPersonDocuments(db, userId, options);
1714
+ },
1715
+ async createMyDocument(db, userId, input, options) {
1716
+ const authProfile = await getAuthProfileRow(db, userId);
1717
+ if (!authProfile)
1718
+ return null;
1719
+ const personId = await ensureLinkedPerson(db, userId, authProfile);
1720
+ const numberEncrypted = await encryptDocumentNumber(input.number ?? null, options);
1721
+ const payload = {
1722
+ type: toCrmDocumentType(input.type),
1723
+ issuingAuthority: input.issuingAuthority ?? null,
1724
+ issuingCountry: input.issuingCountry ?? null,
1725
+ issueDate: input.issueDate ?? null,
1726
+ expiryDate: input.expiryDate ?? null,
1727
+ attachmentId: input.attachmentId ?? null,
1728
+ isPrimary: input.isPrimary ?? false,
1729
+ notes: input.notes ?? null,
1730
+ };
1731
+ if (numberEncrypted !== undefined) {
1732
+ payload.numberEncrypted = numberEncrypted;
1733
+ }
1734
+ const row = await crmService.createPersonDocument(db, personId, payload);
1735
+ return row ? projectPersonDocumentToWire(row, options) : null;
1736
+ },
1737
+ async updateMyDocument(db, userId, documentId, input, options) {
1738
+ const linkedPersonId = await resolveLinkedCustomerRecordId(db, userId);
1739
+ if (!linkedPersonId)
1740
+ return null;
1741
+ const existing = await crmService.getPersonDocument(db, documentId);
1742
+ if (!existing || existing.personId !== linkedPersonId)
1743
+ return null;
1744
+ const numberEncrypted = input.number !== undefined ? await encryptDocumentNumber(input.number, options) : undefined;
1745
+ const update = {};
1746
+ if (input.type !== undefined)
1747
+ update.type = toCrmDocumentType(input.type);
1748
+ if (input.issuingAuthority !== undefined)
1749
+ update.issuingAuthority = input.issuingAuthority;
1750
+ if (input.issuingCountry !== undefined)
1751
+ update.issuingCountry = input.issuingCountry;
1752
+ if (input.issueDate !== undefined)
1753
+ update.issueDate = input.issueDate;
1754
+ if (input.expiryDate !== undefined)
1755
+ update.expiryDate = input.expiryDate;
1756
+ if (input.attachmentId !== undefined)
1757
+ update.attachmentId = input.attachmentId;
1758
+ if (input.isPrimary !== undefined)
1759
+ update.isPrimary = input.isPrimary;
1760
+ if (input.notes !== undefined)
1761
+ update.notes = input.notes;
1762
+ if (numberEncrypted !== undefined)
1763
+ update.numberEncrypted = numberEncrypted;
1764
+ const row = await crmService.updatePersonDocument(db, documentId, update);
1765
+ return row ? projectPersonDocumentToWire(row, options) : null;
1766
+ },
1767
+ async deleteMyDocument(db, userId, documentId) {
1768
+ const linkedPersonId = await resolveLinkedCustomerRecordId(db, userId);
1769
+ if (!linkedPersonId)
1770
+ return null;
1771
+ const existing = await crmService.getPersonDocument(db, documentId);
1772
+ if (!existing || existing.personId !== linkedPersonId)
1773
+ return null;
1774
+ return crmService.deletePersonDocument(db, documentId);
1775
+ },
1776
+ async setPrimaryMyDocument(db, userId, documentId, options) {
1777
+ const linkedPersonId = await resolveLinkedCustomerRecordId(db, userId);
1778
+ if (!linkedPersonId)
1779
+ return null;
1780
+ const existing = await crmService.getPersonDocument(db, documentId);
1781
+ if (!existing || existing.personId !== linkedPersonId)
1782
+ return null;
1783
+ const row = await crmService.setPrimaryPersonDocument(db, documentId);
1784
+ return row ? projectPersonDocumentToWire(row, options) : null;
1785
+ },
1588
1786
  };