feat: trying to add hashes to scripts

This commit is contained in:
Max Richter
2026-01-10 13:03:13 +01:00
parent e65938ecc2
commit e55f787a29
79 changed files with 4209 additions and 720 deletions

View File

@@ -1,4 +1,4 @@
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 && \

1
assets/style.css Normal file
View File

@@ -0,0 +1 @@
@import "tailwindcss";

1
client.ts Normal file
View File

@@ -0,0 +1 @@
// import "./assets/style.css";

View File

@@ -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<HTMLButtonElement>) {
export function Button(props: ButtonHTMLAttributes<HTMLButtonElement>) {
return (
<button
{...props}

View File

@@ -1,4 +1,4 @@
import { asset } from "$fresh/runtime.ts";
import { asset } from "fresh/runtime";
export const Emoji = (props: { class?: string; name: string }) => {
return props.name

View File

@@ -1,4 +1,4 @@
import { asset } from "$fresh/runtime.ts";
import { asset } from "fresh/runtime";
import * as CSS from "csstype";
interface ResponsiveAttributes {

View File

@@ -1,4 +1,4 @@
import { Head } from "$fresh/runtime.ts";
import { Head } from "fresh/runtime";
import { GenericResource, getNameOfResource } from "@lib/marka/schema.ts";
import { formatDate } from "@lib/string.ts";

View File

@@ -1,21 +1,23 @@
export { default as IconStar } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/star.tsx";
export { default as IconStarFilled } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/star-filled.tsx";
export { default as IconExternalLink } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/external-link.tsx";
export { default as IconArrowNarrowLeft } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/arrow-narrow-left.tsx";
export { default as IconEdit } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/edit.tsx";
export { default as IconArrowLeft } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/arrow-left.tsx";
export { default as IconError404 } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/error-404.tsx";
export { default as IconSquareRoundedPlus } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/square-rounded-plus.tsx";
export { default as IconReportSearch } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/report-search.tsx";
export { default as IconRefresh } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/refresh.tsx";
export { default as IconCirclePlus } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/circle-plus.tsx";
export { default as IconCircleMinus } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/circle-minus.tsx";
export { default as IconLoader2 } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/loader-2.tsx";
export { default as IconLogin } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/login.tsx";
export { default as IconLogout } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/logout.tsx";
export { default as IconSearch } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/search.tsx";
export { default as IconGhost } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/ghost.tsx";
export { default as IconBrandYoutube } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/brand-youtube.tsx";
export { default as IconWand } from "https://deno.land/x/tabler_icons_tsx@0.0.5/tsx/wand.tsx";
export { default as IconMenu2 } from "https://deno.land/x/tabler_icons_tsx@0.0.5/tsx/menu-2.tsx";
export { default as IconAlertCircle } from "https://deno.land/x/tabler_icons_tsx@0.0.5/tsx/alert-circle.tsx";
export {
TbAlertCircle as IconAlertCircle,
TbArrowLeft as IconArrowLeft,
TbArrowNarrowLeft as IconArrowNarrowLeft,
TbBrandYoutube as IconBrandYoutube,
TbCircleMinus as IconCircleMinus,
TbCirclePlus as IconCirclePlus,
TbEdit as IconEdit,
TbError404 as IconError404,
TbExternalLink as IconExternalLink,
TbGhost as IconGhost,
TbLoader2 as IconLoader2,
TbLogin as IconLogin,
TbLogout as IconLogout,
TbMenu2 as IconMenu2,
TbRefresh as IconRefresh,
TbReportSearch as IconReportSearch,
TbSearch as IconSearch,
TbSquareRoundedPlus as IconSquareRoundedPlus,
TbStar as IconStar,
TbStarFilled as IconStarFilled,
TbWand as IconWand,
} from "@preact-icons/tb";

View File

@@ -36,5 +36,5 @@ export const MainLayout = (
);
}
return children;
return <>{children}</>;
};

115
deno.json
View File

@@ -1,19 +1,27 @@
{
"lock": false,
"nodeModulesDir": "auto",
"unstable": ["cron"],
"unstable": [
"cron"
],
"tasks": {
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
"dev": "deno run --env-file -A --watch=static/,routes/ dev.ts",
"check": "deno fmt --check && deno lint && deno check",
"start": "deno run --env-file -A main.ts",
"db": "deno run --env-file -A npm:drizzle-kit",
"build": "deno run -A dev.ts build",
"preview": "deno run -A main.ts",
"update": "deno run -A -r https://fresh.deno.dev/update ."
"update": "deno run -A -r jsr:@fresh/update .",
"dev": "vite",
"build": "vite build",
"preview": "deno serve -A _fresh/server.js"
},
"lint": {
"rules": {
"tags": [
"fresh",
"recommended"
]
}
},
"lint": { "rules": { "tags": ["fresh", "recommended"] } },
"imports": {
"$fresh/": "https://deno.land/x/fresh@1.7.3/",
"@cmd-johnson/oauth2-client": "jsr:@cmd-johnson/oauth2-client@^2.0.0",
"@components": "./components",
"@components/": "./components/",
"@denosaurs/emoji": "jsr:@denosaurs/emoji@^0.3.1",
@@ -22,37 +30,80 @@
"@lib": "./lib",
"@lib/": "./lib/",
"@libsql/client": "npm:@libsql/client@^0.14.0",
"@openai/openai": "jsr:@openai/openai@^6.7.0",
"@preact/signals": "https://esm.sh/*@preact/signals@1.2.2",
"@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.5.1",
"@std/http": "jsr:@std/http@^1.0.12",
"@std/yaml": "jsr:@std/yaml@^1.0.5",
"csstype": "npm:csstype@^3.1.3",
"@openai/openai": "jsr:@openai/openai@^6.16.0",
"@preact-icons/tb": "jsr:@preact-icons/tb@^1.0.14",
"@std/fs": "jsr:@std/fs@^1.0.21",
"@std/http": "jsr:@std/http@^1.0.23",
"@std/media-types": "jsr:@std/media-types@^1.1.0",
"@std/yaml": "jsr:@std/yaml@^1.0.10",
"@zaubrik/djwt": "jsr:@zaubrik/djwt@^3.0.2",
"csstype": "npm:csstype@^3.2.3",
"defuddle": "npm:defuddle@^0.6.6",
"drizzle-kit": "npm:drizzle-kit@^0.30.1",
"drizzle-orm": "npm:drizzle-orm@^0.38.3",
"drizzle-kit": "npm:drizzle-kit@^0.30.6",
"drizzle-orm": "npm:drizzle-orm@^0.38.4",
"fuzzysort": "npm:fuzzysort@^3.1.0",
"grammy": "npm:grammy@^1.39.2",
"jsdom": "npm:jsdom@^24.1.3",
"moviedb-promise": "npm:moviedb-promise@^4.0.7",
"parse-ingredient": "npm:parse-ingredient@^1.3.1",
"playwright": "npm:playwright@^1.49.1",
"playwright": "npm:playwright@^1.57.0",
"playwright-extra": "npm:playwright-extra@^4.3.6",
"preact": "https://esm.sh/preact@10.22.0",
"preact-render-to-string": "https://esm.sh/*preact-render-to-string@6.2.2",
"preact/": "https://esm.sh/preact@10.22.0/",
"gfm": "jsr:@deno/gfm",
"gfm": "jsr:@deno/gfm@0.11.0",
"prismjs": "npm:prismjs@^1.30.0",
"puppeteer-extra-plugin-stealth": "npm:puppeteer-extra-plugin-stealth@^2.11.2",
"tailwindcss": "npm:tailwindcss@^3.4.17",
"tailwindcss/": "npm:/tailwindcss@^3.4.17/",
"tailwindcss/plugin": "npm:/tailwindcss@^3.4.17/plugin.js",
"camelcase-css": "npm:camelcase-css",
"sharp": "npm:sharp@^0.34.5",
"tailwindcss/": "npm:/tailwindcss@^3.4.19/",
"tailwindcss/plugin": "npm:/tailwindcss@^3.4.19/plugin.js",
"camelcase-css": "npm:camelcase-css@2.0.1",
"thumbhash": "npm:thumbhash@^0.1.1",
"tsx": "npm:tsx@^4.19.2",
"tsx": "npm:tsx@^4.21.0",
"turndown": "npm:turndown@^7.2.2",
"yaml": "https://deno.land/std@0.197.0/yaml/mod.ts",
"zod": "npm:zod@^3.24.1",
"fs": "https://deno.land/std/fs/mod.ts"
"zod": "npm:zod@^3.25.76",
"fresh": "jsr:@fresh/core@^2.2.0",
"preact": "npm:preact@^10.27.2",
"@preact/signals": "npm:@preact/signals@^2.5.0",
"@fresh/plugin-vite": "jsr:@fresh/plugin-vite@^1.0.8",
"vite": "npm:vite@^7.3.1",
"tailwindcss": "npm:tailwindcss@^4.1.18",
"@tailwindcss/vite": "npm:@tailwindcss/vite@^4.1.18",
"preact/": "https://esm.sh/preact@10.22.0/"
},
"compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" },
"exclude": ["**/_fresh/*"]
"compilerOptions": {
"lib": [
"dom",
"dom.asynciterable",
"dom.iterable",
"deno.ns"
],
"jsx": "react-jsx",
"jsxImportSource": "preact",
"jsxPrecompileSkipElements": [
"a",
"img",
"source",
"body",
"html",
"head",
"title",
"meta",
"script",
"link",
"style",
"base",
"noscript",
"template"
],
"types": [
"vite/client"
]
},
"exclude": [
"**/_fresh/*"
],
"allowScripts": {
"allow": [
"npm:esbuild@0.27.2",
"npm:sharp@0.34.5"
]
}
}

3510
deno.lock generated Normal file

File diff suppressed because it is too large Load Diff

8
dev.ts
View File

@@ -1,8 +0,0 @@
#!/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", config);
Deno.exit(0);

View File

@@ -1,5 +0,0 @@
import { defineConfig } from "$fresh/server.ts";
import tailwind from "$fresh/plugins/tailwind.ts";
export default defineConfig({
plugins: [tailwind()],
});

View File

@@ -1,145 +0,0 @@
// DO NOT EDIT. This file is generated by Fresh.
// This file SHOULD be checked into source version control.
// This file is automatically updated during development when running `dev.ts`.
import * as $_404 from "./routes/_404.tsx";
import * as $_app from "./routes/_app.tsx";
import * as $_layout from "./routes/_layout.tsx";
import * as $_middleware from "./routes/_middleware.ts";
import * as $admin_cache_index from "./routes/admin/cache/index.tsx";
import * as $admin_log_index from "./routes/admin/log/index.tsx";
import * as $admin_performance_index from "./routes/admin/performance/index.tsx";
import * as $api_articles_name_ from "./routes/api/articles/[name].ts";
import * as $api_articles_create_index from "./routes/api/articles/create/index.ts";
import * as $api_articles_enhance_name_ from "./routes/api/articles/enhance/[name].ts";
import * as $api_articles_index from "./routes/api/articles/index.ts";
import * as $api_auth_callback from "./routes/api/auth/callback.ts";
import * as $api_auth_login from "./routes/api/auth/login.ts";
import * as $api_auth_logout from "./routes/api/auth/logout.ts";
import * as $api_cache from "./routes/api/cache.ts";
import * as $api_images_index from "./routes/api/images/index.ts";
import * as $api_index from "./routes/api/index.ts";
import * as $api_logs from "./routes/api/logs.ts";
import * as $api_movies_name_ from "./routes/api/movies/[name].ts";
import * as $api_movies_enhance_name_ from "./routes/api/movies/enhance/[name].ts";
import * as $api_movies_index from "./routes/api/movies/index.ts";
import * as $api_query_index from "./routes/api/query/index.ts";
import * as $api_recipes_name_ from "./routes/api/recipes/[name].ts";
import * as $api_recipes_create_index from "./routes/api/recipes/create/index.ts";
import * as $api_recipes_create_parseJsonLd from "./routes/api/recipes/create/parseJsonLd.ts";
import * as $api_recipes_index from "./routes/api/recipes/index.ts";
import * as $api_recommendation_all from "./routes/api/recommendation/all.ts";
import * as $api_recommendation_data from "./routes/api/recommendation/data.ts";
import * as $api_recommendation_index from "./routes/api/recommendation/index.ts";
import * as $api_recommendation_movie_id_ from "./routes/api/recommendation/movie/[id].ts";
import * as $api_series_name_ from "./routes/api/series/[name].ts";
import * as $api_series_enhance_name_ from "./routes/api/series/enhance/[name].ts";
import * as $api_series_index from "./routes/api/series/index.ts";
import * as $api_tmdb_id_ from "./routes/api/tmdb/[id].ts";
import * as $api_tmdb_credits_id_ from "./routes/api/tmdb/credits/[id].ts";
import * as $api_tmdb_query from "./routes/api/tmdb/query.ts";
import * as $articles_name_ from "./routes/articles/[name].tsx";
import * as $articles_index from "./routes/articles/index.tsx";
import * as $index from "./routes/index.tsx";
import * as $movies_name_ from "./routes/movies/[name].tsx";
import * as $movies_index from "./routes/movies/index.tsx";
import * as $recipes_name_ from "./routes/recipes/[name].tsx";
import * as $recipes_index from "./routes/recipes/index.tsx";
import * as $series_name_ from "./routes/series/[name].tsx";
import * as $series_index from "./routes/series/index.tsx";
import * as $Counter from "./islands/Counter.tsx";
import * as $IngredientsList from "./islands/IngredientsList.tsx";
import * as $KMenu from "./islands/KMenu.tsx";
import * as $KMenu_commands from "./islands/KMenu/commands.ts";
import * as $KMenu_commands_add_movie_infos from "./islands/KMenu/commands/add_movie_infos.ts";
import * as $KMenu_commands_add_series_infos from "./islands/KMenu/commands/add_series_infos.ts";
import * as $KMenu_commands_create_article from "./islands/KMenu/commands/create_article.ts";
import * as $KMenu_commands_create_movie from "./islands/KMenu/commands/create_movie.ts";
import * as $KMenu_commands_create_recipe from "./islands/KMenu/commands/create_recipe.ts";
import * as $KMenu_commands_create_recommendations from "./islands/KMenu/commands/create_recommendations.ts";
import * as $KMenu_commands_create_series from "./islands/KMenu/commands/create_series.ts";
import * as $KMenu_commands_enhance_article_infos from "./islands/KMenu/commands/enhance_article_infos.ts";
import * as $KMenu_types from "./islands/KMenu/types.ts";
import * as $KMenuButton from "./islands/KMenuButton.tsx";
import * as $Link from "./islands/Link.tsx";
import * as $Recommendations from "./islands/Recommendations.tsx";
import * as $Search from "./islands/Search.tsx";
import type { Manifest } from "$fresh/server.ts";
const manifest = {
routes: {
"./routes/_404.tsx": $_404,
"./routes/_app.tsx": $_app,
"./routes/_layout.tsx": $_layout,
"./routes/_middleware.ts": $_middleware,
"./routes/admin/cache/index.tsx": $admin_cache_index,
"./routes/admin/log/index.tsx": $admin_log_index,
"./routes/admin/performance/index.tsx": $admin_performance_index,
"./routes/api/articles/[name].ts": $api_articles_name_,
"./routes/api/articles/create/index.ts": $api_articles_create_index,
"./routes/api/articles/enhance/[name].ts": $api_articles_enhance_name_,
"./routes/api/articles/index.ts": $api_articles_index,
"./routes/api/auth/callback.ts": $api_auth_callback,
"./routes/api/auth/login.ts": $api_auth_login,
"./routes/api/auth/logout.ts": $api_auth_logout,
"./routes/api/cache.ts": $api_cache,
"./routes/api/images/index.ts": $api_images_index,
"./routes/api/index.ts": $api_index,
"./routes/api/logs.ts": $api_logs,
"./routes/api/movies/[name].ts": $api_movies_name_,
"./routes/api/movies/enhance/[name].ts": $api_movies_enhance_name_,
"./routes/api/movies/index.ts": $api_movies_index,
"./routes/api/query/index.ts": $api_query_index,
"./routes/api/recipes/[name].ts": $api_recipes_name_,
"./routes/api/recipes/create/index.ts": $api_recipes_create_index,
"./routes/api/recipes/create/parseJsonLd.ts":
$api_recipes_create_parseJsonLd,
"./routes/api/recipes/index.ts": $api_recipes_index,
"./routes/api/recommendation/all.ts": $api_recommendation_all,
"./routes/api/recommendation/data.ts": $api_recommendation_data,
"./routes/api/recommendation/index.ts": $api_recommendation_index,
"./routes/api/recommendation/movie/[id].ts": $api_recommendation_movie_id_,
"./routes/api/series/[name].ts": $api_series_name_,
"./routes/api/series/enhance/[name].ts": $api_series_enhance_name_,
"./routes/api/series/index.ts": $api_series_index,
"./routes/api/tmdb/[id].ts": $api_tmdb_id_,
"./routes/api/tmdb/credits/[id].ts": $api_tmdb_credits_id_,
"./routes/api/tmdb/query.ts": $api_tmdb_query,
"./routes/articles/[name].tsx": $articles_name_,
"./routes/articles/index.tsx": $articles_index,
"./routes/index.tsx": $index,
"./routes/movies/[name].tsx": $movies_name_,
"./routes/movies/index.tsx": $movies_index,
"./routes/recipes/[name].tsx": $recipes_name_,
"./routes/recipes/index.tsx": $recipes_index,
"./routes/series/[name].tsx": $series_name_,
"./routes/series/index.tsx": $series_index,
},
islands: {
"./islands/Counter.tsx": $Counter,
"./islands/IngredientsList.tsx": $IngredientsList,
"./islands/KMenu.tsx": $KMenu,
"./islands/KMenu/commands.ts": $KMenu_commands,
"./islands/KMenu/commands/add_movie_infos.ts":
$KMenu_commands_add_movie_infos,
"./islands/KMenu/commands/add_series_infos.ts":
$KMenu_commands_add_series_infos,
"./islands/KMenu/commands/create_article.ts":
$KMenu_commands_create_article,
"./islands/KMenu/commands/create_movie.ts": $KMenu_commands_create_movie,
"./islands/KMenu/commands/create_recipe.ts": $KMenu_commands_create_recipe,
"./islands/KMenu/commands/create_recommendations.ts":
$KMenu_commands_create_recommendations,
"./islands/KMenu/commands/create_series.ts": $KMenu_commands_create_series,
"./islands/KMenu/commands/enhance_article_infos.ts":
$KMenu_commands_enhance_article_infos,
"./islands/KMenu/types.ts": $KMenu_types,
"./islands/KMenuButton.tsx": $KMenuButton,
"./islands/Link.tsx": $Link,
"./islands/Recommendations.tsx": $Recommendations,
"./islands/Search.tsx": $Search,
},
baseUrl: import.meta.url,
} satisfies Manifest;
export default manifest;

View File

@@ -1,6 +1,6 @@
import type { Signal } from "@preact/signals";
import { Button } from "@components/Button.tsx";
import { IconCircleMinus, IconCirclePlus } from "@components/icons.tsx";
import { TbCircleMinus, TbCirclePlus } from "@preact-icons/tb";
interface CounterProps {
count: Signal<number>;
@@ -14,17 +14,20 @@ export default function Counter(props: CounterProps) {
class=""
onClick={() => props.count.value -= 1}
>
<IconCircleMinus />
<TbCircleMinus />
</Button>
<input
class="text-3xl bg-transparent inline text-center -mx-4"
type="number"
size={props.count.toString().length}
value={props.count}
onInput={(ev) => props.count.value = ev.target?.value}
onInput={(ev) => {
const target = ev.target as HTMLInputElement;
props.count.value = Math.max(1, Number(target.value));
}}
/>
<Button onClick={() => props.count.value += 1}>
<IconCirclePlus />
<TbCirclePlus />
</Button>
</div>
);

View File

@@ -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<number>;
portion?: number;
}
> = (
{ ingredients, amount, portion },
},
) => {
return (
<table class="w-full border-collapse table-auto">

View File

@@ -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 }: {

View File

@@ -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";

View File

@@ -42,8 +42,12 @@ export const addMovieInfos: MenuEntry = {
globalThis.location.reload();
} catch (e) {
state.activeState.value = "error";
if (e instanceof Error) {
if ("message" in e) {
state.loadingText.value = e.message;
}
}
}
},
})),
};
@@ -53,8 +57,12 @@ export const addMovieInfos: MenuEntry = {
state.activeState.value = "normal";
} catch (e) {
state.activeState.value = "error";
if (e instanceof Error) {
if ("message" in e) {
state.loadingText.value = e.message;
}
}
}
},
visible: () => {
const loc = globalThis["location"];

View File

@@ -42,8 +42,12 @@ export const addSeriesInfo: MenuEntry = {
//window.location.reload();
} catch (e) {
state.activeState.value = "error";
if (e instanceof Error) {
if ("message" in e) {
state.loadingText.value = e.message;
}
}
}
},
})),
};
@@ -53,8 +57,12 @@ export const addSeriesInfo: MenuEntry = {
state.activeState.value = "normal";
} catch (e) {
state.activeState.value = "error";
if (e instanceof Error) {
if ("message" in e) {
state.loadingText.value = e.message;
}
}
}
},
visible: () => {
const loc = globalThis["location"];

View File

@@ -66,8 +66,12 @@ export const createNewMovie: MenuEntry = {
globalThis.location.href = "/movies/" + movie.name;
} catch (e) {
state.activeState.value = "error";
if (e instanceof Error) {
if ("message" in e) {
state.loadingText.value = e.message;
}
}
}
},
};
}),
@@ -75,8 +79,12 @@ export const createNewMovie: MenuEntry = {
state.activeMenu.value = "input_link";
} catch (e) {
state.activeState.value = "error";
if (e instanceof Error) {
if ("message" in e) {
state.loadingText.value = e.message;
}
}
}
}, 500);
const unsub = state.commandInput.subscribe((value) => {

View File

@@ -68,8 +68,12 @@ export const createNewSeries: MenuEntry = {
globalThis.location.href = "/series/" + series.name;
} catch (e) {
state.activeState.value = "error";
if (e instanceof Error) {
if ("message" in e) {
state.loadingText.value = e.message;
}
}
}
},
};
}),
@@ -78,8 +82,12 @@ export const createNewSeries: MenuEntry = {
state.activeMenu.value = "input_link";
} catch (e) {
state.activeState.value = "error";
if (e instanceof Error) {
if ("message" in e) {
state.loadingText.value = e.message;
}
}
}
}, 500);
const unsub = state.commandInput.subscribe((value) => {

View File

@@ -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";

View File

@@ -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,

View File

@@ -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<State>) => void;
constructor(public statusText = "Internal Server Error") {
super();
}

View File

@@ -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";

View File

@@ -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;

View File

@@ -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());

