clone

a tiny url shortener with four-character slugs, backed by a kv store

hexlinks v0.2

dami committed 1c2bd15
commit
1c2bd152c0554cdff0edea510afeed36c5122f67
parent
efa6aeb272d7efaa1bfb1bb281e7784824a8ef9e
author
dami <dami.etoile@gmail.com>
1 file changed, +31 -22
Mmain.ts53++++++++++++++++++------------
M main.ts +31 -22
@@ -7,13 +7,15 @@ const router = new Router();
77 router.get("/", (ctx) => {88 ctx.response.body = `<!DOCTYPE html>99 <html>10- <head><title>Ziplinks</title><head>10+ <head><title>Hexlinks</title><head>1111 <body>1212 <h1>Meowdy</h1>13- <p>Create ziplinks easily to share links with friends.</p>14- <p>Each link will be hexed! Cursed to shrink down to a vulnerable and balding 4-digit code.</p>15- <p>Point at the puny urls and laugh!</p>16- <!-- TODO: Forum with one text input for the url and a submit button to post the data to /new -->13+ <p>Hex your favorite link to easily share.</p>14+ <p>Each link will be cursed to shrink down to a vulnerable, and balding, 4-digit code.</p>15+ <form action="/new" method="post">16+ <input type="text" name="URL" placeholder="Enter URL" />17+ <button type="submit">Ziplink!</button>18+ </form>1719 </body>1820 </html>1921 `;
@@ -22,46 +24,53 @@ router.get("/", (ctx) => {
2224 router.post("/new", async (ctx) => {2325 if (!ctx.request.hasBody) {2426 ctx.response.status = Status.BadRequest;25- ctx.response.body = { error: "Request body is missing." };27+ ctx.response.body = { error: "Spell, Hex, requires a target." };2628 return;2729 }28-29- const body = ctx.request.body({ type: "json" });30- const data = await body.value;31- console.debug("[Body]", data);32- const longURL = data.URL;30+ const formData = await ctx.request.body.formData();31+ const longURL = formData.get("URL");32+ if (!URL.canParse(longURL)) {33+ console.error(`[UnprocessableEntity] ${ctx.request.ip} ${Date.now()}`)34+ ctx.response.status = Status.UnprocessableEntity;35+ ctx.response.body = {error: "The hex fizzles on non-urls."};36+ return;37+ }38+ 3339 const slug = crypto.randomUUID().substring(0, 4);34- const result = await kv.set(["urls", slug], longURL);35- console.log(`[New] ${slug.padStart(6)} ${Date.now()}`);36- ctx.response.status = Status.Created;40+ await kv.set(["urls", slug], longURL);41+ 42+ console.log(`[Created] ${slug.padStart(6)} ${Date.now()}`);43+ 3744 ctx.response.headers.set("Location", `/${slug}`);38- ctx.response.body = { Destination: longURL };45+ ctx.response.status = Status.Created;46+ ctx.response.body = { URL: longURL, hex: slug };3947 });4048 4149 router.get("/:slug", async (ctx) => {4250 const slug = ctx.params.slug;4351 if (slug.length !== 4) {44- ctx.response.status = Status.BadRequest;4552 console.error(`[BadRequest] ${ctx.request.ip} ${Date.now()}`);53+ ctx.response.status = Status.BadRequest;54+ ctx.response.body = {error: "You ain't gotta do all that. Hexes that big don't exist!"};4655 return;4756 }4857 49- console.info(`[Requested] ${slug.padStart(6)} ${Date.now()}`);50- const entry = await kv.get(["urls", slug]);51- console.debug(`[Result] ${entry} ${Date.now()}`);58+ console.log(`[Requested] ${slug.padStart(6)} ${Date.now()}`);5259 60+ const entry = await kv.get(["urls", slug]);5361 if (entry.value === null) {5462 console.warn(`[NotFound] ${slug.padStart(6)} ${Date.now()}`);5563 ctx.response.status = Status.NotFound;64+ ctx.response.body = {error: "Your URL is in another castle."};5665 return;5766 };58- console.info(`[Found] ${slug.padStart(6)} ${Date.now()}`);67+ 68+ console.log(`[Found] ${slug.padStart(6)} ${Date.now()}`);5969 ctx.response.status = Status.MovedPermanently;60- ctx.response.headers.set("Location", "https://www.youtube.com/");70+ ctx.response.headers.set("Location", entry.value as string);6171 });6272 6373 const app = new Application();6474 app.use(router.routes());6575 app.use(router.allowedMethods());66-6776 app.listen({ port: 8080 });