116 lines
3.1 KiB
JavaScript
116 lines
3.1 KiB
JavaScript
import { error } from '@sveltejs/kit';
|
|
import { db } from '$lib/server/db/index.js';
|
|
import { audioFile, inviteLink, participant } from '$lib/server/db/schema.js';
|
|
import { eq, isNull, and } from 'drizzle-orm';
|
|
import { getFromS3, getFromS3WithRange } from '$lib/server/s3.js';
|
|
import { parseStoredTags, matchesInviteTags } from '$lib/server/tag-utils.js';
|
|
|
|
export async function GET({ params, request, url, cookies }) {
|
|
const fileId = params.id;
|
|
const token = url.searchParams.get('token');
|
|
|
|
if (!token) {
|
|
throw error(400, 'Missing token');
|
|
}
|
|
|
|
const participantId = cookies.get(`participant-${token}`);
|
|
if (!participantId) {
|
|
throw error(403, 'Unauthorized');
|
|
}
|
|
|
|
const participants = await db
|
|
.select({ inviteToken: participant.inviteToken })
|
|
.from(participant)
|
|
.where(
|
|
and(
|
|
eq(participant.id, participantId),
|
|
eq(participant.inviteToken, token),
|
|
isNull(participant.deletedAt)
|
|
)
|
|
);
|
|
|
|
if (participants.length === 0) {
|
|
throw error(403, 'Unauthorized');
|
|
}
|
|
|
|
const invites = await db
|
|
.select({ tags: inviteLink.tags })
|
|
.from(inviteLink)
|
|
.where(
|
|
and(
|
|
eq(inviteLink.token, token),
|
|
isNull(inviteLink.deletedAt)
|
|
)
|
|
);
|
|
|
|
if (invites.length === 0) {
|
|
throw error(404, 'Invite not found');
|
|
}
|
|
|
|
const inviteTags = parseStoredTags(invites[0].tags);
|
|
|
|
// Get file metadata from database (only s3Key and contentType needed)
|
|
const files = await db.select({
|
|
s3Key: audioFile.s3Key,
|
|
contentType: audioFile.contentType,
|
|
fileSize: audioFile.fileSize,
|
|
tags: audioFile.tags
|
|
})
|
|
.from(audioFile)
|
|
.where(and(
|
|
eq(audioFile.id, fileId),
|
|
isNull(audioFile.deletedAt) // Only serve active audio files
|
|
));
|
|
|
|
if (files.length === 0) {
|
|
throw error(404, 'Audio file not found');
|
|
}
|
|
|
|
const file = files[0];
|
|
const audioTags = parseStoredTags(file.tags);
|
|
|
|
if (!matchesInviteTags(audioTags, inviteTags)) {
|
|
throw error(403, 'Unauthorized');
|
|
}
|
|
|
|
// Check if file has S3 key (new files) or fall back to error for old blob-based files
|
|
if (!file.s3Key) {
|
|
console.error(`Audio file ${fileId} missing S3 key - may be an old blob-based file`);
|
|
throw error(500, 'Audio file not available - please re-upload');
|
|
}
|
|
|
|
const range = request.headers.get('range');
|
|
|
|
try {
|
|
if (range) {
|
|
// Handle range requests for audio streaming
|
|
const s3Response = await getFromS3WithRange(file.s3Key, range);
|
|
|
|
return new Response(s3Response.stream, {
|
|
status: 206,
|
|
headers: {
|
|
'Content-Range': s3Response.contentRange,
|
|
'Accept-Ranges': s3Response.acceptRanges || 'bytes',
|
|
'Content-Length': s3Response.contentLength.toString(),
|
|
'Content-Type': s3Response.contentType || file.contentType || 'audio/mpeg'
|
|
}
|
|
});
|
|
} else {
|
|
// Handle regular requests
|
|
const s3Response = await getFromS3(file.s3Key);
|
|
|
|
return new Response(s3Response.stream, {
|
|
headers: {
|
|
'Content-Type': s3Response.contentType || file.contentType || 'audio/mpeg',
|
|
'Content-Length': (s3Response.contentLength || file.fileSize || 0).toString(),
|
|
'Accept-Ranges': 'bytes',
|
|
'Cache-Control': 'public, max-age=3600'
|
|
}
|
|
});
|
|
}
|
|
} catch (s3Error) {
|
|
console.error('Error fetching from S3:', s3Error);
|
|
throw error(500, 'Failed to fetch audio file');
|
|
}
|
|
}
|