diff --git a/registry/src/deps.ts b/registry/src/deps.ts index 6069bb0..9d7f592 100644 --- a/registry/src/deps.ts +++ b/registry/src/deps.ts @@ -12,6 +12,7 @@ export * as Path from "https://deno.land/std@0.62.0/path/mod.ts"; export * as FS from "https://deno.land/std@0.62.0/fs/mod.ts"; export * as Base64 from "https://deno.land/std@0.62.0/encoding/base64.ts"; +export * as Hash from "https://deno.land/std@0.62.0/hash/mod.ts"; export * as Compress from "https://git.stamm.me/Deno/DenReg/raw/branch/master/tar/mod.ts"; @@ -24,6 +25,6 @@ export { React, jsx, Fragment, -} from "https://raw.githubusercontent.com/hibas123/jsx-html/master/mod.ts"; +} from "https://raw.githubusercontent.com/apiel/jsx-html/master/mod.ts"; export const Datastore = DS; diff --git a/registry/src/http/api.ts b/registry/src/http/api.ts index 275fb9b..2a49642 100644 --- a/registry/src/http/api.ts +++ b/registry/src/http/api.ts @@ -142,7 +142,7 @@ async function uploadPackage(ctx: ABC.Context) { } console.log("Setting new live version"); - //TODO: Better option, since this could error whith multiple upload to the same package + //TODO: Better option, since this could error whith multiple uploads to the same package at the same time await db.package.update( { name: packageName }, { diff --git a/registry/src/http/raw.ts b/registry/src/http/raw.ts index 18afae6..0bb13f3 100644 --- a/registry/src/http/raw.ts +++ b/registry/src/http/raw.ts @@ -19,6 +19,7 @@ export default function raw(g: ABC.Group) { ctx.params.path ); if (!result) return E404(); - return result; + ctx.response.headers.set("e-tag", result.etag); + return result.data; }); } diff --git a/registry/src/http/views.ts b/registry/src/http/views.ts index 5f6f5fa..0109f64 100644 --- a/registry/src/http/views.ts +++ b/registry/src/http/views.ts @@ -1,29 +1,63 @@ import { ABC } from "../deps.ts"; -import { basicauth, extractPackagePath } from "../utils.ts"; +import { basicauth, extractPackagePath, sortVersions } from "../utils.ts"; + +import { Hash } from "../deps.ts"; +import db, { IPackage } from "../db.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) => { - return ctx.render("index", { - search: ctx.queryParams["q"], - }); - // const render = await IndexView(); - // console.log(render); - // ctx.response.body = render; - // ctx.response.status = 200; - }, - basicauth("views") - ); - g.get( - "/package/:package", - async (ctx) => { - let [packageName, packageVersion] = extractPackagePath( - ctx.params.package - ); + g.get("/", async (ctx) => { + ctx.response.headers.set("cache-control", CACHE_CONTROL); - return ctx.render("package", { packageName }); - }, - basicauth("views") - ); + 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({}); + } + + 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("e-tag", etag); + + return ctx.render("index", { + packages, + search, + }); + }); + 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 }); + ctx.response.headers.set("cache-control", CACHE_CONTROL); + ctx.response.headers.set("E-Tag", etag); + }); } diff --git a/registry/src/utils.ts b/registry/src/utils.ts index 77eb03d..648369b 100644 --- a/registry/src/utils.ts +++ b/registry/src/utils.ts @@ -95,7 +95,7 @@ export async function getFile( pkgName: string, version: string | null | undefined, file: string -): Promise { +): Promise<{ etag: string; data: Uint8Array } | null | undefined> { const meta = await db.package.findOne({ name: pkgName }); if (!meta || meta.versions.length < 1) return null; @@ -122,8 +122,12 @@ export async function getFile( console.log("Getting file from:", bucketPath); try { - const data = (await bucket.getObject(bucketPath))?.body; - return data; + const res = await bucket.getObject(bucketPath); + if (!res) return undefined; + return { + etag: res.etag, + data: res.body, + }; } catch (err) { const msg = err.message as string; if (msg.indexOf("404") >= 0) return null; diff --git a/registry/src/views/index.tsx b/registry/src/views/index.tsx index 60c6d19..dd5d285 100644 --- a/registry/src/views/index.tsx +++ b/registry/src/views/index.tsx @@ -33,16 +33,13 @@ function Package({ pkg }: { pkg: IPackage }) { import { Main, Menu } from "./_default.tsx"; -export default async function index({ search }: any) { - let packages: IPackage[] = []; - if (search && search !== "") { - packages = await DB.package.find({ - name: RegExp(`${search}`), - }); - } else { - packages = await DB.package.find({}); - } - +export default async function index({ + packages, + search, +}: { + packages: IPackage[]; + search: string; +}) { return (
diff --git a/registry/src/views/package.tsx b/registry/src/views/package.tsx index 5ec8a07..2e71ae5 100644 --- a/registry/src/views/package.tsx +++ b/registry/src/views/package.tsx @@ -30,9 +30,7 @@ import { sortVersions, getFile } from "../utils.ts"; import { Main, Menu } from "./_default.tsx"; -export default async function index({ packageName }: any) { - const pkg = await DB.package.findOne({ name: packageName }); - +export default async function index({ pkg }: { pkg: IPackage }) { if (!pkg) return ( @@ -40,15 +38,14 @@ export default async function index({ packageName }: any) { ); - const readmeContent = await getFile( - packageName, - undefined, - "README.md" - ).then((res) => { - if (res) - return Marked.parse(new TextDecoder().decode(res)).content as string; - else return undefined; - }); + const readmeContent = await getFile(pkg.name, undefined, "README.md").then( + (res) => { + if (res) + return Marked.parse(new TextDecoder().decode(res.data)) + .content as string; + else return undefined; + } + ); return (