220 lines
5.6 KiB
Nim
220 lines
5.6 KiB
Nim
import asyncdispatch, db_sqlite, httpclient, jester, moustachu, os, shorturl,
|
|
strutils, tables, times, twtxt, typetraits
|
|
|
|
const
|
|
baseTemplate*: string = staticRead "./templates/layout.mustache"
|
|
errorTemplate*: string = staticRead "./templates/error.mustache"
|
|
indexTemplate*: string = staticRead "./templates/index.mustache"
|
|
usersTemplate*: string = staticRead "./templates/users.mustache"
|
|
tweetsTemplate*: string = staticRead "./templates/tweets.mustache"
|
|
submitTemplate*: string = staticRead "./templates/submit.mustache"
|
|
|
|
let
|
|
db = open("data/twtxt.db", nil, nil, nil)
|
|
|
|
try:
|
|
db.exec sql"""create table if not exists users (
|
|
id INTEGER PRIMARY KEY,
|
|
username TEXT UNIQUE,
|
|
url TEXT UNIQUE
|
|
);"""
|
|
|
|
db.exec sql"""create table if not exists tweets (
|
|
id INTEGER PRIMARY KEY,
|
|
username TEXT,
|
|
time REAL,
|
|
tweet TEXT,
|
|
UNIQUE (username, time, tweet)
|
|
);"""
|
|
except:
|
|
quit getCurrentExceptionMsg()
|
|
|
|
template renderMustache*(title: string, templ: string, ctx: Context): expr =
|
|
var
|
|
layoutCtx = moustachu.newContext()
|
|
|
|
layoutCtx["title"] = title
|
|
layoutCtx["body"] = render(templ, ctx)
|
|
|
|
resp render(baseTemplate, layoutCtx)
|
|
|
|
template fail*(): expr =
|
|
var ctx = newContext()
|
|
|
|
ctx["exception"] = getCurrentExceptionMsg()
|
|
|
|
var
|
|
layoutCtx = moustachu.newContext()
|
|
|
|
layoutCtx["title"] = "fail"
|
|
layoutCtx["body"] = render(errorTemplate, ctx)
|
|
|
|
halt render(baseTemplate, layoutCtx)
|
|
|
|
template pagination(ctx: Context, isnt1: bool, prev, next: string): expr =
|
|
ctx["paging"] = true
|
|
ctx["isnt1"] = isnt1
|
|
ctx["prev"] = prev
|
|
ctx["next"] = next
|
|
|
|
settings:
|
|
port = getEnv("PORT").parseInt().Port
|
|
bindAddr = "0.0.0.0"
|
|
|
|
routes:
|
|
get "/":
|
|
renderMustache("home", indexTemplate, newContext())
|
|
|
|
get "/content/system":
|
|
let myHeaders = {
|
|
"Content-Type": "text/plain",
|
|
}
|
|
|
|
resp Http200, myHeaders, "hi"
|
|
|
|
get "/timeline":
|
|
redirect "/timeline/0"
|
|
|
|
get "/submit":
|
|
renderMustache "submit", submitTemplate, newContext()
|
|
|
|
post "/submit":
|
|
try:
|
|
var
|
|
username = $(request.formData.mget "username").body
|
|
url = $(request.formData.mget "url").body
|
|
|
|
discard url.getTweetsFrom(username)
|
|
|
|
db.exec(sql"insert into users values (null, ?, ?)", username, url)
|
|
|
|
updateTweetsOnce()
|
|
|
|
redirect "/users/" & username & "/0"
|
|
except: fail()
|
|
|
|
get "/users":
|
|
var
|
|
users = db.getAllRows(sql"select username, url from users order by username")
|
|
ctx = newContext()
|
|
userList = newSeq[Context]()
|
|
|
|
for user in users.items():
|
|
var c = newContext()
|
|
c["username"] = user[0]
|
|
c["url"] = user[1]
|
|
|
|
userList.add c
|
|
|
|
ctx["users"] = userList
|
|
|
|
renderMustache "users", usersTemplate, ctx
|
|
|
|
get "/users/@name/@page":
|
|
try:
|
|
var
|
|
tweets = db.getAllRows(sql"select * from tweets where username=? order by time desc limit 20 offset ?", @"name", (@"page").parseInt() * 20)
|
|
ctx = newContext()
|
|
tweetList = newSeq[Context]()
|
|
|
|
if len(tweets) == 0:
|
|
raise newException(ValueError, "no such page")
|
|
|
|
var
|
|
userctx = newContext()
|
|
userurl = db.getValue(sql"select url from users where username=?", @"name")
|
|
|
|
userctx["url"] = userurl
|
|
ctx["user"] = userctx
|
|
|
|
for tweet in tweets.items():
|
|
var c = newContext()
|
|
|
|
let time = tweet[2].split(".")[0].parseInt()
|
|
|
|
c["id"] = tweet[0]
|
|
c["permalink"] = tweet[0].parseInt().encodeURLSimple()
|
|
c["username"] = tweet[1]
|
|
c["time"] = time
|
|
c["tweet"] = tweet[3]
|
|
|
|
tweetList.add c
|
|
|
|
ctx["tweets"] = tweetList
|
|
|
|
let id = (@"page").parseInt()
|
|
|
|
if @"page" == "0":
|
|
ctx.pagination(false, "", "/users/" & @"name" & "/" & $(id + 1))
|
|
else:
|
|
ctx.pagination(true, "/users/" & @"name" & "/" & $(id - 1), "/users/" & @"name" & "/" & $(id + 1))
|
|
|
|
renderMustache(@"name" & " page " & @"page", tweetsTemplate, ctx)
|
|
except: fail()
|
|
|
|
get "/timeline/@page":
|
|
try:
|
|
var
|
|
tweets = db.getAllRows(sql"select * from tweets order by time desc limit 20 offset ?", (@"page").parseInt() * 20)
|
|
ctx = newContext()
|
|
tweetList = newSeq[Context]()
|
|
|
|
if tweets.len == 0:
|
|
raise newException(ValueError, "no such page")
|
|
|
|
for tweet in tweets.items():
|
|
var c = newContext()
|
|
|
|
let time = tweet[2].split(".")[0].parseInt()
|
|
|
|
c["id"] = tweet[0]
|
|
c["permalink"] = tweet[0].parseInt().encodeURLSimple()
|
|
c["username"] = tweet[1]
|
|
c["time"] = time
|
|
c["tweet"] = tweet[3]
|
|
|
|
tweetList.add c
|
|
|
|
ctx["tweets"] = tweetList
|
|
|
|
let id = (@"page").parseInt()
|
|
|
|
if @"page" == "0":
|
|
ctx.pagination(false, "", "/timeline/" & $(id + 1))
|
|
else:
|
|
ctx.pagination(true, "/timeline/" & $(id - 1), "/timeline/" & $(id + 1))
|
|
|
|
renderMustache("timeline page " & @"page", tweetsTemplate, ctx)
|
|
|
|
except: fail()
|
|
|
|
get "/tweet/@tid":
|
|
try:
|
|
var
|
|
tweets = db.getAllRows(sql"select * from tweets where id=? limit 1", (@"tid").decodeURLSimple())
|
|
ctx = newContext()
|
|
tweetList = newSeq[Context]()
|
|
|
|
if tweets.len == 0:
|
|
raise newException(ValueError, "no such tweet")
|
|
|
|
for tweet in tweets.items():
|
|
var c = newContext()
|
|
|
|
let time = tweet[2].split(".")[0].parseInt()
|
|
|
|
c["id"] = tweet[0]
|
|
c["permalink"] = tweet[0].parseInt().encodeURLSimple()
|
|
c["username"] = tweet[1]
|
|
c["time"] = time
|
|
c["tweet"] = tweet[3]
|
|
|
|
tweetList.add c
|
|
|
|
ctx["tweets"] = tweetList
|
|
|
|
renderMustache("tweet " & @"tid", tweetsTemplate, ctx)
|
|
except: fail()
|
|
|
|
runForever()
|