@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,385 @@
1
+ // packages/phasing-schemas/src/parkingAreaLayer.ts
2
+ import { z } from './zod.js';
3
+ import { createSuccessResponseSchema, createMessageDataResponseSchema } from './common.js';
4
+ // ------------------------------
5
+ // Layer Transform Schema
6
+ // ------------------------------
7
+ /**
8
+ * Transform data for layer positioning and rotation
9
+ */
10
+ export const layerTransformSchema = z
11
+ .object({
12
+ left: z.number().openapi({
13
+ description: 'Horizontal position offset in pixels',
14
+ example: 100
15
+ }),
16
+ top: z.number().openapi({
17
+ description: 'Vertical position offset in pixels',
18
+ example: 50
19
+ }),
20
+ scaleX: z.number().positive().openapi({
21
+ description: 'Horizontal scale factor',
22
+ example: 1.2
23
+ }),
24
+ scaleY: z.number().positive().openapi({
25
+ description: 'Vertical scale factor',
26
+ example: 1.2
27
+ }),
28
+ angle: z.number().min(-180).max(180).openapi({
29
+ description: 'Rotation angle in degrees',
30
+ example: 15
31
+ })
32
+ })
33
+ .openapi('LayerTransform');
34
+ // ------------------------------
35
+ // Parking Area Layer Schemas
36
+ // ------------------------------
37
+ /**
38
+ * Core parking area layer item schema matching database structure
39
+ */
40
+ export const parkingAreaLayerItemSchema = z
41
+ .object({
42
+ id: z.number().int().positive().openapi({
43
+ description: 'Layer ID',
44
+ example: 1
45
+ }),
46
+ event_id: z.number().int().positive().openapi({
47
+ description: 'Event ID this layer belongs to',
48
+ example: 1
49
+ }),
50
+ name: z.string().openapi({
51
+ description: 'Name of the layer',
52
+ example: 'Ground Floor Plan'
53
+ }),
54
+ layer_order: z.number().int().nonnegative().openapi({
55
+ description: 'Display order (lower numbers appear first)',
56
+ example: 0
57
+ }),
58
+ image_url: z.string().url().openapi({
59
+ description: 'URL to the layer image',
60
+ example: 'https://storage.example.com/layers/floor-1.png'
61
+ }),
62
+ visible: z.boolean().openapi({
63
+ description: 'Whether the layer is visible',
64
+ example: true
65
+ }),
66
+ opacity: z.number().min(0).max(1).openapi({
67
+ description: 'Layer opacity (0.0 to 1.0)',
68
+ example: 0.8
69
+ }),
70
+ altitude_meters: z.number().min(-100).max(100).openapi({
71
+ description: 'Altitude in meters for 3D rendering (-100 to +100)',
72
+ example: 0
73
+ }),
74
+ transform: layerTransformSchema.openapi({
75
+ description: 'Transform data for layer positioning and rotation'
76
+ }),
77
+ is_active: z.boolean().openapi({
78
+ description: 'Whether the layer is active',
79
+ example: true
80
+ }),
81
+ created_at: z.string().openapi({
82
+ description: 'Creation timestamp (ISO 8601)',
83
+ example: '2026-01-06T10:30:00Z'
84
+ }),
85
+ updated_at: z.string().openapi({
86
+ description: 'Last update timestamp (ISO 8601)',
87
+ example: '2026-01-06T10:30:00Z'
88
+ }),
89
+ created_by: z.string().uuid().nullable().openapi({
90
+ description: 'UUID of user who created this layer',
91
+ example: '550e8400-e29b-41d4-a716-446655440000'
92
+ }),
93
+ updated_by: z.string().uuid().nullable().openapi({
94
+ description: 'UUID of user who last updated this layer',
95
+ example: '550e8400-e29b-41d4-a716-446655440000'
96
+ })
97
+ })
98
+ .openapi('ParkingAreaLayerItem');
99
+ // ------------------------------
100
+ // Query Schemas
101
+ // ------------------------------
102
+ export const getLayersByEventIdParamsSchema = z
103
+ .object({
104
+ eventId: z
105
+ .string()
106
+ .min(1)
107
+ .transform(val => parseInt(val, 10))
108
+ .refine(val => !isNaN(val) && val > 0, {
109
+ message: 'Event ID must be a positive number'
110
+ })
111
+ .openapi({
112
+ description: 'The ID of the event',
113
+ example: '1'
114
+ })
115
+ })
116
+ .openapi('GetLayersByEventIdParams');
117
+ export const getLayerByIdParamsSchema = z
118
+ .object({
119
+ layerId: z
120
+ .string()
121
+ .min(1)
122
+ .transform(val => parseInt(val, 10))
123
+ .refine(val => !isNaN(val) && val > 0, {
124
+ message: 'Layer ID must be a positive number'
125
+ })
126
+ .openapi({
127
+ description: 'The ID of the layer',
128
+ example: '1'
129
+ })
130
+ })
131
+ .openapi('GetLayerByIdParams');
132
+ // ------------------------------
133
+ // Create Layer Schemas
134
+ // ------------------------------
135
+ export const createLayerBodySchema = z
136
+ .object({
137
+ event_id: z.number().int().positive().openapi({
138
+ description: 'ID of the event',
139
+ example: 1
140
+ }),
141
+ name: z.string().min(1).max(255).openapi({
142
+ description: 'Name of the layer',
143
+ example: 'Ground Floor Plan'
144
+ }),
145
+ layer_order: z.number().int().nonnegative().optional().openapi({
146
+ description: 'Display order (auto-assigned if not provided)',
147
+ example: 0
148
+ }),
149
+ image_url: z.string().url().openapi({
150
+ description: 'URL to the layer image',
151
+ example: 'https://storage.example.com/layers/floor-1.png'
152
+ }),
153
+ visible: z.boolean().default(true).openapi({
154
+ description: 'Whether the layer is visible',
155
+ example: true
156
+ }),
157
+ opacity: z.number().min(0).max(1).default(1).openapi({
158
+ description: 'Layer opacity (0.0 to 1.0)',
159
+ example: 1
160
+ }),
161
+ altitude_meters: z.number().min(-100).max(100).default(0).openapi({
162
+ description: 'Altitude in meters for 3D rendering',
163
+ example: 0
164
+ }),
165
+ transform: layerTransformSchema
166
+ .default({ left: 0, top: 0, scaleX: 1, scaleY: 1, angle: 0 })
167
+ .openapi({
168
+ description: 'Transform data for layer positioning'
169
+ })
170
+ })
171
+ .openapi('CreateLayerBody');
172
+ export const createLayerDataSchema = z
173
+ .object({
174
+ layer_id: z.number().openapi({
175
+ description: 'ID of the created layer',
176
+ example: 1
177
+ }),
178
+ event_id: z.number().openapi({
179
+ description: 'ID of the event',
180
+ example: 1
181
+ }),
182
+ name: z.string().openapi({
183
+ description: 'Name of the layer',
184
+ example: 'Ground Floor Plan'
185
+ }),
186
+ layer_order: z.number().openapi({
187
+ description: 'Display order',
188
+ example: 0
189
+ }),
190
+ image_url: z.string().openapi({
191
+ description: 'URL to the layer image'
192
+ }),
193
+ visible: z.boolean().openapi({
194
+ description: 'Whether the layer is visible',
195
+ example: true
196
+ }),
197
+ opacity: z.number().openapi({
198
+ description: 'Layer opacity',
199
+ example: 1
200
+ }),
201
+ altitude_meters: z.number().openapi({
202
+ description: 'Altitude in meters',
203
+ example: 0
204
+ }),
205
+ transform: layerTransformSchema.openapi({
206
+ description: 'Transform data'
207
+ }),
208
+ created_at: z.string().openapi({
209
+ description: 'Timestamp when layer was created'
210
+ }),
211
+ created_by: z.string().nullable().openapi({
212
+ description: 'ID of the user who created the layer'
213
+ })
214
+ })
215
+ .openapi('CreateLayerData');
216
+ export const createLayerResponseSchema = createMessageDataResponseSchema(createLayerDataSchema, 'CreateLayerResponse', 'Layer created successfully', 'Details of the created layer');
217
+ // ------------------------------
218
+ // Update Layer Schemas
219
+ // ------------------------------
220
+ export const updateLayerParamsSchema = z
221
+ .object({
222
+ layerId: z
223
+ .string()
224
+ .min(1)
225
+ .transform(val => parseInt(val, 10))
226
+ .refine(val => !isNaN(val) && val > 0, {
227
+ message: 'Layer ID must be a positive number'
228
+ })
229
+ .openapi({
230
+ description: 'The ID of the layer to update',
231
+ example: '1'
232
+ })
233
+ })
234
+ .openapi('UpdateLayerParams');
235
+ export const updateLayerBodySchema = z
236
+ .object({
237
+ name: z.string().min(1).max(255).optional().openapi({
238
+ description: 'New name for the layer'
239
+ }),
240
+ layer_order: z.number().int().nonnegative().optional().openapi({
241
+ description: 'New display order'
242
+ }),
243
+ visible: z.boolean().optional().openapi({
244
+ description: 'Whether the layer is visible'
245
+ }),
246
+ opacity: z.number().min(0).max(1).optional().openapi({
247
+ description: 'New opacity value'
248
+ }),
249
+ altitude_meters: z.number().min(-100).max(100).optional().openapi({
250
+ description: 'New altitude in meters'
251
+ }),
252
+ transform: layerTransformSchema.optional().openapi({
253
+ description: 'New transform data'
254
+ }),
255
+ is_active: z.boolean().optional().openapi({
256
+ description: 'Whether the layer is active'
257
+ })
258
+ })
259
+ .refine(data => Object.keys(data).length > 0, {
260
+ message: 'At least one field must be provided for update'
261
+ })
262
+ .openapi('UpdateLayerBody');
263
+ export const updateLayerDataSchema = createLayerDataSchema
264
+ .extend({
265
+ updated_at: z.string().openapi({
266
+ description: 'Timestamp when layer was updated'
267
+ }),
268
+ updated_by: z.string().nullable().openapi({
269
+ description: 'ID of the user who updated the layer'
270
+ })
271
+ })
272
+ .omit({ layer_id: true })
273
+ .extend({
274
+ layer_id: z.number().openapi({
275
+ description: 'ID of the updated layer'
276
+ })
277
+ })
278
+ .openapi('UpdateLayerData');
279
+ export const updateLayerResponseSchema = createMessageDataResponseSchema(updateLayerDataSchema, 'UpdateLayerResponse', 'Layer updated successfully', 'Details of the updated layer');
280
+ // ------------------------------
281
+ // Delete Layer Schemas
282
+ // ------------------------------
283
+ export const deleteLayerParamsSchema = z
284
+ .object({
285
+ layerId: z
286
+ .string()
287
+ .min(1)
288
+ .transform(val => parseInt(val, 10))
289
+ .refine(val => !isNaN(val) && val > 0, {
290
+ message: 'Layer ID must be a positive number'
291
+ })
292
+ .openapi({
293
+ description: 'The ID of the layer to delete',
294
+ example: '1'
295
+ })
296
+ })
297
+ .openapi('DeleteLayerParams');
298
+ export const deleteLayerDataSchema = z
299
+ .object({
300
+ layer_id: z.number().openapi({
301
+ description: 'ID of the deleted layer',
302
+ example: 1
303
+ }),
304
+ deleted_at: z.string().openapi({
305
+ description: 'Timestamp when layer was deleted'
306
+ })
307
+ })
308
+ .openapi('DeleteLayerData');
309
+ export const deleteLayerResponseSchema = createMessageDataResponseSchema(deleteLayerDataSchema, 'DeleteLayerResponse', 'Layer deleted successfully', 'Details of the deleted layer');
310
+ // ------------------------------
311
+ // Get Layers Response Schemas
312
+ // ------------------------------
313
+ export const getLayersDataSchema = z
314
+ .object({
315
+ event_id: z.number().openapi({
316
+ description: 'ID of the event',
317
+ example: 1
318
+ }),
319
+ layers: z.array(parkingAreaLayerItemSchema).openapi({
320
+ description: 'List of layers for the event, ordered by layer_order'
321
+ }),
322
+ total_count: z.number().openapi({
323
+ description: 'Total number of layers',
324
+ example: 3
325
+ })
326
+ })
327
+ .openapi('GetLayersData');
328
+ export const getLayersResponseSchema = createSuccessResponseSchema(getLayersDataSchema, 'GetLayersResponse', 'Layers for the event');
329
+ export const getLayerDetailResponseSchema = createSuccessResponseSchema(parkingAreaLayerItemSchema, 'GetLayerDetailResponse', 'Layer details');
330
+ // ------------------------------
331
+ // Bulk Upsert Layers Schemas
332
+ // ------------------------------
333
+ export const bulkUpsertLayersBodySchema = z
334
+ .object({
335
+ event_id: z.number().int().positive().openapi({
336
+ description: 'ID of the event',
337
+ example: 1
338
+ }),
339
+ layers: z
340
+ .array(z.object({
341
+ id: z.number().int().positive().optional().openapi({
342
+ description: 'Layer ID (for updates). Omit for new layers.'
343
+ }),
344
+ name: z.string().min(1).max(255).openapi({
345
+ description: 'Name of the layer'
346
+ }),
347
+ layer_order: z.number().int().nonnegative().openapi({
348
+ description: 'Display order'
349
+ }),
350
+ image_url: z.string().url().openapi({
351
+ description: 'URL to the layer image'
352
+ }),
353
+ visible: z.boolean().default(true),
354
+ opacity: z.number().min(0).max(1).default(1),
355
+ altitude_meters: z.number().min(-100).max(100).default(0),
356
+ transform: layerTransformSchema.default({
357
+ left: 0,
358
+ top: 0,
359
+ scaleX: 1,
360
+ scaleY: 1,
361
+ angle: 0
362
+ })
363
+ }))
364
+ .min(1, 'At least one layer must be provided')
365
+ .openapi({
366
+ description: 'Array of layers to create or update'
367
+ })
368
+ })
369
+ .openapi('BulkUpsertLayersBody');
370
+ export const bulkUpsertLayersDataSchema = z
371
+ .object({
372
+ event_id: z.number().openapi({
373
+ description: 'ID of the event',
374
+ example: 1
375
+ }),
376
+ total_upserted: z.number().openapi({
377
+ description: 'Total number of layers successfully upserted',
378
+ example: 3
379
+ }),
380
+ upserted_layers: z.array(parkingAreaLayerItemSchema).openapi({
381
+ description: 'Array of successfully upserted layers'
382
+ })
383
+ })
384
+ .openapi('BulkUpsertLayersData');
385
+ export const bulkUpsertLayersResponseSchema = createMessageDataResponseSchema(bulkUpsertLayersDataSchema, 'BulkUpsertLayersResponse', 'Layers upserted successfully', 'Details of the upserted layers');