@voyantjs/bookings 0.3.0 → 0.3.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.
@@ -0,0 +1,580 @@
1
+ import { type Env } from "./routes-shared.js";
2
+ export declare const publicBookingRoutes: import("hono/hono-base").HonoBase<Env, {
3
+ "/sessions": {
4
+ $post: {
5
+ input: {};
6
+ output: {
7
+ error: string;
8
+ };
9
+ outputFormat: "json";
10
+ status: 404;
11
+ } | {
12
+ input: {};
13
+ output: {
14
+ error: string;
15
+ };
16
+ outputFormat: "json";
17
+ status: 409;
18
+ } | {
19
+ input: {};
20
+ output: {
21
+ data: import("hono/utils/types").JSONValue;
22
+ };
23
+ outputFormat: "json";
24
+ status: 201;
25
+ };
26
+ };
27
+ } & {
28
+ "/sessions/:sessionId": {
29
+ $get: {
30
+ input: {
31
+ param: {
32
+ sessionId: string;
33
+ };
34
+ };
35
+ output: {
36
+ error: string;
37
+ };
38
+ outputFormat: "json";
39
+ status: 404;
40
+ } | {
41
+ input: {
42
+ param: {
43
+ sessionId: string;
44
+ };
45
+ };
46
+ output: {
47
+ data: {
48
+ sessionId: string;
49
+ bookingNumber: string;
50
+ status: "cancelled" | "draft" | "on_hold" | "confirmed" | "in_progress" | "completed" | "expired";
51
+ externalBookingRef: string | null;
52
+ communicationLanguage: string | null;
53
+ sellCurrency: string;
54
+ sellAmountCents: number | null;
55
+ startDate: string | null;
56
+ endDate: string | null;
57
+ pax: number | null;
58
+ holdExpiresAt: string | null;
59
+ confirmedAt: string | null;
60
+ expiredAt: string | null;
61
+ cancelledAt: string | null;
62
+ completedAt: string | null;
63
+ participants: {
64
+ id: string;
65
+ participantType: "staff" | "other" | "traveler" | "booker" | "contact" | "occupant";
66
+ travelerCategory: "other" | "adult" | "child" | "infant" | "senior" | null;
67
+ firstName: string;
68
+ lastName: string;
69
+ email: string | null;
70
+ phone: string | null;
71
+ preferredLanguage: string | null;
72
+ accessibilityNeeds: string | null;
73
+ specialRequests: string | null;
74
+ isPrimary: boolean;
75
+ notes: string | null;
76
+ }[];
77
+ items: {
78
+ id: string;
79
+ title: string;
80
+ description: string | null;
81
+ itemType: "other" | "unit" | "extra" | "service" | "fee" | "tax" | "discount" | "adjustment" | "accommodation" | "transport";
82
+ status: "cancelled" | "draft" | "on_hold" | "confirmed" | "expired" | "fulfilled";
83
+ serviceDate: string | null;
84
+ startsAt: string | null;
85
+ endsAt: string | null;
86
+ quantity: number;
87
+ sellCurrency: string;
88
+ unitSellAmountCents: number | null;
89
+ totalSellAmountCents: number | null;
90
+ costCurrency: string | null;
91
+ unitCostAmountCents: number | null;
92
+ totalCostAmountCents: number | null;
93
+ notes: string | null;
94
+ productId: string | null;
95
+ optionId: string | null;
96
+ optionUnitId: string | null;
97
+ pricingCategoryId: string | null;
98
+ participantLinks: {
99
+ id: string;
100
+ participantId: string;
101
+ role: string;
102
+ isPrimary: boolean;
103
+ }[];
104
+ }[];
105
+ allocations: {
106
+ id: string;
107
+ bookingItemId: string;
108
+ productId: string | null;
109
+ optionId: string | null;
110
+ optionUnitId: string | null;
111
+ pricingCategoryId: string | null;
112
+ availabilitySlotId: string | null;
113
+ quantity: number;
114
+ allocationType: "unit" | "pickup" | "resource";
115
+ status: "cancelled" | "confirmed" | "expired" | "fulfilled" | "held" | "released";
116
+ holdExpiresAt: string | null;
117
+ confirmedAt: string | null;
118
+ releasedAt: string | null;
119
+ }[];
120
+ checklist: {
121
+ hasParticipants: boolean;
122
+ hasTraveler: boolean;
123
+ hasPrimaryParticipant: boolean;
124
+ hasItems: boolean;
125
+ hasAllocations: boolean;
126
+ readyForConfirmation: boolean;
127
+ };
128
+ state: {
129
+ sessionId: string;
130
+ stateKey: "wizard";
131
+ currentStep: string | null;
132
+ completedSteps: string[];
133
+ payload: {
134
+ [x: string]: import("hono/utils/types").JSONValue;
135
+ };
136
+ version: number;
137
+ createdAt: string;
138
+ updatedAt: string;
139
+ } | null;
140
+ };
141
+ };
142
+ outputFormat: "json";
143
+ status: import("hono/utils/http-status").ContentfulStatusCode;
144
+ };
145
+ };
146
+ } & {
147
+ "/sessions/:sessionId": {
148
+ $patch: {
149
+ input: {
150
+ param: {
151
+ sessionId: string;
152
+ };
153
+ };
154
+ output: {
155
+ error: string;
156
+ };
157
+ outputFormat: "json";
158
+ status: 404;
159
+ } | {
160
+ input: {
161
+ param: {
162
+ sessionId: string;
163
+ };
164
+ };
165
+ output: {
166
+ error: string;
167
+ };
168
+ outputFormat: "json";
169
+ status: 409;
170
+ } | {
171
+ input: {
172
+ param: {
173
+ sessionId: string;
174
+ };
175
+ };
176
+ output: {
177
+ data: import("hono/utils/types").JSONValue;
178
+ };
179
+ outputFormat: "json";
180
+ status: import("hono/utils/http-status").ContentfulStatusCode;
181
+ };
182
+ };
183
+ } & {
184
+ "/sessions/:sessionId/state": {
185
+ $get: {
186
+ input: {
187
+ param: {
188
+ sessionId: string;
189
+ };
190
+ };
191
+ output: {
192
+ error: string;
193
+ };
194
+ outputFormat: "json";
195
+ status: 404;
196
+ } | {
197
+ input: {
198
+ param: {
199
+ sessionId: string;
200
+ };
201
+ };
202
+ output: {
203
+ data: {
204
+ sessionId: string;
205
+ stateKey: "wizard";
206
+ currentStep: string | null;
207
+ completedSteps: string[];
208
+ payload: {
209
+ [x: string]: import("hono/utils/types").JSONValue;
210
+ };
211
+ version: number;
212
+ createdAt: string;
213
+ updatedAt: string;
214
+ };
215
+ };
216
+ outputFormat: "json";
217
+ status: import("hono/utils/http-status").ContentfulStatusCode;
218
+ };
219
+ };
220
+ } & {
221
+ "/sessions/:sessionId/state": {
222
+ $put: {
223
+ input: {
224
+ param: {
225
+ sessionId: string;
226
+ };
227
+ };
228
+ output: {
229
+ error: string;
230
+ };
231
+ outputFormat: "json";
232
+ status: 404;
233
+ } | {
234
+ input: {
235
+ param: {
236
+ sessionId: string;
237
+ };
238
+ };
239
+ output: {
240
+ data: {
241
+ sessionId: string;
242
+ stateKey: "wizard";
243
+ currentStep: string | null;
244
+ completedSteps: string[];
245
+ payload: {
246
+ [x: string]: import("hono/utils/types").JSONValue;
247
+ };
248
+ version: number;
249
+ createdAt: string;
250
+ updatedAt: string;
251
+ } | null;
252
+ };
253
+ outputFormat: "json";
254
+ status: import("hono/utils/http-status").ContentfulStatusCode;
255
+ };
256
+ };
257
+ } & {
258
+ "/sessions/:sessionId/reprice": {
259
+ $post: {
260
+ input: {
261
+ param: {
262
+ sessionId: string;
263
+ };
264
+ };
265
+ output: {
266
+ error: string;
267
+ };
268
+ outputFormat: "json";
269
+ status: 404;
270
+ } | {
271
+ input: {
272
+ param: {
273
+ sessionId: string;
274
+ };
275
+ };
276
+ output: {
277
+ error: string;
278
+ };
279
+ outputFormat: "json";
280
+ status: 400;
281
+ } | {
282
+ input: {
283
+ param: {
284
+ sessionId: string;
285
+ };
286
+ };
287
+ output: {
288
+ error: string;
289
+ };
290
+ outputFormat: "json";
291
+ status: 409;
292
+ } | {
293
+ input: {
294
+ param: {
295
+ sessionId: string;
296
+ };
297
+ };
298
+ output: {
299
+ data: {
300
+ pricing: {
301
+ sessionId: string;
302
+ catalogId: string | null;
303
+ currencyCode: string;
304
+ totalSellAmountCents: number;
305
+ items: {
306
+ itemId: string;
307
+ title: string;
308
+ productId: string | null;
309
+ optionId: string | null;
310
+ optionUnitId: string | null;
311
+ optionUnitName: string | null;
312
+ optionUnitType: string | null;
313
+ pricingCategoryId: string | null;
314
+ quantity: number;
315
+ pricingMode: string;
316
+ unitSellAmountCents: number | null;
317
+ totalSellAmountCents: number | null;
318
+ warnings: string[];
319
+ }[];
320
+ warnings: string[];
321
+ appliedToSession: boolean;
322
+ };
323
+ session: {
324
+ sessionId: string;
325
+ bookingNumber: string;
326
+ status: "cancelled" | "draft" | "on_hold" | "confirmed" | "in_progress" | "completed" | "expired";
327
+ externalBookingRef: string | null;
328
+ communicationLanguage: string | null;
329
+ sellCurrency: string;
330
+ sellAmountCents: number | null;
331
+ startDate: string | null;
332
+ endDate: string | null;
333
+ pax: number | null;
334
+ holdExpiresAt: string | null;
335
+ confirmedAt: string | null;
336
+ expiredAt: string | null;
337
+ cancelledAt: string | null;
338
+ completedAt: string | null;
339
+ participants: {
340
+ id: string;
341
+ participantType: "staff" | "other" | "traveler" | "booker" | "contact" | "occupant";
342
+ travelerCategory: "other" | "adult" | "child" | "infant" | "senior" | null;
343
+ firstName: string;
344
+ lastName: string;
345
+ email: string | null;
346
+ phone: string | null;
347
+ preferredLanguage: string | null;
348
+ accessibilityNeeds: string | null;
349
+ specialRequests: string | null;
350
+ isPrimary: boolean;
351
+ notes: string | null;
352
+ }[];
353
+ items: {
354
+ id: string;
355
+ title: string;
356
+ description: string | null;
357
+ itemType: "other" | "unit" | "extra" | "service" | "fee" | "tax" | "discount" | "adjustment" | "accommodation" | "transport";
358
+ status: "cancelled" | "draft" | "on_hold" | "confirmed" | "expired" | "fulfilled";
359
+ serviceDate: string | null;
360
+ startsAt: string | null;
361
+ endsAt: string | null;
362
+ quantity: number;
363
+ sellCurrency: string;
364
+ unitSellAmountCents: number | null;
365
+ totalSellAmountCents: number | null;
366
+ costCurrency: string | null;
367
+ unitCostAmountCents: number | null;
368
+ totalCostAmountCents: number | null;
369
+ notes: string | null;
370
+ productId: string | null;
371
+ optionId: string | null;
372
+ optionUnitId: string | null;
373
+ pricingCategoryId: string | null;
374
+ participantLinks: {
375
+ id: string;
376
+ participantId: string;
377
+ role: string;
378
+ isPrimary: boolean;
379
+ }[];
380
+ }[];
381
+ allocations: {
382
+ id: string;
383
+ bookingItemId: string;
384
+ productId: string | null;
385
+ optionId: string | null;
386
+ optionUnitId: string | null;
387
+ pricingCategoryId: string | null;
388
+ availabilitySlotId: string | null;
389
+ quantity: number;
390
+ allocationType: "unit" | "pickup" | "resource";
391
+ status: "cancelled" | "confirmed" | "expired" | "fulfilled" | "held" | "released";
392
+ holdExpiresAt: string | null;
393
+ confirmedAt: string | null;
394
+ releasedAt: string | null;
395
+ }[];
396
+ checklist: {
397
+ hasParticipants: boolean;
398
+ hasTraveler: boolean;
399
+ hasPrimaryParticipant: boolean;
400
+ hasItems: boolean;
401
+ hasAllocations: boolean;
402
+ readyForConfirmation: boolean;
403
+ };
404
+ state: {
405
+ sessionId: string;
406
+ stateKey: "wizard";
407
+ currentStep: string | null;
408
+ completedSteps: string[];
409
+ payload: {
410
+ [x: string]: import("hono/utils/types").JSONValue;
411
+ };
412
+ version: number;
413
+ createdAt: string;
414
+ updatedAt: string;
415
+ } | null;
416
+ } | null;
417
+ };
418
+ };
419
+ outputFormat: "json";
420
+ status: import("hono/utils/http-status").ContentfulStatusCode;
421
+ };
422
+ };
423
+ } & {
424
+ "/sessions/:sessionId/confirm": {
425
+ $post: {
426
+ input: {
427
+ param: {
428
+ sessionId: string;
429
+ };
430
+ };
431
+ output: {
432
+ error: string;
433
+ };
434
+ outputFormat: "json";
435
+ status: 404;
436
+ } | {
437
+ input: {
438
+ param: {
439
+ sessionId: string;
440
+ };
441
+ };
442
+ output: {
443
+ error: string;
444
+ };
445
+ outputFormat: "json";
446
+ status: 409;
447
+ } | {
448
+ input: {
449
+ param: {
450
+ sessionId: string;
451
+ };
452
+ };
453
+ output: {
454
+ data: import("hono/utils/types").JSONValue;
455
+ };
456
+ outputFormat: "json";
457
+ status: import("hono/utils/http-status").ContentfulStatusCode;
458
+ };
459
+ };
460
+ } & {
461
+ "/sessions/:sessionId/expire": {
462
+ $post: {
463
+ input: {
464
+ param: {
465
+ sessionId: string;
466
+ };
467
+ };
468
+ output: {
469
+ error: string;
470
+ };
471
+ outputFormat: "json";
472
+ status: 404;
473
+ } | {
474
+ input: {
475
+ param: {
476
+ sessionId: string;
477
+ };
478
+ };
479
+ output: {
480
+ error: string;
481
+ };
482
+ outputFormat: "json";
483
+ status: 409;
484
+ } | {
485
+ input: {
486
+ param: {
487
+ sessionId: string;
488
+ };
489
+ };
490
+ output: {
491
+ data: import("hono/utils/types").JSONValue;
492
+ };
493
+ outputFormat: "json";
494
+ status: import("hono/utils/http-status").ContentfulStatusCode;
495
+ };
496
+ };
497
+ } & {
498
+ "/overview": {
499
+ $get: {
500
+ input: {};
501
+ output: {
502
+ error: string;
503
+ };
504
+ outputFormat: "json";
505
+ status: 404;
506
+ } | {
507
+ input: {};
508
+ output: {
509
+ data: {
510
+ bookingId: string;
511
+ bookingNumber: string;
512
+ status: "cancelled" | "draft" | "on_hold" | "confirmed" | "in_progress" | "completed" | "expired";
513
+ sellCurrency: string;
514
+ sellAmountCents: number | null;
515
+ startDate: string | null;
516
+ endDate: string | null;
517
+ pax: number | null;
518
+ confirmedAt: string | null;
519
+ cancelledAt: string | null;
520
+ completedAt: string | null;
521
+ participants: {
522
+ id: string;
523
+ participantType: "staff" | "other" | "traveler" | "booker" | "contact" | "occupant";
524
+ firstName: string;
525
+ lastName: string;
526
+ isPrimary: boolean;
527
+ }[];
528
+ items: {
529
+ id: string;
530
+ title: string;
531
+ description: string | null;
532
+ itemType: "other" | "unit" | "extra" | "service" | "fee" | "tax" | "discount" | "adjustment" | "accommodation" | "transport";
533
+ status: "cancelled" | "draft" | "on_hold" | "confirmed" | "expired" | "fulfilled";
534
+ serviceDate: string | null;
535
+ startsAt: string | null;
536
+ endsAt: string | null;
537
+ quantity: number;
538
+ sellCurrency: string;
539
+ unitSellAmountCents: number | null;
540
+ totalSellAmountCents: number | null;
541
+ costCurrency: string | null;
542
+ unitCostAmountCents: number | null;
543
+ totalCostAmountCents: number | null;
544
+ notes: string | null;
545
+ productId: string | null;
546
+ optionId: string | null;
547
+ optionUnitId: string | null;
548
+ pricingCategoryId: string | null;
549
+ participantLinks: {
550
+ id: string;
551
+ participantId: string;
552
+ role: string;
553
+ isPrimary: boolean;
554
+ }[];
555
+ }[];
556
+ documents: {
557
+ id: string;
558
+ participantId: string | null;
559
+ type: "visa" | "other" | "insurance" | "health" | "passport_copy";
560
+ fileName: string;
561
+ fileUrl: string;
562
+ }[];
563
+ fulfillments: {
564
+ id: string;
565
+ bookingItemId: string | null;
566
+ participantId: string | null;
567
+ fulfillmentType: "other" | "voucher" | "ticket" | "pdf" | "qr_code" | "barcode" | "mobile";
568
+ deliveryChannel: "other" | "download" | "email" | "api" | "wallet";
569
+ status: "pending" | "issued" | "reissued" | "revoked" | "failed";
570
+ artifactUrl: string | null;
571
+ }[];
572
+ };
573
+ };
574
+ outputFormat: "json";
575
+ status: import("hono/utils/http-status").ContentfulStatusCode;
576
+ };
577
+ };
578
+ }, "/", "/overview">;
579
+ export type PublicBookingRoutes = typeof publicBookingRoutes;
580
+ //# sourceMappingURL=routes-public.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes-public.d.ts","sourceRoot":"","sources":["../src/routes-public.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,GAAG,EAAY,MAAM,oBAAoB,CAAA;AAsCvD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAqI5B,CAAA;AAEJ,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAA"}
@@ -0,0 +1,105 @@
1
+ import { Hono } from "hono";
2
+ import { notFound } from "./routes-shared.js";
3
+ import { publicBookingsService } from "./service-public.js";
4
+ import { publicBookingOverviewLookupQuerySchema, publicBookingSessionMutationSchema, publicCreateBookingSessionSchema, publicRepriceBookingSessionSchema, publicUpdateBookingSessionSchema, publicUpsertBookingSessionStateSchema, } from "./validation-public.js";
5
+ function hasSessionResult(result) {
6
+ return "session" in result;
7
+ }
8
+ function sessionConflictError(status) {
9
+ switch (status) {
10
+ case "insufficient_capacity":
11
+ return "Insufficient slot capacity";
12
+ case "slot_unavailable":
13
+ return "Availability slot is not bookable";
14
+ case "invalid_transition":
15
+ return "Booking session cannot move to the requested state";
16
+ case "hold_expired":
17
+ return "Booking session hold has expired";
18
+ case "participant_not_found":
19
+ return "Booking session participant not found";
20
+ case "pricing_unavailable":
21
+ return "Pricing is not available for the selected booking session items";
22
+ case "quantity_change_requires_reallocation":
23
+ return "Changing quantity for held items requires a fresh reservation";
24
+ default:
25
+ return "Unable to process booking session";
26
+ }
27
+ }
28
+ export const publicBookingRoutes = new Hono()
29
+ .post("/sessions", async (c) => {
30
+ const result = await publicBookingsService.createSession(c.get("db"), publicCreateBookingSessionSchema.parse(await c.req.json()), c.get("userId"));
31
+ if (result.status === "slot_not_found") {
32
+ return notFound(c, "Availability slot not found");
33
+ }
34
+ if (!hasSessionResult(result)) {
35
+ return c.json({ error: sessionConflictError(result.status) }, 409);
36
+ }
37
+ return c.json({ data: result.session }, 201);
38
+ })
39
+ .get("/sessions/:sessionId", async (c) => {
40
+ const session = await publicBookingsService.getSessionById(c.get("db"), c.req.param("sessionId"));
41
+ return session ? c.json({ data: session }) : notFound(c, "Booking session not found");
42
+ })
43
+ .patch("/sessions/:sessionId", async (c) => {
44
+ const result = await publicBookingsService.updateSession(c.get("db"), c.req.param("sessionId"), publicUpdateBookingSessionSchema.parse(await c.req.json()), c.get("userId"));
45
+ if (result.status === "not_found") {
46
+ return notFound(c, "Booking session not found");
47
+ }
48
+ if (!hasSessionResult(result)) {
49
+ return c.json({ error: sessionConflictError(result.status) }, 409);
50
+ }
51
+ return c.json({ data: result.session });
52
+ })
53
+ .get("/sessions/:sessionId/state", async (c) => {
54
+ const state = await publicBookingsService.getSessionState(c.get("db"), c.req.param("sessionId"));
55
+ return state ? c.json({ data: state }) : notFound(c, "Booking session not found");
56
+ })
57
+ .put("/sessions/:sessionId/state", async (c) => {
58
+ const result = await publicBookingsService.updateSessionState(c.get("db"), c.req.param("sessionId"), publicUpsertBookingSessionStateSchema.parse(await c.req.json()));
59
+ if (result.status === "not_found") {
60
+ return notFound(c, "Booking session not found");
61
+ }
62
+ return c.json({ data: result.state });
63
+ })
64
+ .post("/sessions/:sessionId/reprice", async (c) => {
65
+ const result = await publicBookingsService.repriceSession(c.get("db"), c.req.param("sessionId"), publicRepriceBookingSessionSchema.parse(await c.req.json()));
66
+ if (result.status === "not_found") {
67
+ return notFound(c, "Booking session not found");
68
+ }
69
+ if (result.status === "invalid_selection") {
70
+ return c.json({ error: "Booking session contains an invalid item selection" }, 400);
71
+ }
72
+ if (result.status !== "ok") {
73
+ return c.json({ error: sessionConflictError(result.status) }, 409);
74
+ }
75
+ return c.json({
76
+ data: {
77
+ pricing: result.pricing,
78
+ session: result.session,
79
+ },
80
+ });
81
+ })
82
+ .post("/sessions/:sessionId/confirm", async (c) => {
83
+ const result = await publicBookingsService.confirmSession(c.get("db"), c.req.param("sessionId"), publicBookingSessionMutationSchema.parse(await c.req.json()), c.get("userId"));
84
+ if (result.status === "not_found") {
85
+ return notFound(c, "Booking session not found");
86
+ }
87
+ if (!hasSessionResult(result)) {
88
+ return c.json({ error: sessionConflictError(result.status) }, 409);
89
+ }
90
+ return c.json({ data: result.session });
91
+ })
92
+ .post("/sessions/:sessionId/expire", async (c) => {
93
+ const result = await publicBookingsService.expireSession(c.get("db"), c.req.param("sessionId"), publicBookingSessionMutationSchema.parse(await c.req.json()), c.get("userId"));
94
+ if (result.status === "not_found") {
95
+ return notFound(c, "Booking session not found");
96
+ }
97
+ if (!hasSessionResult(result)) {
98
+ return c.json({ error: sessionConflictError(result.status) }, 409);
99
+ }
100
+ return c.json({ data: result.session });
101
+ })
102
+ .get("/overview", async (c) => {
103
+ const overview = await publicBookingsService.getOverview(c.get("db"), publicBookingOverviewLookupQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams)));
104
+ return overview ? c.json({ data: overview }) : notFound(c, "Booking overview not found");
105
+ });