From e55f787a29204cca60da88abcef9cc51b15f2445 Mon Sep 17 00:00:00 2001 From: Max Richter Date: Sat, 10 Jan 2026 13:03:13 +0100 Subject: [PATCH] feat: trying to add hashes to scripts --- Dockerfile | 16 +- assets/style.css | 1 + client.ts | 1 + components/Button.tsx | 6 +- components/Emoji.tsx | 2 +- components/Image.tsx | 2 +- components/MetaTags.tsx | 2 +- components/icons.tsx | 44 +- components/layouts/main.tsx | 2 +- deno.json | 115 +- deno.lock | 3510 ++++++++++++++++++++ dev.ts | 8 - fresh.config.ts | 5 - fresh.gen.ts | 145 - islands/Counter.tsx | 11 +- islands/IngredientsList.tsx | 9 +- islands/KMenu.tsx | 2 +- islands/KMenu/commands.ts | 1 - islands/KMenu/commands/add_movie_infos.ts | 12 +- islands/KMenu/commands/add_series_infos.ts | 12 +- islands/KMenu/commands/create_movie.ts | 12 +- islands/KMenu/commands/create_series.ts | 12 +- islands/Search.tsx | 2 +- lib/auth.ts | 2 +- lib/errors.ts | 5 +- lib/image.ts | 2 +- lib/markdown.ts | 6 +- lib/playwright.ts | 4 +- lib/recipeSchema.ts | 2 +- lib/recommendation.ts | 4 +- lib/string.ts | 4 + lib/telegram.ts | 2 +- main.ts | 17 +- routes/_app.tsx | 5 +- routes/{_404.tsx => _error.tsx} | 15 +- routes/_layout.tsx | 2 +- routes/_middleware.ts | 32 +- routes/admin/cache/index.tsx | 13 +- routes/admin/log/index.tsx | 27 +- routes/admin/performance/index.tsx | 11 +- routes/api/articles/[name].ts | 8 +- routes/api/articles/create/index.ts | 12 +- routes/api/articles/enhance/[name].ts | 46 +- routes/api/articles/index.ts | 6 +- routes/api/auth/callback.ts | 28 +- routes/api/auth/login.ts | 9 +- routes/api/auth/logout.ts | 9 +- routes/api/cache.ts | 6 +- routes/api/images/index.ts | 8 +- routes/api/index.ts | 6 +- routes/api/logs.ts | 6 +- routes/api/movies/[name].ts | 12 +- routes/api/movies/enhance/[name].ts | 125 +- routes/api/movies/index.ts | 6 +- routes/api/query/index.ts | 9 +- routes/api/recipes/[name].ts | 8 +- routes/api/recipes/create/index.ts | 12 +- routes/api/recipes/index.ts | 6 +- routes/api/recommendation/all.ts | 8 +- routes/api/recommendation/data.ts | 8 +- routes/api/recommendation/index.ts | 6 +- routes/api/recommendation/movie/[id].ts | 8 +- routes/api/series/[name].ts | 18 +- routes/api/series/enhance/[name].ts | 118 +- routes/api/series/index.ts | 6 +- routes/api/tmdb/[id].ts | 60 +- routes/api/tmdb/credits/[id].ts | 58 +- routes/api/tmdb/query.ts | 43 +- routes/articles/[name].tsx | 27 +- routes/articles/index.tsx | 22 +- routes/index.tsx | 3 +- routes/movies/[name].tsx | 13 +- routes/movies/index.tsx | 6 +- routes/recipes/[name].tsx | 32 +- routes/recipes/index.tsx | 28 +- routes/series/[name].tsx | 16 +- routes/series/index.tsx | 21 +- utils.ts | 11 + vite.config.ts | 10 + 79 files changed, 4209 insertions(+), 720 deletions(-) create mode 100644 assets/style.css create mode 100644 client.ts create mode 100644 deno.lock delete mode 100755 dev.ts delete mode 100644 fresh.config.ts delete mode 100644 fresh.gen.ts rename routes/{_404.tsx => _error.tsx} (59%) create mode 100644 utils.ts create mode 100644 vite.config.ts diff --git a/Dockerfile b/Dockerfile index aaae893..209884d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ -FROM denoland/deno:2.5.4 AS build +FROM denoland/deno:2.6.4 AS build RUN apt-get update && apt-get install -y --no-install-recommends \ - curl ffmpeg && \ - deno run -A npm:playwright install --with-deps firefox &&\ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* + curl ffmpeg && \ + deno run -A npm:playwright install --with-deps firefox &&\ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* WORKDIR /app @@ -15,9 +15,9 @@ COPY . . ENV DATA_DIR=/app/data RUN mkdir -p $DATA_DIR && \ - deno install --allow-import --allow-ffi --allow-scripts=npm:sharp -e main.ts &&\ - sed -i -e 's/"deno"/"no-deno"/' node_modules/@libsql/client/package.json &&\ - deno task build + deno install --allow-import --allow-ffi --allow-scripts=npm:sharp -e main.ts &&\ + sed -i -e 's/"deno"/"no-deno"/' node_modules/@libsql/client/package.json &&\ + deno task build EXPOSE 8000 diff --git a/assets/style.css b/assets/style.css new file mode 100644 index 0000000..f1d8c73 --- /dev/null +++ b/assets/style.css @@ -0,0 +1 @@ +@import "tailwindcss"; diff --git a/client.ts b/client.ts new file mode 100644 index 0000000..68a8d5a --- /dev/null +++ b/client.ts @@ -0,0 +1 @@ +// import "./assets/style.css"; diff --git a/components/Button.tsx b/components/Button.tsx index 057704d..4bed6a4 100644 --- a/components/Button.tsx +++ b/components/Button.tsx @@ -1,7 +1,7 @@ -import { JSX } from "preact"; -import { IS_BROWSER } from "$fresh/runtime.ts"; +import { ButtonHTMLAttributes } from "preact"; +import { IS_BROWSER } from "fresh/runtime"; -export function Button(props: JSX.HTMLAttributes) { +export function Button(props: ButtonHTMLAttributes) { return ( props.count.value = ev.target?.value} + onInput={(ev) => { + const target = ev.target as HTMLInputElement; + props.count.value = Math.max(1, Number(target.value)); + }} /> ); diff --git a/islands/IngredientsList.tsx b/islands/IngredientsList.tsx index 8a31007..fe31f66 100644 --- a/islands/IngredientsList.tsx +++ b/islands/IngredientsList.tsx @@ -1,6 +1,5 @@ import { Signal } from "@preact/signals"; import type { Ingredient, IngredientGroup } from "@lib/recipeSchema.ts"; -import { FunctionalComponent } from "preact"; import { unitsOfMeasure } from "@lib/parseIngredient.ts"; function formatAmount(num: number) { @@ -50,14 +49,12 @@ const Ingredient = ( ); }; -export const IngredientsList: FunctionalComponent< - { +export const IngredientsList = ( + { ingredients, amount, portion }: { ingredients: (Ingredient | IngredientGroup)[]; amount: Signal; portion?: number; - } -> = ( - { ingredients, amount, portion }, + }, ) => { return ( diff --git a/islands/KMenu.tsx b/islands/KMenu.tsx index 33bd339..6e444e7 100644 --- a/islands/KMenu.tsx +++ b/islands/KMenu.tsx @@ -4,7 +4,7 @@ import { useEventListener } from "@lib/hooks/useEventListener.ts"; import { menus } from "@islands/KMenu/commands.ts"; import { MenuEntry } from "@islands/KMenu/types.ts"; import * as icons from "@components/icons.tsx"; -import { IS_BROWSER } from "$fresh/runtime.ts"; +import { IS_BROWSER } from "fresh/runtime"; import { isKMenuOpen } from "@lib/kmenu.ts"; const KMenuEntry = ( { entry, activeIndex, index }: { diff --git a/islands/KMenu/commands.ts b/islands/KMenu/commands.ts index e56db37..b85096e 100644 --- a/islands/KMenu/commands.ts +++ b/islands/KMenu/commands.ts @@ -5,7 +5,6 @@ import { createNewArticle } from "@islands/KMenu/commands/create_article.ts"; import { getCookie } from "@lib/string.ts"; import { addSeriesInfo } from "@islands/KMenu/commands/add_series_infos.ts"; import { createNewSeries } from "@islands/KMenu/commands/create_series.ts"; -import { updateAllRecommendations } from "@islands/KMenu/commands/create_recommendations.ts"; import { createNewRecipe } from "@islands/KMenu/commands/create_recipe.ts"; import { enhanceArticleInfo } from "@islands/KMenu/commands/enhance_article_infos.ts"; diff --git a/islands/KMenu/commands/add_movie_infos.ts b/islands/KMenu/commands/add_movie_infos.ts index aa9e415..5a456e9 100644 --- a/islands/KMenu/commands/add_movie_infos.ts +++ b/islands/KMenu/commands/add_movie_infos.ts @@ -42,7 +42,11 @@ export const addMovieInfos: MenuEntry = { globalThis.location.reload(); } catch (e) { state.activeState.value = "error"; - state.loadingText.value = e.message; + if (e instanceof Error) { + if ("message" in e) { + state.loadingText.value = e.message; + } + } } }, })), @@ -53,7 +57,11 @@ export const addMovieInfos: MenuEntry = { state.activeState.value = "normal"; } catch (e) { state.activeState.value = "error"; - state.loadingText.value = e.message; + if (e instanceof Error) { + if ("message" in e) { + state.loadingText.value = e.message; + } + } } }, visible: () => { diff --git a/islands/KMenu/commands/add_series_infos.ts b/islands/KMenu/commands/add_series_infos.ts index f4bcbcf..a3b4786 100644 --- a/islands/KMenu/commands/add_series_infos.ts +++ b/islands/KMenu/commands/add_series_infos.ts @@ -42,7 +42,11 @@ export const addSeriesInfo: MenuEntry = { //window.location.reload(); } catch (e) { state.activeState.value = "error"; - state.loadingText.value = e.message; + if (e instanceof Error) { + if ("message" in e) { + state.loadingText.value = e.message; + } + } } }, })), @@ -53,7 +57,11 @@ export const addSeriesInfo: MenuEntry = { state.activeState.value = "normal"; } catch (e) { state.activeState.value = "error"; - state.loadingText.value = e.message; + if (e instanceof Error) { + if ("message" in e) { + state.loadingText.value = e.message; + } + } } }, visible: () => { diff --git a/islands/KMenu/commands/create_movie.ts b/islands/KMenu/commands/create_movie.ts index 0496c74..651e1cb 100644 --- a/islands/KMenu/commands/create_movie.ts +++ b/islands/KMenu/commands/create_movie.ts @@ -66,7 +66,11 @@ export const createNewMovie: MenuEntry = { globalThis.location.href = "/movies/" + movie.name; } catch (e) { state.activeState.value = "error"; - state.loadingText.value = e.message; + if (e instanceof Error) { + if ("message" in e) { + state.loadingText.value = e.message; + } + } } }, }; @@ -75,7 +79,11 @@ export const createNewMovie: MenuEntry = { state.activeMenu.value = "input_link"; } catch (e) { state.activeState.value = "error"; - state.loadingText.value = e.message; + if (e instanceof Error) { + if ("message" in e) { + state.loadingText.value = e.message; + } + } } }, 500); diff --git a/islands/KMenu/commands/create_series.ts b/islands/KMenu/commands/create_series.ts index 27a4f7c..6559b04 100644 --- a/islands/KMenu/commands/create_series.ts +++ b/islands/KMenu/commands/create_series.ts @@ -68,7 +68,11 @@ export const createNewSeries: MenuEntry = { globalThis.location.href = "/series/" + series.name; } catch (e) { state.activeState.value = "error"; - state.loadingText.value = e.message; + if (e instanceof Error) { + if ("message" in e) { + state.loadingText.value = e.message; + } + } } }, }; @@ -78,7 +82,11 @@ export const createNewSeries: MenuEntry = { state.activeMenu.value = "input_link"; } catch (e) { state.activeState.value = "error"; - state.loadingText.value = e.message; + if (e instanceof Error) { + if ("message" in e) { + state.loadingText.value = e.message; + } + } } }, 500); diff --git a/islands/Search.tsx b/islands/Search.tsx index ee43eb6..dbc774a 100644 --- a/islands/Search.tsx +++ b/islands/Search.tsx @@ -4,7 +4,7 @@ import { IconLoader2, IconSearch } from "@components/icons.tsx"; import { useEventListener } from "@lib/hooks/useEventListener.ts"; import { resources } from "@lib/resources.ts"; import { getCookie } from "@lib/string.ts"; -import { IS_BROWSER } from "$fresh/runtime.ts"; +import { IS_BROWSER } from "fresh/runtime"; import Checkbox from "@components/Checkbox.tsx"; import { Rating } from "@components/Rating.tsx"; import { useSignal } from "@preact/signals"; diff --git a/lib/auth.ts b/lib/auth.ts index 07b7ba4..fc97215 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -1,4 +1,4 @@ -import { OAuth2Client } from "https://deno.land/x/oauth2_client@v1.0.2/mod.ts"; +import { OAuth2Client } from "@cmd-johnson/oauth2-client"; import { GITEA_CLIENT_ID, GITEA_CLIENT_SECRET, diff --git a/lib/errors.ts b/lib/errors.ts index cab221b..f3325f1 100644 --- a/lib/errors.ts +++ b/lib/errors.ts @@ -1,8 +1,9 @@ -import { FreshContext } from "$fresh/server.ts"; +import { Context } from "fresh"; +import { State } from "../utils.ts"; class DomainError extends Error { status = 500; - render?: (ctx: FreshContext) => void; + render?: (ctx: Context) => void; constructor(public statusText = "Internal Server Error") { super(); } diff --git a/lib/image.ts b/lib/image.ts index ef02e41..4397106 100644 --- a/lib/image.ts +++ b/lib/image.ts @@ -1,7 +1,7 @@ import { rgbToHex } from "@lib/string.ts"; import { createLogger } from "@lib/log/index.ts"; import { generateThumbhash } from "@lib/thumbhash.ts"; -import { parseMediaType } from "https://deno.land/std@0.224.0/media_types/parse_media_type.ts"; +import { parseMediaType } from "@std/media-types"; import path from "node:path"; import { mkdir } from "node:fs/promises"; import { DATA_DIR } from "@lib/env.ts"; diff --git a/lib/markdown.ts b/lib/markdown.ts index 42c0521..e00b2d5 100644 --- a/lib/markdown.ts +++ b/lib/markdown.ts @@ -1,7 +1,7 @@ import { render } from "gfm"; -import "https://esm.sh/prismjs@1.29.0/components/prism-typescript?no-check"; -import "https://esm.sh/prismjs@1.29.0/components/prism-bash?no-check"; -import "https://esm.sh/prismjs@1.29.0/components/prism-rust?no-check"; +import "prismjs/components/prism-typescript.js"; +import "prismjs/components/prism-bash.js"; +import "prismjs/components/prism-rust.js"; export type Document = { name: string; diff --git a/lib/playwright.ts b/lib/playwright.ts index e05666d..885f85c 100644 --- a/lib/playwright.ts +++ b/lib/playwright.ts @@ -1,6 +1,6 @@ -import { firefox } from "npm:playwright-extra"; +import { firefox } from "playwright-extra"; import { createStreamResponse } from "@lib/helpers.ts"; -import StealthPlugin from "npm:puppeteer-extra-plugin-stealth"; +import StealthPlugin from "puppeteer-extra-plugin-stealth"; import * as env from "@lib/env.ts"; firefox.use(StealthPlugin()); diff --git a/lib/recipeSchema.ts b/lib/recipeSchema.ts index cfa63de..19898e5 100644 --- a/lib/recipeSchema.ts +++ b/lib/recipeSchema.ts @@ -1,4 +1,4 @@ -import { z } from "npm:zod"; +import { z } from "zod"; import { RecipeResource } from "./marka/schema.ts"; export const IngredientSchema = z.object({ diff --git a/lib/recommendation.ts b/lib/recommendation.ts index ece882a..ae8e44f 100644 --- a/lib/recommendation.ts +++ b/lib/recommendation.ts @@ -96,5 +96,7 @@ export async function getAllRecommendations(): Promise< > { const keys = cache.keys(); const res = await Promise.all(keys.map((k) => cache.get(k))); - return res.filter((s) => !!s).map((r) => JSON.parse(r)); + return res.filter((s) => !!s).map((r) => + typeof r === "string" ? JSON.parse(r) : r + ); } diff --git a/lib/string.ts b/lib/string.ts index 13ed976..414e423 100644 --- a/lib/string.ts +++ b/lib/string.ts @@ -186,3 +186,7 @@ export function removeMarkdownFormatting(text: string): string { return text; } + +export function fileExtension(fname: string) { + return fname.slice((fname.lastIndexOf(".") - 1 >>> 0) + 2); +} diff --git a/lib/telegram.ts b/lib/telegram.ts index d4d671d..e9597e4 100644 --- a/lib/telegram.ts +++ b/lib/telegram.ts @@ -1,4 +1,4 @@ -import { Bot } from "https://deno.land/x/grammy@v1.36.1/mod.ts"; +import { Bot } from "grammy"; import { TELEGRAM_API_KEY } from "@lib/env.ts"; import { createLogger } from "./log/index.ts"; diff --git a/main.ts b/main.ts index 248e889..1ab909c 100644 --- a/main.ts +++ b/main.ts @@ -1,12 +1,7 @@ -/// -/// -/// -/// -/// +import { App, staticFiles } from "fresh"; -import { start } from "$fresh/server.ts"; -import manifest from "./fresh.gen.ts"; -import config from "./fresh.config.ts"; -// import "@lib/telegram.ts"; - -await start(manifest, config); +export const app = new App() + // Add static file serving middleware + .use(staticFiles()) + // Enable file-system based routing + .fsRoutes(); diff --git a/routes/_app.tsx b/routes/_app.tsx index 0adf36f..7bd64b7 100644 --- a/routes/_app.tsx +++ b/routes/_app.tsx @@ -1,6 +1,5 @@ -// deno-lint-ignore-file react-no-danger -import { PageProps } from "$fresh/server.ts"; -import { Partial } from "$fresh/runtime.ts"; +import { PageProps } from "fresh"; +import { Partial } from "fresh/runtime"; export default function App({ Component }: PageProps) { const globalCss = Deno.readTextFileSync("./static/global.css"); diff --git a/routes/_404.tsx b/routes/_error.tsx similarity index 59% rename from routes/_404.tsx rename to routes/_error.tsx index 04fa785..11f9d3d 100644 --- a/routes/_404.tsx +++ b/routes/_error.tsx @@ -1,7 +1,18 @@ -import { Head } from "$fresh/runtime.ts"; +import { Head } from "fresh/runtime"; import { MainLayout } from "@components/layouts/main.tsx"; +import { HttpError, PageProps } from "fresh"; + +export default function ErrorPage(props: PageProps) { + const error = props.error; // Contains the thrown Error or HTTPError + if (error instanceof HttpError) { + const status = error.status; // HTTP status code + + // Render a 404 not found page + if (status === 404) { + return

