mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 02:45:39 -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,35 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import { requireSession } from "@/lib/auth/session";
|
||||
import { discover } from "@/lib/tmdb/client";
|
||||
import { tmdbImageUrl } from "@/lib/tmdb/image";
|
||||
|
||||
export async function discoverByGenre(
|
||||
mediaType: "movie" | "tv",
|
||||
genreId: number,
|
||||
) {
|
||||
await requireSession();
|
||||
const results = await discover(mediaType, {
|
||||
sort_by: "popularity.desc",
|
||||
"vote_count.gte": "50",
|
||||
with_genres: String(genreId),
|
||||
});
|
||||
// Discover may return movie (title, release_date) or TV (name, first_air_date)
|
||||
// fields depending on mediaType. The schema types them separately, so widen.
|
||||
type DiscoverResult = NonNullable<typeof results.results>[number] & {
|
||||
title?: string;
|
||||
name?: string;
|
||||
release_date?: string;
|
||||
first_air_date?: string;
|
||||
};
|
||||
return ((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,
|
||||
}));
|
||||
}
|
||||
@@ -19,14 +19,6 @@ import {
|
||||
restoreFromBackup,
|
||||
} from "@/lib/services/backup";
|
||||
import { getSetting, setSetting } from "@/lib/services/settings";
|
||||
import {
|
||||
getSystemHealth,
|
||||
type SystemHealthData,
|
||||
} from "@/lib/services/system-health";
|
||||
import {
|
||||
getCachedUpdateCheck,
|
||||
type UpdateCheckResult,
|
||||
} from "@/lib/services/update-check";
|
||||
|
||||
const providerSchema = z.enum(["plex", "jellyfin", "emby", "sonarr", "radarr"]);
|
||||
|
||||
@@ -222,13 +214,6 @@ export async function setBackupScheduleAction(
|
||||
rescheduleBackup();
|
||||
}
|
||||
|
||||
// --- System health actions ---
|
||||
|
||||
export async function getSystemHealthAction(): Promise<SystemHealthData> {
|
||||
await requireAdmin();
|
||||
return getSystemHealth();
|
||||
}
|
||||
|
||||
// --- Job trigger action ---
|
||||
|
||||
export async function triggerJobAction(
|
||||
@@ -256,17 +241,6 @@ export async function restoreBackupAction(formData: FormData): Promise<void> {
|
||||
await restoreFromBackup(buffer);
|
||||
}
|
||||
|
||||
// --- Update check action ---
|
||||
|
||||
export async function getUpdateCheckAction(): Promise<UpdateCheckResult | null> {
|
||||
try {
|
||||
await requireAdmin();
|
||||
return getCachedUpdateCheck();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Avatar actions ---
|
||||
|
||||
const MAX_AVATAR_SIZE = 2 * 1024 * 1024; // 2MB
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import { isTmdbConfigured } from "@/lib/config";
|
||||
|
||||
export async function checkTmdbConfigured() {
|
||||
return isTmdbConfigured();
|
||||
}
|
||||
@@ -1,38 +1,11 @@
|
||||
"use server";
|
||||
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { getSession, requireSession } from "@/lib/auth/session";
|
||||
import { requireSession } from "@/lib/auth/session";
|
||||
import { db } from "@/lib/db/client";
|
||||
import { userTitleStatus } from "@/lib/db/schema";
|
||||
import {
|
||||
getWatchCount,
|
||||
getWatchHistory,
|
||||
type HistoryBucket,
|
||||
type TimePeriod,
|
||||
} from "@/lib/services/discovery";
|
||||
import { getOrFetchTitleByTmdbId } from "@/lib/services/metadata";
|
||||
import {
|
||||
getEpisodeProgressByTmdbIds,
|
||||
getUserStatusesByTmdbIds,
|
||||
setTitleStatus,
|
||||
} from "@/lib/services/tracking";
|
||||
|
||||
export async function fetchUserStatuses(
|
||||
tmdbIds: { tmdbId: number; type: string }[],
|
||||
): Promise<Record<string, "watchlist" | "in_progress" | "completed">> {
|
||||
const session = await getSession();
|
||||
if (!session) return {};
|
||||
return getUserStatusesByTmdbIds(session.user.id, tmdbIds);
|
||||
}
|
||||
|
||||
export async function fetchEpisodeProgress(
|
||||
tmdbIds: { tmdbId: number; type: string }[],
|
||||
): Promise<Record<string, { watched: number; total: number }>> {
|
||||
const session = await getSession();
|
||||
if (!session) return {};
|
||||
return getEpisodeProgressByTmdbIds(session.user.id, tmdbIds);
|
||||
}
|
||||
import { setTitleStatus } from "@/lib/services/tracking";
|
||||
|
||||
export async function quickAddToWatchlist(
|
||||
tmdbId: number,
|
||||
@@ -62,19 +35,3 @@ export async function quickAddToWatchlist(
|
||||
setTitleStatus(userId, title.id, "watchlist");
|
||||
return { success: true, titleId: title.id, alreadyAdded: false };
|
||||
}
|
||||
|
||||
const statsSchema = z.object({
|
||||
type: z.enum(["movies", "episodes"]),
|
||||
period: z.enum(["today", "this_week", "this_month", "this_year"]),
|
||||
});
|
||||
|
||||
export async function getStatsAction(
|
||||
type: "movies" | "episodes",
|
||||
period: TimePeriod,
|
||||
): Promise<{ count: number; history: HistoryBucket[] }> {
|
||||
const session = await requireSession();
|
||||
const parsed = statsSchema.parse({ type, period });
|
||||
const count = getWatchCount(session.user.id, parsed.type, parsed.period);
|
||||
const history = getWatchHistory(session.user.id, parsed.type, parsed.period);
|
||||
return { count, history };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user