Compare commits

...

No commits in common. "master" and "master-fresh" have entirely different histories.

42 changed files with 622 additions and 602 deletions

12
.gitignore vendored Normal file
View file

@ -0,0 +1,12 @@
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# Fresh build directory
_fresh/
# npm dependencies
node_modules/
/local

5
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"recommendations": [
"denoland.vscode-deno"
]
}

18
.vscode/settings.json vendored
View file

@ -1,8 +1,18 @@
{ {
"deno.enablePaths": [
"./"
],
"deno.enable": true, "deno.enable": true,
"deno.lint": true,
"deno.unstable": true, "deno.unstable": true,
"editor.inlayHints.enabled": "off" "editor.defaultFormatter": "denoland.vscode-deno",
"[typescriptreact]": {
"editor.defaultFormatter": "denoland.vscode-deno"
},
"[typescript]": {
"editor.defaultFormatter": "denoland.vscode-deno"
},
"[javascriptreact]": {
"editor.defaultFormatter": "denoland.vscode-deno"
},
"[javascript]": {
"editor.defaultFormatter": "denoland.vscode-deno"
}
} }

View file

@ -1,7 +1,16 @@
# Twifeur # Fresh project
Feur Your new Fresh project is ready to go. You can follow the Fresh "Getting
Started" guide here: https://fresh.deno.dev/docs/getting-started
## Feur ### Usage
Feur feur. Make sure to install Deno: https://deno.land/manual/getting_started/installation
Then start the project:
```
deno task start
```
This will watch the project directory and restart as necessary.

View file

@ -1,16 +0,0 @@
import { Context } from "https://deno.land/x/hono@v4.3.10/mod.ts";
import { BlankInput } from "https://deno.land/x/hono@v4.3.10/types.ts";
import { FeurEnv } from "../main.ts";
import { login, set_user } from "../lib/auth.ts";
export async function login_route(context: Context<FeurEnv, string, BlankInput>) {
const data = await context.req.formData();
let username = data.get("login"), pass = data.get("password");
if (username === null || pass === null) return context.redirect("/login");
username = username.toString(), pass = pass.toString();
const logged = await login(username, pass);
if (logged === null) return context.redirect("/login");
console.log("Logged in", { username });
set_user(context, logged);
return context.redirect("/user");
}

View file

@ -1,9 +0,0 @@
import { Context } from "https://deno.land/x/hono@v4.3.10/mod.ts";
import { BlankInput } from "https://deno.land/x/hono@v4.3.10/types.ts";
import { FeurEnv } from "../main.ts";
import { set_user } from "../lib/auth.ts";
export function logout_route(context: Context<FeurEnv, string, BlankInput>) {
set_user(context, null);
return context.redirect("/");
}

72
auth/auth.ts Normal file
View file

@ -0,0 +1,72 @@
import { assert } from "$std/assert/assert.ts";
import { User } from "../lib/store.ts";
import { generate } from "https://deno.land/std@0.224.0/uuid/v1.ts";
class Authentificator {
tokens;
user_storage;
constructor(user_storage: UserStorage) {
this.tokens = new TokenSet();
this.user_storage = user_storage;
}
}
interface UserStorage {
get_user(login: string): User | null;
}
export class Token {
raw;
user_id;
constructor(raw: string, user_id: string) {
this.raw = raw;
this.user_id = user_id;
}
}
class TokenSet {
tokens;
constructor() {
this.tokens = new Map<string, Token>();
}
get(token: string) {
return this.tokens.get(token) ?? null;
}
create(user_id: string) {
const raw = generate();
assert(typeof raw == "string");
const token = new Token(raw, user_id);
this.tokens.set(raw, token);
}
}
class StaticUserStorage implements UserStorage {
users;
constructor() {
this.users = new Map<string, User>();
}
get_user(user_id: string) {
return this.users.get(user_id) ?? null;
}
with(user: User) {
this.users.set(user.id, user);
return this;
}
}
export const auth = new Authentificator(
new StaticUserStorage().with({
id: "pleinplein",
email: "feur@feur.feur",
like_set: new Set(),
name: "Feur",
password: "feurfeur",
pfp_url: "https://feur.feur.feur/feur.feur",
}),
);

12
components/Button.tsx Normal file
View file

@ -0,0 +1,12 @@
import { JSX } from "preact";
import { IS_BROWSER } from "$fresh/runtime.ts";
export function Button(props: JSX.HTMLAttributes<HTMLButtonElement>) {
return (
<button
{...props}
disabled={!IS_BROWSER || props.disabled}
class="px-2 py-1 border-gray-500 border-2 rounded bg-white hover:bg-gray-200 transition-colors"
/>
);
}

View file

@ -1,6 +1,39 @@
{ {
"lock": false,
"tasks": {
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
"cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run --unstable -A -",
"manifest": "deno task cli manifest $(pwd)",
"start": "deno run -A --unstable --watch=static/,routes/ dev.ts",
"build": "deno run -A dev.ts build",
"preview": "deno run -A main.ts",
"update": "deno run -A -r https://fresh.deno.dev/update ."
},
"lint": {
"rules": {
"tags": [
"fresh",
"recommended"
]
}
},
"exclude": [
"**/_fresh/*"
],
"imports": {
"$fresh/": "https://deno.land/x/fresh@1.6.8/",
"preact": "https://esm.sh/preact@10.19.6",
"preact/": "https://esm.sh/preact@10.19.6/",
"@preact/signals": "https://esm.sh/*@preact/signals@1.2.2",
"@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.5.1",
"$std/": "https://deno.land/std@0.216.0/"
},
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"fmt": { "fmt": {
"lineWidth": 120, "useTabs": true,
"useTabs": true "lineWidth": 120
} }
} }

223
deno.lock generated
View file