404 - Page not found

; + } + } -export default function Error404() { return ( <> diff --git a/routes/_layout.tsx b/routes/_layout.tsx index 7dcf8e7..8c9f836 100644 --- a/routes/_layout.tsx +++ b/routes/_layout.tsx @@ -1,8 +1,8 @@ -import { PageProps } from "$fresh/server.ts"; import { resources } from "@lib/resources.ts"; import { Link } from "@islands/Link.tsx"; import { Emoji } from "@components/Emoji.tsx"; import KMenuButton from "@islands/KMenuButton.tsx"; +import { PageProps } from "fresh"; export default function MyLayout({ Component }: PageProps) { return ( diff --git a/routes/_middleware.ts b/routes/_middleware.ts index 64c7f07..4c47202 100644 --- a/routes/_middleware.ts +++ b/routes/_middleware.ts @@ -1,27 +1,39 @@ -//routes/middleware-error-handler/_middleware.ts -import { FreshContext } from "$fresh/server.ts"; import { DomainError } from "@lib/errors.ts"; import { getCookies } from "@std/http/cookie"; -import { verify } from "https://deno.land/x/djwt@v2.2/mod.ts"; +import { verify } from "@zaubrik/djwt"; import * as perf from "@lib/performance.ts"; import { JWT_SECRET } from "@lib/env.ts"; +import { define } from "../utils.ts"; -export async function handler( - req: Request, - ctx: FreshContext, +function importKey(secret: string) { + return crypto.subtle.importKey( + "raw", + new TextEncoder().encode(secret), + { name: "HMAC", hash: "SHA-512" }, + false, + ["sign", "verify"], + ); +} + +const authMiddleware = define.middleware(async function ( + ctx, ) { + const req = ctx.req; + try { performance.mark("a"); const allCookies = getCookies(req.headers); const sessionCookie = allCookies["session_cookie"]; if (!ctx.state.session && sessionCookie && JWT_SECRET) { try { - const payload = await verify(sessionCookie, JWT_SECRET, "HS512"); + const payload = await verify( + sessionCookie, + await importKey(JWT_SECRET), + ); if (payload) { ctx.state.session = payload; } } catch (_err) { - // console.log({ _err }); } } @@ -44,4 +56,6 @@ export async function handler( status: 500, }); } -} +}); + +export default [authMiddleware]; diff --git a/routes/admin/cache/index.tsx b/routes/admin/cache/index.tsx index eefcae3..1d5ee5a 100644 --- a/routes/admin/cache/index.tsx +++ b/routes/admin/cache/index.tsx @@ -1,14 +1,13 @@ import { MainLayout } from "@components/layouts/main.tsx"; -import { Handlers, PageProps } from "$fresh/server.ts"; +import { PageProps } from "fresh"; import { getCacheInfo } from "@lib/cache.ts"; +import { define } from "../../../utils.ts"; -export const handler: Handlers< - { cacheInfo: ReturnType } -> = { - GET(_, ctx) { - return ctx.render({ cacheInfo: getCacheInfo() }); +export const handler = define.handlers({ + GET() { + return { data: { cacheInfo: getCacheInfo() } }; }, -}; +}); export default function Greet( props: PageProps< diff --git a/routes/admin/log/index.tsx b/routes/admin/log/index.tsx index 2193839..c7b6998 100644 --- a/routes/admin/log/index.tsx +++ b/routes/admin/log/index.tsx @@ -1,31 +1,34 @@ import { MainLayout } from "@components/layouts/main.tsx"; -import { Handlers, PageProps } from "$fresh/server.ts"; +import { PageProps } from "fresh"; import { AccessDeniedError } from "@lib/errors.ts"; import { getLogs, Log } from "@lib/log/index.ts"; import { formatDate } from "@lib/string.ts"; import { renderMarkdown } from "@lib/markdown.ts"; +import { define } from "../../../utils.ts"; const renderLog = (t: unknown) => renderMarkdown(`\`\`\`js ${typeof t === "string" ? t : JSON.stringify(t, null, 2).trim()} \`\`\``); -export const handler: Handlers = { - async GET(_, ctx) { +export const handler = define.handlers({ + async GET(ctx) { const logs = await getLogs(); if (!("session" in ctx.state)) { throw new AccessDeniedError(); } - return ctx.render({ - logs: logs.map((l) => { - return { - ...l, - html: l.args.map(renderLog).join("
"), - }; - }), - }); + return { + data: { + logs: logs.map((l) => { + return { + ...l, + html: l.args.map(renderLog).join("
"), + }; + }), + }, + }; }, -}; +}); function LogLine( { log }: { diff --git a/routes/admin/performance/index.tsx b/routes/admin/performance/index.tsx index a2f2376..276e68e 100644 --- a/routes/admin/performance/index.tsx +++ b/routes/admin/performance/index.tsx @@ -1,18 +1,19 @@ import { MainLayout } from "@components/layouts/main.tsx"; -import { Handlers, PageProps } from "$fresh/server.ts"; +import { PageProps } from "fresh"; import { getPerformances, PerformanceRes } from "@lib/performance.ts"; import { AccessDeniedError } from "@lib/errors.ts"; +import { define } from "../../../utils.ts"; -export const handler: Handlers = { - async GET(_, ctx) { +export const handler = define.handlers({ + async GET(ctx) { const performances = await getPerformances(); if (!("session" in ctx.state)) { throw new AccessDeniedError(); } - return ctx.render({ performances }); + return { data: { performances } }; }, -}; +}); function PerformanceLine( { maximum, data: [amount, min, average, max], url }: { diff --git a/routes/api/articles/[name].ts b/routes/api/articles/[name].ts index 4c4e301..5451778 100644 --- a/routes/api/articles/[name].ts +++ b/routes/api/articles/[name].ts @@ -1,10 +1,10 @@ -import { Handlers } from "$fresh/server.ts"; import { json } from "@lib/helpers.ts"; import { fetchResource } from "@lib/marka/index.ts"; +import { define } from "../../../utils.ts"; -export const handler: Handlers = { - async GET(_, ctx) { +export const handler = define.handlers({ + async GET(ctx) { const article = await fetchResource(`articles/${ctx.params.name}`); return json(article); }, -}; +}); diff --git a/routes/api/articles/create/index.ts b/routes/api/articles/create/index.ts index 6597070..72a9a29 100644 --- a/routes/api/articles/create/index.ts +++ b/routes/api/articles/create/index.ts @@ -1,5 +1,3 @@ -import { Handlers } from "$fresh/server.ts"; -import { Defuddle } from "defuddle/node"; import { AccessDeniedError, BadRequestError } from "@lib/errors.ts"; import { createStreamResponse, isValidUrl } from "@lib/helpers.ts"; import * as openai from "@lib/openai.ts"; @@ -7,6 +5,7 @@ import * as unsplash from "@lib/unsplash.ts"; import { getYoutubeVideoDetails } from "@lib/youtube.ts"; import { extractYoutubeId, + fileExtension, formatDate, isYoutubeLink, safeFileName, @@ -16,7 +15,7 @@ import { createLogger } from "@lib/log/index.ts"; import { createResource } from "@lib/marka/index.ts"; import { webScrape } from "@lib/webScraper.ts"; import { ArticleResource } from "@lib/marka/schema.ts"; -import { fileExtension } from "https://deno.land/x/file_extension@v2.1.0/mod.ts"; +import { define } from "../../../../utils.ts"; const log = createLogger("api/article"); @@ -201,8 +200,9 @@ async function processCreateYoutubeVideo( streamResponse.send({ type: "finished", url: filename }); } -export const handler: Handlers = { - GET(req, ctx) { +export const handler = define.handlers({ + GET(ctx) { + const req = ctx.req; const session = ctx.state.session; if (!session) { throw new AccessDeniedError(); @@ -239,4 +239,4 @@ export const handler: Handlers = { return streamResponse.response; }, -}; +}); diff --git a/routes/api/articles/enhance/[name].ts b/routes/api/articles/enhance/[name].ts index 1acbc96..fa62af3 100644 --- a/routes/api/articles/enhance/[name].ts +++ b/routes/api/articles/enhance/[name].ts @@ -1,6 +1,4 @@ -import { FreshContext, Handlers } from "$fresh/server.ts"; -import { fileExtension } from "https://deno.land/x/file_extension@v2.1.0/mod.ts"; -import { formatDate, safeFileName } from "@lib/string.ts"; +import { fileExtension, formatDate, safeFileName } from "@lib/string.ts"; import { createStreamResponse } from "@lib/helpers.ts"; import { AccessDeniedError, @@ -13,6 +11,7 @@ import { webScrape } from "@lib/webScraper.ts"; import * as openai from "@lib/openai.ts"; import * as unsplash from "@lib/unsplash.ts"; import { createLogger } from "@lib/log/index.ts"; +import { define } from "../../../../utils.ts"; function ext(str: string) { try { @@ -163,29 +162,24 @@ async function processEnhanceArticle( streamResponse.send({ type: "finished", url: name.replace(/$\.md/, "") }); } -const POST = ( - _req: Request, - ctx: FreshContext, -): Response => { - const session = ctx.state.session; - if (!session) { - throw new AccessDeniedError(); - } +export const handler = define.handlers({ + POST: (ctx) => { + const session = ctx.state.session; + if (!session) { + throw new AccessDeniedError(); + } - const streamResponse = createStreamResponse(); + const streamResponse = createStreamResponse(); - processEnhanceArticle(ctx.params.name, streamResponse) - .catch((err) => { - log.error(err); - streamResponse.error(err.message); - }) - .finally(() => { - streamResponse.cancel(); - }); + processEnhanceArticle(ctx.params.name, streamResponse) + .catch((err) => { + log.error(err); + streamResponse.error(err.message); + }) + .finally(() => { + streamResponse.cancel(); + }); - return streamResponse.response; -}; - -export const handler: Handlers = { - POST, -}; + return streamResponse.response; + }, +}); diff --git a/routes/api/articles/index.ts b/routes/api/articles/index.ts index f050f4a..028646c 100644 --- a/routes/api/articles/index.ts +++ b/routes/api/articles/index.ts @@ -1,10 +1,10 @@ -import { Handlers } from "$fresh/server.ts"; import { json } from "@lib/helpers.ts"; import { fetchResource } from "@lib/marka/index.ts"; +import { define } from "../../../utils.ts"; -export const handler: Handlers = { +export const handler = define.handlers({ async GET() { const articles = await fetchResource("articles"); return json(articles?.content); }, -}; +}); diff --git a/routes/api/auth/callback.ts b/routes/api/auth/callback.ts index 829ec41..531b21a 100644 --- a/routes/api/auth/callback.ts +++ b/routes/api/auth/callback.ts @@ -1,5 +1,4 @@ -import { Handlers } from "$fresh/server.ts"; -import { create, getNumericDate } from "https://deno.land/x/djwt@v2.2/mod.ts"; +import { create, getNumericDate } from "@zaubrik/djwt"; import { oauth2Client } from "@lib/auth.ts"; import { getCookies, setCookie } from "@std/http/cookie"; import { codeChallengeMap } from "./login.ts"; @@ -9,15 +8,16 @@ import { BadRequestError } from "@lib/errors.ts"; import { db } from "@lib/db/sqlite.ts"; import { userTable } from "@lib/db/schema.ts"; import { eq } from "drizzle-orm"; +import { define } from "../../../utils.ts"; -export const handler: Handlers = { - async GET(request) { +export const handler = define.handlers({ + async GET(ctx) { if (!JWT_SECRET) { throw new BadRequestError(); } // Exchange the authorization code for an access token - const cookies = getCookies(request.headers); + const cookies = getCookies(ctx.req.headers); const stored = codeChallengeMap.get(cookies["code_challenge"]); if (!stored) { @@ -26,7 +26,7 @@ export const handler: Handlers = { const { codeVerifier, redirect } = stored; - const tokens = await oauth2Client.code.getToken(request.url, { + const tokens = await oauth2Client.code.getToken(ctx.req.url, { codeVerifier, }); @@ -53,11 +53,23 @@ export const handler: Handlers = { user = res[0]; } + if (!JWT_SECRET) { + throw new BadRequestError(); + } + + const key = await crypto.subtle.importKey( + "raw", + new TextEncoder().encode(JWT_SECRET), + { name: "HMAC", hash: "SHA-512" }, + false, + ["sign", "verify"], + ); + const jwt = await create({ alg: "HS512", type: "JWT" }, { id: user.id, name: user.name, exp: getNumericDate(SESSION_DURATION), - }, JWT_SECRET); + }, key); const headers = new Headers({ location: redirect || "/", @@ -78,4 +90,4 @@ export const handler: Handlers = { status: 302, }); }, -}; +}); diff --git a/routes/api/auth/login.ts b/routes/api/auth/login.ts index e0c68ef..08b3cd0 100644 --- a/routes/api/auth/login.ts +++ b/routes/api/auth/login.ts @@ -1,14 +1,15 @@ -import { Handlers } from "$fresh/server.ts"; import { oauth2Client } from "@lib/auth.ts"; import { setCookie } from "@std/http/cookie"; +import { define } from "../../../utils.ts"; export const codeChallengeMap = new Map< string, { codeVerifier: string; redirect?: string } >(); -export const handler: Handlers = { - async GET(req) { +export const handler = define.handlers({ + async GET(ctx) { + const req = ctx.req; const url = new URL(req.url); const { codeVerifier, uri } = await oauth2Client.code.getAuthorizationUri(); @@ -33,4 +34,4 @@ export const handler: Handlers = { status: 302, }); }, -}; +}); diff --git a/routes/api/auth/logout.ts b/routes/api/auth/logout.ts index de17fd0..190a6e0 100644 --- a/routes/api/auth/logout.ts +++ b/routes/api/auth/logout.ts @@ -1,8 +1,9 @@ import { deleteCookie } from "@std/http/cookie"; -import { Handlers } from "$fresh/server.ts"; +import { define } from "../../../utils.ts"; -export const handler: Handlers = { - GET(req) { +export const handler = define.handlers({ + GET(ctx) { + const req = ctx.req; const url = new URL(req.url); const redirect = decodeURIComponent(url.searchParams.get("redirect") || ""); @@ -19,4 +20,4 @@ export const handler: Handlers = { status: 302, }); }, -}; +}); diff --git a/routes/api/cache.ts b/routes/api/cache.ts index 4bca612..fe55050 100644 --- a/routes/api/cache.ts +++ b/routes/api/cache.ts @@ -1,10 +1,10 @@ -import { Handlers } from "$fresh/server.ts"; import { documentTable } from "@lib/db/schema.ts"; import { db } from "@lib/db/sqlite.ts"; import { json } from "@lib/helpers.ts"; import { caches } from "@lib/cache.ts"; +import { define } from "../../utils.ts"; -export const handler: Handlers = { +export const handler = define.handlers({ async DELETE() { for (const cache of caches.values()) { cache.clear(); @@ -12,4 +12,4 @@ export const handler: Handlers = { await db.delete(documentTable).run(); return json({ status: "ok" }); }, -}; +}); diff --git a/routes/api/images/index.ts b/routes/api/images/index.ts index 306a2e2..6bb0351 100644 --- a/routes/api/images/index.ts +++ b/routes/api/images/index.ts @@ -1,6 +1,6 @@ -import { FreshContext, Handlers } from "$fresh/server.ts"; import { getImageContent } from "@lib/image.ts"; import { createLogger } from "@lib/log/index.ts"; +import { Context } from "fresh"; const log = createLogger("api/image"); @@ -69,9 +69,9 @@ async function generateETag(content: Uint8Array): Promise { }"`; } -async function GET(req: Request, _ctx: FreshContext): Promise { +async function GET(ctx: Context): Promise { try { - const url = new URL(req.url); + const url = new URL(ctx.req.url); const params = parseParams(url); if (typeof params === "string") { @@ -106,6 +106,6 @@ async function GET(req: Request, _ctx: FreshContext): Promise { } } -export const handler: Handlers = { +export const handler = { GET, }; diff --git a/routes/api/index.ts b/routes/api/index.ts index a4dcaa8..176efd4 100644 --- a/routes/api/index.ts +++ b/routes/api/index.ts @@ -1,8 +1,8 @@ -import { Handlers } from "$fresh/server.ts"; import { json } from "@lib/helpers.ts"; +import { define } from "../../utils.ts"; -export const handler: Handlers = { +export const handler = define.handlers({ GET() { return json([]); }, -}; +}); diff --git a/routes/api/logs.ts b/routes/api/logs.ts index f3fdf71..d095b5e 100644 --- a/routes/api/logs.ts +++ b/routes/api/logs.ts @@ -1,9 +1,9 @@ -import { Handlers } from "$fresh/server.ts"; import { createStreamResponse } from "@lib/helpers.ts"; +import { define } from "../../utils.ts"; const activeResponses: ReturnType[] = []; -export const handler: Handlers = { +export const handler = define.handlers({ GET() { const r = createStreamResponse(); @@ -11,4 +11,4 @@ export const handler: Handlers = { return r.response; }, -}; +}); diff --git a/routes/api/movies/[name].ts b/routes/api/movies/[name].ts index 71acc67..8d0333d 100644 --- a/routes/api/movies/[name].ts +++ b/routes/api/movies/[name].ts @@ -1,8 +1,7 @@ -import { Handlers } from "$fresh/server.ts"; import { json } from "@lib/helpers.ts"; import * as tmdb from "@lib/tmdb.ts"; -import { fileExtension } from "https://deno.land/x/file_extension@v2.1.0/mod.ts"; import { + fileExtension, formatDate, isString, safeFileName, @@ -11,13 +10,14 @@ import { import { AccessDeniedError, BadRequestError } from "@lib/errors.ts"; import { createResource, fetchResource } from "@lib/marka/index.ts"; import { ReviewResource } from "@lib/marka/schema.ts"; +import { define } from "../../../utils.ts"; -export const handler: Handlers = { - async GET(_, ctx) { +export const handler = define.handlers({ + async GET(ctx) { const movie = await fetchResource(`movies/${ctx.params.name}`); return json(movie?.content); }, - async POST(_, ctx) { + async POST(ctx) { const session = ctx.state.session; if (!session) throw new AccessDeniedError(); @@ -70,4 +70,4 @@ export const handler: Handlers = { return json({ name: fileName }); }, -}; +}); diff --git a/routes/api/movies/enhance/[name].ts b/routes/api/movies/enhance/[name].ts index 0e347c2..7035467 100644 --- a/routes/api/movies/enhance/[name].ts +++ b/routes/api/movies/enhance/[name].ts @@ -1,7 +1,6 @@ -import { FreshContext, Handlers } from "$fresh/server.ts"; -import { fileExtension } from "https://deno.land/x/file_extension@v2.1.0/mod.ts"; import * as tmdb from "@lib/tmdb.ts"; import { + fileExtension, formatDate, isString, safeFileName, @@ -16,80 +15,76 @@ import { import { createRecommendationResource } from "@lib/recommendation.ts"; import { createResource, fetchResource } from "@lib/marka/index.ts"; import { ReviewResource } from "@lib/marka/schema.ts"; +import { define } from "../../../../utils.ts"; -const POST = async ( - req: Request, - ctx: FreshContext, -): Promise => { - const session = ctx.state.session; - if (!session) { - throw new AccessDeniedError(); - } +export const handler = define.handlers({ + POST: async function (ctx) { + const session = ctx.state.session; + if (!session) { + throw new AccessDeniedError(); + } - const movie = await fetchResource( - `movies/${ctx.params.name}`, - ); - if (!movie) { - throw new NotFoundError(); - } + const movie = await fetchResource( + `movies/${ctx.params.name}`, + ); + if (!movie) { + throw new NotFoundError(); + } - const body = await req.json(); - const name = ctx.params.name; - const { tmdbId } = body; - if (!name || !tmdbId) { - throw new BadRequestError(); - } + const body = await ctx.req.json(); + const name = ctx.params.name; + const { tmdbId } = body; + if (!name || !tmdbId) { + throw new BadRequestError(); + } - const movieDetails = await tmdb.getMovie(tmdbId); - const movieCredits = !movie.content?.author && - await tmdb.getMovieCredits(tmdbId); + const movieDetails = await tmdb.getMovie(tmdbId); + const movieCredits = !movie.content?.author && + await tmdb.getMovieCredits(tmdbId); - const director = movieCredits && - movieCredits?.crew?.filter?.((person) => person.job === "Director")[0]; + const director = movieCredits && + movieCredits?.crew?.filter?.((person) => person.job === "Director")[0]; - movie.content ??= { - _type: "Review", - }; - - movie.content.datePublished ??= formatDate(movieDetails.release_date); - - if (director && !movie.content?.author) { - movie.content.author = { - _type: "Person", - name: director.name, + movie.content ??= { + _type: "Review", }; - } - if (movieDetails.genres) { - movie.content.keywords = [ - ...new Set([ - ...(movie.content.keywords?.map((g) => g.toLowerCase()) || []), - ...movieDetails.genres.map((g) => - g.name?.toLowerCase().replaceAll(" ", "-") - ), - ].filter(isString)), - ]; - } + movie.content.datePublished ??= formatDate(movieDetails.release_date); - movie.content.tmdbId ??= tmdbId; + if (director && !movie.content?.author) { + movie.content.author = { + _type: "Person", + name: director.name, + }; + } - let finalPath = ""; - const posterPath = movieDetails.poster_path; - if (posterPath && !movie.content.image) { - const poster = await tmdb.getMoviePoster(posterPath); - const extension = fileExtension(posterPath); - finalPath = `movies/images/${safeFileName(name)}_cover.${extension}`; - await createResource(finalPath, poster); - movie.content.image = finalPath; - } + if (movieDetails.genres) { + movie.content.keywords = [ + ...new Set([ + ...(movie.content.keywords?.map((g) => g.toLowerCase()) || []), + ...movieDetails.genres.map((g) => + g.name?.toLowerCase().replaceAll(" ", "-") + ), + ].filter(isString)), + ]; + } - await createResource(`movies/${toUrlSafeString(movie.name)}.md`, movie); + movie.content.tmdbId ??= tmdbId; - createRecommendationResource(movie, movieDetails.overview); + let finalPath = ""; + const posterPath = movieDetails.poster_path; + if (posterPath && !movie.content.image) { + const poster = await tmdb.getMoviePoster(posterPath); + const extension = fileExtension(posterPath); + finalPath = `movies/images/${safeFileName(name)}_cover.${extension}`; + await createResource(finalPath, poster); + movie.content.image = finalPath; + } - return json(movie); -}; + await createResource(`movies/${toUrlSafeString(movie.name)}.md`, movie); -export const handler: Handlers = { - POST, -}; + createRecommendationResource(movie, movieDetails.overview); + + return json(movie); + }, +}); diff --git a/routes/api/movies/index.ts b/routes/api/movies/index.ts index 18ebf25..0080739 100644 --- a/routes/api/movies/index.ts +++ b/routes/api/movies/index.ts @@ -1,10 +1,10 @@ -import { Handlers } from "$fresh/server.ts"; import { json } from "@lib/helpers.ts"; import { fetchResource } from "@lib/marka/index.ts"; +import { define } from "../../../utils.ts"; -export const handler: Handlers = { +export const handler = define.handlers({ async GET() { const movies = await fetchResource("movies"); return json(movies); }, -}; +}); diff --git a/routes/api/query/index.ts b/routes/api/query/index.ts index c6f1d24..123b27d 100644 --- a/routes/api/query/index.ts +++ b/routes/api/query/index.ts @@ -1,10 +1,11 @@ -import { Handlers } from "$fresh/server.ts"; import { json } from "@lib/helpers.ts"; import { AccessDeniedError, BadRequestError } from "@lib/errors.ts"; import { parseResourceUrl, searchResource } from "@lib/search.ts"; +import { define } from "../../../utils.ts"; -export const handler: Handlers = { - async GET(req, ctx) { +export const handler = define.handlers({ + async GET(ctx) { + const req = ctx.req; const session = ctx.state.session; if (!session) { throw new AccessDeniedError(); @@ -20,4 +21,4 @@ export const handler: Handlers = { return json(resources); }, -}; +}); diff --git a/routes/api/recipes/[name].ts b/routes/api/recipes/[name].ts index 6f60cc1..e8be28a 100644 --- a/routes/api/recipes/[name].ts +++ b/routes/api/recipes/[name].ts @@ -1,10 +1,10 @@ -import { Handlers } from "$fresh/server.ts"; import { json } from "@lib/helpers.ts"; import { fetchResource } from "@lib/marka/index.ts"; +import { define } from "../../../utils.ts"; -export const handler: Handlers = { - async GET(_, ctx) { +export const handler = define.handlers({ + async GET(ctx) { const recipe = await fetchResource(`recipes/${ctx.params.name}`); return json(recipe); }, -}; +}); diff --git a/routes/api/recipes/create/index.ts b/routes/api/recipes/create/index.ts index 19e5371..ab11cab 100644 --- a/routes/api/recipes/create/index.ts +++ b/routes/api/recipes/create/index.ts @@ -1,16 +1,15 @@ -import { Handlers } from "$fresh/server.ts"; import { AccessDeniedError, BadRequestError } from "@lib/errors.ts"; import { createStreamResponse, isValidUrl } from "@lib/helpers.ts"; import * as openai from "@lib/openai.ts"; import { createLogger } from "@lib/log/index.ts"; import recipeSchema from "@lib/recipeSchema.ts"; -import { fileExtension } from "https://deno.land/x/file_extension@v2.1.0/mod.ts"; -import { safeFileName, toUrlSafeString } from "@lib/string.ts"; +import { fileExtension, safeFileName, toUrlSafeString } from "@lib/string.ts"; import { parseJsonLdToRecipeSchema } from "./parseJsonLd.ts"; import z from "zod"; import { createResource } from "@lib/marka/index.ts"; import { webScrape } from "@lib/webScraper.ts"; import { RecipeResource } from "@lib/marka/schema.ts"; +import { define } from "../../../../utils.ts"; const log = createLogger("api/article"); @@ -93,8 +92,9 @@ async function processCreateRecipeFromUrl( streamResponse.send({ type: "finished", url: id }); } -export const handler: Handlers = { - GET(req, ctx) { +export const handler = define.handlers({ + GET(ctx) { + const req = ctx.req; const session = ctx.state.session; if (!session) { throw new AccessDeniedError(); @@ -120,4 +120,4 @@ export const handler: Handlers = { return streamResponse.response; }, -}; +}); diff --git a/routes/api/recipes/index.ts b/routes/api/recipes/index.ts index 7b889e9..6c71156 100644 --- a/routes/api/recipes/index.ts +++ b/routes/api/recipes/index.ts @@ -1,10 +1,10 @@ -import { Handlers } from "$fresh/server.ts"; import { json } from "@lib/helpers.ts"; import { fetchResource } from "@lib/marka/index.ts"; +import { define } from "../../../utils.ts"; -export const handler: Handlers = { +export const handler = define.handlers({ async GET() { const recipes = await fetchResource("recipes"); return json(recipes); }, -}; +}); diff --git a/routes/api/recommendation/all.ts b/routes/api/recommendation/all.ts index 3188a34..7c32bbf 100644 --- a/routes/api/recommendation/all.ts +++ b/routes/api/recommendation/all.ts @@ -1,4 +1,3 @@ -import { Handlers } from "$fresh/server.ts"; import { createStreamResponse } from "@lib/helpers.ts"; import * as tmdb from "@lib/tmdb.ts"; import { @@ -8,6 +7,7 @@ import { import { AccessDeniedError } from "@lib/errors.ts"; import { listResources } from "@lib/marka/index.ts"; import { ReviewResource } from "@lib/marka/schema.ts"; +import { define } from "../../../utils.ts"; async function processUpdateRecommendations( streamResponse: ReturnType, @@ -53,8 +53,8 @@ async function processUpdateRecommendations( streamResponse.info("100% Finished"); } -export const handler: Handlers = { - GET(_, ctx) { +export const handler = define.handlers({ + GET(ctx) { const session = ctx.state.session; if (!session) { throw new AccessDeniedError(); @@ -65,4 +65,4 @@ export const handler: Handlers = { return streamResponse.response; }, -}; +}); diff --git a/routes/api/recommendation/data.ts b/routes/api/recommendation/data.ts index d260237..9abe24b 100644 --- a/routes/api/recommendation/data.ts +++ b/routes/api/recommendation/data.ts @@ -1,10 +1,10 @@ -import { Handlers } from "$fresh/server.ts"; import { AccessDeniedError } from "@lib/errors.ts"; import { getAllRecommendations } from "@lib/recommendation.ts"; import { json } from "@lib/helpers.ts"; +import { define } from "../../../utils.ts"; -export const handler: Handlers = { - async GET(_, ctx) { +export const handler = define.handlers({ + async GET(ctx) { const session = ctx.state.session; if (!session) { throw new AccessDeniedError(); @@ -34,4 +34,4 @@ export const handler: Handlers = { keywords, }); }, -}; +}); diff --git a/routes/api/recommendation/index.ts b/routes/api/recommendation/index.ts index b3e495a..e7d6944 100644 --- a/routes/api/recommendation/index.ts +++ b/routes/api/recommendation/index.ts @@ -1,10 +1,10 @@ -import { Handlers } from "$fresh/server.ts"; import { json } from "@lib/helpers.ts"; import { getAllRecommendations } from "@lib/recommendation.ts"; +import { define } from "../../../utils.ts"; -export const handler: Handlers = { +export const handler = define.handlers({ async GET() { const recommendations = await getAllRecommendations(); return json(recommendations); }, -}; +}); diff --git a/routes/api/recommendation/movie/[id].ts b/routes/api/recommendation/movie/[id].ts index 0e1d587..74e72dd 100644 --- a/routes/api/recommendation/movie/[id].ts +++ b/routes/api/recommendation/movie/[id].ts @@ -1,10 +1,10 @@ -import { Handlers } from "$fresh/server.ts"; import { AccessDeniedError } from "@lib/errors.ts"; import { getSimilarMovies } from "@lib/recommendation.ts"; import { json } from "@lib/helpers.ts"; +import { define } from "../../../../utils.ts"; -export const handler: Handlers = { - async GET(_, ctx) { +export const handler = define.handlers({ + async GET(ctx) { const session = ctx.state.session; if (!session) { throw new AccessDeniedError(); @@ -14,4 +14,4 @@ export const handler: Handlers = { return json(recommendations); }, -}; +}); diff --git a/routes/api/series/[name].ts b/routes/api/series/[name].ts index 5e22588..7ac6db1 100644 --- a/routes/api/series/[name].ts +++ b/routes/api/series/[name].ts @@ -1,12 +1,16 @@ -import { Handlers } from "$fresh/server.ts"; import { json } from "@lib/helpers.ts"; import * as tmdb from "@lib/tmdb.ts"; -import { fileExtension } from "https://deno.land/x/file_extension@v2.1.0/mod.ts"; -import { formatDate, isString, safeFileName } from "@lib/string.ts"; +import { + fileExtension, + formatDate, + isString, + safeFileName, +} from "@lib/string.ts"; import { AccessDeniedError, BadRequestError } from "@lib/errors.ts"; import { createResource, fetchResource } from "@lib/marka/index.ts"; import { ReviewResource } from "@lib/marka/schema.ts"; import { toUrlSafeString } from "@lib/string.ts"; +import { define } from "../../../utils.ts"; function pickDirector( credits: Awaited>, @@ -16,12 +20,12 @@ function pickDirector( return crewDirector?.name ?? createdBy?.[0]?.name; } -export const handler: Handlers = { - async GET(_, ctx) { +export const handler = define.handlers({ + async GET(ctx) { const series = await fetchResource(`series/${ctx.params.name}`); return json(series); }, - async POST(_, ctx) { + async POST(ctx) { const session = ctx.state.session; if (!session) { throw new AccessDeniedError(); @@ -78,4 +82,4 @@ export const handler: Handlers = { return json({ name: fileName }); }, -}; +}); diff --git a/routes/api/series/enhance/[name].ts b/routes/api/series/enhance/[name].ts index 54a191d..215e350 100644 --- a/routes/api/series/enhance/[name].ts +++ b/routes/api/series/enhance/[name].ts @@ -1,7 +1,5 @@ -import { FreshContext, Handlers } from "$fresh/server.ts"; -import { fileExtension } from "https://deno.land/x/file_extension@v2.1.0/mod.ts"; import * as tmdb from "@lib/tmdb.ts"; -import { safeFileName } from "@lib/string.ts"; +import { fileExtension, safeFileName } from "@lib/string.ts"; import { json } from "@lib/helpers.ts"; import { AccessDeniedError, @@ -9,74 +7,72 @@ import { NotFoundError, } from "@lib/errors.ts"; import { createResource, fetchResource } from "@lib/marka/index.ts"; +import { define } from "../../../../utils.ts"; const isString = (input: string | undefined): input is string => { return typeof input === "string"; }; -const POST = async ( - req: Request, - ctx: FreshContext, -): Promise => { - const session = ctx.state.session; - if (!session) { - throw new AccessDeniedError(); - } +export const handler = define.handlers({ + POST: async (ctx) => { + const session = ctx.state.session; + if (!session) { + throw new AccessDeniedError(); + } - const body = await req.json(); - const name = ctx.params.name; - const { tmdbId } = body; - if (!name || !tmdbId) { - throw new BadRequestError(); - } + const body = await ctx.req.json(); + const name = ctx.params.name; + const { tmdbId } = body; + if (!name || !tmdbId) { + throw new BadRequestError(); + } - const series = await fetchResource(`series/${ctx.params.name}`); - if (!series) { - throw new NotFoundError(); - } + const series = await fetchResource(`series/${ctx.params.name}`); + if (!series) { + throw new NotFoundError(); + } - const seriesDetails = await tmdb.getSeries(tmdbId); - const seriesCredits = !series?.content?.author && - await tmdb.getSeriesCredits(tmdbId); + const seriesDetails = await tmdb.getSeries(tmdbId); + const seriesCredits = !series?.content?.author && + await tmdb.getSeriesCredits(tmdbId); - const releaseDate = seriesDetails.first_air_date; - if (releaseDate && series.content?.datePublished) { - series.content.datePublished = new Date(releaseDate).toISOString(); - } - const posterPath = seriesDetails.poster_path; - const director = seriesCredits && - seriesCredits.crew?.filter?.((person) => person.job === "Director")[0] || - seriesDetails?.created_by?.[0]; - if (director && director.name && !series.content?.author) { - series.content.author = series.content.author || { - _type: "Person", - name: director.name, - }; - } + const releaseDate = seriesDetails.first_air_date; + if (releaseDate && series.content?.datePublished) { + series.content.datePublished = new Date(releaseDate).toISOString(); + } + const posterPath = seriesDetails.poster_path; + const director = seriesCredits && + seriesCredits.crew?.filter?.((person) => + person.job === "Director" + )[0] || + seriesDetails?.created_by?.[0]; + if (director && director.name && !series.content?.author) { + series.content.author = series.content.author || { + _type: "Person", + name: director.name, + }; + } - if (seriesDetails.genres) { - series.content.keywords = [ - ...new Set([ - ...(series.content.keywords?.map((t) => t.toLowerCase()) || []), - ...seriesDetails.genres.map((g) => g.name?.toLowerCase()), - ].filter(isString)), - ]; - } + if (seriesDetails.genres) { + series.content.keywords = [ + ...new Set([ + ...(series.content.keywords?.map((t) => t.toLowerCase()) || []), + ...seriesDetails.genres.map((g) => g.name?.toLowerCase()), + ].filter(isString)), + ]; + } - let finalPath = ""; - if (posterPath && !series.content?.image) { - const poster = await tmdb.getMoviePoster(posterPath); - const extension = fileExtension(posterPath); + let finalPath = ""; + if (posterPath && !series.content?.image) { + const poster = await tmdb.getMoviePoster(posterPath); + const extension = fileExtension(posterPath); - finalPath = `series/images/${safeFileName(name)}_cover.${extension}`; - await createResource(finalPath, poster); - series.content.image = finalPath; - } - await createResource(`series/${safeFileName(series.name)}.md`, series); + finalPath = `series/images/${safeFileName(name)}_cover.${extension}`; + await createResource(finalPath, poster); + series.content.image = finalPath; + } + await createResource(`series/${safeFileName(series.name)}.md`, series); - return json(series); -}; - -export const handler: Handlers = { - POST, -}; + return json(series); + }, +}); diff --git a/routes/api/series/index.ts b/routes/api/series/index.ts index ebfcf4c..3555e6b 100644 --- a/routes/api/series/index.ts +++ b/routes/api/series/index.ts @@ -1,10 +1,10 @@ -import { Handlers } from "$fresh/server.ts"; import { json } from "@lib/helpers.ts"; import { fetchResource } from "@lib/marka/index.ts"; +import { define } from "../../../utils.ts"; -export const handler: Handlers = { +export const handler = define.handlers({ async GET() { const series = await fetchResource("series"); return json(series); }, -}; +}); diff --git a/routes/api/tmdb/[id].ts b/routes/api/tmdb/[id].ts index 4e62fac..521634b 100644 --- a/routes/api/tmdb/[id].ts +++ b/routes/api/tmdb/[id].ts @@ -1,7 +1,7 @@ -import { FreshContext, Handlers } from "$fresh/server.ts"; import { getMovie } from "@lib/tmdb.ts"; import { json } from "@lib/helpers.ts"; import { createCache } from "@lib/cache.ts"; +import { define } from "../../../utils.ts"; type CachedMovieCredits = { lastUpdated: number; @@ -13,40 +13,36 @@ const cache = createCache("movie-credits", { expires: CACHE_INTERVAL, }); -const GET = async ( - _req: Request, - _ctx: FreshContext, -) => { - const id = _ctx.params.id; +export const handler = define.handlers({ + GET: async (ctx) => { + const id = ctx.params.id; - if (!id) { - return new Response("Bad Request", { - status: 400, - }); - } + if (!id) { + return new Response("Bad Request", { + status: 400, + }); + } - const cacheId = `/movie/${id}`; + const cacheId = `/movie/${id}`; - const cachedResponse = cache.get(cacheId); - if ( - cachedResponse && Date.now() < (cachedResponse.lastUpdated + CACHE_INTERVAL) - ) { - return json(cachedResponse.data); - } + const cachedResponse = cache.get(cacheId); + if ( + cachedResponse && + Date.now() < (cachedResponse.lastUpdated + CACHE_INTERVAL) + ) { + return json(cachedResponse.data); + } - const res = await getMovie(+id); + const res = await getMovie(+id); - cache.set( - cacheId, - JSON.stringify({ - lastUpdated: Date.now(), - data: res, - }), - ); + cache.set( + cacheId, + JSON.stringify({ + lastUpdated: Date.now(), + data: res, + }), + ); - return json(res); -}; - -export const handler: Handlers = { - GET, -}; + return json(res); + }, +}); diff --git a/routes/api/tmdb/credits/[id].ts b/routes/api/tmdb/credits/[id].ts index f005e62..ec4df8d 100644 --- a/routes/api/tmdb/credits/[id].ts +++ b/routes/api/tmdb/credits/[id].ts @@ -1,8 +1,8 @@ -import { FreshContext } from "$fresh/server.ts"; import { getMovieCredits } from "@lib/tmdb.ts"; import { json } from "@lib/helpers.ts"; import { createLogger } from "@lib/log/index.ts"; import { createCache } from "@lib/cache.ts"; +import { define } from "../../../../utils.ts"; type CachedMovieCredits = { lastUpdated: number; @@ -16,37 +16,37 @@ const cache = createCache("movie-credits", { const log = createLogger("api/tmdb"); -export const handler = async ( - _req: Request, - _ctx: FreshContext, -) => { - const id = _ctx.params.id; +export const handler = define.handlers({ + GET: async (ctx) => { + const id = ctx.params.id; - if (!id) { - return new Response("Bad Request", { - status: 400, - }); - } + if (!id) { + return new Response("Bad Request", { + status: 400, + }); + } - log.debug("getting movie credits"); + log.debug("getting movie credits"); - const cacheId = `/movie/credits/${id}`; + const cacheId = `/movie/credits/${id}`; - const cachedResponse = cache.get(cacheId); - if ( - cachedResponse && Date.now() < (cachedResponse.lastUpdated + CACHE_INTERVAL) - ) { - return json(cachedResponse.data); - } + const cachedResponse = cache.get(cacheId); + if ( + cachedResponse && + Date.now() < (cachedResponse.lastUpdated + CACHE_INTERVAL) + ) { + return json(cachedResponse.data); + } - const res = await getMovieCredits(+id); - cache.set( - cacheId, - JSON.stringify({ - lastUpdated: Date.now(), - data: res, - }), - ); + const res = await getMovieCredits(+id); + cache.set( + cacheId, + JSON.stringify({ + lastUpdated: Date.now(), + data: res, + }), + ); - return json(res); -}; + return json(res); + }, +}); diff --git a/routes/api/tmdb/query.ts b/routes/api/tmdb/query.ts index 6a6367e..2061988 100644 --- a/routes/api/tmdb/query.ts +++ b/routes/api/tmdb/query.ts @@ -1,33 +1,28 @@ -import { FreshContext, Handlers } from "$fresh/server.ts"; import { searchMovie, searchTVShow } from "@lib/tmdb.ts"; import { AccessDeniedError, BadRequestError } from "@lib/errors.ts"; +import { define } from "../../../utils.ts"; -const GET = async ( - req: Request, - ctx: FreshContext, -) => { - const session = ctx.state.session; - if (!session) { - throw new AccessDeniedError(); - } +export const handler = define.handlers({ + GET: async (ctx) => { + const session = ctx.state.session; + if (!session) { + throw new AccessDeniedError(); + } - const u = new URL(req.url); + const u = new URL(ctx.req.url); - const query = u.searchParams.get("q"); + const query = u.searchParams.get("q"); - if (!query) { - throw new BadRequestError(); - } + if (!query) { + throw new BadRequestError(); + } - const type = u.searchParams.get("type") || "movies"; + const type = u.searchParams.get("type") || "movies"; - const res = type === "movies" - ? await searchMovie(query) - : await searchTVShow(query); + const res = type === "movies" + ? await searchMovie(query) + : await searchTVShow(query); - return new Response(JSON.stringify(res.results)); -}; - -export const handler: Handlers = { - GET, -}; + return new Response(JSON.stringify(res.results)); + }, +}); diff --git a/routes/articles/[name].tsx b/routes/articles/[name].tsx index b2ed563..9ef4fe1 100644 --- a/routes/articles/[name].tsx +++ b/routes/articles/[name].tsx @@ -1,4 +1,4 @@ -import { Handlers, PageProps } from "$fresh/server.ts"; +import { PageProps } from "fresh"; import { MainLayout } from "@components/layouts/main.tsx"; import { KMenu } from "@islands/KMenu.tsx"; import { YoutubePlayer } from "@components/Youtube.tsx"; @@ -12,19 +12,20 @@ import { MetaTags } from "@components/MetaTags.tsx"; import { fetchResource } from "@lib/marka/index.ts"; import { ArticleResource } from "@lib/marka/schema.ts"; import { parseRating } from "@lib/helpers.ts"; +import { HttpError } from "fresh"; +import { define } from "../../utils.ts"; -export const handler: Handlers<{ article: ArticleResource; session: unknown }> = - { - async GET(_, ctx) { - const article = await fetchResource( - `articles/${ctx.params.name}.md`, - ); - if (!article) { - return ctx.renderNotFound(); - } - return ctx.render({ article, session: ctx.state.session }); - }, - }; +export const handler = define.handlers({ + async GET(ctx) { + const article = await fetchResource( + `articles/${ctx.params.name}.md`, + ); + if (!article) { + throw new HttpError(404); + } + return { data: { article, session: ctx.state.session } }; + }, +}); export default function Greet( props: PageProps< diff --git a/routes/articles/index.tsx b/routes/articles/index.tsx index e579c67..4cac314 100644 --- a/routes/articles/index.tsx +++ b/routes/articles/index.tsx @@ -1,28 +1,28 @@ -import { Handlers, PageProps } from "$fresh/server.ts"; +import { PageProps } from "fresh"; import { MainLayout } from "@components/layouts/main.tsx"; import { type ArticleResource, GenericResource } from "@lib/marka/schema.ts"; import { KMenu } from "@islands/KMenu.tsx"; import { Grid } from "@components/Grid.tsx"; -import { IconArrowLeft } from "@components/icons.tsx"; import { RedirectSearchHandler } from "@islands/Search.tsx"; import { parseResourceUrl, searchResource } from "@lib/search.ts"; import { ResourceCard } from "@components/Card.tsx"; import { Link } from "@islands/Link.tsx"; import { listResources } from "@lib/marka/index.ts"; +import { define } from "../../utils.ts"; +import { TbArrowLeft } from "@preact-icons/tb"; -export const handler: Handlers< - { articles: ArticleResource[] | null; searchResults?: GenericResource[] } -> = { - async GET(req, ctx) { +export const handler = define.handlers({ + async GET(ctx) { + const req = ctx.req; const articles = await listResources("articles"); const searchParams = parseResourceUrl(req.url); const searchResults = searchParams && await searchResource({ ...searchParams, types: ["articles"] }); - return ctx.render({ articles, searchResults }); + return { data: { articles, searchResults } }; }, -}; +}); -export default function Greet( +export default define.page(function Greet( props: PageProps< { articles: ArticleResource[] | null; searchResults: GenericResource[] } >, @@ -40,7 +40,7 @@ export default function Greet( class="px-4 ml-4 py-2 bg-gray-300 text-gray-800 rounded-lg flex items-center gap-1" href="/" > - + Back @@ -59,4 +59,4 @@ export default function Greet( ); -} +}); diff --git a/routes/index.tsx b/routes/index.tsx index 3dbfdb9..8065dd5 100644 --- a/routes/index.tsx +++ b/routes/index.tsx @@ -1,6 +1,6 @@ import { MainLayout } from "@components/layouts/main.tsx"; import { Card } from "@components/Card.tsx"; -import { PageProps } from "$fresh/server.ts"; +import { PageProps } from "fresh"; import { resources } from "@lib/resources.ts"; import { RedirectSearchHandler } from "@islands/Search.tsx"; import { KMenu } from "@islands/KMenu.tsx"; @@ -16,7 +16,6 @@ export default function Home(props: PageProps) { {Object.values(resources).filter((v) => v.link !== "/").map((m) => { return ( }>, - ctx: RouteContext, -) { +export default define.page(async function (ctx) { + const props = ctx.req; const movie = await fetchResource( `movies/${ctx.params.name}.md`, ); const session = ctx.state.session; if (!movie) { - return ctx.renderNotFound(); + throw new HttpError(404); } const { author, datePublished, reviewBody = "", reviewRating } = @@ -87,4 +86,4 @@ export default async function Greet( ); -} +}); diff --git a/routes/movies/index.tsx b/routes/movies/index.tsx index 4eac1af..c6576d1 100644 --- a/routes/movies/index.tsx +++ b/routes/movies/index.tsx @@ -2,13 +2,13 @@ import { MainLayout } from "@components/layouts/main.tsx"; import { GenericResource, ReviewResource } from "@lib/marka/schema.ts"; import { ResourceCard } from "@components/Card.tsx"; import { Grid } from "@components/Grid.tsx"; -import { IconArrowLeft } from "@components/icons.tsx"; import { KMenu } from "@islands/KMenu.tsx"; import { RedirectSearchHandler } from "@islands/Search.tsx"; -import { PageProps } from "$fresh/server.ts"; +import { PageProps } from "fresh"; import { listResources } from "@lib/marka/index.ts"; import { parseResourceUrl, searchResource } from "@lib/search.ts"; import { parseRating } from "@lib/helpers.ts"; +import { TbArrowLeft } from "@preact-icons/tb"; function sortOptional(a: number | string = 0, b: number | string = 0) { return (parseRating(a) > parseRating(b)) ? 1 : -1; @@ -44,7 +44,7 @@ export default async function MovieIndex( class="px-4 ml-4 py-2 bg-gray-300 text-gray-800 rounded-lg flex items-center gap-1" href="/" > - + {TbArrowLeft({ class: "w-5 h-5" })} Back diff --git a/routes/recipes/[name].tsx b/routes/recipes/[name].tsx index da7a403..b850663 100644 --- a/routes/recipes/[name].tsx +++ b/routes/recipes/[name].tsx @@ -1,4 +1,4 @@ -import { Handlers, PageProps } from "$fresh/server.ts"; +import { PageProps } from "fresh"; import { IngredientsList } from "@islands/IngredientsList.tsx"; import { MainLayout } from "@components/layouts/main.tsx"; import Counter from "@islands/Counter.tsx"; @@ -14,24 +14,24 @@ import { fetchResource } from "@lib/marka/index.ts"; import { RecipeResource } from "@lib/marka/schema.ts"; import { parseIngredients } from "@lib/parseIngredient.ts"; import { parseRating } from "@lib/helpers.ts"; +import { HttpError } from "fresh"; +import { define } from "../../utils.ts"; -export const handler: Handlers< - { recipe: RecipeResource; session: unknown } | null -> = { - async GET(_, ctx) { +export const handler = define.handlers({ + async GET(ctx) { try { const recipe = await fetchResource( `recipes/${ctx.params.name}.md`, ); if (!recipe) { - return ctx.renderNotFound(); + throw new HttpError(404); } - return ctx.render({ recipe, session: ctx.state.session }); + return { data: { recipe, session: ctx.state.session } }; } catch (_e) { - return ctx.renderNotFound(); + throw new HttpError(404); } }, -}; +}); function ValidRecipe({ recipe, @@ -48,11 +48,13 @@ function ValidRecipe({

Ingredients

{portion && } - + { + + }

Preparation

    @@ -135,7 +137,7 @@ export default function Page( ) : (
    - {JSON.stringify(recipe)} + {recipe}
    )}
diff --git a/routes/recipes/index.tsx b/routes/recipes/index.tsx index ab6a9e5..3ba1823 100644 --- a/routes/recipes/index.tsx +++ b/routes/recipes/index.tsx @@ -1,7 +1,7 @@ -import { Handlers, PageProps } from "$fresh/server.ts"; +import { Context, PageProps } from "fresh"; import { MainLayout } from "@components/layouts/main.tsx"; import { Grid } from "@components/Grid.tsx"; -import { IconArrowLeft } from "@components/icons.tsx"; +import { TbArrowLeft } from "@preact-icons/tb"; import { KMenu } from "@islands/KMenu.tsx"; import { RedirectSearchHandler } from "@islands/Search.tsx"; import { ResourceCard } from "@components/Card.tsx"; @@ -9,27 +9,27 @@ import { listResources } from "@lib/marka/index.ts"; import { parseResourceUrl, searchResource } from "@lib/search.ts"; import { GenericResource, RecipeResource } from "@lib/marka/schema.ts"; -export const handler: Handlers< - { recipes: RecipeResource[] | null; searchResults?: GenericResource[] } -> = { - async GET(req, ctx) { +export const handler = { + async GET(ctx: Context<{ test: number }>) { + const req = ctx.req; const recipes = await listResources("recipes"); const searchParams = parseResourceUrl(req.url); const searchResults = searchParams && await searchResource({ ...searchParams, types: ["recipes"] }); - return ctx.render({ recipes, searchResults }); + return { data: { recipes, searchResults } }; }, }; -export default function Greet( - props: PageProps< - { recipes: RecipeResource[] | null; searchResults: GenericResource[] } - >, +export default function Page( + { data, url }: PageProps<{ + recipes: RecipeResource[] | null; + searchResults: GenericResource[]; + }>, ) { - const { recipes, searchResults } = props.data; + const { recipes, searchResults } = data; return ( - + Back diff --git a/routes/series/[name].tsx b/routes/series/[name].tsx index ec8527f..31c589f 100644 --- a/routes/series/[name].tsx +++ b/routes/series/[name].tsx @@ -1,4 +1,4 @@ -import { Handlers, PageProps } from "$fresh/server.ts"; +import { PageProps } from "fresh"; import { MainLayout } from "@components/layouts/main.tsx"; import { HashTags } from "@components/HashTags.tsx"; import { removeImage, renderMarkdown } from "@lib/markdown.ts"; @@ -9,20 +9,22 @@ import { Star } from "@components/Stars.tsx"; import { MetaTags } from "@components/MetaTags.tsx"; import { parseRating } from "@lib/helpers.ts"; import { fetchResource } from "@lib/marka/index.ts"; -import { getNameOfResource, ReviewResource } from "@lib/marka/schema.ts"; +import { ReviewResource } from "@lib/marka/schema.ts"; +import { HttpError } from "fresh"; +import { define } from "../../utils.ts"; -export const handler: Handlers<{ serie: ReviewResource; session: unknown }> = { - async GET(_, ctx) { +export const handler = define.handlers({ + async GET(ctx) { const serie = await fetchResource( `series/${ctx.params.name}.md`, ); if (!serie) { - return ctx.renderNotFound(); + throw new HttpError(404); } - return ctx.render({ serie, session: ctx.state.session }); + return { data: { serie, session: ctx.state.session } }; }, -}; +}); export default function Greet( props: PageProps<{ serie: ReviewResource; session: Record }>, diff --git a/routes/series/index.tsx b/routes/series/index.tsx index 11d2c89..82a874a 100644 --- a/routes/series/index.tsx +++ b/routes/series/index.tsx @@ -1,25 +1,28 @@ -import { Handlers, PageProps } from "$fresh/server.ts"; +import { PageProps } from "fresh"; import { MainLayout } from "@components/layouts/main.tsx"; import { Grid } from "@components/Grid.tsx"; -import { IconArrowLeft } from "@components/icons.tsx"; import { RedirectSearchHandler } from "@islands/Search.tsx"; import { KMenu } from "@islands/KMenu.tsx"; import { ResourceCard } from "@components/Card.tsx"; import { listResources } from "@lib/marka/index.ts"; import { parseResourceUrl, searchResource } from "@lib/search.ts"; import { GenericResource, ReviewResource } from "@lib/marka/schema.ts"; +import { define } from "../../utils.ts"; +import { TbArrowLeft } from "@preact-icons/tb"; -export const handler: Handlers< - { series: ReviewResource[] | null; searchResults?: GenericResource[] } -> = { - async GET(req, ctx) { +// : < +// { series: ReviewResource[] | null; searchResults?: GenericResource[] } +// > +export const handler = define.handlers({ + async GET(ctx) { + const req = ctx.req; const series = await listResources("series"); const searchParams = parseResourceUrl(req.url); const searchResults = searchParams && await searchResource({ ...searchParams, types: ["series"] }); - return ctx.render({ series, searchResults }); + return { data: { series, searchResults } }; }, -}; +}); export default function Greet( props: PageProps< @@ -42,7 +45,7 @@ export default function Greet( class="px-4 ml-4 py-2 bg-gray-300 text-gray-800 rounded-lg flex items-center gap-1" href="/" > - + Back diff --git a/utils.ts b/utils.ts new file mode 100644 index 0000000..4eedf12 --- /dev/null +++ b/utils.ts @@ -0,0 +1,11 @@ +import { createDefine } from "fresh"; + +export interface State { + session: { + id: string; + name: string; + exp: number; + }; +} + +export const define = createDefine(); diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..07973da --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "vite"; +import { fresh } from "@fresh/plugin-vite"; +import tailwindcss from "@tailwindcss/vite"; + +export default defineConfig({ + plugins: [ + fresh(), + tailwindcss(), + ], +});