docs(api): document phase1 and phase2 device/realtime endpoints

This commit is contained in:
2026-01-09 16:40:00 +00:00
parent 250d072e8b
commit 71401e1973
2 changed files with 240 additions and 0 deletions

View File

@@ -108,6 +108,55 @@ const DeleteVideoResponseSchema = registry.register(
}),
);
const DeviceSchema = registry.register(
'Device',
z.object({
id: z.string().uuid(),
userId: z.string().uuid(),
name: z.string().nullable(),
role: z.enum(['camera', 'client']),
platform: z.string().nullable(),
appVersion: z.string().nullable(),
status: z.string(),
isCamera: z.boolean(),
lastSeenAt: z.string().datetime().nullable(),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
}),
);
const DeviceLinkSchema = registry.register(
'DeviceLink',
z.object({
id: z.string().uuid(),
ownerUserId: z.string().uuid(),
cameraDeviceId: z.string().uuid(),
clientDeviceId: z.string().uuid(),
status: z.string(),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
}),
);
const DeviceCommandSchema = registry.register(
'DeviceCommand',
z.object({
id: z.string().uuid(),
ownerUserId: z.string().uuid(),
sourceDeviceId: z.string().uuid(),
targetDeviceId: z.string().uuid(),
commandType: z.string(),
payload: z.record(z.string(), z.unknown()).nullable(),
status: z.string(),
retryCount: z.number().int(),
lastDispatchedAt: z.string().datetime().nullable(),
acknowledgedAt: z.string().datetime().nullable(),
error: z.string().nullable(),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
}),
);
registry.registerPath({
method: 'get',
path: '/',
@@ -403,6 +452,162 @@ registry.registerPath({
},
});
registry.registerPath({
method: 'post',
path: '/devices/register',
summary: 'Register a mobile device and issue a device token',
tags: ['Devices'],
security: [{ cookieAuth: [] }],
request: {
body: {
content: {
'application/json': {
schema: z.object({
name: z.string().optional(),
role: z.enum(['camera', 'client']),
platform: z.string().optional(),
appVersion: z.string().optional(),
pushToken: z.string().optional(),
}),
},
},
},
},
responses: {
201: {
description: 'Device registered',
content: {
'application/json': {
schema: z.object({
message: z.string(),
device: DeviceSchema,
deviceToken: z.string(),
}),
},
},
},
},
});
registry.registerPath({
method: 'get',
path: '/devices',
summary: 'List devices for the authenticated user',
tags: ['Devices'],
security: [{ cookieAuth: [] }],
responses: {
200: {
description: 'User devices',
content: {
'application/json': {
schema: z.object({
count: z.number().int(),
devices: z.array(DeviceSchema),
}),
},
},
},
},
});
registry.registerPath({
method: 'post',
path: '/devices/{deviceId}/heartbeat',
summary: 'Record heartbeat for a device using bearer device token',
tags: ['Devices'],
security: [{ bearerDeviceToken: [] }],
request: {
params: z.object({ deviceId: z.string().uuid() }),
body: {
content: {
'application/json': {
schema: z.object({ status: z.enum(['online', 'offline']).optional() }),
},
},
},
},
responses: {
200: {
description: 'Heartbeat recorded',
content: {
'application/json': {
schema: z.object({
message: z.string(),
device: z.object({
id: z.string().uuid(),
status: z.string(),
lastSeenAt: z.string().datetime().nullable(),
updatedAt: z.string().datetime(),
}),
}),
},
},
},
},
});
registry.registerPath({
method: 'post',
path: '/device-links',
summary: 'Link a client device to a camera device',
tags: ['Device Links'],
security: [{ cookieAuth: [] }],
request: {
body: {
content: {
'application/json': {
schema: z.object({
cameraDeviceId: z.string().uuid(),
clientDeviceId: z.string().uuid(),
}),
},
},
},
},
responses: {
201: {
description: 'Link created',
content: {
'application/json': {
schema: z.object({ message: z.string(), link: DeviceLinkSchema }),
},
},
},
},
});
registry.registerPath({
method: 'post',
path: '/commands',
summary: 'Create and dispatch a realtime command from client to camera',
tags: ['Commands'],
security: [{ cookieAuth: [] }],
request: {
body: {
content: {
'application/json': {
schema: z.object({
sourceDeviceId: z.string().uuid(),
targetDeviceId: z.string().uuid(),
commandType: z.enum(['start_stream', 'stop_stream', 'ping', 'update_settings']),
payload: z.record(z.string(), z.unknown()).optional(),
}),
},
},
},
},
responses: {
201: {
description: 'Command queued',
content: {
'application/json': {
schema: z.object({ message: z.string(), command: DeviceCommandSchema }),
},
},
},
},
});
export function buildOpenApiDocument() {
const generator = new OpenApiGeneratorV3(registry.definitions);
const document = generator.generateDocument({
@@ -417,6 +622,9 @@ export function buildOpenApiDocument() {
{ name: 'System', description: 'Service endpoints' },
{ name: 'Videos', description: 'Authenticated video object operations' },
{ name: 'Admin', description: 'Basic-auth protected admin operations' },
{ name: 'Devices', description: 'Device registration and heartbeat endpoints' },
{ name: 'Device Links', description: 'Client-camera authorization links' },
{ name: 'Commands', description: 'Realtime command dispatch and status' },
],
});
@@ -433,6 +641,10 @@ export function buildOpenApiDocument() {
type: 'http',
scheme: 'basic',
},
bearerDeviceToken: {
type: 'http',
scheme: 'bearer',
},
},
};