From dc65756db80b971622f0d8e54b4670a51f0abeea Mon Sep 17 00:00:00 2001 From: Matiss Jurevics Date: Sat, 10 Jan 2026 14:50:00 +0000 Subject: [PATCH] docs(events): add phase3 motion API and realtime event docs --- Backend/README.md | 11 ++++ Backend/docs/openapi.ts | 127 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) diff --git a/Backend/README.md b/Backend/README.md index 2ab4a93..c871ff3 100644 --- a/Backend/README.md +++ b/Backend/README.md @@ -111,6 +111,17 @@ Socket.IO channel: - Camera sends `command:ack` with `acknowledged` or `rejected`. - Source client receives `command:status`. +### Motion Events (Phase 3) +| Endpoint | Purpose | +| --- | --- | +| `POST /events/motion/start` | Camera device creates motion event and fan-outs notification to linked clients | +| `POST /events/:eventId/motion/end` | Camera device closes a motion event and broadcasts end state | +| `GET /events` | Authenticated user fetches event history | + +Motion realtime events: +- Linked clients receive `motion:detected` as soon as camera starts event. +- Linked clients receive `motion:ended` when camera ends event. + ### API Docs OpenAPI docs are generated from Zod/OpenAPI definitions: diff --git a/Backend/docs/openapi.ts b/Backend/docs/openapi.ts index beb8c00..7627476 100644 --- a/Backend/docs/openapi.ts +++ b/Backend/docs/openapi.ts @@ -608,6 +608,132 @@ registry.registerPath({ }, }); +registry.registerPath({ + method: 'post', + path: '/events/motion/start', + summary: 'Camera device starts a motion event and notifies linked clients', + tags: ['Events'], + security: [{ bearerDeviceToken: [] }], + request: { + body: { + content: { + 'application/json': { + schema: z.object({ + title: z.string().optional(), + triggeredBy: z.string().optional(), + videoUrl: z.string().url().optional(), + }), + }, + }, + }, + }, + responses: { + 201: { + description: 'Motion event started', + content: { + 'application/json': { + schema: z.object({ + message: z.string(), + event: z.object({ + id: z.string().uuid(), + userId: z.string().uuid(), + deviceId: z.string().uuid().nullable(), + title: z.string().nullable(), + triggeredBy: z.string().nullable(), + status: z.string(), + startedAt: z.string().datetime(), + endedAt: z.string().datetime().nullable(), + videoUrl: z.string().nullable(), + createdAt: z.string().datetime(), + updatedAt: z.string().datetime(), + notifiedAt: z.string().datetime().nullable(), + }), + notifiedClients: z.number().int(), + }), + }, + }, + }, + }, +}); + +registry.registerPath({ + method: 'post', + path: '/events/{eventId}/motion/end', + summary: 'Camera device ends a motion event and notifies linked clients', + tags: ['Events'], + security: [{ bearerDeviceToken: [] }], + request: { + params: z.object({ eventId: z.string().uuid() }), + body: { + content: { + 'application/json': { + schema: z.object({ + status: z.enum(['completed', 'cancelled', 'failed']).optional(), + videoUrl: z.string().url().optional(), + }), + }, + }, + }, + }, + responses: { + 200: { + description: 'Motion event ended', + content: { + 'application/json': { + schema: z.object({ + message: z.string(), + event: z.object({ + id: z.string().uuid(), + status: z.string(), + endedAt: z.string().datetime().nullable(), + updatedAt: z.string().datetime(), + }), + notifiedClients: z.number().int(), + }), + }, + }, + }, + }, +}); + +registry.registerPath({ + method: 'get', + path: '/events', + summary: 'List events for authenticated user', + tags: ['Events'], + security: [{ cookieAuth: [] }], + request: { + query: z.object({ + status: z.string().optional(), + limit: z.coerce.number().int().min(1).max(100).default(25), + }), + }, + responses: { + 200: { + description: 'User event list', + content: { + 'application/json': { + schema: z.object({ + count: z.number().int(), + events: z.array( + z.object({ + id: z.string().uuid(), + status: z.string(), + startedAt: z.string().datetime(), + endedAt: z.string().datetime().nullable(), + deviceId: z.string().uuid().nullable(), + title: z.string().nullable(), + triggeredBy: z.string().nullable(), + videoUrl: z.string().nullable(), + }), + ), + }), + }, + }, + }, + }, +}); + export function buildOpenApiDocument() { const generator = new OpenApiGeneratorV3(registry.definitions); const document = generator.generateDocument({ @@ -625,6 +751,7 @@ export function buildOpenApiDocument() { { name: 'Devices', description: 'Device registration and heartbeat endpoints' }, { name: 'Device Links', description: 'Client-camera authorization links' }, { name: 'Commands', description: 'Realtime command dispatch and status' }, + { name: 'Events', description: 'Motion event lifecycle and user event history' }, ], });