View File

@@ -1,4 +1,4 @@
import { z } from "npm:zod";
import { z } from "zod";
import { RecipeResource } from "./marka/schema.ts";
export const IngredientSchema = z.object({

View File

@@ -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
);
}

View File

@@ -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);
}

View File

@@ -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";

17
main.ts
View File

@@ -1,12 +1,7 @@
/// <reference no-default-lib="true" />
/// <reference lib="dom" />
/// <reference lib="dom.iterable" />
/// <reference lib="dom.asynciterable" />
/// <reference lib="deno.ns" />
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();

View File

@@ -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");

View File

@@ -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 <h1>404 - Page not found</h1>;
}
}
export default function Error404() {
return (
<>
<Head>

View File

@@ -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 (

View File

@@ -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<typeof ctx.state.session>(
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];

View File

@@ -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<typeof getCacheInfo> }
> = {
GET(_, ctx) {
return ctx.render({ cacheInfo: getCacheInfo() });
export const handler = define.handlers({
GET() {
return { data: { cacheInfo: getCacheInfo() } };
},
};
});
export default function Greet(
props: PageProps<

View File

@@ -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({
return {
data: {
logs: logs.map((l) => {
return {
...l,
html: l.args.map(renderLog).join("<br/>"),
};
}),
});
},
};
},
});
function LogLine(
{ log }: {

View File

@@ -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 }: {

View File

@@ -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);
},
};
});

