@voyantjs/transactions 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAsCjE,KAAK,GAAG,GAAG;IACT,QAAQ,EAAE,OAAO,CAAC;QAChB,YAAY,EAAE,MAAM,CAAA;QACpB,WAAW,EAAE,MAAM,CAAA;QACnB,aAAa,EAAE,MAAM,CAAA;QACrB,cAAc,EAAE,MAAM,CAAA;QACtB,yBAAyB,EAAE,MAAM,CAAA;QACjC,eAAe,EAAE,MAAM,CAAA;QACvB,eAAe,EAAE,MAAM,CAAA;QACvB,gBAAgB,EAAE,MAAM,CAAA;QACxB,uBAAuB,EAAE,MAAM,CAAA;QAC/B,6BAA6B,EAAE,MAAM,CAAA;QACrC,UAAU,EAAE,MAAM,CAAA;QAClB,iBAAiB,EAAE,MAAM,CAAA;QACzB,qBAAqB,EAAE,MAAM,CAAA;QAC7B,iBAAiB,EAAE,MAAM,CAAA;QACzB,gBAAgB,EAAE,MAAM,CAAA;QACxB,qBAAqB,EAAE,MAAM,CAAA;QAC7B,2BAA2B,EAAE,MAAM,CAAA;KACpC,CAAC,CAAA;IACF,SAAS,EAAE;QACT,EAAE,EAAE,kBAAkB,CAAA;QACtB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,KAAK,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAA;QACrD,UAAU,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU,CAAA;QAC/C,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;QACxB,iBAAiB,CAAC,EAAE,OAAO,CAAA;QAC3B,uBAAuB,CAAC,EAAE,CAAC,IAAI,EAAE;YAC/B,EAAE,EAAE,kBAAkB,CAAA;YACtB,MAAM,CAAC,EAAE,MAAM,CAAA;YACf,KAAK,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAA;YACrD,UAAU,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU,CAAA;YAC/C,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;YACxB,iBAAiB,CAAC,EAAE,OAAO,CAAA;YAC3B,eAAe,EAAE,OAAO,GAAG,OAAO,CAAA;YAClC,aAAa,EAAE,MAAM,CAAA;YACrB,QAAQ,EAAE,MAAM,CAAA;YAChB,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAA;SACrC,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KACjC,CAAA;CACF,CAAA;AAsHD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAklB3B,CAAA;AAEJ,MAAM,MAAM,kBAAkB,GAAG,OAAO,kBAAkB,CAAA"}
package/dist/routes.js ADDED
@@ -0,0 +1,598 @@
1
+ import { Hono } from "hono";
2
+ import { createKmsProviderFromEnv } from "@voyantjs/utils";
3
+ import { createTransactionPiiService } from "./pii.js";
4
+ import { transactionPiiAccessLog } from "./schema.js";
5
+ import { transactionsService } from "./service.js";
6
+ import { insertOfferItemParticipantSchema, insertOfferItemSchema, insertOfferParticipantSchema, insertOfferSchema, insertOrderItemParticipantSchema, insertOrderItemSchema, insertOrderParticipantSchema, insertOrderSchema, insertOrderTermSchema, offerItemListQuerySchema, offerItemParticipantListQuerySchema, offerListQuerySchema, offerParticipantListQuerySchema, orderItemListQuerySchema, orderItemParticipantListQuerySchema, orderListQuerySchema, orderParticipantListQuerySchema, orderTermListQuerySchema, updateOfferItemParticipantSchema, updateOfferItemSchema, updateOfferParticipantSchema, updateOfferSchema, updateOrderItemParticipantSchema, updateOrderItemSchema, updateOrderParticipantSchema, updateOrderSchema, updateOrderTermSchema, } from "./validation.js";
7
+ function getRuntimeEnv(c) {
8
+ const processEnv = globalThis.process?.env ?? {};
9
+ return {
10
+ ...processEnv,
11
+ ...(c.env ?? {}),
12
+ };
13
+ }
14
+ function hasPiiScope(scopes, action) {
15
+ if (!scopes || scopes.length === 0) {
16
+ return false;
17
+ }
18
+ return (scopes.includes("*") ||
19
+ scopes.includes("transactions-pii:*") ||
20
+ scopes.includes(`transactions-pii:${action}`));
21
+ }
22
+ function hasParticipantIdentityInput(body) {
23
+ return "dateOfBirth" in body || "nationality" in body;
24
+ }
25
+ async function logTransactionPiiAccess(c, input) {
26
+ await c.get("db").insert(transactionPiiAccessLog).values({
27
+ participantKind: input.participantKind,
28
+ parentId: input.parentId ?? null,
29
+ participantId: input.participantId ?? null,
30
+ actorId: c.get("userId") ?? null,
31
+ actorType: c.get("actor") ?? null,
32
+ callerType: c.get("callerType") ?? null,
33
+ action: input.action,
34
+ outcome: input.outcome,
35
+ reason: input.reason ?? null,
36
+ metadata: input.metadata ?? null,
37
+ });
38
+ }
39
+ async function authorizeTransactionPiiAccess(c, input) {
40
+ if (c.get("isInternalRequest")) {
41
+ return { allowed: true };
42
+ }
43
+ const userId = c.get("userId");
44
+ if (!userId) {
45
+ await logTransactionPiiAccess(c, {
46
+ ...input,
47
+ outcome: "denied",
48
+ reason: "missing_user",
49
+ });
50
+ return { allowed: false, response: c.json({ error: "Unauthorized" }, 401) };
51
+ }
52
+ const customAuthorizer = c.get("authorizeTransactionPii");
53
+ if (customAuthorizer) {
54
+ const allowed = await customAuthorizer({
55
+ db: c.get("db"),
56
+ userId,
57
+ actor: c.get("actor"),
58
+ callerType: c.get("callerType"),
59
+ scopes: c.get("scopes"),
60
+ isInternalRequest: c.get("isInternalRequest"),
61
+ ...input,
62
+ });
63
+ if (!allowed) {
64
+ await logTransactionPiiAccess(c, {
65
+ ...input,
66
+ outcome: "denied",
67
+ reason: "custom_policy_denied",
68
+ });
69
+ return { allowed: false, response: c.json({ error: "Forbidden" }, 403) };
70
+ }
71
+ return { allowed: true };
72
+ }
73
+ const allowed = hasPiiScope(c.get("scopes"), input.action) || c.get("actor") === "staff";
74
+ if (!allowed) {
75
+ await logTransactionPiiAccess(c, {
76
+ ...input,
77
+ outcome: "denied",
78
+ reason: "insufficient_scope",
79
+ metadata: { actor: c.get("actor") ?? null },
80
+ });
81
+ return { allowed: false, response: c.json({ error: "Forbidden" }, 403) };
82
+ }
83
+ return { allowed: true };
84
+ }
85
+ export const transactionsRoutes = new Hono()
86
+ .get("/offers", async (c) => {
87
+ const query = offerListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
88
+ return c.json(await transactionsService.listOffers(c.get("db"), query));
89
+ })
90
+ .post("/offers", async (c) => {
91
+ return c.json({
92
+ data: await transactionsService.createOffer(c.get("db"), insertOfferSchema.parse(await c.req.json())),
93
+ }, 201);
94
+ })
95
+ .get("/offers/:id", async (c) => {
96
+ const row = await transactionsService.getOfferById(c.get("db"), c.req.param("id"));
97
+ if (!row)
98
+ return c.json({ error: "Offer not found" }, 404);
99
+ return c.json({ data: row });
100
+ })
101
+ .patch("/offers/:id", async (c) => {
102
+ const row = await transactionsService.updateOffer(c.get("db"), c.req.param("id"), updateOfferSchema.parse(await c.req.json()));
103
+ if (!row)
104
+ return c.json({ error: "Offer not found" }, 404);
105
+ return c.json({ data: row });
106
+ })
107
+ .delete("/offers/:id", async (c) => {
108
+ const row = await transactionsService.deleteOffer(c.get("db"), c.req.param("id"));
109
+ if (!row)
110
+ return c.json({ error: "Offer not found" }, 404);
111
+ return c.json({ success: true });
112
+ })
113
+ .get("/offer-participants", async (c) => {
114
+ const query = offerParticipantListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
115
+ return c.json(await transactionsService.listOfferParticipants(c.get("db"), query));
116
+ })
117
+ .post("/offer-participants", async (c) => {
118
+ const payload = insertOfferParticipantSchema.parse(await c.req.json());
119
+ return c.json({
120
+ data: await (async () => {
121
+ const row = await transactionsService.createOfferParticipant(c.get("db"), payload);
122
+ if (!row)
123
+ return row;
124
+ if (hasParticipantIdentityInput(payload)) {
125
+ const pii = createTransactionPiiService({
126
+ kms: createKmsProviderFromEnv(getRuntimeEnv(c)),
127
+ onAudit: async (event) => {
128
+ await logTransactionPiiAccess(c, {
129
+ participantKind: event.participantKind,
130
+ parentId: row.offerId,
131
+ participantId: event.participantId,
132
+ action: event.action,
133
+ outcome: "allowed",
134
+ });
135
+ },
136
+ });
137
+ await pii.upsertParticipantIdentity(c.get("db"), "offer", row.id, payload, c.get("userId"));
138
+ return transactionsService.getOfferParticipantById(c.get("db"), row.id);
139
+ }
140
+ return row;
141
+ })(),
142
+ }, 201);
143
+ })
144
+ .get("/offer-participants/:id", async (c) => {
145
+ const row = await transactionsService.getOfferParticipantById(c.get("db"), c.req.param("id"));
146
+ if (!row)
147
+ return c.json({ error: "Offer participant not found" }, 404);
148
+ return c.json({ data: row });
149
+ })
150
+ .patch("/offer-participants/:id", async (c) => {
151
+ const payload = updateOfferParticipantSchema.parse(await c.req.json());
152
+ const row = await transactionsService.updateOfferParticipant(c.get("db"), c.req.param("id"), payload);
153
+ if (!row)
154
+ return c.json({ error: "Offer participant not found" }, 404);
155
+ if (hasParticipantIdentityInput(payload)) {
156
+ const pii = createTransactionPiiService({
157
+ kms: createKmsProviderFromEnv(getRuntimeEnv(c)),
158
+ onAudit: async (event) => {
159
+ await logTransactionPiiAccess(c, {
160
+ participantKind: event.participantKind,
161
+ parentId: row.offerId,
162
+ participantId: event.participantId,
163
+ action: event.action,
164
+ outcome: "allowed",
165
+ });
166
+ },
167
+ });
168
+ await pii.upsertParticipantIdentity(c.get("db"), "offer", row.id, payload, c.get("userId"));
169
+ return c.json({ data: await transactionsService.getOfferParticipantById(c.get("db"), row.id) });
170
+ }
171
+ return c.json({ data: row });
172
+ })
173
+ .get("/offer-participants/:id/travel-details", async (c) => {
174
+ const participant = await transactionsService.getOfferParticipantById(c.get("db"), c.req.param("id"));
175
+ if (!participant)
176
+ return c.json({ error: "Offer participant not found" }, 404);
177
+ const auth = await authorizeTransactionPiiAccess(c, {
178
+ participantKind: "offer",
179
+ participantId: participant.id,
180
+ parentId: participant.offerId,
181
+ action: "read",
182
+ });
183
+ if (!auth.allowed)
184
+ return auth.response;
185
+ const pii = createTransactionPiiService({
186
+ kms: createKmsProviderFromEnv(getRuntimeEnv(c)),
187
+ onAudit: async (event) => {
188
+ await logTransactionPiiAccess(c, {
189
+ participantKind: event.participantKind,
190
+ parentId: participant.offerId,
191
+ participantId: event.participantId,
192
+ action: event.action,
193
+ outcome: "allowed",
194
+ });
195
+ },
196
+ });
197
+ const row = await pii.getParticipantIdentity(c.get("db"), "offer", participant.id, c.get("userId"));
198
+ if (!row) {
199
+ await logTransactionPiiAccess(c, {
200
+ participantKind: "offer",
201
+ parentId: participant.offerId,
202
+ participantId: participant.id,
203
+ action: "read",
204
+ outcome: "denied",
205
+ reason: "travel_details_not_found",
206
+ });
207
+ return c.json({ error: "Offer participant travel details not found" }, 404);
208
+ }
209
+ return c.json({ data: row });
210
+ })
211
+ .patch("/offer-participants/:id/travel-details", async (c) => {
212
+ const participant = await transactionsService.getOfferParticipantById(c.get("db"), c.req.param("id"));
213
+ if (!participant)
214
+ return c.json({ error: "Offer participant not found" }, 404);
215
+ const auth = await authorizeTransactionPiiAccess(c, {
216
+ participantKind: "offer",
217
+ participantId: participant.id,
218
+ parentId: participant.offerId,
219
+ action: "update",
220
+ });
221
+ if (!auth.allowed)
222
+ return auth.response;
223
+ const pii = createTransactionPiiService({
224
+ kms: createKmsProviderFromEnv(getRuntimeEnv(c)),
225
+ onAudit: async (event) => {
226
+ await logTransactionPiiAccess(c, {
227
+ participantKind: event.participantKind,
228
+ parentId: participant.offerId,
229
+ participantId: event.participantId,
230
+ action: event.action,
231
+ outcome: "allowed",
232
+ });
233
+ },
234
+ });
235
+ const row = await pii.upsertParticipantIdentity(c.get("db"), "offer", participant.id, updateOfferParticipantSchema.parse(await c.req.json()), c.get("userId"));
236
+ if (!row)
237
+ return c.json({ error: "Offer participant not found" }, 404);
238
+ return c.json({ data: row });
239
+ })
240
+ .delete("/offer-participants/:id/travel-details", async (c) => {
241
+ const participant = await transactionsService.getOfferParticipantById(c.get("db"), c.req.param("id"));
242
+ if (!participant)
243
+ return c.json({ error: "Offer participant not found" }, 404);
244
+ const auth = await authorizeTransactionPiiAccess(c, {
245
+ participantKind: "offer",
246
+ participantId: participant.id,
247
+ parentId: participant.offerId,
248
+ action: "delete",
249
+ });
250
+ if (!auth.allowed)
251
+ return auth.response;
252
+ const pii = createTransactionPiiService({
253
+ kms: createKmsProviderFromEnv(getRuntimeEnv(c)),
254
+ onAudit: async (event) => {
255
+ await logTransactionPiiAccess(c, {
256
+ participantKind: event.participantKind,
257
+ parentId: participant.offerId,
258
+ participantId: event.participantId,
259
+ action: event.action,
260
+ outcome: "allowed",
261
+ });
262
+ },
263
+ });
264
+ const row = await pii.deleteParticipantIdentity(c.get("db"), "offer", participant.id, c.get("userId"));
265
+ if (!row)
266
+ return c.json({ error: "Offer participant travel details not found" }, 404);
267
+ return c.json({ success: true });
268
+ })
269
+ .delete("/offer-participants/:id", async (c) => {
270
+ const row = await transactionsService.deleteOfferParticipant(c.get("db"), c.req.param("id"));
271
+ if (!row)
272
+ return c.json({ error: "Offer participant not found" }, 404);
273
+ return c.json({ success: true });
274
+ })
275
+ .get("/offer-items", async (c) => {
276
+ const query = offerItemListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
277
+ return c.json(await transactionsService.listOfferItems(c.get("db"), query));
278
+ })
279
+ .post("/offer-items", async (c) => {
280
+ return c.json({
281
+ data: await transactionsService.createOfferItem(c.get("db"), insertOfferItemSchema.parse(await c.req.json())),
282
+ }, 201);
283
+ })
284
+ .get("/offer-items/:id", async (c) => {
285
+ const row = await transactionsService.getOfferItemById(c.get("db"), c.req.param("id"));
286
+ if (!row)
287
+ return c.json({ error: "Offer item not found" }, 404);
288
+ return c.json({ data: row });
289
+ })
290
+ .patch("/offer-items/:id", async (c) => {
291
+ const row = await transactionsService.updateOfferItem(c.get("db"), c.req.param("id"), updateOfferItemSchema.parse(await c.req.json()));
292
+ if (!row)
293
+ return c.json({ error: "Offer item not found" }, 404);
294
+ return c.json({ data: row });
295
+ })
296
+ .delete("/offer-items/:id", async (c) => {
297
+ const row = await transactionsService.deleteOfferItem(c.get("db"), c.req.param("id"));
298
+ if (!row)
299
+ return c.json({ error: "Offer item not found" }, 404);
300
+ return c.json({ success: true });
301
+ })
302
+ .get("/offer-item-participants", async (c) => {
303
+ const query = offerItemParticipantListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
304
+ return c.json(await transactionsService.listOfferItemParticipants(c.get("db"), query));
305
+ })
306
+ .post("/offer-item-participants", async (c) => {
307
+ return c.json({
308
+ data: await transactionsService.createOfferItemParticipant(c.get("db"), insertOfferItemParticipantSchema.parse(await c.req.json())),
309
+ }, 201);
310
+ })
311
+ .get("/offer-item-participants/:id", async (c) => {
312
+ const row = await transactionsService.getOfferItemParticipantById(c.get("db"), c.req.param("id"));
313
+ if (!row)
314
+ return c.json({ error: "Offer item participant not found" }, 404);
315
+ return c.json({ data: row });
316
+ })
317
+ .patch("/offer-item-participants/:id", async (c) => {
318
+ const row = await transactionsService.updateOfferItemParticipant(c.get("db"), c.req.param("id"), updateOfferItemParticipantSchema.parse(await c.req.json()));
319
+ if (!row)
320
+ return c.json({ error: "Offer item participant not found" }, 404);
321
+ return c.json({ data: row });
322
+ })
323
+ .delete("/offer-item-participants/:id", async (c) => {
324
+ const row = await transactionsService.deleteOfferItemParticipant(c.get("db"), c.req.param("id"));
325
+ if (!row)
326
+ return c.json({ error: "Offer item participant not found" }, 404);
327
+ return c.json({ success: true });
328
+ })
329
+ .get("/orders", async (c) => {
330
+ const query = orderListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
331
+ return c.json(await transactionsService.listOrders(c.get("db"), query));
332
+ })
333
+ .post("/orders", async (c) => {
334
+ return c.json({
335
+ data: await transactionsService.createOrder(c.get("db"), insertOrderSchema.parse(await c.req.json())),
336
+ }, 201);
337
+ })
338
+ .get("/orders/:id", async (c) => {
339
+ const row = await transactionsService.getOrderById(c.get("db"), c.req.param("id"));
340
+ if (!row)
341
+ return c.json({ error: "Order not found" }, 404);
342
+ return c.json({ data: row });
343
+ })
344
+ .patch("/orders/:id", async (c) => {
345
+ const row = await transactionsService.updateOrder(c.get("db"), c.req.param("id"), updateOrderSchema.parse(await c.req.json()));
346
+ if (!row)
347
+ return c.json({ error: "Order not found" }, 404);
348
+ return c.json({ data: row });
349
+ })
350
+ .delete("/orders/:id", async (c) => {
351
+ const row = await transactionsService.deleteOrder(c.get("db"), c.req.param("id"));
352
+ if (!row)
353
+ return c.json({ error: "Order not found" }, 404);
354
+ return c.json({ success: true });
355
+ })
356
+ .get("/order-participants", async (c) => {
357
+ const query = orderParticipantListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
358
+ return c.json(await transactionsService.listOrderParticipants(c.get("db"), query));
359
+ })
360
+ .post("/order-participants", async (c) => {
361
+ const payload = insertOrderParticipantSchema.parse(await c.req.json());
362
+ return c.json({
363
+ data: await (async () => {
364
+ const row = await transactionsService.createOrderParticipant(c.get("db"), payload);
365
+ if (!row)
366
+ return row;
367
+ if (hasParticipantIdentityInput(payload)) {
368
+ const pii = createTransactionPiiService({
369
+ kms: createKmsProviderFromEnv(getRuntimeEnv(c)),
370
+ onAudit: async (event) => {
371
+ await logTransactionPiiAccess(c, {
372
+ participantKind: event.participantKind,
373
+ parentId: row.orderId,
374
+ participantId: event.participantId,
375
+ action: event.action,
376
+ outcome: "allowed",
377
+ });
378
+ },
379
+ });
380
+ await pii.upsertParticipantIdentity(c.get("db"), "order", row.id, payload, c.get("userId"));
381
+ return transactionsService.getOrderParticipantById(c.get("db"), row.id);
382
+ }
383
+ return row;
384
+ })(),
385
+ }, 201);
386
+ })
387
+ .get("/order-participants/:id", async (c) => {
388
+ const row = await transactionsService.getOrderParticipantById(c.get("db"), c.req.param("id"));
389
+ if (!row)
390
+ return c.json({ error: "Order participant not found" }, 404);
391
+ return c.json({ data: row });
392
+ })
393
+ .patch("/order-participants/:id", async (c) => {
394
+ const payload = updateOrderParticipantSchema.parse(await c.req.json());
395
+ const row = await transactionsService.updateOrderParticipant(c.get("db"), c.req.param("id"), payload);
396
+ if (!row)
397
+ return c.json({ error: "Order participant not found" }, 404);
398
+ if (hasParticipantIdentityInput(payload)) {
399
+ const pii = createTransactionPiiService({
400
+ kms: createKmsProviderFromEnv(getRuntimeEnv(c)),
401
+ onAudit: async (event) => {
402
+ await logTransactionPiiAccess(c, {
403
+ participantKind: event.participantKind,
404
+ parentId: row.orderId,
405
+ participantId: event.participantId,
406
+ action: event.action,
407
+ outcome: "allowed",
408
+ });
409
+ },
410
+ });
411
+ await pii.upsertParticipantIdentity(c.get("db"), "order", row.id, payload, c.get("userId"));
412
+ return c.json({ data: await transactionsService.getOrderParticipantById(c.get("db"), row.id) });
413
+ }
414
+ return c.json({ data: row });
415
+ })
416
+ .get("/order-participants/:id/travel-details", async (c) => {
417
+ const participant = await transactionsService.getOrderParticipantById(c.get("db"), c.req.param("id"));
418
+ if (!participant)
419
+ return c.json({ error: "Order participant not found" }, 404);
420
+ const auth = await authorizeTransactionPiiAccess(c, {
421
+ participantKind: "order",
422
+ participantId: participant.id,
423
+ parentId: participant.orderId,
424
+ action: "read",
425
+ });
426
+ if (!auth.allowed)
427
+ return auth.response;
428
+ const pii = createTransactionPiiService({
429
+ kms: createKmsProviderFromEnv(getRuntimeEnv(c)),
430
+ onAudit: async (event) => {
431
+ await logTransactionPiiAccess(c, {
432
+ participantKind: event.participantKind,
433
+ parentId: participant.orderId,
434
+ participantId: event.participantId,
435
+ action: event.action,
436
+ outcome: "allowed",
437
+ });
438
+ },
439
+ });
440
+ const row = await pii.getParticipantIdentity(c.get("db"), "order", participant.id, c.get("userId"));
441
+ if (!row) {
442
+ await logTransactionPiiAccess(c, {
443
+ participantKind: "order",
444
+ parentId: participant.orderId,
445
+ participantId: participant.id,
446
+ action: "read",
447
+ outcome: "denied",
448
+ reason: "travel_details_not_found",
449
+ });
450
+ return c.json({ error: "Order participant travel details not found" }, 404);
451
+ }
452
+ return c.json({ data: row });
453
+ })
454
+ .patch("/order-participants/:id/travel-details", async (c) => {
455
+ const participant = await transactionsService.getOrderParticipantById(c.get("db"), c.req.param("id"));
456
+ if (!participant)
457
+ return c.json({ error: "Order participant not found" }, 404);
458
+ const auth = await authorizeTransactionPiiAccess(c, {
459
+ participantKind: "order",
460
+ participantId: participant.id,
461
+ parentId: participant.orderId,
462
+ action: "update",
463
+ });
464
+ if (!auth.allowed)
465
+ return auth.response;
466
+ const pii = createTransactionPiiService({
467
+ kms: createKmsProviderFromEnv(getRuntimeEnv(c)),
468
+ onAudit: async (event) => {
469
+ await logTransactionPiiAccess(c, {
470
+ participantKind: event.participantKind,
471
+ parentId: participant.orderId,
472
+ participantId: event.participantId,
473
+ action: event.action,
474
+ outcome: "allowed",
475
+ });
476
+ },
477
+ });
478
+ const row = await pii.upsertParticipantIdentity(c.get("db"), "order", participant.id, updateOrderParticipantSchema.parse(await c.req.json()), c.get("userId"));
479
+ if (!row)
480
+ return c.json({ error: "Order participant not found" }, 404);
481
+ return c.json({ data: row });
482
+ })
483
+ .delete("/order-participants/:id/travel-details", async (c) => {
484
+ const participant = await transactionsService.getOrderParticipantById(c.get("db"), c.req.param("id"));
485
+ if (!participant)
486
+ return c.json({ error: "Order participant not found" }, 404);
487
+ const auth = await authorizeTransactionPiiAccess(c, {
488
+ participantKind: "order",
489
+ participantId: participant.id,
490
+ parentId: participant.orderId,
491
+ action: "delete",
492
+ });
493
+ if (!auth.allowed)
494
+ return auth.response;
495
+ const pii = createTransactionPiiService({
496
+ kms: createKmsProviderFromEnv(getRuntimeEnv(c)),
497
+ onAudit: async (event) => {
498
+ await logTransactionPiiAccess(c, {
499
+ participantKind: event.participantKind,
500
+ parentId: participant.orderId,
501
+ participantId: event.participantId,
502
+ action: event.action,
503
+ outcome: "allowed",
504
+ });
505
+ },
506
+ });
507
+ const row = await pii.deleteParticipantIdentity(c.get("db"), "order", participant.id, c.get("userId"));
508
+ if (!row)
509
+ return c.json({ error: "Order participant travel details not found" }, 404);
510
+ return c.json({ success: true });
511
+ })
512
+ .delete("/order-participants/:id", async (c) => {
513
+ const row = await transactionsService.deleteOrderParticipant(c.get("db"), c.req.param("id"));
514
+ if (!row)
515
+ return c.json({ error: "Order participant not found" }, 404);
516
+ return c.json({ success: true });
517
+ })
518
+ .get("/order-items", async (c) => {
519
+ const query = orderItemListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
520
+ return c.json(await transactionsService.listOrderItems(c.get("db"), query));
521
+ })
522
+ .post("/order-items", async (c) => {
523
+ return c.json({
524
+ data: await transactionsService.createOrderItem(c.get("db"), insertOrderItemSchema.parse(await c.req.json())),
525
+ }, 201);
526
+ })
527
+ .get("/order-items/:id", async (c) => {
528
+ const row = await transactionsService.getOrderItemById(c.get("db"), c.req.param("id"));
529
+ if (!row)
530
+ return c.json({ error: "Order item not found" }, 404);
531
+ return c.json({ data: row });
532
+ })
533
+ .patch("/order-items/:id", async (c) => {
534
+ const row = await transactionsService.updateOrderItem(c.get("db"), c.req.param("id"), updateOrderItemSchema.parse(await c.req.json()));
535
+ if (!row)
536
+ return c.json({ error: "Order item not found" }, 404);
537
+ return c.json({ data: row });
538
+ })
539
+ .delete("/order-items/:id", async (c) => {
540
+ const row = await transactionsService.deleteOrderItem(c.get("db"), c.req.param("id"));
541
+ if (!row)
542
+ return c.json({ error: "Order item not found" }, 404);
543
+ return c.json({ success: true });
544
+ })
545
+ .get("/order-item-participants", async (c) => {
546
+ const query = orderItemParticipantListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
547
+ return c.json(await transactionsService.listOrderItemParticipants(c.get("db"), query));
548
+ })
549
+ .post("/order-item-participants", async (c) => {
550
+ return c.json({
551
+ data: await transactionsService.createOrderItemParticipant(c.get("db"), insertOrderItemParticipantSchema.parse(await c.req.json())),
552
+ }, 201);
553
+ })
554
+ .get("/order-item-participants/:id", async (c) => {
555
+ const row = await transactionsService.getOrderItemParticipantById(c.get("db"), c.req.param("id"));
556
+ if (!row)
557
+ return c.json({ error: "Order item participant not found" }, 404);
558
+ return c.json({ data: row });
559
+ })
560
+ .patch("/order-item-participants/:id", async (c) => {
561
+ const row = await transactionsService.updateOrderItemParticipant(c.get("db"), c.req.param("id"), updateOrderItemParticipantSchema.parse(await c.req.json()));
562
+ if (!row)
563
+ return c.json({ error: "Order item participant not found" }, 404);
564
+ return c.json({ data: row });
565
+ })
566
+ .delete("/order-item-participants/:id", async (c) => {
567
+ const row = await transactionsService.deleteOrderItemParticipant(c.get("db"), c.req.param("id"));
568
+ if (!row)
569
+ return c.json({ error: "Order item participant not found" }, 404);
570
+ return c.json({ success: true });
571
+ })
572
+ .get("/order-terms", async (c) => {
573
+ const query = orderTermListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
574
+ return c.json(await transactionsService.listOrderTerms(c.get("db"), query));
575
+ })
576
+ .post("/order-terms", async (c) => {
577
+ return c.json({
578
+ data: await transactionsService.createOrderTerm(c.get("db"), insertOrderTermSchema.parse(await c.req.json())),
579
+ }, 201);
580
+ })
581
+ .get("/order-terms/:id", async (c) => {
582
+ const row = await transactionsService.getOrderTermById(c.get("db"), c.req.param("id"));
583
+ if (!row)
584
+ return c.json({ error: "Order term not found" }, 404);
585
+ return c.json({ data: row });
586
+ })
587
+ .patch("/order-terms/:id", async (c) => {
588
+ const row = await transactionsService.updateOrderTerm(c.get("db"), c.req.param("id"), updateOrderTermSchema.parse(await c.req.json()));
589
+ if (!row)
590
+ return c.json({ error: "Order term not found" }, 404);
591
+ return c.json({ data: row });
592
+ })
593
+ .delete("/order-terms/:id", async (c) => {
594
+ const row = await transactionsService.deleteOrderTerm(c.get("db"), c.req.param("id"));
595
+ if (!row)
596
+ return c.json({ error: "Order term not found" }, 404);
597
+ return c.json({ success: true });
598
+ });
@@ -0,0 +1,28 @@
1
+ import type { KmsEnvelope } from "@voyantjs/db/schema/iam/kms";
2
+ import { z } from "zod";
3
+ export declare const transactionParticipantIdentitySchema: z.ZodObject<{
4
+ dateOfBirth: z.ZodNullable<z.ZodOptional<z.ZodString>>;
5
+ nationality: z.ZodNullable<z.ZodOptional<z.ZodString>>;
6
+ }, z.core.$strip>;
7
+ export declare const decryptedTransactionParticipantIdentitySchema: z.ZodObject<{
8
+ participantId: z.ZodString;
9
+ participantKind: z.ZodEnum<{
10
+ offer: "offer";
11
+ order: "order";
12
+ }>;
13
+ dateOfBirth: z.ZodNullable<z.ZodString>;
14
+ nationality: z.ZodNullable<z.ZodString>;
15
+ createdAt: z.ZodDate;
16
+ updatedAt: z.ZodDate;
17
+ }, z.core.$strip>;
18
+ export declare const transactionParticipantIdentityEnvelopeSchema: z.ZodObject<{
19
+ identityEncrypted: z.ZodNullable<z.ZodOptional<z.ZodNullable<z.ZodObject<{
20
+ enc: z.ZodString;
21
+ }, z.core.$strip>>>>;
22
+ }, z.core.$strip>;
23
+ export type TransactionParticipantIdentity = z.infer<typeof transactionParticipantIdentitySchema>;
24
+ export type TransactionParticipantIdentityEnvelope = {
25
+ identityEncrypted?: KmsEnvelope | null;
26
+ };
27
+ export type DecryptedTransactionParticipantIdentity = z.infer<typeof decryptedTransactionParticipantIdentitySchema>;
28
+ //# sourceMappingURL=participant-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"participant-identity.d.ts","sourceRoot":"","sources":["../../src/schema/participant-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAA;AAE9D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,oCAAoC;;;iBAG/C,CAAA;AAEF,eAAO,MAAM,6CAA6C;;;;;;;;;;iBAOxD,CAAA;AAEF,eAAO,MAAM,4CAA4C;;;;iBAEvD,CAAA;AAEF,MAAM,MAAM,8BAA8B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oCAAoC,CAAC,CAAA;AACjG,MAAM,MAAM,sCAAsC,GAAG;IACnD,iBAAiB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;CACvC,CAAA;AACD,MAAM,MAAM,uCAAuC,GAAG,CAAC,CAAC,KAAK,CAC3D,OAAO,6CAA6C,CACrD,CAAA"}
@@ -0,0 +1,17 @@
1
+ import { kmsEnvelopeSchema } from "@voyantjs/db/schema/iam/kms";
2
+ import { z } from "zod";
3
+ export const transactionParticipantIdentitySchema = z.object({
4
+ dateOfBirth: z.string().optional().nullable(),
5
+ nationality: z.string().max(2).optional().nullable(),
6
+ });
7
+ export const decryptedTransactionParticipantIdentitySchema = z.object({
8
+ participantId: z.string(),
9
+ participantKind: z.enum(["offer", "order"]),
10
+ dateOfBirth: z.string().nullable(),
11
+ nationality: z.string().nullable(),
12
+ createdAt: z.date(),
13
+ updatedAt: z.date(),
14
+ });
15
+ export const transactionParticipantIdentityEnvelopeSchema = z.object({
16
+ identityEncrypted: kmsEnvelopeSchema.optional().nullable(),
17
+ });