@ -1,223 +0,0 @@
{
"version": "3",
"packages": {
"specifiers": {
"npm:@types/node": "npm:@types/node@18.16.19",
"npm:hono@^4.0.0": "npm:hono@4.4.0",
"npm:iron-webcrypto@0.10.1": "npm:iron-webcrypto@0.10.1"
},
"npm": {
"@types/node@18.16.19": {
"integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==",
"dependencies": {}
},
"hono@4.4.0": {
"integrity": "sha512-Bb2GHk8jmlLIuxc3U+7UBGOoA5lByJTAFnRdH2N2fqEVy9TEQzJ9saIJUQ/ZqBvEvgEFe7UjPFNSFi8cyeU+3Q==",
"dependencies": {}
},
"iron-webcrypto@0.10.1": {
"integrity": "sha512-QGOS8MRMnj/UiOa+aMIgfyHcvkhqNUsUxb1XzskENvbo+rEfp6TOwqd1KPuDzXC4OnGHcMSVxDGRoilqB8ViqA==",
"dependencies": {}
}
}
},
"redirects": {
"https://deno.land/x/deno_dom/deno-dom-wasm.ts": "https://deno.land/x/deno_dom@v0.1.45/deno-dom-wasm.ts",
"https://deno.land/x/hono/middleware.ts": "https://deno.land/x/hono@v4.3.10/middleware.ts",
"https://deno.land/x/hono_sessions/mod.ts": "https://deno.land/x/hono_sessions@v0.5.8/mod.ts",
"https://esm.sh/v135/@types/css@latest/index.d.ts": "https://esm.sh/v135/@types/css@0.0.37/index.d.ts"
},
"remote": {
"https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8",
"https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c",
"https://deno.land/std@0.224.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8",
"https://deno.land/std@0.224.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a",
"https://deno.land/std@0.224.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15",
"https://deno.land/std@0.224.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c",
"https://deno.land/std@0.224.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d",
"https://deno.land/std@0.224.0/path/posix/dirname.ts": "76cd348ffe92345711409f88d4d8561d8645353ac215c8e9c80140069bf42f00",
"https://deno.land/std@0.224.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808",
"https://deno.land/std@0.224.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9",
"https://deno.land/x/deno_dom@v0.1.45/build/deno-wasm/deno-wasm.js": "d6841a06342eb6a2798ef28de79ad69c0f2fa349fa04d3ca45e5fcfbf50a9340",
"https://deno.land/x/deno_dom@v0.1.45/deno-dom-wasm.ts": "a33d160421bbb6e3104285ea5ebf33352b7ad50d82ea8765e3cf65f972b25119",
"https://deno.land/x/deno_dom@v0.1.45/src/api.ts": "0ff5790f0a3eeecb4e00b7d8fbfa319b165962cf6d0182a65ba90f158d74f7d7",
"https://deno.land/x/deno_dom@v0.1.45/src/constructor-lock.ts": "59714df7e0571ec7bd338903b1f396202771a6d4d7f55a452936bd0de9deb186",
"https://deno.land/x/deno_dom@v0.1.45/src/deserialize.ts": "1cf4096678d8afed8ed28dbad690504c4d2c28149ba768b26eacd1416873425b",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/document-fragment.ts": "1c7352a3c816587ed7fad574b42636198f680f17abc3836fcfe7799b31e7718f",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/document.ts": "a182727dd9179e5712e31be66f4f72b766a5b714c765a75950babe6dd756b4ee",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/dom-parser.ts": "609097b426f8c2358f3e5d2bca55ed026cf26cdf86562e94130dfdb0f2537f92",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/element.ts": "d5371cd83ff2128353c1975465c368ef83d7441568626b386557deba51315111",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/elements/html-template-element.ts": "740b97a5378c9a14cccf3429299846eda240b613013e2d2d7f20b393897453c2",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/html-collection.ts": "829a965f419f8286d5f43a12e27886d10836d519ca2d5e74cb3f2e1d35f35746",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/node-list.ts": "9008303fe236e40e74f9f93e398bd173d2e9b09065932a0153dd0142c759397b",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/node.ts": "3069e6fc93ac4111a136ed68199d76673339842b9751610ba06f111ba7dc10a7",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/selectors/custom-api.ts": "852696bd58e534bc41bd3be9e2250b60b67cd95fd28ed16b1deff1d548531a71",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/selectors/nwsapi-types.ts": "c43b36c36acc5d32caabaa54fda8c9d239b2b0fcbce9a28efb93c84aa1021698",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/selectors/nwsapi.js": "985d7d8fc1eabbb88946b47a1c44c1b2d4aa79ff23c21424219f1528fa27a2ff",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/selectors/selectors.ts": "83eab57be2290fb48e3130533448c93c6c61239f2a2f3b85f1917f80ca0fdc75",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/selectors/sizzle-types.ts": "78149e2502409989ce861ed636b813b059e16bc267bb543e7c2b26ef43e4798b",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/selectors/sizzle.js": "c3aed60c1045a106d8e546ac2f85cc82e65f62d9af2f8f515210b9212286682a",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/utils-types.ts": "96db30e3e4a75b194201bb9fa30988215da7f91b380fca6a5143e51ece2a8436",
"https://deno.land/x/deno_dom@v0.1.45/src/dom/utils.ts": "4c6206516fb8f61f37a209c829e812c4f5a183e46d082934dd14c91bde939263",
"https://deno.land/x/deno_dom@v0.1.45/src/parser.ts": "e06b2300d693e6ae7564e53dfa5c9a9e97fdb8c044c39c52c8b93b5d60860be3",
"https://deno.land/x/hono@v4.3.10/adapter/deno/serve-static.ts": "db226d30f08f1a8bb77653ead42a911357b2f8710d653e43c01eccebb424b295",
"https://deno.land/x/hono@v4.3.10/client/client.ts": "dcda3887257fa3164db7b32c56665c6e757f0ef047a14f3f9599ef41725c1525",
"https://deno.land/x/hono@v4.3.10/client/index.ts": "30def535310a37bede261f1b23d11a9758983b8e9d60a6c56309cee5f6746ab2",
"https://deno.land/x/hono@v4.3.10/client/types.ts": "73fbec704cf968ca5c5bba10aa1131758c52ab31a78301451c79cfc47368d43c",
"https://deno.land/x/hono@v4.3.10/client/utils.ts": "8be84b49c5c7952666875a8e901fde3044c85c853ea6ba3a7e2c0468478459c0",
"https://deno.land/x/hono@v4.3.10/compose.ts": "37d6e33b7db80e4c43a0629b12fd3a1e3406e7d9e62a4bfad4b30426ea7ae4f1",
"https://deno.land/x/hono@v4.3.10/context.ts": "facfd749d823a645039571d66d9d228f5ae6836818b65d3b6c4c6891adfe071e",
"https://deno.land/x/hono@v4.3.10/helper/adapter/index.ts": "ff7e11eb1ca1fbd74ca3c46cd1d24014582f91491ef6d3846d66ed1cede18ec4",
"https://deno.land/x/hono@v4.3.10/helper/cookie/index.ts": "689c84eae410f0444a4598f136a4f859b9122ec6f790dff74412d34405883db8",
"https://deno.land/x/hono@v4.3.10/helper/html/index.ts": "48a0ddc576c10452db6c3cab03dd4ee6986ab61ebdc667335b40a81fa0487f69",
"https://deno.land/x/hono@v4.3.10/hono-base.ts": "fd7e9c1bba1e13119e95158270011784da3a7c3014c149ba0700e700f840ae0d",
"https://deno.land/x/hono@v4.3.10/hono.ts": "23edd0140bf0bd5a68c14ae96e5856a5cec6b844277e853b91025e91ea74f416",
"https://deno.land/x/hono@v4.3.10/http-exception.ts": "f5dd375e61aa4b764eb9b99dd45a7160f8317fd36d3f79ae22585b9a5e8ad7c5",
"https://deno.land/x/hono@v4.3.10/jsx/base.ts": "33f1c302c8f72ae948abd9c3ef85f4b3be6525251a13b95fd18fe2910b7d4a0d",
"https://deno.land/x/hono@v4.3.10/jsx/children.ts": "26ead0f151faba5307883614b5b064299558f06798c695c432f32acbb1127d56",
"https://deno.land/x/hono@v4.3.10/jsx/components.ts": "f79ab215f59388f01a69e2d6ec0b841fd3b42ba38e0ee7c93a525cdf06e159f9",
"https://deno.land/x/hono@v4.3.10/jsx/constants.ts": "984e0797194be1fbc935cb688c8d0a60c112b21bc59301be5354c02232f18820",
"https://deno.land/x/hono@v4.3.10/jsx/context.ts": "2b7a86e6b35da171fab27aa05f09748bb3eba64b26c037ea1da655c07e8f6bc1",
"https://deno.land/x/hono@v4.3.10/jsx/dom/components.ts": "733da654edb3d4c178a4479649fac2c64e79069e37e848add0c3a49f90e7f2d7",
"https://deno.land/x/hono@v4.3.10/jsx/dom/context.ts": "06209d14553398750c69252cc826082018cefa277f5c82cbe58d7261c8a2d81e",
"https://deno.land/x/hono@v4.3.10/jsx/dom/jsx-dev-runtime.ts": "ba87562d14b77dd5f2a3cc30d41b1eb5edb0800e5f4a7337b5b87b2e66f8a099",
"https://deno.land/x/hono@v4.3.10/jsx/dom/jsx-runtime.ts": "6a50a65306771a9000030f494d92a5fdeeb055112e0126234b2fd9179de1d4f5",
"https://deno.land/x/hono@v4.3.10/jsx/dom/render.ts": "7db816d40de58c60e1cbdab64ac3f170b1e30696ed61ad449bbb823f60b46146",
"https://deno.land/x/hono@v4.3.10/jsx/dom/utils.ts": "5d3e8c14996902db9c1223041fb21480fa0e921a4ccdc59f8c7571c08b7810f2",
"https://deno.land/x/hono@v4.3.10/jsx/hooks/index.ts": "b7e0f0a754f31a1e1fbe0ac636b38b031603eb0ae195c32a30769a11d79fb871",
"https://deno.land/x/hono@v4.3.10/jsx/index.ts": "fe3e582c2a4e24e5f8b6027925bddccaae0283747d8f0161eb6f5a34616edd11",
"https://deno.land/x/hono@v4.3.10/jsx/intrinsic-elements.ts": "21c3a8f6ba07f0d7d7c0ec7293c79c26b9b62df2894e26cb6c17b6c7ec381264",
"https://deno.land/x/hono@v4.3.10/jsx/streaming.ts": "5e5dde9a546041353b9a3860fc9020471f762813f10e1290009ab6bd40e7bdcf",
"https://deno.land/x/hono@v4.3.10/jsx/types.ts": "51c2bdbb373860e2570ad403546a7fdbbb1cf00a47ce7ed10b2aece922031ac4",
"https://deno.land/x/hono@v4.3.10/jsx/utils.ts": "4b8299d402ba5395472c552d1fe3297ee60112bfc32e0ef86cfe8e40086f7d54",
"https://deno.land/x/hono@v4.3.10/middleware.ts": "2e7c6062e36b0e5f84b44a62e7b0e1cef33a9827c19937c648be4b63e1b7d7c6",
"https://deno.land/x/hono@v4.3.10/middleware/basic-auth/index.ts": "2c8cb563f3b89df1a7a2232be37377c3df6194af38613dc0a823c6595816fc66",
"https://deno.land/x/hono@v4.3.10/middleware/bearer-auth/index.ts": "b3b7469bc0eb9543c6c47f3ff67de879210dd73063307a61536042ff30e8720e",
"https://deno.land/x/hono@v4.3.10/middleware/body-limit/index.ts": "3fefeaf7e6e576aa1b33f2694072d2eaab692842acd29cb360d98e20eebfe5aa",
"https://deno.land/x/hono@v4.3.10/middleware/cache/index.ts": "5e6273e5c9ea73ef387b25923ab23274c220b29d7c981b62ac0be26d6a1aa3d8",
"https://deno.land/x/hono@v4.3.10/middleware/compress/index.ts": "98c403a5fe7e9c5f5d776350b422b0a125fb34696851b8b14f825b9b7b06f2ac",
"https://deno.land/x/hono@v4.3.10/middleware/cors/index.ts": "976eb9ce8cefc214b403a2939503a13177cec76223274609a07ca554e0dc623b",
"https://deno.land/x/hono@v4.3.10/middleware/csrf/index.ts": "077bb0ce299d79d0d232cb9e462aaa4eaa901164f1310f74a7630f7e6cfe74e8",
"https://deno.land/x/hono@v4.3.10/middleware/etag/index.ts": "95e0270ea349cf00537ee6e58985a4cc7dba44091ca8e2dc42b6d8b2f01bcfe7",
"https://deno.land/x/hono@v4.3.10/middleware/jsx-renderer/index.ts": "229322c66ebc7f426cd2d71f282438025b4ee7ce8cb8e97e87c7efbc94530c19",
"https://deno.land/x/hono@v4.3.10/middleware/jwt/index.ts": "4cb997d3d7a09d0b0c0e273841d29729e13e35dfc00021089aebaad868a7f8c6",
"https://deno.land/x/hono@v4.3.10/middleware/logger/index.ts": "52a2e968890ada2c11ce89a7a783692c5767b8ed7fb23ccf6b559d255d13ccbc",
"https://deno.land/x/hono@v4.3.10/middleware/method-override/index.ts": "bc13bdcf70c777b72b1300a5cca1b51a8bd126e0d922b991d89e96fe7c694b5b",
"https://deno.land/x/hono@v4.3.10/middleware/powered-by/index.ts": "6faba0cf042278d60b317b690640bb0b58747690cf280fa09024424c5174e66d",
"https://deno.land/x/hono@v4.3.10/middleware/pretty-json/index.ts": "2216ce4c9910be009fecac63367c3626b46137d4cf7cb9a82913e501104b4a88",
"https://deno.land/x/hono@v4.3.10/middleware/secure-headers/index.ts": "f2e4c3858d26ff47bc6909513607e6a3c31184aabe78fb272ed08e1d62a750f0",
"https://deno.land/x/hono@v4.3.10/middleware/serve-static/index.ts": "14b760bbbc4478cc3a7fb9728730bc6300581c890365b7101b80c16e70e4b21e",
"https://deno.land/x/hono@v4.3.10/middleware/timing/index.ts": "6fddbb3e47ae875c16907cf23b9bb503ec2ad858406418b5f38f1e7fbca8c6f6",
"https://deno.land/x/hono@v4.3.10/middleware/trailing-slash/index.ts": "419cf0af99a137f591b72cc71c053e524fe3574393ce81e0e9dbce84a4046e24",
"https://deno.land/x/hono@v4.3.10/mod.ts": "35fd2a2e14b52365e0ad66f168b067363fd0a60d75cbcb1b01685b04de97d60e",
"https://deno.land/x/hono@v4.3.10/request.ts": "7b08602858e642d1626c3106c0bedc2aa8d97e30691a079351d9acef7c5955e6",
"https://deno.land/x/hono@v4.3.10/router.ts": "880316f561918fc197481755aac2165fdbe2f530b925c5357a9f98d6e2cc85c7",
"https://deno.land/x/hono@v4.3.10/router/linear-router/index.ts": "8a2a7144c50b1f4a92d9ee99c2c396716af144c868e10608255f969695efccd0",
"https://deno.land/x/hono@v4.3.10/router/linear-router/router.ts": "928d29894e4b45b047a4f453c7f1745c8b1869cd68447e1cb710c7bbf99a4e29",
"https://deno.land/x/hono@v4.3.10/router/pattern-router/index.ts": "304a66c50e243872037ed41c7dd79ed0c89d815e17e172e7ad7cdc4bc07d3383",
"https://deno.land/x/hono@v4.3.10/router/pattern-router/router.ts": "1b5f68e6af942579d3a40ee834294fea3d1f05fd5f70514e46ae301dd0107e46",
"https://deno.land/x/hono@v4.3.10/router/reg-exp-router/index.ts": "52755829213941756159b7a963097bafde5cc4fc22b13b1c7c9184dc0512d1db",
"https://deno.land/x/hono@v4.3.10/router/reg-exp-router/node.ts": "7efaa6f4301efc2aad0519c84973061be8555da02e5868409293a1fd98536aaf",
"https://deno.land/x/hono@v4.3.10/router/reg-exp-router/router.ts": "632f2fa426b3e45a66aeed03f7205dad6d13e8081bed6f8d1d987b6cad8fb455",
"https://deno.land/x/hono@v4.3.10/router/reg-exp-router/trie.ts": "852ce7207e6701e47fa30889a0d2b8bfcd56d8862c97e7bc9831e0a64bd8835f",
"https://deno.land/x/hono@v4.3.10/router/smart-router/index.ts": "74f9b4fe15ea535900f2b9b048581915f12fe94e531dd2b0032f5610e61c3bef",
"https://deno.land/x/hono@v4.3.10/router/smart-router/router.ts": "dc22a8505a0f345476f07dca3054c0c50a64d7b81c9af5a904476490dfd5cbb4",
"https://deno.land/x/hono@v4.3.10/router/trie-router/index.ts": "3eb75e7f71ba81801631b30de6b1f5cefb2c7239c03797e2b2cbab5085911b41",
"https://deno.land/x/hono@v4.3.10/router/trie-router/node.ts": "d3e00e8f1ba7fb26896459d5bba882356891a07793387c4655d1864c519a91de",
"https://deno.land/x/hono@v4.3.10/router/trie-router/router.ts": "54ced78d35676302c8fcdda4204f7bdf5a7cc907fbf9967c75674b1e394f830d",
"https://deno.land/x/hono@v4.3.10/types.ts": "b561c3ee846121b33c2d81331246cdedf7781636ed72dad7406677105b4275de",
"https://deno.land/x/hono@v4.3.10/utils/body.ts": "774cb319dfbe886a9d39f12c43dea15a39f9d01e45de0323167cdd5d0aad14d4",
"https://deno.land/x/hono@v4.3.10/utils/buffer.ts": "2fae689954b427b51fb84ad02bed11a72eae96692c2973802b3b4c1e39cd5b9c",
"https://deno.land/x/hono@v4.3.10/utils/color.ts": "10575c221f48bc806887710da8285f859f51daf9e6878bbdf99cb406b8494457",
"https://deno.land/x/hono@v4.3.10/utils/cookie.ts": "662529d55703d2c0bad8736cb1274eb97524c0ef7882d99254fc7c8fa925b46c",
"https://deno.land/x/hono@v4.3.10/utils/crypto.ts": "bda0e141bbe46d3a4a20f8fbcb6380d473b617123d9fdfa93e4499410b537acc",
"https://deno.land/x/hono@v4.3.10/utils/encode.ts": "311dfdfae7eb0b6345e9680f7ebbb3a692e872ed964e2029aca38567af8d1f33",
"https://deno.land/x/hono@v4.3.10/utils/filepath.ts": "a83e5fe87396bb291a6c5c28e13356fcbea0b5547bad2c3ba9660100ff964000",
"https://deno.land/x/hono@v4.3.10/utils/html.ts": "6ea4f6bf41587a51607dff7a6d2865ef4d5001e4203b07e5c8a45b63a098e871",
"https://deno.land/x/hono@v4.3.10/utils/http-status.ts": "f5b820f2793e45209f34deddf147b23e3133a89eb4c57dc643759a504706636b",
"https://deno.land/x/hono@v4.3.10/utils/jwt/index.ts": "3b66f48cdd3fcc2caed5e908ca31776e11b1c30391008931276da3035e6ba1e9",
"https://deno.land/x/hono@v4.3.10/utils/jwt/jwa.ts": "6874cacd8b6dde386636b81b5ea2754f8e4c61757802fa908dd1ce54b91a52fa",
"https://deno.land/x/hono@v4.3.10/utils/jwt/jws.ts": "878fa7d1966b0db20ae231cfee279ba2bb198943e949049cab3f5845cd5ee2d1",
"https://deno.land/x/hono@v4.3.10/utils/jwt/jwt.ts": "80452edc3498c6670a211fdcd33cfc4d5c00dfac79aa9f403b0623dedc039554",
"https://deno.land/x/hono@v4.3.10/utils/jwt/types.ts": "b6659ac85e7f8fcdd8cdfc7d51f5d1a91107ad8dfb647a8e4ea9c80f0f02afee",
"https://deno.land/x/hono@v4.3.10/utils/jwt/utf8.ts": "17c507f68f23ccb82503ea6183e54b5f748a6fe621eb60994adfb4a8c2a3f561",
"https://deno.land/x/hono@v4.3.10/utils/mime.ts": "d1fc2c047191ccb01d736c6acf90df731324536298181dba0ecc2259e5f7d661",
"https://deno.land/x/hono@v4.3.10/utils/types.ts": "050bfa9dc6d0cc4b7c5069944a8bd60066c2f9f95ee69833623ad104f11f92bf",
"https://deno.land/x/hono@v4.3.10/utils/url.ts": "855169632c61d03703bd08cafb27664ba3fdb352892f01687d5cce8fd49e3cb1",
"https://deno.land/x/hono@v4.3.10/validator/index.ts": "6c986e8b91dcf857ecc8164a506ae8eea8665792a4ff7215471df669c632ae7c",
"https://deno.land/x/hono@v4.3.10/validator/validator.ts": "53f3d2ad442e22f0bc2d85b7d8d90320d4e5ecf5fdd58882f906055d33a18e13",
"https://deno.land/x/hono_sessions@v0.5.8/deps.ts": "a86ee9f6d96c0dcc80380eaa3d1cef13ff972a56003d8e1d7f461cc32554fa34",
"https://deno.land/x/hono_sessions@v0.5.8/mod.ts": "67e89aebb3c0e87e7e6065aefb56606d2f1017dd8ed030ef742f5bb73398abda",
"https://deno.land/x/hono_sessions@v0.5.8/src/Crypto.ts": "dcbf1dc60d5c67426f3a25bcd56a38012336c2418ab1291550f2fa1f76b66e25",
"https://deno.land/x/hono_sessions@v0.5.8/src/Middleware.ts": "05879181e4f649098c05800a47c12c21649dde41f8d21aceefc7b2dbb91b261b",
"https://deno.land/x/hono_sessions@v0.5.8/src/Session.ts": "9de1c3661a9b713a4eded0457a60432a96118be59741cb32e43c39a437da33cd",
"https://deno.land/x/hono_sessions@v0.5.8/src/store/CookieStore.ts": "9175f9da3e555c0e07928db03bb26ad40b6b0c1f661bfccd9aeddad0a615ccc2",
"https://deno.land/x/hono_sessions@v0.5.8/src/store/MemoryStore.ts": "f702d81a948a775207f047b60e2dd6c903a4b25b041c827309486055be3103fd",
"https://deno.land/x/hono_sessions@v0.5.8/src/store/Store.ts": "8f26b96fa22c330846ccb318cb90d3d1135aec9078c0faa7b5dc39ceb49a0da4",
"https://deno.land/x/zod@v3.22.4/ZodError.ts": "4de18ff525e75a0315f2c12066b77b5c2ae18c7c15ef7df7e165d63536fdf2ea",
"https://deno.land/x/zod@v3.22.4/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef",
"https://deno.land/x/zod@v3.22.4/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe",
"https://deno.land/x/zod@v3.22.4/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c",
"https://deno.land/x/zod@v3.22.4/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7",
"https://deno.land/x/zod@v3.22.4/helpers/parseUtil.ts": "f791e6e65a0340d85ad37d26cd7a3ba67126cd9957eac2b7163162155283abb1",
"https://deno.land/x/zod@v3.22.4/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7",
"https://deno.land/x/zod@v3.22.4/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e",
"https://deno.land/x/zod@v3.22.4/helpers/util.ts": "8baf19b19b2fca8424380367b90364b32503b6b71780269a6e3e67700bb02774",
"https://deno.land/x/zod@v3.22.4/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268",
"https://deno.land/x/zod@v3.22.4/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c",
"https://deno.land/x/zod@v3.22.4/mod.ts": "64e55237cb4410e17d968cd08975566059f27638ebb0b86048031b987ba251c4",
"https://deno.land/x/zod@v3.22.4/types.ts": "724185522fafe43ee56a52333958764c8c8cd6ad4effa27b42651df873fc151e",
"https://esm.sh/jsdom@22.1.0": "2efb54a90536dc0f4bb00f442c32fcedb2224af5aad5c87db87660f15c013170",
"https://esm.sh/v128/@tootallnate/once@2.0.0/denonext/once.mjs": "2c69d53dd1de1d7e5f9a78d46041f5d869f7d058f22d72e7f25821e61dddd147",
"https://esm.sh/v128/abab@2.0.6/denonext/abab.mjs": "7101c07a728882d686222e5f5c5a6fa956a558e4f406c521f1c0b61bc60cd07d",
"https://esm.sh/v128/agent-base@6.0.2/denonext/agent-base.mjs": "73a5fb40647fa22367f61cb5573a0404c21fed9d86445d21ece399ff47e097ad",
"https://esm.sh/v128/bufferutil@4.0.7/denonext/bufferutil.mjs": "f08b440283b2993a9e360b0e092c1714bc689230323961d60d7aad805b79c026",
"https://esm.sh/v128/canvas@2.11.2/denonext/canvas.mjs": "4245b1d01d91b5e807b85e40e98efe28c93634260bd8cb5ac0da71c42098a1a4",
"https://esm.sh/v128/cssstyle@3.0.0/denonext/cssstyle.mjs": "4fe8a9be3507541807575ed9254ca9e74c3ad950777a4478876ed52ac9c2e6da",
"https://esm.sh/v128/data-urls@4.0.0/denonext/data-urls.mjs": "fa125b728922f32cac094291ac3b8c0abcfac2c9963ee2c6280769f0e972519d",
"https://esm.sh/v128/debug@4.3.4/denonext/debug.mjs": "b837e7a39e434a676c751cd5ebd29351d8ad48d083006582edcf40361cadeccc",
"https://esm.sh/v128/decimal.js@10.4.3/denonext/decimal.mjs": "ece864b80732112b324aed7fd89ec03128627206e1e4b30057651f495b594dee",
"https://esm.sh/v128/domexception@4.0.0/denonext/webidl2js-wrapper.js": "d5c3a0961bc0fe877f335ade79e191d3e0848785a6b4521f67c54303f0c5a39d",
"https://esm.sh/v128/entities@4.5.0/denonext/lib/decode.js": "7fea6d8bd725edbbf7ea05031d2ea1bbbc1166dc11e3345d541198dd2dc16f1e",
"https://esm.sh/v128/entities@4.5.0/denonext/lib/escape.js": "7ebdc622bf3618bab25db40da4a49e2b9d03f044745f125f0bc3359f2d060def",
"https://esm.sh/v128/form-data@4.0.0/denonext/form-data.mjs": "48e84ac3b590bc364839367938d7e48ca37615a0c66e56dcc7356c3172ec7790",
"https://esm.sh/v128/html-encoding-sniffer@3.0.0/denonext/html-encoding-sniffer.mjs": "e5ca81202625bbf8802002b420cad9ddd1facb5ebd834c3a23bbac0a2362bbbc",
"https://esm.sh/v128/http-proxy-agent@5.0.0/denonext/http-proxy-agent.mjs": "b17dce342ba465c56d371c0ce5c0ee4ab56182bf878e465695c24380acebf29f",
"https://esm.sh/v128/https-proxy-agent@5.0.1/denonext/https-proxy-agent.mjs": "e250f991702f9f98f94e631d4d1a4a90020ddc667686c86390af1c50fa585f30",
"https://esm.sh/v128/iconv-lite@0.6.3/denonext/iconv-lite.mjs": "5840545a9ac1cea4684c800dfafca7200d8b0b7ea0f561b85c8445c12fc004d8",
"https://esm.sh/v128/is-potential-custom-element-name@1.0.1/denonext/is-potential-custom-element-name.mjs": "de2781ef99795b662f43c0840c3dcfdc303f9e60a75e66924370f902133469ed",
"https://esm.sh/v128/jsdom@22.1.0/denonext/jsdom.mjs": "de374334080b3d18ac730f117aceab674e50642285a3f94f3b7db5de04c6798b",
"https://esm.sh/v128/ms@2.1.2/denonext/ms.mjs": "aa4dc45ba72554c5011168f8910cc646c37af53cfff1a15a4decced838b8eb14",
"https://esm.sh/v128/node-gyp-build@4.6.0/denonext/node-gyp-build.mjs": "0de7d4d97a896a89efb9b3a31a0746f89f5c0e86d7f92badd0fddc3cd22ca40b",
"https://esm.sh/v128/nwsapi@2.2.6/denonext/nwsapi.mjs": "def5ad23c81fba69b806c0daf07c6b932a8069715e41404d27fe443cd46d75bd",
"https://esm.sh/v128/parse5@7.1.2/denonext/parse5.mjs": "9885dc571f470bdb484d8f0e1dbab42af18f406200ed5bcf7ebb9c183faa32ae",
"https://esm.sh/v128/psl@1.9.0/denonext/psl.mjs": "f40e9bcba5f6602eeb677f85f8e2b5c24b706bade58ced5b272044ebea609d9b",
"https://esm.sh/v128/querystringify@2.2.0/denonext/querystringify.mjs": "4f0f639f99ec4a7ddaffb886bd7e6f3fe4b088e1fcd60336dd10d447d2093ef6",
"https://esm.sh/v128/requires-port@1.0.0/denonext/requires-port.mjs": "c4f20b71539d08fc2662d75dfd79881fce985a0e4592268f18ac13bf53679efa",
"https://esm.sh/v128/rrweb-cssom@0.6.0/denonext/rrweb-cssom.mjs": "e2a14692d801a24edadd3fd4ecdf017382494258064134e33390a6bf1146950d",
"https://esm.sh/v128/safer-buffer@2.1.2/denonext/safer-buffer.mjs": "ce0e787812c668ba082ad5b75958490c714b6e05836bd5b6013d9f75727c006f",
"https://esm.sh/v128/saxes@6.0.0/denonext/saxes.mjs": "51f5349277fb67f314089490b16b9732fc85e85eae0c2ae6c5efcb965a57260f",
"https://esm.sh/v128/symbol-tree@3.2.4/denonext/symbol-tree.mjs": "67199d1e47bd6e5b7d2715dd04d25658061c95fc4464f7d200b6aab9e439b5f4",
"https://esm.sh/v128/tough-cookie@4.1.3/denonext/tough-cookie.mjs": "f9f24f23ada31799b530ed23b265f7cf5e17b70b8141687ee0c3e613ef55d766",
"https://esm.sh/v128/tr46@4.1.1/denonext/tr46.mjs": "2042307b546151186c6912a27265e444331e3d0bd6820c92a750aa67b3e5fdfc",
"https://esm.sh/v128/universalify@0.2.0/denonext/universalify.mjs": "6ebb2a9b372d4ae89f494d9b0044942dc76a5b5fb366ab3018e2bf0aeb8eae94",
"https://esm.sh/v128/url-parse@1.5.10/denonext/url-parse.mjs": "f04a3a5b4b960a62bab9c4dc9a4f8cfe3094ce24cc870c979b6cf9c3890c9a19",
"https://esm.sh/v128/utf-8-validate@6.0.3/denonext/utf-8-validate.mjs": "5f09fc9a7a6d2ed3fedf17fbfee3d906fb3acecfe18e9351de026b857e4b1d86",
"https://esm.sh/v128/w3c-xmlserializer@4.0.0/denonext/w3c-xmlserializer.mjs": "e23495a59a8c55bf3c1202a2e7ee4b1c8e25d1c6be14f5872d51ef8e50f6382f",
"https://esm.sh/v128/webidl-conversions@7.0.0/denonext/webidl-conversions.mjs": "04e3e6917179380727c6f65cd16a5a89836fb5a104fe5524c10a0a697f88d552",
"https://esm.sh/v128/whatwg-encoding@2.0.0/denonext/whatwg-encoding.mjs": "3483d367364a2aaf503a4fc0e56e6163c88c50d1aede2117c9ec264ea558e0a6",
"https://esm.sh/v128/whatwg-mimetype@3.0.0/denonext/whatwg-mimetype.mjs": "59446370c8333e5956416535e9773585645c22e7ea3420c8df5d78d1e9127c59",
"https://esm.sh/v128/whatwg-url@12.0.1/denonext/webidl2js-wrapper.js": "d9a6264ca46bf11a22eb24577dd549f2c5778fb0ec4783e69ff9a705a8aa3baa",
"https://esm.sh/v128/whatwg-url@12.0.1/denonext/whatwg-url.mjs": "efecc5954d62474ceb6a528a81e8b6503ab2473f0913fa2341ddcf381651bbf9",
"https://esm.sh/v128/ws@8.13.0/denonext/ws.mjs": "9891167e91ecedb8aa3e01398b564429aff5538ba58a9f27c680552b7ccab835",
"https://esm.sh/v128/xml-name-validator@4.0.0/denonext/xml-name-validator.mjs": "69af66c891312f304a8be720961ad5cce5c49c85a3bba03275b56a70dec7a21e",
"https://esm.sh/v128/xmlchars@2.2.0/denonext/xml/1.0/ed5.js": "60f8f018eb1d79d69a41324155b7d9f52f1058b37060b28acc1dfc49446e549d",
"https://esm.sh/v128/xmlchars@2.2.0/denonext/xml/1.1/ed2.js": "ba7d1fe5694f62469c4b293a1fadad332c637cbcfbc74147a296475c2ff8ad3d",
"https://esm.sh/v128/xmlchars@2.2.0/denonext/xmlns/1.0/ed3.js": "929d15ffc72d56c8909f87e7df8288f060bda0256622e8e95c24f0decb06adc7",
"https://git.barnulf.net/mb/debilus/raw/commit/fc701bec680dd73be29c72164f47ee87fac540c7/mod.ts": "bc9436bcf45f3204ebcd0b2559f53e5d38162195fc48ac6d435cf8ca38c77ae3",
"https://git.barnulf.net/mb/debilus/raw/commit/fc701bec680dd73be29c72164f47ee87fac540c7/src/entry.ts": "243a53b27e494c01e944331e2b71dfbe96ff906921aca8870603eb19bbce8235",
"https://git.barnulf.net/mb/debilus/raw/commit/fc701bec680dd73be29c72164f47ee87fac540c7/src/lib.ts": "76973ce240e2bcadaf13e43115d050dee70b92dc5cd7cd726cdd5d9adf4c1ad6",
"https://git.barnulf.net/mb/debilus/raw/commit/fc701bec680dd73be29c72164f47ee87fac540c7/src/store.ts": "ff6cdb0c045818d8e4ab6eb2bb304db8ac387ef05514af09e6c731bb65815a10",
"https://git.barnulf.net/mb/debilus/raw/commit/fc701bec680dd73be29c72164f47ee87fac540c7/src/typing.ts": "cb0d315461aab883ddd2396f5a5ce2a88615257b402d5a5cd06c10dc44ffa453"
}
}

