Add movies & TV tracker: movies schema, Netflix/Letterboxd/Plex/JSON/recs importers, TMDB matching, stats, owner visibility
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { isAdmin } from '#lib/server/authorization.js';
|
||||
import { isOwner } from '#lib/server/owner.js';
|
||||
import { loadInactiveGames } from '#lib/server/games/active.js';
|
||||
import { loadHiddenTrackers } from '#lib/server/movies/site.js';
|
||||
import { dev } from '$app/env';
|
||||
import { siteEnvironment } from '#lib/title.js';
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
@@ -15,5 +16,7 @@ export const load: LayoutServerLoad = async (event) => ({
|
||||
version: process.env.APP_VERSION || 'dev',
|
||||
environment,
|
||||
/** Games I don't play now: navigation lists them under "non-active" (#lib/projects). */
|
||||
inactiveGames: await loadInactiveGames()
|
||||
inactiveGames: await loadInactiveGames(),
|
||||
/** Trackers switched to private by their owner setting (movies): hidden from everyone else. */
|
||||
hiddenTrackers: await loadHiddenTrackers()
|
||||
});
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
>
|
||||
</h2>
|
||||
<CatalogGrid
|
||||
items={visibleTrackers(data.isOwner, data.inactiveGames)}
|
||||
items={visibleTrackers(data.isOwner, data.inactiveGames, data.hiddenTrackers)}
|
||||
variant="tracker"
|
||||
subtitleClass="mt-1 block text-base text-[var(--site-muted)]"
|
||||
/>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { races } from '#lib/derby/races.js';
|
||||
import { COURSES } from '#lib/language-learner/courses/index.js';
|
||||
import { gameTrackers } from '#lib/projects.js';
|
||||
import { and, eq, isNull, ne } from 'drizzle-orm';
|
||||
import { getVisibility } from '#lib/server/movies/site.js';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
const staticPaths = [
|
||||
@@ -41,6 +42,7 @@ const staticPaths = [
|
||||
'/trackers/wildlife',
|
||||
'/trackers/wildlife/groups',
|
||||
'/trackers/music',
|
||||
'/trackers/movies',
|
||||
'/trackers/pc',
|
||||
'/trackers/games',
|
||||
...gameTrackers.map((g) => g.href),
|
||||
@@ -68,7 +70,7 @@ function escapeXml(value: string): string {
|
||||
}
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
const [articles, lists, books] = await Promise.all([
|
||||
const [articles, lists, books, moviesVisibility] = await Promise.all([
|
||||
db
|
||||
.select({ slug: blogArticles.slug, updatedAt: blogArticles.updatedAt })
|
||||
.from(blogArticles)
|
||||
@@ -78,10 +80,13 @@ export const GET: RequestHandler = async () => {
|
||||
.select({ id: bookWorks.id, updatedAt: bookWorks.updatedAt })
|
||||
.from(bookWorks)
|
||||
// Articles are links to other sites; their pages here are thin.
|
||||
.where(and(ne(bookWorks.kind, 'article'), isNull(bookWorks.archivedAt)))
|
||||
.where(and(ne(bookWorks.kind, 'article'), isNull(bookWorks.archivedAt))),
|
||||
getVisibility()
|
||||
]);
|
||||
const entries: { path: string; lastmod?: string }[] = [
|
||||
...staticPaths.map((path) => ({ path })),
|
||||
...staticPaths
|
||||
.filter((path) => moviesVisibility === 'public' || path !== '/trackers/movies')
|
||||
.map((path) => ({ path })),
|
||||
...races.map((race) => ({ path: `/projects/derby/${race.slug}` })),
|
||||
...articles.map((article) => ({
|
||||
path: `/blog/${encodeURIComponent(article.slug)}`,
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
// Same list, order and icons as the header Trackers dropdown (owner-only ones only when
|
||||
// the header shows them). Spotting is a group tile with one-click chips per sub-tracker.
|
||||
const trackers = $derived(visibleTrackers(data.isOwner, data.inactiveGames));
|
||||
const trackers = $derived(visibleTrackers(data.isOwner, data.inactiveGames, data.hiddenTrackers));
|
||||
// Search-only shortcuts that aren't trackers of their own.
|
||||
const extraHits = [
|
||||
{ name: 'Weigh-in', href: '/trackers/health#body' },
|
||||
|
||||
@@ -10,5 +10,9 @@
|
||||
<p class="mb-10 text-[var(--site-muted)]">
|
||||
Personal data I keep track of: games, money, houses, books, prices, animals and lists.
|
||||
</p>
|
||||
<CatalogGrid items={visibleTrackers(data.isOwner, data.inactiveGames)} variant="tracker" wide />
|
||||
<CatalogGrid
|
||||
items={visibleTrackers(data.isOwner, data.inactiveGames, data.hiddenTrackers)}
|
||||
variant="tracker"
|
||||
wide
|
||||
/>
|
||||
</div>
|
||||
|
||||
52
src/routes/trackers/movies/+page.server.ts
Normal file
52
src/routes/trackers/movies/+page.server.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { error, fail } from '@sveltejs/kit';
|
||||
import { isOwner, requireOwner } from '#lib/server/owner.js';
|
||||
import {
|
||||
addTitle,
|
||||
deleteRec,
|
||||
deleteTitle,
|
||||
getVisibility,
|
||||
loadRecs,
|
||||
loadStats,
|
||||
loadTitles,
|
||||
setVisibility,
|
||||
updateTitle
|
||||
} from '#lib/server/movies/site.js';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
// Movies & TV: public by default; the owner's visibility switch can make it owner-only.
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const owner = isOwner(event);
|
||||
const visibility = await getVisibility();
|
||||
if (visibility === 'private' && !owner) error(404, 'Not found');
|
||||
if (owner) event.setHeaders({ 'cache-control': 'private, no-store' });
|
||||
const [titles, stats, recs] = await Promise.all([loadTitles(), loadStats(), loadRecs()]);
|
||||
return {
|
||||
owner,
|
||||
visibility,
|
||||
titles,
|
||||
stats,
|
||||
recs,
|
||||
seo: {
|
||||
title: 'Movies & TV tracker',
|
||||
description:
|
||||
'The movies and TV shows we watch: what we are watching, ratings, rewatches and stats per month, genre and platform.'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
type Result = { ok: true; id?: number } | { ok: false; message: string };
|
||||
const run =
|
||||
(fn: (form: FormData) => Promise<Result>) => async (event: Parameters<Actions[string]>[0]) => {
|
||||
requireOwner(event);
|
||||
const res = await fn(await event.request.formData());
|
||||
if (!res.ok) return fail(400, { message: res.message });
|
||||
return res;
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
update: run(updateTitle),
|
||||
add: run(addTitle),
|
||||
delete: run(deleteTitle),
|
||||
deleteRec: run(deleteRec),
|
||||
visibility: run(setVisibility)
|
||||
};
|
||||
1529
src/routes/trackers/movies/+page.svelte
Normal file
1529
src/routes/trackers/movies/+page.svelte
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user