refactor(backend): simplify media schema and recording metadata

This commit is contained in:
2026-03-11 17:15:00 +00:00
parent 662d8d7b90
commit c6919d8174
18 changed files with 223 additions and 113 deletions

View File

@@ -3,7 +3,7 @@ import { and, eq } from 'drizzle-orm';
import { z } from 'zod';
import { db } from '../db/client';
import { devices, videos } from '../db/schema';
import { devices, events, recordings } from '../db/schema';
import { requireAuth } from '../middleware/auth';
import {
ensureMinioBucket,
@@ -18,6 +18,8 @@ const uploadUrlSchema = z.object({
fileName: z.string().trim().min(1).max(255),
deviceId: z.string().uuid(),
prefix: z.string().trim().optional(),
recordingId: z.string().uuid().optional(),
eventId: z.string().uuid().optional(),
});
const downloadUrlSchema = z.object({
@@ -67,44 +69,81 @@ router.post('/upload-url', async (req, res) => {
return;
}
if (parsed.data.eventId) {
const event = await db.query.events.findFirst({
where: and(eq(events.id, parsed.data.eventId), eq(events.userId, authSession.user.id)),
columns: { id: true },
});
if (!event) {
res.status(400).json({ message: 'Invalid eventId for this user' });
return;
}
}
const objectKey = buildObjectKey(authSession.user.id, parsed.data.fileName, parsed.data.prefix);
const uploadUrl = await minioClient.presignedPutObject(minioBucket, objectKey, minioPresignedExpirySeconds);
const now = new Date();
const expiresAt = new Date(now.getTime() + minioPresignedExpirySeconds * 1000);
const [videoRecord] = await db
.insert(videos)
.values({
userId: authSession.user.id,
deviceId: parsed.data.deviceId,
objectKey,
bucket: minioBucket,
uploadUrl,
status: 'upload_link_sent',
expiresAt,
updatedAt: now,
})
.returning({
id: videos.id,
objectKey: videos.objectKey,
bucket: videos.bucket,
status: videos.status,
createdAt: videos.createdAt,
expiresAt: videos.expiresAt,
let persistedRecording;
if (parsed.data.recordingId) {
const existingRecording = await db.query.recordings.findFirst({
where: and(eq(recordings.id, parsed.data.recordingId), eq(recordings.ownerUserId, authSession.user.id)),
});
if (!videoRecord) {
res.status(500).json({ message: 'Unable to persist video metadata' });
if (!existingRecording) {
res.status(404).json({ message: 'Recording not found' });
return;
}
[persistedRecording] = await db
.update(recordings)
.set({
objectKey,
bucket: minioBucket,
status: 'awaiting_upload',
updatedAt: now,
error: null,
})
.where(eq(recordings.id, existingRecording.id))
.returning();
} else {
[persistedRecording] = await db
.insert(recordings)
.values({
ownerUserId: authSession.user.id,
cameraDeviceId: parsed.data.deviceId,
requesterDeviceId: null,
eventId: parsed.data.eventId ?? null,
objectKey,
bucket: minioBucket,
status: 'awaiting_upload',
updatedAt: now,
})
.returning();
}
if (!persistedRecording) {
res.status(500).json({ message: 'Unable to persist recording metadata' });
return;
}
res.status(201).json({
message: 'Dummy upload URL generated',
message: 'Upload URL generated',
bucket: minioBucket,
objectKey,
uploadUrl,
expiresInSeconds: minioPresignedExpirySeconds,
video: videoRecord,
video: {
id: persistedRecording.id,
objectKey: persistedRecording.objectKey,
bucket: persistedRecording.bucket,
status: persistedRecording.status,
createdAt: persistedRecording.createdAt,
expiresAt,
},
});
});