8
dev.ts Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env -S deno run -A --watch=static/,routes/
import dev from "$fresh/dev.ts";
import config from "./fresh.config.ts";
import "$std/dotenv/load.ts";
await dev(import.meta.url, "./main.ts", config);

46
doc/models.md Normal file
View file

@ -0,0 +1,46 @@
## Views
- Home
- Landing
- Feed (logged)
- Post
- new
- view
- likes
- add (logged)
- comment section
- new (logged)
- Account
- Login
- Register
## Models
- User
- name
- email
- password
- pfp_url
- like_set
* posts
* comments
- Post
- title
- content
- date
- like_count
+ author
* comments
- Comment
- date
+ post
+ author
---
- trend algo ?
- notifications ?

3
fresh.config.ts Normal file
View file

@ -0,0 +1,3 @@
import { defineConfig } from "$fresh/server.ts";
export default defineConfig({});

31
fresh.gen.ts Normal file
View file

@ -0,0 +1,31 @@
// 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 $_middleware from "./routes/_middleware.ts";
import * as $api_joke from "./routes/api/joke.ts";
import * as $feur from "./routes/feur.tsx";
import * as $greet_name_ from "./routes/greet/[name].tsx";
import * as $index from "./routes/index.tsx";
import * as $Counter from "./islands/Counter.tsx";
import { type Manifest } from "$fresh/server.ts";
const manifest = {
routes: {
"./routes/_404.tsx": $_404,
"./routes/_app.tsx": $_app,
"./routes/_middleware.ts": $_middleware,
"./routes/api/joke.ts": $api_joke,
"./routes/feur.tsx": $feur,
"./routes/greet/[name].tsx": $greet_name_,
"./routes/index.tsx": $index,
},
islands: {
"./islands/Counter.tsx": $Counter,
},
baseUrl: import.meta.url,
} satisfies Manifest;
export default manifest;

