From 5bd28aa150f4404d39cd0bb97d5b264d757a66db Mon Sep 17 00:00:00 2001 From: nzambello Date: Sun, 10 Mar 2024 00:38:36 +0200 Subject: [PATCH] initial implementation --- .editorConfig | 12 +++ .gitignore | 1 + data/.gitkeep | 0 src/html/homepage.ts | 46 +++++++++ src/html/layout.ts | 40 ++++++++ src/html/linkDetails.ts | 75 ++++++++++++++ src/html/notFound.ts | 24 +++++ src/index.ts | 156 ++++++++++++++++++++++++++++-- static/android-chrome-192x192.png | Bin 0 -> 5763 bytes static/android-chrome-512x512.png | Bin 0 -> 17448 bytes static/apple-touch-icon.png | Bin 0 -> 5079 bytes static/favicon-16x16.png | Bin 0 -> 306 bytes static/favicon-32x32.png | Bin 0 -> 637 bytes static/favicon.ico | Bin 0 -> 15406 bytes static/logo.png | Bin 0 -> 9018 bytes static/logo.svg | 1 + 16 files changed, 349 insertions(+), 6 deletions(-) create mode 100644 .editorConfig create mode 100644 data/.gitkeep create mode 100644 src/html/homepage.ts create mode 100644 src/html/layout.ts create mode 100644 src/html/linkDetails.ts create mode 100644 src/html/notFound.ts create mode 100644 static/android-chrome-192x192.png create mode 100644 static/android-chrome-512x512.png create mode 100644 static/apple-touch-icon.png create mode 100644 static/favicon-16x16.png create mode 100644 static/favicon-32x32.png create mode 100644 static/favicon.ico create mode 100644 static/logo.png create mode 100644 static/logo.svg diff --git a/.editorConfig b/.editorConfig new file mode 100644 index 0000000..afbd9e3 --- /dev/null +++ b/.editorConfig @@ -0,0 +1,12 @@ +[*] +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[{*.css,*.scss,*.less,*.overrides,*.variables}] +indent_size = 4 + +[{*.js,*.jsx,*.json,*.ts,*.tsx}] +indent_size = 2 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3c3629e..6eb5a28 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +data/*.sqlite diff --git a/data/.gitkeep b/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/html/homepage.ts b/src/html/homepage.ts new file mode 100644 index 0000000..36190f4 --- /dev/null +++ b/src/html/homepage.ts @@ -0,0 +1,46 @@ +import { html } from "hono/html"; +import layout from "./layout"; + +const homepage = layout(html` +
+

Link Shortner

+ + +
+
+
+ + +
+ +
+
+
+`); + +export default homepage; diff --git a/src/html/layout.ts b/src/html/layout.ts new file mode 100644 index 0000000..3d7bae9 --- /dev/null +++ b/src/html/layout.ts @@ -0,0 +1,40 @@ +import { html } from "hono/html"; + +const layout = (children: any) => html` + + + + + + + + + + + + + + + Link Shortner + + + + + + + + + + + + + + + + + ${children} + + +`; + +export default layout; diff --git a/src/html/linkDetails.ts b/src/html/linkDetails.ts new file mode 100644 index 0000000..8405f76 --- /dev/null +++ b/src/html/linkDetails.ts @@ -0,0 +1,75 @@ +import { html } from "hono/html"; +import layout from "./layout"; + +const linkDetails = (link: { + id: string; + url: string; + expires_at?: string; + created_at: string; +}) => + layout(html` +
+ +

Link Shortner

+
+ + +
+

Link Details

+ +

Target URL:

+ + ${link.url} + + +

Expires:

+

+ +

+ +

Short URL:

+ + + /${link.id} + + + +
+ +
+ `); + +export default linkDetails; diff --git a/src/html/notFound.ts b/src/html/notFound.ts new file mode 100644 index 0000000..27771d8 --- /dev/null +++ b/src/html/notFound.ts @@ -0,0 +1,24 @@ +import { html } from "hono/html"; +import layout from "./layout"; + +const notFound = layout(html` +
+ +

Link Shortner

+
+ + +
+

Not Found

+

The link you are trying to access does not exist or has expired.

+
+
+`); + +export default notFound; diff --git a/src/index.ts b/src/index.ts index 3191383..d37272e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,153 @@ -import { Hono } from 'hono' +import { Hono } from "hono"; +import { cors } from "hono/cors"; +import { etag } from "hono/etag"; +import { logger } from "hono/logger"; +import { prettyJSON } from "hono/pretty-json"; +import { serveStatic } from "hono/bun"; -const app = new Hono() +import notFound from "./html/notFound"; +import homepage from "./html/homepage"; +import linkDetails from "./html/linkDetails"; -app.get('/', (c) => { - return c.text('Hello Hono!') -}) +import { Database } from "bun:sqlite"; -export default app +const db = new Database("./data/links.sqlite", { create: true }); + +const prepareDb = () => { + db.prepare( + `CREATE TABLE IF NOT EXISTS links ( + id TEXT PRIMARY KEY, + url TEXT NOT NULL, + expires_at TEXT, + created_at TEXT NOT NULL DEFAULT current_timestamp + )` + ).run(); +}; + +prepareDb(); + +const deleteExpiredLinks = () => { + db.prepare( + "DELETE FROM links WHERE datetime(expires_at) < datetime('now')" + ).run(); + + setTimeout(deleteExpiredLinks, 1000 * 60 * 60); +}; + +deleteExpiredLinks(); + +const app = new Hono(); +app.use(prettyJSON()); +app.notFound((c) => c.html(notFound, 404)); +app.use(etag(), logger()); + +app.use("/favicon.ico", serveStatic({ path: "./static/favicon.ico" })); +app.use("/logo.svg", serveStatic({ path: "./static/logo.svg" })); +app.use("/logo.png", serveStatic({ path: "./static/logo.png" })); +app.use( + "/apple-touch-icon.png", + serveStatic({ path: "./static/apple-touch-icon.png" }) +); +app.use( + "/favicon-32x32.png", + serveStatic({ path: "./static/favicon-32x32.png" }) +); +app.use( + "/favicon-16x16.png", + serveStatic({ path: "./static/favicon-16x16.png" }) +); + +app.use("/api/*", cors()); + +app.get("/", (c) => { + return c.html(homepage); +}); + +app.get("/:id", (c) => { + const { id } = c.req.param(); + const link = db.prepare("SELECT * FROM links WHERE id = ?").get(id) as + | { + id: string; + url: string; + expires_at: string; + created_at: string; + } + | undefined; + if (link) { + if (link.expires_at?.length) { + const expiresAt = new Date(link.expires_at); + if (expiresAt < new Date()) { + return c.html(notFound, 404); + } + } + + return c.redirect(link.url); + } + + return c.html(notFound, 404); +}); + +app.get("/link/:id", (c) => { + const { id } = c.req.param(); + const link = db.prepare("SELECT * FROM links WHERE id = ?").get(id) as + | { + id: string; + url: string; + expires_at?: string; + created_at: string; + } + | undefined; + if (link) { + return c.html(linkDetails(link)); + } + + return c.html(notFound, 404); +}); + +app.post("/api/shorten", async (c) => { + const formData = await c.req.formData(); + const url = formData.get("url") as string; + const expiration = formData.get("expiration") as string | null | undefined; + + let expires_at = ""; + if (expiration?.length && expiration !== "never") { + const date = new Date(); + switch (expiration) { + case "hour": + date.setHours(date.getHours() + 1); + break; + + case "day": + date.setDate(date.getDate() + 1); + break; + + case "week": + date.setDate(date.getDate() + 7); + break; + + case "month": + date.setMonth(date.getMonth() + 1); + break; + + default: + break; + } + + expires_at = date.toISOString(); + } + + const id = Math.random().toString(36).slice(2, 9); + + const query = expires_at?.length + ? `INSERT INTO links (id, url, expires_at) VALUES ('${id}', '${url}', '${expires_at}')` + : `INSERT INTO links (id, url) VALUES ('${id}', '${url}')`; + console.log(query); + db.prepare(query).run(); + + return c.redirect(`/link/${id}`); +}); + +export default { + port: Bun.env.PORT || 8787, + fetch: app.fetch, +}; diff --git a/static/android-chrome-192x192.png b/static/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..ddf5134656be602a972d17efb0655e9f81b9537b GIT binary patch literal 5763 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 I<@n-%0pK)*5dZ)H literal 0 HcmV?d00001 diff --git a/static/apple-touch-icon.png b/static/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2177b8c5dea7ad57eae39c39fc7261982c619f63 GIT binary patch literal 5079 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 literal 0 HcmV?d00001 diff --git a/static/favicon-16x16.png b/static/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..7335583db89c29306a6ce030c0c1b1397010e01a GIT binary patch literal 306 zcmV-20nPr2P)|Dk&s*$Z+&$yGqS2#8;!Dbxq@b%8h#-4J90 zrT`_E1MyL)W*#6d0cF1c(iK2_9Ei6f8;)$idZ0ua)OL_!FCe}H#Q%YKH4uvdF$WO8 z1>zk*{1s#nvH>Yj8+QZgd?1cOvPlrg23e2@#1=rT4#W-U27n9$NjL)WM5>17zn6zNCyJ+;9s60NCCp5e$8so&W#<07*qoM6N<$ Eg1QoEjQ{`u literal 0 HcmV?d00001 diff --git a/static/favicon-32x32.png b/static/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..c050cdef1abca11e64c04498d9bb94458b9dcab3 GIT binary patch literal 637 zcmV-@0)qXCP)<;q6-5vQ9+DIqDU}JLDKIhb7j2Vci(-_#dC27KIY#!GrxOg=G>G>l1oaG_m6}E z!U%*B*!K}AB~R4B8A$WV8ULC44&Cq+mV@S(Xc5r(Be)5FU=aTKEGqkaD1=$K0@Fc6 zL=$iq=Lhf@nqq8j?{k^X8(4&*Ad&DAkZ!`+OI;P@D_k}b@eyu8TP%Ms0o6FYhcf8% zSl|F-r{E~4?UbK1#!P3N`ZrukL_i}>3VoJ|guTXfIxUdxvG6KmM_?V^8Ic9XF4c_h za`TG7HLTvkdH7^`Xybb@3{OCpO}F1Ezi50lol$DN1ihjf=rnCbW=HW7kcq`N^A)wm zT4MU)72I%b>|XOC13J29cnHTpf)?Ox5CO7jh1;O}@WE_cL46$lx)-tf<+NYJ3Y>-| zGgnWosFlEo?H}zK>dfwdp41F{hIx<(`E-FUp;L4jbK#`1m)(>#e}cA-OhV+lU4o`! z3DB`z1#PB`XA96>_yIle0^CU}k1-`CWz}bi2zZH8CFu719f7^J-EN@M0ma}v)vfuU z5jcnK06aHeu@P-2kMSJFhF}hK2X>U80_3C{t&6D)*$xV+{S4h}A7(&VQLo`3=ygo& zMSy(ubGi>1aYR6o)7ejY`NR_e$NvzG3!wMy=fB3h63-Vuv55$XyN5o%1~Har~Zhe!t&&jv3$g_Bzk^`F=m&%k%l(p5OO5 zQB)PRi>j+5u7zl7`zSgqilRcH@xG=@6fGuh;J`S&cNA?pGKy-jAqzPg`L`k<*|%_`4An!y*_B*TD@i4mv?oEhJ({ zDzY=-UNMH}!6lHTo+P{u)4^v?V_yA!5HzPFNYuYKHNkpY%8N-*xgK!97T^MKeF_R5 z2p;%Pc;GOe;v$#`lbXT(qz~k&85H&{{80w4VS6gHRtqS=de?#b25(dc_OWC?OJDnw zelvUn-$7rfROgWKcX$-4$}GgaeJ8w8X1=ufQf_vG`*kI9@78x`RFauITj0ewulu#( z%_-g@`~p~yE%|>WHum+Wa5yCL{mlA(2W?5+F!&jkr3?oXwmnNh|2L;| zNz~SNMRhFf0Q;*HjmOBgI_A^5ha2O47rY+MgP|#WmA}>flB1N z@(-}OlFZ~80rtOZU!}T}jQZeVC^nz7%C&CSTjyY>ETacn;-xLkDPU zb)kta`?8?6Oe+7<#JmpW9k3j#-hYpaZ z94GZ)A5R6(x|bKztwj1wM;pIWTnYtno*Y$#Zp5YcxhuKf4fgS1Q0^sP!cQ<4DpgXB zZ5xtNh~sGvSig3DYkMDFh4n>POWY_(G>CxT?exL8IMRNZ0*&VxB%pE=%r_B>@D&IHG_YxP*jQ#~l`V{rX> z4DuK=6!RDLa9u5xT5Lx{0et@n3LXd^2p$L?2p$L?2p$L?2p$L? K2p%ZQ1AhVsN0@K` literal 0 HcmV?d00001 diff --git a/static/logo.png b/static/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..92b2705df1d17e1aa0e5699d19f534f2776e17f0 GIT binary patch literal 9018 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 literal 0 HcmV?d00001 diff --git a/static/logo.svg b/static/logo.svg new file mode 100644 index 0000000..ae6e160 --- /dev/null +++ b/static/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file