@voyantjs/customer-portal 0.5.0 → 0.6.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 +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -8
- package/dist/route-runtime.d.ts +16 -0
- package/dist/route-runtime.d.ts.map +1 -0
- package/dist/route-runtime.js +27 -0
- package/dist/routes-public.d.ts +976 -218
- package/dist/routes-public.d.ts.map +1 -1
- package/dist/routes-public.js +111 -152
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +3 -2
- package/dist/service-public.d.ts +4 -3
- package/dist/service-public.d.ts.map +1 -1
- package/dist/service-public.js +24 -34
- package/dist/validation-public.d.ts +44 -44
- package/package.json +10 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes-public.d.ts","sourceRoot":"","sources":["../src/routes-public.ts"],"names":[],"mappings":"
|
|
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
|
@@ -1,25 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ForbiddenApiError, handleApiError, parseJsonBody, requireUserId } from "@voyantjs/hono";
|
|
2
2
|
import { Hono } from "hono";
|
|
3
|
+
import { buildPublicCustomerPortalRouteRuntime, CUSTOMER_PORTAL_ROUTE_RUNTIME_CONTAINER_KEY, } from "./route-runtime.js";
|
|
4
|
+
import { customerPortalRoutes } from "./routes.js";
|
|
3
5
|
import { publicCustomerPortalService } from "./service-public.js";
|
|
4
6
|
import { bootstrapCustomerPortalSchema, createCustomerPortalCompanionSchema, importCustomerPortalBookingParticipantsSchema, updateCustomerPortalCompanionSchema, updateCustomerPortalProfileSchema, } from "./validation-public.js";
|
|
5
|
-
function getRuntimeEnv(c) {
|
|
6
|
-
const processEnv = globalThis.process?.env ?? {};
|
|
7
|
-
return {
|
|
8
|
-
...processEnv,
|
|
9
|
-
...(c.env ?? {}),
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
function resolveOptionalKms(c) {
|
|
13
|
-
try {
|
|
14
|
-
return createKmsProviderFromEnv(getRuntimeEnv(c));
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function unauthorized(c) {
|
|
21
|
-
return c.json({ error: "Unauthorized" }, 401);
|
|
22
|
-
}
|
|
23
7
|
function notFound(c, error) {
|
|
24
8
|
return c.json({ error }, 404);
|
|
25
9
|
}
|
|
@@ -29,139 +13,114 @@ function hasErrorResult(value) {
|
|
|
29
13
|
function hasBootstrapErrorResult(value) {
|
|
30
14
|
return "error" in value;
|
|
31
15
|
}
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
16
|
+
export function createPublicCustomerPortalRoutes(options = {}) {
|
|
17
|
+
const getRuntime = (c) => c.var.container?.resolve(CUSTOMER_PORTAL_ROUTE_RUNTIME_CONTAINER_KEY) ?? buildPublicCustomerPortalRouteRuntime(c.env, options);
|
|
18
|
+
const resolveOptionalKms = (c) => getRuntime(c).getOptionalKmsProvider();
|
|
19
|
+
const resolveDocumentDownloadUrl = (c, storageKey) => getRuntime(c).resolveDocumentDownloadUrl?.(storageKey) ?? null;
|
|
20
|
+
return new Hono()
|
|
21
|
+
.route("/", customerPortalRoutes)
|
|
22
|
+
.get("/me", async (c) => {
|
|
23
|
+
const userId = requireUserId(c);
|
|
24
|
+
const profile = await publicCustomerPortalService.getProfileWithOptions(c.get("db"), userId, {
|
|
25
|
+
kms: resolveOptionalKms(c),
|
|
26
|
+
});
|
|
27
|
+
return profile ? c.json({ data: profile }) : notFound(c, "Customer profile not found");
|
|
28
|
+
})
|
|
29
|
+
.patch("/me", async (c) => {
|
|
30
|
+
const userId = requireUserId(c);
|
|
31
|
+
const result = await publicCustomerPortalService.updateProfileWithOptions(c.get("db"), userId, await parseJsonBody(c, updateCustomerPortalProfileSchema), {
|
|
32
|
+
kms: resolveOptionalKms(c),
|
|
33
|
+
});
|
|
34
|
+
if (hasErrorResult(result)) {
|
|
35
|
+
if (result.error === "not_found") {
|
|
36
|
+
return notFound(c, "Customer profile not found");
|
|
37
|
+
}
|
|
38
|
+
return c.json({ error: "Customer record is not linked to this account" }, 409);
|
|
39
|
+
}
|
|
40
|
+
return c.json({ data: result.profile });
|
|
41
|
+
})
|
|
42
|
+
.post("/bootstrap", async (c) => {
|
|
43
|
+
const userId = requireUserId(c);
|
|
44
|
+
const result = await publicCustomerPortalService.bootstrap(c.get("db"), userId, await parseJsonBody(c, bootstrapCustomerPortalSchema));
|
|
45
|
+
if (hasBootstrapErrorResult(result)) {
|
|
46
|
+
if (result.error === "not_found") {
|
|
47
|
+
return notFound(c, "Customer profile not found");
|
|
48
|
+
}
|
|
49
|
+
if (result.error === "customer_record_not_found") {
|
|
50
|
+
return notFound(c, "Customer record not found");
|
|
51
|
+
}
|
|
52
|
+
return c.json({ error: "Customer record is already linked to another account" }, 409);
|
|
53
|
+
}
|
|
54
|
+
if (result.status === "customer_selection_required") {
|
|
55
|
+
return c.json({ data: result }, 409);
|
|
54
56
|
}
|
|
55
|
-
return c.json({
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (result.error === "not_found") {
|
|
67
|
-
return notFound(c, "Customer profile not found");
|
|
57
|
+
return c.json({ data: result });
|
|
58
|
+
})
|
|
59
|
+
.get("/companions", async (c) => {
|
|
60
|
+
const userId = requireUserId(c);
|
|
61
|
+
return c.json({ data: await publicCustomerPortalService.listCompanions(c.get("db"), userId) });
|
|
62
|
+
})
|
|
63
|
+
.post("/companions", async (c) => {
|
|
64
|
+
const userId = requireUserId(c);
|
|
65
|
+
const companion = await publicCustomerPortalService.createCompanion(c.get("db"), userId, await parseJsonBody(c, createCustomerPortalCompanionSchema));
|
|
66
|
+
if (!companion) {
|
|
67
|
+
return c.json({ error: "Customer record is not linked to this account" }, 409);
|
|
68
68
|
}
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
return c.json({ data: companion }, 201);
|
|
70
|
+
})
|
|
71
|
+
.post("/companions/import-booking-participants", async (c) => {
|
|
72
|
+
const userId = requireUserId(c);
|
|
73
|
+
const result = await publicCustomerPortalService.importBookingParticipantsAsCompanions(c.get("db"), userId, await parseJsonBody(c, importCustomerPortalBookingParticipantsSchema));
|
|
74
|
+
if (!result) {
|
|
75
|
+
return c.json({ error: "Customer record is not linked to this account" }, 409);
|
|
71
76
|
}
|
|
72
|
-
return c.json({
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return notFound(c, "
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
.delete("/companions/:companionId", async (c) => {
|
|
123
|
-
const userId = c.get("userId");
|
|
124
|
-
if (!userId) {
|
|
125
|
-
return unauthorized(c);
|
|
126
|
-
}
|
|
127
|
-
const result = await publicCustomerPortalService.deleteCompanion(c.get("db"), userId, c.req.param("companionId"));
|
|
128
|
-
if (result === "forbidden") {
|
|
129
|
-
return c.json({ error: "Companion does not belong to this customer" }, 403);
|
|
130
|
-
}
|
|
131
|
-
if (result === "not_found") {
|
|
132
|
-
return notFound(c, "Companion not found");
|
|
133
|
-
}
|
|
134
|
-
return c.json({ success: true });
|
|
135
|
-
})
|
|
136
|
-
.get("/bookings", async (c) => {
|
|
137
|
-
const userId = c.get("userId");
|
|
138
|
-
if (!userId) {
|
|
139
|
-
return unauthorized(c);
|
|
140
|
-
}
|
|
141
|
-
const bookings = await publicCustomerPortalService.listBookings(c.get("db"), userId);
|
|
142
|
-
return bookings ? c.json({ data: bookings }) : notFound(c, "Customer profile not found");
|
|
143
|
-
})
|
|
144
|
-
.get("/bookings/:bookingId", async (c) => {
|
|
145
|
-
const userId = c.get("userId");
|
|
146
|
-
if (!userId) {
|
|
147
|
-
return unauthorized(c);
|
|
148
|
-
}
|
|
149
|
-
const booking = await publicCustomerPortalService.getBooking(c.get("db"), userId, c.req.param("bookingId"));
|
|
150
|
-
return booking ? c.json({ data: booking }) : notFound(c, "Booking not found");
|
|
151
|
-
})
|
|
152
|
-
.get("/bookings/:bookingId/documents", async (c) => {
|
|
153
|
-
const userId = c.get("userId");
|
|
154
|
-
if (!userId) {
|
|
155
|
-
return unauthorized(c);
|
|
156
|
-
}
|
|
157
|
-
const documents = await publicCustomerPortalService.listBookingDocuments(c.get("db"), userId, c.req.param("bookingId"));
|
|
158
|
-
return documents ? c.json({ data: documents }) : notFound(c, "Booking not found");
|
|
159
|
-
})
|
|
160
|
-
.get("/bookings/:bookingId/billing-contact", async (c) => {
|
|
161
|
-
const userId = c.get("userId");
|
|
162
|
-
if (!userId) {
|
|
163
|
-
return unauthorized(c);
|
|
164
|
-
}
|
|
165
|
-
const billingContact = await publicCustomerPortalService.getBookingBillingContact(c.get("db"), userId, c.req.param("bookingId"));
|
|
166
|
-
return billingContact ? c.json({ data: billingContact }) : notFound(c, "Booking not found");
|
|
167
|
-
});
|
|
77
|
+
return c.json({ data: result });
|
|
78
|
+
})
|
|
79
|
+
.patch("/companions/:companionId", async (c) => {
|
|
80
|
+
const userId = requireUserId(c);
|
|
81
|
+
const companion = await publicCustomerPortalService.updateCompanion(c.get("db"), userId, c.req.param("companionId"), await parseJsonBody(c, updateCustomerPortalCompanionSchema));
|
|
82
|
+
if (companion === "forbidden") {
|
|
83
|
+
return handleApiError(new ForbiddenApiError("Companion does not belong to this customer"), c);
|
|
84
|
+
}
|
|
85
|
+
if (!companion) {
|
|
86
|
+
return notFound(c, "Companion not found");
|
|
87
|
+
}
|
|
88
|
+
return c.json({ data: companion });
|
|
89
|
+
})
|
|
90
|
+
.delete("/companions/:companionId", async (c) => {
|
|
91
|
+
const userId = requireUserId(c);
|
|
92
|
+
const result = await publicCustomerPortalService.deleteCompanion(c.get("db"), userId, c.req.param("companionId"));
|
|
93
|
+
if (result === "forbidden") {
|
|
94
|
+
return handleApiError(new ForbiddenApiError("Companion does not belong to this customer"), c);
|
|
95
|
+
}
|
|
96
|
+
if (result === "not_found") {
|
|
97
|
+
return notFound(c, "Companion not found");
|
|
98
|
+
}
|
|
99
|
+
return c.json({ success: true });
|
|
100
|
+
})
|
|
101
|
+
.get("/bookings", async (c) => {
|
|
102
|
+
const userId = requireUserId(c);
|
|
103
|
+
const bookings = await publicCustomerPortalService.listBookings(c.get("db"), userId);
|
|
104
|
+
return bookings ? c.json({ data: bookings }) : notFound(c, "Customer profile not found");
|
|
105
|
+
})
|
|
106
|
+
.get("/bookings/:bookingId", async (c) => {
|
|
107
|
+
const userId = requireUserId(c);
|
|
108
|
+
const booking = await publicCustomerPortalService.getBooking(c.get("db"), userId, c.req.param("bookingId"), {
|
|
109
|
+
resolveDocumentDownloadUrl: (storageKey) => resolveDocumentDownloadUrl(c, storageKey),
|
|
110
|
+
});
|
|
111
|
+
return booking ? c.json({ data: booking }) : notFound(c, "Booking not found");
|
|
112
|
+
})
|
|
113
|
+
.get("/bookings/:bookingId/documents", async (c) => {
|
|
114
|
+
const userId = requireUserId(c);
|
|
115
|
+
const documents = await publicCustomerPortalService.listBookingDocuments(c.get("db"), userId, c.req.param("bookingId"), {
|
|
116
|
+
resolveDocumentDownloadUrl: (storageKey) => resolveDocumentDownloadUrl(c, storageKey),
|
|
117
|
+
});
|
|
118
|
+
return documents ? c.json({ data: documents }) : notFound(c, "Booking not found");
|
|
119
|
+
})
|
|
120
|
+
.get("/bookings/:bookingId/billing-contact", async (c) => {
|
|
121
|
+
const userId = requireUserId(c);
|
|
122
|
+
const billingContact = await publicCustomerPortalService.getBookingBillingContact(c.get("db"), userId, c.req.param("bookingId"));
|
|
123
|
+
return billingContact ? c.json({ data: billingContact }) : notFound(c, "Booking not found");
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
export const publicCustomerPortalRoutes = createPublicCustomerPortalRoutes();
|
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":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AASjE,KAAK,GAAG,GAAG;IACT,SAAS,EAAE;QACT,EAAE,EAAE,kBAAkB,CAAA;KACvB,CAAA;CACF,CAAA;AAED,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAe7B,CAAA;AAEJ,MAAM,MAAM,oBAAoB,GAAG,OAAO,oBAAoB,CAAA"}
|
package/dist/routes.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
import { parseQuery } from "@voyantjs/hono";
|
|
1
2
|
import { Hono } from "hono";
|
|
2
3
|
import { publicCustomerPortalService } from "./service-public.js";
|
|
3
4
|
import { customerPortalContactExistsQuerySchema, customerPortalPhoneContactExistsQuerySchema, } from "./validation-public.js";
|
|
4
5
|
export const customerPortalRoutes = new Hono()
|
|
5
6
|
.get("/contact-exists", async (c) => {
|
|
6
|
-
const query =
|
|
7
|
+
const query = parseQuery(c, customerPortalContactExistsQuerySchema);
|
|
7
8
|
return c.json({
|
|
8
9
|
data: await publicCustomerPortalService.contactExists(c.get("db"), query.email),
|
|
9
10
|
});
|
|
10
11
|
})
|
|
11
12
|
.get("/contact-exists/phone", async (c) => {
|
|
12
|
-
const query =
|
|
13
|
+
const query = parseQuery(c, customerPortalPhoneContactExistsQuerySchema);
|
|
13
14
|
return c.json({
|
|
14
15
|
data: await publicCustomerPortalService.phoneContactExists(c.get("db"), query.phone),
|
|
15
16
|
});
|
package/dist/service-public.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
|
3
3
|
import type { BootstrapCustomerPortalInput, BootstrapCustomerPortalResult, CreateCustomerPortalCompanionInput, CustomerPortalBookingDetail, CustomerPortalBookingSummary, CustomerPortalCompanion, CustomerPortalContactExistsResult, CustomerPortalPhoneContactExistsResult, CustomerPortalProfile, ImportCustomerPortalBookingParticipantsInput, ImportCustomerPortalBookingParticipantsResult, UpdateCustomerPortalCompanionInput, UpdateCustomerPortalProfileInput } from "./validation-public.js";
|
|
4
4
|
interface CustomerPortalServiceOptions {
|
|
5
5
|
kms?: KmsProvider | null;
|
|
6
|
+
resolveDocumentDownloadUrl?: (storageKey: string) => Promise<string | null> | string | null;
|
|
6
7
|
}
|
|
7
8
|
export declare const publicCustomerPortalService: {
|
|
8
9
|
contactExists(db: PostgresJsDatabase, email: string): Promise<CustomerPortalContactExistsResult>;
|
|
@@ -28,12 +29,12 @@ export declare const publicCustomerPortalService: {
|
|
|
28
29
|
updateCompanion(db: PostgresJsDatabase, userId: string, companionId: string, input: UpdateCustomerPortalCompanionInput): Promise<CustomerPortalCompanion | null | "forbidden">;
|
|
29
30
|
deleteCompanion(db: PostgresJsDatabase, userId: string, companionId: string): Promise<"deleted" | "not_found" | "forbidden">;
|
|
30
31
|
listBookings(db: PostgresJsDatabase, userId: string): Promise<CustomerPortalBookingSummary[] | null>;
|
|
31
|
-
getBooking(db: PostgresJsDatabase, userId: string, bookingId: string): Promise<CustomerPortalBookingDetail | null>;
|
|
32
|
-
listBookingDocuments(db: PostgresJsDatabase, userId: string, bookingId: string): Promise<{
|
|
32
|
+
getBooking(db: PostgresJsDatabase, userId: string, bookingId: string, options?: CustomerPortalServiceOptions): Promise<CustomerPortalBookingDetail | null>;
|
|
33
|
+
listBookingDocuments(db: PostgresJsDatabase, userId: string, bookingId: string, options?: CustomerPortalServiceOptions): Promise<{
|
|
33
34
|
id: string;
|
|
34
35
|
source: "legal" | "finance" | "booking_document";
|
|
35
36
|
participantId: string | null;
|
|
36
|
-
type: "visa" | "
|
|
37
|
+
type: "visa" | "other" | "insurance" | "health" | "passport_copy" | "invoice" | "proforma" | "credit_note" | "contract";
|
|
37
38
|
fileName: string;
|
|
38
39
|
fileUrl: string;
|
|
39
40
|
mimeType: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-public.d.ts","sourceRoot":"","sources":["../src/service-public.ts"],"names":[],"mappings":"AAoBA,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,4CAA4C,EAC5C,6CAA6C,EAE7C,kCAAkC,EAClC,gCAAgC,EACjC,MAAM,wBAAwB,CAAA;AAQ/B,UAAU,4BAA4B;IACpC,GAAG,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"service-public.d.ts","sourceRoot":"","sources":["../src/service-public.ts"],"names":[],"mappings":"AAoBA,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,4CAA4C,EAC5C,6CAA6C,EAE7C,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;AAmwCD,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;kBAgNK,kBAAkB,UACd,MAAM,SACP,4BAA4B,GAClC,OAAO,CACN,6BAA6B,GAC7B;QAAE,KAAK,EAAE,WAAW,GAAG,2BAA2B,GAAG,yBAAyB,CAAA;KAAE,CACnF;uBA4MwB,kBAAkB,UAAU,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC;8CAiB1F,kBAAkB,UACd,MAAM,SACP,4CAA4C,GAClD,OAAO,CAAC,6CAA6C,GAAG,IAAI,CAAC;wBA4G1D,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"}
|
package/dist/service-public.js
CHANGED
|
@@ -320,7 +320,7 @@ function resolveFinanceDocumentFileName(invoiceNumber, invoiceType, format) {
|
|
|
320
320
|
const extension = format ?? "pdf";
|
|
321
321
|
return `${invoiceType}-${invoiceNumber}.${extension}`;
|
|
322
322
|
}
|
|
323
|
-
async function listLegalDocumentsForBooking(db, bookingId) {
|
|
323
|
+
async function listLegalDocumentsForBooking(db, bookingId, options = {}) {
|
|
324
324
|
const contractRows = await db
|
|
325
325
|
.select({
|
|
326
326
|
id: contracts.id,
|
|
@@ -340,22 +340,20 @@ async function listLegalDocumentsForBooking(db, bookingId) {
|
|
|
340
340
|
const bestAttachmentByContractId = new Map();
|
|
341
341
|
for (const attachment of attachmentRows) {
|
|
342
342
|
const metadata = getMetadataRecord(attachment.metadata);
|
|
343
|
-
const downloadUrl =
|
|
343
|
+
const downloadUrl = attachment.storageKey && options.resolveDocumentDownloadUrl
|
|
344
|
+
? await options.resolveDocumentDownloadUrl(attachment.storageKey)
|
|
345
|
+
: getMetadataString(metadata, ["url"]);
|
|
344
346
|
if (!downloadUrl || bestAttachmentByContractId.has(attachment.contractId)) {
|
|
345
347
|
continue;
|
|
346
348
|
}
|
|
347
|
-
bestAttachmentByContractId.set(attachment.contractId, attachment);
|
|
349
|
+
bestAttachmentByContractId.set(attachment.contractId, { attachment, downloadUrl });
|
|
348
350
|
}
|
|
349
351
|
return contractRows.flatMap((contract) => {
|
|
350
|
-
const
|
|
351
|
-
if (!
|
|
352
|
-
return [];
|
|
353
|
-
}
|
|
354
|
-
const metadata = getMetadataRecord(attachment.metadata);
|
|
355
|
-
const downloadUrl = getMetadataString(metadata, ["url", "downloadUrl"]);
|
|
356
|
-
if (!downloadUrl) {
|
|
352
|
+
const document = bestAttachmentByContractId.get(contract.id);
|
|
353
|
+
if (!document) {
|
|
357
354
|
return [];
|
|
358
355
|
}
|
|
356
|
+
const { attachment, downloadUrl } = document;
|
|
359
357
|
return [
|
|
360
358
|
{
|
|
361
359
|
id: attachment.id,
|
|
@@ -371,17 +369,7 @@ async function listLegalDocumentsForBooking(db, bookingId) {
|
|
|
371
369
|
});
|
|
372
370
|
}
|
|
373
371
|
function resolveFinanceDocumentDownloadUrl(metadata) {
|
|
374
|
-
return getMetadataString(metadata, [
|
|
375
|
-
"downloadUrl",
|
|
376
|
-
"download_url",
|
|
377
|
-
"signedUrl",
|
|
378
|
-
"signed_url",
|
|
379
|
-
"publicUrl",
|
|
380
|
-
"public_url",
|
|
381
|
-
"fileUrl",
|
|
382
|
-
"file_url",
|
|
383
|
-
"url",
|
|
384
|
-
]);
|
|
372
|
+
return getMetadataString(metadata, ["url"]);
|
|
385
373
|
}
|
|
386
374
|
function selectBookingSummaryProductTitle(items) {
|
|
387
375
|
const preferredItem = items.find((item) => item.itemType === "unit") ??
|
|
@@ -409,7 +397,7 @@ function deriveBookingSummaryPaymentStatus(invoicesForBooking, fallbackSellAmoun
|
|
|
409
397
|
}
|
|
410
398
|
return "unpaid";
|
|
411
399
|
}
|
|
412
|
-
async function getFinanceDataForBooking(db, bookingId) {
|
|
400
|
+
async function getFinanceDataForBooking(db, bookingId, options = {}) {
|
|
413
401
|
const invoiceRows = await db
|
|
414
402
|
.select()
|
|
415
403
|
.from(invoices)
|
|
@@ -436,11 +424,13 @@ async function getFinanceDataForBooking(db, bookingId) {
|
|
|
436
424
|
renditionByInvoiceId.set(rendition.invoiceId, existing);
|
|
437
425
|
}
|
|
438
426
|
const invoiceById = new Map(invoiceRows.map((invoice) => [invoice.id, invoice]));
|
|
439
|
-
const
|
|
427
|
+
const resolvedDocuments = await Promise.all(invoiceRows.map(async (invoice) => {
|
|
440
428
|
const renditions = renditionByInvoiceId.get(invoice.id) ?? [];
|
|
441
429
|
const selectedRendition = renditions.find((rendition) => rendition.status === "ready") ?? renditions[0] ?? null;
|
|
442
430
|
const metadata = getMetadataRecord(selectedRendition?.metadata ?? null);
|
|
443
|
-
const downloadUrl =
|
|
431
|
+
const downloadUrl = selectedRendition?.storageKey && options.resolveDocumentDownloadUrl
|
|
432
|
+
? await options.resolveDocumentDownloadUrl(selectedRendition.storageKey)
|
|
433
|
+
: resolveFinanceDocumentDownloadUrl(metadata);
|
|
444
434
|
return {
|
|
445
435
|
invoiceId: invoice.id,
|
|
446
436
|
invoiceNumber: invoice.invoiceNumber,
|
|
@@ -457,7 +447,7 @@ async function getFinanceDataForBooking(db, bookingId) {
|
|
|
457
447
|
generatedAt: normalizeDateTime(selectedRendition?.generatedAt ?? null),
|
|
458
448
|
downloadUrl,
|
|
459
449
|
};
|
|
460
|
-
});
|
|
450
|
+
}));
|
|
461
451
|
const paymentHistory = paymentRows.flatMap((payment) => {
|
|
462
452
|
const invoice = invoiceById.get(payment.invoiceId);
|
|
463
453
|
if (!invoice) {
|
|
@@ -479,7 +469,7 @@ async function getFinanceDataForBooking(db, bookingId) {
|
|
|
479
469
|
},
|
|
480
470
|
];
|
|
481
471
|
});
|
|
482
|
-
const portalDocuments =
|
|
472
|
+
const portalDocuments = resolvedDocuments.flatMap((document) => {
|
|
483
473
|
if (!document.downloadUrl) {
|
|
484
474
|
return [];
|
|
485
475
|
}
|
|
@@ -496,7 +486,7 @@ async function getFinanceDataForBooking(db, bookingId) {
|
|
|
496
486
|
},
|
|
497
487
|
];
|
|
498
488
|
});
|
|
499
|
-
return { documents, payments: paymentHistory, portalDocuments };
|
|
489
|
+
return { documents: resolvedDocuments, payments: paymentHistory, portalDocuments };
|
|
500
490
|
}
|
|
501
491
|
function toCustomerCompanion(row) {
|
|
502
492
|
const metadata = row.metadata ?? null;
|
|
@@ -810,7 +800,7 @@ async function getBookingBillingContact(db, bookingId, customerRecord) {
|
|
|
810
800
|
const hasValue = Object.values(result).some((value) => typeof value === "string" && value.length > 0);
|
|
811
801
|
return hasValue ? result : null;
|
|
812
802
|
}
|
|
813
|
-
async function buildBookingDetail(db, bookingId, customerRecord = null) {
|
|
803
|
+
async function buildBookingDetail(db, bookingId, customerRecord = null, options = {}) {
|
|
814
804
|
const [booking] = await db.select().from(bookings).where(eq(bookings.id, bookingId)).limit(1);
|
|
815
805
|
if (!booking) {
|
|
816
806
|
return null;
|
|
@@ -848,8 +838,8 @@ async function buildBookingDetail(db, bookingId, customerRecord = null) {
|
|
|
848
838
|
.from(bookingFulfillments)
|
|
849
839
|
.where(eq(bookingFulfillments.bookingId, booking.id))
|
|
850
840
|
.orderBy(asc(bookingFulfillments.createdAt)),
|
|
851
|
-
listLegalDocumentsForBooking(db, booking.id),
|
|
852
|
-
getFinanceDataForBooking(db, booking.id),
|
|
841
|
+
listLegalDocumentsForBooking(db, booking.id, options),
|
|
842
|
+
getFinanceDataForBooking(db, booking.id, options),
|
|
853
843
|
getBookingBillingContact(db, booking.id, customerRecord),
|
|
854
844
|
]);
|
|
855
845
|
const itemLinksByItemId = new Map();
|
|
@@ -1601,7 +1591,7 @@ export const publicCustomerPortalService = {
|
|
|
1601
1591
|
};
|
|
1602
1592
|
});
|
|
1603
1593
|
},
|
|
1604
|
-
async getBooking(db, userId, bookingId) {
|
|
1594
|
+
async getBooking(db, userId, bookingId, options = {}) {
|
|
1605
1595
|
const authProfile = await getAuthProfileRow(db, userId);
|
|
1606
1596
|
if (!authProfile) {
|
|
1607
1597
|
return null;
|
|
@@ -1621,10 +1611,10 @@ export const publicCustomerPortalService = {
|
|
|
1621
1611
|
if (!canAccess) {
|
|
1622
1612
|
return null;
|
|
1623
1613
|
}
|
|
1624
|
-
return buildBookingDetail(db, bookingId, customerRecord);
|
|
1614
|
+
return buildBookingDetail(db, bookingId, customerRecord, options);
|
|
1625
1615
|
},
|
|
1626
|
-
async listBookingDocuments(db, userId, bookingId) {
|
|
1627
|
-
const detail = await this.getBooking(db, userId, bookingId);
|
|
1616
|
+
async listBookingDocuments(db, userId, bookingId, options = {}) {
|
|
1617
|
+
const detail = await this.getBooking(db, userId, bookingId, options);
|
|
1628
1618
|
return detail?.documents ?? null;
|
|
1629
1619
|
},
|
|
1630
1620
|
async getBookingBillingContact(db, userId, bookingId) {
|