diff --git a/components/Card.tsx b/components/Card.tsx index 3cc339a..7277ac0 100644 --- a/components/Card.tsx +++ b/components/Card.tsx @@ -12,7 +12,7 @@ export function Card( const backgroundStyle = { backgroundImage: `url(${image})`, backgroundSize: "cover", - boxShadow: "0px -60px 90px black inset, 0px 10px 20px #fff3 inset", + boxShadow: "0px -60px 90px black inset, 0px 10px 20px #fff1 inset", }; if (backgroundSize !== 100) { @@ -30,11 +30,14 @@ export function Card( sm:w-48 sm:h-48 w-[37vw] h-[37vw]" > - + {!image?.includes("placeholder.svg") && + ( + + )}
{/* Recipe Card content */} diff --git a/islands/KMenu.tsx b/islands/KMenu.tsx index 32844db..b76497b 100644 --- a/islands/KMenu.tsx +++ b/islands/KMenu.tsx @@ -1,5 +1,5 @@ import { Signal, useSignal } from "@preact/signals"; -import { useRef } from "preact/hooks"; +import { useEffect, useRef } from "preact/hooks"; import { useEventListener } from "@lib/hooks/useEventListener.ts"; import { menus } from "@islands/KMenu/commands.ts"; import { MenuEntry } from "@islands/KMenu/types.ts"; diff --git a/islands/KMenu/commands/add_movie_infos.ts b/islands/KMenu/commands/add_movie_infos.ts index 09a5e9a..be8db00 100644 --- a/islands/KMenu/commands/add_movie_infos.ts +++ b/islands/KMenu/commands/add_movie_infos.ts @@ -39,7 +39,7 @@ export const addMovieInfos: MenuEntry = { }; state.activeMenu.value = menuID; - + state.commandInput.value = ""; state.activeState.value = "normal"; }, visible: () => { diff --git a/islands/KMenu/commands/add_series_infos.ts b/islands/KMenu/commands/add_series_infos.ts index b901178..e533497 100644 --- a/islands/KMenu/commands/add_series_infos.ts +++ b/islands/KMenu/commands/add_series_infos.ts @@ -20,8 +20,6 @@ export const addSeriesInfo: MenuEntry = { const json = await response.json() as TMDBSeries[]; - console.log("Result", json); - const menuID = `result/${series.name}`; state.menus[menuID] = { @@ -42,12 +40,8 @@ export const addSeriesInfo: MenuEntry = { })), }; - await new Promise((res) => setTimeout(res, 20)); - + state.commandInput.value = ""; state.activeMenu.value = menuID; - - await new Promise((res) => setTimeout(res, 20)); - state.activeState.value = "normal"; }, visible: () => { diff --git a/lib/cache/cache.ts b/lib/cache/cache.ts index 31afaf4..ad47448 100644 --- a/lib/cache/cache.ts +++ b/lib/cache/cache.ts @@ -104,3 +104,27 @@ export async function set( } return res; } + +export const cacheFunction = async Promise)>( + { + fn, + id, + options = {}, + }: { + fn: T; + id: string; + options?: RedisOptions; + }, +): Promise>> => { + const cacheResult = await get(id) as string; + + if (cacheResult) { + return JSON.parse(cacheResult) as Awaited>; + } + + const result = await fn(); + + set(id, JSON.stringify(result), options); + + return result as Awaited>; +}; diff --git a/lib/resource/movies.ts b/lib/resource/movies.ts index 94705cd..4b0fe89 100644 --- a/lib/resource/movies.ts +++ b/lib/resource/movies.ts @@ -19,10 +19,10 @@ export type Movie = { }; }; -function renderMovie(movie: Movie) { +export function renderMovie(movie: Movie) { const meta = movie.meta; - if ("date" in meta) { - meta.date = formatDate(meta.date); + if ("date" in meta && typeof meta.date !== "string") { + meta.date = formatDate(meta.date) as unknown as Date; } return fixRenderedMarkdown(`${ diff --git a/lib/tmdb.ts b/lib/tmdb.ts index f90c058..947634c 100644 --- a/lib/tmdb.ts +++ b/lib/tmdb.ts @@ -2,29 +2,63 @@ import * as cache from "@lib/cache/cache.ts"; import { MovieDb } from "https://esm.sh/moviedb-promise@3.4.1"; const moviedb = new MovieDb(Deno.env.get("TMDB_API_KEY") || ""); -export function searchMovie(query: string) { - return moviedb.searchMovie({ query }); -} +const CACHE_INTERVAL = 1000 * 60 * 24 * 30; -export function searchTVShow(query: string) { - return moviedb.searchTv({ query }); -} +export const searchMovie = (query: string) => + cache.cacheFunction({ + fn: () => moviedb.searchMovie({ query }), + id: `query:moviesearch:${query}`, + options: { + expires: CACHE_INTERVAL, + }, + }); -export function getMovie(id: number) { - return moviedb.movieInfo({ id }); -} +export const searchTVShow = (query: string) => + cache.cacheFunction( + { + fn: () => moviedb.searchTv({ query }), + id: `query:tvshowsearch:${query}`, + options: { + expires: CACHE_INTERVAL, + }, + }, + ); -export function getSeries(id: number) { - return moviedb.tvInfo({ id }); -} +export const getMovie = (id: number) => + cache.cacheFunction({ + fn: () => moviedb.movieInfo({ id }), + id: `query:movie:${id}`, + options: { + expires: CACHE_INTERVAL, + }, + }); -export function getMovieCredits(id: number) { - return moviedb.movieCredits(id); -} +export const getSeries = (id: number) => + cache.cacheFunction({ + fn: () => moviedb.tvInfo({ id }), + id: `query:tvshow:${id}`, + options: { + expires: CACHE_INTERVAL, + }, + }); -export function getSeriesCredits(id: number) { - return moviedb.tvCredits(id); -} +export const getMovieCredits = (id: number) => + cache.cacheFunction({ + fn: () => moviedb.movieCredits(id), + id: `query:moviecredits:${id}`, + options: { + expires: CACHE_INTERVAL, + }, + }); + +export const getSeriesCredits = (id: number) => + cache.cacheFunction({ + fn: () => moviedb.tvCredits(id), + id: `query:tvshowcredits:${id}`, + options: { + expires: CACHE_INTERVAL, + }, + }); export async function getMovieGenre(id: number) { const genres = await cache.get("/genres/movies"); @@ -36,14 +70,8 @@ export async function getSeriesGenre(id: number) { } export async function getMoviePoster(id: string): Promise { - const cachedPoster = await cache.get("posters:" + id); - - if (cachedPoster) return cachedPoster as ArrayBuffer; - const posterUrl = `https://image.tmdb.org/t/p/original/${id}`; const response = await fetch(posterUrl); const poster = await response.arrayBuffer(); - - cache.set(`posters:${id}`, new Uint8Array()); return poster; } diff --git a/routes/api/movies/enhance/[name].ts b/routes/api/movies/enhance/[name].ts index 7fb0341..535541f 100644 --- a/routes/api/movies/enhance/[name].ts +++ b/routes/api/movies/enhance/[name].ts @@ -1,62 +1,35 @@ import { HandlerContext, Handlers } from "$fresh/server.ts"; -import { - createDocument, - getDocument, - transformDocument, -} from "@lib/documents.ts"; +import { createDocument } from "@lib/documents.ts"; import { fileExtension } from "https://deno.land/x/file_extension@v2.1.0/mod.ts"; -import { getMovie, type Movie } from "@lib/resource/movies.ts"; +import { createMovie, getMovie } from "@lib/resource/movies.ts"; import * as tmdb from "@lib/tmdb.ts"; -import { parse, stringify } from "https://deno.land/std@0.194.0/yaml/mod.ts"; -import { formatDate, safeFileName } from "@lib/string.ts"; +import { safeFileName } from "@lib/string.ts"; import { json } from "@lib/helpers.ts"; -import { AccessDeniedError, BadRequestError } from "@lib/errors.ts"; +import { + AccessDeniedError, + BadRequestError, + NotFoundError, +} from "@lib/errors.ts"; +import * as cache from "@lib/cache/cache.ts"; -async function updateMovieMetadata( - name: string, - metadata: Partial, -) { - const docId = `Media/movies/${name}.md`; - - let currentDoc = await getDocument(docId); - if (!currentDoc) return; - - if (!currentDoc.startsWith("---\n---\n")) { - currentDoc = `---\n---\n\n${currentDoc}`; - } - - const newDoc = transformDocument(currentDoc, (root) => { - const frontmatterNode = root.children.find((c) => c.type === "yaml"); - - const frontmatter = frontmatterNode?.value as string; - - const value = parse(frontmatter) as Movie["meta"]; - - const newValue = { - ...metadata, - date: formatDate(metadata.date), - ...value, - }; - - frontmatterNode.value = stringify(newValue); - - return root; - }); - - return createDocument(docId, newDoc); -} +const isString = (input: string | undefined): input is string => { + return typeof input === "string"; +}; const POST = async ( req: Request, ctx: HandlerContext, ): Promise => { - const movie = await getMovie(ctx.params.name); - const session = ctx.state.session; if (!session) { throw new AccessDeniedError(); } + const movie = await getMovie(ctx.params.name); + if (!movie) { + throw new NotFoundError(); + } + const body = await req.json(); const name = ctx.params.name; const { tmdbId } = body; @@ -69,33 +42,41 @@ const POST = async ( await tmdb.getMovieCredits(tmdbId); const releaseDate = movieDetails.release_date; - const posterPath = movieDetails.poster_path; + if (releaseDate && !movie.meta.date) { + movie.meta.date = new Date(releaseDate); + } + const director = movieCredits?.crew?.filter?.((person) => person.job === "Director")[0]; + if (director && !movie.meta.author) { + movie.meta.author = director.name; + } + + if (movieDetails.genres) { + movie.tags = [ + ...new Set([ + ...movie.tags.map((g) => g.toLowerCase()), + ...movieDetails.genres.map((g) => g.name?.toLowerCase()), + ].filter(isString)), + ]; + } let finalPath = ""; + const posterPath = movieDetails.poster_path; if (posterPath && !movie.meta.image) { const poster = await tmdb.getMoviePoster(posterPath); const extension = fileExtension(posterPath); finalPath = `Media/movies/images/${safeFileName(name)}_cover.${extension}`; await createDocument(finalPath, poster); + movie.meta.image = finalPath; } - const metadata = {} as Movie["meta"]; - if (releaseDate) { - metadata.date = new Date(releaseDate); - } - if (finalPath) { - metadata.image = finalPath; - } - if (director) { - metadata.author = director.name; - } + await createMovie(movie); - await updateMovieMetadata(name, metadata); + cache.del(`documents:Media:movies:${name}.md`); - return json(movieCredits); + return json(movie); }; export const handler: Handlers = { diff --git a/routes/api/series/enhance/[name].ts b/routes/api/series/enhance/[name].ts index 5a1b73a..ed22ec8 100644 --- a/routes/api/series/enhance/[name].ts +++ b/routes/api/series/enhance/[name].ts @@ -14,47 +14,12 @@ import { BadRequestError, NotFoundError, } from "@lib/errors.ts"; -import { getSeries, Series } from "@lib/resource/series.ts"; +import { createSeries, getSeries, Series } from "@lib/resource/series.ts"; +import * as cache from "@lib/cache/cache.ts"; -async function updateSeriesMetadata( - name: string, - metadata: Partial, -) { - const docId = `Media/series/${name}.md`; - - console.log({ docId, metadata }); - - let currentDoc = await getDocument(docId); - if (!currentDoc) { - throw new NotFoundError(); - } - - if (!currentDoc.startsWith("---\n---\n")) { - currentDoc = `---\n---\n\n${currentDoc}`; - } - - const newDoc = transformDocument(currentDoc, (root) => { - const frontmatterNode = root.children.find((c) => c.type === "yaml"); - - const frontmatter = frontmatterNode?.value as string; - - const value = parse(frontmatter) as Series["meta"]; - - const newValue = { - ...metadata, - date: formatDate(metadata.date), - ...value, - }; - - frontmatterNode.value = stringify(newValue); - - return root; - }); - - console.log(newDoc); - - return createDocument(docId, newDoc); -} +const isString = (input: string | undefined): input is string => { + return typeof input === "string"; +}; const POST = async ( req: Request, @@ -82,9 +47,24 @@ const POST = async ( await tmdb.getSeriesCredits(tmdbId); const releaseDate = seriesDetails.first_air_date; + if (releaseDate && series.meta.date) { + series.meta.date = new Date(releaseDate); + } const posterPath = seriesDetails.poster_path; const director = seriesCredits && seriesCredits.crew?.filter?.((person) => person.job === "Director")[0]; + if (director && director.name && !series.meta.author) { + series.meta.author = director.name; + } + + if (seriesDetails.genres) { + series.tags = [ + ...new Set([ + ...series.tags.map((t) => t.toLowerCase()), + ...seriesDetails.genres.map((g) => g.name?.toLowerCase()), + ].filter(isString)), + ]; + } let finalPath = ""; if (posterPath && !series.meta.image) { @@ -93,22 +73,13 @@ const POST = async ( finalPath = `Media/series/images/${safeFileName(name)}_cover.${extension}`; await createDocument(finalPath, poster); + series.meta.image = finalPath; } + await createSeries(series); - const metadata = {} as Series["meta"]; - if (releaseDate) { - metadata.date = new Date(releaseDate); - } - if (finalPath) { - metadata.image = finalPath; - } - if (director && director.name) { - metadata.author = director.name; - } + cache.del(`documents:Media:series:${name}.md`); - await updateSeriesMetadata(name, metadata); - - return json(seriesCredits); + return json(series); }; export const handler: Handlers = { diff --git a/routes/api/tmdb/query.ts b/routes/api/tmdb/query.ts index 598e931..e158efd 100644 --- a/routes/api/tmdb/query.ts +++ b/routes/api/tmdb/query.ts @@ -2,14 +2,6 @@ import { HandlerContext, Handlers } from "$fresh/server.ts"; import { searchMovie, searchTVShow } from "@lib/tmdb.ts"; import * as cache from "@lib/cache/cache.ts"; import { AccessDeniedError, BadRequestError } from "@lib/errors.ts"; -import { json } from "@lib/helpers.ts"; - -type CachedMovieQuery = { - lastUpdated: number; - data: unknown; -}; - -const CACHE_INTERVAL = 1000 * 60 * 24 * 30; const GET = async ( req: Request, @@ -30,27 +22,10 @@ const GET = async ( const type = u.searchParams.get("type") || "movie"; - const cacheId = `/${type}/query/${query}`; - - const cachedResponse = await cache.get(cacheId); - if ( - cachedResponse && Date.now() < (cachedResponse.lastUpdated + CACHE_INTERVAL) - ) { - return json(cachedResponse.data); - } - const res = type === "movie" ? await searchMovie(query) : await searchTVShow(query); - cache.set( - cacheId, - JSON.stringify({ - lastUpdated: Date.now(), - data: res, - }), - ); - return new Response(JSON.stringify(res.results)); };