16
islands/Counter.tsx Normal file
View file

@ -0,0 +1,16 @@
import type { Signal } from "@preact/signals";
import { Button } from "../components/Button.tsx";
interface CounterProps {
count: Signal<number>;
}
export default function Counter(props: CounterProps) {
return (
<div class="flex gap-8 py-6">
<Button onClick={() => props.count.value -= 1}>-1</Button>
<p class="text-3xl tabular-nums">{props.count}</p>
<Button onClick={() => props.count.value += 1}>+1</Button>
</div>
);
}

View file

@ -1,24 +0,0 @@
import { Context } from "https://deno.land/x/hono@v4.3.10/mod.ts";
import { db, User } from "./storage.ts";
import { FeurEnv } from "../main.ts";
import { BlankInput } from "https://deno.land/x/hono@v4.3.10/types.ts";
export async function login(login: string, password: string) {
for await (const user of db.all("user")) {
if (await user.get("username") !== login) continue;
if (await user.get("password") === password) return user;
return null;
}
return null;
}
export async function get_user(context: Context<FeurEnv, string, BlankInput>) {
const id = context.get("session").get("user");
if (typeof id !== "string") return null;
return await db.get("user", id);
}
export function set_user(context: Context<FeurEnv, string, BlankInput>, user: User | null) {
if (user === null) context.get("session").set("user", "");
else context.get("session").set("user", user.id);
}

