Removes Directus 11 and replaces it with a lean, purpose-built backend: - packages/backend/: Fastify v5 + GraphQL Yoga v5 + Pothos (code-first) with Drizzle ORM, Redis sessions (session_token cookie), argon2 auth, Nodemailer, fluent-ffmpeg, and full gamification system ported from bundle - Frontend: @directus/sdk replaced by graphql-request v7; services.ts fully rewritten with identical signatures; directus.ts now re-exports from api.ts - Cookie renamed directus_session_token → session_token - Dev proxy target updated 8055 → 4000 - compose.yml: Directus service removed, backend service added (port 4000) - Dockerfile.backend: new multi-stage image with ffmpeg - Dockerfile: bundle build step and ffmpeg removed from frontend image - data-migration.ts: one-time script to migrate all Directus/sexy_ tables Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
61 lines
1.5 KiB
TypeScript
61 lines
1.5 KiB
TypeScript
import { redirect } from "@sveltejs/kit";
|
|
import type { PageServerLoad } from "./$types";
|
|
import { gql } from "graphql-request";
|
|
import { getGraphQLClient } from "$lib/api";
|
|
|
|
const LEADERBOARD_QUERY = gql`
|
|
query Leaderboard($limit: Int, $offset: Int) {
|
|
leaderboard(limit: $limit, offset: $offset) {
|
|
user_id display_name avatar
|
|
total_weighted_points total_raw_points
|
|
recordings_count playbacks_count achievements_count rank
|
|
}
|
|
}
|
|
`;
|
|
|
|
export const load: PageServerLoad = async ({ fetch, url, locals }) => {
|
|
// Guard: Redirect to login if not authenticated
|
|
if (!locals.authStatus.authenticated) {
|
|
throw redirect(302, "/login");
|
|
}
|
|
|
|
try {
|
|
const limit = parseInt(url.searchParams.get("limit") || "100");
|
|
const offset = parseInt(url.searchParams.get("offset") || "0");
|
|
|
|
const client = getGraphQLClient(fetch);
|
|
const data = await client.request<{
|
|
leaderboard: {
|
|
user_id: string;
|
|
display_name: string | null;
|
|
avatar: string | null;
|
|
total_weighted_points: number | null;
|
|
total_raw_points: number | null;
|
|
recordings_count: number | null;
|
|
playbacks_count: number | null;
|
|
achievements_count: number | null;
|
|
rank: number;
|
|
}[];
|
|
}>(LEADERBOARD_QUERY, { limit, offset });
|
|
|
|
return {
|
|
leaderboard: data.leaderboard || [],
|
|
pagination: {
|
|
limit,
|
|
offset,
|
|
hasMore: data.leaderboard?.length === limit,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error("Leaderboard load error:", error);
|
|
return {
|
|
leaderboard: [],
|
|
pagination: {
|
|
limit: 100,
|
|
offset: 0,
|
|
hasMore: false,
|
|
},
|
|
};
|
|
}
|
|
};
|