View File

@@ -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;
},
};
});

View File

@@ -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,10 +162,8 @@ async function processEnhanceArticle(
streamResponse.send({ type: "finished", url: name.replace(/$\.md/, "") });
}
const POST = (
_req: Request,
ctx: FreshContext,
): Response => {
export const handler = define.handlers({
POST: (ctx) => {
const session = ctx.state.session;
if (!session) {
throw new AccessDeniedError();
@@ -184,8 +181,5 @@ const POST = (
});
return streamResponse.response;
};
export const handler: Handlers = {
POST,
};
},
});

View File

@@ -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);
},
};
});

View File

@@ -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,
});
},
};
});

View File

@@ -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,
});
},
};
});

View File

@@ -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,
});
},
};
});

View File

@@ -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" });
},
};
});

View File

@@ -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<ArrayBuffer>): Promise<string> {
}"`;
}
async function GET(req: Request, _ctx: FreshContext): Promise<Response> {
async function GET(ctx: Context<unknown>): Promise<Response> {
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<Response> {
}
}
export const handler: Handlers = {
export const handler = {
GET,
};

View File

@@ -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([]);
},
};
});

View File

@@ -1,9 +1,9 @@
import { Handlers } from "$fresh/server.ts";
import { createStreamResponse } from "@lib/helpers.ts";
import { define } from "../../utils.ts";
const activeResponses: ReturnType<typeof createStreamResponse>[] = [];
export const handler: Handlers = {
export const handler = define.handlers({
GET() {
const r = createStreamResponse();
@@ -11,4 +11,4 @@ export const handler: Handlers = {
return r.response;
},
};
});

View File

@@ -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 });
},
};
});

