Making TForumData a ref object

Signed-off-by: Euan Torano <euantorano@gmail.com>
This commit is contained in:
Euan Torano 2017-03-19 18:43:28 +00:00
commit e6c4c85bb1
3 changed files with 55 additions and 54 deletions

View file

@ -5,7 +5,7 @@
#end template #end template
# #
# #
#proc genThreadsList(c: var TForumData, count: var int): string = #proc genThreadsList(c: TForumData, count: var int): string =
# const queryModAdmin = sql"""select id, name, views, modified from thread # const queryModAdmin = sql"""select id, name, views, modified from thread
# where id in (select thread from post where author in # where id in (select thread from post where author in
# (select id from person where status not in ('Spammer') or id = ?)) # (select id from person where status not in ('Spammer') or id = ?))
@ -92,7 +92,7 @@
#end proc #end proc
# #
# #
#proc genPostPreview(c: var TForumData, #proc genPostPreview(c: TForumData,
# title, content, author, date: string): string = # title, content, author, date: string): string =
# result = "" # result = ""
<a name="preview"></a> <a name="preview"></a>
@ -119,7 +119,7 @@
#end proc #end proc
# #
# #
#proc genPostsList(c: var TForumData, threadId: string, count: var int): string = #proc genPostsList(c: TForumData, threadId: string, count: var int): string =
# let 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 # person u
# where u.id = p.author and p.thread = ? and $# # where u.id = p.author and p.thread = ? and $#
@ -180,7 +180,7 @@
# #
#proc genMarkHelp(): string #proc genMarkHelp(): string
#end proc #end proc
#proc genFormPost(c: var TForumData, action: string, #proc genFormPost(c: TForumData, action: string,
# topText, title, content: string, isEdit: bool): string = # topText, title, content: string, isEdit: bool): string =
# result = "" # result = ""
<br /> <br />
@ -225,7 +225,7 @@
#end proc #end proc
# #
# #
#proc genFormRegister(c: var TForumData): string = #proc genFormRegister(c: TForumData): string =
# result = "" # result = ""
<div id="talk-head"> <div id="talk-head">
<div class="info-post"> <div class="info-post">
@ -265,7 +265,7 @@
</form> </form>
#end proc #end proc
# #
#proc genFormSetRank(c: var TForumData; ui: TUserInfo): string = #proc genFormSetRank(c: TForumData; ui: TUserInfo): string =
# result = "" # result = ""
<form action="${c.req.makeUri("/dosetrank/" & ui.nick, false)}" method="POST"> <form action="${c.req.makeUri("/dosetrank/" & ui.nick, false)}" method="POST">
<table border="0"> <table border="0">
@ -288,7 +288,7 @@
</form> </form>
#end proc #end proc
# #
#proc genFormLogin(c: var TForumData): string = #proc genFormLogin(c: TForumData): string =
# result = "" # result = ""
# if not c.loggedIn: # if not c.loggedIn:
<form action="${c.req.makeUri("/dologin", false)}" method="POST"> <form action="${c.req.makeUri("/dologin", false)}" method="POST">
@ -307,7 +307,7 @@
#end proc #end proc
# #
# #
#proc genListOnline(c: var TForumData, stats: TForumStats): string = #proc genListOnline(c: TForumData, stats: TForumStats): string =
# result = "" # result = ""
# var active: seq[string] = @[] # var active: seq[string] = @[]
# for i in stats.activeUsers: # for i in stats.activeUsers:
@ -324,7 +324,7 @@
# #
# #
# #
#proc genSearchResults(c: var TForumData, #proc genSearchResults(c: TForumData,
# results: iterator: db_sqlite.Row {.closure, tags: [ReadDbEffect].}, # results: iterator: db_sqlite.Row {.closure, tags: [ReadDbEffect].},
# count: var int): string = # count: var int): string =
# const threadId = 0 # const threadId = 0
@ -407,7 +407,7 @@
#end proc #end proc
# #
# #
#proc genFormResetPassword(c: var TForumData): string = #proc genFormResetPassword(c: TForumData): string =
# result = "" # result = ""
<div id="talk-head"> <div id="talk-head">
<div class="info-post"> <div class="info-post">

