@voyantjs/crm 0.25.0 → 0.26.1
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 +18 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -6
- package/dist/route-runtime.d.ts +21 -0
- package/dist/route-runtime.d.ts.map +1 -0
- package/dist/route-runtime.js +28 -0
- package/dist/routes/accounts.d.ts +50 -2
- package/dist/routes/accounts.d.ts.map +1 -1
- package/dist/routes/index.d.ts +496 -2
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -0
- package/dist/routes/person-documents.d.ts +458 -0
- package/dist/routes/person-documents.d.ts.map +1 -0
- package/dist/routes/person-documents.js +160 -0
- package/dist/schema-accounts.d.ts +367 -0
- package/dist/schema-accounts.d.ts.map +1 -1
- package/dist/schema-accounts.js +68 -2
- package/dist/schema-activities.js +2 -2
- package/dist/schema-relations.js +3 -3
- package/dist/schema-sales.js +2 -2
- package/dist/schema.d.ts +6 -6
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +6 -6
- package/dist/service/accounts-people.d.ts +49 -1
- package/dist/service/accounts-people.d.ts.map +1 -1
- package/dist/service/accounts-shared.d.ts +12 -0
- package/dist/service/accounts-shared.d.ts.map +1 -1
- package/dist/service/accounts-shared.js +4 -0
- package/dist/service/accounts.d.ts +49 -1
- package/dist/service/accounts.d.ts.map +1 -1
- package/dist/service/index.d.ts +1172 -1
- package/dist/service/index.d.ts.map +1 -1
- package/dist/service/index.js +3 -0
- package/dist/service/person-documents.d.ts +1188 -0
- package/dist/service/person-documents.d.ts.map +1 -0
- package/dist/service/person-documents.js +216 -0
- package/dist/validation.d.ts +168 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +68 -0
- package/package.json +6 -5
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import type { ModuleContainer } from "@voyantjs/core";
|
|
2
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
3
|
+
type Env = {
|
|
4
|
+
Variables: {
|
|
5
|
+
container?: ModuleContainer;
|
|
6
|
+
db: PostgresJsDatabase;
|
|
7
|
+
userId?: string;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export declare const personDocumentRoutes: import("hono/hono-base").HonoBase<Env, {
|
|
11
|
+
"/people/:id/documents": {
|
|
12
|
+
$get: {
|
|
13
|
+
input: {
|
|
14
|
+
param: {
|
|
15
|
+
id: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
output: {
|
|
19
|
+
data: {
|
|
20
|
+
id: string;
|
|
21
|
+
personId: string;
|
|
22
|
+
type: "passport" | "visa" | "other" | "id_card" | "driver_license";
|
|
23
|
+
numberEncrypted: {
|
|
24
|
+
enc: string;
|
|
25
|
+
} | null;
|
|
26
|
+
issuingAuthority: string | null;
|
|
27
|
+
issuingCountry: string | null;
|
|
28
|
+
issueDate: string | null;
|
|
29
|
+
expiryDate: string | null;
|
|
30
|
+
attachmentId: string | null;
|
|
31
|
+
isPrimary: boolean;
|
|
32
|
+
notes: string | null;
|
|
33
|
+
metadata: {
|
|
34
|
+
[x: string]: import("hono/utils/types").JSONValue;
|
|
35
|
+
} | null;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
updatedAt: string;
|
|
38
|
+
}[];
|
|
39
|
+
};
|
|
40
|
+
outputFormat: "json";
|
|
41
|
+
status: import("hono/utils/http-status").ContentfulStatusCode;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
} & {
|
|
45
|
+
"/people/:id/documents": {
|
|
46
|
+
$post: {
|
|
47
|
+
input: {
|
|
48
|
+
param: {
|
|
49
|
+
id: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
output: {
|
|
53
|
+
error: string;
|
|
54
|
+
};
|
|
55
|
+
outputFormat: "json";
|
|
56
|
+
status: 404;
|
|
57
|
+
} | {
|
|
58
|
+
input: {
|
|
59
|
+
param: {
|
|
60
|
+
id: string;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
output: {
|
|
64
|
+
data: {
|
|
65
|
+
metadata: {
|
|
66
|
+
[x: string]: import("hono/utils/types").JSONValue;
|
|
67
|
+
} | null;
|
|
68
|
+
id: string;
|
|
69
|
+
createdAt: string;
|
|
70
|
+
updatedAt: string;
|
|
71
|
+
type: "passport" | "visa" | "other" | "id_card" | "driver_license";
|
|
72
|
+
issuingAuthority: string | null;
|
|
73
|
+
issuingCountry: string | null;
|
|
74
|
+
expiryDate: string | null;
|
|
75
|
+
issueDate: string | null;
|
|
76
|
+
notes: string | null;
|
|
77
|
+
isPrimary: boolean;
|
|
78
|
+
personId: string;
|
|
79
|
+
numberEncrypted: {
|
|
80
|
+
enc: string;
|
|
81
|
+
} | null;
|
|
82
|
+
attachmentId: string | null;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
outputFormat: "json";
|
|
86
|
+
status: 201;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
} & {
|
|
90
|
+
"/person-documents/:id": {
|
|
91
|
+
$get: {
|
|
92
|
+
input: {
|
|
93
|
+
param: {
|
|
94
|
+
id: string;
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
output: {
|
|
98
|
+
error: string;
|
|
99
|
+
};
|
|
100
|
+
outputFormat: "json";
|
|
101
|
+
status: 404;
|
|
102
|
+
} | {
|
|
103
|
+
input: {
|
|
104
|
+
param: {
|
|
105
|
+
id: string;
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
output: {
|
|
109
|
+
data: {
|
|
110
|
+
id: string;
|
|
111
|
+
personId: string;
|
|
112
|
+
type: "passport" | "visa" | "other" | "id_card" | "driver_license";
|
|
113
|
+
numberEncrypted: {
|
|
114
|
+
enc: string;
|
|
115
|
+
} | null;
|
|
116
|
+
issuingAuthority: string | null;
|
|
117
|
+
issuingCountry: string | null;
|
|
118
|
+
issueDate: string | null;
|
|
119
|
+
expiryDate: string | null;
|
|
120
|
+
attachmentId: string | null;
|
|
121
|
+
isPrimary: boolean;
|
|
122
|
+
notes: string | null;
|
|
123
|
+
metadata: {
|
|
124
|
+
[x: string]: import("hono/utils/types").JSONValue;
|
|
125
|
+
} | null;
|
|
126
|
+
createdAt: string;
|
|
127
|
+
updatedAt: string;
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
outputFormat: "json";
|
|
131
|
+
status: import("hono/utils/http-status").ContentfulStatusCode;
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
} & {
|
|
135
|
+
"/person-documents/:id": {
|
|
136
|
+
$patch: {
|
|
137
|
+
input: {
|
|
138
|
+
param: {
|
|
139
|
+
id: string;
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
output: {
|
|
143
|
+
error: string;
|
|
144
|
+
};
|
|
145
|
+
outputFormat: "json";
|
|
146
|
+
status: 404;
|
|
147
|
+
} | {
|
|
148
|
+
input: {
|
|
149
|
+
param: {
|
|
150
|
+
id: string;
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
output: {
|
|
154
|
+
data: {
|
|
155
|
+
id: string;
|
|
156
|
+
personId: string;
|
|
157
|
+
type: "passport" | "visa" | "other" | "id_card" | "driver_license";
|
|
158
|
+
numberEncrypted: {
|
|
159
|
+
enc: string;
|
|
160
|
+
} | null;
|
|
161
|
+
issuingAuthority: string | null;
|
|
162
|
+
issuingCountry: string | null;
|
|
163
|
+
issueDate: string | null;
|
|
164
|
+
expiryDate: string | null;
|
|
165
|
+
attachmentId: string | null;
|
|
166
|
+
isPrimary: boolean;
|
|
167
|
+
notes: string | null;
|
|
168
|
+
metadata: {
|
|
169
|
+
[x: string]: import("hono/utils/types").JSONValue;
|
|
170
|
+
} | null;
|
|
171
|
+
createdAt: string;
|
|
172
|
+
updatedAt: string;
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
outputFormat: "json";
|
|
176
|
+
status: import("hono/utils/http-status").ContentfulStatusCode;
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
} & {
|
|
180
|
+
"/person-documents/:id": {
|
|
181
|
+
$delete: {
|
|
182
|
+
input: {
|
|
183
|
+
param: {
|
|
184
|
+
id: string;
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
output: {
|
|
188
|
+
error: string;
|
|
189
|
+
};
|
|
190
|
+
outputFormat: "json";
|
|
191
|
+
status: 404;
|
|
192
|
+
} | {
|
|
193
|
+
input: {
|
|
194
|
+
param: {
|
|
195
|
+
id: string;
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
output: {
|
|
199
|
+
success: true;
|
|
200
|
+
};
|
|
201
|
+
outputFormat: "json";
|
|
202
|
+
status: import("hono/utils/http-status").ContentfulStatusCode;
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
} & {
|
|
206
|
+
"/person-documents/:id/set-primary": {
|
|
207
|
+
$post: {
|
|
208
|
+
input: {
|
|
209
|
+
param: {
|
|
210
|
+
id: string;
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
output: {
|
|
214
|
+
error: string;
|
|
215
|
+
};
|
|
216
|
+
outputFormat: "json";
|
|
217
|
+
status: 404;
|
|
218
|
+
} | {
|
|
219
|
+
input: {
|
|
220
|
+
param: {
|
|
221
|
+
id: string;
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
output: {
|
|
225
|
+
data: {
|
|
226
|
+
id: string;
|
|
227
|
+
personId: string;
|
|
228
|
+
type: "passport" | "visa" | "other" | "id_card" | "driver_license";
|
|
229
|
+
numberEncrypted: {
|
|
230
|
+
enc: string;
|
|
231
|
+
} | null;
|
|
232
|
+
issuingAuthority: string | null;
|
|
233
|
+
issuingCountry: string | null;
|
|
234
|
+
issueDate: string | null;
|
|
235
|
+
expiryDate: string | null;
|
|
236
|
+
attachmentId: string | null;
|
|
237
|
+
isPrimary: boolean;
|
|
238
|
+
notes: string | null;
|
|
239
|
+
metadata: {
|
|
240
|
+
[x: string]: import("hono/utils/types").JSONValue;
|
|
241
|
+
} | null;
|
|
242
|
+
createdAt: string;
|
|
243
|
+
updatedAt: string;
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
outputFormat: "json";
|
|
247
|
+
status: import("hono/utils/http-status").ContentfulStatusCode;
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
} & {
|
|
251
|
+
"/people/:id/travel-snapshot": {
|
|
252
|
+
$get: {
|
|
253
|
+
input: {
|
|
254
|
+
param: {
|
|
255
|
+
id: string;
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
output: {
|
|
259
|
+
error: string;
|
|
260
|
+
};
|
|
261
|
+
outputFormat: "json";
|
|
262
|
+
status: 503;
|
|
263
|
+
} | {
|
|
264
|
+
input: {
|
|
265
|
+
param: {
|
|
266
|
+
id: string;
|
|
267
|
+
};
|
|
268
|
+
};
|
|
269
|
+
output: {
|
|
270
|
+
error: string;
|
|
271
|
+
};
|
|
272
|
+
outputFormat: "json";
|
|
273
|
+
status: 404;
|
|
274
|
+
} | {
|
|
275
|
+
input: {
|
|
276
|
+
param: {
|
|
277
|
+
id: string;
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
output: {
|
|
281
|
+
data: {
|
|
282
|
+
dateOfBirth: string | null;
|
|
283
|
+
dietaryRequirements: string | null;
|
|
284
|
+
accessibilityNeeds: string | null;
|
|
285
|
+
passportNumber: string | null;
|
|
286
|
+
passportExpiry: string | null;
|
|
287
|
+
passportIssuingCountry: string | null;
|
|
288
|
+
passportIssuingAuthority: string | null;
|
|
289
|
+
passportPersonDocumentId: string | null;
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
outputFormat: "json";
|
|
293
|
+
status: import("hono/utils/http-status").ContentfulStatusCode;
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
} & {
|
|
297
|
+
"/people/:id/profile-pii": {
|
|
298
|
+
$patch: {
|
|
299
|
+
input: {
|
|
300
|
+
param: {
|
|
301
|
+
id: string;
|
|
302
|
+
};
|
|
303
|
+
};
|
|
304
|
+
output: {
|
|
305
|
+
error: string;
|
|
306
|
+
};
|
|
307
|
+
outputFormat: "json";
|
|
308
|
+
status: 503;
|
|
309
|
+
} | {
|
|
310
|
+
input: {
|
|
311
|
+
param: {
|
|
312
|
+
id: string;
|
|
313
|
+
};
|
|
314
|
+
};
|
|
315
|
+
output: {
|
|
316
|
+
error: string;
|
|
317
|
+
};
|
|
318
|
+
outputFormat: "json";
|
|
319
|
+
status: 400;
|
|
320
|
+
} | {
|
|
321
|
+
input: {
|
|
322
|
+
param: {
|
|
323
|
+
id: string;
|
|
324
|
+
};
|
|
325
|
+
};
|
|
326
|
+
output: {
|
|
327
|
+
error: string;
|
|
328
|
+
};
|
|
329
|
+
outputFormat: "json";
|
|
330
|
+
status: 404;
|
|
331
|
+
} | {
|
|
332
|
+
input: {
|
|
333
|
+
param: {
|
|
334
|
+
id: string;
|
|
335
|
+
};
|
|
336
|
+
};
|
|
337
|
+
output: {
|
|
338
|
+
success: true;
|
|
339
|
+
};
|
|
340
|
+
outputFormat: "json";
|
|
341
|
+
status: import("hono/utils/http-status").ContentfulStatusCode;
|
|
342
|
+
};
|
|
343
|
+
};
|
|
344
|
+
} & {
|
|
345
|
+
"/people/:id/documents/from-plaintext": {
|
|
346
|
+
$post: {
|
|
347
|
+
input: {
|
|
348
|
+
param: {
|
|
349
|
+
id: string;
|
|
350
|
+
};
|
|
351
|
+
};
|
|
352
|
+
output: {
|
|
353
|
+
error: string;
|
|
354
|
+
};
|
|
355
|
+
outputFormat: "json";
|
|
356
|
+
status: 503;
|
|
357
|
+
} | {
|
|
358
|
+
input: {
|
|
359
|
+
param: {
|
|
360
|
+
id: string;
|
|
361
|
+
};
|
|
362
|
+
};
|
|
363
|
+
output: {
|
|
364
|
+
error: string;
|
|
365
|
+
};
|
|
366
|
+
outputFormat: "json";
|
|
367
|
+
status: 404;
|
|
368
|
+
} | {
|
|
369
|
+
input: {
|
|
370
|
+
param: {
|
|
371
|
+
id: string;
|
|
372
|
+
};
|
|
373
|
+
};
|
|
374
|
+
output: {
|
|
375
|
+
data: {
|
|
376
|
+
metadata: {
|
|
377
|
+
[x: string]: import("hono/utils/types").JSONValue;
|
|
378
|
+
} | null;
|
|
379
|
+
id: string;
|
|
380
|
+
createdAt: string;
|
|
381
|
+
updatedAt: string;
|
|
382
|
+
type: "passport" | "visa" | "other" | "id_card" | "driver_license";
|
|
383
|
+
issuingAuthority: string | null;
|
|
384
|
+
issuingCountry: string | null;
|
|
385
|
+
expiryDate: string | null;
|
|
386
|
+
issueDate: string | null;
|
|
387
|
+
notes: string | null;
|
|
388
|
+
isPrimary: boolean;
|
|
389
|
+
personId: string;
|
|
390
|
+
numberEncrypted: {
|
|
391
|
+
enc: string;
|
|
392
|
+
} | null;
|
|
393
|
+
attachmentId: string | null;
|
|
394
|
+
};
|
|
395
|
+
};
|
|
396
|
+
outputFormat: "json";
|
|
397
|
+
status: 201;
|
|
398
|
+
};
|
|
399
|
+
};
|
|
400
|
+
} & {
|
|
401
|
+
"/person-documents/:id/from-plaintext": {
|
|
402
|
+
$patch: {
|
|
403
|
+
input: {
|
|
404
|
+
param: {
|
|
405
|
+
id: string;
|
|
406
|
+
};
|
|
407
|
+
};
|
|
408
|
+
output: {
|
|
409
|
+
error: string;
|
|
410
|
+
};
|
|
411
|
+
outputFormat: "json";
|
|
412
|
+
status: 503;
|
|
413
|
+
} | {
|
|
414
|
+
input: {
|
|
415
|
+
param: {
|
|
416
|
+
id: string;
|
|
417
|
+
};
|
|
418
|
+
};
|
|
419
|
+
output: {
|
|
420
|
+
error: string;
|
|
421
|
+
};
|
|
422
|
+
outputFormat: "json";
|
|
423
|
+
status: 404;
|
|
424
|
+
} | {
|
|
425
|
+
input: {
|
|
426
|
+
param: {
|
|
427
|
+
id: string;
|
|
428
|
+
};
|
|
429
|
+
};
|
|
430
|
+
output: {
|
|
431
|
+
data: {
|
|
432
|
+
id: string;
|
|
433
|
+
personId: string;
|
|
434
|
+
type: "passport" | "visa" | "other" | "id_card" | "driver_license";
|
|
435
|
+
numberEncrypted: {
|
|
436
|
+
enc: string;
|
|
437
|
+
} | null;
|
|
438
|
+
issuingAuthority: string | null;
|
|
439
|
+
issuingCountry: string | null;
|
|
440
|
+
issueDate: string | null;
|
|
441
|
+
expiryDate: string | null;
|
|
442
|
+
attachmentId: string | null;
|
|
443
|
+
isPrimary: boolean;
|
|
444
|
+
notes: string | null;
|
|
445
|
+
metadata: {
|
|
446
|
+
[x: string]: import("hono/utils/types").JSONValue;
|
|
447
|
+
} | null;
|
|
448
|
+
createdAt: string;
|
|
449
|
+
updatedAt: string;
|
|
450
|
+
};
|
|
451
|
+
};
|
|
452
|
+
outputFormat: "json";
|
|
453
|
+
status: import("hono/utils/http-status").ContentfulStatusCode;
|
|
454
|
+
};
|
|
455
|
+
};
|
|
456
|
+
}, "/", "/person-documents/:id/from-plaintext">;
|
|
457
|
+
export {};
|
|
458
|
+
//# sourceMappingURL=person-documents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"person-documents.d.ts","sourceRoot":"","sources":["../../src/routes/person-documents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAIrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAejE,KAAK,GAAG,GAAG;IACT,SAAS,EAAE;QACT,SAAS,CAAC,EAAE,eAAe,CAAA;QAC3B,EAAE,EAAE,kBAAkB,CAAA;QACtB,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAA;CACF,CAAA;AAiBD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+CA8I7B,CAAA"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { parseJsonBody, parseQuery } from "@voyantjs/hono";
|
|
2
|
+
import { encryptOptionalJsonEnvelope } from "@voyantjs/utils";
|
|
3
|
+
import { eq } from "drizzle-orm";
|
|
4
|
+
import { Hono } from "hono";
|
|
5
|
+
import { CRM_ROUTE_RUNTIME_CONTAINER_KEY } from "../route-runtime.js";
|
|
6
|
+
import { people } from "../schema.js";
|
|
7
|
+
import { crmService } from "../service/index.js";
|
|
8
|
+
import { insertPersonDocumentFromPlaintextSchema, insertPersonDocumentSchema, personDocumentListQuerySchema, updatePersonDocumentFromPlaintextSchema, updatePersonDocumentSchema, updatePersonProfilePiiSchema, } from "../validation.js";
|
|
9
|
+
const peopleKeyRef = { keyType: "people" };
|
|
10
|
+
async function getCrmKms(c) {
|
|
11
|
+
const runtime = c.var.container?.resolve(CRM_ROUTE_RUNTIME_CONTAINER_KEY);
|
|
12
|
+
if (!runtime)
|
|
13
|
+
return null;
|
|
14
|
+
return runtime.getKmsProvider();
|
|
15
|
+
}
|
|
16
|
+
function kmsRequired(c) {
|
|
17
|
+
return c.json({ error: "KMS provider not configured — admin PII routes require a wired KMS key" }, 503);
|
|
18
|
+
}
|
|
19
|
+
export const personDocumentRoutes = new Hono()
|
|
20
|
+
.get("/people/:id/documents", async (c) => {
|
|
21
|
+
const query = parseQuery(c, personDocumentListQuerySchema);
|
|
22
|
+
return c.json({
|
|
23
|
+
data: await crmService.listPersonDocuments(c.get("db"), c.req.param("id"), query),
|
|
24
|
+
});
|
|
25
|
+
})
|
|
26
|
+
.post("/people/:id/documents", async (c) => {
|
|
27
|
+
const row = await crmService.createPersonDocument(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertPersonDocumentSchema));
|
|
28
|
+
if (!row)
|
|
29
|
+
return c.json({ error: "Person not found" }, 404);
|
|
30
|
+
return c.json({ data: row }, 201);
|
|
31
|
+
})
|
|
32
|
+
.get("/person-documents/:id", async (c) => {
|
|
33
|
+
const row = await crmService.getPersonDocument(c.get("db"), c.req.param("id"));
|
|
34
|
+
if (!row)
|
|
35
|
+
return c.json({ error: "Document not found" }, 404);
|
|
36
|
+
return c.json({ data: row });
|
|
37
|
+
})
|
|
38
|
+
.patch("/person-documents/:id", async (c) => {
|
|
39
|
+
const row = await crmService.updatePersonDocument(c.get("db"), c.req.param("id"), await parseJsonBody(c, updatePersonDocumentSchema));
|
|
40
|
+
if (!row)
|
|
41
|
+
return c.json({ error: "Document not found" }, 404);
|
|
42
|
+
return c.json({ data: row });
|
|
43
|
+
})
|
|
44
|
+
.delete("/person-documents/:id", async (c) => {
|
|
45
|
+
const row = await crmService.deletePersonDocument(c.get("db"), c.req.param("id"));
|
|
46
|
+
if (!row)
|
|
47
|
+
return c.json({ error: "Document not found" }, 404);
|
|
48
|
+
return c.json({ success: true });
|
|
49
|
+
})
|
|
50
|
+
.post("/person-documents/:id/set-primary", async (c) => {
|
|
51
|
+
const row = await crmService.setPrimaryPersonDocument(c.get("db"), c.req.param("id"));
|
|
52
|
+
if (!row)
|
|
53
|
+
return c.json({ error: "Document not found" }, 404);
|
|
54
|
+
return c.json({ data: row });
|
|
55
|
+
})
|
|
56
|
+
// ── Admin PII conveniences (server-side encrypt/decrypt) ──────────────
|
|
57
|
+
// Operator UIs (booking-traveler dialog) need to read a person's
|
|
58
|
+
// primary passport + free-text PII in plaintext to pre-fill forms,
|
|
59
|
+
// and write changes back without round-tripping ciphertext through
|
|
60
|
+
// the browser. Endpoints below use the request-scoped CRM runtime
|
|
61
|
+
// to access the people KMS key.
|
|
62
|
+
/**
|
|
63
|
+
* Decrypted snapshot of a person's primary passport + dietary +
|
|
64
|
+
* accessibility values. Used by the booking-traveler dialog to
|
|
65
|
+
* pre-fill snapshot fields when an operator picks an existing
|
|
66
|
+
* person. Returns 404 when person missing, 503 when KMS unwired.
|
|
67
|
+
*/
|
|
68
|
+
.get("/people/:id/travel-snapshot", async (c) => {
|
|
69
|
+
const kms = await getCrmKms(c);
|
|
70
|
+
if (!kms)
|
|
71
|
+
return kmsRequired(c);
|
|
72
|
+
const snapshot = await crmService.loadPersonTravelSnapshot(c.get("db"), c.req.param("id"), {
|
|
73
|
+
kms,
|
|
74
|
+
});
|
|
75
|
+
if (!snapshot)
|
|
76
|
+
return c.json({ error: "Person not found" }, 404);
|
|
77
|
+
return c.json({ data: snapshot });
|
|
78
|
+
})
|
|
79
|
+
/**
|
|
80
|
+
* Plaintext PATCH for the four free-text PII slots on
|
|
81
|
+
* `crm.people`. The route encrypts each provided value server-side
|
|
82
|
+
* with the people KMS key. `null` clears a slot.
|
|
83
|
+
*/
|
|
84
|
+
.patch("/people/:id/profile-pii", async (c) => {
|
|
85
|
+
const kms = await getCrmKms(c);
|
|
86
|
+
if (!kms)
|
|
87
|
+
return kmsRequired(c);
|
|
88
|
+
const body = await parseJsonBody(c, updatePersonProfilePiiSchema);
|
|
89
|
+
const updates = {};
|
|
90
|
+
for (const [key, column] of [
|
|
91
|
+
["accessibility", "accessibilityEncrypted"],
|
|
92
|
+
["dietary", "dietaryEncrypted"],
|
|
93
|
+
["loyalty", "loyaltyEncrypted"],
|
|
94
|
+
["insurance", "insuranceEncrypted"],
|
|
95
|
+
]) {
|
|
96
|
+
const value = body[key];
|
|
97
|
+
if (value === undefined)
|
|
98
|
+
continue;
|
|
99
|
+
if (value === null) {
|
|
100
|
+
updates[column] = null;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
updates[column] = await encryptOptionalJsonEnvelope(kms, peopleKeyRef, { text: value });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (Object.keys(updates).length === 0) {
|
|
107
|
+
return c.json({ error: "Nothing to update" }, 400);
|
|
108
|
+
}
|
|
109
|
+
const [row] = await c
|
|
110
|
+
.get("db")
|
|
111
|
+
.update(people)
|
|
112
|
+
.set({ ...updates, updatedAt: new Date() })
|
|
113
|
+
.where(eq(people.id, c.req.param("id")))
|
|
114
|
+
.returning({ id: people.id });
|
|
115
|
+
if (!row)
|
|
116
|
+
return c.json({ error: "Person not found" }, 404);
|
|
117
|
+
return c.json({ success: true });
|
|
118
|
+
})
|
|
119
|
+
/**
|
|
120
|
+
* Plaintext document create — accepts `number` as cleartext, the
|
|
121
|
+
* route encrypts via the people KMS key. Mirrors the existing
|
|
122
|
+
* `POST /people/:id/documents` shape but spares clients from
|
|
123
|
+
* holding KMS material.
|
|
124
|
+
*/
|
|
125
|
+
.post("/people/:id/documents/from-plaintext", async (c) => {
|
|
126
|
+
const kms = await getCrmKms(c);
|
|
127
|
+
if (!kms)
|
|
128
|
+
return kmsRequired(c);
|
|
129
|
+
const body = await parseJsonBody(c, insertPersonDocumentFromPlaintextSchema);
|
|
130
|
+
const { number, ...rest } = body;
|
|
131
|
+
const numberEncrypted = number == null ? null : await encryptOptionalJsonEnvelope(kms, peopleKeyRef, { number });
|
|
132
|
+
const row = await crmService.createPersonDocument(c.get("db"), c.req.param("id"), {
|
|
133
|
+
...rest,
|
|
134
|
+
...(numberEncrypted !== undefined ? { numberEncrypted } : {}),
|
|
135
|
+
});
|
|
136
|
+
if (!row)
|
|
137
|
+
return c.json({ error: "Person not found" }, 404);
|
|
138
|
+
return c.json({ data: row }, 201);
|
|
139
|
+
})
|
|
140
|
+
/**
|
|
141
|
+
* Plaintext document update — same encryption convention as the
|
|
142
|
+
* create variant. Only fields explicitly provided are written;
|
|
143
|
+
* `number: null` clears the encrypted slot.
|
|
144
|
+
*/
|
|
145
|
+
.patch("/person-documents/:id/from-plaintext", async (c) => {
|
|
146
|
+
const kms = await getCrmKms(c);
|
|
147
|
+
if (!kms)
|
|
148
|
+
return kmsRequired(c);
|
|
149
|
+
const body = await parseJsonBody(c, updatePersonDocumentFromPlaintextSchema);
|
|
150
|
+
const { number, ...rest } = body;
|
|
151
|
+
const updateInput = { ...rest };
|
|
152
|
+
if (number !== undefined) {
|
|
153
|
+
updateInput.numberEncrypted =
|
|
154
|
+
number === null ? null : await encryptOptionalJsonEnvelope(kms, peopleKeyRef, { number });
|
|
155
|
+
}
|
|
156
|
+
const row = await crmService.updatePersonDocument(c.get("db"), c.req.param("id"), updateInput);
|
|
157
|
+
if (!row)
|
|
158
|
+
return c.json({ error: "Document not found" }, 404);
|
|
159
|
+
return c.json({ data: row });
|
|
160
|
+
});
|