157 lines
3.9 KiB
JavaScript
157 lines
3.9 KiB
JavaScript
import { error } from '@sveltejs/kit';
|
|
import { db } from '$lib/server/db/index.js';
|
|
import { inviteLink, participant, audioFile, rating, overallRating } from '$lib/server/db/schema.js';
|
|
import { eq, isNull, and, inArray } from 'drizzle-orm';
|
|
import { parseStoredTags, matchesInviteTags } from '$lib/server/tag-utils.js';
|
|
import { env } from '$env/dynamic/private';
|
|
|
|
export async function load({ url, cookies }) {
|
|
const token = url.searchParams.get('token');
|
|
|
|
if (!token) {
|
|
throw error(400, 'Invalid or missing invite token');
|
|
}
|
|
|
|
const invites = await db.select().from(inviteLink).where(
|
|
and(
|
|
eq(inviteLink.token, token),
|
|
isNull(inviteLink.deletedAt)
|
|
)
|
|
);
|
|
|
|
if (invites.length === 0) {
|
|
throw error(404, 'Invite link not found or has been deleted');
|
|
}
|
|
|
|
const { tags: inviteTagString, ...safeInvite } = invites[0];
|
|
const inviteTags = parseStoredTags(inviteTagString);
|
|
|
|
let participantId = cookies.get(`participant-${token}`);
|
|
let isExistingParticipant = false;
|
|
|
|
if (participantId) {
|
|
const participants = await db
|
|
.select()
|
|
.from(participant)
|
|
.where(
|
|
and(
|
|
eq(participant.id, participantId),
|
|
isNull(participant.deletedAt)
|
|
)
|
|
);
|
|
isExistingParticipant = participants.length > 0;
|
|
}
|
|
|
|
if (!isExistingParticipant) {
|
|
participantId = crypto.randomUUID();
|
|
|
|
await db.insert(participant).values({
|
|
id: participantId,
|
|
inviteToken: token,
|
|
sessionId: null,
|
|
createdAt: new Date()
|
|
});
|
|
|
|
await db
|
|
.update(inviteLink)
|
|
.set({
|
|
isUsed: true,
|
|
usedAt: new Date()
|
|
})
|
|
.where(eq(inviteLink.token, token));
|
|
|
|
cookies.set(`participant-${token}`, participantId, {
|
|
path: '/',
|
|
httpOnly: true,
|
|
secure: false,
|
|
sameSite: 'strict',
|
|
maxAge: 60 * 60 * 24 * 30
|
|
});
|
|
}
|
|
|
|
const audioRows = await db.select({
|
|
id: audioFile.id,
|
|
filename: audioFile.filename,
|
|
contentType: audioFile.contentType,
|
|
duration: audioFile.duration,
|
|
fileSize: audioFile.fileSize,
|
|
createdAt: audioFile.createdAt,
|
|
tags: audioFile.tags
|
|
})
|
|
.from(audioFile)
|
|
.where(isNull(audioFile.deletedAt)); // Only show active audio files
|
|
|
|
const filteredAudio = audioRows
|
|
.map((audio) => ({
|
|
data: {
|
|
id: audio.id,
|
|
filename: audio.filename,
|
|
contentType: audio.contentType,
|
|
duration: audio.duration,
|
|
fileSize: audio.fileSize,
|
|
createdAt: audio.createdAt
|
|
},
|
|
tags: parseStoredTags(audio.tags)
|
|
}))
|
|
.filter(({ tags }) => matchesInviteTags(tags, inviteTags))
|
|
.map(({ data }) => data);
|
|
|
|
const allowedAudioIds = filteredAudio.map((file) => file.id);
|
|
|
|
const displayContinuousRating = env.DISPLAY_CONT_RATING !== 'false';
|
|
let completedRatings = [];
|
|
if (allowedAudioIds.length > 0) {
|
|
if (displayContinuousRating) {
|
|
completedRatings = await db
|
|
.select({
|
|
audioFileId: rating.audioFileId
|
|
})
|
|
.from(rating)
|
|
.innerJoin(audioFile, and(
|
|
eq(rating.audioFileId, audioFile.id),
|
|
isNull(audioFile.deletedAt)
|
|
))
|
|
.where(
|
|
and(
|
|
eq(rating.participantId, participantId),
|
|
eq(rating.isCompleted, true),
|
|
isNull(rating.deletedAt),
|
|
inArray(rating.audioFileId, allowedAudioIds)
|
|
)
|
|
);
|
|
} else {
|
|
completedRatings = await db
|
|
.select({ audioFileId: overallRating.audioFileId })
|
|
.from(overallRating)
|
|
.innerJoin(audioFile, and(
|
|
eq(overallRating.audioFileId, audioFile.id),
|
|
isNull(audioFile.deletedAt)
|
|
))
|
|
.where(
|
|
and(
|
|
eq(overallRating.participantId, participantId),
|
|
isNull(overallRating.deletedAt),
|
|
inArray(overallRating.audioFileId, allowedAudioIds)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
const completedAudioIds = new Set(completedRatings.map((r) => r.audioFileId));
|
|
|
|
const audioFilesWithStatus = filteredAudio.map(file => ({
|
|
...file,
|
|
isCompleted: completedAudioIds.has(file.id)
|
|
}));
|
|
|
|
return {
|
|
invite: safeInvite,
|
|
participantId,
|
|
audioFiles: audioFilesWithStatus,
|
|
token,
|
|
completedCount: completedRatings.length,
|
|
totalCount: filteredAudio.length,
|
|
displayContinuousRating
|
|
};
|
|
}
|