9
lib/models/User.ts Normal file
View file

@ -0,0 +1,9 @@
import { z } from "https://deno.land/x/zod@v3.23.8/mod.ts";
export const UserModel = z.object({
id: z.string().uuid().describe("primary"),
name: z.string(),
email: z.string(),
password: z.string(),
pfp_url: z.string(),
});

View file

@ -1,36 +0,0 @@
import {
EntryFor,
Schema,
Store,
} from "https://git.barnulf.net/mb/debilus/raw/commit/fc701bec680dd73be29c72164f47ee87fac540c7/mod.ts";
import { project_root } from "./utils.ts";
export const db = await Store.open(
new Schema({
user: {
username: "string",
password: "string",
upvoted_posts: ["many", "post"],
upvoted_comments: ["many", "comment"],
posts: ["many", "post"],
},
post: {
title: "string",
content: "string",
upvotes: "number",
author: ["one", "user"],
comments: ["many", "comment"],
},
comment: {
content: "string",
upvotes: "number",
author: ["one", "user"],
post: ["one", "post"],
},
}),
`${project_root()}/local/db.kv`,
);
export type User = EntryFor<typeof db, "user">;
export type Post = EntryFor<typeof db, "post">;
export type Comment = EntryFor<typeof db, "comment">;

57
lib/store.ts Normal file
View file

