diff --git a/components/layouts/main.tsx b/components/layouts/main.tsx
index 75cf5e0..7e843f5 100644
--- a/components/layouts/main.tsx
+++ b/components/layouts/main.tsx
@@ -20,48 +20,18 @@ export type Props = {
export const MainLayout = (
{ children, url, title, context, searchResults }: Props,
) => {
- const hasSearch = url.search.includes("q=");
+ const _url = typeof url === "string" ? new URL(url) : url;
+ const hasSearch = _url.search.includes("q=");
- return (
-
-
-
-
- {title &&
-
{title}}
-
-
-
- {hasSearch && (
-
- )}
- {!hasSearch && children}
-
-
- );
+ if (hasSearch) {
+ return (
+
+ );
+ }
+
+ return <>{children}>;
};
diff --git a/deno.json b/deno.json
index d5907e8..159e720 100644
--- a/deno.json
+++ b/deno.json
@@ -1,10 +1,11 @@
{
"lock": false,
"tasks": {
+ "check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
"start": "deno run -A --watch=static/,routes/ dev.ts",
- "update": "deno run -A -r https://fresh.deno.dev/update .",
"build": "deno run -A dev.ts build",
- "preview": "deno run -A main.ts"
+ "preview": "deno run -A main.ts",
+ "update": "deno run -A -r https://fresh.deno.dev/update ."
},
"lint": {
"rules": {
@@ -27,7 +28,7 @@
"@islands": "./islands",
"@islands/": "./islands/",
"zod": "https://deno.land/x/zod@v3.21.4/mod.ts",
- "$fresh/": "https://deno.land/x/fresh@1.4.2/",
+ "$fresh/": "https://deno.land/x/fresh@1.4.3/",
"preact": "https://esm.sh/preact@10.15.1",
"preact/": "https://esm.sh/preact@10.15.1/",
"preact-render-to-string": "https://esm.sh/*preact-render-to-string@6.2.1",
@@ -46,4 +47,4 @@
"_fresh"
]
}
-}
\ No newline at end of file
+}
diff --git a/dev.ts b/dev.ts
index 2d85d6c..1fe3e34 100755
--- a/dev.ts
+++ b/dev.ts
@@ -1,5 +1,6 @@
#!/usr/bin/env -S deno run -A --watch=static/,routes/
import dev from "$fresh/dev.ts";
+import config from "./fresh.config.ts";
-await dev(import.meta.url, "./main.ts");
+await dev(import.meta.url, "./main.ts", config);
diff --git a/fresh.config.ts b/fresh.config.ts
new file mode 100644
index 0000000..fd48d1b
--- /dev/null
+++ b/fresh.config.ts
@@ -0,0 +1,6 @@
+import { defineConfig } from "$fresh/server.ts";
+import twindPlugin from "$fresh/plugins/twind.ts";
+import twindConfig from "./twind.config.ts";
+export default defineConfig({
+ plugins: [twindPlugin(twindConfig)],
+});
diff --git a/fresh.gen.ts b/fresh.gen.ts
index 8c9cf6c..7652e7b 100644
--- a/fresh.gen.ts
+++ b/fresh.gen.ts
@@ -4,42 +4,43 @@
import * as $0 from "./routes/_404.tsx";
import * as $1 from "./routes/_app.tsx";
-import * as $2 from "./routes/_middleware.ts";
-import * as $3 from "./routes/admin/log/index.tsx";
-import * as $4 from "./routes/admin/performance/index.tsx";
-import * as $5 from "./routes/api/articles/[name].ts";
-import * as $6 from "./routes/api/articles/create/index.ts";
-import * as $7 from "./routes/api/articles/index.ts";
-import * as $8 from "./routes/api/auth/callback.ts";
-import * as $9 from "./routes/api/auth/login.ts";
-import * as $10 from "./routes/api/auth/logout.ts";
-import * as $11 from "./routes/api/cache/index.ts";
-import * as $12 from "./routes/api/images/index.ts";
-import * as $13 from "./routes/api/index.ts";
-import * as $14 from "./routes/api/logs.ts";
-import * as $15 from "./routes/api/movies/[name].ts";
-import * as $16 from "./routes/api/movies/enhance/[name].ts";
-import * as $17 from "./routes/api/movies/index.ts";
-import * as $18 from "./routes/api/query/index.ts";
-import * as $19 from "./routes/api/query/sync.ts";
-import * as $20 from "./routes/api/recipes/[name].ts";
-import * as $21 from "./routes/api/recipes/index.ts";
-import * as $22 from "./routes/api/resources.ts";
-import * as $23 from "./routes/api/series/[name].ts";
-import * as $24 from "./routes/api/series/enhance/[name].ts";
-import * as $25 from "./routes/api/series/index.ts";
-import * as $26 from "./routes/api/tmdb/[id].ts";
-import * as $27 from "./routes/api/tmdb/credits/[id].ts";
-import * as $28 from "./routes/api/tmdb/query.ts";
-import * as $29 from "./routes/articles/[name].tsx";
-import * as $30 from "./routes/articles/index.tsx";
-import * as $31 from "./routes/index.tsx";
-import * as $32 from "./routes/movies/[name].tsx";
-import * as $33 from "./routes/movies/index.tsx";
-import * as $34 from "./routes/recipes/[name].tsx";
-import * as $35 from "./routes/recipes/index.tsx";
-import * as $36 from "./routes/series/[name].tsx";
-import * as $37 from "./routes/series/index.tsx";
+import * as $2 from "./routes/_layout.tsx";
+import * as $3 from "./routes/_middleware.ts";
+import * as $4 from "./routes/admin/log/index.tsx";
+import * as $5 from "./routes/admin/performance/index.tsx";
+import * as $6 from "./routes/api/articles/[name].ts";
+import * as $7 from "./routes/api/articles/create/index.ts";
+import * as $8 from "./routes/api/articles/index.ts";
+import * as $9 from "./routes/api/auth/callback.ts";
+import * as $10 from "./routes/api/auth/login.ts";
+import * as $11 from "./routes/api/auth/logout.ts";
+import * as $12 from "./routes/api/cache/index.ts";
+import * as $13 from "./routes/api/images/index.ts";
+import * as $14 from "./routes/api/index.ts";
+import * as $15 from "./routes/api/logs.ts";
+import * as $16 from "./routes/api/movies/[name].ts";
+import * as $17 from "./routes/api/movies/enhance/[name].ts";
+import * as $18 from "./routes/api/movies/index.ts";
+import * as $19 from "./routes/api/query/index.ts";
+import * as $20 from "./routes/api/query/sync.ts";
+import * as $21 from "./routes/api/recipes/[name].ts";
+import * as $22 from "./routes/api/recipes/index.ts";
+import * as $23 from "./routes/api/resources.ts";
+import * as $24 from "./routes/api/series/[name].ts";
+import * as $25 from "./routes/api/series/enhance/[name].ts";
+import * as $26 from "./routes/api/series/index.ts";
+import * as $27 from "./routes/api/tmdb/[id].ts";
+import * as $28 from "./routes/api/tmdb/credits/[id].ts";
+import * as $29 from "./routes/api/tmdb/query.ts";
+import * as $30 from "./routes/articles/[name].tsx";
+import * as $31 from "./routes/articles/index.tsx";
+import * as $32 from "./routes/index.tsx";
+import * as $33 from "./routes/movies/[name].tsx";
+import * as $34 from "./routes/movies/index.tsx";
+import * as $35 from "./routes/recipes/[name].tsx";
+import * as $36 from "./routes/recipes/index.tsx";
+import * as $37 from "./routes/series/[name].tsx";
+import * as $38 from "./routes/series/index.tsx";
import * as $$0 from "./islands/Counter.tsx";
import * as $$1 from "./islands/IngredientsList.tsx";
import * as $$2 from "./islands/KMenu.tsx";
@@ -56,42 +57,43 @@ const manifest = {
routes: {
"./routes/_404.tsx": $0,
"./routes/_app.tsx": $1,
- "./routes/_middleware.ts": $2,
- "./routes/admin/log/index.tsx": $3,
- "./routes/admin/performance/index.tsx": $4,
- "./routes/api/articles/[name].ts": $5,
- "./routes/api/articles/create/index.ts": $6,
- "./routes/api/articles/index.ts": $7,
- "./routes/api/auth/callback.ts": $8,
- "./routes/api/auth/login.ts": $9,
- "./routes/api/auth/logout.ts": $10,
- "./routes/api/cache/index.ts": $11,
- "./routes/api/images/index.ts": $12,
- "./routes/api/index.ts": $13,
- "./routes/api/logs.ts": $14,
- "./routes/api/movies/[name].ts": $15,
- "./routes/api/movies/enhance/[name].ts": $16,
- "./routes/api/movies/index.ts": $17,
- "./routes/api/query/index.ts": $18,
- "./routes/api/query/sync.ts": $19,
- "./routes/api/recipes/[name].ts": $20,
- "./routes/api/recipes/index.ts": $21,
- "./routes/api/resources.ts": $22,
- "./routes/api/series/[name].ts": $23,
- "./routes/api/series/enhance/[name].ts": $24,
- "./routes/api/series/index.ts": $25,
- "./routes/api/tmdb/[id].ts": $26,
- "./routes/api/tmdb/credits/[id].ts": $27,
- "./routes/api/tmdb/query.ts": $28,
- "./routes/articles/[name].tsx": $29,
- "./routes/articles/index.tsx": $30,
- "./routes/index.tsx": $31,
- "./routes/movies/[name].tsx": $32,
- "./routes/movies/index.tsx": $33,
- "./routes/recipes/[name].tsx": $34,
- "./routes/recipes/index.tsx": $35,
- "./routes/series/[name].tsx": $36,
- "./routes/series/index.tsx": $37,
+ "./routes/_layout.tsx": $2,
+ "./routes/_middleware.ts": $3,
+ "./routes/admin/log/index.tsx": $4,
+ "./routes/admin/performance/index.tsx": $5,
+ "./routes/api/articles/[name].ts": $6,
+ "./routes/api/articles/create/index.ts": $7,
+ "./routes/api/articles/index.ts": $8,
+ "./routes/api/auth/callback.ts": $9,
+ "./routes/api/auth/login.ts": $10,
+ "./routes/api/auth/logout.ts": $11,
+ "./routes/api/cache/index.ts": $12,
+ "./routes/api/images/index.ts": $13,
+ "./routes/api/index.ts": $14,
+ "./routes/api/logs.ts": $15,
+ "./routes/api/movies/[name].ts": $16,
+ "./routes/api/movies/enhance/[name].ts": $17,
+ "./routes/api/movies/index.ts": $18,
+ "./routes/api/query/index.ts": $19,
+ "./routes/api/query/sync.ts": $20,
+ "./routes/api/recipes/[name].ts": $21,
+ "./routes/api/recipes/index.ts": $22,
+ "./routes/api/resources.ts": $23,
+ "./routes/api/series/[name].ts": $24,
+ "./routes/api/series/enhance/[name].ts": $25,
+ "./routes/api/series/index.ts": $26,
+ "./routes/api/tmdb/[id].ts": $27,
+ "./routes/api/tmdb/credits/[id].ts": $28,
+ "./routes/api/tmdb/query.ts": $29,
+ "./routes/articles/[name].tsx": $30,
+ "./routes/articles/index.tsx": $31,
+ "./routes/index.tsx": $32,
+ "./routes/movies/[name].tsx": $33,
+ "./routes/movies/index.tsx": $34,
+ "./routes/recipes/[name].tsx": $35,
+ "./routes/recipes/index.tsx": $36,
+ "./routes/series/[name].tsx": $37,
+ "./routes/series/index.tsx": $38,
},
islands: {
"./islands/Counter.tsx": $$0,
diff --git a/islands/Search.tsx b/islands/Search.tsx
index 6db3605..778452b 100644
--- a/islands/Search.tsx
+++ b/islands/Search.tsx
@@ -71,7 +71,7 @@ export const SearchResultItem = (
) => {
const doc = item.document;
const resourceType = resources[doc.type];
- const href = (resourceType) ? `${resourceType.link}/${doc.id}` : "";
+ const href = resourceType ? `${resourceType.link}/${doc.id}` : "";
return (
v.replaceAll(" ", "-"));
+}
+
export async function createTags(content: string) {
if (!openAI) return;
const chatCompletion = await openAI.createChatCompletion({
diff --git a/lib/recommendation.ts b/lib/recommendation.ts
new file mode 100644
index 0000000..3df2753
--- /dev/null
+++ b/lib/recommendation.ts
@@ -0,0 +1,53 @@
+import * as cache from "@lib/cache/cache.ts";
+import * as openai from "@lib/openai.ts";
+import { GenericResource } from "@lib/types.ts";
+import { parseRating } from "@lib/helpers.ts";
+
+type RecommendationResource = {
+ id: string;
+ type: string;
+ rating: number;
+ tags?: string[];
+ keywords?: string[];
+ author?: string;
+ year?: number;
+};
+
+export async function createRecommendationResource(
+ res: GenericResource,
+ description?: string,
+) {
+ const cacheId = `recommendations:${res.type}:${res.id}`;
+ const resource: RecommendationResource = await cache.get(cacheId) || {
+ id: res.id,
+ type: res.type,
+ rating: -1,
+ };
+ if (description && !resource.keywords) {
+ const keywords = await openai.createKeywords(res.type, description);
+ if (keywords?.length) {
+ resource.keywords = keywords;
+ }
+ }
+
+ const { author, date, rating } = res.meta || {};
+
+ if (res?.tags) {
+ resource.tags = res.tags;
+ }
+
+ if (typeof rating !== "undefined") {
+ resource.rating = parseRating(rating);
+ }
+
+ if (author) {
+ resource.author = author;
+ }
+
+ if (date) {
+ const d = typeof date === "string" ? new Date(date) : date;
+ resource.year = d.getFullYear();
+ }
+
+ cache.set(cacheId, JSON.stringify(resource));
+}
diff --git a/lib/resource/movies.ts b/lib/resource/movies.ts
index 3ed7566..2b86b62 100644
--- a/lib/resource/movies.ts
+++ b/lib/resource/movies.ts
@@ -12,6 +12,8 @@ export type Movie = {
tags: string[];
meta: {
date: Date;
+ tmdbId?: number;
+ keywords?: string[];
image: string;
thumbnail?: string;
average?: string;
@@ -26,6 +28,11 @@ export function renderMovie(movie: Movie) {
meta.date = formatDate(meta.date) as unknown as Date;
}
+ delete meta.thumbnail;
+ delete meta.average;
+
+ const movieImage = ``;
+
return fixRenderedMarkdown(`${
meta
? `---
@@ -35,7 +42,11 @@ ${stringify(meta)}
---`
}
# ${movie.name}
-${movie.meta.image ? `` : ""}
+${
+ // So we do not add a new image to the description everytime we render
+ (movie.meta.image && !movie.description.includes(movieImage))
+ ? movieImage
+ : ""}
${movie.tags.map((t) => `#${t}`).join(" ")}
${movie.description}
`);
@@ -103,6 +114,10 @@ const crud = createCrud({
hasThumbnails: true,
});
-export const getMovie = crud.read;
+export const getMovie = async (id: string) => {
+ const movie = await crud.read(id);
+ return movie;
+};
+
export const getAllMovies = crud.readAll;
export const createMovie = crud.create;
diff --git a/lib/resource/series.ts b/lib/resource/series.ts
index 069f59a..3b4bda2 100644
--- a/lib/resource/series.ts
+++ b/lib/resource/series.ts
@@ -15,6 +15,7 @@ export type Series = {
date: Date;
image: string;
author: string;
+ tmdbId?: number;
rating: number;
average?: string;
thumbnail?: string;
@@ -22,12 +23,17 @@ export type Series = {
};
};
-function renderSeries(movie: Series) {
- const meta = movie.meta;
+function renderSeries(series: Series) {
+ const meta = series.meta;
if ("date" in meta) {
meta.date = formatDate(meta.date);
}
+ delete meta.thumbnail;
+ delete meta.average;
+
+ const movieImage = ``;
+
return fixRenderedMarkdown(`${
meta
? `---
@@ -36,10 +42,14 @@ ${stringify(meta)}
: `---
---`
}
-# ${movie.name}
-${movie.meta.image ? `` : ""}
-${movie.tags.map((t) => `#${t}`).join(" ")}
-${movie.description}
+# ${series.name}
+${
+ // So we do not add a new image to the description everytime we render
+ (series.meta.image && !series.description.includes(movieImage))
+ ? movieImage
+ : ""}
+${series.tags.map((t) => `#${t}`).join(" ")}
+${series.description}
`);
}
diff --git a/lib/search.ts b/lib/search.ts
index 99d1803..1a2cabc 100644
--- a/lib/search.ts
+++ b/lib/search.ts
@@ -1,4 +1,3 @@
-import { BadRequestError } from "@lib/errors.ts";
import { resources } from "@lib/resources.ts";
import { SearchResult } from "@lib/types.ts";
import { getTypeSenseClient } from "@lib/typesense.ts";
@@ -15,9 +14,9 @@ type SearchParams = {
query_by?: string;
};
-export function parseResourceUrl(_url: string): SearchParams | undefined {
+export function parseResourceUrl(_url: string | URL): SearchParams | undefined {
try {
- const url = new URL(_url);
+ const url = typeof _url === "string" ? new URL(_url) : _url;
let query = url.searchParams.get("q") || "*";
if (!query) {
return undefined;
diff --git a/lib/types.ts b/lib/types.ts
index 850609d..f5024c1 100644
--- a/lib/types.ts
+++ b/lib/types.ts
@@ -37,6 +37,7 @@ export interface TMDBSeries {
export type GenericResource = {
name: string;
id: string;
+ tags?: string[];
type: keyof typeof resources;
meta?: {
image?: string;
diff --git a/main.ts b/main.ts
index 984b0ae..1990aa8 100644
--- a/main.ts
+++ b/main.ts
@@ -8,8 +8,7 @@ import "$std/dotenv/load.ts";
import { start } from "$fresh/server.ts";
import manifest from "./fresh.gen.ts";
+import config from "./fresh.config.ts";
-import twindPlugin from "$fresh/plugins/twind.ts";
-import twindConfig from "./twind.config.ts";
+await start(manifest, config);
-await start(manifest, { plugins: [twindPlugin(twindConfig)] });
diff --git a/routes/_layout.tsx b/routes/_layout.tsx
new file mode 100644
index 0000000..8fc8acd
--- /dev/null
+++ b/routes/_layout.tsx
@@ -0,0 +1,41 @@
+import { LayoutProps } from "$fresh/server.ts";
+import { resources } from "@lib/resources.ts";
+import { CSS, KATEX_CSS } from "https://deno.land/x/gfm@0.2.5/mod.ts";
+import { Head } from "$fresh/runtime.ts";
+import { Emoji } from "@components/Emoji.tsx";
+
+export default function MyLayout({ Component, url }: LayoutProps) {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/routes/api/movies/[name].ts b/routes/api/movies/[name].ts
index 28321a2..80e45fa 100644
--- a/routes/api/movies/[name].ts
+++ b/routes/api/movies/[name].ts
@@ -25,8 +25,11 @@ export const handler: Handlers = {
const releaseDate = movieDetails.release_date;
const posterPath = movieDetails.poster_path;
- const director =
- movieCredits?.crew?.filter?.((person) => person.job === "Director")[0];
+ const director = movieCredits?.crew?.filter?.((person) =>
+ person.job === "Director"
+ )[0];
+
+ movieDetails.overview;
let finalPath = "";
const name = movieDetails.title || movieDetails.original_title ||
@@ -41,7 +44,9 @@ export const handler: Handlers = {
await createDocument(finalPath, poster);
}
- const metadata = {} as Movie["meta"];
+ const metadata = {
+ tmdbId,
+ } as Movie["meta"];
if (releaseDate) {
metadata.date = new Date(releaseDate);
}
diff --git a/routes/api/movies/enhance/[name].ts b/routes/api/movies/enhance/[name].ts
index 5f38ede..d4c3c81 100644
--- a/routes/api/movies/enhance/[name].ts
+++ b/routes/api/movies/enhance/[name].ts
@@ -11,6 +11,7 @@ import {
NotFoundError,
} from "@lib/errors.ts";
import * as cache from "@lib/cache/cache.ts";
+import { createRecommendationResource } from "@lib/recommendation.ts";
const POST = async (
req: Request,
@@ -42,8 +43,9 @@ const POST = async (
movie.meta.date = new Date(releaseDate);
}
- const director =
- movieCredits?.crew?.filter?.((person) => person.job === "Director")[0];
+ const director = movieCredits?.crew?.filter?.((person) =>
+ person.job === "Director"
+ )[0];
if (director && !movie.meta.author) {
movie.meta.author = director.name;
}
@@ -57,6 +59,10 @@ const POST = async (
];
}
+ if (!movie.meta.tmdbId) {
+ movie.meta.tmdbId = tmdbId;
+ }
+
let finalPath = "";
const posterPath = movieDetails.poster_path;
if (posterPath && !movie.meta.image) {
@@ -72,6 +78,8 @@ const POST = async (
cache.del(`documents:Media:movies:${name}.md`);
+ createRecommendationResource(movie, movieDetails.overview);
+
return json(movie);
};
diff --git a/routes/api/series/[name].ts b/routes/api/series/[name].ts
index cdf0078..16ca4ad 100644
--- a/routes/api/series/[name].ts
+++ b/routes/api/series/[name].ts
@@ -42,7 +42,7 @@ export const handler: Handlers = {
await createDocument(finalPath, poster);
}
- const metadata = {} as Series["meta"];
+ const metadata = { tmdbId } as Series["meta"];
if (releaseDate) {
metadata.date = new Date(releaseDate);
}
diff --git a/routes/movies/[name].tsx b/routes/movies/[name].tsx
index e22fa46..fd75097 100644
--- a/routes/movies/[name].tsx
+++ b/routes/movies/[name].tsx
@@ -1,4 +1,4 @@
-import { Handlers, PageProps } from "$fresh/server.ts";
+import { Handlers, PageProps, RouteContext } from "$fresh/server.ts";
import { MainLayout } from "@components/layouts/main.tsx";
import { getMovie, Movie } from "@lib/resource/movies.ts";
import { RecipeHero } from "@components/RecipeHero.tsx";
@@ -7,17 +7,12 @@ import { renderMarkdown } from "@lib/documents.ts";
import { KMenu } from "@islands/KMenu.tsx";
import { RedirectSearchHandler } from "@islands/Search.tsx";
-export const handler: Handlers = {
- async GET(_, ctx) {
- const movie = await getMovie(ctx.params.name);
- return ctx.render({ movie, session: ctx.state.session });
- },
-};
-
-export default function Greet(
+export default async function Greet(
props: PageProps<{ movie: Movie; session: Record }>,
+ ctx: RouteContext,
) {
- const { movie, session } = props.data;
+ const movie = await getMovie(ctx.params.name);
+ const session = ctx.state.session;
const { author = "", date = "" } = movie.meta;
diff --git a/routes/movies/index.tsx b/routes/movies/index.tsx
index 33e11d3..28038fc 100644
--- a/routes/movies/index.tsx
+++ b/routes/movies/index.tsx
@@ -1,4 +1,3 @@
-
import { MainLayout } from "@components/layouts/main.tsx";
import { getAllMovies, Movie } from "@lib/resource/movies.ts";
import { ResourceCard } from "@components/Card.tsx";
@@ -8,26 +7,18 @@ import { KMenu } from "@islands/KMenu.tsx";
import { RedirectSearchHandler } from "@islands/Search.tsx";
import { parseResourceUrl, searchResource } from "@lib/search.ts";
import { SearchResult } from "@lib/types.ts";
+import { PageProps } from "$fresh/server.ts";
-export const handler: Handlers<
- { movies: Movie[] | null; searchResults?: SearchResult }
-> = {
- async GET(req, ctx) {
- const movies = await getAllMovies();
- const searchParams = parseResourceUrl(req.url);
- const searchResults = searchParams &&
- await searchResource({ ...searchParams, type: "movie" });
- return ctx.render({
- movies: movies.sort((a, b) => a?.meta?.rating > b?.meta?.rating ? -1 : 1),
- searchResults,
- });
- },
-};
-
-export default function Greet(
+export default async function Greet(
props: PageProps<{ movies: Movie[] | null; searchResults: SearchResult }>,
) {
- const { movies, searchResults } = props.data;
+ const allMovies = await getAllMovies();
+ const searchParams = parseResourceUrl(props.url);
+ const searchResults = searchParams &&
+ await searchResource({ ...searchParams, type: "movie" });
+ const movies = allMovies.sort((a, b) =>
+ a?.meta?.rating > b?.meta?.rating ? -1 : 1
+ );
return (