fix(backend): add recording upload diagnostics
This commit is contained in:
@@ -43,7 +43,7 @@ const buildObjectKey = (userId: string, fileName: string, prefix?: string): stri
|
||||
router.use(requireAuth);
|
||||
|
||||
router.post('/upload-url', async (req, res) => {
|
||||
const parsed = uploadUrlSchema.safeParse(req.body);
|
||||
const parsed = uploadUrlSchema.safeParse(req.body);
|
||||
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({ message: 'Invalid request body', errors: parsed.error.flatten() });
|
||||
@@ -57,94 +57,120 @@ router.post('/upload-url', async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
await ensureMinioBucket();
|
||||
try {
|
||||
await ensureMinioBucket();
|
||||
|
||||
const device = await db.query.devices.findFirst({
|
||||
where: and(eq(devices.id, parsed.data.deviceId), eq(devices.userId, authSession.user.id)),
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
if (!device) {
|
||||
res.status(400).json({ message: 'Invalid deviceId for this user' });
|
||||
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)),
|
||||
const device = await db.query.devices.findFirst({
|
||||
where: and(eq(devices.id, parsed.data.deviceId), eq(devices.userId, authSession.user.id)),
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
if (!event) {
|
||||
res.status(400).json({ message: 'Invalid eventId for this user' });
|
||||
if (!device) {
|
||||
res.status(400).json({ message: 'Invalid deviceId 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);
|
||||
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 },
|
||||
});
|
||||
|
||||
let persistedRecording;
|
||||
if (!event) {
|
||||
res.status(400).json({ message: 'Invalid eventId for this user' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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)),
|
||||
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);
|
||||
|
||||
console.info('[recording.upload-url]', {
|
||||
ownerUserId: authSession.user.id,
|
||||
deviceId: parsed.data.deviceId,
|
||||
recordingId: parsed.data.recordingId ?? null,
|
||||
eventId: parsed.data.eventId ?? null,
|
||||
objectKey,
|
||||
bucket: minioBucket,
|
||||
expiresInSeconds: minioPresignedExpirySeconds,
|
||||
minioEndpoint: process.env.MINIO_ENDPOINT ?? 'localhost',
|
||||
minioPort: Number(process.env.MINIO_PORT ?? 9000),
|
||||
minioUseSSL: (process.env.MINIO_USE_SSL ?? 'false').toLowerCase() === 'true',
|
||||
});
|
||||
|
||||
if (!existingRecording) {
|
||||
res.status(404).json({ message: 'Recording not found' });
|
||||
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 (!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;
|
||||
}
|
||||
|
||||
[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();
|
||||
res.status(201).json({
|
||||
message: 'Upload URL generated',
|
||||
bucket: minioBucket,
|
||||
objectKey,
|
||||
uploadUrl,
|
||||
expiresInSeconds: minioPresignedExpirySeconds,
|
||||
video: {
|
||||
id: persistedRecording.id,
|
||||
objectKey: persistedRecording.objectKey,
|
||||
bucket: persistedRecording.bucket,
|
||||
status: persistedRecording.status,
|
||||
createdAt: persistedRecording.createdAt,
|
||||
expiresAt,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[recording.upload-url] failed', {
|
||||
ownerUserId: authSession.user.id,
|
||||
deviceId: parsed.data.deviceId,
|
||||
recordingId: parsed.data.recordingId ?? null,
|
||||
eventId: parsed.data.eventId ?? null,
|
||||
fileName: parsed.data.fileName,
|
||||
prefix: parsed.data.prefix ?? null,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!persistedRecording) {
|
||||
res.status(500).json({ message: 'Unable to persist recording metadata' });
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
message: 'Upload URL generated',
|
||||
bucket: minioBucket,
|
||||
objectKey,
|
||||
uploadUrl,
|
||||
expiresInSeconds: minioPresignedExpirySeconds,
|
||||
video: {
|
||||
id: persistedRecording.id,
|
||||
objectKey: persistedRecording.objectKey,
|
||||
bucket: persistedRecording.bucket,
|
||||
status: persistedRecording.status,
|
||||
createdAt: persistedRecording.createdAt,
|
||||
expiresAt,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/download-url', async (req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user