From 1c73bdc630bbcedaba06038fc784c385592db411 Mon Sep 17 00:00:00 2001 From: ynwd <10122431+ynwd@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:38:23 +0700 Subject: [PATCH] chore: deno 2.1.1 --- b.svg | 11 +- components/button.tsx | 16 +- components/header.tsx | 106 +++--- components/icons/admin-svg.tsx | 52 +-- components/icons/ads-svg.tsx | 40 +-- components/icons/angle-left-svg.tsx | 38 +-- components/icons/arrow-svg.tsx | 30 +- components/icons/bolt-svg.tsx | 13 - components/icons/bolt.tsx | 23 +- components/icons/deno-svg.tsx | 40 +-- components/icons/github-svg.tsx | 34 +- components/icons/info-svg.tsx | 38 +-- components/icons/preact-svg.tsx | 28 +- components/icons/repeat-svg.tsx | 36 +-- components/icons/rocket-svg.tsx | 38 +-- components/icons/sales-svg.tsx | 38 +-- components/icons/scale-svg.tsx | 46 +-- components/icons/seo-svg.tsx | 40 +-- components/icons/setting-svg.tsx | 36 +-- components/icons/tailwind-svg.tsx | 26 +- components/icons/ts-svg.tsx | 34 +- components/icons/user-account-svg.tsx | 38 +-- components/icons/user-group-svg.tsx | 36 +-- components/icons/ux-svg.tsx | 40 +-- components/icons/warehouse-svg.tsx | 38 +-- components/icons/www-svg.tsx | 50 +-- components/project-box.tsx | 34 +- components/search.tsx | 62 ++-- core/map/mod.test.ts | 250 +++++++------- core/map/mod.ts | 394 +++++++++++------------ core/server/mod.ts | 36 +-- docs/app-middleware.md | 18 +- docs/benchmarks.md | 13 +- docs/fn-component.md | 4 +- docs/hello-context.md | 8 +- docs/route-middleware.md | 24 +- docs/ssr.md | 32 +- docs/store.md | 60 ++-- docs/tsx.md | 8 +- docs/url-params.md | 4 +- docs/url-query.md | 4 +- examples/store.ts | 50 +-- middleware/github/mod.ts | 32 +- modules/admin/admin.handler.tsx | 2 +- modules/admin/admin.layout.tsx | 40 +-- modules/admin/admin.page.tsx | 206 ++++++------ modules/admin/mod.ts | 14 +- modules/blog/blog.json | 90 +++--- modules/blog/mod.ts | 36 +-- modules/docs/docs.json | 216 ++++++------- modules/docs/mod.ts | 36 +-- modules/group/group.service.test.ts | 102 +++--- modules/group/group.service.ts | 108 +++---- modules/group/group.type.ts | 8 +- modules/hook/fetch.ts | 48 +-- modules/hook/socket.ts | 176 ++++------ modules/index/index.ads.tsx | 52 +-- modules/index/index.chat.tsx | 44 +-- modules/index/index.context.ts | 32 +- modules/index/index.discover.tsx | 94 +++--- modules/index/index.handler.ts | 89 ++--- modules/index/index.input.tsx | 170 +++++----- modules/index/index.launchpad.tsx | 76 ++++- modules/index/index.main.tsx | 364 +++++++++++---------- modules/index/index.menu.tsx | 24 +- modules/index/index.message.tsx | 126 ++++---- modules/index/index.navigation.tsx | 100 +++--- modules/index/index.non-login.tsx | 249 +++++++------- modules/index/index.page.tsx | 48 ++- modules/index/index.profile.tsx | 58 ++-- modules/index/index.room.tsx | 78 ++--- modules/index/index.signout.tsx | 48 +-- modules/index/mod.ts | 14 +- modules/markdown/mod.tsx | 48 +-- modules/message/mod.test.ts | 14 +- modules/message/mod.ts | 42 +-- modules/permission/permission.service.ts | 292 ++++++++--------- modules/permission/permission.test.ts | 218 ++++++------- modules/permission/permission.type.ts | 12 +- modules/room/mod.ts | 218 ++++++------- modules/socket/init.ts | 24 +- modules/socket/mod.ts | 212 ++++++------ modules/store/mod.ts | 34 +- modules/toc/toc.layout.tsx | 84 ++--- modules/toc/toc.page.tsx | 14 +- modules/types/mod.ts | 18 +- modules/user/user.service.test.ts | 174 +++++----- modules/user/user.service.ts | 96 +++--- modules/user/user.type.ts | 12 +- post/store.md | 22 +- static/manifest.json | 74 ++--- static/markdown.css | 20 +- static/tailwind.css | 64 ++-- task/db_dump.ts | 6 +- utils/cmd.ts | 66 ++-- utils/exe.ts | 139 ++++++++ utils/general.ts | 2 +- utils/octokit.ts | 180 +++++------ utils/promise.ts | 41 +++ utils/queue.test.ts | 32 +- utils/queue.ts | 56 ++-- utils/session.ts | 20 +- utils/ulid.ts | 74 ++--- 103 files changed, 3612 insertions(+), 3442 deletions(-) delete mode 100644 components/icons/bolt-svg.tsx create mode 100644 utils/promise.ts diff --git a/b.svg b/b.svg index 4173a4a10..6abbb7aac 100644 --- a/b.svg +++ b/b.svg @@ -1,3 +1,10 @@ - - + + + \ No newline at end of file diff --git a/components/button.tsx b/components/button.tsx index c40c2b16c..2fa12407a 100644 --- a/components/button.tsx +++ b/components/button.tsx @@ -1,12 +1,12 @@ import type { ComponentChildren } from "preact"; export default function Button(props: { children: ComponentChildren }) { - return ( - - ); + return ( + + ); } diff --git a/components/header.tsx b/components/header.tsx index 2b18d7992..4f5ac0398 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -4,60 +4,60 @@ import GithubSvg from "@app/components/icons/github-svg.tsx"; import RocketSvg from "./icons/rocket-svg.tsx"; export default function Header( - props: { - isLogin: boolean; - avatar_url: string; - html_url: string; - title?: string; - previous_url?: string; - }, + props: { + isLogin: boolean; + avatar_url: string; + html_url: string; + title?: string; + previous_url?: string; + }, ) { - return ( -
-
- -
- {props.isLogin - ? - : props.previous_url - ? - : } -
-
- {`${props.title || "Fastro"}`} -
-
- Blog - Docs + return ( +
+ +
+ Blog + Docs - {props.isLogin && ( - Sign out - )} - {!props.isLogin && ( - Sign in - )} + {props.isLogin && ( + Sign out + )} + {!props.isLogin && ( + Sign in + )} - - {!props.avatar_url ? : ( - - )} - -
-
- ); + + {!props.avatar_url ? : ( + + )} + +
+
+ ); } diff --git a/components/icons/admin-svg.tsx b/components/icons/admin-svg.tsx index d25e43f32..d117a5a3e 100644 --- a/components/icons/admin-svg.tsx +++ b/components/icons/admin-svg.tsx @@ -1,28 +1,28 @@ export default function AdminSvg() { - return ( - - - - - - - - - - - - - - ); + return ( + + + + + + + + + + + + + + ); } diff --git a/components/icons/ads-svg.tsx b/components/icons/ads-svg.tsx index 297865640..d3354d2f4 100644 --- a/components/icons/ads-svg.tsx +++ b/components/icons/ads-svg.tsx @@ -1,22 +1,22 @@ export default function AdsSvg() { - return ( - - - - - - - - ); + return ( + + + + + + + + ); } diff --git a/components/icons/angle-left-svg.tsx b/components/icons/angle-left-svg.tsx index 816644b2d..5ce09b613 100644 --- a/components/icons/angle-left-svg.tsx +++ b/components/icons/angle-left-svg.tsx @@ -1,21 +1,21 @@ export default function AngleLeftSvg() { - return ( - - ); + return ( + + ); } diff --git a/components/icons/arrow-svg.tsx b/components/icons/arrow-svg.tsx index 7dd2bed72..a4808db22 100644 --- a/components/icons/arrow-svg.tsx +++ b/components/icons/arrow-svg.tsx @@ -1,17 +1,17 @@ export default function ArrowSvg() { - return ( - - - - - ); + return ( + + + + + ); } diff --git a/components/icons/bolt-svg.tsx b/components/icons/bolt-svg.tsx deleted file mode 100644 index 8cf5b8946..000000000 --- a/components/icons/bolt-svg.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export default function BoltSvg(props: { height?: string; width?: string }) { - return ( - - - - ); -} diff --git a/components/icons/bolt.tsx b/components/icons/bolt.tsx index 168ea941b..3c5ec7011 100644 --- a/components/icons/bolt.tsx +++ b/components/icons/bolt.tsx @@ -1,13 +1,14 @@ export default function BoltSvg(props: { height?: string; width?: string }) { - return ( - - - - ); + return ( + + + + + ); } diff --git a/components/icons/deno-svg.tsx b/components/icons/deno-svg.tsx index 6f19e2fe8..a4f550c04 100644 --- a/components/icons/deno-svg.tsx +++ b/components/icons/deno-svg.tsx @@ -1,22 +1,22 @@ export default function DenoSvg() { - return ( - - Deno logo - - - - - - ); + return ( + + Deno logo + + + + + + ); } diff --git a/components/icons/github-svg.tsx b/components/icons/github-svg.tsx index be34fbc65..781f755cc 100644 --- a/components/icons/github-svg.tsx +++ b/components/icons/github-svg.tsx @@ -1,19 +1,19 @@ export default function GithubSvg() { - return ( - - ); + return ( + + ); } diff --git a/components/icons/info-svg.tsx b/components/icons/info-svg.tsx index 48a29df6b..5af6f3496 100644 --- a/components/icons/info-svg.tsx +++ b/components/icons/info-svg.tsx @@ -1,21 +1,21 @@ export default function Info() { - return ( - - ); + return ( + + ); } diff --git a/components/icons/preact-svg.tsx b/components/icons/preact-svg.tsx index 3cd5047f7..f9080b0dc 100644 --- a/components/icons/preact-svg.tsx +++ b/components/icons/preact-svg.tsx @@ -1,16 +1,16 @@ export default function PreactSvg() { - return ( - - Preact - - - ); + return ( + + Preact + + + ); } diff --git a/components/icons/repeat-svg.tsx b/components/icons/repeat-svg.tsx index b4bc5d4f3..4436ca9df 100644 --- a/components/icons/repeat-svg.tsx +++ b/components/icons/repeat-svg.tsx @@ -1,20 +1,20 @@ export default function RepeatSvg() { - return ( - - - - - - ); + return ( + + + + + + ); } diff --git a/components/icons/rocket-svg.tsx b/components/icons/rocket-svg.tsx index 3c6a8dfd5..d3f53cc81 100644 --- a/components/icons/rocket-svg.tsx +++ b/components/icons/rocket-svg.tsx @@ -1,21 +1,21 @@ export default function RocketSvg() { - return ( - - - - - - - ); + return ( + + + + + + + ); } diff --git a/components/icons/sales-svg.tsx b/components/icons/sales-svg.tsx index 3657f7ce8..61b920286 100644 --- a/components/icons/sales-svg.tsx +++ b/components/icons/sales-svg.tsx @@ -1,21 +1,21 @@ export default function SalesSvg() { - return ( - - - - - - - ); + return ( + + + + + + + ); } diff --git a/components/icons/scale-svg.tsx b/components/icons/scale-svg.tsx index c96ec7ded..929826d5b 100644 --- a/components/icons/scale-svg.tsx +++ b/components/icons/scale-svg.tsx @@ -1,25 +1,25 @@ export default function ScaleSvg() { - return ( - - - - - - - - - - - ); + return ( + + + + + + + + + + + ); } diff --git a/components/icons/seo-svg.tsx b/components/icons/seo-svg.tsx index c52caccda..8ee95830a 100644 --- a/components/icons/seo-svg.tsx +++ b/components/icons/seo-svg.tsx @@ -1,22 +1,22 @@ export default function SeoSvg() { - return ( - - - - - - - - ); + return ( + + + + + + + + ); } diff --git a/components/icons/setting-svg.tsx b/components/icons/setting-svg.tsx index 54af1ef08..e82780076 100644 --- a/components/icons/setting-svg.tsx +++ b/components/icons/setting-svg.tsx @@ -1,20 +1,20 @@ export default function SettingSvg() { - return ( - - - - - - ); + return ( + + + + + + ); } diff --git a/components/icons/tailwind-svg.tsx b/components/icons/tailwind-svg.tsx index db84a2485..325371b4a 100644 --- a/components/icons/tailwind-svg.tsx +++ b/components/icons/tailwind-svg.tsx @@ -1,15 +1,15 @@ export default function TailwindSvg() { - return ( - - ); + return ( + + ); } diff --git a/components/icons/ts-svg.tsx b/components/icons/ts-svg.tsx index 7581d50c0..3ddf6e4a0 100644 --- a/components/icons/ts-svg.tsx +++ b/components/icons/ts-svg.tsx @@ -1,19 +1,19 @@ export default function TypeScriptSvg() { - return ( - - - - - ); + return ( + + + + + ); } diff --git a/components/icons/user-account-svg.tsx b/components/icons/user-account-svg.tsx index 1bfd85e73..2f36b5609 100644 --- a/components/icons/user-account-svg.tsx +++ b/components/icons/user-account-svg.tsx @@ -1,21 +1,21 @@ export default function UserAccountSvg() { - return ( - - ); + return ( + + ); } diff --git a/components/icons/user-group-svg.tsx b/components/icons/user-group-svg.tsx index 64bf80f9d..782006879 100644 --- a/components/icons/user-group-svg.tsx +++ b/components/icons/user-group-svg.tsx @@ -1,20 +1,20 @@ export default function UserGroupsSvg() { - return ( - - ); + return ( + + ); } diff --git a/components/icons/ux-svg.tsx b/components/icons/ux-svg.tsx index da8cd2997..50a15e83e 100644 --- a/components/icons/ux-svg.tsx +++ b/components/icons/ux-svg.tsx @@ -1,22 +1,22 @@ export default function UxSvg() { - return ( - - - - - - - - ); + return ( + + + + + + + + ); } diff --git a/components/icons/warehouse-svg.tsx b/components/icons/warehouse-svg.tsx index 738e08c94..6c3b2cab0 100644 --- a/components/icons/warehouse-svg.tsx +++ b/components/icons/warehouse-svg.tsx @@ -1,21 +1,21 @@ export default function WareHouseSvg() { - return ( - - - - - - - ); + return ( + + + + + + + ); } diff --git a/components/icons/www-svg.tsx b/components/icons/www-svg.tsx index bffb54f57..65f5f6b80 100644 --- a/components/icons/www-svg.tsx +++ b/components/icons/www-svg.tsx @@ -1,27 +1,27 @@ export default function WwwSvg() { - return ( - - - - - - - - - - - - - ); + return ( + + + + + + + + + + + + + ); } diff --git a/components/project-box.tsx b/components/project-box.tsx index 6d24e6175..230d9c672 100644 --- a/components/project-box.tsx +++ b/components/project-box.tsx @@ -1,24 +1,24 @@ import { VNode } from "preact"; export default function ProjectBox( - props: { children: VNode[]; active?: boolean; url?: string }, + props: { children: VNode[]; active?: boolean; url?: string }, ) { - const ready = props.active ? "bg-green-700 cursor-pointer" : ""; + const ready = props.active ? "bg-green-700 cursor-pointer" : ""; - const onClickHandler = () => { - if (props.url) { - location.replace(props.url); - } - }; + const onClickHandler = () => { + if (props.url) { + location.replace(props.url); + } + }; - return ( -
-
- {props.children} -
-
- ); + return ( +
+
+ {props.children} +
+
+ ); } diff --git a/components/search.tsx b/components/search.tsx index 2df17c50e..902a3aa6d 100644 --- a/components/search.tsx +++ b/components/search.tsx @@ -1,33 +1,33 @@ export default function Search(props: { placeholder?: string }) { - return ( -
- - -
- ); + return ( +
+ + +
+ ); } diff --git a/core/map/mod.test.ts b/core/map/mod.test.ts index c32600284..8103f95df 100644 --- a/core/map/mod.test.ts +++ b/core/map/mod.test.ts @@ -2,204 +2,204 @@ import { assertEquals } from "../server/deps.ts"; import { Store } from "./mod.ts"; Deno.test("Store: set and get value without expiry", async () => { - const store = new Store(); - store.set("key1", 100); - const value = await store.get("key1"); - assertEquals(value, 100); + const store = new Store(); + store.set("key1", 100); + const value = await store.get("key1"); + assertEquals(value, 100); }); Deno.test("Store: set and get value with expiry", async () => { - const store = new Store(); - store.set("key2", 200, 1000); // Set with 1 second expiry - const value = await store.get("key2"); - assertEquals(value, 200); + const store = new Store(); + store.set("key2", 200, 1000); // Set with 1 second expiry + const value = await store.get("key2"); + assertEquals(value, 200); - // Wait for 1.1 seconds to let it expire - await new Promise((resolve) => setTimeout(resolve, 1100)); - const expiredValue = await store.get("key2"); - assertEquals(expiredValue, undefined); + // Wait for 1.1 seconds to let it expire + await new Promise((resolve) => setTimeout(resolve, 1100)); + const expiredValue = await store.get("key2"); + assertEquals(expiredValue, undefined); }); Deno.test("Store: has method returns true for existing key", async () => { - const store = new Store(); - store.set("key3", 300); - assertEquals(await store.has("key3"), true); + const store = new Store(); + store.set("key3", 300); + assertEquals(await store.has("key3"), true); }); Deno.test("Store: has method returns false for expired key", async () => { - const store = new Store(); - store.set("key4", 400, 500); // Set with 0.5 seconds expiry - await new Promise((resolve) => setTimeout(resolve, 600)); // Wait for it to expire - assertEquals(await store.has("key4"), false); + const store = new Store(); + store.set("key4", 400, 500); // Set with 0.5 seconds expiry + await new Promise((resolve) => setTimeout(resolve, 600)); // Wait for it to expire + assertEquals(await store.has("key4"), false); }); Deno.test("Store: delete method removes key", async () => { - const store = new Store(); - store.set("key5", 500); - assertEquals(store.delete("key5"), true); - assertEquals(await store.has("key5"), false); + const store = new Store(); + store.set("key5", 500); + assertEquals(store.delete("key5"), true); + assertEquals(await store.has("key5"), false); }); Deno.test("Store: clear method removes all keys", () => { - const store = new Store(); - store.set("key6", 600); - store.set("key7", 700); - store.clear(); - assertEquals(store.size(), 0); + const store = new Store(); + store.set("key6", 600); + store.set("key7", 700); + store.clear(); + assertEquals(store.size(), 0); }); Deno.test("Store: entries", () => { - const store = new Store(); - store.set("key6", 600); - const e = store.entries(); - const entries = Array.from(e); - assertEquals(entries, [ - ["key6", { value: 600, expiry: undefined }], - ]); + const store = new Store(); + store.set("key6", 600); + const e = store.entries(); + const entries = Array.from(e); + assertEquals(entries, [ + ["key6", { value: 600, expiry: undefined }], + ]); }); Deno.test("Store: values", () => { - const store = new Store(); - store.set("key6", 600); - const e = store.values(); - const entries = Array.from(e); - assertEquals(entries, [{ expiry: undefined, value: 600 }]); + const store = new Store(); + store.set("key6", 600); + const e = store.values(); + const entries = Array.from(e); + assertEquals(entries, [{ expiry: undefined, value: 600 }]); }); Deno.test("Store: keys", () => { - const store = new Store(); - store.set("key6", 600); - const e = store.keys(); - const entries = Array.from(e); - assertEquals(entries, ["key6"]); + const store = new Store(); + store.set("key6", 600); + const e = store.keys(); + const entries = Array.from(e); + assertEquals(entries, ["key6"]); }); function processMap(map: Store): Map { - const result = new Map(); - map.forEach((value, key) => { - result.set(key, value * 2); - }); - return result; + const result = new Map(); + map.forEach((value, key) => { + result.set(key, value * 2); + }); + return result; } Deno.test("Store: forEach", () => { - const inputMap = new Store(); - inputMap.set("one", 1); - inputMap.set("two", 2); - inputMap.set("three", 3); + const inputMap = new Store(); + inputMap.set("one", 1); + inputMap.set("two", 2); + inputMap.set("three", 3); - const expectedOutput = new Map(); - expectedOutput.set("one", 2); - expectedOutput.set("two", 4); - expectedOutput.set("three", 6); + const expectedOutput = new Map(); + expectedOutput.set("one", 2); + expectedOutput.set("two", 4); + expectedOutput.set("three", 6); - const result = processMap(inputMap); + const result = processMap(inputMap); - assertEquals(result, expectedOutput); + assertEquals(result, expectedOutput); }); Deno.test("Store: size method returns correct count", () => { - const store = new Store(); - store.set("key8", 800); - store.set("key9", 900); - assertEquals(store.size(), 2); + const store = new Store(); + store.set("key8", 800); + store.set("key9", 900); + assertEquals(store.size(), 2); }); Deno.test("Store: commit without options is error", async () => { - try { - const store = new Store(); - await store.set("key6", 600).commit(); - } catch (error) { - assertEquals(error, new Error("Options are needed to commit")); - } + try { + const store = new Store(); + await store.set("key6", 600).commit(); + } catch (error) { + assertEquals(error, new Error("Options are needed to commit")); + } }); Deno.test("Store: set with check", () => { - // try { - // const store = new Store(); - // store.set("key6", 600); - // store.check("key6").set("key6", 700); - // } catch (error) { - // assertEquals(error, new Error("Key key6 is already used")); - // } + // try { + // const store = new Store(); + // store.set("key6", 600); + // store.check("key6").set("key6", 700); + // } catch (error) { + // assertEquals(error, new Error("Key key6 is already used")); + // } }); const d = new Date(); const time = d.getTime(); const store = new Store({ - key: "modules/store/records.json", + key: "modules/store/records.json", }); Deno.test("Store: set and get value from github", async () => { - store.set("key1", time); - await store.commit(); - const g = await store.get("key1"); - assertEquals(g, time); + store.set("key1", time); + await store.commit(); + const g = await store.get("key1"); + assertEquals(g, time); }); Deno.test("Store: update value", async () => { - store.set("key1", 2); - const g = await store.get("key1"); - assertEquals(g, 2); + store.set("key1", 2); + const g = await store.get("key1"); + assertEquals(g, 2); }); Deno.test("Store: sync with github periodically", async () => { - const g = await store.get("key1"); - assertEquals(g, 2); + const g = await store.get("key1"); + assertEquals(g, 2); }); Deno.test("Store: destroy map without options", async () => { - try { - const s = new Store(); - await s.destroy(); - } catch (error) { - assertEquals(error, new Error("Options are needed to destroy")); - } + try { + const s = new Store(); + await s.destroy(); + } catch (error) { + assertEquals(error, new Error("Options are needed to destroy")); + } }); Deno.test("Store: destroy map", { - sanitizeResources: false, - sanitizeOps: false, - sanitizeExit: false, + sanitizeResources: false, + sanitizeOps: false, + sanitizeExit: false, }, async () => { - await store.destroy().commit(); - const g = await store.get("key1"); - // console.log(); - assertEquals(g, undefined); + await store.destroy().commit(); + const g = await store.get("key1"); + // console.log(); + assertEquals(g, undefined); }); const s = new Store({ - key: "test-key", + key: "test-key", }); await s.set("exist", true).commit(); Deno.test("Store: sync exist file", { - sanitizeResources: false, - sanitizeOps: false, - sanitizeExit: false, + sanitizeResources: false, + sanitizeOps: false, + sanitizeExit: false, }, async () => { - const newStore = new Store({ - key: "test-key", - }); - await newStore.get("exist"); - const intervalId = newStore.sync(); - // console.log(); - const r = await newStore.get("exist"); - assertEquals(r, true); - clearInterval(intervalId); + const newStore = new Store({ + key: "test-key", + }); + await newStore.get("exist"); + const intervalId = newStore.sync(); + // console.log(); + const r = await newStore.get("exist"); + assertEquals(r, true); + clearInterval(intervalId); }); Deno.test("Store: sync, same size after multiple commit", { - sanitizeResources: false, - sanitizeOps: false, - sanitizeExit: false, + sanitizeResources: false, + sanitizeOps: false, + sanitizeExit: false, }, async () => { - const newStore = new Store({ - key: "test-key", - }); - await Promise.all([ - newStore.set("user", "zaid").commit(), - newStore.set("city", "pare").commit(), - newStore.set("country", "indonesia").commit(), - ]); - assertEquals(newStore.size(), 3); + const newStore = new Store({ + key: "test-key", + }); + await Promise.all([ + newStore.set("user", "zaid").commit(), + newStore.set("city", "pare").commit(), + newStore.set("country", "indonesia").commit(), + ]); + assertEquals(newStore.size(), 3); }); diff --git a/core/map/mod.ts b/core/map/mod.ts index 5ce0dfbd1..244d2e888 100644 --- a/core/map/mod.ts +++ b/core/map/mod.ts @@ -2,205 +2,205 @@ import { kv } from "@app/utils/db.ts"; import { createTaskQueue } from "../../utils/queue.ts"; type StoreOptions = { - key: string; - namespace?: Array; + key: string; + namespace?: Array; } | null; export class Store { - private map: Map; - private options: StoreOptions; - private intervalId: number | null = null; - private taskQueue = createTaskQueue(); - - constructor(options: StoreOptions = null) { - this.map = new Map(); - this.options = options; - } - - /** - * Adds a new element with a specified key and value to the Map. If an element with the same key already exists, the element will be updated. - * @param key - * @param value - * @param ttl - */ - set(key: K, value: V, ttl?: number) { - const expiry = ttl ? Date.now() + ttl : undefined; - this.map.set(key, { value, expiry }); - return this; - } - - /** - * @param key - * @returns boolean indicating whether an element with the specified key exists or not. - */ - check(key: K) { - if (this.map.has(key)) { - throw new Error(`Key ${String(key)} is already used`); - } - - return this; - } - - /** - * Returns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map. - * @returns - Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned. - */ - async get(key: K) { - await this.syncMap(); - const entry = this.map.get(key); - if (entry) { - if (entry.expiry === undefined || Date.now() < entry.expiry) { - return entry.value; - } else { - this.map.delete(key); - } - } - return undefined; - } - - /** - * @param key - * @returns boolean indicating whether an element with the specified key exists or not. - */ - async has(key: K) { - await this.syncMap(); - const entry = this.map.get(key); - if (entry) { - if (entry.expiry === undefined || Date.now() < entry.expiry) { - return true; - } else { - this.map.delete(key); - } - } - return false; - } - - /** - * @param key - * @returns true if an element in the Map existed and has been removed, or false if the element does not exist. - */ - delete(key: K): boolean { - return this.map.delete(key); - } - - clear(): void { - this.map.clear(); - } - - /** - * @returns the number of elements in the Map. - */ - size(): number { - this.cleanUpExpiredEntries(); - return this.map.size; - } - - /** - * @returns an iterable of key, value pairs for every entry in the map. - */ - entries() { - this.cleanUpExpiredEntries(); - return this.map.entries(); - } - - /** - * @returns an iterable of values in the map - */ - values() { - this.cleanUpExpiredEntries(); - return this.map.values(); - } - - /** - * @returns an iterable of keys in the map - */ - keys() { - this.cleanUpExpiredEntries(); - return this.map.keys(); - } - - /** - * Executes a provided function once per each key/value pair in the Map, in insertion order. - * @param callback - */ - forEach(callback: (value: V, key: K) => void): void { - this.cleanUpExpiredEntries(); - this.map.forEach((entry, key) => { - callback(entry.value, key); - }); - } - - /** - * Save to github with queue - */ - commit = async () => { - return await this.taskQueue.process(this.commiting, this.options); - }; - - private commiting = async (options?: StoreOptions) => { - if (!options) return; - this.cleanUpExpiredEntries(); - - const key = options.namespace - ? [options.key, ...options.namespace] - : [options.key]; - return await kv.atomic() - .set(key, this.map) - .commit(); - }; - - /** - * Save the map to the repository periodically at intervals - * @param interval - * @returns intervalId - */ - sync(interval: number = 5000) { - if (this.intervalId) clearInterval(this.intervalId); - this.intervalId = setInterval(() => { - if (this.map.size === 0) return; - this.taskQueue.process(this.commiting, this.options); - }, interval); - return this.intervalId; - } - - /** - * Delete file from repository - */ - destroy() { - try { - if (this.intervalId) clearInterval(this.intervalId); - this.map.clear(); - return this; - } catch (error) { - throw error; - } - } - - public async syncMap() { - if (this.map.size === 0 && this.options) { - const key = this.options.namespace - ? [this.options.key, ...this.options.namespace] - : [this.options.key]; - const res = await kv.get(key); - - // deno-lint-ignore no-explicit-any - const map = res.value as Map; - if (!map) return false; - this.map = map; - this.cleanUpExpiredEntries(); - await this.commit(); - } - + private map: Map; + private options: StoreOptions; + private intervalId: number | null = null; + private taskQueue = createTaskQueue(); + + constructor(options: StoreOptions = null) { + this.map = new Map(); + this.options = options; + } + + /** + * Adds a new element with a specified key and value to the Map. If an element with the same key already exists, the element will be updated. + * @param key + * @param value + * @param ttl + */ + set(key: K, value: V, ttl?: number) { + const expiry = ttl ? Date.now() + ttl : undefined; + this.map.set(key, { value, expiry }); + return this; + } + + /** + * @param key + * @returns boolean indicating whether an element with the specified key exists or not. + */ + check(key: K) { + if (this.map.has(key)) { + throw new Error(`Key ${String(key)} is already used`); + } + + return this; + } + + /** + * Returns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map. + * @returns - Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned. + */ + async get(key: K) { + await this.syncMap(); + const entry = this.map.get(key); + if (entry) { + if (entry.expiry === undefined || Date.now() < entry.expiry) { + return entry.value; + } else { + this.map.delete(key); + } + } + return undefined; + } + + /** + * @param key + * @returns boolean indicating whether an element with the specified key exists or not. + */ + async has(key: K) { + await this.syncMap(); + const entry = this.map.get(key); + if (entry) { + if (entry.expiry === undefined || Date.now() < entry.expiry) { return true; - } - - private cleanUpExpiredEntries(): void { - let count = 0; - for (const [key, entry] of this.map.entries()) { - if (entry.expiry !== undefined && Date.now() >= entry.expiry) { - this.map.delete(key); - count++; - } - } - } + } else { + this.map.delete(key); + } + } + return false; + } + + /** + * @param key + * @returns true if an element in the Map existed and has been removed, or false if the element does not exist. + */ + delete(key: K): boolean { + return this.map.delete(key); + } + + clear(): void { + this.map.clear(); + } + + /** + * @returns the number of elements in the Map. + */ + size(): number { + this.cleanUpExpiredEntries(); + return this.map.size; + } + + /** + * @returns an iterable of key, value pairs for every entry in the map. + */ + entries() { + this.cleanUpExpiredEntries(); + return this.map.entries(); + } + + /** + * @returns an iterable of values in the map + */ + values() { + this.cleanUpExpiredEntries(); + return this.map.values(); + } + + /** + * @returns an iterable of keys in the map + */ + keys() { + this.cleanUpExpiredEntries(); + return this.map.keys(); + } + + /** + * Executes a provided function once per each key/value pair in the Map, in insertion order. + * @param callback + */ + forEach(callback: (value: V, key: K) => void): void { + this.cleanUpExpiredEntries(); + this.map.forEach((entry, key) => { + callback(entry.value, key); + }); + } + + /** + * Save to github with queue + */ + commit = async () => { + return await this.taskQueue.process(this.commiting, this.options); + }; + + private commiting = async (options?: StoreOptions) => { + if (!options) return; + this.cleanUpExpiredEntries(); + + const key = options.namespace + ? [options.key, ...options.namespace] + : [options.key]; + return await kv.atomic() + .set(key, this.map) + .commit(); + }; + + /** + * Save the map to the repository periodically at intervals + * @param interval + * @returns intervalId + */ + sync(interval: number = 5000) { + if (this.intervalId) clearInterval(this.intervalId); + this.intervalId = setInterval(() => { + if (this.map.size === 0) return; + this.taskQueue.process(this.commiting, this.options); + }, interval); + return this.intervalId; + } + + /** + * Delete file from repository + */ + destroy() { + try { + if (this.intervalId) clearInterval(this.intervalId); + this.map.clear(); + return this; + } catch (error) { + throw error; + } + } + + public async syncMap() { + if (this.map.size === 0 && this.options) { + const key = this.options.namespace + ? [this.options.key, ...this.options.namespace] + : [this.options.key]; + const res = await kv.get(key); + + // deno-lint-ignore no-explicit-any + const map = res.value as Map; + if (!map) return false; + this.map = map; + this.cleanUpExpiredEntries(); + await this.commit(); + } + + return true; + } + + private cleanUpExpiredEntries(): void { + let count = 0; + for (const [key, entry] of this.map.entries()) { + if (entry.expiry !== undefined && Date.now() >= entry.expiry) { + this.map.delete(key); + count++; + } + } + } } diff --git a/core/server/mod.ts b/core/server/mod.ts index 90888191f..08fc30e75 100644 --- a/core/server/mod.ts +++ b/core/server/mod.ts @@ -382,33 +382,31 @@ if (root) fetchProps(root); const url = new URL(req.url); let key = url.pathname; let page: Page = this.#routePage[key]; + if (!page) return []; let params: Record | undefined = undefined; - if (!page) { - const res = this.#getParamsPage(req, this.#routePage); - if (res) { - const [pg, prm] = res; - page = pg; - params = prm; - } + const res = this.#getParamsPage(req, this.#routePage); + if (res) { + const [pg, prm] = res; + page = pg; + params = prm; } - if (!page) return []; const ctx = this.serverOptions as Context; - ctx.render = (data: T, headers?: Headers) => { - const r = new Render(this); - key = key === "/" ? "" : key; - key = url.origin + "/__/props" + key; - return r.render(key, page, data, this.getNonce(), headers); - }; ctx.info = info; ctx.next = () => {}; ctx.url = new URL(req.url); ctx.server = this; - ctx.send = (data: T, status = 200, headers?: Headers) => { - return createResponse(data, status, headers); - }; ctx.kv = this.serverOptions["kv"]; ctx.options = this.serverOptions; ctx.stores = this.stores; + ctx.render = async (data: T, headers?: Headers) => { + const r = new Render(this); + key = key === "/" ? "" : key; + key = url.origin + "/__/props" + key; + return await r.render(key, page, data, this.getNonce(), headers); + }; + ctx.send = (data: T, status = 200, headers?: Headers) => { + return createResponse(data, status, headers); + }; return [page, ctx, params, url]; }; @@ -557,10 +555,10 @@ if (root) fetchProps(root); return await page.handler( this.#transformRequest(req, pageParams, pageUrl), pageCtx, - ) as Response; + ) as Promise; } - return this.#handleStaticFile(req); + return await this.#handleStaticFile(req); }; }; diff --git a/docs/app-middleware.md b/docs/app-middleware.md index 2e187423e..d939a58c7 100644 --- a/docs/app-middleware.md +++ b/docs/app-middleware.md @@ -12,20 +12,20 @@ import fastro, { Context, HttpRequest } from "https://fastro.deno.dev/mod.ts"; const f = new fastro(); const m = (req: HttpRequest, ctx: Context) => { - req.ok = true; - ctx.msg = "hello"; - ctx.getTitle = () => "oke"; - return ctx.next(); + req.ok = true; + ctx.msg = "hello"; + ctx.getTitle = () => "oke"; + return ctx.next(); }; f.use(m); f.get("/", (req: HttpRequest, ctx: Context) => { - return { - ok: req.ok, - msg: ctx.msg, - title: ctx.getTitle(), - }; + return { + ok: req.ok, + msg: ctx.msg, + title: ctx.getTitle(), + }; }); await f.serve(); diff --git a/docs/benchmarks.md b/docs/benchmarks.md index deef5b533..ef17099bf 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -4,13 +4,18 @@ description: This is the final output of an internal benchmark run in github act image: https://fastro.dev/fastro.png --- -This is the final output of an internal benchmark run in [github action](https://github.com/fastrodev/fastro/actions) on `10/5/2024, 10:37:19 PM`. It consists of several simple applications for [specific purpose](https://github.com/fastrodev/fastro/blob/main/deno.json). Each is then accessed by the [OHA](https://github.com/hatoo/oha) within 10s. The results are then sorted by the fastest. +This is the final output of an internal benchmark run in +[github action](https://github.com/fastrodev/fastro/actions) on +`10/5/2024, 10:37:19 PM`. It consists of several simple applications for +[specific purpose](https://github.com/fastrodev/fastro/blob/main/deno.json). +Each is then accessed by the [OHA](https://github.com/hatoo/oha) within 10s. The +results are then sorted by the fastest. -You can find the benchmark script in this code: [run.ts](https://github.com/fastrodev/fastro/blob/main/bench/run.ts) +You can find the benchmark script in this code: +[run.ts](https://github.com/fastrodev/fastro/blob/main/bench/run.ts) ## Benchmark results - | module | rps | % | oha cmd | | :--------------------------------------------------------------------------------------------------- | ----: | ---: | :----------------------------------------------------------------- | | [route_middleware](https://github.com/fastrodev/fastro/blob/main/examples/route_middleware.ts) | 60977 | 100% | `oha -j --no-tui -z 10s http://localhost:8000` | @@ -35,4 +40,4 @@ You can find the benchmark script in this code: [run.ts](https://github.com/fast | [markdown_middleware](https://github.com/fastrodev/fastro/blob/main/examples/markdown_middleware.ts) | 9937 | 16% | `oha -j --no-tui -z 10s http://localhost:8000/blog/hello` | | [static_file_string](https://github.com/fastrodev/fastro/blob/main/examples/static_file_string.ts) | 8819 | 14% | `oha -j --no-tui -z 10s http://localhost:8000/static/tailwind.css` | | [static_file_image](https://github.com/fastrodev/fastro/blob/main/examples/static_file_image.ts) | 8358 | 14% | `oha -j --no-tui -z 10s http://localhost:8000/static/favicon.ico` | -| [deno_postgres](https://github.com/fastrodev/fastro/blob/main/examples/deno_postgres.ts) | 3786 | 6% | `oha -j --no-tui -z 10s http://localhost:8000` | \ No newline at end of file +| [deno_postgres](https://github.com/fastrodev/fastro/blob/main/examples/deno_postgres.ts) | 3786 | 6% | `oha -j --no-tui -z 10s http://localhost:8000` | diff --git a/docs/fn-component.md b/docs/fn-component.md index b1cafd9ea..2705feb61 100644 --- a/docs/fn-component.md +++ b/docs/fn-component.md @@ -10,7 +10,7 @@ This component will build JS script. ```tsx export const hello = () => { - return

Hello

; + return

Hello

; }; ``` @@ -18,6 +18,6 @@ You can also pass a props there. ```tsx export const hello = (props: { name: string }) => { - return

Hello {props.name}

; + return

Hello {props.name}

; }; ``` diff --git a/docs/hello-context.md b/docs/hello-context.md index c9d070def..3dbdac644 100644 --- a/docs/hello-context.md +++ b/docs/hello-context.md @@ -12,10 +12,10 @@ import fastro, { Context, HttpRequest } from "https://fastro.deno.dev/mod.ts"; const f = new fastro(); f.get( - "/", - (_req: HttpRequest, ctx: Context) => { - return ctx.send("Helo world", 200); - }, + "/", + (_req: HttpRequest, ctx: Context) => { + return ctx.send("Helo world", 200); + }, ); await f.serve(); diff --git a/docs/route-middleware.md b/docs/route-middleware.md index 6a3c79c68..2dbea38f4 100644 --- a/docs/route-middleware.md +++ b/docs/route-middleware.md @@ -12,30 +12,30 @@ import fastro, { Context, HttpRequest } from "https://fastro.deno.dev/mod.ts"; const f = new fastro(); const m1 = (req: HttpRequest, ctx: Context) => { - // console.log("middleware 1"); - req.m1 = "middleware1"; - return ctx.next(); + // console.log("middleware 1"); + req.m1 = "middleware1"; + return ctx.next(); }; const m2 = (_req: HttpRequest, ctx: Context) => { - // console.log("middleware 2"); - return ctx.next(); + // console.log("middleware 2"); + return ctx.next(); }; const m3 = (_req: HttpRequest, ctx: Context) => { - // console.log("middleware 3"); - return ctx.next(); + // console.log("middleware 3"); + return ctx.next(); }; const m4 = (_req: HttpRequest, ctx: Context) => { - // console.log("middleware 4"); - return ctx.next(); + // console.log("middleware 4"); + return ctx.next(); }; const handler = (req: HttpRequest) => { - // `middleware1` for get - // `undefined` for post - return req.m1; + // `middleware1` for get + // `undefined` for post + return req.m1; }; f.get("/", m1, m2, m3, handler); diff --git a/docs/ssr.md b/docs/ssr.md index 61cf79537..3f79d4653 100644 --- a/docs/ssr.md +++ b/docs/ssr.md @@ -20,7 +20,7 @@ Create component `modules/web/hello.tsx` ```tsx export const hello = () => { - return

Hello

; + return

Hello

; }; ``` @@ -30,18 +30,18 @@ Create layout `modules/web/app.layout.tsx` import { LayoutProps } from "https://fastro.deno.dev/http/server/types.ts"; export function layout( - { data, children }: LayoutProps<{ title: string }>, + { data, children }: LayoutProps<{ title: string }>, ) { - return ( - - - {data.title} - - - {children} - - - ); + return ( + + + {data.title} + + + {children} + + + ); } ``` @@ -55,10 +55,10 @@ import hello from "$fastro/modules/web/hello.page.tsx"; const f = new fastro(); f.page("/", { - component: hello, - layout, - handler: (_req, ctx) => ctx.render({ title: "Hello world" }), - folder: "modules/web", + component: hello, + layout, + handler: (_req, ctx) => ctx.render({ title: "Hello world" }), + folder: "modules/web", }); f.serve(); diff --git a/docs/store.md b/docs/store.md index 535f191b7..d349f570a 100644 --- a/docs/store.md +++ b/docs/store.md @@ -15,48 +15,48 @@ const f = new fastro(); f.store.set("hello", "hello world"); f.post( - "/", - (_req: HttpRequest, ctx: Context) => { - // update default value - ctx.store.set("hello", "hello world v2"); - return ctx.send("Helo world", 200); - }, + "/", + (_req: HttpRequest, ctx: Context) => { + // update default value + ctx.store.set("hello", "hello world v2"); + return ctx.send("Helo world", 200); + }, ); f.post( - "/ttl", - (_req: HttpRequest, ctx: Context) => { - // update default value with TTL - ctx.store.set("hello", "world", 1000); - return ctx.send("ttl", 200); - }, + "/ttl", + (_req: HttpRequest, ctx: Context) => { + // update default value with TTL + ctx.store.set("hello", "world", 1000); + return ctx.send("ttl", 200); + }, ); f.post( - "/commit", - async (_req: HttpRequest, ctx: Context) => { - // save store to github - await ctx.store.commit(); - return ctx.send("commit", 200); - }, + "/commit", + async (_req: HttpRequest, ctx: Context) => { + // save store to github + await ctx.store.commit(); + return ctx.send("commit", 200); + }, ); f.get( - "/", - (_req: HttpRequest, ctx: Context) => { - // get the value - const res = ctx.store.get("hello"); - return Response.json({ value: res }); - }, + "/", + (_req: HttpRequest, ctx: Context) => { + // get the value + const res = ctx.store.get("hello"); + return Response.json({ value: res }); + }, ); f.post( - "/destroy", - async (_req: HttpRequest, ctx: Context) => { - // destroy file - await ctx.store.destroy(); - return ctx.send("destroy", 200); - }, + "/destroy", + async (_req: HttpRequest, ctx: Context) => { + // destroy file + await ctx.store.destroy(); + return ctx.send("destroy", 200); + }, ); await f.serve(); diff --git a/docs/tsx.md b/docs/tsx.md index 06c9c4ec9..cb5dfae79 100644 --- a/docs/tsx.md +++ b/docs/tsx.md @@ -14,10 +14,10 @@ import fastro, { Context, HttpRequest } from "https://fastro.deno.dev/mod.ts"; const f = new fastro(); f.get( - "/", - (_req: HttpRequest, ctx: Context) => { - return ctx.render(

Hello, jsx!

); - }, + "/", + (_req: HttpRequest, ctx: Context) => { + return ctx.render(

Hello, jsx!

); + }, ); await f.serve(); diff --git a/docs/url-params.md b/docs/url-params.md index e6e40db32..e317a605f 100644 --- a/docs/url-params.md +++ b/docs/url-params.md @@ -12,8 +12,8 @@ import fastro, { HttpRequest } from "https://fastro.deno.dev/mod.ts"; const f = new fastro(); f.get("/:user", (req: HttpRequest) => { - const data = { user: req.params?.user }; - return Response.json(data); + const data = { user: req.params?.user }; + return Response.json(data); }); await f.serve(); diff --git a/docs/url-query.md b/docs/url-query.md index 7a5212554..45544d94b 100644 --- a/docs/url-query.md +++ b/docs/url-query.md @@ -12,8 +12,8 @@ import fastro, { HttpRequest } from "https://fastro.deno.dev/mod.ts"; const f = new fastro(); f.get("/:user", (req: HttpRequest) => { - const data = { user: req.params?.user, title: req.query?.title }; - return Response.json(data); + const data = { user: req.params?.user, title: req.query?.title }; + return Response.json(data); }); await f.serve(); diff --git a/examples/store.ts b/examples/store.ts index 97b7dde85..afd199fb8 100644 --- a/examples/store.ts +++ b/examples/store.ts @@ -7,47 +7,47 @@ f.store.set("hello", "hello world"); // update default value f.post( - "/", - (_req: HttpRequest, ctx: Context) => { - ctx.store.set("hello", "hello world v2"); - return ctx.send("Helo world", 200); - }, + "/", + (_req: HttpRequest, ctx: Context) => { + ctx.store.set("hello", "hello world v2"); + return ctx.send("Helo world", 200); + }, ); // update default value with TTL f.post( - "/ttl", - (_req: HttpRequest, ctx: Context) => { - ctx.store.set("hello", "world", 1000); - return ctx.send("ttl", 200); - }, + "/ttl", + (_req: HttpRequest, ctx: Context) => { + ctx.store.set("hello", "world", 1000); + return ctx.send("ttl", 200); + }, ); // save store to github f.post( - "/commit", - async (_req: HttpRequest, ctx: Context) => { - await ctx.store.commit(); - return ctx.send("commit", 200); - }, + "/commit", + async (_req: HttpRequest, ctx: Context) => { + await ctx.store.commit(); + return ctx.send("commit", 200); + }, ); // destroy file f.post( - "/destroy", - async (_req: HttpRequest, ctx: Context) => { - await ctx.store.destroy(); - return ctx.send("destroy", 200); - }, + "/destroy", + async (_req: HttpRequest, ctx: Context) => { + await ctx.store.destroy(); + return ctx.send("destroy", 200); + }, ); // get the value f.get( - "/", - (_req: HttpRequest, ctx: Context) => { - const res = ctx.store.get("hello"); - return Response.json({ value: res }); - }, + "/", + (_req: HttpRequest, ctx: Context) => { + const res = ctx.store.get("hello"); + return Response.json({ value: res }); + }, ); await f.serve(); diff --git a/middleware/github/mod.ts b/middleware/github/mod.ts index d158aff0f..8ae9fb300 100644 --- a/middleware/github/mod.ts +++ b/middleware/github/mod.ts @@ -1,21 +1,21 @@ import { Context, HttpRequest } from "@app/mod.ts"; export default async function github(_req: HttpRequest, ctx: Context) { - if ( - ctx.url.pathname.endsWith(".ts") || - ctx.url.pathname.endsWith(".tsx") - ) { - const version = ctx.url.pathname.startsWith("/v") - ? "" - : ctx.url.pathname.startsWith("/canary") - ? "/canary" - : "/main"; + if ( + ctx.url.pathname.endsWith(".ts") || + ctx.url.pathname.endsWith(".tsx") + ) { + const version = ctx.url.pathname.startsWith("/v") + ? "" + : ctx.url.pathname.startsWith("/canary") + ? "/canary" + : "/main"; - const path = - `https://raw.githubusercontent.com/fastrodev/fastro${version}${ctx.url.pathname}`; - const res = await fetch(path); - const content = await res.text(); - return new Response(content); - } - return ctx.next(); + const path = + `https://raw.githubusercontent.com/fastrodev/fastro${version}${ctx.url.pathname}`; + const res = await fetch(path); + const content = await res.text(); + return new Response(content); + } + return ctx.next(); } diff --git a/modules/admin/admin.handler.tsx b/modules/admin/admin.handler.tsx index d386cd472..a7004f07b 100644 --- a/modules/admin/admin.handler.tsx +++ b/modules/admin/admin.handler.tsx @@ -1,5 +1,5 @@ import { Context, HttpRequest } from "@app/mod.ts"; export default function adminHandler(_req: HttpRequest, ctx: Context) { - return ctx.render({ title: "User Administration" }); + return ctx.render({ title: "User Administration" }); } diff --git a/modules/admin/admin.layout.tsx b/modules/admin/admin.layout.tsx index a8dbf22f1..e9db5ca4b 100644 --- a/modules/admin/admin.layout.tsx +++ b/modules/admin/admin.layout.tsx @@ -1,25 +1,25 @@ import { LayoutProps } from "@app/mod.ts"; export default function adminLayout( - { data, children }: LayoutProps< - { title: string; description: string; image: string } - >, + { data, children }: LayoutProps< + { title: string; description: string; image: string } + >, ) { - return ( - - - - - - {data.title} | Fastro - - - - {children} - - - ); + return ( + + + + + + {data.title} | Fastro + + + + {children} + + + ); } diff --git a/modules/admin/admin.page.tsx b/modules/admin/admin.page.tsx index 0c20d0458..1aa79c107 100644 --- a/modules/admin/admin.page.tsx +++ b/modules/admin/admin.page.tsx @@ -8,115 +8,115 @@ import UserGroupsSvg from "@app/components/icons/user-group-svg.tsx"; import Info from "@app/components/icons/info-svg.tsx"; function UserItem( - props: { username: string; groupname?: string; group?: boolean }, + props: { username: string; groupname?: string; group?: boolean }, ) { - return ( -
-
- {props.group && } - {!props.group && } -
- {props.username} - {props.groupname && ( - - {props.groupname} - - )} -
-
-
- -
+ return ( +
+
+ {props.group && } + {!props.group && } +
+ {props.username} + {props.groupname && ( + + {props.groupname} + + )}
- ); +
+
+ +
+
+ ); } export default function Admin({ data }: PageProps< - { - user: string; - title: string; - description: string; - } + { + user: string; + title: string; + description: string; + } >) { - console.log("data ==>", data); - return ( -
-
-
- {/* user */} -
-
- - -
-
-
- - - - - - -
-
-
+ console.log("data ==>", data); + return ( +
+
+
+ {/* user */} +
+
+ + +
+
+
+ + + + + + +
+
+
- {/* groups */} -
-
- - -
-
-
- - - - - - - -
-
-
-
-
+ {/* groups */} +
+
+ + +
+
+
+ + + + + + + +
+
- ); +
+
+
+ ); } diff --git a/modules/admin/mod.ts b/modules/admin/mod.ts index 456d945db..9f4760db2 100644 --- a/modules/admin/mod.ts +++ b/modules/admin/mod.ts @@ -4,11 +4,11 @@ import adminLayout from "./admin.layout.tsx"; import adminHandler from "./admin.handler.tsx"; export default function adminModule(s: Fastro) { - s.page("/mod/admin", { - component: adminPage, - layout: adminLayout, - handler: adminHandler, - folder: "modules/admin", - }); - return s; + s.page("/mod/admin", { + component: adminPage, + layout: adminLayout, + handler: adminHandler, + folder: "modules/admin", + }); + return s; } diff --git a/modules/blog/blog.json b/modules/blog/blog.json index c8c87fcbd..51798a77d 100644 --- a/modules/blog/blog.json +++ b/modules/blog/blog.json @@ -1,47 +1,47 @@ [ - { - "title": "Using Queues to Avoid Race Conditions", - "url": "/blog/queue", - "date": "10/05/2024" - }, - { - "title": "Store: Key and Value Map with TTL", - "url": "/blog/store", - "date": "09/17/2024" - }, - { - "title": "Collaboration and Profit Sharing", - "url": "/blog/collaboration", - "date": "06/18/2024" - }, - { - "title": "Set up Tailwind on Deno", - "url": "/blog/tailwind", - "date": "01/26/2024" - }, - { - "title": "Deno KV OAuth Implementation", - "url": "/blog/oauth", - "date": "11/15/2023" - }, - { - "title": "renderToReadableStream", - "url": "/blog/render_to_readable_stream", - "date": "10/26/2023" - }, - { - "title": "React", - "url": "/blog/react", - "date": "10/22/2023" - }, - { - "title": "Preact", - "url": "/blog/preact_and_encrypted_props", - "date": "08/16/2023" - }, - { - "title": "Hello", - "url": "/blog/hello", - "date": "11/15/2023" - } + { + "title": "Using Queues to Avoid Race Conditions", + "url": "/blog/queue", + "date": "10/05/2024" + }, + { + "title": "Store: Key and Value Map with TTL", + "url": "/blog/store", + "date": "09/17/2024" + }, + { + "title": "Collaboration and Profit Sharing", + "url": "/blog/collaboration", + "date": "06/18/2024" + }, + { + "title": "Set up Tailwind on Deno", + "url": "/blog/tailwind", + "date": "01/26/2024" + }, + { + "title": "Deno KV OAuth Implementation", + "url": "/blog/oauth", + "date": "11/15/2023" + }, + { + "title": "renderToReadableStream", + "url": "/blog/render_to_readable_stream", + "date": "10/26/2023" + }, + { + "title": "React", + "url": "/blog/react", + "date": "10/22/2023" + }, + { + "title": "Preact", + "url": "/blog/preact_and_encrypted_props", + "date": "08/16/2023" + }, + { + "title": "Hello", + "url": "/blog/hello", + "date": "11/15/2023" + } ] diff --git a/modules/blog/mod.ts b/modules/blog/mod.ts index 8e13b82e6..4d29134b7 100644 --- a/modules/blog/mod.ts +++ b/modules/blog/mod.ts @@ -5,23 +5,23 @@ import { getSession } from "@app/utils/session.ts"; import posts from "@app/modules/blog/blog.json" with { type: "json" }; export default function (s: Fastro) { - s.page("/blog", { - component: tocApp, - layout: tocLayout, - folder: "modules/toc", - handler: async (req, ctx) => { - const ses = await getSession(req, ctx); - return ctx.render({ - isLogin: ses?.isLogin, - avatar_url: ses?.avatar_url, - html_url: ses?.html_url, - title: "Blog", - description: "Blog", - destination: "/blog", - posts, - }); - }, - }); + s.page("/blog", { + component: tocApp, + layout: tocLayout, + folder: "modules/toc", + handler: async (req, ctx) => { + const ses = await getSession(req, ctx); + return ctx.render({ + isLogin: ses?.isLogin, + avatar_url: ses?.avatar_url, + html_url: ses?.html_url, + title: "Blog", + description: "Blog", + destination: "/blog", + posts, + }); + }, + }); - return s; + return s; } diff --git a/modules/docs/docs.json b/modules/docs/docs.json index 426bdc994..bcbcb5929 100644 --- a/modules/docs/docs.json +++ b/modules/docs/docs.json @@ -1,110 +1,110 @@ [ - { - "title": "Get Started", - "url": "/docs/start" - }, - { - "title": "App Structure", - "url": "/docs/structure" - }, - { - "title": "Hello World", - "url": "/docs/hello" - }, - { - "title": "Hello World Context", - "url": "/docs/hello-context" - }, - { - "title": "Hello JSON", - "url": "/docs/json" - }, - { - "title": "Routing", - "url": "/docs/route" - }, - { - "title": "URL Params", - "url": "/docs/url-params" - }, - { - "title": "Query Params", - "url": "/docs/url-query" - }, - { - "title": "App Middleware", - "url": "/docs/app-middleware" - }, - { - "title": "Route Middleware", - "url": "/docs/route-middleware" - }, - { - "title": "Markdown Middleware", - "url": "/docs/markdown" - }, - { - "title": "Tailwind Middleware", - "url": "/docs/tailwind" - }, - { - "title": "Static File", - "url": "/docs/static" - }, - { - "title": "Hello TSX", - "url": "/docs/tsx" - }, - { - "title": "TSX Component", - "url": "/docs/tsx-component" - }, - { - "title": "Function Component", - "url": "/docs/fn-component" - }, - { - "title": "Server Side Rendering", - "url": "/docs/ssr" - }, - { - "title": "OAuth", - "url": "/docs/oauth" - }, - { - "title": "MySQL", - "url": "/docs/mysql" - }, - { - "title": "Postgres", - "url": "/docs/postgres" - }, - { - "title": "Redis", - "url": "/docs/redis" - }, - { - "title": "Mongo", - "url": "/docs/mongo" - }, - { - "title": "Deno KV", - "url": "/docs/kv" - }, - { - "title": "Grouping", - "url": "/docs/group" - }, - { - "title": "Deployment", - "url": "/docs/deploy" - }, - { - "title": "Store", - "url": "/docs/store" - }, - { - "title": "Benchmarks", - "url": "/docs/benchmarks" - } + { + "title": "Get Started", + "url": "/docs/start" + }, + { + "title": "App Structure", + "url": "/docs/structure" + }, + { + "title": "Hello World", + "url": "/docs/hello" + }, + { + "title": "Hello World Context", + "url": "/docs/hello-context" + }, + { + "title": "Hello JSON", + "url": "/docs/json" + }, + { + "title": "Routing", + "url": "/docs/route" + }, + { + "title": "URL Params", + "url": "/docs/url-params" + }, + { + "title": "Query Params", + "url": "/docs/url-query" + }, + { + "title": "App Middleware", + "url": "/docs/app-middleware" + }, + { + "title": "Route Middleware", + "url": "/docs/route-middleware" + }, + { + "title": "Markdown Middleware", + "url": "/docs/markdown" + }, + { + "title": "Tailwind Middleware", + "url": "/docs/tailwind" + }, + { + "title": "Static File", + "url": "/docs/static" + }, + { + "title": "Hello TSX", + "url": "/docs/tsx" + }, + { + "title": "TSX Component", + "url": "/docs/tsx-component" + }, + { + "title": "Function Component", + "url": "/docs/fn-component" + }, + { + "title": "Server Side Rendering", + "url": "/docs/ssr" + }, + { + "title": "OAuth", + "url": "/docs/oauth" + }, + { + "title": "MySQL", + "url": "/docs/mysql" + }, + { + "title": "Postgres", + "url": "/docs/postgres" + }, + { + "title": "Redis", + "url": "/docs/redis" + }, + { + "title": "Mongo", + "url": "/docs/mongo" + }, + { + "title": "Deno KV", + "url": "/docs/kv" + }, + { + "title": "Grouping", + "url": "/docs/group" + }, + { + "title": "Deployment", + "url": "/docs/deploy" + }, + { + "title": "Store", + "url": "/docs/store" + }, + { + "title": "Benchmarks", + "url": "/docs/benchmarks" + } ] diff --git a/modules/docs/mod.ts b/modules/docs/mod.ts index db7087648..c369e5c46 100644 --- a/modules/docs/mod.ts +++ b/modules/docs/mod.ts @@ -5,23 +5,23 @@ import { getSession } from "@app/utils/session.ts"; import posts from "@app/modules/docs/docs.json" with { type: "json" }; export default function (s: Fastro) { - s.page("/docs", { - component: tocApp, - layout: tocLayout, - folder: "modules/toc", - handler: async (req, ctx) => { - const ses = await getSession(req, ctx); - return ctx.render({ - isLogin: ses?.isLogin, - avatar_url: ses?.avatar_url, - html_url: ses?.html_url, - title: "Docs", - description: "Docs", - destination: "/docs", - posts, - }); - }, - }); + s.page("/docs", { + component: tocApp, + layout: tocLayout, + folder: "modules/toc", + handler: async (req, ctx) => { + const ses = await getSession(req, ctx); + return ctx.render({ + isLogin: ses?.isLogin, + avatar_url: ses?.avatar_url, + html_url: ses?.html_url, + title: "Docs", + description: "Docs", + destination: "/docs", + posts, + }); + }, + }); - return s; + return s; } diff --git a/modules/group/group.service.test.ts b/modules/group/group.service.test.ts index d250c8c67..f4c41f2c4 100644 --- a/modules/group/group.service.test.ts +++ b/modules/group/group.service.test.ts @@ -1,11 +1,11 @@ import { assertEquals } from "@app/core/server/deps.ts"; import { - createGroup, - deleteGroup, - getGroup, - listGroups, - listGroupsByName, - updateGroup, + createGroup, + deleteGroup, + getGroup, + listGroups, + listGroupsByName, + updateGroup, } from "@app/modules/group/group.service.ts"; import { collectValues, reset } from "@app/utils/db.ts"; import { GroupType } from "@app/modules/group/group.type.ts"; @@ -14,70 +14,70 @@ await reset(); let group: GroupType; Deno.test({ - name: "createUserGroup", - async fn() { - const res = await createGroup({ name: "admin" }); - group = res; - assertEquals(res.name, "admin"); - }, + name: "createUserGroup", + async fn() { + const res = await createGroup({ name: "admin" }); + group = res; + assertEquals(res.name, "admin"); + }, }); Deno.test({ - name: "getGroup", - async fn() { - if (!group.groupId) return; - const res = await getGroup(group.groupId); - assertEquals(res?.name, "admin"); - }, + name: "getGroup", + async fn() { + if (!group.groupId) return; + const res = await getGroup(group.groupId); + assertEquals(res?.name, "admin"); + }, }); Deno.test({ - name: "updateGroup", - async fn() { - if (!group.groupId) return; - group.name = "sales"; - const res = await updateGroup(group); - assertEquals(res?.name, "sales"); - }, + name: "updateGroup", + async fn() { + if (!group.groupId) return; + group.name = "sales"; + const res = await updateGroup(group); + assertEquals(res?.name, "sales"); + }, }); Deno.test({ - name: "listGroups", - async fn() { - const res = await collectValues(listGroups()); - assertEquals(res.length, 1); - }, + name: "listGroups", + async fn() { + const res = await collectValues(listGroups()); + assertEquals(res.length, 1); + }, }); Deno.test({ - name: "listGroupsByName", - async fn() { - const res = await collectValues(listGroupsByName()); - assertEquals(res.length, 1); - }, + name: "listGroupsByName", + async fn() { + const res = await collectValues(listGroupsByName()); + assertEquals(res.length, 1); + }, }); Deno.test({ - name: "listGroupsByName with name", - async fn() { - const res = await collectValues(listGroupsByName("sales")); - assertEquals(res.length, 1); - }, + name: "listGroupsByName with name", + async fn() { + const res = await collectValues(listGroupsByName("sales")); + assertEquals(res.length, 1); + }, }); Deno.test({ - name: "listGroupsByName with wrong name", - async fn() { - const res = await collectValues(listGroupsByName("admin")); - assertEquals(res.length, 0); - }, + name: "listGroupsByName with wrong name", + async fn() { + const res = await collectValues(listGroupsByName("admin")); + assertEquals(res.length, 0); + }, }); Deno.test({ - name: "deleteGroup", - async fn() { - if (!group.groupId) throw new Error("group.id must be defined"); - const res = await deleteGroup(group.groupId); - assertEquals(res.ok, true); - }, + name: "deleteGroup", + async fn() { + if (!group.groupId) throw new Error("group.id must be defined"); + const res = await deleteGroup(group.groupId); + assertEquals(res.ok, true); + }, }); diff --git a/modules/group/group.service.ts b/modules/group/group.service.ts index c8a78acf3..0014fb5c7 100644 --- a/modules/group/group.service.ts +++ b/modules/group/group.service.ts @@ -4,83 +4,83 @@ import { ulid } from "jsr:@std/ulid"; import { combineObjects } from "@app/utils/general.ts"; export async function createGroup(group: GroupArgType) { - const groupId = ulid(); - const newGroup = { - groupId, - active: true, - }; - const g = combineObjects(newGroup, group) as GroupType; - const primaryKey = ["groups", groupId]; - const groupByNameKey = ["groups_by_name", group.name, groupId]; + const groupId = ulid(); + const newGroup = { + groupId, + active: true, + }; + const g = combineObjects(newGroup, group) as GroupType; + const primaryKey = ["groups", groupId]; + const groupByNameKey = ["groups_by_name", group.name, groupId]; - const atomicOp = kv.atomic(); - atomicOp - .check({ key: primaryKey, versionstamp: null }) - .set(primaryKey, g) - .set(groupByNameKey, g); + const atomicOp = kv.atomic(); + atomicOp + .check({ key: primaryKey, versionstamp: null }) + .set(primaryKey, g) + .set(groupByNameKey, g); - const res = await atomicOp.commit(); - if (!res.ok) throw new Error("Failed to create group"); - return g; + const res = await atomicOp.commit(); + if (!res.ok) throw new Error("Failed to create group"); + return g; } export async function updateGroup(group: GroupType) { - if (!group.groupId) throw new Error("group.id must not be null"); + if (!group.groupId) throw new Error("group.id must not be null"); - const atomicOp = kv.atomic(); - const groupKey = ["groups", group.groupId]; + const atomicOp = kv.atomic(); + const groupKey = ["groups", group.groupId]; - const ug = await kv.get(groupKey); - if (ug.value?.groupId) { - atomicOp.check(ug) - .delete(["groups_by_name", ug.value.name, ug.value.groupId]) - .set(["groups_by_name", group.name, group.groupId], group); - } + const ug = await kv.get(groupKey); + if (ug.value?.groupId) { + atomicOp.check(ug) + .delete(["groups_by_name", ug.value.name, ug.value.groupId]) + .set(["groups_by_name", group.name, group.groupId], group); + } - atomicOp.set(groupKey, group); + atomicOp.set(groupKey, group); - const res = await atomicOp.commit(); - if (!res.ok) throw new Error("Failed to create group"); + const res = await atomicOp.commit(); + if (!res.ok) throw new Error("Failed to create group"); - const g = await kv.get(groupKey); - return g.value; + const g = await kv.get(groupKey); + return g.value; } export async function getGroup(groupId: string): Promise { - const res = await kv.get(["groups", groupId]); - return res.value; + const res = await kv.get(["groups", groupId]); + return res.value; } export function listGroups( - options?: Deno.KvListOptions, + options?: Deno.KvListOptions, ) { - return kv.list({ prefix: ["groups"] }, options); + return kv.list({ prefix: ["groups"] }, options); } export function listGroupsByName( - name?: string, - options?: Deno.KvListOptions, + name?: string, + options?: Deno.KvListOptions, ) { - if (name) { - return kv.list( - { prefix: ["groups_by_name", name] }, - options, - ); - } - return kv.list({ prefix: ["groups_by_name"] }, options); + if (name) { + return kv.list( + { prefix: ["groups_by_name", name] }, + options, + ); + } + return kv.list({ prefix: ["groups_by_name"] }, options); } export async function deleteGroup(id: string) { - let res = { ok: false }; - while (!res.ok) { - const getRes = await kv.get(["groups", id]); - if (getRes && getRes.value) { - res = await kv.atomic() - .check(getRes) - .delete(["groups", id]) - .delete(["groups_by_name", getRes.value.name, id]) - .commit(); - } + let res = { ok: false }; + while (!res.ok) { + const getRes = await kv.get(["groups", id]); + if (getRes && getRes.value) { + res = await kv.atomic() + .check(getRes) + .delete(["groups", id]) + .delete(["groups_by_name", getRes.value.name, id]) + .commit(); } - return res; + } + return res; } diff --git a/modules/group/group.type.ts b/modules/group/group.type.ts index 4bdadc7d9..5f91716cd 100644 --- a/modules/group/group.type.ts +++ b/modules/group/group.type.ts @@ -1,9 +1,9 @@ export type GroupArgType = { - name: string; - module?: string; + name: string; + module?: string; }; export type GroupType = { - groupId: string; - active: boolean; + groupId: string; + active: boolean; } & GroupArgType; diff --git a/modules/hook/fetch.ts b/modules/hook/fetch.ts index 9850d3903..5123d1dc9 100644 --- a/modules/hook/fetch.ts +++ b/modules/hook/fetch.ts @@ -1,33 +1,33 @@ import { useEffect, useState } from "preact/hooks"; const useFetch = (url: string) => { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); - useEffect(() => { - const fetchData = async () => { - try { - if (url) { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Error: ${response.statusText}`); - } - const result: T = await response.json(); - setData(result); - } - } catch (err) { - console.log(err); - setError(err instanceof Error ? err.message : "Unknown error"); - } finally { - setLoading(false); - } - }; + useEffect(() => { + const fetchData = async () => { + try { + if (url) { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Error: ${response.statusText}`); + } + const result: T = await response.json(); + setData(result); + } + } catch (err) { + console.log(err); + setError(err instanceof Error ? err.message : "Unknown error"); + } finally { + setLoading(false); + } + }; - fetchData(); - }, [url]); + fetchData(); + }, [url]); - return { data, loading, setLoading, error }; + return { data, loading, setLoading, error }; }; export default useFetch; diff --git a/modules/hook/socket.ts b/modules/hook/socket.ts index 408c611a0..2d9c4e943 100644 --- a/modules/hook/socket.ts +++ b/modules/hook/socket.ts @@ -2,123 +2,89 @@ import { useEffect, useRef, useState } from "preact/hooks"; const useWebSocket = (url: string, room: string, user: string) => { - const [message, setMessage] = useState(""); - const [isConnected, setIsConnected] = useState(false); - const socketRef = useRef(null); - const reconnectTimeoutRef = useRef(null); - const messageQueueRef = useRef([]); - const countRef = useRef(0); + const [message, setMessage] = useState(""); + const [isConnected, setIsConnected] = useState(false); + const socketRef = useRef(null); + const reconnectTimeoutRef = useRef(null); + const messageQueueRef = useRef([]); + const countRef = useRef(0); - function ping(data: any) { - const i = setInterval(() => { - if ( - countRef.current > 1 - ) { - setIsConnected(true); - return clearInterval(i); - } + function ping(data: any) { + const i = setInterval(() => { + if ( + countRef.current > 1 + ) { + setIsConnected(true); + return clearInterval(i); + } - if (socketRef.current?.readyState === WebSocket.OPEN) { - socketRef.current?.send( - JSON.stringify({ - ...{ type: "ping", room, user }, - ...data, - }), - ); - countRef.current++; - return console.log(`WebSocket connection established: ${room}`); - } + if (socketRef.current?.readyState === WebSocket.OPEN) { + socketRef.current?.send( + JSON.stringify({ + ...{ type: "ping", room, user }, + ...data, + }), + ); + countRef.current++; + return console.log(`WebSocket connection established: ${room}`); + } - console.log(`WebSocket connection closed: ${room}`); - }, 300); - } - - const connectWebSocket = () => { - socketRef.current = new WebSocket(url); - socketRef.current.onopen = () => { - setIsConnected(true); - while (messageQueueRef.current.length > 0) { - const queuedMessage = messageQueueRef.current.shift(); - if (queuedMessage) { - socketRef.current?.send(queuedMessage); - } - } - if (reconnectTimeoutRef.current) { - clearTimeout(reconnectTimeoutRef.current); - } - }; - - socketRef.current.onmessage = (event) => { - setMessage(event.data); - }; + console.log(`WebSocket connection closed: ${room}`); + }, 300); + } - socketRef.current.onclose = () => { - setIsConnected(false); - console.log( - "WebSocket connection closed. Attempting to reconnect...", - ); - const i = setInterval(() => { - if (isConnected) { - return clearInterval(i); - } - countRef.current++; - reconnect(countRef.current); - }, 1000); - }; - - socketRef.current.onerror = (error) => { - console.error("WebSocket error:", error); - // Close the socket on error - socketRef.current?.close(); - if (reconnectTimeoutRef.current) { - clearTimeout(reconnectTimeoutRef.current); - setIsConnected(false); - countRef.current = 0; - setMessage(""); - } - }; + useEffect(() => { + socketRef.current = new WebSocket(url); + socketRef.current.onopen = () => { + setIsConnected(true); + while (messageQueueRef.current.length > 0) { + const queuedMessage = messageQueueRef.current.shift(); + if (queuedMessage) { + socketRef.current?.send(queuedMessage); + } + } + if (reconnectTimeoutRef.current) { + clearTimeout(reconnectTimeoutRef.current); + } }; - const reconnect = (attempt: number = 1) => { - const delay = Math.min(1000 * Math.pow(2, attempt), 30000); // Exponential backoff with max delay - reconnectTimeoutRef.current = setTimeout(() => { - console.log(`Reconnecting... Attempt #${attempt}`); - connectWebSocket(); - }, delay); + socketRef.current.onmessage = (event) => { + setMessage(event.data); }; - useEffect(() => { - connectWebSocket(); - - return () => { - socketRef.current?.close(); - if (reconnectTimeoutRef.current) { - clearTimeout(reconnectTimeoutRef.current); - setIsConnected(false); - countRef.current = 0; - setMessage(""); - } - }; - }, [url]); + socketRef.current.onclose = () => { + setIsConnected(false); + }; - const sendMessage = (msg: string) => { - if (socketRef.current && isConnected) { - socketRef.current.send(msg); - } else { - // Queue message if not connected - messageQueueRef.current.push(msg); - console.log("Message queued:", msg); - } + socketRef.current.onerror = (error) => { + console.error("WebSocket error:", error); + // Close the socket on error + socketRef.current?.close(); }; - return { - message, - sendMessage, - isConnected, - setIsConnected, - ping, - countRef, + return () => { + socketRef.current?.close(); }; + }, [url]); + + const sendMessage = (msg: string) => { + if (socketRef.current && isConnected) { + socketRef.current.send(msg); + } else { + // Queue message if not connected + messageQueueRef.current.push(msg); + console.log("Message queued:", msg); + } + }; + + return { + message, + sendMessage, + isConnected, + setIsConnected, + ping, + countRef, + }; }; export default useWebSocket; diff --git a/modules/index/index.ads.tsx b/modules/index/index.ads.tsx index c22b5770a..e3a23d30c 100644 --- a/modules/index/index.ads.tsx +++ b/modules/index/index.ads.tsx @@ -3,34 +3,34 @@ import { useEffect, useState } from "preact/hooks"; const images = ["gesits.jpg", "gesits-2.jpg", "gesits-3.webp"]; const ADS_INTERVAL = 5000; export default function Ads() { - const [currentImage, setCurrentImage] = useState(images[0]); - const [index, setIndex] = useState(0); + const [currentImage, setCurrentImage] = useState(images[0]); + const [index, setIndex] = useState(0); - useEffect(() => { - const intervalId = setInterval(() => { - setIndex((prevIndex) => (prevIndex + 1) % images.length); - }, ADS_INTERVAL); - return () => clearInterval(intervalId); - }, []); + useEffect(() => { + const intervalId = setInterval(() => { + setIndex((prevIndex) => (prevIndex + 1) % images.length); + }, ADS_INTERVAL); + return () => clearInterval(intervalId); + }, []); - useEffect(() => { - setCurrentImage(images[index]); - }, [index]); + useEffect(() => { + setCurrentImage(images[index]); + }, [index]); - return ( -
- -
- - Ads from Gesits Motor - - gesits ads -
-
+ return ( + + ); } diff --git a/modules/index/index.chat.tsx b/modules/index/index.chat.tsx index 29178767a..ad35473be 100644 --- a/modules/index/index.chat.tsx +++ b/modules/index/index.chat.tsx @@ -1,25 +1,25 @@ export function Chat() { - return ( -
- + return ( +
+ - Chats -
- ); + Chats +
+ ); } diff --git a/modules/index/index.context.ts b/modules/index/index.context.ts index ce0358b72..75bd865e5 100644 --- a/modules/index/index.context.ts +++ b/modules/index/index.context.ts @@ -1,31 +1,29 @@ // deno-lint-ignore-file -import { - computed, - Signal, - signal, - useSignal, -} from "https://esm.sh/@preact/signals@1.3.0"; +import { signal } from "https://esm.sh/@preact/signals@1.3.0"; import { createContext } from "preact"; +import { DataType, RoomType } from "@app/modules/types/mod.ts"; export function createAppState() { - const room = signal({ - name: "global", - id: "01JAC4GM721KGRWZHG53SMXZP0", - }); - const message = signal(); + const room = signal({ + name: "global", + id: "01JAC4GM721KGRWZHG53SMXZP0", + }); + const message = signal(); + const rooms = signal(); + const messages = signal(); - return { room, message }; + return { room, message, rooms, messages }; } export const AppContext = createContext(createAppState()); export function createMainState() { - const state = signal({ - name: "global", - id: "01JAC4GM721KGRWZHG53SMXZP0", - }); + const state = signal({ + name: "global", + id: "01JAC4GM721KGRWZHG53SMXZP0", + }); - return { state }; + return { state }; } export const MainContext = createContext(createMainState()); diff --git a/modules/index/index.discover.tsx b/modules/index/index.discover.tsx index 113755fca..5ff9adce9 100644 --- a/modules/index/index.discover.tsx +++ b/modules/index/index.discover.tsx @@ -1,59 +1,45 @@ import { Room } from "@app/modules/index/index.room.tsx"; -import useFetch from "../hook/fetch.ts"; -import { RoomType } from "@app/modules/types/mod.ts"; +import { AppContext } from "@app/modules/index/index.context.ts"; +import { useContext } from "preact/hooks"; export function Discover() { - const { data: rooms, loading } = useFetch( - "/api/room", - ); + const state = useContext(AppContext); + return ( +
+
+ + + + + - return ( -
-
- - - - - - - Discover -
- {loading && ( -
-
- Loading ... -
-
- )} - {!loading && ( -
-
    - {rooms && rooms.map((r) => { - return ( - - ); - })} -
-
- )} -
- ); + Discover +
+
+
    + {state.rooms.value && state.rooms.value.map((r) => { + return ( + + ); + })} +
+
+
+ ); } diff --git a/modules/index/index.handler.ts b/modules/index/index.handler.ts index 57be9c50f..19019f89d 100644 --- a/modules/index/index.handler.ts +++ b/modules/index/index.handler.ts @@ -2,54 +2,59 @@ import { Context, HttpRequest } from "@app/mod.ts"; import { getSession } from "@app/utils/session.ts"; function init() { - const basePath = Deno.env.get("DENO_DEPLOYMENT_ID") - ? `https://raw.githubusercontent.com/fastrodev/fastro/main/static` - : "http://localhost:8000/static"; - const code = - `import init from "${basePath}/init.ts"; const name = Deno.args[0] ?? 'project'; await init(name);`; - return new Response(code, { - headers: { - "content-type": "application/typescript; charset=utf-8", - }, - }); + const basePath = Deno.env.get("DENO_DEPLOYMENT_ID") + ? `https://raw.githubusercontent.com/fastrodev/fastro/main/static` + : "http://localhost:8000/static"; + const code = + `import init from "${basePath}/init.ts"; const name = Deno.args[0] ?? 'project'; await init(name);`; + return new Response(code, { + headers: { + "content-type": "application/typescript; charset=utf-8", + }, + }); } function denoRunCheck(req: HttpRequest) { - const regex = /^Deno\/(\d+\.\d+\.\d+)$/; - const string = req.headers.get("user-agent"); - if (!string) return false; - const match = regex.exec(string); - if (!match) return false; - return true; + const regex = /^Deno\/(\d+\.\d+\.\d+)$/; + const string = req.headers.get("user-agent"); + if (!string) return false; + const match = regex.exec(string); + if (!match) return false; + return true; } export const handler = async (req: HttpRequest, ctx: Context) => { - const res = denoRunCheck(req); - if (res) return init(); - const ses = await getSession(req, ctx); - const title = ses?.isLogin - ? "Under construction ~ Home" - : "Fast & Modular Web Framework"; - const ws_url = Deno.env.get("DENO_DEPLOYMENT_ID") - ? "wss://fastro.dev" - : "ws://localhost:8000"; + const res = denoRunCheck(req); + if (res) return init(); + + const ses = await getSession(req, ctx); + const ws_url = Deno.env.get("DENO_DEPLOYMENT_ID") + ? "wss://fastro.deno.dev" + : "ws://localhost:8000"; + + if (ses) { return ctx.render({ - title, - description: - "Enhance SSR web app maintainability through a flat modular architecture", - image: "https://fastro.dev/fastro.png", - start: Deno.env.get("ENV") === "DEVELOPMENT" - ? "http://localhost:8000/docs/start" - : "https://fastro.dev/docs/start", - baseUrl: Deno.env.get("ENV") === "DEVELOPMENT" - ? "http://localhost:8000" - : "https://fastro.dev", - new: "Using Queues to Avoid Race Conditions", - destination: "blog/queue", - isLogin: ses?.isLogin, - avatar_url: ses?.avatar_url, - html_url: ses?.html_url, - ws_url, - username: ses?.username, + title: "Under construction ~ Home", + isLogin: true, + avatar_url: ses?.avatar_url, + html_url: ses?.html_url, + ws_url, + username: ses?.username, }); + } + + return ctx.render({ + title: "Fast & Modular Web Framework", + description: + "Enhance SSR web app maintainability through a flat modular architecture", + image: "https://fastro.dev/fastro.png", + start: Deno.env.get("ENV") === "DEVELOPMENT" + ? "http://localhost:8000/docs/start" + : "https://fastro.dev/docs/start", + baseUrl: Deno.env.get("ENV") === "DEVELOPMENT" + ? "http://localhost:8000" + : "https://fastro.dev", + new: "Using Queues to Avoid Race Conditions", + destination: "blog/queue", + }); }; diff --git a/modules/index/index.input.tsx b/modules/index/index.input.tsx index 8e490b3fa..29b74c7b3 100644 --- a/modules/index/index.input.tsx +++ b/modules/index/index.input.tsx @@ -3,95 +3,95 @@ import { ulid } from "jsr:@std/ulid/ulid"; import { useState } from "preact/hooks"; export default function MessageInput( - props: { - room: { id: string; name: string }; - username: string; - avatar_url: string; - ws_url: string; - sendMessage: (message: string) => void; - }, + props: { + room: { id: string; name: string }; + username: string; + avatar_url: string; + ws_url: string; + sendMessage: (message: string) => void; + }, ) { - const [inputValue, setInputValue] = useState(""); + const [inputValue, setInputValue] = useState(""); - const handleSendMessage = (data: any) => { - props.sendMessage(JSON.stringify(data)); - }; + const handleSendMessage = (data: any) => { + props.sendMessage(JSON.stringify(data)); + }; - const handleClick = () => { - // const newMessage = { - // msg: inputValue, - // time: new Date().toISOString(), - // }; - // handleSendMessage(newMessage); - }; + const handleClick = () => { + // const newMessage = { + // msg: inputValue, + // time: new Date().toISOString(), + // }; + // handleSendMessage(newMessage); + }; - const handleKeyPress = (event: KeyboardEvent) => { - if (event.key === "Enter" && inputValue.trim() !== "") { - const newMessage = { - username: props.username, - img: props.avatar_url, - msg: inputValue, - id: ulid(), - }; - const data = { - room: props.room.id, - type: "message", - message: newMessage, - }; - handleSendMessage(data); - setInputValue(""); - } - }; + const handleKeyPress = (event: KeyboardEvent) => { + if (event.key === "Enter" && inputValue.trim() !== "") { + const newMessage = { + username: props.username, + img: props.avatar_url, + msg: inputValue, + id: ulid(), + }; + const data = { + room: props.room.id, + type: "message", + message: newMessage, + }; + handleSendMessage(data); + setInputValue(""); + } + }; - const handleInputChange = (event: Event) => { - const target = event.target as HTMLInputElement; - setInputValue(target.value); - }; + const handleInputChange = (event: Event) => { + const target = event.target as HTMLInputElement; + setInputValue(target.value); + }; - return ( -
-
- -
-
- - -
-
- ); + return ( +
+
+ +
+
+ + +
+
+ ); } diff --git a/modules/index/index.launchpad.tsx b/modules/index/index.launchpad.tsx index f945b536d..88b1df230 100644 --- a/modules/index/index.launchpad.tsx +++ b/modules/index/index.launchpad.tsx @@ -1,24 +1,66 @@ import { Main } from "@app/modules/index/index.main.tsx"; import { Navigation } from "@app/modules/index/index.navigation.tsx"; import { Menu } from "@app/modules/index/index.menu.tsx"; +import useFetch from "@app/modules/hook/fetch.ts"; +import { DataType, RoomType } from "@app/modules/types/mod.ts"; +import { useContext } from "preact/hooks"; +import { effect } from "https://esm.sh/@preact/signals@1.3.0"; +import { AppContext } from "@app/modules/index/index.context.ts"; + +function Loader(props: { text: string }) { + return ( +
+ {props.text} +
+ ); +} export default function Launchpad( - props: { avatar_url: string; username: string; ws_url: string }, + props: { avatar_url: string; username: string; ws_url: string }, ) { - return ( -
- -
- -
- ); + const state = useContext(AppContext); + const { data: rooms, loading: loadRoom } = useFetch( + "/api/room", + ); + + const url = `api/message/${state.room.value.id}/${props.username}`; + const { data: messages, loading: loadMessage, error } = useFetch< + DataType[] + >(url); + + effect(() => { + state.rooms.value = rooms; + state.messages.value = messages; + return () => { + state.rooms.value = null; + state.messages.value = null; + }; + }); + + if (error) { + return ; + } + + if (loadRoom || loadMessage) { + return ; + } + + return ( +
+ +
+ +
+ ); } diff --git a/modules/index/index.main.tsx b/modules/index/index.main.tsx index a1b5f9b7c..451ed4185 100644 --- a/modules/index/index.main.tsx +++ b/modules/index/index.main.tsx @@ -11,206 +11,202 @@ import { effect } from "https://esm.sh/@preact/signals@1.3.0"; import MessageInput from "@app/modules/index/index.input.tsx"; function ListMessage(props: { data: DataType[] }) { - const listRef = useRef(null); - const data = props.data; - - useEffect(() => { - if (listRef.current) { - listRef.current.scrollTop = listRef.current.scrollHeight; - } - }, [props.data]); - - return ( -
-
    - {data && data.map((item, index) => { - return ( -
      - {item.messages.map((d, x) => { - const idx = x; - return ( - - ); - })} -
    - ); - })} + const listRef = useRef(null); + const data = props.data; + + useEffect(() => { + if (listRef.current) { + listRef.current.scrollTop = listRef.current.scrollHeight; + } + }, [props.data]); + + return ( +
    +
      + {data && data.map((item, index) => { + return ( +
        + {item.messages.map((d, x) => { + const idx = x; + return ( + + ); + })}
      -
    - ); + ); + })} +
+
+ ); } function Background() { - return ( -
- ); + return ( +
+ ); } function Loading(props: { text: string }) { - return ( -
- {props.text} - -
- ); + return ( +
+ {props.text} + +
+ ); } function capitalizeFirstLetterOfWords(input: string): string { - // Split the input string into words using space as a delimiter - return input.split(" ").map((word) => { - // Capitalize the first letter and concatenate with the rest of the word - return word.charAt(0).toUpperCase() + word.slice(1); - }).join(" "); // Join the words back together with spaces + // Split the input string into words using space as a delimiter + return input.split(" ").map((word) => { + // Capitalize the first letter and concatenate with the rest of the word + return word.charAt(0).toUpperCase() + word.slice(1); + }).join(" "); // Join the words back together with spaces } export function Main( - props: { avatar_url: string; username: string; ws_url: string }, + props: { avatar_url: string; username: string; ws_url: string }, ) { - const state = useContext(AppContext); - const [room, setRoom] = useState({ - name: "global", - id: "01JAC4GM721KGRWZHG53SMXZP0", - }); - const [url, setUrl] = useState(""); - const { data: d, loading, setLoading, error } = useFetch( - url, - ); - const [data, setData] = useState(d as any); - const { message, sendMessage, isConnected, ping, countRef } = useWebSocket( - props.ws_url, - room.id, - props.username, - ); - - const insertData = (newMessage: { - msg: string; - username: string; - id: string; - img: string; - }) => { - if (!newMessage.id && !newMessage.msg) return; - const updatedData = [...data]; - const lastUser = updatedData[updatedData.length - 1]; - const time = ulidToDate(newMessage.id); - const msg = { - type: "message", - msg: newMessage.msg, - id: newMessage.id, - time, - }; - - if (lastUser.username === newMessage.username) { - const lastUserMessages = lastUser.messages; - lastUserMessages.push(msg); - } else { - updatedData.push({ - type: "message", - username: newMessage.username, - img: newMessage.img, - messages: [msg], - }); - } - - setData(updatedData); + const state = useContext(AppContext); + const [room, setRoom] = useState(state.room.value); + const [url, setUrl] = useState(""); + const { data: d, loading, setLoading, error } = useFetch( + url, + ); + const [data, setData] = useState(d as any); + const { message, sendMessage, isConnected, ping, countRef } = useWebSocket( + props.ws_url, + room.id, + props.username, + ); + + const insertData = (newMessage: { + msg: string; + username: string; + id: string; + img: string; + }) => { + if (!newMessage.id && !newMessage.msg) return; + const updatedData = [...data]; + const lastUser = updatedData[updatedData.length - 1]; + const time = ulidToDate(newMessage.id); + const msg = { + type: "message", + msg: newMessage.msg, + id: newMessage.id, + time, }; - const listRef = useRef(null); - useEffect(() => { - if (listRef.current) { - listRef.current.scrollTop = listRef.current.scrollHeight; - } - }, [data]); - - // save messages received from server - useEffect(() => { - if (isConnected && message) { - const msg = JSON.parse(message); - if (Array.isArray(msg)) { - state.message.value = msg; - } else { - insertData(msg); - } - } - }, [message, isConnected]); - - // load saved message from server - useEffect(() => { - if (isConnected && d) { - const arr = [...initialData]; - const name = capitalizeFirstLetterOfWords(`${room.name} room`); - arr[0].messages[0].msg = - `Hello ${props.username}! Welcome to ${name}.`; - - const dd = [...d, ...arr]; - const ddd = dd.map((v) => { - const msg = v.messages.map((m) => { - m.time = ulidToDate(m.id); - return m; - }); - v.messages = msg; - return v; - }); - setData(ddd); - } - }, [d, initialData, isConnected]); - - // ping ws when the room changes - useEffect(() => { - const url = `api/message/${room.id}/${props.username}`; - setUrl(url); - setLoading(true); - ping({ - room: room.id, - username: props.username, - avatar_url: props.avatar_url, + if (lastUser.username === newMessage.username) { + const lastUserMessages = lastUser.messages; + lastUserMessages.push(msg); + } else { + updatedData.push({ + type: "message", + username: newMessage.username, + img: newMessage.img, + messages: [msg], + }); + } + + setData(updatedData); + }; + + const listRef = useRef(null); + useEffect(() => { + if (listRef.current) { + listRef.current.scrollTop = listRef.current.scrollHeight; + } + }, [data]); + + // save messages received from server + useEffect(() => { + if (isConnected && message) { + const msg = JSON.parse(message); + if (Array.isArray(msg)) { + state.message.value = msg; + } else { + insertData(msg); + } + } + }, [message, isConnected]); + + // load saved message from server + useEffect(() => { + if (isConnected && d) { + const arr = [...initialData]; + const name = capitalizeFirstLetterOfWords(`${room.name} room`); + arr[0].messages[0].msg = `Hello ${props.username}! Welcome to ${name}.`; + + const dd = [...d, ...arr]; + const ddd = dd.map((v) => { + const msg = v.messages.map((m) => { + m.time = ulidToDate(m.id); + return m; }); - return () => setRoom(state.room.value); - }, [room]); - - // reset connection if the room changes - useEffect(() => { - return () => countRef.current = 0; - }, [isConnected, room]); - - // monitor the room state - effect(() => { - setRoom(state.room.value); + v.messages = msg; + return v; + }); + setData(ddd); + } + }, [d, initialData, isConnected]); + + // ping ws when the room changes + useEffect(() => { + const url = `api/message/${room.id}/${props.username}`; + setUrl(url); + setLoading(true); + ping({ + room: room.id, + username: props.username, + avatar_url: props.avatar_url, }); - - return ( - <> - {error && } - {!error && loading && } - {!error && !loading && ( -
- -
- -
-
- -
-
- )} - - ); + return () => setRoom(state.room.value); + }, [room]); + + // reset connection if the room changes + useEffect(() => { + return () => countRef.current = 0; + }, [isConnected, room]); + + // monitor the room state + effect(() => { + setRoom(state.room.value); + }); + + return ( + <> + {error && } + {!error && loading && } + {!error && !loading && ( +
+ +
+ +
+
+ +
+
+ )} + + ); } diff --git a/modules/index/index.menu.tsx b/modules/index/index.menu.tsx index 49ed55ce5..e258c484c 100644 --- a/modules/index/index.menu.tsx +++ b/modules/index/index.menu.tsx @@ -4,17 +4,17 @@ import { Setting } from "@app/modules/index/index.profile.tsx"; import { Signout } from "@app/modules/index/index.signout.tsx"; export function Menu( - props: { avatar_url: string; username: string }, + props: { avatar_url: string; username: string }, ) { - return ( -
- - -
- - -
- ); + return ( +
+ + +
+ + +
+ ); } diff --git a/modules/index/index.message.tsx b/modules/index/index.message.tsx index c614cdc0a..867220b45 100644 --- a/modules/index/index.message.tsx +++ b/modules/index/index.message.tsx @@ -1,67 +1,67 @@ export function Message( - props: { - id: string; - username: string; - msg: string; - time: string; - img: string; - idx: number; - }, + props: { + id: string; + username: string; + msg: string; + time: string; + img: string; + idx: number; + }, ) { - return ( -
  • - {props.idx === 0 - ? ( -
    - -
    - ) - :
    } + return ( +
  • + {props.idx === 0 + ? ( +
    + +
    + ) + :
    } -
    -
    -
    - - {props.username} - - - {props.time} - -
    - -
    - - {props.msg} - -
    -
  • - ); +
    +
    +
    + + {props.username} + + + {props.time} + +
    + +
    + + {props.msg} + +
    + + ); } diff --git a/modules/index/index.navigation.tsx b/modules/index/index.navigation.tsx index 594d4ff7c..9e44e4856 100644 --- a/modules/index/index.navigation.tsx +++ b/modules/index/index.navigation.tsx @@ -182,57 +182,57 @@ function Fieldset() { } */ export function Navigation() { - const state = useContext(AppContext); - const [message, setMessage] = useState(); - const [room, setRoom] = useState(); - const [data, setData] = useState(); + const state = useContext(AppContext); + const [message, setMessage] = useState(); + const [room, setRoom] = useState(); + const [data, setData] = useState(); - effect(() => { - setRoom(state.room.value.id); - setMessage(state.message.value); - return () => { - setMessage(null); - setRoom(null); - }; - }); + effect(() => { + setRoom(state.room.value.id); + setMessage(state.message.value); + return () => { + setMessage(null); + setRoom(null); + }; + }); - useEffect(() => { - if (!message) return; - const d = [...message]; - const dd = d.filter((v: any) => v.room === room); - setData(dd); - return () => { - setData(null); - }; - }, [message, room]); + useEffect(() => { + if (!message) return; + const d = [...message]; + const dd = d.filter((v: any) => v.room === room); + setData(dd); + return () => { + setData(null); + }; + }, [message, room]); - return ( -
    - {data - ? ( -
      - {data.map((v: any) => { - return ( -
    • - - - {v.username} - -
    • - ); - })} -
    - ) - :
    } -
    - ); + return ( +
    + {data + ? ( +
      + {data.map((v: any) => { + return ( +
    • + + + {v.username} + +
    • + ); + })} +
    + ) + :
    } +
    + ); } diff --git a/modules/index/index.non-login.tsx b/modules/index/index.non-login.tsx index 0fd4a0daa..369278886 100644 --- a/modules/index/index.non-login.tsx +++ b/modules/index/index.non-login.tsx @@ -5,152 +5,151 @@ import DenoSvg from "@app/components/icons/deno-svg.tsx"; import TypeScriptSvg from "@app/components/icons/ts-svg.tsx"; import ProjectBox from "@app/components/project-box.tsx"; import SeoSvg from "@app/components/icons/seo-svg.tsx"; -import BoltSvg from "@app/components/icons/bolt-svg.tsx"; import UxSvg from "@app/components/icons/ux-svg.tsx"; import SettingSvg from "@app/components/icons/setting-svg.tsx"; import ScaleSvg from "@app/components/icons/scale-svg.tsx"; import RepeatSvg from "@app/components/icons/repeat-svg.tsx"; import WwwSvg from "@app/components/icons/www-svg.tsx"; +import BoltSvg from "@app/components/icons/bolt.tsx"; function PoweredBy() { - return ( -
    -

    - High-performance web framework built on a flat, modular - architecture. Powered by Deno, TypeScript, Preact JS, and - Tailwind CSS -

    + return ( +
    +

    + High-performance web framework built on a flat, modular architecture. + Powered by Deno, TypeScript, Preact JS, and Tailwind CSS +

    -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    +
    +
    + +
    +
    + +
    +
    +
    - ); +
    + +
    +
    +
    + ); } function WhyFlat() { - return ( -
    -

    - Why use a flat modular architecture? -

    -
    - - -

    Rapid Build

    -
    - - -

    Maintainable

    -
    - - -

    Scalable

    -
    - - -

    Reusable

    -
    -
    -
    - ); + return ( +
    +

    + Why use a flat modular architecture? +

    +
    + + +

    Rapid Build

    +
    + + +

    Maintainable

    +
    + + +

    Scalable

    +
    + + +

    Reusable

    +
    +
    +
    + ); } function WhySSR() { - return ( -
    -

    - Why use SSR? -

    -
    - - -

    Quick Load

    -
    - - -

    Improved SEO

    -
    - - -

    Better UX

    -
    - - -

    Browser Legacy

    -
    -
    -
    - ); + return ( +
    +

    + Why use SSR? +

    +
    + + +

    Quick Load

    +
    + + +

    Improved SEO

    +
    + + +

    Better UX

    +
    + + +

    Browser Legacy

    +
    +
    +
    + ); } // deno-lint-ignore no-explicit-any export default function NonLogin(props: { data: any }) { - const data = props.data; - return ( -
    -
    -
    - -
    + const data = props.data; + return ( +
    +
    +
    + +
    -

    - {data.title} -

    +

    + {data.title} +

    -
    - - Get started - - - - - -
    - deno run -A -r https://fastro.dev -
    -
    -
    +
    + + Get started + + + + + +
    + deno run -A -r https://fastro.dev +
    +
    +
    - + -
    - - -
    -
    - ); +
    + + +
    + + ); } diff --git a/modules/index/index.page.tsx b/modules/index/index.page.tsx index 82074340e..de41412cf 100644 --- a/modules/index/index.page.tsx +++ b/modules/index/index.page.tsx @@ -1,4 +1,4 @@ -import { PageProps } from "../../core/server/types.ts"; +import { PageProps } from "@app/core/server/types.ts"; import { Footer } from "@app/components/footer.tsx"; import Header from "@app/components/header.tsx"; import Launchpad from "@app/modules/index/index.launchpad.tsx"; @@ -22,30 +22,28 @@ export default function Index({ data }: PageProps< ws_url: string; } >) { - return ( - <> - {data.isLogin && ( - - - - )} + if (data.isLogin) { + return ( + + + + ); + } - {!data.isLogin && ( -
    -
    - -
    -
    - )} - + return ( +
    +
    + +
    +
    ); } diff --git a/modules/index/index.profile.tsx b/modules/index/index.profile.tsx index 7408e6aa4..649db91cc 100644 --- a/modules/index/index.profile.tsx +++ b/modules/index/index.profile.tsx @@ -1,33 +1,33 @@ export function Setting(props: { avatar_url: string; username: string }) { - return ( -
    -
    - + return ( +
    +
    + - {props.username} -
    + {props.username} +
    - - - - - -
    - ); + + + + + +
    + ); } diff --git a/modules/index/index.room.tsx b/modules/index/index.room.tsx index bcd755445..7961a5eea 100644 --- a/modules/index/index.room.tsx +++ b/modules/index/index.room.tsx @@ -4,47 +4,47 @@ import { AppContext } from "@app/modules/index/index.context.ts"; import useFetch from "../hook/fetch.ts"; export function Room( - props: { title: string; id: string; checked?: boolean; disabled?: boolean }, + props: { title: string; id: string; checked?: boolean; disabled?: boolean }, ) { - const [selected, setSelected] = useState( - "01JAC4GM721KGRWZHG53SMXZP0", - ); - const state = useContext(AppContext); - const { data } = useFetch(`/api/room/${props.id}`); + const [selected, setSelected] = useState( + "01JAC4GM721KGRWZHG53SMXZP0", + ); + const state = useContext(AppContext); + const { data } = useFetch(`/api/room/${props.id}`); - const handleChange = ( - e: JSX.TargetedEvent, - ) => { - const target = e.target as HTMLInputElement; - setSelected(target.value); - state.room.value = data as { - name: string; - id: string; - }; + const handleChange = ( + e: JSX.TargetedEvent, + ) => { + const target = e.target as HTMLInputElement; + setSelected(target.value); + state.room.value = data as { + name: string; + id: string; }; + }; - return ( -
  • - - handleChange( - e as JSX.TargetedEvent< - HTMLInputElement, - InputEvent - >, - )} - checked={selected === props.id} - disabled={props.disabled} - class="w-4 h-4 text-blue-600 bg-gray-100 focus:ring-blue-600 ring-offset-gray-700 focus:ring-offset-gray-700 focus:ring-2 border-gray-500" - /> - {props.title} -
  • - ); + return ( +
  • + + handleChange( + e as JSX.TargetedEvent< + HTMLInputElement, + InputEvent + >, + )} + checked={selected === props.id} + disabled={props.disabled} + class="w-4 h-4 text-blue-600 bg-gray-100 focus:ring-blue-600 ring-offset-gray-700 focus:ring-offset-gray-700 focus:ring-2 border-gray-500" + /> + {props.title} +
  • + ); } diff --git a/modules/index/index.signout.tsx b/modules/index/index.signout.tsx index c4101e1bc..59c17b2fa 100644 --- a/modules/index/index.signout.tsx +++ b/modules/index/index.signout.tsx @@ -1,27 +1,27 @@ export function Signout() { - return ( - - + + ); } diff --git a/modules/index/mod.ts b/modules/index/mod.ts index 553fb993e..dfede24ee 100644 --- a/modules/index/mod.ts +++ b/modules/index/mod.ts @@ -4,11 +4,11 @@ import index from "@app/modules/index/index.layout.tsx"; import { handler } from "@app/modules/index/index.handler.ts"; export default function (s: Fastro) { - s.page("/", { - component: indexApp, - layout: index, - folder: "modules/index", - handler, - }); - return s; + s.page("/", { + component: indexApp, + layout: index, + folder: "modules/index", + handler, + }); + return s; } diff --git a/modules/markdown/mod.tsx b/modules/markdown/mod.tsx index f1355623f..829f87e66 100644 --- a/modules/markdown/mod.tsx +++ b/modules/markdown/mod.tsx @@ -1,7 +1,7 @@ import { Context, HttpRequest } from "@app/mod.ts"; import { - defaultLayout, - getMarkdownBody, + defaultLayout, + getMarkdownBody, } from "@app/middleware/markdown/mod.tsx"; import { getSession } from "@app/utils/session.ts"; @@ -12,27 +12,27 @@ import { getSession } from "@app/utils/session.ts"; * @returns */ export default function ( - layout = defaultLayout, - folder: string, - prefix: string, + layout = defaultLayout, + folder: string, + prefix: string, ) { - return async function middleware(req: HttpRequest, ctx: Context) { - const ses = await getSession(req, ctx); - const body = await getMarkdownBody( - req, - layout, - folder, - ctx.url.pathname, - prefix, - { - avatar_url: ses?.avatar_url, - html_url: ses?.html_url, - isLogin: ses?.isLogin, - }, - ); - if (!body) return ctx.next(); - return new Response(body, { - headers: { "content-type": "text/html" }, - }); - }; + return async function middleware(req: HttpRequest, ctx: Context) { + const ses = await getSession(req, ctx); + const body = await getMarkdownBody( + req, + layout, + folder, + ctx.url.pathname, + prefix, + { + avatar_url: ses?.avatar_url, + html_url: ses?.html_url, + isLogin: ses?.isLogin, + }, + ); + if (!body) return ctx.next(); + return new Response(body, { + headers: { "content-type": "text/html" }, + }); + }; } diff --git a/modules/message/mod.test.ts b/modules/message/mod.test.ts index 517a768c1..839303af2 100644 --- a/modules/message/mod.test.ts +++ b/modules/message/mod.test.ts @@ -2,13 +2,13 @@ import { assertEquals } from "@app/core/server/deps.ts"; import { createMessage } from "@app/modules/message/mod.ts"; Deno.test("Message: create message", async () => { - const userId = "01J9GHHJB9JW2R13P6S24CH5B7"; + const userId = "01J9GHHJB9JW2R13P6S24CH5B7"; - const res = await createMessage({ - userId, - content: "Hello", - title: "hi", - }); + const res = await createMessage({ + userId, + content: "Hello", + title: "hi", + }); - assertEquals(res?.userId, userId); + assertEquals(res?.userId, userId); }); diff --git a/modules/message/mod.ts b/modules/message/mod.ts index 9518888d2..f350b6272 100644 --- a/modules/message/mod.ts +++ b/modules/message/mod.ts @@ -3,29 +3,29 @@ import { Store } from "@app/core/map/mod.ts"; import { ulid } from "jsr:@std/ulid/ulid"; export async function createMessage( - options: { userId: string; content: string; title: string }, + options: { userId: string; content: string; title: string }, ) { - const store = new Store({ - // token: Deno.env.get("GITHUB_TOKEN"), - // owner: Deno.env.get("MESSAGE_OWNER") || "fastrodev", - // repo: Deno.env.get("MESSAGE_REPO") || "fastro", - // branch: Deno.env.get("MESSAGE_BRANCH") || "store", - // path: `modules/store/${options.userId}/message.json`, - key: "message", - }); + const store = new Store({ + // token: Deno.env.get("GITHUB_TOKEN"), + // owner: Deno.env.get("MESSAGE_OWNER") || "fastrodev", + // repo: Deno.env.get("MESSAGE_REPO") || "fastro", + // branch: Deno.env.get("MESSAGE_BRANCH") || "store", + // path: `modules/store/${options.userId}/message.json`, + key: "message", + }); - const postId = ulid(); - await store.set(postId, { title: options.title, postId }).commit(); + const postId = ulid(); + await store.set(postId, { title: options.title, postId }).commit(); - const r = await uploadFileToGitHub({ - token: Deno.env.get("GITHUB_TOKEN"), - owner: Deno.env.get("MESSAGE_OWNER") || "fastrodev", - repo: Deno.env.get("MESSAGE_REPO") || "fastro", - branch: Deno.env.get("MESSAGE_BRANCH") || "store", - path: `modules/store/${options.userId}/${postId}.md`, - content: options.content, - }); + const r = await uploadFileToGitHub({ + token: Deno.env.get("GITHUB_TOKEN"), + owner: Deno.env.get("MESSAGE_OWNER") || "fastrodev", + repo: Deno.env.get("MESSAGE_REPO") || "fastro", + branch: Deno.env.get("MESSAGE_BRANCH") || "store", + path: `modules/store/${options.userId}/${postId}.md`, + content: options.content, + }); - if (r.status !== 201) return; - return { postId, ...options }; + if (r.status !== 201) return; + return { postId, ...options }; } diff --git a/modules/permission/permission.service.ts b/modules/permission/permission.service.ts index 1eaa538cb..3a349d3b4 100644 --- a/modules/permission/permission.service.ts +++ b/modules/permission/permission.service.ts @@ -1,8 +1,8 @@ import { kv } from "@app/utils/db.ts"; import { - PermissionArgsType, - PermissionOpType, - PermissionType, + PermissionArgsType, + PermissionOpType, + PermissionType, } from "@app/modules/permission/permission.type.ts"; import { UserType } from "@app/modules/user/user.type.ts"; import { GroupType } from "@app/modules/group/group.type.ts"; @@ -15,179 +15,179 @@ const PERMISSION_BY_GROUP_KEY = "permissions_by_group"; const PERMISSION_BY_MODULE_KEY = "permissions_by_module"; function isValidPermissionsArray(permissions: PermissionOpType[]): boolean { - const expectedPermissions: Set = new Set([ - "read", - "write", - "execute", - ]); - - if (permissions.length === 0) { - return false; - } + const expectedPermissions: Set = new Set([ + "read", + "write", + "execute", + ]); + + if (permissions.length === 0) { + return false; + } - const uniquePermissions = new Set(permissions); + const uniquePermissions = new Set(permissions); - const isValid = [...uniquePermissions].every((permission) => - expectedPermissions.has(permission) - ); + const isValid = [...uniquePermissions].every((permission) => + expectedPermissions.has(permission) + ); - return isValid && uniquePermissions.size <= expectedPermissions.size; + return isValid && uniquePermissions.size <= expectedPermissions.size; } export async function createPermission(permissionArgs: PermissionArgsType) { - if (!isValidPermissionsArray(permissionArgs.permission)) { - throw new Error("Permission is not valid"); - } - - const userId = permissionArgs.userId; - const user = await kv.get(["users", userId]); - if (!user.value) throw new Error("User is not found"); - - const groupId = permissionArgs.groupId; - const group = await kv.get(["groups", groupId]); - if (!group.value) throw new Error("Group is not found"); - - const module = permissionArgs.module || "null"; - - const permissionId = ulid(); - const permissionWithId = { - permissionId, - active: true, - }; - const permission = combineObjects( - permissionWithId, - permissionArgs, - ) as PermissionType; - - const primaryKey = [PRIMARY_KEY, permissionId]; - const userKey = [PERMISSION_BY_USER_KEY, userId, permissionId]; - const groupKey = [PERMISSION_BY_GROUP_KEY, groupId, permissionId]; - const moduleKey = [PERMISSION_BY_MODULE_KEY, module, permissionId]; - - const atomicOp = kv.atomic(); - atomicOp - .check({ key: primaryKey, versionstamp: null }) - .set(primaryKey, permission) - .set(groupKey, permission) - .set(userKey, permission) - .set(moduleKey, permission); - - const res = await atomicOp.commit(); - if (!res.ok) throw new Error("Failed to create the permission"); - return permission; + if (!isValidPermissionsArray(permissionArgs.permission)) { + throw new Error("Permission is not valid"); + } + + const userId = permissionArgs.userId; + const user = await kv.get(["users", userId]); + if (!user.value) throw new Error("User is not found"); + + const groupId = permissionArgs.groupId; + const group = await kv.get(["groups", groupId]); + if (!group.value) throw new Error("Group is not found"); + + const module = permissionArgs.module || "null"; + + const permissionId = ulid(); + const permissionWithId = { + permissionId, + active: true, + }; + const permission = combineObjects( + permissionWithId, + permissionArgs, + ) as PermissionType; + + const primaryKey = [PRIMARY_KEY, permissionId]; + const userKey = [PERMISSION_BY_USER_KEY, userId, permissionId]; + const groupKey = [PERMISSION_BY_GROUP_KEY, groupId, permissionId]; + const moduleKey = [PERMISSION_BY_MODULE_KEY, module, permissionId]; + + const atomicOp = kv.atomic(); + atomicOp + .check({ key: primaryKey, versionstamp: null }) + .set(primaryKey, permission) + .set(groupKey, permission) + .set(userKey, permission) + .set(moduleKey, permission); + + const res = await atomicOp.commit(); + if (!res.ok) throw new Error("Failed to create the permission"); + return permission; } export async function updatePermission(p: PermissionType) { - if (!isValidPermissionsArray(p.permission)) { - throw new Error("Permission is not valid"); - } + if (!isValidPermissionsArray(p.permission)) { + throw new Error("Permission is not valid"); + } - const atomicOp = kv.atomic(); - const primaryKey = [PRIMARY_KEY, p.permissionId]; - const res = await kv.get(primaryKey); + const atomicOp = kv.atomic(); + const primaryKey = [PRIMARY_KEY, p.permissionId]; + const res = await kv.get(primaryKey); - atomicOp - .check(res) - .set(primaryKey, p); - - if (res.value?.userId) { - atomicOp - .delete([ - PERMISSION_BY_USER_KEY, - res.value?.userId, - res.value.permissionId, - ]) - .set([PERMISSION_BY_USER_KEY, p.userId, p.permissionId], p); - } - if (res.value?.groupId) { - atomicOp - .delete([ - PERMISSION_BY_GROUP_KEY, - res.value?.groupId, - res.value.permissionId, - ]) - .set([PERMISSION_BY_GROUP_KEY, p.groupId, p.permissionId], p); - } - if (res.value?.module) { - atomicOp - .delete([ - PERMISSION_BY_MODULE_KEY, - res.value?.module, - res.value.permissionId, - ]) - .set( - [PERMISSION_BY_GROUP_KEY, p.module || "null", p.permissionId], - p, - ); - } + atomicOp + .check(res) + .set(primaryKey, p); - const r = await atomicOp.commit(); - if (!r.ok) throw new Error("Failed to create the permission"); - return p; + if (res.value?.userId) { + atomicOp + .delete([ + PERMISSION_BY_USER_KEY, + res.value?.userId, + res.value.permissionId, + ]) + .set([PERMISSION_BY_USER_KEY, p.userId, p.permissionId], p); + } + if (res.value?.groupId) { + atomicOp + .delete([ + PERMISSION_BY_GROUP_KEY, + res.value?.groupId, + res.value.permissionId, + ]) + .set([PERMISSION_BY_GROUP_KEY, p.groupId, p.permissionId], p); + } + if (res.value?.module) { + atomicOp + .delete([ + PERMISSION_BY_MODULE_KEY, + res.value?.module, + res.value.permissionId, + ]) + .set( + [PERMISSION_BY_GROUP_KEY, p.module || "null", p.permissionId], + p, + ); + } + + const r = await atomicOp.commit(); + if (!r.ok) throw new Error("Failed to create the permission"); + return p; } export async function getPermission(permissionId: string) { - const res = await kv.get([PRIMARY_KEY, permissionId]); - return res.value; + const res = await kv.get([PRIMARY_KEY, permissionId]); + return res.value; } export function listPermissionByUserId( - userId: string, - options?: Deno.KvListOptions, + userId: string, + options?: Deno.KvListOptions, ) { - return kv.list( - { prefix: [PERMISSION_BY_USER_KEY, userId] }, - options, - ); + return kv.list( + { prefix: [PERMISSION_BY_USER_KEY, userId] }, + options, + ); } export function listPemissionByGroupId( - groupId: string, - options?: Deno.KvListOptions, + groupId: string, + options?: Deno.KvListOptions, ) { - return kv.list({ - prefix: [PERMISSION_BY_GROUP_KEY, groupId], - }, options); + return kv.list({ + prefix: [PERMISSION_BY_GROUP_KEY, groupId], + }, options); } export function listPemissionByModule( - module: string, - options?: Deno.KvListOptions, + module: string, + options?: Deno.KvListOptions, ) { - return kv.list({ - prefix: [PERMISSION_BY_MODULE_KEY, module], - }, options); + return kv.list({ + prefix: [PERMISSION_BY_MODULE_KEY, module], + }, options); } export async function deletePermission(permissionId: string) { - const atomicOp = kv.atomic(); - let res = { ok: false }; - while (!res.ok) { - const k = [ - PRIMARY_KEY, - permissionId, - ]; - const r = await kv.get(k); - if (r) { - atomicOp - .check(r) - .delete(k); - } - - if (r.value?.userId) { - atomicOp.delete([PERMISSION_BY_USER_KEY, permissionId]); - } - - if (r.value?.groupId) { - atomicOp.delete([PERMISSION_BY_GROUP_KEY, permissionId]); - } - - if (r.value?.module) { - atomicOp.delete([PERMISSION_BY_MODULE_KEY, permissionId]); - } - - res = await atomicOp.commit(); + const atomicOp = kv.atomic(); + let res = { ok: false }; + while (!res.ok) { + const k = [ + PRIMARY_KEY, + permissionId, + ]; + const r = await kv.get(k); + if (r) { + atomicOp + .check(r) + .delete(k); + } + + if (r.value?.userId) { + atomicOp.delete([PERMISSION_BY_USER_KEY, permissionId]); + } + + if (r.value?.groupId) { + atomicOp.delete([PERMISSION_BY_GROUP_KEY, permissionId]); } - return res; + if (r.value?.module) { + atomicOp.delete([PERMISSION_BY_MODULE_KEY, permissionId]); + } + + res = await atomicOp.commit(); + } + + return res; } diff --git a/modules/permission/permission.test.ts b/modules/permission/permission.test.ts index 4ee3128af..d48e6bca3 100644 --- a/modules/permission/permission.test.ts +++ b/modules/permission/permission.test.ts @@ -1,12 +1,12 @@ import { assertEquals } from "@app/core/server/deps.ts"; import { - createPermission, - deletePermission, - getPermission, - listPemissionByGroupId, - listPemissionByModule, - listPermissionByUserId, - updatePermission, + createPermission, + deletePermission, + getPermission, + listPemissionByGroupId, + listPemissionByModule, + listPermissionByUserId, + updatePermission, } from "@app/modules/permission/permission.service.ts"; import { createUser } from "@app/modules/user/user.service.ts"; import { createGroup } from "@app/modules/group/group.service.ts"; @@ -20,132 +20,132 @@ let g: GroupType; let p: PermissionType; Deno.test({ - name: "createPermission", - async fn() { - await reset(); - u = await createUser({ - username: "john", - password: "password", - email: "john@email.com", - }); - g = await createGroup({ name: "admin" }); - createGroup({ name: "user" }); + name: "createPermission", + async fn() { + await reset(); + u = await createUser({ + username: "john", + password: "password", + email: "john@email.com", + }); + g = await createGroup({ name: "admin" }); + createGroup({ name: "user" }); - if (u && g.groupId) { - p = await createPermission({ - groupId: g.groupId, - userId: u.userId, - permission: [ - "read", - "write", - "execute", - ], - }); + if (u && g.groupId) { + p = await createPermission({ + groupId: g.groupId, + userId: u.userId, + permission: [ + "read", + "write", + "execute", + ], + }); - assertEquals(p.userId, u.userId); - assertEquals(p.groupId, g.groupId); - assertEquals(p.permission, [ - "read", - "write", - "execute", - ]); - } - }, + assertEquals(p.userId, u.userId); + assertEquals(p.groupId, g.groupId); + assertEquals(p.permission, [ + "read", + "write", + "execute", + ]); + } + }, }); Deno.test({ - name: "createPermission user not found", - async fn() { - try { - await createPermission( - { - userId: "not exist", - groupId: "not exist", - permission: [ - "read", - "write", - "execute", - ], - }, - ); - } catch (error) { - const err = error as Error; - assertEquals(err.message, "User is not found"); - } - }, + name: "createPermission user not found", + async fn() { + try { + await createPermission( + { + userId: "not exist", + groupId: "not exist", + permission: [ + "read", + "write", + "execute", + ], + }, + ); + } catch (error) { + const err = error as Error; + assertEquals(err.message, "User is not found"); + } + }, }); Deno.test({ - name: "createPermission not valid", - async fn() { - try { - await createPermission( - { - userId: "not exist", - groupId: "not exist", - permission: [], - }, - ); - } catch (error) { - const err = error as Error; - assertEquals(err.message, "Permission is not valid"); - } - }, + name: "createPermission not valid", + async fn() { + try { + await createPermission( + { + userId: "not exist", + groupId: "not exist", + permission: [], + }, + ); + } catch (error) { + const err = error as Error; + assertEquals(err.message, "Permission is not valid"); + } + }, }); Deno.test({ - name: "getPermission", - async fn() { - const res = await getPermission(p.permissionId); - assertEquals(res?.permission, ["read", "write", "execute"]); - assertEquals(res?.permissionId, p.permissionId); - }, + name: "getPermission", + async fn() { + const res = await getPermission(p.permissionId); + assertEquals(res?.permission, ["read", "write", "execute"]); + assertEquals(res?.permissionId, p.permissionId); + }, }); Deno.test({ - name: "getPemissionByUserId", - async fn() { - const [res] = await collectValues(listPermissionByUserId(p.userId)); - assertEquals(res?.permission, ["read", "write", "execute"]); - assertEquals(res?.permissionId, p.permissionId); - }, + name: "getPemissionByUserId", + async fn() { + const [res] = await collectValues(listPermissionByUserId(p.userId)); + assertEquals(res?.permission, ["read", "write", "execute"]); + assertEquals(res?.permissionId, p.permissionId); + }, }); Deno.test({ - name: "getPemissionByGroupId", - async fn() { - const [res] = await collectValues( - listPemissionByGroupId(p.groupId), - ); - assertEquals(res?.permission, ["read", "write", "execute"]); - assertEquals(res?.permissionId, p.permissionId); - }, + name: "getPemissionByGroupId", + async fn() { + const [res] = await collectValues( + listPemissionByGroupId(p.groupId), + ); + assertEquals(res?.permission, ["read", "write", "execute"]); + assertEquals(res?.permissionId, p.permissionId); + }, }); Deno.test({ - name: "getPemissionByModule", - async fn() { - const [res] = await collectValues(listPemissionByModule("null")); - assertEquals(res?.permission, ["read", "write", "execute"]); - assertEquals(res?.permissionId, p.permissionId); - }, + name: "getPemissionByModule", + async fn() { + const [res] = await collectValues(listPemissionByModule("null")); + assertEquals(res?.permission, ["read", "write", "execute"]); + assertEquals(res?.permissionId, p.permissionId); + }, }); Deno.test({ - name: "updatePermission", - async fn() { - p.active = false; - p.permission = ["read"]; - const res = await updatePermission(p); - assertEquals(res?.active, false); - assertEquals(res?.permission, ["read"]); - }, + name: "updatePermission", + async fn() { + p.active = false; + p.permission = ["read"]; + const res = await updatePermission(p); + assertEquals(res?.active, false); + assertEquals(res?.permission, ["read"]); + }, }); Deno.test({ - name: "deletePermission", - async fn() { - const res = await deletePermission(p.permissionId); - assertEquals(res.ok, true); - }, + name: "deletePermission", + async fn() { + const res = await deletePermission(p.permissionId); + assertEquals(res.ok, true); + }, }); diff --git a/modules/permission/permission.type.ts b/modules/permission/permission.type.ts index 3a814e3b4..7d0a978e5 100644 --- a/modules/permission/permission.type.ts +++ b/modules/permission/permission.type.ts @@ -1,13 +1,13 @@ export type PermissionOpType = "read" | "write" | "execute"; export type PermissionArgsType = { - groupId: string; - userId: string; - permission: PermissionOpType[]; - module?: string; + groupId: string; + userId: string; + permission: PermissionOpType[]; + module?: string; }; export type PermissionType = { - active: boolean; - permissionId: string; + active: boolean; + permissionId: string; } & PermissionArgsType; diff --git a/modules/room/mod.ts b/modules/room/mod.ts index 17454b3d0..adf0a02ad 100644 --- a/modules/room/mod.ts +++ b/modules/room/mod.ts @@ -7,142 +7,142 @@ import { createCollection } from "@app/modules/store/mod.ts"; import { DAY } from "jsr:@std/datetime@^0.221.0/constants"; const initRooms = [ - { name: "startup", id: "01JBNPF4KWPD0TYS6SSSHZGY5E" }, - { name: "english", id: "01JACJFARBMNDSF1FCAH776YST" }, - { name: "health", id: "01JBNM5X3Y12692X1EDCCR3G2Z" }, - { name: "finance", id: "01JBNM75G892T2K4FN7Q10AYAZ" }, - { name: "travel", id: "01JBNM7ZRDMZTM21F6X0986YB4" }, - { name: "food", id: "01JBNM9H6TTQMF9C2JBX5CEJSY" }, - { name: "automotive", id: "01JBNMBY9PV6ZY22RZJSKXAP2B" }, - { name: "sport", id: "01JBNMDE6XR6S4WKKSZ715B913" }, - { name: "education", id: "01JBNMYGN7VSN4H49YF63TX08R" }, - { name: "trading", id: "01JBNPHEMX9GYCH0RXB1WR75VR" }, - { name: "fashion", id: "01JBNVTCRG7A5SQ61BHJZKHT2Y" }, - { name: "furniture", id: "01JBNVV1H4DM86NAJ59DBCSMNA" }, - { name: "electronic", id: "01JBNVW2H8GWWACVB6R7XYR49F" }, + { name: "startup", id: "01JBNPF4KWPD0TYS6SSSHZGY5E" }, + { name: "english", id: "01JACJFARBMNDSF1FCAH776YST" }, + { name: "health", id: "01JBNM5X3Y12692X1EDCCR3G2Z" }, + { name: "finance", id: "01JBNM75G892T2K4FN7Q10AYAZ" }, + { name: "travel", id: "01JBNM7ZRDMZTM21F6X0986YB4" }, + { name: "food", id: "01JBNM9H6TTQMF9C2JBX5CEJSY" }, + { name: "automotive", id: "01JBNMBY9PV6ZY22RZJSKXAP2B" }, + { name: "sport", id: "01JBNMDE6XR6S4WKKSZ715B913" }, + { name: "education", id: "01JBNMYGN7VSN4H49YF63TX08R" }, + { name: "trading", id: "01JBNPHEMX9GYCH0RXB1WR75VR" }, + { name: "fashion", id: "01JBNVTCRG7A5SQ61BHJZKHT2Y" }, + { name: "furniture", id: "01JBNVV1H4DM86NAJ59DBCSMNA" }, + { name: "electronic", id: "01JBNVW2H8GWWACVB6R7XYR49F" }, ]; const g = [ - { name: "global", id: "01JAC4GM721KGRWZHG53SMXZP0" }, - { - name: "jobs", - id: "01JACBS4WXSJ1EG8G5C6NVHY7E", - }, - { name: "news", id: "01JBNM8TFVV961WFHCDK50N7CV" }, + { name: "global", id: "01JAC4GM721KGRWZHG53SMXZP0" }, + { + name: "jobs", + id: "01JACBS4WXSJ1EG8G5C6NVHY7E", + }, + { name: "news", id: "01JBNM8TFVV961WFHCDK50N7CV" }, ]; const i = initRooms.sort((a, b) => { - return a.name.localeCompare(b.name); + return a.name.localeCompare(b.name); }); const arr = [...g, ...i]; type Arr = { - type: string; - username: string; - img: string; - messages: { - msg: any; - time: string; - id: string | number | symbol; - }[]; + type: string; + username: string; + img: string; + messages: { + msg: any; + time: string; + id: string | number | symbol; + }[]; }; async function getMessageFromRoom( - ctx: Context, - room: string, - username: string, + ctx: Context, + room: string, + username: string, ) { - let store = ctx.stores.get(room); - if (!store) { - store = await createCollection("rooms", room); - ctx.stores.set(room, store); - } - const entries = store?.entries().toArray(); + let store = ctx.stores.get(room); + if (!store) { + store = await createCollection("rooms", room); + ctx.stores.set(room, store); + } + const entries = store?.entries().toArray(); - const o = entries.map(([id, { value }]) => ({ - type: value.type, - username: value.message.username, - img: value.message.img, - messages: [{ - msg: value.message.msg, - time: ulidToDate(id as string), - id, - }], - })); + const o = entries.map(([id, { value }]) => ({ + type: value.type, + username: value.message.username, + img: value.message.img, + messages: [{ + msg: value.message.msg, + time: ulidToDate(id as string), + id, + }], + })); - const y: Arr[] = []; - const updatedData = [...o]; - for (const e of updatedData) { - const l = y[y.length - 1]; - if (l && l.username === e.username) { - l.messages.push(e.messages[0]); - } else { - y.push(e); - } + const y: Arr[] = []; + const updatedData = [...o]; + for (const e of updatedData) { + const l = y[y.length - 1]; + if (l && l.username === e.username) { + l.messages.push(e.messages[0]); + } else { + y.push(e); } + } - return y; + return y; } export default function roomModules(s: Fastro) { - s.get("/api/message/:room_id/:username", async (req, ctx) => { - const r = req.params?.room_id; - const u = req.params?.username; - if (!r || !u) return Response.json([]); - const room = await getMessageFromRoom(ctx, r, u); - if (!room) return Response.json([]); - return Response.json(room); - }); + s.get("/api/message/:room_id/:username", async (req, ctx) => { + const r = req.params?.room_id; + const u = req.params?.username; + if (!r || !u) return Response.json([]); + const room = await getMessageFromRoom(ctx, r, u); + if (!room) return Response.json([]); + return Response.json(room); + }); - s.get("/api/room", async (req, ctx) => { - const store = ctx.stores.get("rooms"); - await store?.syncMap(); - const entries = store?.entries().toArray(); - // console.log("entries", entries); - if (!entries) return Response.json([]); - const r: any = []; - entries.map(([id, { value }]) => { - r.push({ id, name: value.name }); - }); - const rooms = [...arr, ...r]; - return Response.json(rooms); + s.get("/api/room", async (req, ctx) => { + const store = ctx.stores.get("rooms"); + await store?.syncMap(); + const entries = store?.entries().toArray(); + // console.log("entries", entries); + if (!entries) return Response.json([]); + const r: any = []; + entries.map(([id, { value }]) => { + r.push({ id, name: value.name }); }); + const rooms = [...arr, ...r]; + return Response.json(rooms); + }); - s.post("/api/room/:name", async (req, ctx) => { - const roomStore = ctx.stores.get("rooms"); - const entries = roomStore?.entries().toArray(); - const name = req.params?.name || ""; - const found = entries?.find(([id, { value }]) => { - return value.name === name; - }); + s.post("/api/room/:name", async (req, ctx) => { + const roomStore = ctx.stores.get("rooms"); + const entries = roomStore?.entries().toArray(); + const name = req.params?.name || ""; + const found = entries?.find(([id, { value }]) => { + return value.name === name; + }); - const roomName = found ? `${name}-${new Date().getTime()}` : name; - const room = { name: roomName }; - const roomId = ulid(); - await roomStore?.set(roomId, room, DAY).commit(); - return Response.json({ - message: `Room created`, - room: { name: roomName, id: roomId }, - }, { - status: STATUS_CODE.Created, - }); + const roomName = found ? `${name}-${new Date().getTime()}` : name; + const room = { name: roomName }; + const roomId = ulid(); + await roomStore?.set(roomId, room, DAY).commit(); + return Response.json({ + message: `Room created`, + room: { name: roomName, id: roomId }, + }, { + status: STATUS_CODE.Created, }); + }); - s.get("/api/room/:id", async (req, ctx) => { - const target = req.params?.id; - if (!target) return Response.json([]); + s.get("/api/room/:id", async (req, ctx) => { + const target = req.params?.id; + if (!target) return Response.json([]); - const store = ctx.stores.get("rooms"); - // console.log(store); - const entries = store?.entries().toArray(); - if (!entries) return Response.json([]); - const r: any = []; - entries.map(([id, { value }]) => r.push({ id, name: value.name })); + const store = ctx.stores.get("rooms"); + // console.log(store); + const entries = store?.entries().toArray(); + if (!entries) return Response.json([]); + const r: any = []; + entries.map(([id, { value }]) => r.push({ id, name: value.name })); - const rooms = [...arr, ...r]; - const room = rooms.find((v) => v.id.toString() === target); - if (!room) return Response.json([]); - return Response.json(room); - }); + const rooms = [...arr, ...r]; + const room = rooms.find((v) => v.id.toString() === target); + if (!room) return Response.json([]); + return Response.json(room); + }); - return s; + return s; } diff --git a/modules/socket/init.ts b/modules/socket/init.ts index 15c5f3414..5d7d6c436 100644 --- a/modules/socket/init.ts +++ b/modules/socket/init.ts @@ -6,16 +6,16 @@ const id = ulid(); const time = ulidToDate(id); export const initialData: DataType[] = [ - { - type: "message", - username: "admin", - img: "https://avatars.githubusercontent.com/in/15368?v=4", - messages: [ - { - msg: "Hello world!", - time, - id, - }, - ], - }, + { + type: "message", + username: "admin", + img: "https://avatars.githubusercontent.com/in/15368?v=4", + messages: [ + { + msg: "Hello world!", + time, + id, + }, + ], + }, ]; diff --git a/modules/socket/mod.ts b/modules/socket/mod.ts index aabbb2796..299dac037 100644 --- a/modules/socket/mod.ts +++ b/modules/socket/mod.ts @@ -4,131 +4,131 @@ import { createCollection } from "@app/modules/store/mod.ts"; import { DAY } from "jsr:@std/datetime@^0.221.0/constants"; interface Message { - img: string; - username: string; - msg: string; - id: string; + img: string; + username: string; + msg: string; + id: string; } interface Data { - type: string; - room: string; - user: string; - message?: Message; + type: string; + room: string; + user: string; + message?: Message; } export default function socketModule(s: Fastro) { - function broadcastMessage(ctx: Context, room: string, message: string) { - const c = ctx.stores.get("connected"); - if (!c) return; - const entries = c.entries().toArray(); - // console.log("broadcastMessage:", JSON.stringify(entries)); - if (entries) { - for (const key in entries) { - const [, { value: { socket } }] = entries[key]; - if (socket.readyState === WebSocket.OPEN) { - socket.send(message); - } - } + function broadcastMessage(ctx: Context, room: string, message: string) { + const c = ctx.stores.get("connected"); + if (!c) return; + const entries = c.entries().toArray(); + // console.log("broadcastMessage:", JSON.stringify(entries)); + if (entries) { + for (const key in entries) { + const [, { value: { socket } }] = entries[key]; + if (socket.readyState === WebSocket.OPEN) { + socket.send(message); } + } } + } - async function broadcastConnection(ctx: Context, data: Data) { - const c = ctx.stores.get("connected"); - if (!c) return; - const entries = c.entries().toArray(); - const connected = Array.from(entries).map(([, { value }]) => ({ - username: value.data.username, - room: value.data.room, - avatar_url: value.data.avatar_url, - })); + async function broadcastConnection(ctx: Context, data: Data) { + const c = ctx.stores.get("connected"); + if (!c) return; + const entries = c.entries().toArray(); + const connected = Array.from(entries).map(([, { value }]) => ({ + username: value.data.username, + room: value.data.room, + avatar_url: value.data.avatar_url, + })); - for (const key in entries) { - const [, { value: { socket } }] = entries[key]; - if (socket.readyState === WebSocket.OPEN) { - socket.send(JSON.stringify(connected)); - } - } + for (const key in entries) { + const [, { value: { socket } }] = entries[key]; + if (socket.readyState === WebSocket.OPEN) { + socket.send(JSON.stringify(connected)); + } } + } - async function joinRoom( - ctx: Context, - socket: WebSocket, - data: Data, - ) { - const connected = ctx.stores.get("connected"); - if (data.user) { - connected?.set(data.user, { data, socket }, DAY); - } + async function joinRoom( + ctx: Context, + socket: WebSocket, + data: Data, + ) { + const connected = ctx.stores.get("connected"); + if (data.user) { + connected?.set(data.user, { data, socket }, DAY); } + } - const injectData = async (ctx: Context, data: Data) => { - let rs = ctx.stores.get(data.room); - const d: any = { ...data }; - delete d["room"]; - const id = data.message?.id as string; - if (!rs) { - const store = await createCollection("rooms", data.room); - await store.set(id, d, DAY).commit(); - ctx.stores.set(data.room, store); - rs = store; - } + const injectData = async (ctx: Context, data: Data) => { + let rs = ctx.stores.get(data.room); + const d: any = { ...data }; + delete d["room"]; + const id = data.message?.id as string; + if (!rs) { + const store = await createCollection("rooms", data.room); + await store.set(id, d, DAY).commit(); + ctx.stores.set(data.room, store); + rs = store; + } + + await rs?.set(id, d, DAY).commit(); + }; - await rs?.set(id, d, DAY).commit(); + function handleConnection( + ctx: Context, + socket: WebSocket, + ) { + socket.onopen = () => { + console.log("CONNECTED"); }; - function handleConnection( - ctx: Context, - socket: WebSocket, - ) { - socket.onopen = () => { - console.log("CONNECTED"); - }; + socket.onmessage = async (event) => { + const data: Data = JSON.parse(event.data); + await joinRoom(ctx, socket, data); + if (data.type === "ping") { + return await broadcastConnection(ctx, data); + } + if (data.type === "message" && data.message?.msg !== "") { + broadcastMessage( + ctx, + data.room, + JSON.stringify(data.message), + ); + return await injectData(ctx, data); + } + }; + socket.onclose = async () => { + const c = ctx.stores.get("connected"); + if (!c) return; + const entries = c.entries().toArray(); + for (const key in entries) { + const [username, { value: { socket, data } }] = entries[key]; + if (socket && socket.readyState !== WebSocket.OPEN) { + c.delete(username); + await broadcastConnection(ctx, { + type: "ping", + room: data.room, + user: data.user, + }); + } + } + console.log("DISCONNECTED"); + }; - socket.onmessage = async (event) => { - const data: Data = JSON.parse(event.data); - await joinRoom(ctx, socket, data); - if (data.type === "ping") { - return await broadcastConnection(ctx, data); - } - if (data.type === "message" && data.message?.msg !== "") { - broadcastMessage( - ctx, - data.room, - JSON.stringify(data.message), - ); - return await injectData(ctx, data); - } - }; - socket.onclose = async () => { - const c = ctx.stores.get("connected"); - if (!c) return; - const entries = c.entries().toArray(); - for (const key in entries) { - const [username, { value: { socket, data } }] = entries[key]; - if (socket && socket.readyState !== WebSocket.OPEN) { - c.delete(username); - await broadcastConnection(ctx, { - type: "ping", - room: data.room, - user: data.user, - }); - } - } - console.log("DISCONNECTED"); - }; + socket.onerror = (error) => console.error("ERROR:", error); + } - socket.onerror = (error) => console.error("ERROR:", error); + s.use((request, ctx) => { + const ws = request.headers.get("upgrade"); + if (ws && ws === "websocket") { + const { socket, response } = Deno.upgradeWebSocket(request); + handleConnection(ctx, socket); + return response; } + }); - s.use((request, ctx) => { - const ws = request.headers.get("upgrade"); - if (ws && ws === "websocket") { - const { socket, response } = Deno.upgradeWebSocket(request); - handleConnection(ctx, socket); - return response; - } - }); - - return s; + return s; } diff --git a/modules/store/mod.ts b/modules/store/mod.ts index 00995b273..d7f365ec1 100644 --- a/modules/store/mod.ts +++ b/modules/store/mod.ts @@ -4,26 +4,26 @@ import { Store } from "@app/core/map/mod.ts"; import { reset } from "@app/utils/db.ts"; export async function createCollection( - key: string, - ...namespace: Array + key: string, + ...namespace: Array ) { - const store = new Store({ - key, - namespace, - }); + const store = new Store({ + key, + namespace, + }); - await store.syncMap(); - return store; + await store.syncMap(); + return store; } export default async function storeModule(s: Fastro) { - s.stores.set("core", await createCollection("core")); - s.stores.set("users", await createCollection("users")); - s.stores.set("rooms", await createCollection("rooms")); - s.stores.set("connected", await createCollection("connected")); - s.get("/api/store/reset", async (req) => { - await reset(); - return Response.json([]); - }); - return s; + s.stores.set("core", await createCollection("core")); + s.stores.set("users", await createCollection("users")); + s.stores.set("rooms", await createCollection("rooms")); + s.stores.set("connected", await createCollection("connected")); + s.get("/api/store/reset", async (req) => { + await reset(); + return Response.json([]); + }); + return s; } diff --git a/modules/toc/toc.layout.tsx b/modules/toc/toc.layout.tsx index 19ecc14a8..83da92b2e 100644 --- a/modules/toc/toc.layout.tsx +++ b/modules/toc/toc.layout.tsx @@ -4,47 +4,47 @@ import { LayoutProps } from "../../core/server/types.ts"; import Header from "@app/components/header.tsx"; export default function ({ children, data }: LayoutProps< - { - title: string; - description: string; - image: string; - destination: string; - isLogin: boolean; - avatar_url: string; - html_url: string; - } + { + title: string; + description: string; + image: string; + destination: string; + isLogin: boolean; + avatar_url: string; + html_url: string; + } >) { - return ( - - - - - - - {`${data.title} | Fastro`} - - - -
    -
    - {children} -
    -