From d0c777424f801dec786c0859cf8905228291bfab Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Thu, 16 Mar 2023 15:51:50 +0300 Subject: [PATCH] chore: remove web --- web/.gitignore | 14 --- web/Dockerfile | 13 --- web/comp/layout.ts | 19 ---- web/comp/page_layout.ts | 67 ------------ web/context.ts | 24 ----- web/deno.json | 6 -- web/domain/ingredient/types.ts | 4 - web/flake.lock | 43 -------- web/flake.nix | 21 ---- web/import_map.json | 5 - web/log.ts | 7 -- web/makefile | 23 ----- web/repo/ingredient/rest.ts | 35 ------- web/repo/ingredient/types.ts | 6 -- web/repo/misc_types.ts | 4 - web/server.ts | 183 --------------------------------- web/translates/eng.ts | 10 -- web/translates/rus.ts | 12 --- web/uikit/typo.ts | 6 -- web/views/e404.ts | 15 --- web/views/e500.ts | 15 --- web/views/home.ts | 13 --- web/views/ingredients.ts | 35 ------- web/views/recipes.ts | 13 --- 24 files changed, 593 deletions(-) delete mode 100644 web/.gitignore delete mode 100644 web/Dockerfile delete mode 100644 web/comp/layout.ts delete mode 100644 web/comp/page_layout.ts delete mode 100644 web/context.ts delete mode 100644 web/deno.json delete mode 100644 web/domain/ingredient/types.ts delete mode 100644 web/flake.lock delete mode 100644 web/flake.nix delete mode 100644 web/import_map.json delete mode 100644 web/log.ts delete mode 100644 web/makefile delete mode 100644 web/repo/ingredient/rest.ts delete mode 100644 web/repo/ingredient/types.ts delete mode 100644 web/repo/misc_types.ts delete mode 100644 web/server.ts delete mode 100644 web/translates/eng.ts delete mode 100644 web/translates/rus.ts delete mode 100644 web/uikit/typo.ts delete mode 100644 web/views/e404.ts delete mode 100644 web/views/e500.ts delete mode 100644 web/views/home.ts delete mode 100644 web/views/ingredients.ts delete mode 100644 web/views/recipes.ts diff --git a/web/.gitignore b/web/.gitignore deleted file mode 100644 index f4dd52f..0000000 --- a/web/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -/* - -!/makefile -!/*ignore - -!/Dockerfile - -!/*json -!/*.ts -!/*.nix - -!/(domain|repo|uikit|comp|views|translates)/*.ts -!/styles/*.scss - diff --git a/web/Dockerfile b/web/Dockerfile deleted file mode 100644 index 6fa85e9..0000000 --- a/web/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM denoland/deno:alpine-1.22.1 - -EXPOSE 33334 - -WORKDIR /app - -USER deno - -ADD . . -# Compile the main app so that it doesn't need to be compiled each startup/entry. -RUN deno cache server.ts - -CMD ["run", "-A", "server.ts"] diff --git a/web/comp/layout.ts b/web/comp/layout.ts deleted file mode 100644 index 8b79236..0000000 --- a/web/comp/layout.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { AnyNode, E } from "ren/node.ts"; -import { Context } from "../context.ts"; - -export function Layout(ctx: Context, page: AnyNode): AnyNode { - return E("html", { lang: ctx.lang }, [ - E("head", [], [ - E("meta", { charset: "utf-8" }), - E("meta", { - name: "viewport", - content: "width=device-width, initial-scale=1", - }), - E("link", { rel: "stylesheet", href: "/styles/main.css" }), - E("title", [], "Recipes"), - ]), - E("body", [], [ - E("div", { id: "root" }, [page]), - ]), - ]); -} diff --git a/web/comp/page_layout.ts b/web/comp/page_layout.ts deleted file mode 100644 index 3e45f6a..0000000 --- a/web/comp/page_layout.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { AnyNode, Attrs, E, Elem } from "ren/node.ts"; -import { classNames } from "ren/attrs.ts"; -import { Context, getLangHref, iterLangs, Lang } from "../context.ts"; - -export function PageLayout(ctx: Context, children: AnyNode[]): Elem { - return E("div", { id: "main" }, [ - Header(ctx), - E("div", classNames("content"), children), - Footer(ctx), - ]); -} - -export function Header(ctx: Context): AnyNode { - return E("header", classNames("header gap-v-1x5"), [ - E("div", classNames("content-width"), [HeaderNav(ctx)]), - ]); -} - -export function HeaderNav(ctx: Context): AnyNode { - return E("nav", classNames("main-menu"), [ - Link(ctx.tr.Home, navLink("/", ctx)), - Link(ctx.tr.Recipes, navLink("/recipes", ctx)), - Link(ctx.tr.Ingredients, navLink("/ingredients", ctx)), - ]); -} - -function navLink(lhref: string, ctx?: Context): Attrs { - const attrs: Attrs = { lhref }; - if (ctx?.locPath === lhref) attrs["aria-current"] = "true"; - return attrs; -} - -export function Footer(ctx: Context): AnyNode { - return E("footer", classNames("footer"), [ - E("div", classNames("content-width row-sta-bet"), [ - Link(ctx.tr.Source_code, { - target: "_blank", - href: "https://notabug.org/pleshevskiy/recipes", - rel: "external nofollow noopener noreferrer", - }), - ChangeLang(ctx), - ]), - ]); -} - -export function Link(text: string, attrs: Attrs | Attrs[]): AnyNode { - return E("a", attrs, text); -} - -export function ChangeLang(ctx: Context): AnyNode { - const dropdownId = "change_langs"; - return E("div", classNames("dropdown"), [ - E("input", { id: dropdownId, type: "checkbox" }), - E("label", { for: dropdownId }, ctx.lang), - E( - "ul", - [], - iterLangs().filter((l) => l !== ctx.lang).map((l) => - ChangeLangBtn(ctx, l) - ), - ), - ]); -} - -export function ChangeLangBtn(ctx: Context, lang: Lang): AnyNode { - return E("a", { "href": getLangHref(lang, ctx.locPath) }, lang); -} diff --git a/web/context.ts b/web/context.ts deleted file mode 100644 index 6b88679..0000000 --- a/web/context.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Translations } from "./translates/rus.ts"; - -export interface Context { - locPath: string; - lang: Lang; - tr: Translations; -} - -export function getLangHref(lang: Lang, url: string): string { - return getLangUrlPrefix(lang) + url; -} - -export function getLangUrlPrefix(lang: Lang): string { - return lang === Lang.Rus ? "" : `/${lang}`; -} - -export function iterLangs(): Lang[] { - return [Lang.Eng, Lang.Rus]; -} - -export enum Lang { - Rus = "rus", - Eng = "eng", -} diff --git a/web/deno.json b/web/deno.json deleted file mode 100644 index f76bf0a..0000000 --- a/web/deno.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "compilerOptions": { - "lib": ["deno.ns", "dom"] - }, - "importMap": "./import_map.json" -} diff --git a/web/domain/ingredient/types.ts b/web/domain/ingredient/types.ts deleted file mode 100644 index 8f44f46..0000000 --- a/web/domain/ingredient/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface Ingredient { - readonly key: string; - readonly name: string; -} diff --git a/web/flake.lock b/web/flake.lock deleted file mode 100644 index 3660fb7..0000000 --- a/web/flake.lock +++ /dev/null @@ -1,43 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1654593855, - "narHash": "sha256-c+SyXvj7THre87OyIdZfRVR+HhI/g1ZDrQ3VUtTuHkU=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "033bd4fa9a8fbe0c68a88e925d9a884161044b25", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs", - "utils": "utils" - } - }, - "utils": { - "locked": { - "lastModified": 1653893745, - "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/web/flake.nix b/web/flake.nix deleted file mode 100644 index b3dd4ec..0000000 --- a/web/flake.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - description = "Recipes web"; - - inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - utils.url = "github:numtide/flake-utils"; - }; - - outputs = {self, nixpkgs, utils}: - let out = system: - let pkgs = nixpkgs.legacyPackages."${system}"; - in { - devShell = pkgs.mkShell { - buildInputs = with pkgs; [ - nodePackages.sass - ]; - }; - }; - in with utils.lib; eachSystem defaultSystems out; - -} diff --git a/web/import_map.json b/web/import_map.json deleted file mode 100644 index 1abee95..0000000 --- a/web/import_map.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "imports": { - "ren/": "https://git.pleshevski.ru/pleshevskiy/paren/raw/commit/1a67959b1a19598f8e95bf2ec5f7b1a4c3da4da6/ren/" - } -} diff --git a/web/log.ts b/web/log.ts deleted file mode 100644 index 7d17f05..0000000 --- a/web/log.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function info(...args: unknown[]): void { - console.log("[INFO]", ...args); -} - -export function debug(...args: unknown[]): void { - console.log("[DEBUG]", ...args); -} diff --git a/web/makefile b/web/makefile deleted file mode 100644 index 90e4ed6..0000000 --- a/web/makefile +++ /dev/null @@ -1,23 +0,0 @@ -PAR := $(MAKE) -j 128 -DOCKER_NAME := recipes -DOCKER_TAG := recipes - -watch: - ${PAR} deno-w sass-w - -deno-w: - deno run -A --watch server.ts - -sass-w: - sass -w styles/main.scss public/styles/main.css - -docker-restart: docker-stop docker-run - -docker-stop: - docker rm ${DOCKER_NAME} --force - -docker-run: - docker run -d --restart always -p 30000:30000 --name ${DOCKER_NAME} ${DOCKER_TAG} - -docker-build: - docker build -t ${DOCKER_TAG} . diff --git a/web/repo/ingredient/rest.ts b/web/repo/ingredient/rest.ts deleted file mode 100644 index 9a9b590..0000000 --- a/web/repo/ingredient/rest.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Lang } from "../../context.ts"; -import { Ingredient } from "../../domain/ingredient/types.ts"; -import { RestLang } from "../misc_types.ts"; -import { IngredientRepo } from "./types.ts"; - -export class RestIngredientRepo implements IngredientRepo { - async fetchIngredients(lang: Lang): Promise { - const url = new URL("http://localhost:33333/api/ingredients"); - url.searchParams.set("lang", lang); - - const res = await fetch( - url.toString(), - { headers: { "content-type": "application/json" } }, - ); - - const restIngrs: RestIngredient[] = await res.json(); - - return restIngrs.map(intoAppIngredient).sort((a, b) => - a.name.localeCompare(b.name) - ); - } -} - -export interface RestIngredient { - readonly key: string; - readonly lang: RestLang; - readonly name: string; -} - -export function intoAppIngredient(rest: RestIngredient): Ingredient { - return { - key: rest.key, - name: rest.name, - }; -} diff --git a/web/repo/ingredient/types.ts b/web/repo/ingredient/types.ts deleted file mode 100644 index e691f78..0000000 --- a/web/repo/ingredient/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Lang } from "../../context.ts"; -import { Ingredient } from "../../domain/ingredient/types.ts"; - -export interface IngredientRepo { - fetchIngredients(lang: Lang): Promise; -} diff --git a/web/repo/misc_types.ts b/web/repo/misc_types.ts deleted file mode 100644 index 3bc6247..0000000 --- a/web/repo/misc_types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum RestLang { - Rus = "Rus", - Eng = "Eng", -} diff --git a/web/server.ts b/web/server.ts deleted file mode 100644 index 084af8a..0000000 --- a/web/server.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { StrRenderer } from "ren/str.ts"; -import { Layout } from "./comp/layout.ts"; -import { Context, getLangHref, Lang } from "./context.ts"; -import { E404Page } from "./views/e404.ts"; -import * as log from "./log.ts"; -import { HomePage } from "./views/home.ts"; -import { RecipesPage } from "./views/recipes.ts"; -import { IngredientsPage } from "./views/ingredients.ts"; -import { RestIngredientRepo } from "./repo/ingredient/rest.ts"; -import rusTranslates from "./translates/rus.ts"; -import type { Translations } from "./translates/rus.ts"; -import { E500Page } from "./views/e500.ts"; - -if (import.meta.main) { - await main(); -} - -async function main() { - await startServer({ port: 33334 }); -} - -async function startServer(cfg: ServerConfig) { - const srv = Deno.listen({ hostname: "localhost", port: cfg.port }); - log.info(`Server listening at http://localhost:${cfg.port}`); - - for await (const conn of srv) { - serveHttp(conn); - } -} - -interface ServerConfig { - port: number; -} - -async function serveHttp(conn: Deno.Conn) { - const httpConn = Deno.serveHttp(conn); - - for await (const reqEvt of httpConn) { - const res = await handleRequest(reqEvt.request); - reqEvt.respondWith(res); - } -} - -async function handleRequest(req: Request): Promise { - log.info({ method: req.method, url: req.url }); - - if (req.method === "GET") { - return await handleGet(req); - } else { - return new Response("Method Not Allowed", { status: 405 }); - } -} - -async function handleGet(req: Request) { - const ctx = createContextFromRequest(req); - - try { - const res = await tryCreateFileResponse(ctx.locPath); - return res; - } catch (_) { - if (ctx.lang !== Lang.Rus) { - await loadAndUpdateTranslations(ctx); - } - log.debug({ context: ctx }); - - const ren = new StrRenderer({ - wrapNode: Layout.bind(null, ctx), - onVisitAttr: ([key, value]) => { - if (key === "lhref" && typeof value === "string") { - return ["href", getLangHref(ctx.lang, value)]; - } else { - return [key, value]; - } - }, - }); - - try { - if (ctx.locPath === "/") { - return createHtmlResponse(ren.render(HomePage(ctx))); - } else if (ctx.locPath === "/recipes") { - return createHtmlResponse(ren.render(RecipesPage(ctx))); - } else if (ctx.locPath === "/ingredients") { - const repo = new RestIngredientRepo(); - const ingredients = await repo.fetchIngredients(ctx.lang); - - return createHtmlResponse( - ren.render(IngredientsPage(ctx, { ingredients })), - ); - } else { - return createHtmlResponse(ren.render(E404Page(ctx)), 404); - } - } catch (_) { - return createHtmlResponse(ren.render(E500Page(ctx)), 500); - } - } -} - -async function loadAndUpdateTranslations(ctx: Context) { - try { - const translates = await import(`./translates/${ctx.lang}.ts`); - ctx.tr = Object.entries(translates.default as Partial) - .reduce( - (acc, [key, val]) => ({ - ...acc, - [key as keyof Translations]: val, - }), - { ...ctx.tr } as Translations, - ); - } catch (_e) { /* ignore */ } -} - -function createContextFromRequest(req: Request): Context { - const locUrl = new URL(req.url); - const lang = langFromUrl(locUrl); - - return { - lang, - locPath: stripPrefix(`/${lang}`, locUrl.pathname), - tr: rusTranslates, - }; -} - -function langFromUrl(url: URL): Lang { - return url.pathname.startsWith("/eng/") ? Lang.Eng : Lang.Rus; -} - -function stripPrefix(prefix: string, val: string): string { - return val.startsWith(prefix) ? val.slice(prefix.length) : val; -} - -function createHtmlResponse(body: string, status = 200): Response { - return new Response(body, { - status, - headers: getContentTypeHeader("html"), - }); -} - -async function tryCreateFileResponse(urlPath: string): Promise { - const filePath = extractFilePath(urlPath); - log.debug({ filePath }); - if (!filePath) throw new SkipFile(); - - const content = await Deno.readTextFile(filePath).catch(() => { - throw new SkipFile(); - }); - - return createFileResponse(content, getFileExt(filePath)); -} - -class SkipFile extends Error {} - -function createFileResponse(content: string, fileExt: string): Response { - return new Response(content, { - headers: getContentTypeHeader(fileExt), - }); -} - -function extractFilePath(urlPath: string): string | null { - const relPath = urlPath.slice(1); - if (relPath.startsWith("styles/")) { - return `public/${relPath}`; - } - return null; -} - -function getContentTypeHeader(fileExt: string): Record { - return { "content-type": getContentTypeByExt(fileExt) }; -} - -function getContentTypeByExt(fileExt: string): string { - switch (fileExt) { - case "html": - return "text/html"; - case "css": - return "text/css"; - default: - return "text/plain"; - } -} - -function getFileExt(filePath: string): string { - return filePath.slice((filePath.lastIndexOf(".") - 1 >>> 0) + 2); -} diff --git a/web/translates/eng.ts b/web/translates/eng.ts deleted file mode 100644 index 28e6ef0..0000000 --- a/web/translates/eng.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Translations } from "./rus.ts"; - -export default { - Home: "Home", - Recipes: "Recipes", - Ingredients: "Ingredients", - Source_code: "Source code", - Page_not_found: "Page not found", - Internal_server_error: "Internal server error", -} as Translations; diff --git a/web/translates/rus.ts b/web/translates/rus.ts deleted file mode 100644 index 9859ec0..0000000 --- a/web/translates/rus.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const rus = { - Home: "Главная", - Recipes: "Рецепты", - Ingredients: "Ингредиенты", - Source_code: "Исходный код", - Page_not_found: "Страница не найдена", - Internal_server_error: "Внутренняя ошибка сервера", -}; - -export default rus; - -export type Translations = typeof rus; diff --git a/web/uikit/typo.ts b/web/uikit/typo.ts deleted file mode 100644 index 5f95691..0000000 --- a/web/uikit/typo.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { classNames } from "ren/attrs.ts"; -import { E, Elem } from "ren/node.ts"; - -export function H3(text: string): Elem { - return E("h3", classNames("font-h3"), text); -} diff --git a/web/views/e404.ts b/web/views/e404.ts deleted file mode 100644 index 864fc75..0000000 --- a/web/views/e404.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { PageLayout } from "../comp/page_layout.ts"; -import { AnyNode, E } from "ren/node.ts"; -import { classNames } from "ren/attrs.ts"; -import { Context } from "../context.ts"; -import { H3 } from "../uikit/typo.ts"; - -export function E404Page(ctx: Context): AnyNode { - return PageLayout(ctx, [E404(ctx)]); -} - -export function E404(ctx: Context): AnyNode { - return E("div", classNames("content-width gap-v-1x5"), [ - H3(ctx.tr.Page_not_found), - ]); -} diff --git a/web/views/e500.ts b/web/views/e500.ts deleted file mode 100644 index dae2512..0000000 --- a/web/views/e500.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { PageLayout } from "../comp/page_layout.ts"; -import { AnyNode, E } from "ren/node.ts"; -import { classNames } from "ren/attrs.ts"; -import { Context } from "../context.ts"; -import { H3 } from "../uikit/typo.ts"; - -export function E500Page(ctx: Context): AnyNode { - return PageLayout(ctx, [E500(ctx)]); -} - -export function E500(ctx: Context): AnyNode { - return E("div", classNames("content-width gap-v-1x5"), [ - H3(ctx.tr.Internal_server_error), - ]); -} diff --git a/web/views/home.ts b/web/views/home.ts deleted file mode 100644 index 6a429f6..0000000 --- a/web/views/home.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PageLayout } from "../comp/page_layout.ts"; -import { AnyNode, E } from "ren/node.ts"; -import { classNames } from "ren/attrs.ts"; -import { Context } from "../context.ts"; -import { H3 } from "../uikit/typo.ts"; - -export function HomePage(ctx: Context): AnyNode { - return PageLayout(ctx, [ - E("div", classNames("content-width gap-v-1x5"), [ - H3(ctx.tr.Home), - ]), - ]); -} diff --git a/web/views/ingredients.ts b/web/views/ingredients.ts deleted file mode 100644 index 8f71070..0000000 --- a/web/views/ingredients.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { PageLayout } from "../comp/page_layout.ts"; -import { AnyNode, E } from "ren/node.ts"; -import { classNames } from "ren/attrs.ts"; -import { Context } from "../context.ts"; -import { H3 } from "../uikit/typo.ts"; -import { Ingredient } from "../domain/ingredient/types.ts"; - -interface IngredientsPageData { - ingredients: Ingredient[]; -} - -export function IngredientsPage( - ctx: Context, - data: IngredientsPageData, -): AnyNode { - return PageLayout(ctx, [ - E("div", classNames("content-width gap-v-1x5"), [ - H3(ctx.tr.Ingredients), - IngredientList(data.ingredients), - ]), - ]); -} - -export function IngredientList(ingrs: Ingredient[]): AnyNode { - return E("div", classNames("responsive-typography"), [ - E("ul", [], ingrs.map(IngredientItem)), - ]); -} - -export function IngredientItem(ingr: Ingredient): AnyNode { - return E("li", [], [ - ingr.name, - // E("a", { href: `/ingredients/${ingr.key}` }, ingr.name), - ]); -} diff --git a/web/views/recipes.ts b/web/views/recipes.ts deleted file mode 100644 index cdbe173..0000000 --- a/web/views/recipes.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PageLayout } from "../comp/page_layout.ts"; -import { AnyNode, E } from "ren/node.ts"; -import { classNames } from "ren/attrs.ts"; -import { Context } from "../context.ts"; -import { H3 } from "../uikit/typo.ts"; - -export function RecipesPage(ctx: Context): AnyNode { - return PageLayout(ctx, [ - E("div", classNames("content-width gap-v-1x5"), [ - H3(ctx.tr.Recipes), - ]), - ]); -}