View File

@@ -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,11 +15,10 @@ 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<Response> => {
export const handler = define.handlers({
POST: async function (ctx) {
const session = ctx.state.session;
if (!session) {
throw new AccessDeniedError();
@@ -33,7 +31,7 @@ const POST = async (
throw new NotFoundError();
}
const body = await req.json();
const body = await ctx.req.json();
const name = ctx.params.name;
const { tmdbId } = body;
if (!name || !tmdbId) {
@@ -88,8 +86,5 @@ const POST = async (
createRecommendationResource(movie, movieDetails.overview);
return json(movie);
};
export const handler: Handlers = {
POST,
};
},
});

View File

@@ -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);
},
};
});

View File

@@ -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);
},
};
});

View File

@@ -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);
},
};
});

View File

@@ -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;
},
};
});

View File

@@ -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);
},
};
});

View File

@@ -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<typeof createStreamResponse>,
@@ -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;
},
};
});

View File

@@ -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,
});
},
};
});

View File

@@ -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);
},
};
});

View File

@@ -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);
},
};
});

View File

@@ -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<ReturnType<typeof tmdb.getSeriesCredits>>,
@@ -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 });
},
};
});

View File

@@ -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,21 +7,20 @@ 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<Response> => {
export const handler = define.handlers({
POST: async (ctx) => {
const session = ctx.state.session;
if (!session) {
throw new AccessDeniedError();
}
const body = await req.json();
const body = await ctx.req.json();
const name = ctx.params.name;
const { tmdbId } = body;
if (!name || !tmdbId) {
@@ -45,7 +42,9 @@ const POST = async (
}
const posterPath = seriesDetails.poster_path;
const director = seriesCredits &&
seriesCredits.crew?.filter?.((person) => person.job === "Director")[0] ||
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 || {
@@ -75,8 +74,5 @@ const POST = async (
await createResource(`series/${safeFileName(series.name)}.md`, series);
return json(series);
};
export const handler: Handlers = {
POST,
};
},
});

View File

@@ -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);
},
};
});