@ -0,0 +1,57 @@
import { createPentagon } from "https://deno.land/x/pentagon@v0.1.5/mod.ts";
import { project_root_dir } from "../utils.ts";
import { z } from "https://deno.land/x/zod@v3.21.4/mod.ts";
const kv = await Deno.openKv(project_root_dir() + "local/kv");
export type User = z.infer<typeof user_model>;
export const user_model = z.object({
id: z.string().uuid().describe("primary"),
name: z.string(),
email: z.string(),
password: z.string(),
pfp_url: z.string(),
like_set: z.set(z.string().uuid()),
});
export type Post = z.infer<typeof post_model>;
export const post_model = z.object({
id: z.string().uuid().describe("primary"),
title: z.string(),
content: z.string(),
date: z.number(),
like_count: z.number(),
author_id: z.string().uuid(),
});
export type Comment = z.infer<typeof comment_model>;
export const comment_model = z.object({
id: z.string().uuid().describe("primary"),
date: z.number(),
post_id: z.string().uuid(),
author_id: z.string().uuid(),
});
export const db = createPentagon(kv, {
users: {
schema: user_model,
relations: {
posts: ["posts", [post_model], "id", "author_id"],
comments: ["comments", [comment_model], "id", "author_id"],
},
},
posts: {
schema: post_model,
relations: {
author: ["users", user_model, "author_id", "id"],
comments: ["comments", [comment_model], "id", "post_id"],
},
},
comments: {
schema: comment_model,
relations: {
author: ["users", user_model, "author_id", "id"],
post: ["post", post_model, "post_id", "id"],
},
},
});

View file

@ -1,14 +0,0 @@
import { dirname } from "https://deno.land/std@0.224.0/path/dirname.ts";
import { CSSProperties } from "https://deno.land/std@0.40.0/types/react.d.ts";
export function _css(prop: CSSProperties) {
return prop;
}
export function project_root() {
const this_url = new URL(import.meta.url);
const this_abs_path = Deno.realPathSync(this_url.pathname);
const lib_path = dirname(this_abs_path);
return dirname(lib_path);
}

