DenReg/registry/src/http/views.ts

154 lines
4.5 KiB
TypeScript

import type { ABC } from "../deps.ts";
import {
basicauth,
extractPackagePath,
getBucketFilePath,
getFile,
getAbsolutePackageVersion,
sortVersions,
} from "../utils.ts";
import { Hash, Path } from "../deps.ts";
import db, { IPackage } from "../db.ts";
import bucket from "../s3.ts";
const MAX_CACHE_AGE = 60 * 30; // 30 Minutes
const CACHE_CONTROL = "public, max-age=" + MAX_CACHE_AGE;
export default function views(g: ABC.Application) {
g.get("/", async (ctx) => {
ctx.response.headers.set("cache-control", CACHE_CONTROL);
const search = ctx.queryParams.q;
let packages: IPackage[] = [];
if (search && search !== "") {
packages = await db.package.find({
name: RegExp(`${search}`),
});
} else {
packages = await db.package.find({});
}
await ctx.render("index", {
packages: packages.reverse(),
search,
});
const etag =
"W/" +
Hash.createHash("sha3-256")
.update(
packages
.map((e) => {
const sorted = e.versions.sort(sortVersions).reverse();
return e.name + sorted[0];
})
.join(":")
)
.toString("base64");
ctx.response.headers.set("cache-control", CACHE_CONTROL);
ctx.response.headers.set("E-Tag", etag);
});
g.get("/package/:package", async (ctx) => {
let [packageName, packageVersion] = extractPackagePath(
ctx.params.package
);
const pkg = await db.package.findOne({ name: packageName });
const etag =
"W/" +
Hash.createHash("sha3-256")
.update(`${packageName}:${packageVersion}`)
.toString("base64");
await ctx.render("package", { pkg, version: packageVersion });
ctx.response.headers.set("cache-control", CACHE_CONTROL);
ctx.response.headers.set("E-Tag", etag);
});
async function handleBrowse(ctx: ABC.Context) {
const E404 = () => {
ctx.response.status = 404;
ctx.response.body = "// Not found!";
};
let [packageName, packageVersion] = extractPackagePath(
ctx.params.package
);
const pkg = await db.package.findOne({ name: packageName });
packageVersion = getAbsolutePackageVersion(pkg, packageVersion);
if (!packageVersion) return E404();
const path = ctx.params.path || "";
const fileContent = await getFile(packageName, packageVersion, path);
if (fileContent) {
await ctx.render("browse_file", {
pkg,
version: packageVersion,
content: new TextDecoder().decode(fileContent.data),
ext: Path.extname(path),
path: `${packageName}@${packageVersion}/${path}`,
});
} else {
const bucketPath = await getBucketFilePath(
packageName,
packageVersion,
path
);
if (!bucketPath) return E404();
console.log(bucketPath);
const filesItr = bucket.listAllObjects({
batchSize: 100,
prefix: bucketPath,
// delimiter: "/",
});
let files: { name: string; size: number }[] = [];
let directories: Set<string> = new Set();
let readme: string | null = null;
for await (let file of filesItr) {
const relPath = Path.posix.relative(bucketPath, file.key || "");
if (relPath.indexOf("/") >= 0) {
directories.add(relPath.split("/")[0]);
} else {
files.push({ name: relPath, size: file.size || -1 });
if (relPath.toLowerCase() === "readme.md") {
let readmeCont = await getFile(
packageName,
packageVersion,
Path.posix.join(path, relPath)
);
if (readmeCont) {
readme = new TextDecoder().decode(readmeCont?.data);
}
}
}
}
await ctx.render("browse_folder", {
pkg,
version: packageVersion,
readme,
files,
directories: Array.from(directories.values()).map((e) => ({
name: e,
})),
path: `${packageName}@${packageVersion}/${path}`,
});
}
}
g.get("/browse/:package", handleBrowse);
g.get("/browse/:package/*path", handleBrowse);
}