From cf8aabf12f0254ad3a2a2a5c45831d596860f204 Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Sat, 24 Aug 2024 15:17:33 +0900 Subject: [PATCH 1/4] feat(hono): Use Hono as a web framework It's more useful and actively maintained. --- deno.json | 6 +- deno.lock | 146 ++++++-------------------- src/libs/middlewares/content.ts | 13 +-- src/libs/middlewares/content_test.ts | 26 ++--- src/libs/middlewares/redirect.ts | 13 +-- src/libs/middlewares/redirect_test.ts | 19 ++-- src/router.ts | 14 --- src/router_test.ts | 30 ------ src/server.ts | 61 ++++++----- src/server_test.ts | 56 ++++++++++ 10 files changed, 151 insertions(+), 233 deletions(-) delete mode 100644 src/router.ts delete mode 100644 src/router_test.ts create mode 100644 src/server_test.ts diff --git a/deno.json b/deno.json index 3601d89..cf3dbd1 100644 --- a/deno.json +++ b/deno.json @@ -5,16 +5,14 @@ "run": "deno run --env='.env' --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH,GITHUB_TOKEN' --allow-net='0.0.0.0,api.github.com'", "start": "deno task run ./src/server.ts", "dev": "deno task run --watch ./src/server.ts", - "test": "deno test --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH,GITHUB_TOKEN' --allow-net='api.github.com' --parallel --shuffle", + "test": "deno test --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH,GITHUB_TOKEN' --allow-net='0.0.0.0,api.github.com' --parallel --shuffle", "cov": "deno task test --coverage && deno coverage --lcov > coverage.lcov" }, "imports": { - "@oak/oak": "jsr:@oak/oak@16.1.0", + "@hono/hono": "jsr:@hono/hono@4.5.8", "@octokit/rest": "https://esm.sh/@octokit/rest@21.0.1", "@std/assert": "jsr:@std/assert@1.0.2", - "@std/fmt": "jsr:@std/fmt@1.0.0", "@std/http": "jsr:@std/http@1.0.2", - "@std/http/user-agent": "jsr:@std/http@~0.223/user-agent", "@std/path": "jsr:@std/path@1.0.2" }, "deploy": { diff --git a/deno.lock b/deno.lock index 035607a..69b5ed0 100644 --- a/deno.lock +++ b/deno.lock @@ -2,66 +2,22 @@ "version": "3", "packages": { "specifiers": { - "jsr:@oak/commons@0.11": "jsr:@oak/commons@0.11.0", - "jsr:@oak/oak@16.1.0": "jsr:@oak/oak@16.1.0", - "jsr:@std/assert@0.223": "jsr:@std/assert@0.223.0", - "jsr:@std/assert@0.226": "jsr:@std/assert@0.226.0", + "jsr:@hono/hono@4.5.8": "jsr:@hono/hono@4.5.8", "jsr:@std/assert@1.0.2": "jsr:@std/assert@1.0.2", - "jsr:@std/assert@^0.223.0": "jsr:@std/assert@0.223.0", - "jsr:@std/assert@^0.224.0": "jsr:@std/assert@0.224.0", - "jsr:@std/bytes@0.223": "jsr:@std/bytes@0.223.0", - "jsr:@std/bytes@0.224": "jsr:@std/bytes@0.224.0", - "jsr:@std/bytes@^0.223.0": "jsr:@std/bytes@0.223.0", - "jsr:@std/crypto@0.223": "jsr:@std/crypto@0.223.0", - "jsr:@std/crypto@0.224": "jsr:@std/crypto@0.224.0", - "jsr:@std/encoding@1.0.0-rc.2": "jsr:@std/encoding@1.0.0-rc.2", - "jsr:@std/encoding@^0.223.0": "jsr:@std/encoding@0.223.0", - "jsr:@std/fmt@1.0.0": "jsr:@std/fmt@1.0.0", - "jsr:@std/http@0.223": "jsr:@std/http@0.223.0", - "jsr:@std/http@0.224": "jsr:@std/http@0.224.5", + "jsr:@std/cli@^1.0.3": "jsr:@std/cli@1.0.3", + "jsr:@std/encoding@^1.0.1": "jsr:@std/encoding@1.0.2", + "jsr:@std/fmt@^1.0.0": "jsr:@std/fmt@1.0.0", "jsr:@std/http@1.0.2": "jsr:@std/http@1.0.2", - "jsr:@std/http@~0.223": "jsr:@std/http@0.223.0", "jsr:@std/internal@^1.0.1": "jsr:@std/internal@1.0.1", - "jsr:@std/io@0.223": "jsr:@std/io@0.223.0", - "jsr:@std/media-types@0.223": "jsr:@std/media-types@0.223.0", - "jsr:@std/media-types@0.224": "jsr:@std/media-types@0.224.1", - "jsr:@std/path@0.223": "jsr:@std/path@0.223.0", + "jsr:@std/media-types@^1.0.2": "jsr:@std/media-types@1.0.2", + "jsr:@std/net@^1.0.0": "jsr:@std/net@1.0.0", "jsr:@std/path@1.0.2": "jsr:@std/path@1.0.2", - "npm:path-to-regexp@6.2.1": "npm:path-to-regexp@6.2.1" + "jsr:@std/path@^1.0.2": "jsr:@std/path@1.0.2", + "jsr:@std/streams@^1.0.1": "jsr:@std/streams@1.0.2" }, "jsr": { - "@oak/commons@0.11.0": { - "integrity": "07702bfe5c07cd8144c422022994da1f9fea466b185824f4be63a2b1b1a65125", - "dependencies": [ - "jsr:@std/assert@0.226", - "jsr:@std/bytes@0.224", - "jsr:@std/crypto@0.224", - "jsr:@std/http@0.224", - "jsr:@std/media-types@0.224" - ] - }, - "@oak/oak@16.1.0": { - "integrity": "ab21506555fffeb08dc8f45ff5d28607e8087949ff58bd2964b27df65994480b", - "dependencies": [ - "jsr:@oak/commons@0.11", - "jsr:@std/assert@0.223", - "jsr:@std/bytes@0.223", - "jsr:@std/crypto@0.223", - "jsr:@std/http@0.223", - "jsr:@std/io@0.223", - "jsr:@std/media-types@0.223", - "jsr:@std/path@0.223", - "npm:path-to-regexp@6.2.1" - ] - }, - "@std/assert@0.223.0": { - "integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24" - }, - "@std/assert@0.224.0": { - "integrity": "8643233ec7aec38a940a8264a6e3eed9bfa44e7a71cc6b3c8874213ff401967f" - }, - "@std/assert@0.226.0": { - "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3" + "@hono/hono@4.5.8": { + "integrity": "60f5b4c61edae2016022d6087b4fc381f378d337f046f56e00a3a3512c7e9c16" }, "@std/assert@1.0.2": { "integrity": "ccacec332958126deaceb5c63ff8b4eaf9f5ed0eac9feccf124110435e59e49c", @@ -69,79 +25,41 @@ "jsr:@std/internal@^1.0.1" ] }, - "@std/bytes@0.223.0": { - "integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8" - }, - "@std/bytes@0.224.0": { - "integrity": "a2250e1d0eb7d1c5a426f21267ab9bdeac2447fa87a3d0d1a467d3f7a6058e49" - }, - "@std/crypto@0.223.0": { - "integrity": "1aa9555ff56b09e197ad988ea200f84bc6781fd4fd83f3a156ee44449af93000", - "dependencies": [ - "jsr:@std/assert@^0.223.0", - "jsr:@std/encoding@^0.223.0" - ] - }, - "@std/crypto@0.224.0": { - "integrity": "154ef3ff08ef535562ef1a718718c5b2c5fc3808f0f9100daad69e829bfcdf2d", - "dependencies": [ - "jsr:@std/assert@^0.224.0" - ] - }, - "@std/encoding@0.223.0": { - "integrity": "2b5615a75e00337ce113f34cf2f9b8c18182c751a8dcc8b1a2c2fc0e117bef00" + "@std/cli@1.0.3": { + "integrity": "9a0488b5d2e58d29dce106a941eecec7181fae996bf0d2225563f1ca7e4b100c" }, - "@std/encoding@1.0.0-rc.2": { - "integrity": "160d7674a20ebfbccdf610b3801fee91cf6e42d1c106dd46bbaf46e395cd35ef" + "@std/encoding@1.0.2": { + "integrity": "7ed640c777e3275550e2cd937c440acdbebfdcd2d13ef67052f0536bf43e707f" }, "@std/fmt@1.0.0": { "integrity": "8a95c9fdbb61559418ccbc0f536080cf43341655e1444f9d375a66886ceaaa3d" }, - "@std/http@0.223.0": { - "integrity": "15ab8a0c5a7e9d5be017a15b01600f20f66602ceec48b378939fa24fcec522aa", - "dependencies": [ - "jsr:@std/assert@^0.223.0", - "jsr:@std/encoding@^0.223.0" - ] - }, - "@std/http@0.224.5": { - "integrity": "b03b5d1529f6c423badfb82f6640f9f2557b4034cd7c30655ba5bb447ff750a4", + "@std/http@1.0.2": { + "integrity": "e28d612e2b5e0a40704e8c64d9a5d55cf8c6accf6d798cd1cfb43aad02d1e4fe", "dependencies": [ - "jsr:@std/encoding@1.0.0-rc.2" + "jsr:@std/cli@^1.0.3", + "jsr:@std/encoding@^1.0.1", + "jsr:@std/fmt@^1.0.0", + "jsr:@std/media-types@^1.0.2", + "jsr:@std/net@^1.0.0", + "jsr:@std/path@^1.0.2", + "jsr:@std/streams@^1.0.1" ] }, - "@std/http@1.0.2": { - "integrity": "e28d612e2b5e0a40704e8c64d9a5d55cf8c6accf6d798cd1cfb43aad02d1e4fe" - }, "@std/internal@1.0.1": { "integrity": "6f8c7544d06a11dd256c8d6ba54b11ed870aac6c5aeafff499892662c57673e6" }, - "@std/io@0.223.0": { - "integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1", - "dependencies": [ - "jsr:@std/bytes@^0.223.0" - ] + "@std/media-types@1.0.2": { + "integrity": "abb78dc8f7d88141cba8c4d60fc1e8b421e5c7b0d7c84f2f708bc666cad46784" }, - "@std/media-types@0.223.0": { - "integrity": "84684680c2eb6bc6d9369c6d6f26a49decaf2c7603ff531862dda575d9d6776e" - }, - "@std/media-types@0.224.1": { - "integrity": "9e69a5daed37c5b5c6d3ce4731dc191f80e67f79bed392b0957d1d03b87f11e1" - }, - "@std/path@0.223.0": { - "integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989", - "dependencies": [ - "jsr:@std/assert@^0.223.0" - ] + "@std/net@1.0.0": { + "integrity": "31de7a45f074edee8296d77fde85fef53fed9baa6f3122a38d796af08ecb3fc9" }, "@std/path@1.0.2": { "integrity": "a452174603f8c620bd278a380c596437a9eef50c891c64b85812f735245d9ec7" - } - }, - "npm": { - "path-to-regexp@6.2.1": { - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", - "dependencies": {} + }, + "@std/streams@1.0.2": { + "integrity": "187c3c875675221f5355807a735e51b0f8769caade2cbca6d7f4fa710ea4ace4" } } }, @@ -165,11 +83,9 @@ }, "workspace": { "dependencies": [ - "jsr:@oak/oak@16.1.0", + "jsr:@hono/hono@4.5.8", "jsr:@std/assert@1.0.2", - "jsr:@std/fmt@1.0.0", "jsr:@std/http@1.0.2", - "jsr:@std/http@~0.223", "jsr:@std/path@1.0.2" ] } diff --git a/src/libs/middlewares/content.ts b/src/libs/middlewares/content.ts index 8fdbd43..217a147 100644 --- a/src/libs/middlewares/content.ts +++ b/src/libs/middlewares/content.ts @@ -1,12 +1,11 @@ -import type { RouterContext } from "@oak/oak"; import { Octokit } from "@octokit/rest"; +import type { StatusCode } from "@std/http"; import { getRepository, githubToken } from "../utils/env.ts"; -export async function getContent( - ctx: RouterContext, +export async function getContent( ref: string | undefined = undefined, -): Promise { +): Promise<[string, StatusCode]> { const octokit = new Octokit({ auth: githubToken }); const repository = getRepository(); @@ -19,10 +18,8 @@ export async function getContent( ref: ref, }); - ctx.response.status = status; - ctx.response.body = data; + return [data.toString(), status]; } catch (error) { - ctx.response.status = error.status; - ctx.response.body = `⚠️ ${ctx.response.status}: ${error.message}`; + return [`⚠️ ${error.status}: ${error.message}`, error.status]; } } diff --git a/src/libs/middlewares/content_test.ts b/src/libs/middlewares/content_test.ts index b065e8f..c8ccd0d 100644 --- a/src/libs/middlewares/content_test.ts +++ b/src/libs/middlewares/content_test.ts @@ -1,35 +1,31 @@ -import { type RouterContext, testing } from "@oak/oak"; -import { assertEquals, assertStringIncludes } from "@std/assert"; +import { assertEquals, assertExists, assertStringIncludes } from "@std/assert"; import { STATUS_CODE } from "@std/http/status"; import { getContent } from "./content.ts"; import { exportRepo, testRef, testRepo, unknownRepo } from "../test_utils.ts"; -Deno.test("Get Content", async (t: Deno.TestContext) => { - const ctx: RouterContext = testing.createMockContext(); - +Deno.test("Get Content", async (t: Deno.TestContext) => { await t.step("normal", async () => { exportRepo(testRepo); - await getContent(ctx); + const [data, status] = await getContent(); - assertEquals(ctx.response.status, STATUS_CODE.OK); + assertExists(data); + assertEquals(status, STATUS_CODE.OK); }); await t.step("with ref", async () => { exportRepo(testRepo); - await getContent(ctx, testRef); + const [data, status] = await getContent(testRef); - assertEquals(ctx.response.status, STATUS_CODE.OK); + assertExists(data); + assertEquals(status, STATUS_CODE.OK); }); await t.step("not found", async () => { exportRepo(unknownRepo); - await getContent(ctx); + const [data, status] = await getContent(); - assertEquals(ctx.response.status, STATUS_CODE.NotFound); - assertStringIncludes( - ctx.response.body!.toString(), - `⚠️ ${STATUS_CODE.NotFound}:`, - ); + assertStringIncludes(data, `⚠️ ${STATUS_CODE.NotFound}:`); + assertEquals(status, STATUS_CODE.NotFound); }); }); diff --git a/src/libs/middlewares/redirect.ts b/src/libs/middlewares/redirect.ts index 956ac11..c9014a2 100644 --- a/src/libs/middlewares/redirect.ts +++ b/src/libs/middlewares/redirect.ts @@ -1,12 +1,9 @@ -import type { RouterContext } from "@oak/oak"; -import { STATUS_CODE } from "@std/http/status"; -import { UserAgent } from "@std/http/user-agent"; +import type { UserAgent } from "@std/http/user-agent"; import { join } from "@std/path"; import { getRepository } from "../utils/env.ts"; -export function redirect( - ctx: RouterContext, +export function redirect( userAgent: UserAgent, ref: string = "master", ): boolean { @@ -22,9 +19,5 @@ export function redirect( "https://github.com", ); - if (!userAgent?.browser.name) return false; - - ctx.response.status = STATUS_CODE.PermanentRedirect; - ctx.response.redirect(url); - return true; + return userAgent?.browser.name ? url : null; } diff --git a/src/libs/middlewares/redirect_test.ts b/src/libs/middlewares/redirect_test.ts index cb945fb..2c00bfc 100644 --- a/src/libs/middlewares/redirect_test.ts +++ b/src/libs/middlewares/redirect_test.ts @@ -1,31 +1,26 @@ -import { type RouterContext, testing } from "@oak/oak"; import { assertEquals } from "@std/assert"; -import { STATUS_CODE } from "@std/http/status"; import { UserAgent } from "@std/http/user-agent"; import { redirect } from "./redirect.ts"; import { exportRepo, testRef, testRepo } from "../test_utils.ts"; -Deno.test("Redirect Detection", async (t: Deno.TestContext) => { - const ctx: RouterContext = testing.createMockContext(); - exportRepo(testRepo); - +Deno.test("Redirect Detection", async (t: Deno.TestContext) => { await t.step("normal", () => { - redirect(ctx, new UserAgent("Chrome/1.2.3")); + exportRepo(testRepo); + const url: URL | null = redirect(new UserAgent("Chrome/1.2.3")); - assertEquals(ctx.response.status, STATUS_CODE.PermanentRedirect); assertEquals( - ctx.response.headers.get("location"), + url?.toString(), `https://github.com/${testRepo.owner}/${testRepo.name}/blob/master/${testRepo.path}`, ); }); await t.step("with ref", () => { - redirect(ctx, new UserAgent("Chrome/1.2.3"), testRef); + exportRepo(testRepo); + const url: URL | null = redirect(new UserAgent("Chrome/1.2.3"), testRef); - assertEquals(ctx.response.status, STATUS_CODE.PermanentRedirect); assertEquals( - ctx.response.headers.get("location"), + url?.toString(), `https://github.com/${testRepo.owner}/${testRepo.name}/blob/${testRef}/${testRepo.path}`, ); }); diff --git a/src/router.ts b/src/router.ts deleted file mode 100644 index a9f7f51..0000000 --- a/src/router.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Router, type RouterContext } from "@oak/oak"; -import { getContent, redirect } from "./libs/mod.ts"; - -export const router = new Router(); -router - .get("/", async (ctx: RouterContext) => { - ctx.response.type = "text/plain"; - if (!redirect(ctx, ctx.request.userAgent)) await getContent(ctx); - }) - .get("/:ref", async (ctx: RouterContext) => { - const ref: string | undefined = ctx.params.ref; - ctx.response.type = "text/plain"; - if (!redirect(ctx, ctx.request.userAgent, ref)) await getContent(ctx, ref); - }); diff --git a/src/router_test.ts b/src/router_test.ts deleted file mode 100644 index 3f01af3..0000000 --- a/src/router_test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { type RouterContext, testing } from "@oak/oak"; -import { assertEquals } from "@std/assert"; -import { STATUS_CODE } from "@std/http/status"; - -import { router } from "./router.ts"; -import { exportRepo, testRef, testRepo } from "./libs/test_utils.ts"; - -Deno.test("Serve", async (t: Deno.TestContext) => { - await t.step("/", async () => { - const ctx: RouterContext = testing.createMockContext({ - method: "GET", - path: "/", - }); - exportRepo(testRepo); - await router.routes()(ctx, () => Promise.resolve()); - - assertEquals(ctx.response.status, STATUS_CODE.OK); - }); - - await t.step("/:ref", async () => { - const ctx: RouterContext = testing.createMockContext({ - method: "GET", - path: `/${testRef}`, - }); - exportRepo(testRepo); - await router.routes()(ctx, () => Promise.resolve()); - - assertEquals(ctx.response.status, STATUS_CODE.OK); - }); -}); diff --git a/src/server.ts b/src/server.ts index b5089f2..65561c9 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,25 +1,36 @@ -import { Application } from "@oak/oak"; -import type { ApplicationListenEvent } from "@oak/oak/application"; -import { yellow } from "@std/fmt/colors"; - -import { router } from "./router.ts"; - -const app = new Application(); - -app.use(router.routes()); -app.use(router.allowedMethods()); - -app.addEventListener( - "listen", - ({ secure, hostname, port }: ApplicationListenEvent) => { - console.log( - `🔔 listening: ${ - yellow( - `${secure ? "https" : "http"}://${hostname ?? "localhost"}:${port}`, - ) - }`, - ); - }, -); - -await app.listen({ port: 8080 }); +import { type Context, Hono } from "@hono/hono"; +import { STATUS_CODE } from "@std/http/status"; +import { UserAgent } from "@std/http/user-agent"; + +import { getContent, redirect } from "./libs/mod.ts"; + +export const app = new Hono(); + +app.get("/", async (ctx: Context) => { + const url: URL | null = redirect( + new UserAgent(ctx.req.header("User-Agent") ?? ""), + ); + if (url) return ctx.redirect(url.toString(), STATUS_CODE.PermanentRedirect); + + const [data, status] = await getContent(); + return ctx.text(data, status); +}); + +app.get("/:ref", async (ctx: Context) => { + const ref: string = ctx.req.param("ref"); + + const url: URL | null = redirect( + new UserAgent(ctx.req.header("User-Agent") ?? ""), + ref, + ); + if (url) return ctx.redirect(url.toString(), STATUS_CODE.PermanentRedirect); + + const [data, status] = await getContent(ref); + return ctx.text(data, status); +}); + +app.get("*", (ctx: Context) => { + return ctx.redirect("/", STATUS_CODE.SeeOther); +}); + +Deno.serve(app.fetch); diff --git a/src/server_test.ts b/src/server_test.ts new file mode 100644 index 0000000..212504a --- /dev/null +++ b/src/server_test.ts @@ -0,0 +1,56 @@ +import { assertEquals } from "@std/assert"; +import { UserAgent } from "@std/http"; +import { STATUS_CODE } from "@std/http/status"; + +import { app } from "./server.ts"; +import { exportRepo, testRef, testRepo } from "./libs/test_utils.ts"; + +Deno.test("Serve", async (t: Deno.TestContext) => { + await t.step("/", async () => { + exportRepo(testRepo); + const res: Response = await app.request("/"); + + assertEquals(res.status, STATUS_CODE.OK); + }); + + await t.step("/ (Redirect)", async () => { + exportRepo(testRepo); + const res: Response = await app.request("/", { + headers: { "User-Agent": new UserAgent("Chrome/1.2.3").toString() }, + }); + + assertEquals( + res.headers.get("Location"), + `https://github.com/${testRepo.owner}/${testRepo.name}/blob/master/${testRepo.path}`, + ); + assertEquals(res.status, STATUS_CODE.PermanentRedirect); + }); + + await t.step("/:ref", async () => { + exportRepo(testRepo); + const res: Response = await app.request(`/${testRef}`); + + assertEquals(res.status, STATUS_CODE.OK); + }); + + await t.step("/:ref (Redirect)", async () => { + exportRepo(testRepo); + const res: Response = await app.request(`/${testRef}`, { + headers: { "User-Agent": new UserAgent("Chrome/1.2.3").toString() }, + }); + + assertEquals( + res.headers.get("Location"), + `https://github.com/${testRepo.owner}/${testRepo.name}/blob/${testRef}/${testRepo.path}`, + ); + assertEquals(res.status, STATUS_CODE.PermanentRedirect); + }); + + await t.step("*", async () => { + exportRepo(testRepo); + const res: Response = await app.request("/anything/else"); + + assertEquals(res.headers.get("Location"), "/"); + assertEquals(res.status, STATUS_CODE.SeeOther); + }); +}); From 6b398d93001219bd81eb79a9c2f10d71a6e34489 Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Sat, 24 Aug 2024 15:19:18 +0900 Subject: [PATCH 2/4] refactor(test): Split the functions for ease of reading Hard coded strings are separated from each file. --- src/libs/middlewares/redirect.ts | 17 +++-------------- src/libs/middlewares/redirect_test.ts | 18 ++++++------------ src/libs/test_utils.ts | 3 +++ src/libs/utils/env.ts | 18 ++++++++++++++++++ src/server_test.ts | 17 +++++++++++------ 5 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/libs/middlewares/redirect.ts b/src/libs/middlewares/redirect.ts index c9014a2..98567d6 100644 --- a/src/libs/middlewares/redirect.ts +++ b/src/libs/middlewares/redirect.ts @@ -1,23 +1,12 @@ import type { UserAgent } from "@std/http/user-agent"; -import { join } from "@std/path"; -import { getRepository } from "../utils/env.ts"; +import { getGitHubUrl, getRepository } from "../utils/env.ts"; export function redirect( userAgent: UserAgent, ref: string = "master", -): boolean { - const repository = getRepository(); - const url = new URL( - join( - repository.owner, - repository.name, - "blob", - ref, - repository.path, - ), - "https://github.com", - ); +): URL | null { + const url = getGitHubUrl(getRepository(), ref); return userAgent?.browser.name ? url : null; } diff --git a/src/libs/middlewares/redirect_test.ts b/src/libs/middlewares/redirect_test.ts index 2c00bfc..7027342 100644 --- a/src/libs/middlewares/redirect_test.ts +++ b/src/libs/middlewares/redirect_test.ts @@ -1,27 +1,21 @@ import { assertEquals } from "@std/assert"; -import { UserAgent } from "@std/http/user-agent"; import { redirect } from "./redirect.ts"; -import { exportRepo, testRef, testRepo } from "../test_utils.ts"; +import { exportRepo, testRef, testRepo, testUserAgent } from "../test_utils.ts"; +import { getGitHubUrl } from "../utils/env.ts"; Deno.test("Redirect Detection", async (t: Deno.TestContext) => { await t.step("normal", () => { exportRepo(testRepo); - const url: URL | null = redirect(new UserAgent("Chrome/1.2.3")); + const url: URL | null = redirect(testUserAgent); - assertEquals( - url?.toString(), - `https://github.com/${testRepo.owner}/${testRepo.name}/blob/master/${testRepo.path}`, - ); + assertEquals(url, getGitHubUrl(testRepo)); }); await t.step("with ref", () => { exportRepo(testRepo); - const url: URL | null = redirect(new UserAgent("Chrome/1.2.3"), testRef); + const url: URL | null = redirect(testUserAgent, testRef); - assertEquals( - url?.toString(), - `https://github.com/${testRepo.owner}/${testRepo.name}/blob/${testRef}/${testRepo.path}`, - ); + assertEquals(url, getGitHubUrl(testRepo, testRef)); }); }); diff --git a/src/libs/test_utils.ts b/src/libs/test_utils.ts index 4362005..1669993 100644 --- a/src/libs/test_utils.ts +++ b/src/libs/test_utils.ts @@ -1,3 +1,4 @@ +import { UserAgent } from "@std/http/user-agent"; import type { Repository } from "./types.ts"; export const testRepo: Repository = { @@ -14,6 +15,8 @@ export const unknownRepo: Repository = { export const testRef = "v1.0.0"; +export const testUserAgent = new UserAgent("Chrome/1.2.3"); + export function exportRepo(repository: Repository) { Deno.env.set("REPOSITORY_OWNER", repository.owner); Deno.env.set("REPOSITORY_NAME", repository.name); diff --git a/src/libs/utils/env.ts b/src/libs/utils/env.ts index 56b33b0..6bc9eff 100644 --- a/src/libs/utils/env.ts +++ b/src/libs/utils/env.ts @@ -1,3 +1,5 @@ +import { join } from "@std/path"; + import type { Repository } from "../types.ts"; export function getRepository(): Repository { @@ -17,3 +19,19 @@ export function getRepository(): Repository { } export const githubToken: string | undefined = Deno.env.get("GITHUB_TOKEN"); + +export function getGitHubUrl( + repository: Repository, + ref: string = "master", +): URL { + return new URL( + join( + repository.owner, + repository.name, + "blob", + ref, + repository.path, + ), + "https://github.com", + ); +} diff --git a/src/server_test.ts b/src/server_test.ts index 212504a..6bb8d85 100644 --- a/src/server_test.ts +++ b/src/server_test.ts @@ -1,9 +1,14 @@ import { assertEquals } from "@std/assert"; -import { UserAgent } from "@std/http"; import { STATUS_CODE } from "@std/http/status"; import { app } from "./server.ts"; -import { exportRepo, testRef, testRepo } from "./libs/test_utils.ts"; +import { + exportRepo, + testRef, + testRepo, + testUserAgent, +} from "./libs/test_utils.ts"; +import { getGitHubUrl } from "./libs/utils/env.ts"; Deno.test("Serve", async (t: Deno.TestContext) => { await t.step("/", async () => { @@ -16,12 +21,12 @@ Deno.test("Serve", async (t: Deno.TestContext) => { await t.step("/ (Redirect)", async () => { exportRepo(testRepo); const res: Response = await app.request("/", { - headers: { "User-Agent": new UserAgent("Chrome/1.2.3").toString() }, + headers: { "User-Agent": testUserAgent.toString() }, }); assertEquals( res.headers.get("Location"), - `https://github.com/${testRepo.owner}/${testRepo.name}/blob/master/${testRepo.path}`, + getGitHubUrl(testRepo).toString(), ); assertEquals(res.status, STATUS_CODE.PermanentRedirect); }); @@ -36,12 +41,12 @@ Deno.test("Serve", async (t: Deno.TestContext) => { await t.step("/:ref (Redirect)", async () => { exportRepo(testRepo); const res: Response = await app.request(`/${testRef}`, { - headers: { "User-Agent": new UserAgent("Chrome/1.2.3").toString() }, + headers: { "User-Agent": testUserAgent.toString() }, }); assertEquals( res.headers.get("Location"), - `https://github.com/${testRepo.owner}/${testRepo.name}/blob/${testRef}/${testRepo.path}`, + getGitHubUrl(testRepo, testRef).toString(), ); assertEquals(res.status, STATUS_CODE.PermanentRedirect); }); From 34ca55e71dfba5d88f7f6815e52689a20d2d3210 Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Sat, 24 Aug 2024 15:29:09 +0900 Subject: [PATCH 3/4] refactor(hono): Use the optional path parameter Combine the root and ref endpoints. --- src/server.ts | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/src/server.ts b/src/server.ts index 65561c9..6ee77a1 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,32 +5,21 @@ import { UserAgent } from "@std/http/user-agent"; import { getContent, redirect } from "./libs/mod.ts"; export const app = new Hono(); - -app.get("/", async (ctx: Context) => { - const url: URL | null = redirect( - new UserAgent(ctx.req.header("User-Agent") ?? ""), - ); - if (url) return ctx.redirect(url.toString(), STATUS_CODE.PermanentRedirect); - - const [data, status] = await getContent(); - return ctx.text(data, status); -}); - -app.get("/:ref", async (ctx: Context) => { - const ref: string = ctx.req.param("ref"); - - const url: URL | null = redirect( - new UserAgent(ctx.req.header("User-Agent") ?? ""), - ref, - ); - if (url) return ctx.redirect(url.toString(), STATUS_CODE.PermanentRedirect); - - const [data, status] = await getContent(ref); - return ctx.text(data, status); -}); - -app.get("*", (ctx: Context) => { - return ctx.redirect("/", STATUS_CODE.SeeOther); -}); +app + .get("/:ref?", async (ctx: Context) => { + const ref: string = ctx.req.param("ref"); + + const url: URL | null = redirect( + new UserAgent(ctx.req.header("User-Agent") ?? ""), + ref, + ); + if (url) return ctx.redirect(url.toString(), STATUS_CODE.PermanentRedirect); + + const [data, status] = await getContent(ref); + return ctx.text(data, status); + }) + .get("*", (ctx: Context) => { + return ctx.redirect("/", STATUS_CODE.SeeOther); + }); Deno.serve(app.fetch); From e00138b244e7099246c47960537a849df51a9185 Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Sat, 24 Aug 2024 15:31:45 +0900 Subject: [PATCH 4/4] refactor(middleware): Rename the directory name Correct the spelling. --- src/libs/{middlewares => middleware}/content.ts | 0 src/libs/{middlewares => middleware}/content_test.ts | 0 src/libs/{middlewares => middleware}/redirect.ts | 0 src/libs/{middlewares => middleware}/redirect_test.ts | 0 src/libs/mod.ts | 4 ++-- 5 files changed, 2 insertions(+), 2 deletions(-) rename src/libs/{middlewares => middleware}/content.ts (100%) rename src/libs/{middlewares => middleware}/content_test.ts (100%) rename src/libs/{middlewares => middleware}/redirect.ts (100%) rename src/libs/{middlewares => middleware}/redirect_test.ts (100%) diff --git a/src/libs/middlewares/content.ts b/src/libs/middleware/content.ts similarity index 100% rename from src/libs/middlewares/content.ts rename to src/libs/middleware/content.ts diff --git a/src/libs/middlewares/content_test.ts b/src/libs/middleware/content_test.ts similarity index 100% rename from src/libs/middlewares/content_test.ts rename to src/libs/middleware/content_test.ts diff --git a/src/libs/middlewares/redirect.ts b/src/libs/middleware/redirect.ts similarity index 100% rename from src/libs/middlewares/redirect.ts rename to src/libs/middleware/redirect.ts diff --git a/src/libs/middlewares/redirect_test.ts b/src/libs/middleware/redirect_test.ts similarity index 100% rename from src/libs/middlewares/redirect_test.ts rename to src/libs/middleware/redirect_test.ts diff --git a/src/libs/mod.ts b/src/libs/mod.ts index a7769af..44fff98 100644 --- a/src/libs/mod.ts +++ b/src/libs/mod.ts @@ -1,2 +1,2 @@ -export * from "./middlewares/content.ts"; -export * from "./middlewares/redirect.ts"; +export * from "./middleware/content.ts"; +export * from "./middleware/redirect.ts";