@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.
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/routes-public.d.ts +368 -82
- package/dist/routes-public.d.ts.map +1 -1
- package/dist/routes-public.js +40 -1
- package/dist/service-public.d.ts +80 -0
- package/dist/service-public.d.ts.map +1 -1
- package/dist/service-public.js +242 -44
- package/dist/validation-public.d.ts +77 -47
- package/dist/validation-public.d.ts.map +1 -1
- package/dist/validation-public.js +52 -21
- package/package.json +10 -10
|
@@ -1 +1 @@
|
|
|
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;
|
|
1
|
+
{"version":3,"file":"routes-public.d.ts","sourceRoot":"","sources":["../src/routes-public.ts"],"names":[],"mappings":"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"}
|
package/dist/routes-public.js
CHANGED
|
@@ -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);
|
package/dist/service-public.d.ts
CHANGED
|
@@ -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":"
|
|
1
|
+
{"version":3,"file":"service-public.d.ts","sourceRoot":"","sources":["../src/service-public.ts"],"names":[],"mappings":"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"}
|
package/dist/service-public.js
CHANGED
|
@@ -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,
|
|
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
|
|
86
|
-
return type === "
|
|
85
|
+
function toCrmDocumentType(type) {
|
|
86
|
+
return type === "drivers_license" ? "driver_license" : type;
|
|
87
87
|
}
|
|
88
|
-
function
|
|
89
|
-
return 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
|
|
534
|
-
if (!
|
|
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
|
|
538
|
-
return
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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
|
|
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
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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 = [
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
};
|