Rewritten to use Jester as the backend web framework.
This commit is contained in:
parent
ecbd49d2a0
commit
5e79d2191b
9 changed files with 180 additions and 372 deletions
|
|
@ -8,10 +8,13 @@
|
|||
|
||||
import cairo, os, strutils
|
||||
|
||||
proc getCaptureFilename*(i: int): string {.inline.} =
|
||||
result = "captures/capture_" & $i & ".png"
|
||||
proc getCaptchaFilename*(i: int): string {.inline.} =
|
||||
result = "public/captchas/capture_" & $i & ".png"
|
||||
|
||||
proc createCapture*(file, text: string) =
|
||||
proc getCaptchaUrl*(i: int): string =
|
||||
result = "/captchas/capture_" & $i & ".png"
|
||||
|
||||
proc createCaptcha*(file, text: string) =
|
||||
var surface = imageSurfaceCreate(FORMAT_ARGB32, 10*text.len, 10)
|
||||
var cr = create(surface)
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ create table if not exists person(
|
|||
email $# not null,
|
||||
creation timestamp not null default (DATETIME('now')),
|
||||
salt varbin(128) not null,
|
||||
status integer not null
|
||||
status integer not null,
|
||||
admin bool default false
|
||||
);""" % [TUserName, TPassword, TEmail]), [])
|
||||
# echo "person table already exists"
|
||||
|
||||
|
|
|
|||
16
forms.tmpl
16
forms.tmpl
|
|
@ -22,7 +22,7 @@
|
|||
</tr>
|
||||
# for row in Rows(db, query):
|
||||
<tr>
|
||||
<td class="topic">${UrlButton(c, XMLencode(%name), actionShow, %threadId)}</td>
|
||||
<td class="topic">${UrlButton(c, XMLencode(%name), c.genThreadUrl(threadid = %threadid))}</td>
|
||||
#let authorName = getValue(db, sql("select name from person where id = " &
|
||||
# "(select author from post where id = " &
|
||||
# "(select min(id) from post where thread = ?))"), %threadId)
|
||||
|
|
@ -95,10 +95,10 @@
|
|||
<hr/>
|
||||
${genGravatar(%userEmail)}
|
||||
#if c.userId == %postAuthor and c.currentPost.subject.len == 0:
|
||||
<hr/>${UrlButton(c, "Edit post", actionEditForm, %postId)}
|
||||
<hr/>${UrlButton(c, "Edit post", c.genThreadUrl(%postId, "edit"))}
|
||||
#elif c.isAdmin and c.currentPost.subject.len == 0:
|
||||
<hr/><span style="color:red">
|
||||
${UrlButton(c, "Edit post", actionEditForm, %postId)}</span>
|
||||
${UrlButton(c, "Edit post", c.genThreadUrl(%postId, "edit"))}</span>
|
||||
#end if
|
||||
</td>
|
||||
<td class="content">
|
||||
|
|
@ -114,7 +114,7 @@
|
|||
#end proc
|
||||
#
|
||||
#
|
||||
#proc genFormPost(c: var TForumData, action: TForumAction,
|
||||
#proc genFormPost(c: var TForumData, action: string,
|
||||
# isEdit: bool, title, content: string): string =
|
||||
# result = ""
|
||||
<br />
|
||||
|
|
@ -123,7 +123,7 @@
|
|||
<div id="replytop">
|
||||
<span>Reply</span>
|
||||
</div>
|
||||
<form action="$postAction" method="POST">
|
||||
<form action="/$action" method="POST">
|
||||
${FieldValid(c, "subject", "Subject:")}
|
||||
${TextWidget(c, "subject", title, maxlength=100)}
|
||||
<br />
|
||||
|
|
@ -146,7 +146,7 @@
|
|||
#
|
||||
#proc genFormRegister(c: var TForumData): string =
|
||||
# result = ""
|
||||
<form action="$postAction" method="POST">
|
||||
<form action="/doregister" method="POST">
|
||||
<b>Register</b><br />
|
||||
<table border="0">
|
||||
<tr>
|
||||
|
|
@ -166,7 +166,6 @@
|
|||
<td>${TextWidget(c, "antibot", "", maxlength=4)}</td>
|
||||
</tr>
|
||||
</table>
|
||||
${FormSession(c, actionRegister)}
|
||||
<input type="submit" value="Register">
|
||||
</form>
|
||||
#end proc
|
||||
|
|
@ -174,14 +173,13 @@
|
|||
#proc genFormLogin(c: var TForumData): string =
|
||||
# result = ""
|
||||
# if not c.loggedIn:
|
||||
<form action="$postAction" method="POST">
|
||||
<form action="/dologin" method="POST">
|
||||
<table border="0">
|
||||
<tr><td>Username:</td><td>
|
||||
<input type="text" name="name" maxlength="20"></td></tr>
|
||||
<tr><td>Password:</td><td>
|
||||
<input type="password" name="password" maxlength="20"></td></tr>
|
||||
</table>
|
||||
${FormSession(c, actionLogin)}
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
<span style="color:red">$c.loginErrorMsg</span>
|
||||
|
|
|
|||
515
forum.nim
515
forum.nim
|
|
@ -8,30 +8,15 @@
|
|||
|
||||
import
|
||||
os, strutils, times, md5, strtabs, cgi, math, db_sqlite, matchers,
|
||||
rst, rstgen, captchas, sockets, scgi, cookies
|
||||
rst, rstgen, captchas, sockets, scgi, jester
|
||||
|
||||
const
|
||||
unselectedThread = -1
|
||||
transientThread = 0
|
||||
websiteLoc = "/"
|
||||
postAction = "/"
|
||||
websiteLoc = ""
|
||||
|
||||
type
|
||||
TCrud = enum crCreate, crRead, crUpdate, crDelete
|
||||
TForumAction = enum
|
||||
actionShow = "show",
|
||||
actionLoginForm = "login",
|
||||
actionLogin = "dologin",
|
||||
actionLogout = "logout",
|
||||
actionRegisterForm = "register",
|
||||
actionRegister = "doregister",
|
||||
actionNewThreadForm = "newthread",
|
||||
actionNewThread = "donewthread",
|
||||
actionReplyForm = "reply",
|
||||
actionReply = "doreply",
|
||||
actionEditForm = "edit",
|
||||
actionEdit = "doedit",
|
||||
action404
|
||||
|
||||
TSession = object of TObject
|
||||
threadid: int
|
||||
|
|
@ -42,25 +27,15 @@ type
|
|||
TPost = tuple[subject, content: string]
|
||||
|
||||
TForumData = object of TSession
|
||||
action: TForumAction
|
||||
cgiData: PStringTable
|
||||
ip: string
|
||||
req: TRequest
|
||||
userid: string
|
||||
actionContent: string
|
||||
errorMsg, loginErrorMsg: string
|
||||
invalidField: string
|
||||
currentPost: TPost
|
||||
reqUrl: string
|
||||
startTime: float
|
||||
cookieData: PStringTable
|
||||
|
||||
TStyledButton = tuple[text: string, action: TForumAction, tid: string]
|
||||
|
||||
TRequest* {.final.} = object ## a request for the application to process
|
||||
ip*: string ## IP of request
|
||||
url*: string ## requested URL
|
||||
vars*: PStringTable ## other variables
|
||||
startTime*: float
|
||||
TStyledButton = tuple[text: string, link: string]
|
||||
|
||||
var
|
||||
db: TDbConn
|
||||
|
|
@ -72,8 +47,6 @@ proc init(c: var TForumData) =
|
|||
c.threadId = unselectedThread
|
||||
c.postId = -1
|
||||
|
||||
c.action = actionShow
|
||||
c.ip = ""
|
||||
c.userid = ""
|
||||
c.actionContent = ""
|
||||
c.errorMsg = ""
|
||||
|
|
@ -94,14 +67,14 @@ const
|
|||
proc TextWidget(c: TForumData, name, defaultText: string,
|
||||
maxlength = 30, size = -1): string =
|
||||
let x = if defaultText != reuseText: defaultText
|
||||
else: XMLencode(c.cgiData[name])
|
||||
else: XMLencode(c.req.params[name])
|
||||
return """<input type="text" name="$1" maxlength="$2" value="$3" $4/>""" % [
|
||||
name, $maxlength, x, if size != -1: "size=\"" & $size & "\"" else: ""]
|
||||
|
||||
proc TextAreaWidget(c: TForumData, name, defaultText: string,
|
||||
width = 80, height = 20): string =
|
||||
let x = if defaultText != reuseText: defaultText
|
||||
else: XMLencode(c.cgiData[name])
|
||||
else: XMLencode(c.req.params[name])
|
||||
return """<textarea name="$1" cols="$2" rows="$3">$4</textarea>""" % [
|
||||
name, $width, $height, x]
|
||||
|
||||
|
|
@ -111,71 +84,44 @@ proc FieldValid(c: TForumData, name, text: string): string =
|
|||
else:
|
||||
result = text
|
||||
|
||||
proc genQuery(c: var TForumData, action: TForumAction, target: string): string =
|
||||
result = websiteLoc
|
||||
case action
|
||||
of actionShow:
|
||||
if target != "":
|
||||
result.add("t/" & target)
|
||||
of actionReplyForm:
|
||||
result.add("t/" & target & "?action=reply")
|
||||
of actionEditForm:
|
||||
result.add("t/" & $c.threadid & "?action=edit&postid=" & target)
|
||||
else:
|
||||
result.add($action & "/" & target)
|
||||
proc genThreadUrl(c: TForumData, postId = "", action = "", threadid = ""): string =
|
||||
result = "/t/" & (if threadid == "": $c.threadId else: threadid)
|
||||
if action != "":
|
||||
result.add("?action=" & action)
|
||||
if postId != "":
|
||||
result.add("&postid=" & postid)
|
||||
result = c.req.makeUri(result, absolute = false)
|
||||
|
||||
proc FormSession(c: var TForumData, nextAction: TForumAction): string =
|
||||
return """<input type="hidden" name="action" value="$1" />
|
||||
<input type="hidden" name="threadid" value="$2" />
|
||||
<input type="hidden" name="postid" value="$3" />""" % [
|
||||
$nextAction, $c.threadId, $c.postid]
|
||||
proc FormSession(c: var TForumData, nextAction: string): string =
|
||||
return """<input type="hidden" name="threadid" value="1" />
|
||||
<input type="hidden" name="postid" value="$2" />""" % [
|
||||
$c.threadId, $c.postid]
|
||||
|
||||
proc UrlButton(c: var TForumData, text: string,
|
||||
nextAction: TForumAction, target=""): string =
|
||||
proc UrlButton(c: var TForumData, text, url: string): string =
|
||||
return ("""<a class="url_button" href="$1">$2</a>""") % [
|
||||
c.genQuery(nextAction, target), text]
|
||||
url, text]
|
||||
|
||||
proc genButtons(c: var TForumData, btns: seq[TStyledButton]): string =
|
||||
if btns.len == 1:
|
||||
var anchor = ""
|
||||
if btns[0].action == actionReplyForm:
|
||||
anchor = "#reply"
|
||||
|
||||
result = ("""<a class="active button" href="$1$3">$2</a>""") % [
|
||||
c.genQuery(btns[0].action, btns[0].tid), btns[0].text, anchor]
|
||||
btns[0].link, btns[0].text, anchor]
|
||||
else:
|
||||
result = ""
|
||||
for i, btn in pairs(btns):
|
||||
var anchor = ""
|
||||
if btns[i].action == actionReplyForm:
|
||||
anchor = "#reply"
|
||||
|
||||
var class = ""
|
||||
if i == 0: class = "left "
|
||||
elif i == btns.len()-1: class = "right "
|
||||
else: class = "middle "
|
||||
result.add(("""<a class="$3active button" href="$1$4">$2</a>""") % [
|
||||
c.genQuery(btns[i].action, btns[i].tid), btns[i].text, class, anchor])
|
||||
|
||||
proc genSlash(c: var TForumData): string =
|
||||
let reqPath = c.reqUrl
|
||||
if reqPath.endswith("/"):
|
||||
return ""
|
||||
else: return reqPath & "/"
|
||||
btns[i].link, btns[i].text, class, anchor])
|
||||
|
||||
proc formatTimestamp(t: int): string =
|
||||
let t2 = getGMTime(TTime(t))
|
||||
result = ""
|
||||
result.add(`$`(t2.weekday)[ .. 2] & ", ")
|
||||
result.add($t2.monthday & " ")
|
||||
result.add(`$`(t2.month)[ .. 2] & " ")
|
||||
result.add($t2.year & " ")
|
||||
if t2.hour < 10:
|
||||
result.add("0")
|
||||
result.add($t2.hour & ":")
|
||||
if t2.minute < 10:
|
||||
result.add("0")
|
||||
result.add($t2.minute)
|
||||
return t2.format("ddd',' d MMM yyyy HH':'mm 'UTC'")
|
||||
|
||||
proc genGravatar(email: string, size: int = 80): string =
|
||||
let emailMD5 = email.toLower.toMD5
|
||||
|
|
@ -226,13 +172,13 @@ proc antibot(c: var TForumData): string =
|
|||
let b = math.random(1000)+1
|
||||
let answer = $(a+b)
|
||||
|
||||
Exec(db, sql"delete from antibot where ip = ?", c.ip)
|
||||
let captureId = TryInsertID(db,
|
||||
sql"insert into antibot(ip, answer) values (?, ?)", c.ip,
|
||||
Exec(db, sql"delete from antibot where ip = ?", c.req.ip)
|
||||
let CaptchaId = TryInsertID(db,
|
||||
sql"insert into antibot(ip, answer) values (?, ?)", c.req.ip,
|
||||
answer).int mod 10_000
|
||||
let captureFile = "captchas/captcha_" & $captureId & ".png"
|
||||
createCapture(captureFile, $a & "+" & $b)
|
||||
result = """<img src="$1" />""" % captureFile
|
||||
let CaptchaFile = getCaptchaFilename(CaptchaId)
|
||||
createCaptcha(CaptchaFile, $a & "+" & $b)
|
||||
result = """<img src="$1" />""" % getCaptchaUrl(captchaId)
|
||||
|
||||
const
|
||||
SecureChars = {'A'..'Z', 'a'..'z', '0'..'9', '_', '\128'..'\255'}
|
||||
|
|
@ -242,12 +188,7 @@ proc setError(c: var TForumData, field, msg: string): bool {.inline.} =
|
|||
c.errorMsg = "Error: " & msg
|
||||
return false
|
||||
|
||||
proc register(c: var TForumData): bool =
|
||||
# get form data:
|
||||
let name = c.cgiData["name"]
|
||||
let pass = c.cgiData["new_password"]
|
||||
let antibot = c.cgiData["antibot"]
|
||||
let email = c.cgiData["email"]
|
||||
proc register(c: var TForumData, name, pass, antibot, email: string): bool =
|
||||
# Username validation:
|
||||
if name.len == 0 or not allCharsInSet(name, SecureChars):
|
||||
return setError(c, "name", "Invalid username!")
|
||||
|
|
@ -260,7 +201,7 @@ proc register(c: var TForumData): bool =
|
|||
|
||||
# antibot validation:
|
||||
let correctRes = GetValue(db,
|
||||
sql"select answer from antibot where ip = ?", c.ip)
|
||||
sql"select answer from antibot where ip = ?", c.req.ip)
|
||||
if antibot != correctRes:
|
||||
return setError(c, "antibot", "You seem to be a bot!")
|
||||
|
||||
|
|
@ -277,16 +218,16 @@ proc register(c: var TForumData): bool =
|
|||
return true
|
||||
|
||||
proc checkLoggedIn(c: var TForumData) =
|
||||
let pass = c.cookieData["sid"]
|
||||
let pass = c.req.cookies["sid"]
|
||||
if pass.len == 0: return
|
||||
if ExecAffectedRows(db,
|
||||
sql("update session set lastModified = DATETIME('now') " &
|
||||
"where ip = ? and password = ?"),
|
||||
c.ip, pass) > 0:
|
||||
c.req.ip, pass) > 0:
|
||||
c.userpass = pass
|
||||
c.userid = GetValue(db,
|
||||
sql"select userid from session where ip = ? and password = ?",
|
||||
c.ip, pass)
|
||||
c.req.ip, pass)
|
||||
|
||||
let row = getRow(db,
|
||||
sql"select name, email, admin from person where id = ?", c.userid)
|
||||
|
|
@ -294,23 +235,23 @@ proc checkLoggedIn(c: var TForumData) =
|
|||
c.email = ||row[1]
|
||||
c.isAdmin = parseBool(||row[2])
|
||||
else:
|
||||
echo("not found login")
|
||||
echo("SID not found in sessions. Assuming logged out.")
|
||||
|
||||
proc logout(c: var TForumData) =
|
||||
const query = sql"delete from session where ip = ? and password = ?"
|
||||
c.username = ""
|
||||
c.userpass = ""
|
||||
Exec(db, query, c.ip, c.cookieData["sid"])
|
||||
Exec(db, query, c.req.ip, c.req.cookies["sid"])
|
||||
|
||||
proc incrementViews(c: var TForumData) =
|
||||
const query = sql"update thread set views = views + 1 where id = ?"
|
||||
Exec(db, query, $c.threadId)
|
||||
|
||||
proc isPreview(c: TForumData): bool =
|
||||
result = c.cgiData["previewBtn"].len > 0
|
||||
result = c.req.params["previewBtn"].len > 0 # TODO: Could be wrong?
|
||||
|
||||
proc isDelete(c: TForumData): bool =
|
||||
result = c.cgiData["delete"].len > 0
|
||||
result = c.req.params["delete"].len > 0
|
||||
|
||||
proc rstToHtml(content: string): string =
|
||||
result = rstgen.rstToHtml(content, {roSupportSmilies, roSupportMarkdown},
|
||||
|
|
@ -352,11 +293,11 @@ proc crud(c: TCrud, table: string, data: openArray[string]): TSqlQuery =
|
|||
result = sql("delete from " & table & " where id = ?")
|
||||
|
||||
template retrSubject(c: expr) =
|
||||
let subject = c.cgiData["subject"]
|
||||
let subject = c.req.params["subject"]
|
||||
if subject.len < 3: return setError(c, "subject", "Subject not long enough")
|
||||
|
||||
template retrContent(c: expr) =
|
||||
let content = c.cgiData["content"]
|
||||
let content = c.req.params["content"]
|
||||
if not validateRst(c, content): return false
|
||||
|
||||
template retrPost(c: expr) =
|
||||
|
|
@ -379,7 +320,7 @@ template setPreviewData(c: expr) =
|
|||
|
||||
template writeToDb(c, cr, postId: expr) =
|
||||
exec(db, crud(cr, "post", "author", "ip", "header", "content", "thread"),
|
||||
c.userId, c.ip, subject, content, $c.threadId, postId)
|
||||
c.userId, c.req.ip, subject, content, $c.threadId, postId)
|
||||
|
||||
proc edit(c: var TForumData, postId: int): bool =
|
||||
checkLogin(c)
|
||||
|
|
@ -422,7 +363,7 @@ proc newThread(c: var TForumData): bool =
|
|||
setPreviewData(c)
|
||||
c.threadID = transientThread
|
||||
else:
|
||||
c.threadID = TryInsertID(db, query, c.cgiData["subject"]).int
|
||||
c.threadID = TryInsertID(db, query, c.req.params["subject"]).int
|
||||
if c.threadID < 0: return setError(c, "subject", "Subject already exists")
|
||||
writeToDb(c, crCreate, "")
|
||||
result = true
|
||||
|
|
@ -447,7 +388,7 @@ proc login(c: var TForumData, name, pass: string): bool =
|
|||
# create session:
|
||||
Exec(db,
|
||||
sql"insert into session (ip, password, userid) values (?, ?, ?)",
|
||||
c.ip, c.userpass, c.userid)
|
||||
c.req.ip, c.userpass, c.userid)
|
||||
return true
|
||||
else:
|
||||
return c.setError("password", "Login failed!")
|
||||
|
|
@ -456,288 +397,150 @@ proc genActionMenu(c: var TForumData): string =
|
|||
result = ""
|
||||
var btns: seq[TStyledButton] = @[]
|
||||
if c.threadId >= 0:
|
||||
btns.add(("Thread List", actionShow, ""))
|
||||
btns.add(("Thread List", c.req.makeUri("/", false)))
|
||||
if c.loggedIn:
|
||||
if c.threadId >= 0:
|
||||
btns.add(("Reply", actionReplyForm, $c.threadId))
|
||||
btns.add(("New Thread", actionNewThreadForm, ""))
|
||||
let replyUrl = c.genThreadUrl("", "reply") & "#reply"
|
||||
btns.add(("Reply", replyUrl))
|
||||
btns.add(("New Thread", c.req.makeUri("/newthread", false)))
|
||||
result = c.genButtons(btns)
|
||||
|
||||
include "forms.tmpl"
|
||||
include "main.tmpl"
|
||||
|
||||
proc contentAtPosition(c: var TForumData): string =
|
||||
if c.threadId == transientThread:
|
||||
result = c.genPostPreview(c.currentPost.subject,
|
||||
c.currentPost.content,
|
||||
c.username, $getGMTime(getTime()))
|
||||
elif validThreadId(c):
|
||||
result = genPostsList(c, $c.threadId)
|
||||
else:
|
||||
result = genThreadsList(c)
|
||||
|
||||
proc prependRe(s: string): string =
|
||||
result = if s.len == 0:
|
||||
""
|
||||
elif s.startswith("Re:"): s
|
||||
else: "Re: " & s
|
||||
|
||||
proc dispatch(c: var TForumData): tuple[status, content: string,
|
||||
headers: PStringTable] =
|
||||
template `@`(x: expr): expr = c.cgiData[x]
|
||||
|
||||
template redirect(): stmt =
|
||||
result[0] = "303 See Other"
|
||||
let q = c.genQuery(actionShow, $c.threadId)
|
||||
result[2] = {"Location": q}.newStringTable()
|
||||
|
||||
template successfulLogin() =
|
||||
redirect()
|
||||
# TODO: Security risk: I'm not sure that using the hashed password as the
|
||||
# sid is the best idea...
|
||||
var tim = TTime(int(getTime()) + 7 * (60 * 60 * 24)) # 7 days added
|
||||
result[2]["Set-Cookie"] = setCookie("sid", c.userpass,
|
||||
tim.getGMTime(), noName = true)
|
||||
|
||||
let tid = @"threadid"
|
||||
if tid.len > 0:
|
||||
parseInt(tid, c.threadId, -1..1000_000)
|
||||
let pid = @"postid"
|
||||
if pid.len > 0:
|
||||
parseInt(pid, c.postId, -1..1000_000)
|
||||
|
||||
result[0] = "200 OK"
|
||||
result[1] = ""
|
||||
result[2] = {"Content-Type": "text/html"}.newStringTable
|
||||
case c.action
|
||||
of actionShow:
|
||||
if tid.len > 0:
|
||||
incrementViews(c)
|
||||
result[1] = contentAtPosition(c)
|
||||
of actionRegisterForm:
|
||||
result[1] = genFormRegister(c)
|
||||
of actionRegister:
|
||||
if register(c):
|
||||
discard login(c, @"name", @"new_password")
|
||||
successfulLogin()
|
||||
else:
|
||||
result[1] = genFormRegister(c)
|
||||
of actionLoginForm:
|
||||
result[1] = genFormLogin(c)
|
||||
of actionLogin:
|
||||
if login(c, @"name", @"password"):
|
||||
successfulLogin()
|
||||
else:
|
||||
result[1] = genFormLogin(c)
|
||||
of actionLogout:
|
||||
logout(c)
|
||||
redirect()
|
||||
of actionReplyForm:
|
||||
let subject = GetValue(db,
|
||||
sql"select header from post where id = (select max(id) from post where thread = ?)",
|
||||
$c.threadId).prependRe
|
||||
result[1] = contentAtPosition(c)
|
||||
result[1].add genFormPost(c, actionReply, false, subject, "")
|
||||
of actionReply:
|
||||
if reply(c):
|
||||
redirect()
|
||||
else:
|
||||
result[1] = contentAtPosition(c)
|
||||
if c.isPreview:
|
||||
result[1] = genPostPreview(c, @"subject", @"content",
|
||||
c.userName, $getGMTime(getTime()))
|
||||
result[1].add genFormPost(c, actionReply, false, reuseText, reuseText)
|
||||
of actionEditForm:
|
||||
const query = sql"select header, content from post where id = ?"
|
||||
let row = getRow(db, query, $c.postId)
|
||||
let header = ||row[0]
|
||||
let content = ||row[1]
|
||||
result[1] = genFormPost(c, actionEdit, true, header, content)
|
||||
of actionEdit:
|
||||
if edit(c, c.postId):
|
||||
redirect()
|
||||
else:
|
||||
if c.isPreview:
|
||||
result[1] = genPostPreview(c, @"subject", @"content",
|
||||
c.userName, $getGMTime(getTime()))
|
||||
result[1].add genFormPost(c, actionEdit, true, reuseText, reuseText)
|
||||
of actionNewThreadForm:
|
||||
result[1] = genFormPost(c, actionNewThread, false, "", "")
|
||||
of actionNewThread:
|
||||
if newThread(c):
|
||||
redirect()
|
||||
else:
|
||||
if c.isPreview:
|
||||
result[1] = genPostPreview(c, @"subject", @"content",
|
||||
c.userName, $getGMTime(getTime()))
|
||||
result[1].add genFormPost(c, actionNewThread, false, reuseText, reuseText)
|
||||
of action404:
|
||||
result[0] = "404 Not Found"
|
||||
result[1] = ""
|
||||
result[2] = newStringTable()
|
||||
|
||||
if result[0] == "200 OK":
|
||||
result[1] = genMain(c, result[1])
|
||||
template createTFD(): stmt =
|
||||
var c: TForumData
|
||||
init(c)
|
||||
c.req = request
|
||||
c.startTime = epochTime()
|
||||
if request.cookies.len > 0:
|
||||
checkLoggedIn(c)
|
||||
|
||||
proc normalizeDocURI(url: string): string =
|
||||
## Adds a leading / if one doesn't exist.
|
||||
result = if url[url.len-1] != '/': url & '/' else: url
|
||||
get "/":
|
||||
createTFD()
|
||||
resp genMain(c, genThreadsList(c))
|
||||
|
||||
proc getAction(cgiData: PStringTable): TForumAction =
|
||||
var a = cgiData["action"]
|
||||
get "/t/@threadid/?":
|
||||
createTFD()
|
||||
parseInt(@"threadid", c.threadId, -1..1000_000)
|
||||
if (@"postid").len > 0:
|
||||
parseInt(@"postid", c.postId, -1..1000_000)
|
||||
|
||||
var path = ""
|
||||
let url = cgiData["DOCUMENT_URI"].normalizeDocURI
|
||||
if url.startswith(websiteLoc):
|
||||
path = url.substr(websiteLoc.len)
|
||||
if path.len != 0:
|
||||
if path[path.len-1] == '/': path = path.substr(0, path.len()-2)
|
||||
echo "PATH: ", path
|
||||
else: return action404
|
||||
|
||||
if path == "":
|
||||
if a != "":
|
||||
result = parseEnum[TForumAction](a, action404)
|
||||
else:
|
||||
result = actionShow
|
||||
if (@"action").len > 0:
|
||||
case @"action"
|
||||
of "reply":
|
||||
let subject = GetValue(db,
|
||||
sql"select header from post where id = (select max(id) from post where thread = ?)",
|
||||
$c.threadId).prependRe
|
||||
body = genPostsList(c, $c.threadId)
|
||||
if c.isPreview:
|
||||
body = genPostPreview(c, @"subject", @"content",
|
||||
c.userName, $getGMTime(getTime()))
|
||||
body.add genFormPost(c, "doreply", false, subject, "")
|
||||
of "edit":
|
||||
cond c.postId != -1
|
||||
const query = sql"select header, content from post where id = ?"
|
||||
let row = getRow(db, query, $c.postId)
|
||||
let header = ||row[0]
|
||||
let content = ||row[1]
|
||||
body = genFormPost(c, "doedit", true, header, content)
|
||||
resp c.genMain(body)
|
||||
else:
|
||||
var slashes = path.split('/')
|
||||
case slashes[0]
|
||||
of "t", "thread":
|
||||
if slashes.len() > 1:
|
||||
cgiData["threadid"] = slashes[1]
|
||||
case a
|
||||
of "reply": result = actionReplyForm
|
||||
of "edit": result = actionEditForm
|
||||
else: result = actionShow
|
||||
else:
|
||||
result = action404
|
||||
else:
|
||||
result = parseEnum[TForumAction](path, action404)
|
||||
echo("Parsed ", path, " as ", result)
|
||||
cond validThreadId(c)
|
||||
incrementViews(c)
|
||||
resp genMain(c, genPostsList(c, $c.threadId))
|
||||
|
||||
proc processRequest(r: TRequest): tuple[status, content: string,
|
||||
headers: PStringTable] =
|
||||
try:
|
||||
var c: TForumData
|
||||
init(c)
|
||||
c.ip = r.ip
|
||||
c.reqUrl = r.url
|
||||
c.startTime = r.startTime
|
||||
|
||||
assert c.ip.len > 0
|
||||
c.cgiData = r.vars
|
||||
c.cookieData = parseCookies(c.cgiData["HTTP_COOKIE"])
|
||||
echo(c.cookieData)
|
||||
if c.cookieData.len > 0:
|
||||
checkLoggedIn(c)
|
||||
if c.cgiData.len > 0:
|
||||
c.action = getAction(c.cgiData)
|
||||
get "/login/?":
|
||||
createTFD()
|
||||
resp genMain(c, genFormLogin(c))
|
||||
|
||||
echo c.cgiData
|
||||
echo(c.action)
|
||||
result = dispatch(c)
|
||||
except:
|
||||
result[0] = "500 Internal Server Error"
|
||||
result[1] = "Internal Error: " & getCurrentExceptionMsg()
|
||||
result[2] = newStringTable()
|
||||
get "/logout/?":
|
||||
createTFD()
|
||||
logout(c)
|
||||
redirect(uri("/"))
|
||||
|
||||
proc extractDirFile(s: string): tuple[dir, file: string] =
|
||||
var last = s.len-1
|
||||
while last > 0 and s[last] == '/': dec last
|
||||
var splitPoint = last-1
|
||||
while splitPoint >= 0 and s[splitPoint] != '/': dec splitPoint
|
||||
|
||||
result.file = s.substr(splitPoint+1, last)
|
||||
# skip '/'
|
||||
var splitPoint2 = splitPoint-1
|
||||
while splitPoint2 >= 0 and s[splitPoint2] != '/': dec splitPoint2
|
||||
result.dir = s.substr(splitPoint2+1, splitPoint-1)
|
||||
get "/register/?":
|
||||
createTFD()
|
||||
resp genMain(c, genFormRegister(c))
|
||||
|
||||
proc processFile(r: TRequest): tuple[isFile: bool,
|
||||
contentType, content: string] =
|
||||
try:
|
||||
let url = r.vars["DOCUMENT_URI"].normalizeDocURI
|
||||
let (dir, file) = extractDirFile(url)
|
||||
case dir
|
||||
of "css":
|
||||
result = (true, "text/css", readFile("style/" & file))
|
||||
of "captchas":
|
||||
result = (true, "image/png", readFile("captchas/" & file))
|
||||
of "smilies":
|
||||
result = (true, "image/gif", readFile("images/smilies/" & file))
|
||||
else:
|
||||
result = (false, "", "")
|
||||
except:
|
||||
result = (false, "", "")
|
||||
template readIDs(): stmt =
|
||||
# Retrieve the threadid and postid
|
||||
if (@"threadid").len > 0:
|
||||
parseInt(@"threadid", c.threadId, -1..1000_000)
|
||||
if (@"postid").len > 0:
|
||||
parseInt(@"postid", c.postId, -1..1000_000)
|
||||
|
||||
# ------------------ main file ----------------------------------------------
|
||||
template finishLogin(): stmt =
|
||||
setCookie("sid", c.userpass, daysForward(7))
|
||||
redirect(uri("/"))
|
||||
|
||||
var s: TScgiState
|
||||
template handleError(action: string, isEdit: bool): stmt =
|
||||
if c.isPreview:
|
||||
body.add genPostPreview(c, @"subject", @"content",
|
||||
c.userName, $getGMTime(getTime()))
|
||||
body.add genFormPost(c, action, isEdit, reuseText, reuseText)
|
||||
resp genMain(c, body)
|
||||
|
||||
proc shutdown() {.noconv.} =
|
||||
s.close()
|
||||
writeStackTrace()
|
||||
quit 1
|
||||
|
||||
system.setControlCHook(shutdown)
|
||||
post "/dologin":
|
||||
createTFD()
|
||||
if login(c, @"name", @"password"):
|
||||
finishLogin()
|
||||
else:
|
||||
resp c.genMain(genFormLogin(c))
|
||||
|
||||
when not defined(writeStatusContent):
|
||||
proc writeStatusContent(c: TSocket, status, content: string,
|
||||
headers: PStringTable) =
|
||||
var strHeaders = ""
|
||||
for key, value in headers:
|
||||
strHeaders.add(key & ": " & value & "\r\L")
|
||||
c.send("Status: " & status & "\r\L" & strHeaders & "\r\L")
|
||||
c.send(content)
|
||||
post "/doregister":
|
||||
createTFD()
|
||||
if c.register(@"name", @"new_password", @"antibot", @"email"):
|
||||
discard c.login(@"name", @"new_password")
|
||||
finishLogin()
|
||||
else:
|
||||
resp c.genMain(genFormRegister(c))
|
||||
|
||||
proc mainLoop() =
|
||||
try:
|
||||
db = Open(connection="nimforum.db", user="postgres", password="",
|
||||
database="nimforum")
|
||||
open(s, 9000.TPort)
|
||||
while next(s):
|
||||
var r: TRequest
|
||||
r.vars = s.headers
|
||||
r.ip = r.vars["REMOTE_ADDR"]
|
||||
r.url = r.vars["DOCUMENT_URI"]
|
||||
r.startTime = epochTime()
|
||||
|
||||
# the server software (nginx) seems to f*ck up SCGI + POST/GET, so I work
|
||||
# around this issue here:
|
||||
try:
|
||||
for key, val in cgi.decodeData(r.vars["QUERY_STRING"]):
|
||||
r.vars[key] = val
|
||||
except ECgi:
|
||||
nil
|
||||
try:
|
||||
for key, val in cgi.decodeData(s.input):
|
||||
r.vars[key] = val
|
||||
except ECgi:
|
||||
nil
|
||||
let fi = processFile(r)
|
||||
|
||||
if fi.isFile:
|
||||
writeStatusOkTextContent(s.client, fi.contentType)
|
||||
send(s.client, fi.content)
|
||||
else:
|
||||
let (status, resp, headers) = processRequest(r)
|
||||
s.client.writeStatusContent(status, resp, headers)
|
||||
s.client.close()
|
||||
finally:
|
||||
close(s)
|
||||
close(db)
|
||||
post "/donewthread":
|
||||
createTFD()
|
||||
if newThread(c):
|
||||
redirect(uri("/"))
|
||||
else:
|
||||
body = ""
|
||||
handleError("donewthread", false)
|
||||
|
||||
proc main() =
|
||||
post "/doreply":
|
||||
createTFD()
|
||||
readIDs()
|
||||
if reply(c):
|
||||
redirect(c.genThreadUrl())
|
||||
else:
|
||||
body = genPostsList(c, $c.threadId)
|
||||
handleError("doreply", false)
|
||||
|
||||
post "/doedit":
|
||||
createTFD()
|
||||
readIDs()
|
||||
if edit(c, c.postId):
|
||||
redirect(c.genThreadUrl())
|
||||
else:
|
||||
body = ""
|
||||
handleError("doedit", true)
|
||||
|
||||
get "/newthread/?":
|
||||
createTFD()
|
||||
resp genMain(c, genFormPost(c, "donewthread", false, "", ""))
|
||||
|
||||
when isMainModule:
|
||||
docConfig = rstgen.defaultConfig()
|
||||
math.randomize()
|
||||
|
||||
for i in 0..10:
|
||||
try:
|
||||
mainLoop()
|
||||
except:
|
||||
echo "FATAL: ", getCurrentExceptionMsg()
|
||||
os.sleep(1000)
|
||||
|
||||
main()
|
||||
db = Open(connection="nimforum.db", user="postgres", password="",
|
||||
database="nimforum")
|
||||
var http = true
|
||||
if paramCount() > 0:
|
||||
if paramStr(1) == "scgi":
|
||||
http = false
|
||||
run(websiteLoc, port = TPort(9001), http = http)
|
||||
db.close()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<title>Nimrod Forum</title>
|
||||
<link rel="stylesheet" href="${c.genSlash}css/normalize.css">
|
||||
<link rel="stylesheet" href="${c.genSlash}css/style.css">
|
||||
<link rel="stylesheet" href="/css/normalize.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
</div>
|
||||
|
||||
<div id="header">
|
||||
#let frontQuery = c.genQuery(actionShow, "")
|
||||
#let frontQuery = c.req.makeUri("/")
|
||||
<span><a href="${frontQuery}">Nimrod's Forum</a></span>
|
||||
#if c.loggedIn:
|
||||
<a href="${websiteLoc}logout" class="right">Logout</a>
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@
|
|||
--path:"$nimrod/packages/docutils"
|
||||
|
||||
--path:"$nimrod"
|
||||
--path:"/home/dominik/code/nimrod/jester"
|
||||
2
public/captchas/.gitignore
vendored
Normal file
2
public/captchas/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
Loading…
Add table
Add a link
Reference in a new issue