mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 05:05:38 -04:00
Replace server actions with API routes and add SWR data-fetching hooks
- Convert discover, stats, status, and system-health server actions to proper API route handlers under `app/api/`; delete `lib/actions/explore.ts`, `lib/actions/settings.ts`, and `lib/actions/setup.ts` - Add `use-discover`, `use-stats`, and `use-system-health` SWR hooks that call the new routes; update `command-palette`, `title-card`, `update-toast`, and `stats-display` to consume them - Lift auth centering wrapper from individual login/register pages into `(auth)/layout.tsx`; switch both pages from `auth.api.getSession` to the cached `getSession()` helper - Relocate setup wizard from `app/(auth)/setup/` to `app/setup/` (outside auth group) with dedicated `copy-button` and `refresh-button` client components - Move `not-found.tsx` and `error.tsx` to app root so they apply globally instead of only within the pages route group
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import path from "node:path";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { getSession } from "@/lib/auth/session";
|
||||
import { AVATAR_DIR } from "@/lib/constants";
|
||||
|
||||
const IMMUTABLE_CACHE = "public, max-age=31536000, immutable";
|
||||
@@ -8,6 +9,11 @@ export async function GET(
|
||||
_req: NextRequest,
|
||||
{ params }: { params: Promise<{ userId: string }> },
|
||||
) {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { userId } = await params;
|
||||
|
||||
// Sanitize userId to prevent path traversal
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { getSession } from "@/lib/auth/session";
|
||||
import { isTmdbConfigured } from "@/lib/config";
|
||||
import {
|
||||
getEpisodeProgressByTmdbIds,
|
||||
getUserStatusesByTmdbIds,
|
||||
} from "@/lib/services/tracking";
|
||||
import { discover } from "@/lib/tmdb/client";
|
||||
import { tmdbImageUrl } from "@/lib/tmdb/image";
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
if (!isTmdbConfigured()) {
|
||||
return NextResponse.json(
|
||||
{ error: "TMDB API key is not configured." },
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
|
||||
const mediaType = req.nextUrl.searchParams.get("mediaType");
|
||||
const genreId = req.nextUrl.searchParams.get("genreId");
|
||||
|
||||
if (mediaType !== "movie" && mediaType !== "tv") {
|
||||
return NextResponse.json(
|
||||
{ error: "mediaType must be movie or tv" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const parsedGenreId = Number(genreId);
|
||||
if (!genreId || !Number.isFinite(parsedGenreId)) {
|
||||
return NextResponse.json(
|
||||
{ error: "genreId must be a number" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await discover(mediaType, {
|
||||
sort_by: "popularity.desc",
|
||||
"vote_count.gte": "50",
|
||||
with_genres: String(parsedGenreId),
|
||||
});
|
||||
|
||||
type DiscoverResult = NonNullable<typeof results.results>[number] & {
|
||||
title?: string;
|
||||
name?: string;
|
||||
release_date?: string;
|
||||
first_air_date?: string;
|
||||
};
|
||||
|
||||
const items = ((results.results ?? []) as DiscoverResult[])
|
||||
.filter((r) => r.poster_path)
|
||||
.map((r) => ({
|
||||
tmdbId: r.id,
|
||||
type: mediaType,
|
||||
title: r.title ?? r.name ?? "",
|
||||
posterPath: tmdbImageUrl(r.poster_path ?? null, "posters"),
|
||||
releaseDate: r.release_date ?? r.first_air_date ?? null,
|
||||
voteAverage: r.vote_average,
|
||||
}));
|
||||
|
||||
const lookups = items.map((r) => ({ tmdbId: r.tmdbId, type: r.type }));
|
||||
const [userStatuses, episodeProgress] =
|
||||
lookups.length > 0
|
||||
? await Promise.all([
|
||||
getUserStatusesByTmdbIds(session.user.id, lookups),
|
||||
getEpisodeProgressByTmdbIds(session.user.id, lookups),
|
||||
])
|
||||
: [{}, {}];
|
||||
|
||||
return NextResponse.json({ items, userStatuses, episodeProgress });
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to fetch discover results" },
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { getSession } from "@/lib/auth/session";
|
||||
import { getWatchCount, getWatchHistory } from "@/lib/services/discovery";
|
||||
|
||||
const paramsSchema = z.object({
|
||||
type: z.enum(["movies", "episodes"]),
|
||||
period: z.enum(["today", "this_week", "this_month", "this_year"]),
|
||||
});
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
const parsed = paramsSchema.safeParse({
|
||||
type: req.nextUrl.searchParams.get("type"),
|
||||
period: req.nextUrl.searchParams.get("period"),
|
||||
});
|
||||
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid type or period parameter" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const { type, period } = parsed.data;
|
||||
const count = getWatchCount(session.user.id, type, period);
|
||||
const history = getWatchHistory(session.user.id, type, period);
|
||||
|
||||
return NextResponse.json({ count, history });
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getSession } from "@/lib/auth/session";
|
||||
import { isTmdbConfigured } from "@/lib/config";
|
||||
import { getSystemHealth } from "@/lib/services/system-health";
|
||||
|
||||
export async function GET() {
|
||||
const tmdbConfigured = isTmdbConfigured();
|
||||
|
||||
const session = await getSession();
|
||||
if (session?.user.role === "admin") {
|
||||
const health = await getSystemHealth();
|
||||
return NextResponse.json({ tmdbConfigured, health });
|
||||
}
|
||||
|
||||
return NextResponse.json({ tmdbConfigured });
|
||||
}
|
||||
Reference in New Issue
Block a user