2
local/.gitignore vendored
View file

@ -1,2 +0,0 @@
*
!/.gitignore

41
main.ts Executable file → Normal file
View file

@ -1,33 +1,14 @@
#!/bin/env -S deno run --allow-net --unstable-kv --watch --allow-read=./pages,./local,./lib,. --allow-write=./local /// <reference no-default-lib="true" />
/// <reference lib="dom" />
/// <reference lib="dom.iterable" />
/// <reference lib="dom.asynciterable" />
/// <reference lib="deno.ns" />
/// <reference lib="deno.unstable" />
import { Hono } from "https://deno.land/x/hono@v4.3.10/mod.ts"; import "$std/dotenv/load.ts";
import { serveStatic } from "https://deno.land/x/hono@v4.3.10/middleware.ts";
import { CookieStore, Session, sessionMiddleware } from "https://deno.land/x/hono_sessions@v0.5.8/mod.ts";
export type FeurEnv = { Variables: { session: Session } }; import { start } from "$fresh/server.ts";
const app = new Hono<FeurEnv>(); import manifest from "./fresh.gen.ts";
import config from "./fresh.config.ts";
app.use("/static/*", serveStatic({ root: "./pages/" })); await start(manifest, config);
const store = new CookieStore();
const encryptionKey = "FeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeur";
app.use("*", sessionMiddleware({ store, encryptionKey }));
app.get("/", (c) => c.redirect("/home"));
import HomePage from "./pages/home.tsx";
app.get("/home", (c) => c.html(HomePage(c)));
import LoginPage from "./pages/login.tsx";
app.get("/login", (c) => c.html(LoginPage()));
import { login_route } from "./api/login.ts";
app.post("/api/login", (c) => login_route(c));
import { logout_route } from "./api/logout.ts";
app.get("/api/logout", (c) => logout_route(c));
import UserPage from "./pages/user.tsx";
app.get("/user", async (c) => await UserPage(c));
Deno.serve(app.fetch);

View file