View file

@ -37,7 +37,7 @@ type
TPost = tuple[subject, content: string] TPost = tuple[subject, content: string]
TForumData = object of TSession TForumData = ref object of TSession
req: Request req: Request
userid: string userid: string
actionContent: string actionContent: string
@ -80,7 +80,7 @@ var
useCaptcha: bool useCaptcha: bool
captcha: ReCaptcha captcha: ReCaptcha
proc init(c: var TForumData) = proc init(c: TForumData) =
c.userPass = "" c.userPass = ""
c.userName = "" c.userName = ""
c.threadId = unselectedThread c.threadId = unselectedThread
@ -143,16 +143,16 @@ 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: 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: 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]
proc genButtons(c: var TForumData, btns: seq[TStyledButton]): string = proc genButtons(c: TForumData, btns: seq[TStyledButton]): string =
if btns.len == 1: if btns.len == 1:
var anchor = "" var anchor = ""
@ -279,13 +279,13 @@ proc validThreadId(c: TForumData): bool =
const const
SecureChars = {'A'..'Z', 'a'..'z', '0'..'9', '_', '\128'..'\255'} SecureChars = {'A'..'Z', 'a'..'z', '0'..'9', '_', '\128'..'\255'}
proc setError(c: var TForumData, field, msg: string): bool {.inline.} = proc setError(c: TForumData, field, msg: string): bool {.inline.} =
c.invalidField = field c.invalidField = field
c.errorMsg = "Error: " & msg c.errorMsg = "Error: " & msg
return false return false
proc register(c: var TForumData, name, pass, antibot, userIp, proc register(c: TForumData, name, pass, antibot, userIp,
email: string): bool = email: string): Future[bool] {.async.} =
# Username validation: # Username validation:
if name.len == 0 or not allCharsInSet(name, SecureChars): if name.len == 0 or not allCharsInSet(name, SecureChars):
return setError(c, "name", "Invalid username!") return setError(c, "name", "Invalid username!")
@ -300,7 +300,7 @@ proc register(c: var TForumData, name, pass, antibot, userIp,
if useCaptcha: if useCaptcha:
var captchaValid: bool = false var captchaValid: bool = false
try: try:
captchaValid = waitFor captcha.verify(antibot, userIp) captchaValid = await captcha.verify(antibot, userIp)
except: except:
echo("[ERROR] Error checking captcha: " & getCurrentExceptionMsg()) echo("[ERROR] Error checking captcha: " & getCurrentExceptionMsg())
captchaValid = false captchaValid = false
@ -341,12 +341,12 @@ proc register(c: var TForumData, name, pass, antibot, userIp,
return true return true
proc resetPassword(c: var TForumData, nick, antibot, userIp: string): bool = proc resetPassword(c: TForumData, nick, antibot, userIp: string): Future[bool] {.async.} =
# captcha validation: # captcha validation:
if useCaptcha: if useCaptcha:
var captchaValid: bool = false var captchaValid: bool = false
try: try:
captchaValid = waitFor captcha.verify(antibot, userIp) captchaValid = await captcha.verify(antibot, userIp)
except: except:
echo("[ERROR] Error checking captcha: " & getCurrentExceptionMsg()) echo("[ERROR] Error checking captcha: " & getCurrentExceptionMsg())
captchaValid = false captchaValid = false
@ -378,7 +378,7 @@ proc resetPassword(c: var TForumData, nick, antibot, userIp: string): bool =
return true return true
proc logout(c: var TForumData) = proc logout(c: TForumData) =
const query = sql"delete from session where ip = ? and password = ?" const query = sql"delete from session where ip = ? and password = ?"
c.username = "" c.username = ""
c.userpass = "" c.userpass = ""
@ -395,7 +395,7 @@ proc getBanErrorMsg(banValue: string; rank: Rank): string =
of Moderated, User, Moderator, Admin: of Moderated, User, Moderator, Admin:
return "" return ""
proc checkLoggedIn(c: var TForumData) = proc checkLoggedIn(c: TForumData) =
if not c.req.cookies.hasKey("sid"): return if not c.req.cookies.hasKey("sid"): return
let pass = c.req.cookies["sid"] let pass = c.req.cookies["sid"]
if execAffectedRows(db, if execAffectedRows(db,
@ -425,7 +425,7 @@ proc checkLoggedIn(c: var TForumData) =
else: else:
echo("SID not found in sessions. Assuming logged out.") echo("SID not found in sessions. Assuming logged out.")
proc incrementViews(c: var TForumData) = proc incrementViews(c: TForumData) =
const query = sql"update thread set views = views + 1 where id = ?" const query = sql"update thread set views = views + 1 where id = ?"
exec(db, query, $c.threadId) exec(db, query, $c.threadId)
@ -435,7 +435,7 @@ proc isPreview(c: TForumData): bool =
proc isDelete(c: TForumData): bool = proc isDelete(c: TForumData): bool =
result = c.req.params.hasKey("delete") result = c.req.params.hasKey("delete")
proc validateRst(c: var TForumData, content: string): bool = proc validateRst(c: TForumData, content: string): bool =
result = true result = true
try: try:
discard rstToHtml(content) discard rstToHtml(content)
@ -513,7 +513,7 @@ template writeToDb(c, cr, setPostId: untyped) =
if setPostId: if setPostId:
c.postId = retID.int c.postId = retID.int
proc updateThreads(c: var TForumData): int = proc updateThreads(c: TForumData): int =
## Removes threads if they have no posts, or changes their modified field ## Removes threads if they have no posts, or changes their modified field
## if they still contain posts. ## if they still contain posts.
const query = const query =
@ -531,7 +531,7 @@ proc updateThreads(c: var TForumData): int =
result = -1 result = -1
discard setError(c, "", "database error") discard setError(c, "", "database error")
proc edit(c: var TForumData, postId: int): bool = proc edit(c: TForumData, postId: int): bool =
checkLogin(c) checkLogin(c)
if c.isPreview: if c.isPreview:
retrPost(c) retrPost(c)
@ -564,8 +564,8 @@ proc edit(c: var TForumData, postId: int): bool =
exec(db, crud(crUpdate, "thread", "name"), subject, $c.threadId) exec(db, crud(crUpdate, "thread", "name"), subject, $c.threadId)
result = true result = true
proc gatherUserInfo(c: var TForumData, nick: string, ui: var TUserInfo): bool proc gatherUserInfo(c: TForumData, nick: string, ui: var TUserInfo): bool
proc spamCheck(c: var TForumData, subject, content: string): bool = proc spamCheck(c: TForumData, subject, content: string): bool =
# Check current user's info # Check current user's info
var ui: TUserInfo var ui: TUserInfo
if gatherUserInfo(c, c.userName, ui): if gatherUserInfo(c, c.userName, ui):
@ -595,7 +595,7 @@ proc spamCheck(c: var TForumData, subject, content: string): bool =
word in contentAlphabet.toLowerAscii(): word in contentAlphabet.toLowerAscii():
return true return true
proc rateLimitCheck(c: var TForumData): bool = proc rateLimitCheck(c: TForumData): bool =
const query40 = const query40 =
sql("SELECT count(*) FROM post where author = ? and " & sql("SELECT count(*) FROM post where author = ? and " &
"(strftime('%s', 'now') - strftime('%s', creation)) < 40") "(strftime('%s', 'now') - strftime('%s', creation)) < 40")
@ -614,7 +614,7 @@ proc rateLimitCheck(c: var TForumData): bool =
if last300s > 6: return true if last300s > 6: return true
return false return false
proc makeThreadURL(c: var TForumData): string = proc makeThreadURL(c: TForumData): string =
c.req.makeUri("/t/" & $c.threadId) c.req.makeUri("/t/" & $c.threadId)
template postChecks() {.dirty.} = template postChecks() {.dirty.} =
@ -624,7 +624,7 @@ template postChecks() {.dirty.} =
if rateLimitCheck(c): if rateLimitCheck(c):
return setError(c, "subject", "You're posting too fast.") return setError(c, "subject", "You're posting too fast.")
proc reply(c: var TForumData): bool = proc reply(c: TForumData): bool =
# reply to an existing thread # reply to an existing thread
checkLogin(c) checkLogin(c)
retrPost(c) retrPost(c)
@ -642,7 +642,7 @@ proc reply(c: var TForumData): bool =
threadUrl=c.makeThreadURL()) threadUrl=c.makeThreadURL())
result = true result = true
proc newThread(c: var TForumData): bool = proc newThread(c: TForumData): bool =
# create new conversation thread (permanent or transient) # create new conversation thread (permanent or transient)
const query = sql"insert into thread(name, views, modified) values (?, 0, DATETIME('now'))" const query = sql"insert into thread(name, views, modified) values (?, 0, DATETIME('now'))"
checkLogin(c) checkLogin(c)
@ -665,7 +665,7 @@ proc newThread(c: var TForumData): bool =
threadUrl=c.makeThreadURL()) threadUrl=c.makeThreadURL())
result = true result = true
proc login(c: var TForumData, name, pass: string): bool = proc login(c: TForumData, name, pass: string): bool =
# get form data: # get form data:
const query = const query =
sql"select id, name, password, email, salt, status, ban from person where name = ?" sql"select id, name, password, email, salt, status, ban from person where name = ?"
@ -693,7 +693,7 @@ proc login(c: var TForumData, name, pass: string): bool =
else: else:
return c.setError("password", "Login failed!") return c.setError("password", "Login failed!")
proc verifyIdentHash(c: var TForumData, name, epoch, ident: string): bool = proc verifyIdentHash(c: TForumData, name, epoch, ident: string): bool =
const query = const query =
sql"select password, salt, strftime('%s', lastOnline) from person where name = ?" sql"select password, salt, strftime('%s', lastOnline) from person where name = ?"
var row = getRow(db, query, name) var row = getRow(db, query, name)
@ -704,13 +704,13 @@ 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 deleteAll(c: var TForumData, nick: string): bool = proc deleteAll(c: 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; proc setStatus(c: TForumData, nick: string, status: Rank;
reason: string): bool = reason: string): bool =
const query = const query =
sql("update person set status = ?, ban = ? where name = ?") sql("update person set status = ?, ban = ? where name = ?")
@ -722,13 +722,13 @@ proc setStatus(c: var TForumData, nick: string, status: Rank;
if status == Spammer and result: if status == Spammer and result:
result = deleteAll(c, nick) result = deleteAll(c, nick)
proc setPassword(c: var TForumData, nick, pass: string): bool = proc setPassword(c: 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 = ?")
var salt = makeSalt() var salt = makeSalt()
result = tryExec(db, query, makePassword(pass, salt), salt, nick) result = tryExec(db, query, makePassword(pass, salt), salt, nick)
proc hasReplyBtn(c: var TForumData): bool = proc hasReplyBtn(c: TForumData): bool =
result = c.req.pathInfo != "/donewthread" and c.req.pathInfo != "/doreply" result = c.req.pathInfo != "/donewthread" and c.req.pathInfo != "/doreply"
result = result and c.req.params.getOrDefault("action") notin ["reply", "edit"] result = result and c.req.params.getOrDefault("action") notin ["reply", "edit"]
# If the user is not logged in and there are no page numbers then we shouldn't # If the user is not logged in and there are no page numbers then we shouldn't
@ -737,7 +737,7 @@ proc hasReplyBtn(c: var TForumData): bool =
result = result and (pages > 1 or c.loggedIn) result = result and (pages > 1 or c.loggedIn)
return c.threadId >= 0 and result return c.threadId >= 0 and result
proc getStats(c: var TForumData, simple: bool): TForumStats = proc getStats(c: TForumData, simple: bool): TForumStats =
const totalUsersQuery = const totalUsersQuery =
sql"select count(*) from person" sql"select count(*) from person"
result.totalUsers = getValue(db, totalUsersQuery).parseInt result.totalUsers = getValue(db, totalUsersQuery).parseInt
@ -762,7 +762,7 @@ proc getStats(c: var TForumData, simple: bool): TForumStats =
result.newestMember = (row[1], row[0].parseInt) result.newestMember = (row[1], row[0].parseInt)
newestMemberCreation = row[3].parseInt newestMemberCreation = row[3].parseInt
proc genPagenumNav(c: var TForumData, stats: TForumStats): string = proc genPagenumNav(c: TForumData, stats: TForumStats): string =
result = "" result = ""
var var
firstUrl = "" firstUrl = ""
@ -835,22 +835,22 @@ proc genPagenumNav(c: var TForumData, stats: TForumStats): string =
result.add(nextTag) result.add(nextTag)
result.add(lastTag) result.add(lastTag)
proc gatherTotalPostsByID(c: var TForumData, thrid: int): int = proc gatherTotalPostsByID(c: TForumData, thrid: int): int =
## Gets the total post count of a thread. ## Gets the total post count of a thread.
result = getValue(db, sql"select count(*) from post where thread = ?", $thrid).parseInt result = getValue(db, sql"select count(*) from post where thread = ?", $thrid).parseInt
proc gatherTotalPosts(c: var TForumData) = proc gatherTotalPosts(c: TForumData) =
if c.totalPosts > 0: return if c.totalPosts > 0: return
# Gather some data. # Gather some data.
const totalPostsQuery = const totalPostsQuery =
sql"select count(*) from post p, person u where u.id = p.author and p.thread = ?" sql"select count(*) from post p, person u where u.id = p.author and p.thread = ?"
c.totalPosts = getValue(db, totalPostsQuery, $c.threadId).parseInt c.totalPosts = getValue(db, totalPostsQuery, $c.threadId).parseInt
proc getPagesInThread(c: var TForumData): int = proc getPagesInThread(c: TForumData): int =
c.gatherTotalPosts() # Get total post count c.gatherTotalPosts() # Get total post count
result = ceil(c.totalPosts / PostsPerPage).int-1 result = ceil(c.totalPosts / PostsPerPage).int-1
proc getPagesInThreadByID(c: var TForumData, thrid: int): int = proc getPagesInThreadByID(c: TForumData, thrid: int): int =
result = ceil(c.gatherTotalPostsByID(thrid) / PostsPerPage).int result = ceil(c.gatherTotalPostsByID(thrid) / PostsPerPage).int
proc getThreadTitle(thrid: int, pageNum: int): string = proc getThreadTitle(thrid: int, pageNum: int): string =
@ -858,7 +858,7 @@ proc getThreadTitle(thrid: int, pageNum: int): string =
if pageNum notin {0,1}: if pageNum notin {0,1}:
result.add(" - Page " & $pageNum) result.add(" - Page " & $pageNum)
proc genPagenumLocalNav(c: var TForumData, thrid: int): string = proc genPagenumLocalNav(c: TForumData, thrid: int): string =
result = "" result = ""
const maxPostPages = 6 # Maximum links to pages shown. const maxPostPages = 6 # Maximum links to pages shown.
const hmpp = maxPostPages div 2 const hmpp = maxPostPages div 2
@ -878,7 +878,7 @@ proc genPagenumLocalNav(c: var TForumData, thrid: int): string =
result = htmlgen.span(class = "pages", result) result = htmlgen.span(class = "pages", result)
proc gatherUserInfo(c: var TForumData, nick: string, ui: var TUserInfo): bool = proc gatherUserInfo(c: TForumData, nick: string, ui: var TUserInfo): bool =
ui.nick = nick ui.nick = nick
const getUIDQuery = sql"select id from person where name = ?" const getUIDQuery = sql"select id from person where name = ?"
var uid = getValue(db, getUIDQuery, nick) var uid = getValue(db, getUIDQuery, nick)
@ -908,7 +908,7 @@ proc gatherUserInfo(c: var TForumData, nick: string, ui: var TUserInfo): bool =
include "forms.tmpl" include "forms.tmpl"
include "main.tmpl" include "main.tmpl"
proc genProfile(c: var TForumData, ui: TUserInfo): string = proc genProfile(c: TForumData, ui: TUserInfo): string =
result = "" result = ""
result.add(htmlgen.`div`(id = "talk-head", result.add(htmlgen.`div`(id = "talk-head",
@ -982,6 +982,7 @@ proc prependRe(s: string): string =
template createTFD() = template createTFD() =
var c {.inject.}: TForumData var c {.inject.}: TForumData
new(c)
init(c) init(c)
c.req = request c.req = request
c.startTime = epochTime() c.startTime = epochTime()
@ -1123,7 +1124,7 @@ routes:
post "/doregister": post "/doregister":
createTFD() createTFD()
if c.register(@"name", @"new_password", @"g-recaptcha-response", request.host, @"email"): if await c.register(@"name", @"new_password", @"g-recaptcha-response", request.host, @"email"):
resp genMain(c, "You are now registered. You must now confirm your" & resp genMain(c, "You are now registered. You must now confirm your" &
" email address by clicking the link sent to " & @"email", " email address by clicking the link sent to " & @"email",
"Registration successful - Nim Forum") "Registration successful - Nim Forum")
@ -1292,7 +1293,7 @@ routes:
echo(request.params) echo(request.params)
cond(@"nick" != "") cond(@"nick" != "")
if resetPassword(c, @"nick", @"g-recaptcha-response", request.host): if await resetPassword(c, @"nick", @"g-recaptcha-response", request.host):
resp genMain(c, "Email sent!", "Reset Password - Nim Forum") resp genMain(c, "Email sent!", "Reset Password - Nim Forum")
else: else:
resp genMain(c, genFormResetPassword(c), "Reset Password - Nim Forum") resp genMain(c, genFormResetPassword(c), "Reset Password - Nim Forum")

View file

@ -1,5 +1,5 @@
#? stdtmpl | standard #? stdtmpl | standard
#proc genMain(c: var TForumData, content: string, title = "Nim Forum", #proc genMain(c: TForumData, content: string, title = "Nim Forum",
# additional_headers = "", showRssLinks = false): string = # additional_headers = "", showRssLinks = false): string =
# result = "" # result = ""
# var stats: TForumStats # var stats: TForumStats
@ -170,7 +170,7 @@
</html> </html>
#end proc #end proc
# #
#proc genRSSHeaders(c: var TForumData): string = #proc genRSSHeaders(c: TForumData): string =
# result = "" # result = ""
<link href="${c.req.makeUri("/threadActivity.xml")}" title="Thread activity" <link href="${c.req.makeUri("/threadActivity.xml")}" title="Thread activity"
type="application/atom+xml" rel="alternate"> type="application/atom+xml" rel="alternate">
@ -178,7 +178,7 @@
type="application/atom+xml" rel="alternate"> type="application/atom+xml" rel="alternate">
#end proc #end proc
# #
#proc genThreadsRSS(c: var TForumData): string = #proc genThreadsRSS(c: TForumData): string =
# result = "" # result = ""
# const query = sql"""SELECT A.id, A.name, # const query = sql"""SELECT A.id, A.name,
# strftime('%Y-%m-%dT%H:%M:%SZ', (A.modified)), # strftime('%Y-%m-%dT%H:%M:%SZ', (A.modified)),
@ -226,7 +226,7 @@ ${xmlEncode(rstToHtml(%postContent))}</content>
</feed> </feed>
#end proc #end proc
# #
#proc genPostsRSS(c: var TForumData): string = #proc genPostsRSS(c: TForumData): string =
# result = "" # result = ""
# const query = sql"""SELECT A.id, B.name, A.content, A.thread, # const query = sql"""SELECT A.id, B.name, A.content, A.thread,
# A.header, strftime('%Y-%m-%dT%H:%M:%SZ', A.creation), # A.header, strftime('%Y-%m-%dT%H:%M:%SZ', A.creation),