View File

@@ -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,11 +13,9 @@ const cache = createCache<CachedMovieCredits>("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", {
@@ -29,7 +27,8 @@ const GET = async (
const cachedResponse = cache.get(cacheId);
if (
cachedResponse && Date.now() < (cachedResponse.lastUpdated + CACHE_INTERVAL)
cachedResponse &&
Date.now() < (cachedResponse.lastUpdated + CACHE_INTERVAL)
) {
return json(cachedResponse.data);
}
@@ -45,8 +44,5 @@ const GET = async (
);
return json(res);
};
export const handler: Handlers = {
GET,
};
},
});

View File

@@ -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,11 +16,9 @@ const cache = createCache<CachedMovieCredits>("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", {
@@ -34,7 +32,8 @@ export const handler = async (
const cachedResponse = cache.get(cacheId);
if (
cachedResponse && Date.now() < (cachedResponse.lastUpdated + CACHE_INTERVAL)
cachedResponse &&
Date.now() < (cachedResponse.lastUpdated + CACHE_INTERVAL)
) {
return json(cachedResponse.data);
}
@@ -49,4 +48,5 @@ export const handler = async (
);
return json(res);
};
},
});

View File

@@ -1,17 +1,15 @@
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,
) => {
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");
@@ -26,8 +24,5 @@ const GET = async (
: await searchTVShow(query);
return new Response(JSON.stringify(res.results));
};
export const handler: Handlers = {
GET,
};
},
});

