Merge pull request #91 from nim-lang/first-post-needs-moderation
First post needs moderation
This commit is contained in:
commit
27a1305b2d
8 changed files with 249 additions and 249 deletions
|
|
@ -16,13 +16,13 @@ proc newCacheHolder*(): CacheHolder =
|
||||||
result.caches = initTable[string, CacheInfo]()
|
result.caches = initTable[string, CacheInfo]()
|
||||||
|
|
||||||
proc invalidate*(cache: CacheHolder, name: string) =
|
proc invalidate*(cache: CacheHolder, name: string) =
|
||||||
cache.caches.mget(name.normalizePath()).valid = false
|
cache.caches[name.normalizePath()].valid = false
|
||||||
|
|
||||||
proc invalidateAll*(cache: CacheHolder) =
|
proc invalidateAll*(cache: CacheHolder) =
|
||||||
for key, val in mpairs(cache.caches):
|
for key, val in mpairs(cache.caches):
|
||||||
val.valid = false
|
val.valid = false
|
||||||
|
|
||||||
template get*(cache: CacheHolder, name: string, grabValue: expr): expr =
|
template get*(cache: CacheHolder, name: string, grabValue: untyped): untyped =
|
||||||
## Check to see if the cache contains value for ``name``. If it does and the
|
## Check to see if the cache contains value for ``name``. If it does and the
|
||||||
## cache is valid then doesn't recalculate it but returns the cached version.
|
## cache is valid then doesn't recalculate it but returns the cached version.
|
||||||
mixin normalizePath
|
mixin normalizePath
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import cairo, os, strutils, jester
|
||||||
proc getCaptchaFilename*(i: int): string {.inline.} =
|
proc getCaptchaFilename*(i: int): string {.inline.} =
|
||||||
result = "public/captchas/capture_" & $i & ".png"
|
result = "public/captchas/capture_" & $i & ".png"
|
||||||
|
|
||||||
proc getCaptchaUrl*(req: PRequest, i: int): string =
|
proc getCaptchaUrl*(req: Request, i: int): string =
|
||||||
result = req.makeUri("/captchas/capture_" & $i & ".png", absolute = false)
|
result = req.makeUri("/captchas/capture_" & $i & ".png", absolute = false)
|
||||||
|
|
||||||
proc createCaptcha*(file, text: string) =
|
proc createCaptcha*(file, text: string) =
|
||||||
|
|
@ -23,17 +23,15 @@ proc createCaptcha*(file, text: string) =
|
||||||
|
|
||||||
setSourceRgb(cr, 1.0, 0.5, 0.0)
|
setSourceRgb(cr, 1.0, 0.5, 0.0)
|
||||||
moveTo(cr, 0.0, 10.0)
|
moveTo(cr, 0.0, 10.0)
|
||||||
showText(cr, repeatChar(text.len, 'O'))
|
showText(cr, repeat('O', text.len))
|
||||||
|
|
||||||
setSourceRgb(cr, 0.0, 0.0, 1.0)
|
setSourceRgb(cr, 0.0, 0.0, 1.0)
|
||||||
moveTo(cr, 0.0, 10.0)
|
moveTo(cr, 0.0, 10.0)
|
||||||
showText(cr, text)
|
showText(cr, text)
|
||||||
|
|
||||||
destroy(cr)
|
destroy(cr)
|
||||||
discard writeToPng(surface, file)
|
discard writeToPng(surface, file)
|
||||||
destroy(surface)
|
destroy(surface)
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
createCaptcha("test.png", "1+33")
|
createCaptcha("test.png", "1+33")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,7 @@ create table if not exists person(
|
||||||
email $# not null,
|
email $# not null,
|
||||||
creation timestamp not null default (DATETIME('now')),
|
creation timestamp not null default (DATETIME('now')),
|
||||||
salt varbin(128) not null,
|
salt varbin(128) not null,
|
||||||
status integer not null,
|
status varchar(30) not null,
|
||||||
admin bool default false,
|
|
||||||
lastOnline timestamp not null default (DATETIME('now'))
|
lastOnline timestamp not null default (DATETIME('now'))
|
||||||
);""" % [TUserName, TPassword, TEmail]), [])
|
);""" % [TUserName, TPassword, TEmail]), [])
|
||||||
# echo "person table already exists"
|
# echo "person table already exists"
|
||||||
|
|
|
||||||
14
editdb.nim
14
editdb.nim
|
|
@ -1,11 +1,13 @@
|
||||||
|
|
||||||
import strutils, db_sqlite
|
import strutils, db_sqlite, ranks
|
||||||
|
|
||||||
var db = Open(connection="nimforum.db", user="postgres", password="",
|
var db = open(connection="nimforum.db", user="postgres", password="",
|
||||||
database="nimforum")
|
database="nimforum")
|
||||||
|
|
||||||
db.exec(sql"""ALTER TABLE person add column
|
db.exec(sql("update person set status = ?"), $User)
|
||||||
lastOnline timestamp
|
db.exec(sql("update person set status = ? where ban <> ''"), $Troll)
|
||||||
""", [])
|
db.exec(sql("update person set status = ? where ban like '%spam%'"), $Spammer)
|
||||||
|
db.exec(sql("update person set status = ? where ban = 'DEACTIVATED' or ban = 'EMAILCONFIRMATION'"), $EmailUnconfirmed)
|
||||||
|
db.exec(sql("update person set status = ? where admin = 'true'"), $Admin)
|
||||||
|
|
||||||
close(db)
|
close(db)
|
||||||
|
|
|
||||||
85
forms.tmpl
85
forms.tmpl
|
|
@ -1,12 +1,19 @@
|
||||||
#? stdtmpl | standard
|
#? stdtmpl | standard
|
||||||
#
|
#
|
||||||
#template `%`(idx: expr): expr {.immediate.} =
|
#template `%`(idx: untyped): untyped =
|
||||||
# row[idx]
|
# row[idx]
|
||||||
#end template
|
#end template
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
#proc genThreadsList(c: var TForumData, count: var int): string =
|
#proc genThreadsList(c: var TForumData, count: var int): string =
|
||||||
# const query = sql"select id, name, views, modified from thread order by modified desc limit ?, ?"
|
# const queryModAdmin = sql"""select id, name, views, modified from thread
|
||||||
|
# where id in (select thread from post where author in
|
||||||
|
# (select id from person where status not in ('Spammer') or id = ?))
|
||||||
|
# order by modified desc limit ?, ?"""
|
||||||
|
# const query = sql"""select id, name, views, modified from thread
|
||||||
|
# where id in (select thread from post where author in
|
||||||
|
# (select id from person where status not in ('Moderated', 'Spammer') or id = ?))
|
||||||
|
# order by modified desc limit ?, ?"""
|
||||||
# const threadId = 0
|
# const threadId = 0
|
||||||
# const name = 1
|
# const name = 1
|
||||||
# const views = 2
|
# const views = 2
|
||||||
|
|
@ -34,7 +41,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="talk-threads">
|
<div id="talk-threads">
|
||||||
# for row in rows(db, query, $((c.pageNum-1) * ThreadsPerPage), $ThreadsPerPage):
|
# for row in rows(db, if c.rank >= Moderator: queryModAdmin else: query,
|
||||||
|
# c.userId, $((c.pageNum-1) * ThreadsPerPage), $ThreadsPerPage):
|
||||||
# inc(count)
|
# inc(count)
|
||||||
<div>
|
<div>
|
||||||
<div class="topic">
|
<div class="topic">
|
||||||
|
|
@ -112,8 +120,12 @@
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
#proc genPostsList(c: var TForumData, threadId: string, count: var int): string =
|
#proc genPostsList(c: var TForumData, threadId: string, count: var int): string =
|
||||||
# const query = sql"""select p.id, u.name, p.header, p.content, p.creation, p.author, u.email from post p,
|
# let query = sql("""select p.id, u.name, p.header, p.content, p.creation, p.author, u.email from post p,
|
||||||
# person u where u.id = p.author and p.thread = ? order by p.id limit ?, ?"""
|
# person u
|
||||||
|
# where u.id = p.author and p.thread = ? and $#
|
||||||
|
# and (u.status <> 'Spammer' or p.author = ?)
|
||||||
|
# order by p.id limit ?, ?""" %
|
||||||
|
# (if c.rank >= Moderator: "(1 or u.id = ?)" else: "(u.status <> 'Moderated' or p.author = ?)"))
|
||||||
# const postId = 0
|
# const postId = 0
|
||||||
# const userName = 1
|
# const userName = 1
|
||||||
# const postHeader = 2
|
# const postHeader = 2
|
||||||
|
|
@ -123,7 +135,7 @@
|
||||||
# const userEmail = 6
|
# const userEmail = 6
|
||||||
# result = ""
|
# result = ""
|
||||||
# count = 0
|
# count = 0
|
||||||
# let posts = getAllRows(db, query, threadId, $((c.pageNum-1) * PostsPerPage), $PostsPerPage)
|
# let posts = getAllRows(db, query, threadId, c.userId, c.userId, $((c.pageNum-1) * PostsPerPage), $PostsPerPage)
|
||||||
# if posts.len < 1: return ""
|
# if posts.len < 1: return ""
|
||||||
# end if
|
# end if
|
||||||
<div id="talk-head">
|
<div id="talk-head">
|
||||||
|
|
@ -146,7 +158,7 @@
|
||||||
<a class="name" href="$profileUrl">${xmlEncode(%userName)}</a>
|
<a class="name" href="$profileUrl">${xmlEncode(%userName)}</a>
|
||||||
#if c.userId == %postAuthor and c.currentPost.subject.len == 0:
|
#if c.userId == %postAuthor and c.currentPost.subject.len == 0:
|
||||||
<hr/><a href="${c.genThreadUrl(%postId, "edit")}">Edit post</a>
|
<hr/><a href="${c.genThreadUrl(%postId, "edit")}">Edit post</a>
|
||||||
#elif c.isAdmin and c.currentPost.subject.len == 0:
|
#elif c.rank >= Moderator and c.currentPost.subject.len == 0:
|
||||||
<hr/><a style="color: red;" href="${c.genThreadUrl(%postId, "edit")}">Edit post</a>
|
<hr/><a style="color: red;" href="${c.genThreadUrl(%postId, "edit")}">Edit post</a>
|
||||||
#end if
|
#end if
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -184,15 +196,15 @@
|
||||||
</div>
|
</div>
|
||||||
<form action="${c.req.makeUri(action, false) & "#preview"}" method="POST">
|
<form action="${c.req.makeUri(action, false) & "#preview"}" method="POST">
|
||||||
#if action == "doreply":
|
#if action == "doreply":
|
||||||
${HiddenField(c, "subject", title)}
|
${hiddenField(c, "subject", title)}
|
||||||
#else:
|
#else:
|
||||||
${FieldValid(c, "subject", "Subject:")}
|
${fieldValid(c, "subject", "Subject:")}
|
||||||
${TextWidget(c, "subject", title, maxlength=100)}
|
${textWidget(c, "subject", title, maxlength=100)}
|
||||||
<br />
|
<br />
|
||||||
#end if
|
#end if
|
||||||
${FieldValid(c, "content", "Content:")}<br />
|
${fieldValid(c, "content", "Content:")}<br />
|
||||||
${TextAreaWidget(c, "content", content)}<br />
|
${textAreaWidget(c, "content", content)}<br />
|
||||||
${FormSession(c, action)}
|
${formSession(c, action)}
|
||||||
|
|
||||||
# if isEdit:
|
# if isEdit:
|
||||||
<input type="checkbox" name="delete" value="Delete">Delete Post<br />
|
<input type="checkbox" name="delete" value="Delete">Delete Post<br />
|
||||||
|
|
@ -226,20 +238,20 @@
|
||||||
<form action="${c.req.makeUri("/doregister", false)}" method="POST">
|
<form action="${c.req.makeUri("/doregister", false)}" method="POST">
|
||||||
<table border="0">
|
<table border="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td>${FieldValid(c, "name", "Username:")}</td>
|
<td>${fieldValid(c, "name", "Username:")}</td>
|
||||||
<td>${TextWidget(c, "name", reuseText, maxlength=20)}</td>
|
<td>${textWidget(c, "name", reuseText, maxlength=20)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>${FieldValid(c, "new_password", "Password:")}</td>
|
<td>${fieldValid(c, "new_password", "Password:")}</td>
|
||||||
<td><input type="password" name="new_password" /></td>
|
<td><input type="password" name="new_password" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>${FieldValid(c, "email", "E-Mail:")}</td>
|
<td>${fieldValid(c, "email", "E-Mail:")}</td>
|
||||||
<td>${TextWidget(c, "email", reuseText, maxlength=300)}</td>
|
<td>${textWidget(c, "email", reuseText, maxlength=300)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>${FieldValid(c, "antibot", "What is " & antibot(c) & "?")}</td>
|
<td>${fieldValid(c, "antibot", "What is " & antibot(c) & "?")}</td>
|
||||||
<td>${TextWidget(c, "antibot", "", maxlength=4)}</td>
|
<td>${textWidget(c, "antibot", "", maxlength=4)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
#if c.errorMsg != "":
|
#if c.errorMsg != "":
|
||||||
|
|
@ -251,6 +263,29 @@
|
||||||
</form>
|
</form>
|
||||||
#end proc
|
#end proc
|
||||||
#
|
#
|
||||||
|
#proc genFormSetRank(c: var TForumData; ui: TUserInfo): string =
|
||||||
|
# result = ""
|
||||||
|
<form action="${c.req.makeUri("/dosetrank/" & ui.nick, false)}" method="POST">
|
||||||
|
<table border="0">
|
||||||
|
<tr>
|
||||||
|
<th>Reason</th>
|
||||||
|
<td>${textWidget(c, "reason", ui.ban, maxlength=100)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Rank</th>
|
||||||
|
<td><select name = "rank">
|
||||||
|
# for i in low(Rank)..high(Rank):
|
||||||
|
<option ${if i == ui.rank: "selected" else: ""}>
|
||||||
|
$i
|
||||||
|
</option>
|
||||||
|
# end for
|
||||||
|
</td></select>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<input type="submit" value="Change">
|
||||||
|
</form>
|
||||||
|
#end proc
|
||||||
|
#
|
||||||
#proc genFormLogin(c: var TForumData): string =
|
#proc genFormLogin(c: var TForumData): string =
|
||||||
# result = ""
|
# result = ""
|
||||||
# if not c.loggedIn:
|
# if not c.loggedIn:
|
||||||
|
|
@ -288,7 +323,7 @@
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
#proc genSearchResults(c: var TForumData,
|
#proc genSearchResults(c: var TForumData,
|
||||||
# results: iterator: db_sqlite.TRow {.closure, tags: [FReadDB].},
|
# results: iterator: db_sqlite.Row {.closure, tags: [ReadDbEffect].},
|
||||||
# count: var int): string =
|
# count: var int): string =
|
||||||
# const threadId = 0
|
# const threadId = 0
|
||||||
# const threadName = 1
|
# const threadName = 1
|
||||||
|
|
@ -326,7 +361,7 @@
|
||||||
<div style="padding: 8px 0"><a href="$profileUrl">${xmlEncode(%userName)}</a></div>
|
<div style="padding: 8px 0"><a href="$profileUrl">${xmlEncode(%userName)}</a></div>
|
||||||
#if c.userId == %postAuthor and c.currentPost.subject.len == 0:
|
#if c.userId == %postAuthor and c.currentPost.subject.len == 0:
|
||||||
<hr/><a href="${c.genThreadUrl(%postId, "edit", %threadId)}">Edit post</a>
|
<hr/><a href="${c.genThreadUrl(%postId, "edit", %threadId)}">Edit post</a>
|
||||||
#elif c.isAdmin and c.currentPost.subject.len == 0:
|
#elif c.rank >= Moderator and c.currentPost.subject.len == 0:
|
||||||
<hr/><a style="color: red;" href="${c.genThreadUrl(%postId, "edit", %threadId)}">Edit post</a>
|
<hr/><a style="color: red;" href="${c.genThreadUrl(%postId, "edit", %threadId)}">Edit post</a>
|
||||||
#end if
|
#end if
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -383,12 +418,12 @@
|
||||||
<form action="${c.req.makeUri("/doresetpassword", false)}" method="POST">
|
<form action="${c.req.makeUri("/doresetpassword", false)}" method="POST">
|
||||||
<table border="0">
|
<table border="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td>${FieldValid(c, "nick", "Your nickname:")}</td>
|
<td>${fieldValid(c, "nick", "Your nickname:")}</td>
|
||||||
<td><input type="text" name="nick" maxlength="20" /></td>
|
<td><input type="text" name="nick" maxlength="20" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>${FieldValid(c, "antibot", "What is " & antibot(c) & "?")}</td>
|
<td>${fieldValid(c, "antibot", "What is " & antibot(c) & "?")}</td>
|
||||||
<td>${TextWidget(c, "antibot", "", maxlength=4)}</td>
|
<td>${textWidget(c, "antibot", "", maxlength=4)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
#if c.errorMsg != "":
|
#if c.errorMsg != "":
|
||||||
|
|
|
||||||
358
forum.nim
358
forum.nim
|
|
@ -7,9 +7,9 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import
|
import
|
||||||
os, strutils, times, md5, strtabs, cgi, math, db_sqlite, matchers,
|
os, strutils, times, md5, strtabs, cgi, math, db_sqlite,
|
||||||
captchas, scgi, jester, asyncdispatch, asyncnet, cache, sequtils,
|
captchas, scgi, jester, asyncdispatch, asyncnet, cache, sequtils,
|
||||||
parseutils, utils, random, rst
|
parseutils, utils, random, rst, ranks
|
||||||
|
|
||||||
when not defined(windows):
|
when not defined(windows):
|
||||||
import bcrypt # TODO
|
import bcrypt # TODO
|
||||||
|
|
@ -25,8 +25,6 @@ const
|
||||||
MaxPagesFromCurrent = 8
|
MaxPagesFromCurrent = 8
|
||||||
noPageNums = ["/login", "/register", "/dologin", "/doregister", "/profile"]
|
noPageNums = ["/login", "/register", "/dologin", "/doregister", "/profile"]
|
||||||
noHomeBtn = ["/", "/login", "/register", "/dologin", "/doregister", "/profile"]
|
noHomeBtn = ["/", "/login", "/register", "/dologin", "/doregister", "/profile"]
|
||||||
banReasonDeactivated = "DEACTIVATED"
|
|
||||||
banReasonEmailUnconfirmed = "EMAILCONFIRMATION"
|
|
||||||
|
|
||||||
type
|
type
|
||||||
TCrud = enum crCreate, crRead, crUpdate, crDelete
|
TCrud = enum crCreate, crRead, crUpdate, crDelete
|
||||||
|
|
@ -35,12 +33,12 @@ type
|
||||||
threadid: int
|
threadid: int
|
||||||
postid: int
|
postid: int
|
||||||
userName, userPass, email: string
|
userName, userPass, email: string
|
||||||
isAdmin: bool
|
rank: Rank
|
||||||
|
|
||||||
TPost = tuple[subject, content: string]
|
TPost = tuple[subject, content: string]
|
||||||
|
|
||||||
TForumData = object of TSession
|
TForumData = object of TSession
|
||||||
req: PRequest
|
req: Request
|
||||||
userid: string
|
userid: string
|
||||||
actionContent: string
|
actionContent: string
|
||||||
errorMsg, loginErrorMsg: string
|
errorMsg, loginErrorMsg: string
|
||||||
|
|
@ -60,8 +58,8 @@ type
|
||||||
totalUsers: int
|
totalUsers: int
|
||||||
totalPosts: int
|
totalPosts: int
|
||||||
totalThreads: int
|
totalThreads: int
|
||||||
newestMember: tuple[nick: string, id: int, isAdmin: bool]
|
newestMember: tuple[nick: string, id: int]
|
||||||
activeUsers: seq[tuple[nick: string, id: int, isAdmin: bool]]
|
activeUsers: seq[tuple[nick: string, id: int]]
|
||||||
|
|
||||||
TUserInfo = object
|
TUserInfo = object
|
||||||
nick: string
|
nick: string
|
||||||
|
|
@ -70,11 +68,12 @@ type
|
||||||
lastOnline: int
|
lastOnline: int
|
||||||
email: string
|
email: string
|
||||||
ban: string
|
ban: string
|
||||||
|
rank: Rank
|
||||||
|
|
||||||
ForumError = object of Exception
|
ForumError = object of Exception
|
||||||
|
|
||||||
var
|
var
|
||||||
db: TDbConn
|
db: DbConn
|
||||||
isFTSAvailable: bool
|
isFTSAvailable: bool
|
||||||
config: Config
|
config: Config
|
||||||
|
|
||||||
|
|
@ -103,27 +102,27 @@ proc loggedIn(c: TForumData): bool =
|
||||||
const
|
const
|
||||||
reuseText = "\1"
|
reuseText = "\1"
|
||||||
|
|
||||||
proc TextWidget(c: TForumData, name, defaultText: string,
|
proc textWidget(c: TForumData, name, defaultText: string,
|
||||||
maxlength = 30, size = -1): string =
|
maxlength = 30, size = -1): string =
|
||||||
let x = if defaultText != reuseText: defaultText
|
let x = if defaultText != reuseText: defaultText
|
||||||
else: xmlEncode(c.req.params.getOrDefault(name))
|
else: xmlEncode(c.req.params.getOrDefault(name))
|
||||||
return """<input type="text" name="$1" maxlength="$2" value="$3" $4/>""" % [
|
return """<input type="text" name="$1" maxlength="$2" value="$3" $4/>""" % [
|
||||||
name, $maxlength, x, if size != -1: "size=\"" & $size & "\"" else: ""]
|
name, $maxlength, x, if size != -1: "size=\"" & $size & "\"" else: ""]
|
||||||
|
|
||||||
proc HiddenField(c: TForumData, name, defaultText: string): string =
|
proc hiddenField(c: TForumData, name, defaultText: string): string =
|
||||||
let x = xmlencode(
|
let x = xmlencode(
|
||||||
if defaultText != reuseText: defaultText
|
if defaultText != reuseText: defaultText
|
||||||
else: c.req.params.getOrDefault(name)
|
else: c.req.params.getOrDefault(name)
|
||||||
)
|
)
|
||||||
return """<input type="hidden" name="$1" value="$2"/>""" % [name, x]
|
return """<input type="hidden" name="$1" value="$2"/>""" % [name, x]
|
||||||
|
|
||||||
proc TextAreaWidget(c: TForumData, name, defaultText: string): string =
|
proc textAreaWidget(c: TForumData, name, defaultText: string): string =
|
||||||
let x = if defaultText != reuseText: defaultText
|
let x = if defaultText != reuseText: defaultText
|
||||||
else: xmlEncode(c.req.params.getOrDefault(name))
|
else: xmlEncode(c.req.params.getOrDefault(name))
|
||||||
return """<textarea name="$1">$2</textarea>""" % [
|
return """<textarea name="$1">$2</textarea>""" % [
|
||||||
name, x]
|
name, x]
|
||||||
|
|
||||||
proc FieldValid(c: TForumData, name, text: string): string =
|
proc fieldValid(c: TForumData, name, text: string): string =
|
||||||
if name == c.invalidField:
|
if name == c.invalidField:
|
||||||
result = """<span style="color:red">$1</span>""" % text
|
result = """<span style="color:red">$1</span>""" % text
|
||||||
else:
|
else:
|
||||||
|
|
@ -141,12 +140,12 @@ proc genThreadUrl(c: TForumData, postId = "", action = "", threadid = "", pageNu
|
||||||
result.add("#" & postId)
|
result.add("#" & postId)
|
||||||
result = c.req.makeUri(result, absolute = false)
|
result = c.req.makeUri(result, absolute = false)
|
||||||
|
|
||||||
proc FormSession(c: var TForumData, nextAction: string): string =
|
proc formSession(c: var TForumData, nextAction: string): string =
|
||||||
return """<input type="hidden" name="threadid" value="$1" />
|
return """<input type="hidden" name="threadid" value="$1" />
|
||||||
<input type="hidden" name="postid" value="$2" />""" % [
|
<input type="hidden" name="postid" value="$2" />""" % [
|
||||||
$c.threadId, $c.postid]
|
$c.threadId, $c.postid]
|
||||||
|
|
||||||
proc UrlButton(c: var TForumData, text, url: string): string =
|
proc urlButton(c: var TForumData, text, url: string): string =
|
||||||
return ("""<a class="url_button" href="$1">$2</a>""") % [
|
return ("""<a class="url_button" href="$1">$2</a>""") % [
|
||||||
url, text]
|
url, text]
|
||||||
|
|
||||||
|
|
@ -202,7 +201,7 @@ proc formatTimestamp(t: int): string =
|
||||||
return "just now"
|
return "just now"
|
||||||
|
|
||||||
proc getGravatarUrl(email: string, size = 80): string =
|
proc getGravatarUrl(email: string, size = 80): string =
|
||||||
let emailMD5 = email.toLower.toMD5
|
let emailMD5 = email.toLowerAscii.toMD5
|
||||||
return ("http://www.gravatar.com/avatar/" & $emailMD5 & "?s=" & $size &
|
return ("http://www.gravatar.com/avatar/" & $emailMD5 & "?s=" & $size &
|
||||||
"&d=identicon")
|
"&d=identicon")
|
||||||
|
|
||||||
|
|
@ -268,7 +267,7 @@ proc makeIdentHash(user, password, epoch, secret: string,
|
||||||
result = hash(user & password & epoch & secret, bcryptSalt)
|
result = hash(user & password & epoch & secret, bcryptSalt)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
template `||`(x: expr): expr = (if not isNil(x): x else: "")
|
template `||`(x: untyped): untyped = (if not isNil(x): x else: "")
|
||||||
|
|
||||||
proc validThreadId(c: TForumData): bool =
|
proc validThreadId(c: TForumData): bool =
|
||||||
result = getValue(db, sql"select id from thread where id = ?",
|
result = getValue(db, sql"select id from thread where id = ?",
|
||||||
|
|
@ -343,10 +342,10 @@ proc register(c: var TForumData, name, pass, antibot,
|
||||||
|
|
||||||
# add account to person table
|
# add account to person table
|
||||||
exec(db,
|
exec(db,
|
||||||
sql("INSERT INTO person(name, password, email, salt, status, lastOnline, " &
|
sql("INSERT INTO person(name, password, email, salt, status, lastOnline) " &
|
||||||
"ban) VALUES (?, ?, ?, ?, 'user', DATETIME('now'), ?)"), name,
|
"VALUES (?, ?, ?, ?, ?, DATETIME('now'))"), name,
|
||||||
password, email, salt,
|
password, email, salt,
|
||||||
when defined(dev): "" else: banReasonEmailUnconfirmed)
|
when defined(dev): $Moderated else: $EmailUnconfirmed)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
@ -384,15 +383,16 @@ proc logout(c: var TForumData) =
|
||||||
c.userpass = ""
|
c.userpass = ""
|
||||||
exec(db, query, c.req.ip, c.req.cookies["sid"])
|
exec(db, query, c.req.ip, c.req.cookies["sid"])
|
||||||
|
|
||||||
proc getBanErrorMsg(banValue: string): string =
|
proc getBanErrorMsg(banValue: string; rank: Rank): string =
|
||||||
case banValue
|
if banValue.len > 0:
|
||||||
of "": return ""
|
|
||||||
of banReasonDeactivated:
|
|
||||||
return "Your account has been deactivated."
|
|
||||||
of banReasonEmailUnconfirmed:
|
|
||||||
return "You need to confirm your email first."
|
|
||||||
else:
|
|
||||||
return "You have been banned: " & banValue
|
return "You have been banned: " & banValue
|
||||||
|
case rank
|
||||||
|
of Spammer: return "You are a spammer."
|
||||||
|
of Troll: return "You have been banned."
|
||||||
|
of EmailUnconfirmed:
|
||||||
|
return "You need to confirm your email first."
|
||||||
|
of Moderated, User, Moderator, Admin:
|
||||||
|
return ""
|
||||||
|
|
||||||
proc checkLoggedIn(c: var TForumData) =
|
proc checkLoggedIn(c: var TForumData) =
|
||||||
if not c.req.cookies.hasKey("sid"): return
|
if not c.req.cookies.hasKey("sid"): return
|
||||||
|
|
@ -407,14 +407,13 @@ proc checkLoggedIn(c: var TForumData) =
|
||||||
c.req.ip, pass)
|
c.req.ip, pass)
|
||||||
|
|
||||||
let row = getRow(db,
|
let row = getRow(db,
|
||||||
sql"select name, email, admin, ban from person where id = ?", c.userid)
|
sql"select name, email, status, ban from person where id = ?", c.userid)
|
||||||
c.username = ||row[0]
|
c.username = ||row[0]
|
||||||
c.email = ||row[1]
|
c.email = ||row[1]
|
||||||
c.isAdmin = parseBool(||row[2])
|
c.rank = parseEnum[Rank](||row[2])
|
||||||
# Check ban status.
|
let ban = getBanErrorMsg(||row[3], c.rank)
|
||||||
let banErrorMsg = getBanErrorMsg(||row[3])
|
if ban.len > 0:
|
||||||
if banErrorMsg.len > 0:
|
discard c.setError("name", ban)
|
||||||
discard c.setError("name", banErrorMsg)
|
|
||||||
logout(c)
|
logout(c)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -442,7 +441,7 @@ proc validateRst(c: var TForumData, content: string): bool =
|
||||||
except EParseError:
|
except EParseError:
|
||||||
result = setError(c, "", getCurrentExceptionMsg())
|
result = setError(c, "", getCurrentExceptionMsg())
|
||||||
|
|
||||||
proc crud(c: TCrud, table: string, data: varargs[string]): TSqlQuery =
|
proc crud(c: TCrud, table: string, data: varargs[string]): SqlQuery =
|
||||||
case c
|
case c
|
||||||
of crCreate:
|
of crCreate:
|
||||||
var fields = "insert into " & table & "("
|
var fields = "insert into " & table & "("
|
||||||
|
|
@ -470,14 +469,14 @@ proc crud(c: TCrud, table: string, data: varargs[string]): TSqlQuery =
|
||||||
of crDelete:
|
of crDelete:
|
||||||
result = sql("delete from " & table & " where id = ?")
|
result = sql("delete from " & table & " where id = ?")
|
||||||
|
|
||||||
template retrSubject(c: expr) =
|
template retrSubject(c: untyped) =
|
||||||
if not c.req.params.hasKey("subject"):
|
if not c.req.params.hasKey("subject"):
|
||||||
raise newException(ForumError, "Subject empty")
|
raise newException(ForumError, "Subject empty")
|
||||||
let subject {.inject.} = c.req.params["subject"]
|
let subject {.inject.} = c.req.params["subject"]
|
||||||
if subject.strip.len < 3:
|
if subject.strip.len < 3:
|
||||||
return setError(c, "subject", "Subject not long enough")
|
return setError(c, "subject", "Subject not long enough")
|
||||||
|
|
||||||
template retrContent(c: expr) =
|
template retrContent(c: untyped) =
|
||||||
if not c.req.params.hasKey("content"):
|
if not c.req.params.hasKey("content"):
|
||||||
raise newException(ForumError, "Content empty")
|
raise newException(ForumError, "Content empty")
|
||||||
let content {.inject.} = c.req.params["content"]
|
let content {.inject.} = c.req.params["content"]
|
||||||
|
|
@ -486,25 +485,25 @@ template retrContent(c: expr) =
|
||||||
|
|
||||||
if not validateRst(c, content): return false
|
if not validateRst(c, content): return false
|
||||||
|
|
||||||
template retrPost(c: expr) =
|
template retrPost(c: untyped) =
|
||||||
retrSubject(c)
|
retrSubject(c)
|
||||||
retrContent(c)
|
retrContent(c)
|
||||||
|
|
||||||
template checkLogin(c: expr) =
|
template checkLogin(c: untyped) =
|
||||||
if not loggedIn(c): return setError(c, "", "User is not logged in")
|
if not loggedIn(c): return setError(c, "", "User is not logged in")
|
||||||
|
|
||||||
template checkOwnership(c, postId: expr) =
|
template checkOwnership(c, postId: untyped) =
|
||||||
if not c.isAdmin:
|
if c.rank < Moderator:
|
||||||
let x = getValue(db, sql"select author from post where id = ?",
|
let x = getValue(db, sql"select author from post where id = ?",
|
||||||
postId)
|
postId)
|
||||||
if x != c.userId:
|
if x != c.userId:
|
||||||
return setError(c, "", "You are not the owner of this post")
|
return setError(c, "", "You are not the owner of this post")
|
||||||
|
|
||||||
template setPreviewData(c: expr) {.immediate, dirty.} =
|
template setPreviewData(c: untyped) {.dirty.} =
|
||||||
c.currentPost.subject = subject
|
c.currentPost.subject = subject
|
||||||
c.currentPost.content = content
|
c.currentPost.content = content
|
||||||
|
|
||||||
template writeToDb(c, cr, setPostId: expr) =
|
template writeToDb(c, cr, setPostId: untyped) =
|
||||||
# insert a comment in the DB
|
# insert a comment in the DB
|
||||||
let retID = insertID(db, crud(cr, "post", "author", "ip", "header", "content", "thread"),
|
let retID = insertID(db, crud(cr, "post", "author", "ip", "header", "content", "thread"),
|
||||||
c.userId, c.req.ip, subject, content, $c.threadId, "")
|
c.userId, c.req.ip, subject, content, $c.threadId, "")
|
||||||
|
|
@ -591,7 +590,8 @@ proc spamCheck(c: var TForumData, subject, content: string): bool =
|
||||||
|
|
||||||
for word in ["appliance", "kitchen", "cheap", "sale", "relocating",
|
for word in ["appliance", "kitchen", "cheap", "sale", "relocating",
|
||||||
"packers", "lenders", "fifa", "coins"]:
|
"packers", "lenders", "fifa", "coins"]:
|
||||||
if word in subjAlphabet.toLower() or word in contentAlphabet.toLower():
|
if word in subjAlphabet.toLowerAscii() or
|
||||||
|
word in contentAlphabet.toLowerAscii():
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc rateLimitCheck(c: var TForumData): bool =
|
proc rateLimitCheck(c: var TForumData): bool =
|
||||||
|
|
@ -616,6 +616,13 @@ proc rateLimitCheck(c: var TForumData): bool =
|
||||||
proc makeThreadURL(c: var TForumData): string =
|
proc makeThreadURL(c: var TForumData): string =
|
||||||
c.req.makeUri("/t/" & $c.threadId)
|
c.req.makeUri("/t/" & $c.threadId)
|
||||||
|
|
||||||
|
template postChecks() {.dirty.} =
|
||||||
|
if spamCheck(c, subject, content):
|
||||||
|
echo("[WARNING] Found spam: ", subject)
|
||||||
|
return true
|
||||||
|
if rateLimitCheck(c):
|
||||||
|
return setError(c, "subject", "You're posting too fast.")
|
||||||
|
|
||||||
proc reply(c: var TForumData): bool =
|
proc reply(c: var TForumData): bool =
|
||||||
# reply to an existing thread
|
# reply to an existing thread
|
||||||
checkLogin(c)
|
checkLogin(c)
|
||||||
|
|
@ -623,17 +630,15 @@ proc reply(c: var TForumData): bool =
|
||||||
if c.isPreview:
|
if c.isPreview:
|
||||||
setPreviewData(c)
|
setPreviewData(c)
|
||||||
else:
|
else:
|
||||||
if spamCheck(c, subject, content):
|
postChecks()
|
||||||
echo("[WARNING] Found spam: ", subject)
|
|
||||||
return true
|
|
||||||
if rateLimitCheck(c):
|
|
||||||
return setError(c, "subject", "You're posting too fast.")
|
|
||||||
writeToDb(c, crCreate, true)
|
writeToDb(c, crCreate, true)
|
||||||
|
|
||||||
exec(db, sql"update thread set modified = DATETIME('now') where id = ?",
|
exec(db, sql"update thread set modified = DATETIME('now') where id = ?",
|
||||||
$c.threadId)
|
$c.threadId)
|
||||||
asyncCheck sendMailToMailingList(c.config, c.username, c.email,
|
if c.rank >= User:
|
||||||
subject, content, threadId=c.threadId, postId=c.postID, is_reply=true, threadUrl=c.makeThreadURL())
|
asyncCheck sendMailToMailingList(c.config, c.username, c.email,
|
||||||
|
subject, content, threadId=c.threadId, postId=c.postID, is_reply=true,
|
||||||
|
threadUrl=c.makeThreadURL())
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
proc newThread(c: var TForumData): bool =
|
proc newThread(c: var TForumData): bool =
|
||||||
|
|
@ -645,11 +650,7 @@ proc newThread(c: var TForumData): bool =
|
||||||
setPreviewData(c)
|
setPreviewData(c)
|
||||||
c.threadID = transientThread
|
c.threadID = transientThread
|
||||||
else:
|
else:
|
||||||
if spamCheck(c, subject, content):
|
postChecks()
|
||||||
echo("[WARNING] Found spam: ", subject)
|
|
||||||
return true
|
|
||||||
if rateLimitCheck(c):
|
|
||||||
return setError(c, "subject", "You're posting too fast.")
|
|
||||||
c.threadID = tryInsertID(db, query, c.req.params["subject"]).int
|
c.threadID = tryInsertID(db, query, c.req.params["subject"]).int
|
||||||
if c.threadID < 0: return setError(c, "subject", "Subject already exists")
|
if c.threadID < 0: return setError(c, "subject", "Subject already exists")
|
||||||
discard tryExec(db, crud(crCreate, "thread_fts", "id", "name"),
|
discard tryExec(db, crud(crCreate, "thread_fts", "id", "name"),
|
||||||
|
|
@ -657,26 +658,29 @@ proc newThread(c: var TForumData): bool =
|
||||||
writeToDb(c, crCreate, false)
|
writeToDb(c, crCreate, false)
|
||||||
discard tryExec(db, sql"insert into post_fts(post_fts) values('optimize')")
|
discard tryExec(db, sql"insert into post_fts(post_fts) values('optimize')")
|
||||||
discard tryExec(db, sql"insert into post_fts(thread_fts) values('optimize')")
|
discard tryExec(db, sql"insert into post_fts(thread_fts) values('optimize')")
|
||||||
asyncCheck sendMailToMailingList(c.config, c.username, c.email,
|
if c.rank >= User:
|
||||||
subject, content, threadId=c.threadID, postId=c.postID, is_reply=false, threadUrl=c.makeThreadURL())
|
asyncCheck sendMailToMailingList(c.config, c.username, c.email,
|
||||||
|
subject, content, threadId=c.threadID, postId=c.postID, is_reply=false,
|
||||||
|
threadUrl=c.makeThreadURL())
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
proc login(c: var TForumData, name, pass: string): bool =
|
proc login(c: var TForumData, name, pass: string): bool =
|
||||||
# get form data:
|
# get form data:
|
||||||
const query =
|
const query =
|
||||||
sql"select id, name, password, email, salt, admin, ban from person where name = ?"
|
sql"select id, name, password, email, salt, status, ban from person where name = ?"
|
||||||
if name.len == 0:
|
if name.len == 0:
|
||||||
return c.setError("name", "Username cannot be nil.")
|
return c.setError("name", "Username cannot be nil.")
|
||||||
var success = false
|
var success = false
|
||||||
for row in fastRows(db, query, name):
|
for row in fastRows(db, query, name):
|
||||||
if row[2] == makePassword(pass, row[4], row[2]):
|
if row[2] == makePassword(pass, row[4], row[2]):
|
||||||
if row[6].len > 0:
|
c.rank = parseEnum[Rank](row[5])
|
||||||
return c.setError("name", getBanErrorMsg(row[6]))
|
let ban = getBanErrorMsg(row[6], c.rank)
|
||||||
|
if ban.len > 0:
|
||||||
|
return c.setError("name", ban)
|
||||||
c.userid = row[0]
|
c.userid = row[0]
|
||||||
c.username = row[1]
|
c.username = row[1]
|
||||||
c.userpass = row[2]
|
c.userpass = row[2]
|
||||||
c.email = row[3]
|
c.email = row[3]
|
||||||
c.isAdmin = row[5].parseBool
|
|
||||||
success = true
|
success = true
|
||||||
break
|
break
|
||||||
if success:
|
if success:
|
||||||
|
|
@ -699,17 +703,24 @@ proc verifyIdentHash(c: var TForumData, name, epoch, ident: string): bool =
|
||||||
if row[2].parseInt > (epoch.parseInt + 60): return false
|
if row[2].parseInt > (epoch.parseInt + 60): return false
|
||||||
result = newIdent == ident
|
result = newIdent == ident
|
||||||
|
|
||||||
proc setBan(c: var TForumData, nick, reason: string): bool =
|
|
||||||
const query =
|
|
||||||
sql("update person set ban = ? where name = ?")
|
|
||||||
return tryExec(db, query, reason, nick)
|
|
||||||
|
|
||||||
proc deleteAll(c: var TForumData, nick: string): bool =
|
proc deleteAll(c: var TForumData, nick: string): bool =
|
||||||
const query =
|
const query =
|
||||||
sql("delete from post where author = (select id from person where name = ?)")
|
sql("delete from post where author = (select id from person where name = ?)")
|
||||||
result = tryExec(db, query, nick)
|
result = tryExec(db, query, nick)
|
||||||
result = result and updateThreads(c) >= 0
|
result = result and updateThreads(c) >= 0
|
||||||
|
|
||||||
|
proc setStatus(c: var TForumData, nick: string, status: Rank;
|
||||||
|
reason: string): bool =
|
||||||
|
const query =
|
||||||
|
sql("update person set status = ?, ban = ? where name = ?")
|
||||||
|
result = tryExec(db, query, $status, reason, nick)
|
||||||
|
when false:
|
||||||
|
# for now we filter Spammers in forms.tmpl, so that a moderator
|
||||||
|
# cannot accidentically delete all of a user's posts. We go even
|
||||||
|
# further than that and show spammers their own spam postings.
|
||||||
|
if status == Spammer and result:
|
||||||
|
result = deleteAll(c, nick)
|
||||||
|
|
||||||
proc setPassword(c: var TForumData, nick, pass: string): bool =
|
proc setPassword(c: var TForumData, nick, pass: string): bool =
|
||||||
const query =
|
const query =
|
||||||
sql("update person set password = ?, salt = ? where name = ?")
|
sql("update person set password = ?, salt = ? where name = ?")
|
||||||
|
|
@ -738,17 +749,17 @@ proc getStats(c: var TForumData, simple: bool): TForumStats =
|
||||||
if not simple:
|
if not simple:
|
||||||
var newestMemberCreation = 0
|
var newestMemberCreation = 0
|
||||||
result.activeUsers = @[]
|
result.activeUsers = @[]
|
||||||
result.newestMember = ("", -1, false)
|
result.newestMember = ("", -1)
|
||||||
const getUsersQuery =
|
const getUsersQuery =
|
||||||
sql"select id, name, admin, strftime('%s', lastOnline), strftime('%s', creation) from person"
|
sql"select id, name, strftime('%s', lastOnline), strftime('%s', creation) from person"
|
||||||
for row in fastRows(db, getUsersQuery):
|
for row in fastRows(db, getUsersQuery):
|
||||||
let secs = if row[3] == "": 0 else: row[3].parseint
|
let secs = if row[3] == "": 0 else: row[3].parseint
|
||||||
let lastOnlineSeconds = getTime() - Time(secs)
|
let lastOnlineSeconds = getTime() - Time(secs)
|
||||||
if lastOnlineSeconds < (60 * 5): # 5 minutes
|
if lastOnlineSeconds < (60 * 5): # 5 minutes
|
||||||
result.activeUsers.add((row[1], row[0].parseInt, row[2].parseBool))
|
result.activeUsers.add((row[1], row[0].parseInt))
|
||||||
if row[4].parseInt > newestMemberCreation:
|
if row[3].parseInt > newestMemberCreation:
|
||||||
result.newestMember = (row[1], row[0].parseInt, row[2].parseBool)
|
result.newestMember = (row[1], row[0].parseInt)
|
||||||
newestMemberCreation = row[4].parseInt
|
newestMemberCreation = row[3].parseInt
|
||||||
|
|
||||||
proc genPagenumNav(c: var TForumData, stats: TForumStats): string =
|
proc genPagenumNav(c: var TForumData, stats: TForumStats): string =
|
||||||
result = ""
|
result = ""
|
||||||
|
|
@ -873,7 +884,7 @@ proc gatherUserInfo(c: var TForumData, nick: string, ui: var TUserInfo): bool =
|
||||||
if uid == "": return false
|
if uid == "": return false
|
||||||
result = true
|
result = true
|
||||||
const totalPostsQuery =
|
const totalPostsQuery =
|
||||||
sql"SELECT count(*) FROM post WHERE author = ?"
|
sql"select count(*) from post where author = ?"
|
||||||
ui.posts = getValue(db, totalPostsQuery, uid).parseInt
|
ui.posts = getValue(db, totalPostsQuery, uid).parseInt
|
||||||
const totalThreadsQuery =
|
const totalThreadsQuery =
|
||||||
sql("select count(*) from thread where id in (select thread from post where" &
|
sql("select count(*) from thread where id in (select thread from post where" &
|
||||||
|
|
@ -881,14 +892,16 @@ proc gatherUserInfo(c: var TForumData, nick: string, ui: var TUserInfo): bool =
|
||||||
|
|
||||||
ui.threads = getValue(db, totalThreadsQuery, uid).parseInt
|
ui.threads = getValue(db, totalThreadsQuery, uid).parseInt
|
||||||
const lastOnlineQuery =
|
const lastOnlineQuery =
|
||||||
sql"select strftime('%s', lastOnline) from person where id = ?"
|
sql"""select strftime('%s', lastOnline), email, ban, status
|
||||||
let lastOnlineDBVal = getValue(db, lastOnlineQuery, uid)
|
from person where id = ?"""
|
||||||
ui.lastOnline = if lastOnlineDBVal != "": lastOnlineDBVal.parseInt else: -1
|
let row = db.getRow(lastOnlineQuery, $uid)
|
||||||
ui.email = getValue(db, sql"select email from person where id = ?", uid)
|
ui.lastOnline = if row[0].len > 0: row[0].parseInt else: -1
|
||||||
ui.ban = getValue(db, sql"select ban from person where id = ?", uid)
|
ui.email = row[1]
|
||||||
|
ui.ban = row[2]
|
||||||
|
ui.rank = parseEnum[Rank](row[3])
|
||||||
|
|
||||||
proc genSetUserStatusUrl(c: var TForumData, nick: string, typ: string): string =
|
include "forms.tmpl"
|
||||||
c.req.makeUri("/setUserStatus?nick=$1&type=$2" % [nick, typ])
|
include "main.tmpl"
|
||||||
|
|
||||||
proc genProfile(c: var TForumData, ui: TUserInfo): string =
|
proc genProfile(c: var TForumData, ui: TUserInfo): string =
|
||||||
result = ""
|
result = ""
|
||||||
|
|
@ -929,47 +942,17 @@ proc genProfile(c: var TForumData, ui: TUserInfo): string =
|
||||||
),
|
),
|
||||||
tr(
|
tr(
|
||||||
th("Status"),
|
th("Status"),
|
||||||
td(case ui.ban
|
td($ui.rank)
|
||||||
of banReasonDeactivated:
|
|
||||||
"Deactivated"
|
|
||||||
of banReasonEmailUnconfirmed:
|
|
||||||
"Awaiting email confirmation"
|
|
||||||
of "":
|
|
||||||
"Active"
|
|
||||||
else:
|
|
||||||
"Banned: " & ui.ban
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
tr(
|
tr(
|
||||||
th(""),
|
th(""),
|
||||||
td(if c.isAdmin and ui.ban != banReasonDeactivated:
|
td(if c.rank >= Moderator and c.rank > ui.rank:
|
||||||
if ui.ban == "":
|
c.genFormSetRank(ui)
|
||||||
htmlgen.a(
|
|
||||||
href=c.genSetUserStatusUrl(ui.nick, "ban"),
|
|
||||||
"Ban user")
|
|
||||||
else:
|
|
||||||
htmlgen.a(href=c.genSetUserStatusUrl(ui.nick, "unban"),
|
|
||||||
"Unban user")
|
|
||||||
else: "")
|
else: "")
|
||||||
),
|
),
|
||||||
tr(
|
tr(
|
||||||
th(""),
|
th(""),
|
||||||
td(if c.userName == ui.nick or c.isAdmin:
|
td(if c.rank >= Moderator:
|
||||||
if ui.ban == "":
|
|
||||||
htmlgen.a(href=c.genSetUserStatusUrl(ui.nick, "deactivate"),
|
|
||||||
"Deactivate user")
|
|
||||||
elif ui.ban == banReasonDeactivated:
|
|
||||||
htmlgen.a(href=c.genSetUserStatusUrl(ui.nick, "activate"),
|
|
||||||
"Activate user")
|
|
||||||
elif ui.ban == banReasonEmailUnconfirmed:
|
|
||||||
htmlgen.a(href=c.genSetUserStatusUrl(ui.nick, "activate"),
|
|
||||||
"Confirm user's email")
|
|
||||||
else: ""
|
|
||||||
else: "")
|
|
||||||
),
|
|
||||||
tr(
|
|
||||||
th(""),
|
|
||||||
td(if c.isAdmin:
|
|
||||||
htmlgen.a(href=c.req.makeUri("/deleteAll?nick=$1" % ui.nick),
|
htmlgen.a(href=c.req.makeUri("/deleteAll?nick=$1" % ui.nick),
|
||||||
"Delete all user's posts and threads")
|
"Delete all user's posts and threads")
|
||||||
else: "")
|
else: "")
|
||||||
|
|
@ -980,16 +963,13 @@ proc genProfile(c: var TForumData, ui: TUserInfo): string =
|
||||||
result = htmlgen.`div`(id = "profile",
|
result = htmlgen.`div`(id = "profile",
|
||||||
htmlgen.`div`(id = "left", result))
|
htmlgen.`div`(id = "left", result))
|
||||||
|
|
||||||
include "forms.tmpl"
|
|
||||||
include "main.tmpl"
|
|
||||||
|
|
||||||
proc prependRe(s: string): string =
|
proc prependRe(s: string): string =
|
||||||
result = if s.len == 0:
|
result = if s.len == 0:
|
||||||
""
|
""
|
||||||
elif s.startswith("Re:"): s
|
elif s.startswith("Re:"): s
|
||||||
else: "Re: " & s
|
else: "Re: " & s
|
||||||
|
|
||||||
template createTFD(): stmt =
|
template createTFD() =
|
||||||
var c {.inject.}: TForumData
|
var c {.inject.}: TForumData
|
||||||
init(c)
|
init(c)
|
||||||
c.req = request
|
c.req = request
|
||||||
|
|
@ -1031,7 +1011,7 @@ routes:
|
||||||
parseInt(@"page", c.pageNum, 0..1000_000)
|
parseInt(@"page", c.pageNum, 0..1000_000)
|
||||||
if @"postid".len > 0:
|
if @"postid".len > 0:
|
||||||
parseInt(@"postid", c.postId, 0..1000_000)
|
parseInt(@"postid", c.postId, 0..1000_000)
|
||||||
cond (c.pageNum > 0)
|
cond(c.pageNum > 0)
|
||||||
var count = 0
|
var count = 0
|
||||||
var pSubject = getThreadTitle(c.threadid, c.pageNum)
|
var pSubject = getThreadTitle(c.threadid, c.pageNum)
|
||||||
cond validThreadId(c)
|
cond validThreadId(c)
|
||||||
|
|
@ -1066,9 +1046,9 @@ routes:
|
||||||
get "/page/?@page?/?":
|
get "/page/?@page?/?":
|
||||||
createTFD()
|
createTFD()
|
||||||
c.isThreadsList = true
|
c.isThreadsList = true
|
||||||
cond (@"page" != "")
|
cond(@"page" != "")
|
||||||
parseInt(@"page", c.pageNum, 0..1000_000)
|
parseInt(@"page", c.pageNum, 0..1000_000)
|
||||||
cond (c.pageNum > 0)
|
cond(c.pageNum > 0)
|
||||||
var count = 0
|
var count = 0
|
||||||
let list = genThreadsList(c, count)
|
let list = genThreadsList(c, count)
|
||||||
if count == 0:
|
if count == 0:
|
||||||
|
|
@ -1078,7 +1058,7 @@ routes:
|
||||||
|
|
||||||
get "/profile/@nick/?":
|
get "/profile/@nick/?":
|
||||||
createTFD()
|
createTFD()
|
||||||
cond (@"nick" != "")
|
cond(@"nick" != "")
|
||||||
var userinfo: TUserInfo
|
var userinfo: TUserInfo
|
||||||
if gatherUserInfo(c, @"nick", userinfo):
|
if gatherUserInfo(c, @"nick", userinfo):
|
||||||
resp genMain(c, c.genProfile(userinfo),
|
resp genMain(c, c.genProfile(userinfo),
|
||||||
|
|
@ -1099,18 +1079,18 @@ routes:
|
||||||
createTFD()
|
createTFD()
|
||||||
resp genMain(c, genFormRegister(c), "Register - Nim Forum")
|
resp genMain(c, genFormRegister(c), "Register - Nim Forum")
|
||||||
|
|
||||||
template readIDs(): stmt =
|
template readIDs() =
|
||||||
# Retrieve the threadid, postid and pagenum
|
# Retrieve the threadid, postid and pagenum
|
||||||
if (@"threadid").len > 0:
|
if (@"threadid").len > 0:
|
||||||
parseInt(@"threadid", c.threadId, -1..1000_000)
|
parseInt(@"threadid", c.threadId, -1..1000_000)
|
||||||
if (@"postid").len > 0:
|
if (@"postid").len > 0:
|
||||||
parseInt(@"postid", c.postId, -1..1000_000)
|
parseInt(@"postid", c.postId, -1..1000_000)
|
||||||
|
|
||||||
template finishLogin(): stmt =
|
template finishLogin() =
|
||||||
setCookie("sid", c.userpass, daysForward(7))
|
setCookie("sid", c.userpass, daysForward(7))
|
||||||
redirect(uri("/"))
|
redirect(uri("/"))
|
||||||
|
|
||||||
template handleError(action: string, topText: string, isEdit: bool): stmt =
|
template handleError(action: string, topText: string, isEdit: bool) =
|
||||||
if c.isPreview:
|
if c.isPreview:
|
||||||
body.add genPostPreview(c, @"subject", @"content",
|
body.add genPostPreview(c, @"subject", @"content",
|
||||||
c.userName, $getGMTime(getTime()))
|
c.userName, $getGMTime(getTime()))
|
||||||
|
|
@ -1174,67 +1154,9 @@ routes:
|
||||||
resp genMain(c, genFormPost(c, "donewthread", "New thread", "", "", false),
|
resp genMain(c, genFormPost(c, "donewthread", "New thread", "", "", false),
|
||||||
"New Thread - Nim Forum")
|
"New Thread - Nim Forum")
|
||||||
|
|
||||||
get "/setUserStatus/?":
|
|
||||||
createTFD()
|
|
||||||
cond (@"nick" != "")
|
|
||||||
cond (@"type" != "")
|
|
||||||
var formBody = "<input type=\"hidden\" name=\"nick\" value=\"" &
|
|
||||||
@"nick" & "\">"
|
|
||||||
var del = false
|
|
||||||
var content = ""
|
|
||||||
echo("Got type: ", @"type")
|
|
||||||
case @"type"
|
|
||||||
of "ban":
|
|
||||||
formBody.add "<input type='text' name='reason' />" &
|
|
||||||
"<input type='submit' name='banBtn' value='Ban' />"
|
|
||||||
content =
|
|
||||||
htmlgen.p("Please enter a reason for banning this user:")
|
|
||||||
of "unban":
|
|
||||||
formBody.add "<input type='submit' name='unbanBtn' value='Unban' />"
|
|
||||||
content =
|
|
||||||
htmlgen.p("Are you sure you wish to unban ", htmlgen.b(@"nick"),
|
|
||||||
"?")
|
|
||||||
del = true
|
|
||||||
of "deactivate":
|
|
||||||
formBody.add "<input type='hidden' name='reason' value='" &
|
|
||||||
banReasonDeactivated & "' />" &
|
|
||||||
"<input type='submit' name='actBtn' value='Deactivate' />"
|
|
||||||
content =
|
|
||||||
htmlgen.p("Are you sure you wish to deactivate ", htmlgen.b(@"nick"),
|
|
||||||
"?")
|
|
||||||
of "activate":
|
|
||||||
formBody.add "<input type='submit' name='actBtn' value='Activate' />"
|
|
||||||
content =
|
|
||||||
htmlgen.p("Are you sure you wish to activate ", htmlgen.b(@"nick"),
|
|
||||||
"?")
|
|
||||||
del = true
|
|
||||||
formBody.add "<input type='hidden' name='del' value='" & $del & "'/>"
|
|
||||||
content = content & htmlgen.form(action = c.req.makeUri("/dosetban"),
|
|
||||||
`method` = "POST", formBody)
|
|
||||||
resp genMain(c, content, "Set user status - Nim Forum")
|
|
||||||
|
|
||||||
post "/dosetban":
|
|
||||||
createTFD()
|
|
||||||
cond (@"nick" != "")
|
|
||||||
if not c.isAdmin and @"nick" != c.userName:
|
|
||||||
resp genMain(c, "You cannot ban this user.", "Error - Nim Forum")
|
|
||||||
if @"reason" == "" and @"del" != "true":
|
|
||||||
resp genMain(c, "Invalid ban reason.", "Error - Nim Forum")
|
|
||||||
let result =
|
|
||||||
if @"del" == "true":
|
|
||||||
# Remove the ban.
|
|
||||||
setBan(c, @"nick", "")
|
|
||||||
else:
|
|
||||||
setBan(c, @"nick", @"reason")
|
|
||||||
if result:
|
|
||||||
redirect(c.req.makeUri("/profile/" & @"nick"))
|
|
||||||
else:
|
|
||||||
resp genMain(c, "Failed to change the ban status of user.",
|
|
||||||
"Error - Nim Forum")
|
|
||||||
|
|
||||||
get "/deleteAll/?":
|
get "/deleteAll/?":
|
||||||
createTFD()
|
createTFD()
|
||||||
cond (@"nick" != "")
|
cond(@"nick" != "")
|
||||||
var formBody = "<input type=\"hidden\" name=\"nick\" value=\"" &
|
var formBody = "<input type=\"hidden\" name=\"nick\" value=\"" &
|
||||||
@"nick" & "\">"
|
@"nick" & "\">"
|
||||||
var del = false
|
var del = false
|
||||||
|
|
@ -1248,8 +1170,8 @@ routes:
|
||||||
|
|
||||||
post "/dodeleteall/?":
|
post "/dodeleteall/?":
|
||||||
createTFD()
|
createTFD()
|
||||||
cond (@"nick" != "")
|
cond(@"nick" != "")
|
||||||
if not c.isAdmin:
|
if c.rank < Moderator:
|
||||||
resp genMain(c, "You cannot delete this user's data.", "Error - Nim Forum")
|
resp genMain(c, "You cannot delete this user's data.", "Error - Nim Forum")
|
||||||
let result = deleteAll(c, @"nick")
|
let result = deleteAll(c, @"nick")
|
||||||
if result:
|
if result:
|
||||||
|
|
@ -1258,11 +1180,31 @@ routes:
|
||||||
resp genMain(c, "Failed to delete all user's posts and threads.",
|
resp genMain(c, "Failed to delete all user's posts and threads.",
|
||||||
"Error - NimForum")
|
"Error - NimForum")
|
||||||
|
|
||||||
|
post "/dosetrank/?@nick?/?":
|
||||||
|
createTFD()
|
||||||
|
cond(@"nick" != "")
|
||||||
|
|
||||||
|
if c.rank < Moderator:
|
||||||
|
resp genMain(c, "You cannot change this user's rank.", "Error - Nim Forum")
|
||||||
|
|
||||||
|
var ui: TUserInfo
|
||||||
|
if not gatherUserInfo(c, @"nick", ui):
|
||||||
|
resp genMain(c, "User " & @"nick" & " does not exist.", "Error - Nim Forum")
|
||||||
|
let newRank = parseEnum[Rank](@"rank")
|
||||||
|
if newRank > c.rank:
|
||||||
|
resp genMain(c, "You cannot change this user's rank to this value.", "Error - Nim Forum")
|
||||||
|
|
||||||
|
if setStatus(c, @"nick", newRank, @"reason"):
|
||||||
|
redirect(c.req.makeUri("/profile/" & @"nick"))
|
||||||
|
else:
|
||||||
|
resp genMain(c, "Failed to change the ban status of user.",
|
||||||
|
"Error - Nim Forum")
|
||||||
|
|
||||||
get "/setpassword/?":
|
get "/setpassword/?":
|
||||||
createTFD()
|
createTFD()
|
||||||
cond (@"nick" != "")
|
cond(@"nick" != "")
|
||||||
cond (@"pass" != "")
|
cond(@"pass" != "")
|
||||||
if not c.isAdmin:
|
if c.rank < Moderator:
|
||||||
resp genMain(c, "You cannot change this user's pass.", "Error - Nim Forum")
|
resp genMain(c, "You cannot change this user's pass.", "Error - Nim Forum")
|
||||||
let res = setPassword(c, @"nick", @"pass")
|
let res = setPassword(c, @"nick", @"pass")
|
||||||
if res:
|
if res:
|
||||||
|
|
@ -1272,16 +1214,16 @@ routes:
|
||||||
|
|
||||||
get "/activateEmail/?":
|
get "/activateEmail/?":
|
||||||
createTFD()
|
createTFD()
|
||||||
cond (@"nick" != "")
|
cond(@"nick" != "")
|
||||||
cond (@"epoch" != "")
|
cond(@"epoch" != "")
|
||||||
cond (@"ident" != "")
|
cond(@"ident" != "")
|
||||||
var epoch: BiggestInt = 0
|
var epoch: BiggestInt = 0
|
||||||
cond(parseBiggestInt(@"epoch", epoch) > 0)
|
cond(parseBiggestInt(@"epoch", epoch) > 0)
|
||||||
var success = false
|
var success = false
|
||||||
if verifyIdentHash(c, @"nick", $epoch, @"ident"):
|
if verifyIdentHash(c, @"nick", $epoch, @"ident"):
|
||||||
let ban = db.getValue(sql"select ban from person where name = ?", @"nick")
|
let ban = parseEnum[Rank](db.getValue(sql"select status from person where name = ?", @"nick"))
|
||||||
if ban == banReasonEmailUnconfirmed:
|
if ban == EmailUnconfirmed:
|
||||||
success = setBan(c, @"nick", "")
|
success = setStatus(c, @"nick", Moderated, "")
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
resp genMain(c, "Account activated", "Nim Forum")
|
resp genMain(c, "Account activated", "Nim Forum")
|
||||||
|
|
@ -1290,9 +1232,9 @@ routes:
|
||||||
|
|
||||||
get "/emailResetPassword/?":
|
get "/emailResetPassword/?":
|
||||||
createTFD()
|
createTFD()
|
||||||
cond (@"nick" != "")
|
cond(@"nick" != "")
|
||||||
cond (@"epoch" != "")
|
cond(@"epoch" != "")
|
||||||
cond (@"ident" != "")
|
cond(@"ident" != "")
|
||||||
var epoch: BiggestInt = 0
|
var epoch: BiggestInt = 0
|
||||||
cond(parseBiggestInt(@"epoch", epoch) > 0)
|
cond(parseBiggestInt(@"epoch", epoch) > 0)
|
||||||
if verifyIdentHash(c, @"nick", $epoch, @"ident"):
|
if verifyIdentHash(c, @"nick", $epoch, @"ident"):
|
||||||
|
|
@ -1314,10 +1256,10 @@ routes:
|
||||||
|
|
||||||
post "/doemailresetpassword":
|
post "/doemailresetpassword":
|
||||||
createTFD()
|
createTFD()
|
||||||
cond (@"nick" != "")
|
cond(@"nick" != "")
|
||||||
cond (@"epoch" != "")
|
cond(@"epoch" != "")
|
||||||
cond (@"ident" != "")
|
cond(@"ident" != "")
|
||||||
cond (@"password" != "")
|
cond(@"password" != "")
|
||||||
var epoch: BiggestInt = 0
|
var epoch: BiggestInt = 0
|
||||||
cond(parseBiggestInt(@"epoch", epoch) > 0)
|
cond(parseBiggestInt(@"epoch", epoch) > 0)
|
||||||
if verifyIdentHash(c, @"nick", $epoch, @"ident"):
|
if verifyIdentHash(c, @"nick", $epoch, @"ident"):
|
||||||
|
|
@ -1337,7 +1279,7 @@ routes:
|
||||||
post "/doresetpassword":
|
post "/doresetpassword":
|
||||||
createTFD()
|
createTFD()
|
||||||
echo(request.params)
|
echo(request.params)
|
||||||
cond (@"nick" != "")
|
cond(@"nick" != "")
|
||||||
|
|
||||||
if resetPassword(c, @"nick", @"antibot"):
|
if resetPassword(c, @"nick", @"antibot"):
|
||||||
resp genMain(c, "Email sent!", "Reset Password - Nim Forum")
|
resp genMain(c, "Email sent!", "Reset Password - Nim Forum")
|
||||||
|
|
@ -1359,11 +1301,11 @@ routes:
|
||||||
for i in 0 .. q.len-1:
|
for i in 0 .. q.len-1:
|
||||||
if q[i].int < 32: q[i] = ' '
|
if q[i].int < 32: q[i] = ' '
|
||||||
elif q[i] == '\'': q[i] = '"'
|
elif q[i] == '\'': q[i] = '"'
|
||||||
c.search = q.replace("\"",""");
|
c.search = q.replace("\"",""")
|
||||||
if @"page".len > 0:
|
if @"page".len > 0:
|
||||||
parseInt(@"page", c.pageNum, 0..1000_000)
|
parseInt(@"page", c.pageNum, 0..1000_000)
|
||||||
cond (c.pageNum > 0)
|
cond(c.pageNum > 0)
|
||||||
iterator searchResults(): db_sqlite.TRow {.closure, tags: [FReadDB].} =
|
iterator searchResults(): db_sqlite.Row {.closure, tags: [ReadDbEffect].} =
|
||||||
const queryFT = "fts.sql".slurp.sql
|
const queryFT = "fts.sql".slurp.sql
|
||||||
for rowFT in fastRows(db, queryFT,
|
for rowFT in fastRows(db, queryFT,
|
||||||
[q,q,$ThreadsPerPage,$c.pageNum,$ThreadsPerPage,q,
|
[q,q,$ThreadsPerPage,$c.pageNum,$ThreadsPerPage,q,
|
||||||
|
|
@ -1373,7 +1315,7 @@ routes:
|
||||||
additionalHeaders = genRSSHeaders(c), showRssLinks = true)
|
additionalHeaders = genRSSHeaders(c), showRssLinks = true)
|
||||||
|
|
||||||
# tries first to read html, then to read rst, convert ot html, cache and return
|
# tries first to read html, then to read rst, convert ot html, cache and return
|
||||||
template textPage(path: string): stmt =
|
template textPage(path: string) =
|
||||||
createTFD()
|
createTFD()
|
||||||
#c.isThreadsList = true
|
#c.isThreadsList = true
|
||||||
var page = ""
|
var page = ""
|
||||||
|
|
|
||||||
11
ranks.nim
Normal file
11
ranks.nim
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
type
|
||||||
|
Rank* = enum ## serialized as 'status'
|
||||||
|
Spammer ## spammer: every post is invisible
|
||||||
|
Troll ## troll: cannot write new posts
|
||||||
|
EmailUnconfirmed ## member with unconfirmed email address
|
||||||
|
Moderated ## new member: posts manually reviewed before everybody
|
||||||
|
## can see them
|
||||||
|
User ## Ordinary user
|
||||||
|
Moderator ## Moderator: can ban/moderate users
|
||||||
|
Admin ## Admin: can do everything
|
||||||
15
utils.nim
15
utils.nim
|
|
@ -1,7 +1,20 @@
|
||||||
import asyncdispatch, smtp, strutils, json, os, rst, rstgen, xmltree, strtabs,
|
import asyncdispatch, smtp, strutils, json, os, rst, rstgen, xmltree, strtabs,
|
||||||
htmlparser, streams
|
htmlparser, streams, parseutils
|
||||||
from times import getTime, getGMTime, format
|
from times import getTime, getGMTime, format
|
||||||
|
|
||||||
|
proc parseInt*(s: string, value: var int, validRange: Slice[int]) {.
|
||||||
|
noSideEffect.} =
|
||||||
|
## parses `s` into an integer in the range `validRange`. If successful,
|
||||||
|
## `value` is modified to contain the result. Otherwise no exception is
|
||||||
|
## raised and `value` is not touched; this way a reasonable default value
|
||||||
|
## won't be overwritten.
|
||||||
|
var x = value
|
||||||
|
try:
|
||||||
|
discard parseutils.parseInt(s, x, 0)
|
||||||
|
except OverflowError:
|
||||||
|
discard
|
||||||
|
if x in validRange: value = x
|
||||||
|
|
||||||
type
|
type
|
||||||
Config* = object
|
Config* = object
|
||||||
smtpAddress: string
|
smtpAddress: string
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue