feat(recordings): enhance recording management with improved error handling, finalize recording logic, and add motion notification support

This commit is contained in:
2026-02-03 17:45:00 +00:00
parent ef74b5ca19
commit 23db01dfc8
4 changed files with 442 additions and 39 deletions

View File

@@ -6,7 +6,7 @@ import { db } from '../db/client';
import { recordings, streamSessions } from '../db/schema';
import { requireDeviceAuth } from '../middleware/device-auth';
import { writeAuditLog } from '../services/audit';
import { minioBucket, minioClient, minioPresignedExpirySeconds } from '../utils/minio';
import { ensureMinioBucket, minioBucket, minioClient, minioPresignedExpirySeconds } from '../utils/minio';
const router = Router();
@@ -26,6 +26,15 @@ const recordingParamSchema = z.object({
recordingId: z.string().uuid(),
});
const isMissingStorageObjectError = (error: unknown): boolean => {
if (!error || typeof error !== 'object') {
return false;
}
const code = 'code' in error ? String((error as { code?: unknown }).code) : '';
return code === 'NoSuchKey' || code === 'NotFound';
};
router.get('/me/list', requireDeviceAuth, async (req, res) => {
const parsed = listSchema.safeParse(req.query);
@@ -89,12 +98,41 @@ router.post('/:recordingId/finalize', requireDeviceAuth, async (req, res) => {
}
const now = new Date();
const bucket = parsed.data.bucket;
const objectKey = parsed.data.objectKey;
await ensureMinioBucket();
try {
await minioClient.statObject(bucket, objectKey);
} catch (error) {
if (objectKey.startsWith('sim/')) {
const placeholder = Buffer.from(
JSON.stringify({
message: 'simulated recording placeholder',
recordingId: recording.id,
streamSessionId: recording.streamSessionId,
createdAt: now.toISOString(),
}),
'utf8',
);
await minioClient.putObject(bucket, objectKey, placeholder, placeholder.byteLength, {
'Content-Type': 'application/json',
});
} else if (isMissingStorageObjectError(error)) {
res.status(409).json({ message: 'Recording object does not exist in storage yet' });
return;
} else {
throw error;
}
}
const [updated] = await db
.update(recordings)
.set({
objectKey: parsed.data.objectKey,
bucket: parsed.data.bucket,
objectKey,
bucket,
durationSeconds: parsed.data.durationSeconds,
sizeBytes: parsed.data.sizeBytes,
status: 'ready',
@@ -154,6 +192,17 @@ router.get('/:recordingId/download-url', requireDeviceAuth, async (req, res) =>
return;
}
try {
await minioClient.statObject(recording.bucket, recording.objectKey);
} catch (error) {
if (isMissingStorageObjectError(error)) {
res.status(409).json({ message: 'Recording file is missing from storage' });
return;
}
throw error;
}
const downloadUrl = await minioClient.presignedGetObject(
recording.bucket,
recording.objectKey,