@ -1,22 +0,0 @@
/** @jsx jsx */
import { PropsWithChildren, jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts"
import { _css } from "../../lib/utils.ts";
export function BasePage({ name, children }: PropsWithChildren<{ name: string }>) {
return (
<html lang="en">
<head>
<meta charSet="UTF-8"/>
<title>{name} - Twifeur</title>
<link rel="stylesheet" href="/static/style.css" />
</head>
<body style={_css({
width: "100%",
minHeight: "100%",
})}>
{children}
</body>
</html>
)
}

View file

@ -1,53 +0,0 @@
/** @jsx jsx */
import { PropsWithChildren, jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts"
import { _css } from "../../lib/utils.ts";
import { User } from "../../lib/storage.ts";
//
export default function Heading({ username }: PropsWithChildren<{username: string | null}>) {
return (
<header style={_css({
width: "100vw",
display: "grid",
placeContent: "center",
backgroundColor: "#202020",
borderBottom: "1px solid #444",
boxShadow: "0px 0px 10px #0008",
position: "fixed",
})}>
<div style={_css({
width: "100vw",
maxWidth: "900px",
display: "flex",
})}>
<div style={_css({
borderLeft: '1px solid #444',
borderRight: '1px solid #444',
})}>
<h1 style={_css({
margin: "0.3rem",
marginLeft: "3rem",
marginRight: "3rem",
})}>TwiFeur</h1>
</div>
<MenuItem name="Content" location="/home"/>
<MenuItem name="About" location="/about"/>
<MenuItem name="User" location="/user"/>
{(username !== null) && <MenuItem name={`Logout (${username})`} location="/api/logout" />}
</div>
</header>
)
}
function MenuItem({ name, location }: { name: string, location: string }) {
return (
<a href={location} style={_css({
borderRight: '1px solid #444',
textDecoration: 'none',
color: 'unset'
})}>
<h3 style={_css({ margin: "0.7rem" })}>{name}</h3>
</a>
)
}

View file

@ -1,13 +0,0 @@
/** @jsx jsx */
import { jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts"
export default function Login() {
return (
<form action="/api/login" method="post">
<input type="text" name="login"/>
<input type="password" name="password"/>
<input type="submit" value="login" />
</form>
)
}

View file

@ -1,51 +0,0 @@
/** @jsx jsx */
import { PropsWithChildren, jsx, Fragment } from "https://deno.land/x/hono@v4.3.10/middleware.ts"
import { _css } from "../../lib/utils.ts";
export default function Main({ children }: PropsWithChildren) {
return (
<Fragment>
<main style={_css({
width: "100vw",
display: "grid",
placeContent: "center",
})}>
<div style={_css({
width: "100vw",
maxWidth: "1000px",
backgroundColor: "#202020",
borderLeft: "1px solid #444",
borderRight: "1px solid #444",
boxShadow: "0px 0px 10px #0008",
paddingTop: "2rem"
})}>
<div style={_css({
minHeight: "100vh",
padding: "2rem"
})}>
{children}
</div>
</div>
</main>
<footer style={_css({
width: "100vw",
display: "grid",
placeContent: "center",
backgroundColor: "#202020",
borderTop: "1px solid #444",
boxShadow: "0px 0px 10px #0008",
position: "relative",
})}>
<div style={_css({
width: "100vw",
maxWidth: "900px",
display: "flex",
padding: "1rem"
})}>
Footer
</div>
</footer>
</Fragment>
)
}

View file

@ -1,12 +0,0 @@
/** @jsx jsx */
import { PropsWithChildren, jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts"
import { _css } from "../../lib/utils.ts";
export function Post({ children, name }: PropsWithChildren<{ name: string }>) {
return (
<div>
</div>
)
}

View file

@ -1,20 +0,0 @@
/** @jsx jsx */
import { jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts"
import { BasePage } from "./components/base.tsx"
import Main from "./components/main.tsx";
import Heading from "./components/heading.tsx";
import { get_user } from "../lib/auth.ts";
import { Context } from "https://deno.land/x/hono@v4.3.10/mod.ts";
import { FeurEnv } from "../main.ts";
import { BlankInput } from "https://deno.land/x/hono@v4.3.10/types.ts";
export default async function HomePage(context: Context<FeurEnv, string, BlankInput>) {
const user = await get_user(context);
return (
<BasePage name="Home">
<Heading username={await user?.get("username") ?? null}></Heading>
<Main>Main</Main>
</BasePage>
)
}

View file

@ -1,18 +0,0 @@
/** @jsx jsx */
import { jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts"
import { BasePage } from "./components/base.tsx";
import Heading from "./components/heading.tsx";
import Main from "./components/main.tsx";
import Login from "./components/login.tsx";
export default function LoginPage() {
return (
<BasePage name="Login">
<Main>
<h1>Login</h1>
<Login/>
</Main>
</BasePage>
)
}

View file

@ -1,23 +0,0 @@
html,
body,
header,
main,
footer {
margin: 0;
padding: 0;
}
h1 {
font-size: 1.5rem;
}
h3 {
font-size: 1rem;
}
body {
background-color: #181818;
color: #eee;
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
overflow-x: hidden
}

View file

@ -1,24 +0,0 @@
/** @jsx jsx */
import { jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts"
import { BasePage } from "./components/base.tsx";
import Heading from "./components/heading.tsx";
import Main from "./components/main.tsx";
import Login from "./components/login.tsx";
import { User } from "../lib/storage.ts";
import { Context } from "https://deno.land/x/hono@v4.3.10/mod.ts";
import { FeurEnv } from "../main.ts";
import { BlankInput } from "https://deno.land/x/hono@v4.3.10/types.ts";
import { get_user } from "../lib/auth.ts";
export default async function UserPage(context: Context<FeurEnv, string, BlankInput>) {
const user = await get_user(context);
if (user === null) return context.text("Must be logged.", 401);
return context.html(
<BasePage name="Login">
<Main>
<h1>Logged as {await user.get("username")}</h1>
</Main>
</BasePage>
)
}

27
routes/_404.tsx Normal file
View file

@ -0,0 +1,27 @@
import { Head } from "$fresh/runtime.ts";
export default function Error404() {
return (
<>
<Head>
<title>404 - Page not found</title>
</Head>
<div class="px-4 py-8 mx-auto bg-[#86efac]">
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
<img
class="my-6"
src="/logo.svg"
width="128"
height="128"
alt="the Fresh logo: a sliced lemon dripping with juice"
/>
<h1 class="text-4xl font-bold">404 - Page not found</h1>
<p class="my-4">
The page you were looking for doesn't exist.
</p>
<a href="/" class="underline">Go back home</a>
</div>
</div>
</>
);
}

16
routes/_app.tsx Normal file
View file

@ -0,0 +1,16 @@
import { type PageProps } from "$fresh/server.ts";
export default function App({ Component }: PageProps) {
return (
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>twifeur</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<Component />
</body>
</html>
);
}

40
routes/_middleware.ts Normal file
View file

@ -0,0 +1,40 @@
import { FreshContext } from "$fresh/server.ts";
import { deleteCookie, getCookies, setCookie } from "$std/http/cookie.ts";
import { auth, Token } from "../auth/auth.ts";
type State = {
user_id: string | null;
};
export async function handler(
req: Request,
ctx: FreshContext<State>,
) {
const cookies = getCookies(req.headers);
const token = get_session_token(cookies);
ctx.state.user_id = token?.user_id ?? null;
const resp = await ctx.next();
set_session_token(resp.headers, token);
return resp;
}
function get_session_token(cookies: Record<string, string>) {
const stored = cookies["auth_token"];
if (stored === undefined) return null;
const token = auth.tokens.get(stored);
if (token === null) return null;
return token;
}
function set_session_token(headers: Headers, token: Token | null) {
if (token === null) {
deleteCookie(headers, "auth_token");
return;
}
setCookie(headers, {
name: "auth_token",
value: token.raw,
});
}

21
routes/api/joke.ts Normal file
View file

@ -0,0 +1,21 @@
import { FreshContext } from "$fresh/server.ts";
// Jokes courtesy of https://punsandoneliners.com/randomness/programmer-jokes/
const JOKES = [
"Why do Java developers often wear glasses? They can't C#.",
"A SQL query walks into a bar, goes up to two tables and says “can I join you?”",
"Wasn't hard to crack Forrest Gump's password. 1forrest1.",
"I love pressing the F5 key. It's refreshing.",
"Called IT support and a chap from Australia came to fix my network connection. I asked “Do you come from a LAN down under?”",
"There are 10 types of people in the world. Those who understand binary and those who don't.",
"Why are assembly programmers often wet? They work below C level.",
"My favourite computer based band is the Black IPs.",
"What programme do you use to predict the music tastes of former US presidential candidates? An Al Gore Rhythm.",
"An SEO expert walked into a bar, pub, inn, tavern, hostelry, public house.",
];
export const handler = (_req: Request, _ctx: FreshContext): Response => {
const randomIndex = Math.floor(Math.random() * JOKES.length);
const body = JOKES[randomIndex];
return new Response(body);
};

5
routes/greet/[name].tsx Normal file
View file

@ -0,0 +1,5 @@
import { PageProps } from "$fresh/server.ts";
export default function Greet(props: PageProps) {
return <div>Hello {props.params.name}</div>;
}

25
routes/index.tsx Normal file
View file

@ -0,0 +1,25 @@
import { useSignal } from "@preact/signals";
import Counter from "../islands/Counter.tsx";
export default function Home() {
const count = useSignal(3);
return (
<div class="px-4 py-8 mx-auto bg-[#86efac]">
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
<img
class="my-6"
src="/logo.svg"
width="128"
height="128"
alt="the Fresh logo: a sliced lemon dripping with juice"
/>
<h1 class="text-4xl font-bold">Welcome to Fresh</h1>
<p class="my-4">
Try updating this message in the
<code class="mx-2">./routes/index.tsx</code> file, and refresh.
</p>
<Counter count={count} />
</div>
</div>
);
}

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

6
static/logo.svg Normal file
View file

@ -0,0 +1,6 @@
<svg width="40" height="40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M34.092 8.845C38.929 20.652 34.092 27 30 30.5c1 3.5-2.986 4.222-4.5 2.5-4.457 1.537-13.512 1.487-20-5C2 24.5 4.73 16.714 14 11.5c8-4.5 16-7 20.092-2.655Z" fill="#FFDB1E"/>
<path d="M14 11.5c6.848-4.497 15.025-6.38 18.368-3.47C37.5 12.5 21.5 22.612 15.5 25c-6.5 2.587-3 8.5-6.5 8.5-3 0-2.5-4-5.183-7.75C2.232 23.535 6.16 16.648 14 11.5Z" fill="#fff" stroke="#FFDB1E"/>
<path d="M28.535 8.772c4.645 1.25-.365 5.695-4.303 8.536-3.732 2.692-6.606 4.21-7.923 4.83-.366.173-1.617-2.252-1.617-1 0 .417-.7 2.238-.934 2.326-1.365.512-4.223 1.29-5.835 1.29-3.491 0-1.923-4.754 3.014-9.122.892-.789 1.478-.645 2.283-.645-.537-.773-.534-.917.403-1.546C17.79 10.64 23 8.77 25.212 8.42c.366.014.82.35.82.629.41-.14 2.095-.388 2.503-.278Z" fill="#FFE600"/>
<path d="M14.297 16.49c.985-.747 1.644-1.01 2.099-2.526.566.121.841-.08 1.29-.701.324.466 1.657.608 2.453.701-.715.451-1.057.852-1.452 2.106-1.464-.611-3.167-.302-4.39.42Z" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

129
static/styles.css Normal file
View file

@ -0,0 +1,129 @@
*,
*::before,
*::after {
box-sizing: border-box;
}
* {
margin: 0;
}
button {
color: inherit;
}
button, [role="button"] {
cursor: pointer;
}
code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
img,
svg {
display: block;
}
img,
video {
max-width: 100%;
height: auto;
}
html {
line-height: 1.5;
-webkit-text-size-adjust: 100%;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
.transition-colors {
transition-property: background-color, border-color, color, fill, stroke;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.my-6 {
margin-bottom: 1.5rem;
margin-top: 1.5rem;
}
.text-4xl {
font-size: 2.25rem;
line-height: 2.5rem;
}
.mx-2 {
margin-left: 0.5rem;
margin-right: 0.5rem;
}
.my-4 {
margin-bottom: 1rem;
margin-top: 1rem;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.py-8 {
padding-bottom: 2rem;
padding-top: 2rem;
}
.bg-\[\#86efac\] {
background-color: #86efac;
}
.text-3xl {
font-size: 1.875rem;
line-height: 2.25rem;
}
.py-6 {
padding-bottom: 1.5rem;
padding-top: 1.5rem;
}
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.py-1 {
padding-bottom: 0.25rem;
padding-top: 0.25rem;
}
.border-gray-500 {
border-color: #6b7280;
}
.bg-white {
background-color: #fff;
}
.flex {
display: flex;
}
.gap-8 {
grid-gap: 2rem;
gap: 2rem;
}
.font-bold {
font-weight: 700;
}
.max-w-screen-md {
max-width: 768px;
}
.flex-col {
flex-direction: column;
}
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.border-2 {
border-width: 2px;
}
.rounded {
border-radius: 0.25rem;
}
.hover\:bg-gray-200:hover {
background-color: #e5e7eb;
}
.tabular-nums {
font-variant-numeric: tabular-nums;
}

7
utils.ts Normal file
View file

@ -0,0 +1,7 @@
import { dirname } from "$std/path/dirname.ts";
export function project_root_dir() {
const this_url = new URL(import.meta.url);
const this_dir = dirname(this_url.pathname);
return this_dir + "/";
}