- commit
- d70bc13c143a0fdd8aa8016223c885f25b9c04d8
- parent
- 1c2bd152c0554cdff0edea510afeed36c5122f67
- author
- dami <dami.etoile@gmail.com>
| M | Readme.md | 16 | +-- |
| M | main.ts | 154 | +++++++++++++++++++++++++++--- |
@@ -1,9 +1,7 @@
1-# Deno Examples: Hello, World!2-3-Simply serving a single file over HTTP4-5-## Get your own copy to explore6-7-You can clone this example into a repo of your own, and use it to set up a new application hosted on Deno Deploy in a few clicks8-9-[](https://app.deno.com/new?clone=https://github.com/denoland/examples&path=hello-world)\ No newline at end of file1+## Hex As a Service2+- This is a basic URL shortener.3+- It caps at 62^4 URLs and does not check for collision.4+- The URL is stored in a KV store indexed by the slug.5+- Permanent redirect caches locally the long url for subsequent visits.6+- Logging is kept to exclusively meta-data about requests.7+- Served static HTML to allow for easy shortening.@@ -6,18 +6,146 @@ const kv = await Deno.openKv();
66 const router = new Router();77 router.get("/", (ctx) => {88 ctx.response.body = `<!DOCTYPE html>9- <html>10- <head><title>Hexlinks</title><head>11- <body>12- <h1>Meowdy</h1>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>9+<html lang="en">10+<head>11+ <meta charset="UTF-8">12+ <meta name="viewport" content="width=device-width, initial-scale=1.0">13+ <title>Hex As a Service</title>14+ <style>15+ :root {16+ --background-color: #121212;17+ --form-background: #1e1e1e;18+ --input-background: #2a2a2a;19+ --button-background: #9f7aea;20+ --button-hover-background: #805ad5;21+ --text-color: #e2e8f0;22+ --placeholder-color: #718096;23+ --border-color: #4a5568;24+ --focus-ring-color: rgba(159, 122, 234, 0.25);25+ --shadow-color: rgba(0, 0, 0, 0.25);26+ }27+28+ * {29+ box-sizing: border-box;30+ margin: 0;31+ padding: 0;32+ }33+34+ html, body {35+ height: 100%;36+ }37+38+ body {39+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";40+ background-color: var(--background-color);41+ color: var(--text-color);42+ display: flex;43+ align-items: center;44+ justify-content: center;45+ padding: 1rem;46+ }47+48+ .form-container {49+ width: 100%;50+ max-width: 500px;51+ background-color: var(--form-background);52+ padding: 2.5rem;53+ border-radius: 16px;54+ border: 1px solid var(--border-color);55+ box-shadow: 0 8px 32px var(--shadow-color);56+ text-align: center;57+ }58+59+ h1 {60+ font-size: 1.75rem;61+ font-weight: 700;62+ margin-bottom: 0.5rem;63+ color: var(--button-background);64+ }65+66+ p {67+ margin-bottom: 2rem;68+ color: var(--placeholder-color);69+ }70+71+ form {72+ display: flex;73+ flex-direction: column;74+ gap: 1rem;75+ }76+77+ .url-input {78+ width: 100%;79+ padding: 1rem;80+ border: 1px solid var(--border-color);81+ border-radius: 8px;82+ background-color: var(--input-background);83+ font-size: 1rem;84+ color: var(--text-color);85+ transition: border-color 0.2s ease, box-shadow 0.2s ease;86+ }87+88+ .url-input::placeholder {89+ color: var(--placeholder-color);90+ }91+92+ .url-input:focus {93+ outline: none;94+ border-color: var(--button-background);95+ box-shadow: 0 0 0 4px var(--focus-ring-color);96+ }97+ 98+ .submit-button {99+ width: 100%;100+ padding: 1rem;101+ border: none;102+ border-radius: 8px;103+ background-color: var(--button-background);104+ color: white;105+ font-size: 1rem;106+ font-weight: 600;107+ cursor: pointer;108+ transition: background-color 0.2s ease, transform 0.1s ease;109+ }110+111+ .submit-button:hover {112+ background-color: var(--button-hover-background);113+ }114+115+ .submit-button:active {116+ transform: scale(0.98);117+ }118+119+ .submit-button {120+ display: none;121+ }122+123+ .url-input:not(:placeholder-shown) + .submit-button {124+ display: block;125+ }126+ </style>127+</head>128+<body>129+130+ <div class="form-container">131+ <h1>Meowdy, Mortal</h1>132+ <p>Condense any URL into a 4-digit hex code.</p>133+ 15134 <form action="/new" method="post">16- <input type="text" name="URL" placeholder="Enter URL" />17- <button type="submit">Ziplink!</button>135+ <input 136+ type="url" 137+ name="url" 138+ class="url-input" 139+ placeholder="Hex a URL" 140+ required141+ >142+ <button type="submit" class="submit-button">Hex It</button>18143 </form>19- </body>20- </html>144+ </div>145+146+</body>147+</html>148+21149 `;22150 });23151 @@ -28,9 +156,9 @@ router.post("/new", async (ctx) => {
28156 return;29157 }30158 const formData = await ctx.request.body.formData();31- const longURL = formData.get("URL");159+ const longURL = formData.get("url");32160 if (!URL.canParse(longURL)) {33- console.error(`[UnprocessableEntity] ${ctx.request.ip} ${Date.now()}`)161+ console.error(`[UnprocessableEntity] ${Date.now()}`)34162 ctx.response.status = Status.UnprocessableEntity;35163 ctx.response.body = {error: "The hex fizzles on non-urls."};36164 return;@@ -49,7 +177,7 @@ router.post("/new", async (ctx) => {
49177 router.get("/:slug", async (ctx) => {50178 const slug = ctx.params.slug;51179 if (slug.length !== 4) {52- console.error(`[BadRequest] ${ctx.request.ip} ${Date.now()}`);180+ console.error(`[BadRequest] ${Date.now()}`);53181 ctx.response.status = Status.BadRequest;54182 ctx.response.body = {error: "You ain't gotta do all that. Hexes that big don't exist!"};55183 return;