@venulog/phasing-engine-schemas 0.7.6-alpha.0 → 0.8.0-alpha.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,466 @@
1
+ // packages/phasing-schemas/src/parkingAreaAccess.ts
2
+ import { z } from './zod.js';
3
+ import { createSuccessResponseSchema, createMessageDataResponseSchema } from './common.js';
4
+ // ------------------------------
5
+ // Access Point Geometry Schemas
6
+ // ------------------------------
7
+ /**
8
+ * A point in 2D space using percentage-based coordinates (0-100)
9
+ * relative to the site plan image dimensions
10
+ */
11
+ export const accessPointSchema = z
12
+ .object({
13
+ x: z.number().min(0).max(100).openapi({
14
+ description: 'X coordinate as percentage of image width (0-100)',
15
+ example: 25.5
16
+ }),
17
+ y: z.number().min(0).max(100).openapi({
18
+ description: 'Y coordinate as percentage of image height (0-100)',
19
+ example: 30.2
20
+ })
21
+ })
22
+ .openapi('AccessPoint');
23
+ /**
24
+ * GeoJSON LineString for access point geometry
25
+ */
26
+ export const geoJsonLineStringSchema = z
27
+ .object({
28
+ type: z.literal('LineString').openapi({
29
+ description: 'GeoJSON geometry type',
30
+ example: 'LineString'
31
+ }),
32
+ coordinates: z
33
+ .array(z.tuple([z.number(), z.number()]))
34
+ .min(2)
35
+ .openapi({
36
+ description: 'GeoJSON LineString coordinates (array of [x, y] points)',
37
+ example: [
38
+ [25.5, 30.2],
39
+ [35.5, 30.2]
40
+ ]
41
+ })
42
+ })
43
+ .openapi('GeoJsonLineString');
44
+ // ------------------------------
45
+ // Parking Area Access Schemas
46
+ // ------------------------------
47
+ /**
48
+ * Core parking area access item schema matching database structure
49
+ */
50
+ export const parkingAreaAccessItemSchema = z
51
+ .object({
52
+ id: z.number().int().positive().openapi({
53
+ description: 'Access point ID',
54
+ example: 1
55
+ }),
56
+ event_id: z.number().int().positive().openapi({
57
+ description: 'Event ID this access point belongs to',
58
+ example: 1
59
+ }),
60
+ name: z.string().openapi({
61
+ description: 'Name of the access point',
62
+ example: 'Main Entrance'
63
+ }),
64
+ geometry: geoJsonLineStringSchema.nullable().openapi({
65
+ description: 'GeoJSON LineString geometry for the access point'
66
+ }),
67
+ width_meters: z.number().positive().nullable().openapi({
68
+ description: 'Width of the access point in meters',
69
+ example: 6.5
70
+ }),
71
+ height_meters: z.number().positive().nullable().openapi({
72
+ description: 'Height of the access point in meters (for gates/barriers)',
73
+ example: 4.2
74
+ }),
75
+ is_active: z.boolean().openapi({
76
+ description: 'Whether the access point is active',
77
+ example: true
78
+ }),
79
+ created_at: z.string().openapi({
80
+ description: 'Creation timestamp (ISO 8601)',
81
+ example: '2026-01-06T10:30:00Z'
82
+ }),
83
+ updated_at: z.string().openapi({
84
+ description: 'Last update timestamp (ISO 8601)',
85
+ example: '2026-01-06T10:30:00Z'
86
+ }),
87
+ created_by: z.string().uuid().nullable().openapi({
88
+ description: 'UUID of user who created this access point',
89
+ example: '550e8400-e29b-41d4-a716-446655440000'
90
+ }),
91
+ updated_by: z.string().uuid().nullable().openapi({
92
+ description: 'UUID of user who last updated this access point',
93
+ example: '550e8400-e29b-41d4-a716-446655440000'
94
+ })
95
+ })
96
+ .openapi('ParkingAreaAccessItem');
97
+ /**
98
+ * Access point with linked parking areas
99
+ */
100
+ export const parkingAreaAccessWithLinksSchema = parkingAreaAccessItemSchema
101
+ .extend({
102
+ linked_parking_areas: z
103
+ .array(z.object({
104
+ parking_area_id: z.number().int().positive(),
105
+ parking_area_name: z.string().nullable()
106
+ }))
107
+ .openapi({
108
+ description: 'List of parking areas linked to this access point'
109
+ })
110
+ })
111
+ .openapi('ParkingAreaAccessWithLinks');
112
+ // ------------------------------
113
+ // Query Schemas
114
+ // ------------------------------
115
+ export const getAccessesByEventIdParamsSchema = z
116
+ .object({
117
+ eventId: z
118
+ .string()
119
+ .min(1)
120
+ .transform(val => parseInt(val, 10))
121
+ .refine(val => !isNaN(val) && val > 0, {
122
+ message: 'Event ID must be a positive number'
123
+ })
124
+ .openapi({
125
+ description: 'The ID of the event',
126
+ example: '1'
127
+ })
128
+ })
129
+ .openapi('GetAccessesByEventIdParams');
130
+ export const getAccessByIdParamsSchema = z
131
+ .object({
132
+ accessId: z
133
+ .string()
134
+ .min(1)
135
+ .transform(val => parseInt(val, 10))
136
+ .refine(val => !isNaN(val) && val > 0, {
137
+ message: 'Access point ID must be a positive number'
138
+ })
139
+ .openapi({
140
+ description: 'The ID of the access point',
141
+ example: '1'
142
+ })
143
+ })
144
+ .openapi('GetAccessByIdParams');
145
+ // ------------------------------
146
+ // Create Access Point Schemas
147
+ // ------------------------------
148
+ export const createAccessBodySchema = z
149
+ .object({
150
+ event_id: z.number().int().positive().openapi({
151
+ description: 'ID of the event',
152
+ example: 1
153
+ }),
154
+ name: z.string().min(1).max(255).openapi({
155
+ description: 'Name of the access point',
156
+ example: 'Main Entrance'
157
+ }),
158
+ geometry: geoJsonLineStringSchema.optional().openapi({
159
+ description: 'GeoJSON LineString geometry for the access point'
160
+ }),
161
+ width_meters: z.number().positive().optional().openapi({
162
+ description: 'Width of the access point in meters',
163
+ example: 6.5
164
+ }),
165
+ height_meters: z.number().positive().optional().openapi({
166
+ description: 'Height of the access point in meters',
167
+ example: 4.2
168
+ }),
169
+ linked_parking_area_ids: z
170
+ .array(z.number().int().positive())
171
+ .optional()
172
+ .openapi({
173
+ description: 'IDs of parking areas to link to this access point',
174
+ example: [1, 2, 3]
175
+ })
176
+ })
177
+ .openapi('CreateAccessBody');
178
+ export const createAccessDataSchema = z
179
+ .object({
180
+ access_id: z.number().openapi({
181
+ description: 'ID of the created access point',
182
+ example: 1
183
+ }),
184
+ event_id: z.number().openapi({
185
+ description: 'ID of the event',
186
+ example: 1
187
+ }),
188
+ name: z.string().openapi({
189
+ description: 'Name of the access point',
190
+ example: 'Main Entrance'
191
+ }),
192
+ geometry: geoJsonLineStringSchema.nullable().openapi({
193
+ description: 'GeoJSON LineString geometry'
194
+ }),
195
+ width_meters: z.number().nullable().openapi({
196
+ description: 'Width in meters',
197
+ example: 6.5
198
+ }),
199
+ height_meters: z.number().nullable().openapi({
200
+ description: 'Height in meters',
201
+ example: 4.2
202
+ }),
203
+ linked_parking_areas: z
204
+ .array(z.object({
205
+ parking_area_id: z.number(),
206
+ parking_area_name: z.string().nullable()
207
+ }))
208
+ .openapi({
209
+ description: 'Linked parking areas'
210
+ }),
211
+ is_active: z.boolean().openapi({
212
+ description: 'Whether the access point is active',
213
+ example: true
214
+ }),
215
+ created_at: z.string().openapi({
216
+ description: 'Timestamp when access point was created'
217
+ }),
218
+ created_by: z.string().nullable().openapi({
219
+ description: 'ID of the user who created the access point'
220
+ })
221
+ })
222
+ .openapi('CreateAccessData');
223
+ export const createAccessResponseSchema = createMessageDataResponseSchema(createAccessDataSchema, 'CreateAccessResponse', 'Access point created successfully', 'Details of the created access point');
224
+ // ------------------------------
225
+ // Update Access Point Schemas
226
+ // ------------------------------
227
+ export const updateAccessParamsSchema = z
228
+ .object({
229
+ accessId: z
230
+ .string()
231
+ .min(1)
232
+ .transform(val => parseInt(val, 10))
233
+ .refine(val => !isNaN(val) && val > 0, {
234
+ message: 'Access point ID must be a positive number'
235
+ })
236
+ .openapi({
237
+ description: 'The ID of the access point to update',
238
+ example: '1'
239
+ })
240
+ })
241
+ .openapi('UpdateAccessParams');
242
+ export const updateAccessBodySchema = z
243
+ .object({
244
+ name: z.string().min(1).max(255).optional().openapi({
245
+ description: 'New name for the access point'
246
+ }),
247
+ geometry: geoJsonLineStringSchema.nullable().optional().openapi({
248
+ description: 'New geometry'
249
+ }),
250
+ width_meters: z.number().positive().nullable().optional().openapi({
251
+ description: 'New width in meters'
252
+ }),
253
+ height_meters: z.number().positive().nullable().optional().openapi({
254
+ description: 'New height in meters'
255
+ }),
256
+ is_active: z.boolean().optional().openapi({
257
+ description: 'Whether the access point is active'
258
+ })
259
+ })
260
+ .refine(data => Object.keys(data).length > 0, {
261
+ message: 'At least one field must be provided for update'
262
+ })
263
+ .openapi('UpdateAccessBody');
264
+ export const updateAccessDataSchema = createAccessDataSchema
265
+ .extend({
266
+ updated_at: z.string().openapi({
267
+ description: 'Timestamp when access point was updated'
268
+ }),
269
+ updated_by: z.string().nullable().openapi({
270
+ description: 'ID of the user who updated the access point'
271
+ })
272
+ })
273
+ .omit({ access_id: true })
274
+ .extend({
275
+ access_id: z.number().openapi({
276
+ description: 'ID of the updated access point'
277
+ })
278
+ })
279
+ .openapi('UpdateAccessData');
280
+ export const updateAccessResponseSchema = createMessageDataResponseSchema(updateAccessDataSchema, 'UpdateAccessResponse', 'Access point updated successfully', 'Details of the updated access point');
281
+ // ------------------------------
282
+ // Delete Access Point Schemas
283
+ // ------------------------------
284
+ export const deleteAccessParamsSchema = z
285
+ .object({
286
+ accessId: z
287
+ .string()
288
+ .min(1)
289
+ .transform(val => parseInt(val, 10))
290
+ .refine(val => !isNaN(val) && val > 0, {
291
+ message: 'Access point ID must be a positive number'
292
+ })
293
+ .openapi({
294
+ description: 'The ID of the access point to delete',
295
+ example: '1'
296
+ })
297
+ })
298
+ .openapi('DeleteAccessParams');
299
+ export const deleteAccessDataSchema = z
300
+ .object({
301
+ access_id: z.number().openapi({
302
+ description: 'ID of the deleted access point',
303
+ example: 1
304
+ }),
305
+ deleted_at: z.string().openapi({
306
+ description: 'Timestamp when access point was deleted'
307
+ })
308
+ })
309
+ .openapi('DeleteAccessData');
310
+ export const deleteAccessResponseSchema = createMessageDataResponseSchema(deleteAccessDataSchema, 'DeleteAccessResponse', 'Access point deleted successfully', 'Details of the deleted access point');
311
+ // ------------------------------
312
+ // Link/Unlink Parking Areas Schemas
313
+ // ------------------------------
314
+ export const linkAccessAreasParamsSchema = z
315
+ .object({
316
+ accessId: z
317
+ .string()
318
+ .min(1)
319
+ .transform(val => parseInt(val, 10))
320
+ .refine(val => !isNaN(val) && val > 0, {
321
+ message: 'Access point ID must be a positive number'
322
+ })
323
+ .openapi({
324
+ description: 'The ID of the access point',
325
+ example: '1'
326
+ })
327
+ })
328
+ .openapi('LinkAccessAreasParams');
329
+ export const linkAccessAreasBodySchema = z
330
+ .object({
331
+ parking_area_ids: z
332
+ .array(z.number().int().positive())
333
+ .min(1, 'At least one parking area ID must be provided')
334
+ .openapi({
335
+ description: 'IDs of parking areas to link',
336
+ example: [1, 2, 3]
337
+ })
338
+ })
339
+ .openapi('LinkAccessAreasBody');
340
+ export const linkAccessAreasDataSchema = z
341
+ .object({
342
+ access_id: z.number().openapi({
343
+ description: 'ID of the access point',
344
+ example: 1
345
+ }),
346
+ linked_count: z.number().openapi({
347
+ description: 'Number of parking areas linked',
348
+ example: 3
349
+ }),
350
+ linked_parking_areas: z
351
+ .array(z.object({
352
+ parking_area_id: z.number(),
353
+ parking_area_name: z.string().nullable()
354
+ }))
355
+ .openapi({
356
+ description: 'List of linked parking areas'
357
+ })
358
+ })
359
+ .openapi('LinkAccessAreasData');
360
+ export const linkAccessAreasResponseSchema = createMessageDataResponseSchema(linkAccessAreasDataSchema, 'LinkAccessAreasResponse', 'Parking areas linked successfully', 'Details of the linked parking areas');
361
+ export const unlinkAccessAreaParamsSchema = z
362
+ .object({
363
+ accessId: z
364
+ .string()
365
+ .min(1)
366
+ .transform(val => parseInt(val, 10))
367
+ .refine(val => !isNaN(val) && val > 0, {
368
+ message: 'Access point ID must be a positive number'
369
+ })
370
+ .openapi({
371
+ description: 'The ID of the access point',
372
+ example: '1'
373
+ }),
374
+ areaId: z
375
+ .string()
376
+ .min(1)
377
+ .transform(val => parseInt(val, 10))
378
+ .refine(val => !isNaN(val) && val > 0, {
379
+ message: 'Parking area ID must be a positive number'
380
+ })
381
+ .openapi({
382
+ description: 'The ID of the parking area to unlink',
383
+ example: '1'
384
+ })
385
+ })
386
+ .openapi('UnlinkAccessAreaParams');
387
+ export const unlinkAccessAreaDataSchema = z
388
+ .object({
389
+ access_id: z.number().openapi({
390
+ description: 'ID of the access point',
391
+ example: 1
392
+ }),
393
+ parking_area_id: z.number().openapi({
394
+ description: 'ID of the unlinked parking area',
395
+ example: 1
396
+ }),
397
+ unlinked_at: z.string().openapi({
398
+ description: 'Timestamp when the link was removed'
399
+ })
400
+ })
401
+ .openapi('UnlinkAccessAreaData');
402
+ export const unlinkAccessAreaResponseSchema = createMessageDataResponseSchema(unlinkAccessAreaDataSchema, 'UnlinkAccessAreaResponse', 'Parking area unlinked successfully', 'Details of the unlinked parking area');
403
+ // ------------------------------
404
+ // Get Access Points Response Schemas
405
+ // ------------------------------
406
+ export const getAccessesDataSchema = z
407
+ .object({
408
+ event_id: z.number().openapi({
409
+ description: 'ID of the event',
410
+ example: 1
411
+ }),
412
+ accesses: z.array(parkingAreaAccessWithLinksSchema).openapi({
413
+ description: 'List of access points for the event'
414
+ }),
415
+ total_count: z.number().openapi({
416
+ description: 'Total number of access points',
417
+ example: 4
418
+ })
419
+ })
420
+ .openapi('GetAccessesData');
421
+ export const getAccessesResponseSchema = createSuccessResponseSchema(getAccessesDataSchema, 'GetAccessesResponse', 'Access points for the event');
422
+ export const getAccessDetailResponseSchema = createSuccessResponseSchema(parkingAreaAccessWithLinksSchema, 'GetAccessDetailResponse', 'Access point details with linked parking areas');
423
+ // ------------------------------
424
+ // Bulk Upsert Access Points Schemas
425
+ // ------------------------------
426
+ export const bulkUpsertAccessesBodySchema = z
427
+ .object({
428
+ event_id: z.number().int().positive().openapi({
429
+ description: 'ID of the event',
430
+ example: 1
431
+ }),
432
+ accesses: z
433
+ .array(z.object({
434
+ id: z.number().int().positive().optional().openapi({
435
+ description: 'Access point ID (for updates). Omit for new access points.'
436
+ }),
437
+ name: z.string().min(1).max(255).openapi({
438
+ description: 'Name of the access point'
439
+ }),
440
+ geometry: geoJsonLineStringSchema.optional(),
441
+ width_meters: z.number().positive().optional(),
442
+ height_meters: z.number().positive().optional(),
443
+ linked_parking_area_ids: z.array(z.number().int().positive()).optional()
444
+ }))
445
+ .min(1, 'At least one access point must be provided')
446
+ .openapi({
447
+ description: 'Array of access points to create or update'
448
+ })
449
+ })
450
+ .openapi('BulkUpsertAccessesBody');
451
+ export const bulkUpsertAccessesDataSchema = z
452
+ .object({
453
+ event_id: z.number().openapi({
454
+ description: 'ID of the event',
455
+ example: 1
456
+ }),
457
+ total_upserted: z.number().openapi({
458
+ description: 'Total number of access points successfully upserted',
459
+ example: 4
460
+ }),
461
+ upserted_accesses: z.array(parkingAreaAccessWithLinksSchema).openapi({
462
+ description: 'Array of successfully upserted access points'
463
+ })
464
+ })
465
+ .openapi('BulkUpsertAccessesData');
466
+ export const bulkUpsertAccessesResponseSchema = createMessageDataResponseSchema(bulkUpsertAccessesDataSchema, 'BulkUpsertAccessesResponse', 'Access points upserted successfully', 'Details of the upserted access points');