first working demo

This commit is contained in:
2025-07-22 17:03:36 +02:00
parent f58e9ef5a2
commit b084910dc0
52 changed files with 11455 additions and 138 deletions

View File

@@ -0,0 +1,114 @@
import { error } from '@sveltejs/kit';
import { db } from '$lib/server/db/index.js';
import { inviteLink, participant, audioFile, rating } from '$lib/server/db/schema.js';
import { eq, isNull, and } from 'drizzle-orm';
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 invite = invites[0];
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 audioFiles = await db.select({
id: audioFile.id,
filename: audioFile.filename,
contentType: audioFile.contentType,
duration: audioFile.duration,
fileSize: audioFile.fileSize,
createdAt: audioFile.createdAt
})
.from(audioFile)
.where(isNull(audioFile.deletedAt)); // Only show active audio files
// Get completed ratings for this participant (only active, non-deleted ratings for active audio files)
const completedRatings = await db
.select({
audioFileId: rating.audioFileId
})
.from(rating)
.innerJoin(audioFile, and(
eq(rating.audioFileId, audioFile.id),
isNull(audioFile.deletedAt) // Only count ratings for active audio files
))
.where(
and(
eq(rating.participantId, participantId),
eq(rating.isCompleted, true),
isNull(rating.deletedAt) // Only count active ratings
)
);
const completedAudioIds = new Set(completedRatings.map(r => r.audioFileId));
// Add completion status to audio files
const audioFilesWithStatus = audioFiles.map(file => ({
...file,
isCompleted: completedAudioIds.has(file.id)
}));
return {
invite,
participantId,
audioFiles: audioFilesWithStatus,
token,
completedCount: completedRatings.length,
totalCount: audioFiles.length
};
}