refactoring everything before moving on
This commit is contained in:
5
deno.lock
generated
5
deno.lock
generated
@ -673,6 +673,9 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"redirects": {
|
||||||
|
"https://deno.land/x/sleep/mod.ts": "https://deno.land/x/sleep@v1.3.0/mod.ts"
|
||||||
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
"https://deno.land/std@0.203.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee",
|
"https://deno.land/std@0.203.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee",
|
||||||
"https://deno.land/std@0.203.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56",
|
"https://deno.land/std@0.203.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56",
|
||||||
@ -686,6 +689,8 @@
|
|||||||
"https://deno.land/std@0.203.0/async/pool.ts": "47c1841cfa9c036144943d11747ddd44064f5baf8cb7ece25473ba873c6aceb0",
|
"https://deno.land/std@0.203.0/async/pool.ts": "47c1841cfa9c036144943d11747ddd44064f5baf8cb7ece25473ba873c6aceb0",
|
||||||
"https://deno.land/std@0.203.0/async/retry.ts": "296fb9c323e1325a69bee14ba947e7da7409a8dd9dd646d70cb51ea0d301f24e",
|
"https://deno.land/std@0.203.0/async/retry.ts": "296fb9c323e1325a69bee14ba947e7da7409a8dd9dd646d70cb51ea0d301f24e",
|
||||||
"https://deno.land/std@0.203.0/async/tee.ts": "47e42d35f622650b02234d43803d0383a89eb4387e1b83b5a40106d18ae36757",
|
"https://deno.land/std@0.203.0/async/tee.ts": "47e42d35f622650b02234d43803d0383a89eb4387e1b83b5a40106d18ae36757",
|
||||||
|
"https://deno.land/x/sleep@v1.3.0/mod.ts": "e9955ecd3228a000e29d46726cd6ab14b65cf83904e9b365f3a8d64ec61c1af3",
|
||||||
|
"https://deno.land/x/sleep@v1.3.0/sleep.ts": "b6abaca093b094b0c2bba94f287b19a60946a8d15764d168f83fcf555f5bb59e",
|
||||||
"https://wilsonl.in/minify-html/deno/0.15.0/index.js": "8e7ee5067ca84fb5d5a1f33118cac4998de0b7d80b3f56cc5c6728b84e6bfb70"
|
"https://wilsonl.in/minify-html/deno/0.15.0/index.js": "8e7ee5067ca84fb5d5a1f33118cac4998de0b7d80b3f56cc5c6728b84e6bfb70"
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
|
|||||||
@ -5,7 +5,12 @@ const schema = {
|
|||||||
req: z.obj({
|
req: z.obj({
|
||||||
password: z.string(),
|
password: z.string(),
|
||||||
}),
|
}),
|
||||||
res: z.result(z.string(), z.any()),
|
res: z.result(
|
||||||
|
z.obj({
|
||||||
|
isMatch: z.boolean(),
|
||||||
|
}),
|
||||||
|
z.any(),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loginApi = new Api("/login", "POST", schema);
|
export const loginApi = new Api("/login", "POST", schema);
|
||||||
|
|||||||
@ -5,9 +5,8 @@ import rateLimitMiddleware from "@src/middleware/rateLimiter.ts";
|
|||||||
import authMiddleware from "@src/middleware/auth.ts";
|
import authMiddleware from "@src/middleware/auth.ts";
|
||||||
import loggerMiddleware from "@src/middleware/logger.ts";
|
import loggerMiddleware from "@src/middleware/logger.ts";
|
||||||
import { SchemaValidationError, z } from "@shared/utils/validator.ts";
|
import { SchemaValidationError, z } from "@shared/utils/validator.ts";
|
||||||
import { Api } from "@src/lib/apiValidator.ts";
|
|
||||||
import { loginApi } from "./api.ts";
|
import { loginApi } from "./api.ts";
|
||||||
import { err, getMessageFromError, ok } from "@shared/utils/result.ts";
|
import { err, ok } from "@shared/utils/result.ts";
|
||||||
import admin from "@src/lib/admin.ts";
|
import admin from "@src/lib/admin.ts";
|
||||||
import {
|
import {
|
||||||
FailedToParseRequestAsJSON,
|
FailedToParseRequestAsJSON,
|
||||||
@ -49,15 +48,17 @@ router
|
|||||||
return c.html(eta.render("./index.html", {}));
|
return c.html(eta.render("./index.html", {}));
|
||||||
})
|
})
|
||||||
.get(["/login", "/login.html"], (c) => {
|
.get(["/login", "/login.html"], (c) => {
|
||||||
return c.html(eta.render("./login.html", {}));
|
const alreadyLoggedIn = c.cookies.get("token").map((token) =>
|
||||||
|
admin.sessions.verifyToken(token)
|
||||||
|
)
|
||||||
|
.toBoolean();
|
||||||
|
|
||||||
|
console.log(alreadyLoggedIn);
|
||||||
|
|
||||||
|
return c.html(eta.render("./login.html", { alreadyLoggedIn }));
|
||||||
});
|
});
|
||||||
|
|
||||||
const schema = {
|
admin.setPassword("Vermont5481");
|
||||||
req: z.obj({
|
|
||||||
password: z.string().max(1024),
|
|
||||||
}),
|
|
||||||
res: z.result(z.void(), z.string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
router.api(loginApi, async (c) => {
|
router.api(loginApi, async (c) => {
|
||||||
const r = await c
|
const r = await c
|
||||||
@ -68,7 +69,7 @@ router.api(loginApi, async (c) => {
|
|||||||
|
|
||||||
if (r.isErr()) {
|
if (r.isErr()) {
|
||||||
if (r.error.type === "AdminPasswordNotSetError") {
|
if (r.error.type === "AdminPasswordNotSetError") {
|
||||||
return c.json(
|
return c.json400(
|
||||||
err({
|
err({
|
||||||
type: r.error.type,
|
type: r.error.type,
|
||||||
msg: r.error.message,
|
msg: r.error.message,
|
||||||
@ -78,6 +79,9 @@ router.api(loginApi, async (c) => {
|
|||||||
return handleCommonErrors(c, r.error);
|
return handleCommonErrors(c, r.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isMatch = r.value;
|
||||||
|
|
||||||
|
if (isMatch) {
|
||||||
return admin.sessions.create()
|
return admin.sessions.create()
|
||||||
.map(({ value, expires }) => {
|
.map(({ value, expires }) => {
|
||||||
c.cookies.set({
|
c.cookies.set({
|
||||||
@ -85,11 +89,16 @@ router.api(loginApi, async (c) => {
|
|||||||
value,
|
value,
|
||||||
expires,
|
expires,
|
||||||
});
|
});
|
||||||
return ok();
|
return ok({ isMatch: true });
|
||||||
}).match(
|
}).match(
|
||||||
() => c.json(ok()),
|
(v) => c.json(v),
|
||||||
(e) => handleCommonErrors(c, e),
|
(e) => handleCommonErrors(c, e),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return c.json(ok({
|
||||||
|
isMatch: false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleCommonErrors(
|
function handleCommonErrors(
|
||||||
@ -106,11 +115,18 @@ function handleCommonErrors(
|
|||||||
{ status: 500 },
|
{ status: 500 },
|
||||||
);
|
);
|
||||||
case "FailedToParseRequestAsJSON":
|
case "FailedToParseRequestAsJSON":
|
||||||
case "SchemaValiationError":
|
|
||||||
return c.json(
|
return c.json(
|
||||||
err(error),
|
err(error),
|
||||||
{ status: 400 },
|
{ status: 400 },
|
||||||
);
|
);
|
||||||
|
case "SchemaValiationError":
|
||||||
|
return c.json(
|
||||||
|
err({
|
||||||
|
type: "ValidationError",
|
||||||
|
msg: error.msg,
|
||||||
|
}),
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
0
server/public/js/index.js
Normal file
0
server/public/js/index.js
Normal file
@ -1 +1 @@
|
|||||||
import{loginApi as o}from"./shared.bundle.js";const s=document.getElementById("loginForm"),m=document.getElementById("passwordInput");s.addEventListener("submit",async e=>{e.preventDefault();const t=m.value,n=await o.makeRequest({password:t},{});console.log(n)});
|
import{loginApi as s}from"./shared.bundle.js";const o=document.getElementById("loginForm"),i=document.getElementById("passwordInput"),t=document.getElementById("errDiv");o.addEventListener("submit",async n=>{n.preventDefault();const r=i.value,e=(await s.makeRequest({password:r},{})).flatten();e.isErr()?e.error.type==="RequestValidationError"&&(t.innerText=e.error.msg):e.value.isMatch?window.location.href="/":t.innerText="invalid password"});
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
1
server/src/js/index.ts
Normal file
1
server/src/js/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
@ -6,13 +6,24 @@ const form = document.getElementById("loginForm") as HTMLFormElement;
|
|||||||
const passwordInput = document.getElementById(
|
const passwordInput = document.getElementById(
|
||||||
"passwordInput",
|
"passwordInput",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
|
const errDiv = document.getElementById("errDiv") as HTMLDivElement;
|
||||||
|
|
||||||
form.addEventListener("submit", async (e) => {
|
form.addEventListener("submit", async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const password = passwordInput.value;
|
const password = passwordInput.value;
|
||||||
|
|
||||||
const res = await loginApi.makeRequest({ password }, {});
|
const res = (await loginApi.makeRequest({ password }, {})).flatten();
|
||||||
|
|
||||||
console.log(res);
|
if (res.isErr()) {
|
||||||
|
if (res.error.type === "RequestValidationError") {
|
||||||
|
errDiv.innerText = res.error.msg;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!res.value.isMatch) {
|
||||||
|
errDiv.innerText = "invalid password";
|
||||||
|
} else {
|
||||||
|
window.location.href = "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,15 +1,9 @@
|
|||||||
import { Result } from "@shared/utils/result.ts";
|
import { InferSchemaType, Schema } from "@shared/utils/validator.ts";
|
||||||
import {
|
|
||||||
InferSchemaType,
|
|
||||||
ResultSchema,
|
|
||||||
Schema,
|
|
||||||
z,
|
|
||||||
} from "@shared/utils/validator.ts";
|
|
||||||
import {
|
import {
|
||||||
RequestValidationError,
|
RequestValidationError,
|
||||||
ResponseValidationError,
|
ResponseValidationError,
|
||||||
} from "@src/lib/errors.ts";
|
} from "@src/lib/errors.ts";
|
||||||
import { errAsync, okAsync, ResultAsync } from "@shared/utils/resultasync.ts";
|
import { ResultAsync } from "@shared/utils/resultasync.ts";
|
||||||
|
|
||||||
export type ExtractRouteParams<T extends string> = T extends string
|
export type ExtractRouteParams<T extends string> = T extends string
|
||||||
? T extends `${infer _Start}:${infer Param}/${infer Rest}`
|
? T extends `${infer _Start}:${infer Param}/${infer Rest}`
|
||||||
@ -85,4 +79,16 @@ export class Api<
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public makeSafeRequest(
|
||||||
|
reqBody: InferSchemaType<ReqSchema>,
|
||||||
|
params: { [K in ExtractRouteParams<Path>]: string },
|
||||||
|
): ResultAsync<InferSchemaType<ResSchema>, ResponseValidationError> {
|
||||||
|
return this.makeRequest(reqBody, params).mapErr((e) => {
|
||||||
|
if (e.type === "RequestValidationError") {
|
||||||
|
throw "Failed to validate request";
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,19 +3,13 @@ import { type ExtractRouteParams } from "@lib/router.ts";
|
|||||||
import { fromNullableVal, none, Option, some } from "@shared/utils/option.ts";
|
import { fromNullableVal, none, Option, some } from "@shared/utils/option.ts";
|
||||||
import { deleteCookie, getCookies, setCookie } from "@std/http/cookie";
|
import { deleteCookie, getCookies, setCookie } from "@std/http/cookie";
|
||||||
import { type Cookie } from "@std/http/cookie";
|
import { type Cookie } from "@std/http/cookie";
|
||||||
import {
|
import { getMessageFromError } from "@shared/utils/result.ts";
|
||||||
Err,
|
|
||||||
getMessageFromError,
|
|
||||||
Ok,
|
|
||||||
type Result,
|
|
||||||
ResultFromJSON,
|
|
||||||
} from "@shared/utils/result.ts";
|
|
||||||
import {
|
import {
|
||||||
InferSchemaType,
|
InferSchemaType,
|
||||||
Schema,
|
Schema,
|
||||||
SchemaValidationError,
|
SchemaValidationError,
|
||||||
} from "@shared/utils/validator.ts";
|
} from "@shared/utils/validator.ts";
|
||||||
import { errAsync, ResultAsync } from "@shared/utils/resultasync.ts";
|
import { ResultAsync } from "@shared/utils/resultasync.ts";
|
||||||
import log from "@shared/utils/logger.ts";
|
import log from "@shared/utils/logger.ts";
|
||||||
import { FailedToParseRequestAsJSON } from "@src/lib/errors.ts";
|
import { FailedToParseRequestAsJSON } from "@src/lib/errors.ts";
|
||||||
|
|
||||||
@ -141,7 +135,10 @@ export class Context<
|
|||||||
return none;
|
return none;
|
||||||
}
|
}
|
||||||
|
|
||||||
public json(body?: object | string, init: ResponseInit = {}): Response {
|
public json(
|
||||||
|
body?: ResSchema extends Schema<infer T> ? T : object | string,
|
||||||
|
init: ResponseInit = {},
|
||||||
|
): Response {
|
||||||
const headers = mergeHeaders(
|
const headers = mergeHeaders(
|
||||||
SECURITY_HEADERS,
|
SECURITY_HEADERS,
|
||||||
this._responseHeaders,
|
this._responseHeaders,
|
||||||
|
|||||||
83
server/src/lib/devices.ts
Normal file
83
server/src/lib/devices.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import usbip from "@src/lib/usbip.ts";
|
||||||
|
import {
|
||||||
|
type CommandExecutionError,
|
||||||
|
DeviceDetailed,
|
||||||
|
type UsbipUnknownError,
|
||||||
|
} from "@shared/utils/usbip.ts";
|
||||||
|
import { none } from "@shared/utils/option.ts";
|
||||||
|
import { InferSchemaType, z } from "@shared/utils/validator.ts";
|
||||||
|
import log from "@shared/utils/logger.ts";
|
||||||
|
import { errAsync, ResultAsync } from "@shared/utils/resultasync.ts";
|
||||||
|
|
||||||
|
class Devices {
|
||||||
|
private readonly devices: Map<string, Device> = new Map();
|
||||||
|
|
||||||
|
updateDevices(): ResultAsync<
|
||||||
|
void,
|
||||||
|
CommandExecutionError | UsbipUnknownError
|
||||||
|
> {
|
||||||
|
return usbip.getDevicesDetailed().mapErr((e) => {
|
||||||
|
log.error("Failed to update devices!");
|
||||||
|
return e;
|
||||||
|
}).map((d) => d.unwrapOr([])).map(
|
||||||
|
(devices) => {
|
||||||
|
const current = new Set(devices.map((d) => d.busid));
|
||||||
|
const old = new Set(this.devices.keys());
|
||||||
|
|
||||||
|
const connected = current.difference(old);
|
||||||
|
const disconnected = old.difference(current);
|
||||||
|
|
||||||
|
for (const device of devices) {
|
||||||
|
if (connected.has(device.busid)) {
|
||||||
|
this.devices.set(
|
||||||
|
device.busid,
|
||||||
|
this.deviceFromDetailed(device),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const device of disconnected) {
|
||||||
|
this.devices.delete(device);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceFromDetailed(d: DeviceDetailed): Device {
|
||||||
|
return {
|
||||||
|
busid: d.busid,
|
||||||
|
usbid: d.usbid,
|
||||||
|
vendor: d.vendor,
|
||||||
|
name: d.name,
|
||||||
|
displayName: none,
|
||||||
|
description: none,
|
||||||
|
connectedAt: new Date(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deviceSchema = z.obj({
|
||||||
|
busid: z.string(),
|
||||||
|
usbid: z.option(z.string()),
|
||||||
|
vendor: z.option(z.string()),
|
||||||
|
name: z.option(z.string()),
|
||||||
|
displayName: z.option(z.string()),
|
||||||
|
description: z.option(z.string()),
|
||||||
|
connectedAt: z.date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const test = new Devices();
|
||||||
|
|
||||||
|
await test.updateDevices();
|
||||||
|
|
||||||
|
console.log(test);
|
||||||
|
|
||||||
|
import { sleep } from "https://deno.land/x/sleep/mod.ts";
|
||||||
|
|
||||||
|
await sleep(5);
|
||||||
|
|
||||||
|
await test.updateDevices();
|
||||||
|
|
||||||
|
console.log(test);
|
||||||
|
|
||||||
|
export type Device = InferSchemaType<typeof deviceSchema>;
|
||||||
@ -1,9 +1,16 @@
|
|||||||
import { fromNullableVal, none, Option, some } from "@shared/utils/option.ts";
|
import { fromNullableVal, none, Option, some } from "@shared/utils/option.ts";
|
||||||
|
|
||||||
const DEFAULT_WILDCARD_SYMBOL = "*";
|
const DEFAULT_WILDCARD = "*";
|
||||||
const DEFAULT_PARAM_PREFIX = ":";
|
const DEFAULT_PARAM_PREFIX = ":";
|
||||||
const DEFAULT_PATH_SEPARATOR = "/";
|
const DEFAULT_PATH_SEPARATOR = "/";
|
||||||
|
|
||||||
|
export type Params = Record<string, string>;
|
||||||
|
|
||||||
|
interface RouteMatch<T> {
|
||||||
|
value: T;
|
||||||
|
params: Params;
|
||||||
|
}
|
||||||
|
|
||||||
interface Node<T> {
|
interface Node<T> {
|
||||||
handler: Option<T>;
|
handler: Option<T>;
|
||||||
paramNames: string[];
|
paramNames: string[];
|
||||||
@ -29,52 +36,52 @@ class StaticNode<T> implements Node<T> {
|
|||||||
this.handler = fromNullableVal(handler);
|
this.handler = fromNullableVal(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
addStaticChild(segment: string, handler?: T): StaticNode<T> {
|
private addStaticChild(segment: string, handler?: T): StaticNode<T> {
|
||||||
const child = new StaticNode(handler);
|
const child = new StaticNode(handler);
|
||||||
this.staticChildren.set(segment, child);
|
this.staticChildren.set(segment, child);
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
setDynamicChild(handler?: T): DynamicNode<T> {
|
private createDynamicChild(handler?: T): DynamicNode<T> {
|
||||||
const child = new DynamicNode(handler);
|
const child = new DynamicNode(handler);
|
||||||
this.dynamicChild = some(child);
|
this.dynamicChild = some(child);
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
setWildcardNode(handler?: T): WildcardNode<T> {
|
private createWildcardNode(handler?: T): WildcardNode<T> {
|
||||||
const child = new WildcardNode(handler);
|
const child = new WildcardNode(handler);
|
||||||
this.wildcardChild = some(child);
|
this.wildcardChild = some(child);
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
addChild(
|
public addChild(
|
||||||
segment: string,
|
segment: string,
|
||||||
wildcardSymbol: string,
|
wildcardSymbol: string,
|
||||||
paramPrefixSymbol: string,
|
paramPrefixSymbol: string,
|
||||||
handler?: T,
|
handler?: T,
|
||||||
): Node<T> {
|
): Node<T> {
|
||||||
if (segment === wildcardSymbol) {
|
if (segment === wildcardSymbol) {
|
||||||
return this.setWildcardNode(handler);
|
return this.createWildcardNode(handler);
|
||||||
}
|
}
|
||||||
if (segment.startsWith(paramPrefixSymbol)) {
|
if (segment.startsWith(paramPrefixSymbol)) {
|
||||||
return this.setDynamicChild(handler);
|
return this.createDynamicChild(handler);
|
||||||
}
|
}
|
||||||
return this.addStaticChild(segment, handler);
|
return this.addStaticChild(segment, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
getStaticChild(segment: string): Option<StaticNode<T>> {
|
private getStaticChild(segment: string): Option<StaticNode<T>> {
|
||||||
return fromNullableVal(this.staticChildren.get(segment));
|
return fromNullableVal(this.staticChildren.get(segment));
|
||||||
}
|
}
|
||||||
|
|
||||||
getDynamicChild(): Option<DynamicNode<T>> {
|
public getDynamicChild(): Option<DynamicNode<T>> {
|
||||||
return this.dynamicChild;
|
return this.dynamicChild;
|
||||||
}
|
}
|
||||||
|
|
||||||
getWildcardChild(): Option<WildcardNode<T>> {
|
public getWildcardChild(): Option<WildcardNode<T>> {
|
||||||
return this.wildcardChild;
|
return this.wildcardChild;
|
||||||
}
|
}
|
||||||
|
|
||||||
getChild(segment: string): Option<Node<T>> {
|
public getChild(segment: string): Option<Node<T>> {
|
||||||
return this.getStaticChild(segment)
|
return this.getStaticChild(segment)
|
||||||
.orElse(() => this.getWildcardChild())
|
.orElse(() => this.getWildcardChild())
|
||||||
.orElse(() => this.getDynamicChild());
|
.orElse(() => this.getDynamicChild());
|
||||||
@ -89,7 +96,6 @@ class StaticNode<T> implements Node<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: get rid of fixed param name
|
|
||||||
class DynamicNode<T> extends StaticNode<T> implements Node<T> {
|
class DynamicNode<T> extends StaticNode<T> implements Node<T> {
|
||||||
constructor(
|
constructor(
|
||||||
handler?: T,
|
handler?: T,
|
||||||
@ -112,7 +118,7 @@ class WildcardNode<T> implements Node<T> {
|
|||||||
|
|
||||||
// Override to prevent adding children to a wildcard node
|
// Override to prevent adding children to a wildcard node
|
||||||
public addChild(): Node<T> {
|
public addChild(): Node<T> {
|
||||||
throw new Error("Cannot add child to a WildcardNode.");
|
throw new Error("Cannot add child to a wildcard (catch-all) node.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public getChild(): Option<Node<T>> {
|
public getChild(): Option<Node<T>> {
|
||||||
@ -128,16 +134,13 @@ class WildcardNode<T> implements Node<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using Node<T> as the unified type for tree nodes.
|
|
||||||
type TreeNode<T> = Node<T>;
|
|
||||||
|
|
||||||
export class RouterTree<T> {
|
export class RouterTree<T> {
|
||||||
public readonly root: StaticNode<T>;
|
public readonly root: StaticNode<T>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
handler?: T,
|
handler?: T,
|
||||||
private readonly wildcardSymbol: string = DEFAULT_WILDCARD_SYMBOL,
|
private readonly wildcardSymbol: string = DEFAULT_WILDCARD,
|
||||||
private readonly paramPrefixSymbol: string = DEFAULT_PARAM_PREFIX,
|
private readonly paramPrefix: string = DEFAULT_PARAM_PREFIX,
|
||||||
private readonly pathSeparator: string = DEFAULT_PATH_SEPARATOR,
|
private readonly pathSeparator: string = DEFAULT_PATH_SEPARATOR,
|
||||||
) {
|
) {
|
||||||
this.root = new StaticNode(handler);
|
this.root = new StaticNode(handler);
|
||||||
@ -146,7 +149,7 @@ export class RouterTree<T> {
|
|||||||
public add(path: string, handler: T): void {
|
public add(path: string, handler: T): void {
|
||||||
const segments = this.splitPath(path);
|
const segments = this.splitPath(path);
|
||||||
const paramNames: string[] = this.extractParams(segments);
|
const paramNames: string[] = this.extractParams(segments);
|
||||||
let current: TreeNode<T> = this.root;
|
let current: Node<T> = this.root;
|
||||||
|
|
||||||
for (const segment of segments) {
|
for (const segment of segments) {
|
||||||
current = current
|
current = current
|
||||||
@ -155,7 +158,7 @@ export class RouterTree<T> {
|
|||||||
current.addChild(
|
current.addChild(
|
||||||
segment,
|
segment,
|
||||||
this.wildcardSymbol,
|
this.wildcardSymbol,
|
||||||
this.paramPrefixSymbol,
|
this.paramPrefix,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -174,7 +177,7 @@ export class RouterTree<T> {
|
|||||||
public find(path: string): Option<RouteMatch<T>> {
|
public find(path: string): Option<RouteMatch<T>> {
|
||||||
const segments = this.splitPath(path);
|
const segments = this.splitPath(path);
|
||||||
const paramValues: string[] = [];
|
const paramValues: string[] = [];
|
||||||
let current: TreeNode<T> = this.root;
|
let current: Node<T> = this.root;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
for (; i < segments.length; i++) {
|
for (; i < segments.length; i++) {
|
||||||
@ -209,7 +212,7 @@ export class RouterTree<T> {
|
|||||||
|
|
||||||
public getHandler(path: string): Option<T> {
|
public getHandler(path: string): Option<T> {
|
||||||
const segments = this.splitPath(path);
|
const segments = this.splitPath(path);
|
||||||
let current: TreeNode<T> = this.root;
|
let current: Node<T> = this.root;
|
||||||
|
|
||||||
for (const segment of segments) {
|
for (const segment of segments) {
|
||||||
if (current.isWildcardNode()) break;
|
if (current.isWildcardNode()) break;
|
||||||
@ -224,6 +227,16 @@ export class RouterTree<T> {
|
|||||||
return current.handler;
|
return current.handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private traverseOrCreate(segments: string[]): Node<T> {
|
||||||
|
let node: Node<T> = this.root;
|
||||||
|
for (const segment of segments) {
|
||||||
|
if (node.isWildcardNode()) break;
|
||||||
|
node = node.getChild(segment).unwrapOrElse(() =>
|
||||||
|
node.addChild(segment, this.wildcardSymbol, this.paramPrefix)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private splitPath(path: string): string[] {
|
private splitPath(path: string): string[] {
|
||||||
const trimmed = path.trim().replace(/^\/+/, "").replace(/\/+$/, "");
|
const trimmed = path.trim().replace(/^\/+/, "").replace(/\/+$/, "");
|
||||||
return trimmed ? trimmed.split(this.pathSeparator) : [];
|
return trimmed ? trimmed.split(this.pathSeparator) : [];
|
||||||
@ -231,18 +244,11 @@ export class RouterTree<T> {
|
|||||||
|
|
||||||
public extractParams(segments: string[]): string[] {
|
public extractParams(segments: string[]): string[] {
|
||||||
return segments.filter((segment) =>
|
return segments.filter((segment) =>
|
||||||
segment.startsWith(this.paramPrefixSymbol)
|
segment.startsWith(this.paramPrefix)
|
||||||
).map((segment) => this.stripParamPrefix(segment));
|
).map((segment) => this.stripParamPrefix(segment));
|
||||||
}
|
}
|
||||||
|
|
||||||
public stripParamPrefix(segment: string): string {
|
public stripParamPrefix(segment: string): string {
|
||||||
return segment.slice(this.paramPrefixSymbol.length);
|
return segment.slice(this.paramPrefix.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Params = Record<string, string>;
|
|
||||||
|
|
||||||
interface RouteMatch<T> {
|
|
||||||
value: T;
|
|
||||||
params: Params;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -17,10 +17,6 @@ const authMiddleware: Middleware = async (c, next) => {
|
|||||||
return c.redirect("/login");
|
return c.redirect("/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path === LOGIN_PATH && isValid) {
|
|
||||||
return c.redirect("");
|
|
||||||
}
|
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,6 @@ const loggerMiddleware: Middleware = async (c, next) => {
|
|||||||
console.log("", c.req.method, c.path);
|
console.log("", c.req.method, c.path);
|
||||||
await next();
|
await next();
|
||||||
console.log("", c.res.status, "\n");
|
console.log("", c.res.status, "\n");
|
||||||
console.log(await c.res.json());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default loggerMiddleware;
|
export default loggerMiddleware;
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
<% layout("./layouts/layout.html") %>
|
<% layout("./layouts/layout.html") %>
|
||||||
|
|
||||||
this is an index.html
|
<script src="/public/js/index.js" defer></script>
|
||||||
|
|||||||
@ -1,7 +1,17 @@
|
|||||||
<% layout("./layouts/basic.html") %>
|
<% layout("./layouts/basic.html") %>
|
||||||
|
<% if (!it.alreadyLoggedIn) { %>
|
||||||
<main>
|
<main>
|
||||||
<form id=loginForm method=POST>
|
<form id=loginForm method=POST>
|
||||||
<p>password</p><input id=passwordInput name=password type=password><input value="sign in" type=submit>
|
<p>password</p><input id=passwordInput name=password type=password><input value="sign in" type=submit>
|
||||||
|
<div id="errDiv"></div>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
<script defer src=/public/js/login.js type=module></script>
|
<script defer src=/public/js/login.js type=module></script>
|
||||||
|
<% } else { %>
|
||||||
|
<main>
|
||||||
|
You are already logged in!
|
||||||
|
</main>
|
||||||
|
<script>
|
||||||
|
setTimeout(() => {window.location.href = "/"}, 1500)
|
||||||
|
</script>
|
||||||
|
<% } %>
|
||||||
|
|||||||
BIN
server/test.db
BIN
server/test.db
Binary file not shown.
@ -1 +1,6 @@
|
|||||||
<% layout("./layouts/layout.html") %> this is an index.html
|
<% layout("./layouts/layout.html") %>
|
||||||
|
|
||||||
|
devices:
|
||||||
|
<div id="Devices"></div>
|
||||||
|
|
||||||
|
<script defer src=/public/js/index.js></script>
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
<% layout("./layouts/basic.html") %> <main><form id=loginForm method=POST><p>password</p><input id=passwordInput name=password type=password><input value="sign in" type=submit></form></main><script defer src=/public/js/login.js type=module></script>
|
<% layout("./layouts/basic.html") %> <% if (!it.alreadyLoggedIn) { %> <main><form id=loginForm method=POST><p>password</p><input id=passwordInput name=password type=password><input value="sign in" type=submit><div id=errDiv></div></form></main><script defer src=/public/js/login.js type=module></script> <% } else { %> <main>You are already logged in!</main><script>setTimeout(() => {window.location.href = "/"}, 1500)</script> <% } %>
|
||||||
@ -371,7 +371,7 @@ export function flattenResult<R extends Result<any, any>>(
|
|||||||
currentResult = currentResult.value;
|
currentResult = currentResult.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentResult as FlattenResult<R>;
|
return ok(currentResult) as FlattenResult<R>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UnwrapOption<T> = T extends Option<infer V> ? V : T;
|
export type UnwrapOption<T> = T extends Option<infer V> ? V : T;
|
||||||
|
|||||||
@ -254,13 +254,12 @@ export function errAsync<E, T = never>(err: E): ResultAsync<T, E> {
|
|||||||
return new ResultAsync(Promise.resolve(new Err<T, E>(err)));
|
return new ResultAsync(Promise.resolve(new Err<T, E>(err)));
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FlattenResultAsync<R> = R extends ResultAsync<infer T, infer E>
|
type FlattenResultAsync<R> = R extends
|
||||||
? T extends ResultAsync<any, any>
|
ResultAsync<infer Inner, infer OuterError>
|
||||||
? FlattenResultAsync<T> extends ResultAsync<infer V, infer innerE>
|
? Inner extends ResultAsync<infer T, infer InnerError>
|
||||||
? ResultAsync<V, E | innerE>
|
? ResultAsync<T, OuterError | InnerError>
|
||||||
: never
|
|
||||||
: R
|
: R
|
||||||
: never;
|
: R;
|
||||||
|
|
||||||
type UnwrapPromise<Pr extends Promise<unknown>> = Pr extends Promise<infer U>
|
type UnwrapPromise<Pr extends Promise<unknown>> = Pr extends Promise<infer U>
|
||||||
? U
|
? U
|
||||||
|
|||||||
@ -9,42 +9,42 @@ import {
|
|||||||
some,
|
some,
|
||||||
} from "@shared/utils/option.ts";
|
} from "@shared/utils/option.ts";
|
||||||
|
|
||||||
class CommandExecutionError extends Error {
|
export class CommandExecutionError extends Error {
|
||||||
code = "CommandExecutionError";
|
type = "CommandExecutionError";
|
||||||
constructor(msg: string) {
|
constructor(msg: string) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeviceDoesNotExistError extends Error {
|
export class DeviceDoesNotExistError extends Error {
|
||||||
code = "DeviceDoesNotExist";
|
type = "DeviceDoesNotExist";
|
||||||
constructor(msg: string) {
|
constructor(msg: string) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeviceAlreadyBoundError extends Error {
|
export class DeviceAlreadyBoundError extends Error {
|
||||||
code = "DeviceAlreadyBound";
|
type = "DeviceAlreadyBound";
|
||||||
constructor(msg: string) {
|
constructor(msg: string) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeviceNotBound extends Error {
|
export class DeviceNotBound extends Error {
|
||||||
code = "DeviceNotBound";
|
type = "DeviceNotBound";
|
||||||
constructor(msg: string) {
|
constructor(msg: string) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UsbipUknownError extends Error {
|
export class UsbipUnknownError extends Error {
|
||||||
code = "UsbipUknownError";
|
type = "UsbipUknownError";
|
||||||
constructor(msg: string) {
|
constructor(msg: string) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type UsbipCommonError = DeviceDoesNotExistError | UsbipUknownError;
|
type UsbipCommonError = DeviceDoesNotExistError | UsbipUnknownError;
|
||||||
|
|
||||||
class UsbipManager {
|
class UsbipManager {
|
||||||
private readonly listDeatiledCmd = new Deno.Command("usbip", {
|
private readonly listDeatiledCmd = new Deno.Command("usbip", {
|
||||||
@ -84,7 +84,7 @@ class UsbipManager {
|
|||||||
return new DeviceDoesNotExistError(stderr);
|
return new DeviceDoesNotExistError(stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new UsbipUknownError(stderr);
|
return new UsbipUnknownError(stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseDetailedList(stdout: string): Option<DeviceDetailed[]> {
|
private parseDetailedList(stdout: string): Option<DeviceDetailed[]> {
|
||||||
@ -140,7 +140,7 @@ class UsbipManager {
|
|||||||
|
|
||||||
public getDevicesDetailed(): ResultAsync<
|
public getDevicesDetailed(): ResultAsync<
|
||||||
Option<DeviceDetailed[]>,
|
Option<DeviceDetailed[]>,
|
||||||
CommandExecutionError | UsbipUknownError
|
CommandExecutionError | UsbipUnknownError
|
||||||
> {
|
> {
|
||||||
return this.executeCommand(this.listDeatiledCmd).andThen(
|
return this.executeCommand(this.listDeatiledCmd).andThen(
|
||||||
({ stdout, stderr, success }) => {
|
({ stdout, stderr, success }) => {
|
||||||
@ -153,7 +153,7 @@ class UsbipManager {
|
|||||||
return ok(this.parseDetailedList(stdout));
|
return ok(this.parseDetailedList(stdout));
|
||||||
}
|
}
|
||||||
|
|
||||||
return err(new UsbipUknownError(stderr));
|
return err(new UsbipUnknownError(stderr));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -193,7 +193,7 @@ class UsbipManager {
|
|||||||
|
|
||||||
public getDevices(): ResultAsync<
|
public getDevices(): ResultAsync<
|
||||||
Option<Device[]>,
|
Option<Device[]>,
|
||||||
CommandExecutionError | UsbipUknownError
|
CommandExecutionError | UsbipUnknownError
|
||||||
> {
|
> {
|
||||||
return this.executeCommand(this.listParsableCmd).andThenAsync(
|
return this.executeCommand(this.listParsableCmd).andThenAsync(
|
||||||
({ stdout, stderr, success }) => {
|
({ stdout, stderr, success }) => {
|
||||||
@ -205,7 +205,7 @@ class UsbipManager {
|
|||||||
}
|
}
|
||||||
return okAsync(this.parseParsableList(stdout));
|
return okAsync(this.parseParsableList(stdout));
|
||||||
}
|
}
|
||||||
return errAsync(new UsbipUknownError(stderr));
|
return errAsync(new UsbipUnknownError(stderr));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -268,7 +268,7 @@ class CommandOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeviceDetailed {
|
export interface DeviceDetailed {
|
||||||
busid: string;
|
busid: string;
|
||||||
usbid: Option<string>;
|
usbid: Option<string>;
|
||||||
vendor: Option<string>;
|
vendor: Option<string>;
|
||||||
|
|||||||
2
vendor/deno.land/x/sleep@v1.3.0/mod.ts
vendored
Normal file
2
vendor/deno.land/x/sleep@v1.3.0/mod.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./sleep.ts"
|
||||||
|
|
||||||
11
vendor/deno.land/x/sleep@v1.3.0/sleep.ts
vendored
Normal file
11
vendor/deno.land/x/sleep@v1.3.0/sleep.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// I buy and sell https://FreedomCash.org
|
||||||
|
export function sleep(seconds: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, seconds * 1000))
|
||||||
|
}
|
||||||
|
export function sleepRandomAmountOfSeconds(minimumSeconds: number, maximumSeconds: number) {
|
||||||
|
const secondsOfSleep = getRandomArbitrary(minimumSeconds, maximumSeconds)
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, secondsOfSleep * 1000))
|
||||||
|
}
|
||||||
|
function getRandomArbitrary(min: number, max: number) {
|
||||||
|
return Math.random() * (max - min) + min
|
||||||
|
}
|
||||||
6
vendor/manifest.json
vendored
6
vendor/manifest.json
vendored
@ -1,5 +1,11 @@
|
|||||||
{
|
{
|
||||||
"modules": {
|
"modules": {
|
||||||
|
"https://deno.land/x/sleep/mod.ts": {
|
||||||
|
"headers": {
|
||||||
|
"location": "/x/sleep@v1.3.0/mod.ts",
|
||||||
|
"x-deno-warning": "Implicitly using latest version (v1.3.0) for https://deno.land/x/sleep/mod.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
"https://jsr.io/@std/crypto/1.0.3/_wasm/lib/deno_std_wasm_crypto.generated.d.mts": {},
|
"https://jsr.io/@std/crypto/1.0.3/_wasm/lib/deno_std_wasm_crypto.generated.d.mts": {},
|
||||||
"https://jsr.io/@std/crypto/1.0.3/_wasm/lib/deno_std_wasm_crypto.generated.mjs": {},
|
"https://jsr.io/@std/crypto/1.0.3/_wasm/lib/deno_std_wasm_crypto.generated.mjs": {},
|
||||||
"https://jsr.io/@std/net/1.0.4/unstable_get_network_address.ts": {}
|
"https://jsr.io/@std/net/1.0.4/unstable_get_network_address.ts": {}
|
||||||
|
|||||||
Reference in New Issue
Block a user