summaryrefslogtreecommitdiff
path: root/src/short.nim
diff options
context:
space:
mode:
authorJulien Dessaux2021-10-22 17:59:44 +0200
committerJulien Dessaux2021-10-25 15:22:24 +0200
commitad9b9c0f7bd4d95ddc54462970d33d92bab9392c (patch)
treeb8d130d728fda49c64b6e021da31afb2418b78a0 /src/short.nim
parentInitial import (diff)
downloadshort-ad9b9c0f7bd4d95ddc54462970d33d92bab9392c.tar.gz
short-ad9b9c0f7bd4d95ddc54462970d33d92bab9392c.tar.bz2
short-ad9b9c0f7bd4d95ddc54462970d33d92bab9392c.zip
Added a functionning url shortening service
Diffstat (limited to 'src/short.nim')
-rw-r--r--src/short.nim115
1 files changed, 115 insertions, 0 deletions
diff --git a/src/short.nim b/src/short.nim
new file mode 100644
index 0000000..959e5a7
--- /dev/null
+++ b/src/short.nim
@@ -0,0 +1,115 @@
+import os, strutils
+import std/[hashes, oids, re, times, uri]
+
+import tiny_sqlite
+import jester
+import nimja/parser
+
+import database
+
+const allCss = staticRead("../static/all.css")
+const cssRoute = "/static/all.css." & $hash(allCss)
+const favicon = staticRead("../static/favicon.ico")
+
+var db {.threadvar.}: DbConn
+
+proc initDB() {.raises: [SqliteError].}=
+ if not db.isOpen():
+ db = openDatabase("short.db")
+ if not db.Migrate():
+ echo "Failed to migrate database schema"
+ quit 1
+
+func renderIndex(): string {.raises: [].} =
+ var req: ShortUrl
+ compileTemplateFile(getScriptDir() / "templates/index.html")
+
+func renderShort(req: ShortUrl): string {.raises: [].} =
+ compileTemplateFile(getScriptDir() / "templates/short.html")
+
+func renderNoShort(req: ShortUrl): string {.raises: [].} =
+ compileTemplateFile(getScriptDir() / "templates/noshort.html")
+
+func renderError(code: int, msg: string): string {.raises: [].} =
+ compileTemplateFile(getScriptDir() / "templates/error.html")
+
+proc handleToken(token:string): (HttpCode, string) {.raises: [].} =
+ try:
+ let tokenRegexp = re"^[\w]{24}$"
+ if not match(token, tokenRegexp):
+ return (Http400, renderError(400, "Bad Request"))
+ except RegexError:
+ return (Http500, renderError(500, "RegexError"))
+ db.CleanExpired()
+ try:
+ let req = db.GetUrl(token)
+ if req == nil:
+ return (Http404, renderNoShort(req[]))
+ return (Http200, renderShort(req[]))
+ except SqliteError:
+ return (Http500, renderError(500, "SqliteError"))
+
+proc handleIndexPost(params: Table[string, string]): (HttpCode, string) {.raises: [].} =
+ var input: ShortUrl
+ var exp: int
+ for k, v in params.pairs:
+ case k:
+ of "title":
+ try:
+ let titleRegexp = re"^[\w\s]{3,64}$"
+ if match(v, titleRegexp):
+ input.Title = v
+ else:
+ echo "title"
+ return (Http400, renderError(400, "Bad Request"))
+ except RegexError:
+ return (Http500, renderError(500, "RegexError"))
+ of "url":
+ try:
+ discard parseUri(v)
+ input.Url = v
+ except:
+ echo "url"
+ return (Http400, renderError(400, "Bad Request"))
+ of "expires":
+ try:
+ exp = parseInt(v)
+ except ValueError:
+ return (Http400, renderError(400, "Bad Request"))
+ if exp < 1 or exp > 527040:
+ echo "exp"
+ return (Http400, renderError(400, "Bad Request"))
+ of "shorten": discard
+ else: return (Http400, renderError(400, "Bad Request"))
+ if input.Title == "" or input.Url == "" or exp == 0:
+ echo "empty"
+ return (Http400, renderError(400, "Bad Request"))
+ input.Token = $genOid()
+ input.Created = times.now()
+ input.Expires = input.Created + initDuration(minutes=exp)
+ try:
+ db.AddUrl(input)
+ except SqliteError:
+ return (Http500, renderShort(input))
+ return (Http200, input.Token)
+
+routes:
+ get "/":
+ resp renderIndex()
+ post "/":
+ initDB()
+ var (code, content) = handleIndexPost(request.params)
+ if code != Http200:
+ resp code, content
+ else:
+ redirect("/" & content)
+ get "/static/favicon.ico":
+ resp Http200, {"content-type": "image/x-icon"}, favicon
+ get re"^/static/all\.css\.":
+ resp Http200, {"content-type": "text/css"}, allcss
+ get "/@token":
+ initDB()
+ var (code, content) = handleToken(@"token")
+ resp code, content
+
+runForever()