- commit
- efa6aeb272d7efaa1bfb1bb281e7784824a8ef9e
- parent
- a157fad96130d2315c0ab90bf3719c790bdba835
- author
- dami <dami.etoile@gmail.com>
| M | deno.json | 3 | +- |
| M | deno.lock | 68 | +++++++++++++++++++++++-- |
| D | index.html | 80 | ------------------------------ |
| M | main.ts | 70 | +++++++++++++++++++++++++-- |
@@ -1,22 +1,69 @@
11 {22 "version": "5",33 "specifiers": {4+ "jsr:@oak/commons@1": "1.0.1",5+ "jsr:@oak/oak@*": "17.1.6",6+ "jsr:@oak/oak@^17.1.6": "17.1.6",7+ "jsr:@std/assert@1": "1.0.14",8+ "jsr:@std/bytes@1": "1.0.6",49 "jsr:@std/cli@^1.0.20": "1.0.20",10+ "jsr:@std/crypto@1": "1.0.5",11+ "jsr:@std/encoding@1": "1.0.10",512 "jsr:@std/encoding@^1.0.10": "1.0.10",613 "jsr:@std/fmt@^1.0.8": "1.0.8",714 "jsr:@std/fs@^1.0.19": "1.0.19",815 "jsr:@std/html@^1.0.4": "1.0.4",916 "jsr:@std/http@*": "1.0.19",10- "jsr:@std/internal@^1.0.9": "1.0.9",17+ "jsr:@std/http@1": "1.0.19",18+ "jsr:@std/internal@^1.0.10": "1.0.10",19+ "jsr:@std/internal@^1.0.9": "1.0.10",20+ "jsr:@std/media-types@1": "1.1.0",1121 "jsr:@std/media-types@^1.1.0": "1.1.0",1222 "jsr:@std/net@^1.0.4": "1.0.4",23+ "jsr:@std/path@1": "1.1.1",1324 "jsr:@std/path@^1.1.1": "1.1.1",14- "jsr:@std/streams@^1.0.10": "1.0.10"25+ "jsr:@std/streams@^1.0.10": "1.0.10",26+ "npm:path-to-regexp@^6.3.0": "6.3.0"1527 },1628 "jsr": {29+ "@oak/commons@1.0.1": {30+ "integrity": "889ff210f0b4292591721be07244ecb1b5c118742f5273c70cf30d7cd4184d0c",31+ "dependencies": [32+ "jsr:@std/assert",33+ "jsr:@std/bytes",34+ "jsr:@std/crypto",35+ "jsr:@std/encoding@1",36+ "jsr:@std/http@1",37+ "jsr:@std/media-types@1"38+ ]39+ },40+ "@oak/oak@17.1.6": {41+ "integrity": "c7eef2eec733fba8e72b679bba3b8cf2fceccf5ef489a8b8fb43571908c0335d",42+ "dependencies": [43+ "jsr:@oak/commons",44+ "jsr:@std/assert",45+ "jsr:@std/bytes",46+ "jsr:@std/http@1",47+ "jsr:@std/media-types@1",48+ "jsr:@std/path@1",49+ "npm:path-to-regexp"50+ ]51+ },52+ "@std/assert@1.0.14": {53+ "integrity": "68d0d4a43b365abc927f45a9b85c639ea18a9fab96ad92281e493e4ed84abaa4",54+ "dependencies": [55+ "jsr:@std/internal@^1.0.10"56+ ]57+ },58+ "@std/bytes@1.0.6": {59+ "integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a"60+ },1761 "@std/cli@1.0.20": {1862 "integrity": "a8c384a2c98cec6ec6a2055c273a916e2772485eb784af0db004c5ab8ba52333"1963 },64+ "@std/crypto@1.0.5": {65+ "integrity": "0dcfbb319fe0bba1bd3af904ceb4f948cde1b92979ec1614528380ed308a3b40"66+ },2067 "@std/encoding@1.0.10": {2168 "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1"2269 },@@ -33,19 +80,22 @@
3380 "integrity": "52128c8d00a1f0b20019f8b72376e7ef5f3133375b6f805b5bc89b9de2ad4686",3481 "dependencies": [3582 "jsr:@std/cli",36- "jsr:@std/encoding",83+ "jsr:@std/encoding@^1.0.10",3784 "jsr:@std/fmt",3885 "jsr:@std/fs",3986 "jsr:@std/html",40- "jsr:@std/media-types",87+ "jsr:@std/media-types@^1.1.0",4188 "jsr:@std/net",42- "jsr:@std/path",89+ "jsr:@std/path@^1.1.1",4390 "jsr:@std/streams"4491 ]4592 },4693 "@std/internal@1.0.9": {4794 "integrity": "bdfb97f83e4db7a13e8faab26fb1958d1b80cc64366501af78a0aee151696eb8"4895 },96+ "@std/internal@1.0.10": {97+ "integrity": "e3be62ce42cab0e177c27698e5d9800122f67b766a0bea6ca4867886cbde8cf7"98+ },4999 "@std/media-types@1.1.0": {50100 "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4"51101 },@@ -55,15 +105,21 @@
55105 "@std/path@1.1.1": {56106 "integrity": "fe00026bd3a7e6a27f73709b83c607798be40e20c81dde655ce34052fd82ec76",57107 "dependencies": [58- "jsr:@std/internal"108+ "jsr:@std/internal@^1.0.9"59109 ]60110 },61111 "@std/streams@1.0.10": {62112 "integrity": "75c0b1431873cd0d8b3d679015220204d36d3c7420d93b60acfc379eb0dc30af"63113 }64114 },115+ "npm": {116+ "path-to-regexp@6.3.0": {117+ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="118+ }119+ },65120 "workspace": {66121 "dependencies": [122+ "jsr:@oak/oak@^17.1.6",67123 "jsr:@std/assert@1"68124 ]69125 }@@ -1,80 +0,0 @@
1-2-<html lang="en">3-4-<head>5- <meta charset="UTF-8">6- <meta name="viewport" content="width=device-width, initial-scale=1.0">7- <title>Hello, Deno</title>8- <link rel="preload" href="https://demo-styles.deno.deno.net/fonts/Moranga-Regular.woff2" as="font" type="font/woff2" crossorigin />9- <link rel="preload" href="https://demo-styles.deno.deno.net/fonts/Moranga-Medium.woff2" as="font" type="font/woff2" crossorigin />10- <link rel="preload" href="https://demo-styles.deno.deno.net/fonts/Recursive_Variable.woff2" as="font" type="font/woff2" crossorigin />11- <link rel="stylesheet" href="https://demo-styles.deno.deno.net/styles.css">12- <style>13- img {14- display: inline-block;15- vertical-align: middle;16- }17- </style>18-</head>19-20-<body>21- <main>22- <section>23- <a href="https://deno.com/deploy">24- </a>25- <h1>26- <p>27- Hello, Deno!28- </p>29- </h1>30- <p>31- This little example is being served by <a href="https://deno.com/">Deno</a>.32- You can run it locally or in porduction on <a href="https://deno.com/deploy">Deno Deploy</a> or other hosting platforms.33- </p>34- <ul>35- <li>Run it locally with<code>deno task dev</code></li>36- <li>Deploy it to <a href="https://deno.com/deploy">Deno Deploy</a> via the CLI by running<code>deno deploy</code></li>37- <li>Run it locally and share it publically by running <code>deno deploy --connected</code></li>38- </ul>39- </section>40- <hr>41- <section>42- <h2>What's next?</h2>43- <p>44- You can clone this example and deploy it to Deno Deploy in just a few clicks45- <a href="https://app.deno.com/new?clone=https://github.com/denoland/examples&path=hello-world"><img src="https://deno.com/button" alt="Deploy on Deno"/></a>46- </p>47- <ul>48- <li>49- <a href="https://docs.deno.com/examples">More examples in the Deno Docs</a>50- </li>51- <li>52- <a href="https://deno.com/deploy">Deno Deploy Early Access</a>53- </li>54- </ul>55- </section>56- </main>57- <hr>58- <footer>59- <section>60- <ul class="flex-btwn">61- <li>62- <a href="https://deno.com">BlueSky</a>63- </li>64- <li>65- <a href="https://deno.com">Twitter</a>66- </li>67- <li>68- <a href="https://deno.com">YouTube</a>69- </li>70- <li>71- <a href="https://deno.com">Newsletter</a>72- </li>73- <li>74- <a href="https://deno.com">Deno</a>75- </li>76- </ul>77- </section>78- </footer>79-</body>80-</html>\ No newline at end of file@@ -1,5 +1,67 @@
1-import { serveFile } from "jsr:@std/http/file-server";1+import { Application, Status } from "jsr:@oak/oak/";2+import { Router } from "jsr:@oak/oak/router";23 3-Deno.serve((req: Request) => {4- return serveFile(req, "./index.html");5-});\ No newline at end of file4+const kv = await Deno.openKv();5+6+const router = new Router();7+router.get("/", (ctx) => {8+ ctx.response.body = `<!DOCTYPE html>9+ <html>10+ <head><title>Ziplinks</title><head>11+ <body>12+ <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 -->17+ </body>18+ </html>19+ `;20+});21+22+router.post("/new", async (ctx) => {23+ if (!ctx.request.hasBody) {24+ ctx.response.status = Status.BadRequest;25+ ctx.response.body = { error: "Request body is missing." };26+ return;27+ }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;33+ 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;37+ ctx.response.headers.set("Location", `/${slug}`);38+ ctx.response.body = { Destination: longURL };39+});40+41+router.get("/:slug", async (ctx) => {42+ const slug = ctx.params.slug;43+ if (slug.length !== 4) {44+ ctx.response.status = Status.BadRequest;45+ console.error(`[BadRequest] ${ctx.request.ip} ${Date.now()}`);46+ return;47+ }48+ 49+ console.info(`[Requested] ${slug.padStart(6)} ${Date.now()}`);50+ const entry = await kv.get(["urls", slug]);51+ console.debug(`[Result] ${entry} ${Date.now()}`);52+ 53+ if (entry.value === null) {54+ console.warn(`[NotFound] ${slug.padStart(6)} ${Date.now()}`);55+ ctx.response.status = Status.NotFound;56+ return;57+ };58+ console.info(`[Found] ${slug.padStart(6)} ${Date.now()}`);59+ ctx.response.status = Status.MovedPermanently;60+ ctx.response.headers.set("Location", "https://www.youtube.com/");61+});62+63+const app = new Application();64+app.use(router.routes());65+app.use(router.allowedMethods());66+67+app.listen({ port: 8080 });