View File

@@ -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) {
export const handler = define.handlers({
async GET(ctx) {
const article = await fetchResource<ArticleResource>(
`articles/${ctx.params.name}.md`,
);
if (!article) {
return ctx.renderNotFound();
throw new HttpError(404);
}
return ctx.render({ article, session: ctx.state.session });
return { data: { article, session: ctx.state.session } };
},
};
});
export default function Greet(
props: PageProps<

View File

@@ -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<ArticleResource>("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="/"
>
<IconArrowLeft class="w-5 h-5" />
<TbArrowLeft class="w-5 h-5" />
Back
</Link>
@@ -59,4 +59,4 @@ export default function Greet(
</Grid>
</MainLayout>
);
}
});

View File

@@ -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 (
<Card
splotch
key={m.link}
title={`${m.name}`}
backgroundSize={80}

View File

@@ -1,4 +1,3 @@
import { PageProps, RouteContext } from "$fresh/server.ts";
import { MainLayout } from "@components/layouts/main.tsx";
import { ReviewResource } from "@lib/marka/schema.ts";
import { removeImage, renderMarkdown } from "@lib/markdown.ts";
@@ -10,18 +9,18 @@ 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 { HttpError } from "fresh";
import { define } from "../../utils.ts";
export default async function Greet(
props: PageProps<{ movie: ReviewResource; session: Record<string, string> }>,
ctx: RouteContext,
) {
export default define.page(async function (ctx) {
const props = ctx.req;
const movie = await fetchResource<ReviewResource>(
`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(
</div>
</MainLayout>
);
}
});

View File

@@ -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="/"
>
<IconArrowLeft class="w-5 h-5" />
{TbArrowLeft({ class: "w-5 h-5" })}
Back
</a>

View File

@@ -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<RecipeResource>(
`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({
<h3 class="text-3xl my-5">Ingredients</h3>
{portion && <Counter count={amount} />}
</div>
{
<IngredientsList
ingredients={ingredients}
amount={amount}
portion={portion}
/>
}
<h3 class="text-3xl my-5">Preparation</h3>
<div class="pl-2">
<ol class="list-decimal grid gap-4">
@@ -135,7 +137,7 @@ export default function Page(
)
: (
<div class="whitespace-break-spaces markdown-body">
{JSON.stringify(recipe)}
{recipe}
</div>
)}
</div>

View File

@@ -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<RecipeResource>("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 (
<MainLayout
url={props.url}
url={url}
title="Recipes"
searchResults={searchResults}
context={{ type: "recipes" }}
@@ -41,7 +41,7 @@ export default function Greet(
class="px-4 lg:ml-4 py-2 bg-gray-300 text-gray-800 rounded-lg flex items-center gap-1"
href="/"
>
<IconArrowLeft class="w-5 h-5" />
<TbArrowLeft class="w-5 h-5" />
Back
</a>

View File

@@ -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<ReviewResource>(
`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<string, string> }>,

View File

@@ -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<ReviewResource>("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="/"
>
<IconArrowLeft class="w-5 h-5" />
<TbArrowLeft class="w-5 h-5" />
Back
</a>

11
utils.ts Normal file
View File

@@ -0,0 +1,11 @@
import { createDefine } from "fresh";
export interface State {
session: {
id: string;
name: string;
exp: number;
};
}
export const define = createDefine<State>();

10
vite.config.ts Normal file
View File

@@ -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(),
],
});