prep for publish

This commit is contained in:
2025-01-04 18:04:40 -07:00
parent b73af68989
commit 0c8d1865ff
6 changed files with 384 additions and 331 deletions

315
router.ts
View File

@@ -6,6 +6,7 @@ import type {
RouteConfigurator,
RouterContext,
} from "./types.ts";
import { NotFound } from "./util/response.ts";
/**
* A simple router for Deno
@@ -187,7 +188,6 @@ export class Router {
const url = new URL(req.url);
const method = req.method;
// Find the matching route
const matchedRoute = this.findMatchingRoute(url);
if (!matchedRoute) {
return new Response("Not Found", { status: 404 });
@@ -198,10 +198,8 @@ export class Router {
return new Response("Method Not Allowed", { status: 405 });
}
// Get matching middleware and sort by path specificity
const matchingMiddleware = this.getMatchingMiddleware(url);
// Create the base context object
const baseCtx: RouterContext = {
url,
params: {},
@@ -210,26 +208,22 @@ export class Router {
request: req,
};
// Combine route parameters with the base context
baseCtx.params = {
...baseCtx.params,
...matchedRoute.params,
};
// Execute middleware chain
let index = 0;
const executeMiddleware = async (): Promise<Response> => {
if (index < matchingMiddleware.length) {
const { middleware, params } = matchingMiddleware[index++];
// Create a new context for each middleware with its specific parameters
const middlewareCtx: RouterContext = {
...baseCtx,
params: { ...baseCtx.params, ...params },
pattern: middleware.pattern,
};
return await middleware.handler(middlewareCtx, executeMiddleware);
return await middleware.handler(req, middlewareCtx, executeMiddleware);
}
// Final handler gets the accumulated parameters
return await handler(req, baseCtx);
};
@@ -308,6 +302,309 @@ export class Router {
? `/${normalizedRoute}`
: `${normalizedBase}/${normalizedRoute}`;
}
serveDirectory(dir: string, root: string, opts?: { showIndex: boolean }) {
this.route(root + "*").get(async (_req, ctx) => {
const { showIndex } = opts ?? { showIndex: false };
let normalizedPath = (dir + "/" +
ctx.url.pathname.replace(new RegExp("^" + root), "")).trim().replace(
"//",
"/",
);
normalizedPath = normalizedPath.replace(
/\/\s?$/,
"",
);
let fileInfo: Deno.FileInfo;
try {
fileInfo = await Deno.stat(normalizedPath);
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
return NotFound();
} else {
throw error;
}
}
if (fileInfo.isDirectory) {
if (!showIndex) return NotFound();
normalizedPath += "/index.html";
}
try {
const file = await Deno.readFile(normalizedPath);
return new Response(file);
} catch (e) {
if (e instanceof Deno.errors.NotFound) {
return showIndex ? generateIndex(normalizedPath) : NotFound();
}
throw e;
}
});
}
}
async function generateIndex(dir: string) {
dir = dir.replace(/index\.html$/, "");
const items: Deno.DirEntry[] = [];
for await (const entry of Deno.readDir(dir)) {
items.push(entry);
}
const fileIcon = `
<svg
width="48"
height="48"
viewBox="0 0 12.7 12.7"
version="1.1"
id="svg1"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
sodipodi:docname="file icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:document-units="mm"
inkscape:zoom="17.318182"
inkscape:cx="10.018373"
inkscape:cy="28.207349"
inkscape:window-width="3440"
inkscape:window-height="1417"
inkscape:window-x="-8"
inkscape:window-y="1432"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
d="m 3.5196818,1.7373617 c -0.3351528,0 -0.605131,0.2694614 -0.605131,0.6046142 v 8.0160481 c 0,0.335153 0.2699782,0.604614 0.605131,0.604614 h 5.6606364 c 0.3351528,0 0.605131,-0.269461 0.605131,-0.604614 V 3.7372396 L 7.7850545,1.7373617 Z"
style="fill:#4d1f6d;stroke-width:0.264583"
id="path4" />
<path
d="M 9.6529679,3.8697209 7.6525732,1.869843 v 1.3952637 c 0,0.3351528 0.2699782,0.6046142 0.605131,0.6046142 z"
style="fill:#918b93;fill-opacity:1;stroke:#4d1f6d;stroke-width:0.265;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
id="path5" />
<rect
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.285269;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
id="rect5"
width="5.5833006"
height="0.5505569"
x="3.5583498"
y="4.0022206" />
<rect
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.272493;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
id="rect6"
width="5.0944114"
height="0.5505569"
x="3.5583498"
y="4.8455539" />
<rect
style="fill:#463e4b;fill-opacity:1;stroke:none;stroke-width:0.279751;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
id="rect7"
width="5.3694115"
height="0.5505569"
x="3.5583498"
y="5.682776" />
<rect
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.279751;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
id="rect8"
width="5.3694115"
height="0.5505569"
x="3.5583498"
y="5.6888871" />
<rect
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.285269;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
id="rect9"
width="5.5833006"
height="0.5505569"
x="3.5583498"
y="6.5322208" />
<rect
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.272493;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
id="rect10"
width="5.0944114"
height="0.5505569"
x="3.5583498"
y="7.3755541" />
<rect
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.279751;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
id="rect11"
width="5.3694115"
height="0.5505569"
x="3.5583498"
y="8.2188873" />
<rect
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.217416;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
id="rect12"
width="3.2431545"
height="0.5505569"
x="5.8984962"
y="9.7313871" />
</g>
</svg>
`;
const folderIcon = `
<svg
width="48"
height="48"
viewBox="0 0 12.7 12.7"
version="1.1"
id="svg1"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
sodipodi:docname="folder icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:document-units="mm"
inkscape:zoom="12.245804"
inkscape:cx="17.924507"
inkscape:cy="30.990207"
inkscape:window-width="3440"
inkscape:window-height="1417"
inkscape:window-x="-8"
inkscape:window-y="1432"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#463e4b;fill-opacity:1;stroke-width:0.264583"
id="rect3"
width="10.867838"
height="7.4972959"
x="0.91608107"
y="2.1476252"
ry="1.1883322" />
<rect
style="fill:#4d1f6d;fill-opacity:1;stroke-width:0.264583"
id="rect1"
width="10.867838"
height="7.4972959"
x="0.91608107"
y="2.601352"
ry="1.1883322" />
<rect
style="fill:#4d1f6d;fill-opacity:1;stroke-width:0.264583"
id="rect2"
width="4.2779961"
height="4.0619354"
x="0.91608107"
y="1.5426561"
ry="1.1883322" />
</g>
</svg>
`;
const template = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${dir}</title>
<style>
* {
font-family: sans-serif;
}
ul {
margin: 0;
padding: 0;
}
li {
list-style: none;
display: flex;
align-items: center;
gap: 1rem;
padding: 4px 2px;
&:nth-child(odd) {
background-color: rgba(0,0,0, .2)
}
& svg {
height: 1.5rem;
}
}
a {
color: white;
}
@media (prefers-color-scheme: dark) {
:root {
background-color: #1b1220;
color: #dcdcdc
}
ul {
background-color: rgba(255,255,255,.1)
}
}
</style>
</head>
<body>
<h1>${dir}</h1>
<ul>
${
items
.sort((a, b) => {
if (a.isDirectory && b.isDirectory) return a.name > b.name ? 1 : -1;
if (a.isDirectory) return -1;
if (b.isDirectory) return 1;
return a.name > b.name ? 1 : -1;
})
.map((e) =>
`<li>${e.isFile ? fileIcon : folderIcon}<a href="${
"/" + dir.replace(/^\//, "") + e.name
}">${e.name}</a></li>`
)
.join("\n") || "<li>Directory is empty</li>"
}
</ul>
</body>
</html>
`;
return new Response(template, {
headers: {
"Content-Type": "text/html",
},
});
}
export default Router;
@@ -361,7 +658,7 @@ if (import.meta.main) {
// return new Response('POST /*');
// });
router.use("/users", async (_, next) => {
router.use("/users", async (_req, _ctx, next) => {
console.log("Using middleware");
return await next();
});