Fixes forum to work in dev mode. Creates setup_nimforum script.
This commit is contained in:
parent
3804faab3b
commit
7eb6b081ad
7 changed files with 259 additions and 216 deletions
128
createdb.nim
128
createdb.nim
|
|
@ -1,128 +0,0 @@
|
||||||
#
|
|
||||||
#
|
|
||||||
# The Nim Forum
|
|
||||||
# (c) Copyright 2012 Andreas Rumpf, Dominik Picheta
|
|
||||||
# Look at license.txt for more info.
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
|
|
||||||
import strutils, db_sqlite
|
|
||||||
|
|
||||||
var db = open(connection="nimforum.db", user="postgres", password="",
|
|
||||||
database="nimforum")
|
|
||||||
|
|
||||||
const
|
|
||||||
TUserName = "varchar(20)"
|
|
||||||
TPassword = "varchar(32)"
|
|
||||||
TEmail = "varchar(30)"
|
|
||||||
|
|
||||||
db.exec(sql"""
|
|
||||||
create table if not exists thread(
|
|
||||||
id integer primary key,
|
|
||||||
name varchar(100) not null,
|
|
||||||
views integer not null,
|
|
||||||
modified timestamp not null default (DATETIME('now'))
|
|
||||||
);""", [])
|
|
||||||
|
|
||||||
db.exec(sql"""
|
|
||||||
create unique index if not exists ThreadNameIx on thread (name);
|
|
||||||
""", [])
|
|
||||||
|
|
||||||
db.exec(sql("""
|
|
||||||
create table if not exists person(
|
|
||||||
id integer primary key,
|
|
||||||
name $# not null,
|
|
||||||
password $# not null,
|
|
||||||
email $# not null,
|
|
||||||
creation timestamp not null default (DATETIME('now')),
|
|
||||||
salt varbin(128) not null,
|
|
||||||
status varchar(30) not null,
|
|
||||||
lastOnline timestamp not null default (DATETIME('now'))
|
|
||||||
);""" % [TUserName, TPassword, TEmail]), [])
|
|
||||||
# echo "person table already exists"
|
|
||||||
|
|
||||||
db.exec(sql("""
|
|
||||||
alter table person
|
|
||||||
add ban varchar(128) not null default ''
|
|
||||||
"""))
|
|
||||||
|
|
||||||
db.exec(sql"""
|
|
||||||
create unique index if not exists UserNameIx on person (name);
|
|
||||||
""", [])
|
|
||||||
|
|
||||||
# ----------------------- Forum ------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
if not db.tryExec(sql"""
|
|
||||||
create table if not exists post(
|
|
||||||
id integer primary key,
|
|
||||||
author integer not null,
|
|
||||||
ip inet not null,
|
|
||||||
header varchar(100) not null,
|
|
||||||
content varchar(1000) not null,
|
|
||||||
thread integer not null,
|
|
||||||
creation timestamp not null default (DATETIME('now')),
|
|
||||||
|
|
||||||
foreign key (thread) references thread(id),
|
|
||||||
foreign key (author) references person(id)
|
|
||||||
);""", []):
|
|
||||||
echo "post table already exists"
|
|
||||||
|
|
||||||
# -------------------- Session -------------------------------------------------
|
|
||||||
|
|
||||||
if not db.tryExec(sql("""
|
|
||||||
create table if not exists session(
|
|
||||||
id integer primary key,
|
|
||||||
ip inet not null,
|
|
||||||
password $# not null,
|
|
||||||
userid integer not null,
|
|
||||||
lastModified timestamp not null default (DATETIME('now')),
|
|
||||||
foreign key (userid) references person(id)
|
|
||||||
);""" % [TPassword]), []):
|
|
||||||
echo "session table already exists"
|
|
||||||
|
|
||||||
if not db.tryExec(sql"""
|
|
||||||
create table if not exists antibot(
|
|
||||||
id integer primary key,
|
|
||||||
ip inet not null,
|
|
||||||
answer varchar(30) not null,
|
|
||||||
created timestamp not null default (DATETIME('now'))
|
|
||||||
);""", []):
|
|
||||||
echo "antibot table already exists"
|
|
||||||
|
|
||||||
|
|
||||||
db.exec sql"create index PersonStatusIdx on person(status);"
|
|
||||||
db.exec sql"create index PostByAuthorIdx on post(thread, author);"
|
|
||||||
|
|
||||||
# -------------------- Search --------------------------------------------------
|
|
||||||
|
|
||||||
if not db.tryExec(sql"""
|
|
||||||
CREATE VIRTUAL TABLE thread_fts USING fts4 (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
name VARCHAR(100) NOT NULL
|
|
||||||
);""", []):
|
|
||||||
echo "thread_fts table already exists or fts4 not supported"
|
|
||||||
else:
|
|
||||||
db.exec(sql"""
|
|
||||||
INSERT INTO thread_fts
|
|
||||||
SELECT id, name FROM thread;
|
|
||||||
""", [])
|
|
||||||
if not db.tryExec(sql"""
|
|
||||||
CREATE VIRTUAL TABLE post_fts USING fts4 (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
header VARCHAR(100) NOT NULL,
|
|
||||||
content VARCHAR(1000) NOT NULL
|
|
||||||
);""", []):
|
|
||||||
echo "post_fts table already exists or fts4 not supported"
|
|
||||||
else:
|
|
||||||
db.exec(sql"""
|
|
||||||
INSERT INTO post_fts
|
|
||||||
SELECT id, header, content FROM post;
|
|
||||||
""", [])
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#discard stdin.readline()
|
|
||||||
|
|
||||||
close(db)
|
|
||||||
|
|
@ -320,12 +320,9 @@
|
||||||
<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>
|
||||||
#if useCaptcha:
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>${fieldValid(c, "g-recaptcha-response", "Captcha:")}</td>
|
<td>${fieldValid(c, "g-recaptcha-response", "Captcha:")}</td>
|
||||||
<td>${captcha.render(includeNoScript=true)}</td>
|
<td>${captcha.render(includeNoScript=true)}</td>
|
||||||
</tr>
|
|
||||||
#end if
|
|
||||||
</table>
|
</table>
|
||||||
#if c.errorMsg != "":
|
#if c.errorMsg != "":
|
||||||
<div style="float: left; width: 100%;">
|
<div style="float: left; width: 100%;">
|
||||||
|
|
@ -494,12 +491,10 @@
|
||||||
<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>
|
||||||
#if useCaptcha:
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>${fieldValid(c, "g-recaptcha-response", "Captcha:")}</td>
|
<td>${fieldValid(c, "g-recaptcha-response", "Captcha:")}</td>
|
||||||
<td>${captcha.render(includeNoScript=true)}</td>
|
<td>${captcha.render(includeNoScript=true)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
#end if
|
|
||||||
</table>
|
</table>
|
||||||
#if c.errorMsg != "":
|
#if c.errorMsg != "":
|
||||||
<div style="float: left; width: 100%;">
|
<div style="float: left; width: 100%;">
|
||||||
|
|
|
||||||
98
forum.nim
98
forum.nim
|
|
@ -13,6 +13,8 @@ import
|
||||||
import cgi except setCookie
|
import cgi except setCookie
|
||||||
import options
|
import options
|
||||||
|
|
||||||
|
import auth
|
||||||
|
|
||||||
import frontend/threadlist except User
|
import frontend/threadlist except User
|
||||||
import frontend/[
|
import frontend/[
|
||||||
category, postlist, error, header, post, profile, user, karaxutils
|
category, postlist, error, header, post, profile, user, karaxutils
|
||||||
|
|
@ -85,7 +87,6 @@ var
|
||||||
db: DbConn
|
db: DbConn
|
||||||
isFTSAvailable: bool
|
isFTSAvailable: bool
|
||||||
config: Config
|
config: Config
|
||||||
useCaptcha: bool
|
|
||||||
captcha: ReCaptcha
|
captcha: ReCaptcha
|
||||||
|
|
||||||
proc newForumError(message: string,
|
proc newForumError(message: string,
|
||||||
|
|
@ -230,62 +231,7 @@ proc genGravatar(email: string, size: int = 80): string =
|
||||||
result = "<img width=\"$1\" height=\"$2\" src=\"$3\" />" %
|
result = "<img width=\"$1\" height=\"$2\" src=\"$3\" />" %
|
||||||
[$size, $size, getGravatarUrl(email, size)]
|
[$size, $size, getGravatarUrl(email, size)]
|
||||||
|
|
||||||
proc randomSalt(): string =
|
|
||||||
result = ""
|
|
||||||
for i in 0..127:
|
|
||||||
var r = random(225)
|
|
||||||
if r >= 32 and r <= 126:
|
|
||||||
result.add(chr(random(225)))
|
|
||||||
|
|
||||||
proc devRandomSalt(): string =
|
|
||||||
when defined(posix):
|
|
||||||
result = ""
|
|
||||||
var f = open("/dev/urandom")
|
|
||||||
var randomBytes: array[0..127, char]
|
|
||||||
discard f.readBuffer(addr(randomBytes), 128)
|
|
||||||
for i in 0..127:
|
|
||||||
if ord(randomBytes[i]) >= 32 and ord(randomBytes[i]) <= 126:
|
|
||||||
result.add(randomBytes[i])
|
|
||||||
f.close()
|
|
||||||
else:
|
|
||||||
result = randomSalt()
|
|
||||||
|
|
||||||
proc makeSalt(): string =
|
|
||||||
## Creates a salt using a cryptographically secure random number generator.
|
|
||||||
##
|
|
||||||
## Ensures that the resulting salt contains no ``\0``.
|
|
||||||
try:
|
|
||||||
result = devRandomSalt()
|
|
||||||
except IOError:
|
|
||||||
result = randomSalt()
|
|
||||||
|
|
||||||
var newResult = ""
|
|
||||||
for i in 0 .. <result.len:
|
|
||||||
if result[i] != '\0':
|
|
||||||
newResult.add result[i]
|
|
||||||
return newResult
|
|
||||||
|
|
||||||
proc makePassword(password, salt: string, comparingTo = ""): string =
|
|
||||||
## Creates an MD5 hash by combining password and salt.
|
|
||||||
when defined(windows):
|
|
||||||
result = getMD5(salt & getMD5(password))
|
|
||||||
else:
|
|
||||||
let bcryptSalt = if comparingTo != "": comparingTo else: genSalt(8)
|
|
||||||
result = hash(getMD5(salt & getMD5(password)), bcryptSalt)
|
|
||||||
|
|
||||||
proc makeIdentHash(user, password, epoch, secret: string,
|
|
||||||
comparingTo = ""): string =
|
|
||||||
## Creates a hash verifying the identity of a user. Used for password reset
|
|
||||||
## links and email activation links.
|
|
||||||
## If ``epoch`` is smaller than the epoch of the user's last login then
|
|
||||||
## the link is invalid.
|
|
||||||
## The ``secret`` is the 'salt' field in the ``person`` table.
|
|
||||||
echo(user, password, epoch, secret)
|
|
||||||
when defined(windows):
|
|
||||||
result = getMD5(user & password & epoch & secret)
|
|
||||||
else:
|
|
||||||
let bcryptSalt = if comparingTo != "": comparingTo else: genSalt(8)
|
|
||||||
result = hash(user & password & epoch & secret, bcryptSalt)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
template `||`(x: untyped): untyped = (if not isNil(x): x else: "")
|
template `||`(x: untyped): untyped = (if not isNil(x): x else: "")
|
||||||
|
|
@ -301,7 +247,7 @@ proc setError(c: TForumData, field, msg: string): bool {.inline.} =
|
||||||
|
|
||||||
proc resetPassword(c: TForumData, nick, antibot, userIp: string): Future[bool] {.async.} =
|
proc resetPassword(c: TForumData, nick, antibot, userIp: string): Future[bool] {.async.} =
|
||||||
# captcha validation:
|
# captcha validation:
|
||||||
if useCaptcha:
|
if config.recaptchaSecretKey.len > 0:
|
||||||
var captchaValid: bool = false
|
var captchaValid: bool = false
|
||||||
try:
|
try:
|
||||||
captchaValid = await captcha.verify(antibot, userIp)
|
captchaValid = await captcha.verify(antibot, userIp)
|
||||||
|
|
@ -365,15 +311,10 @@ proc checkLoggedIn(c: TForumData) =
|
||||||
c.req.ip, pass)
|
c.req.ip, pass)
|
||||||
|
|
||||||
let row = getRow(db,
|
let row = getRow(db,
|
||||||
sql"select name, email, status, ban from person where id = ?", c.userid)
|
sql"select name, email, status from person where id = ?", c.userid)
|
||||||
c.username = ||row[0]
|
c.username = ||row[0]
|
||||||
c.email = ||row[1]
|
c.email = ||row[1]
|
||||||
c.rank = parseEnum[Rank](||row[2])
|
c.rank = parseEnum[Rank](||row[2])
|
||||||
let ban = getBanErrorMsg(||row[3], c.rank)
|
|
||||||
if ban.len > 0:
|
|
||||||
discard c.setError("name", ban)
|
|
||||||
logout(c)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Update lastOnline
|
# Update lastOnline
|
||||||
db.exec(sql"update person set lastOnline = DATETIME('now') where id = ?",
|
db.exec(sql"update person set lastOnline = DATETIME('now') where id = ?",
|
||||||
|
|
@ -917,16 +858,13 @@ proc initialise() =
|
||||||
database="nimforum")
|
database="nimforum")
|
||||||
isFTSAvailable = db.getAllRows(sql("SELECT name FROM sqlite_master WHERE " &
|
isFTSAvailable = db.getAllRows(sql("SELECT name FROM sqlite_master WHERE " &
|
||||||
"type='table' AND name='post_fts'")).len == 1
|
"type='table' AND name='post_fts'")).len == 1
|
||||||
|
|
||||||
config = loadConfig()
|
config = loadConfig()
|
||||||
if len(config.recaptchaSecretKey) > 0 and len(config.recaptchaSiteKey) > 0:
|
if len(config.recaptchaSecretKey) > 0 and len(config.recaptchaSiteKey) > 0:
|
||||||
useCaptcha = true
|
|
||||||
captcha = initReCaptcha(config.recaptchaSecretKey, config.recaptchaSiteKey)
|
captcha = initReCaptcha(config.recaptchaSecretKey, config.recaptchaSiteKey)
|
||||||
else:
|
else:
|
||||||
useCaptcha = false
|
doAssert config.isDev, "Recaptcha required for production!"
|
||||||
var http = true
|
echo("[WARNING] No recaptcha secret key specified.")
|
||||||
if paramCount() > 0:
|
|
||||||
if paramStr(1) == "scgi":
|
|
||||||
http = false
|
|
||||||
|
|
||||||
template createTFD() =
|
template createTFD() =
|
||||||
var c {.inject.}: TForumData
|
var c {.inject.}: TForumData
|
||||||
|
|
@ -1027,13 +965,13 @@ proc executeReply(c: TForumData, threadId: int, content: string,
|
||||||
# Verify that content can be parsed as RST.
|
# Verify that content can be parsed as RST.
|
||||||
let retID = insertID(
|
let retID = insertID(
|
||||||
db,
|
db,
|
||||||
crud(crCreate, "post", "author", "ip", "header", "content", "thread"),
|
crud(crCreate, "post", "author", "ip", "content", "thread"),
|
||||||
c.userId, c.req.ip, subject, content, $threadId, ""
|
c.userId, c.req.ip, content, $threadId, ""
|
||||||
)
|
)
|
||||||
discard tryExec(
|
discard tryExec(
|
||||||
db,
|
db,
|
||||||
crud(crCreate, "post_fts", "id", "header", "content"),
|
crud(crCreate, "post_fts", "id", "content"),
|
||||||
retID.int, subject, content
|
retID.int, content
|
||||||
)
|
)
|
||||||
|
|
||||||
exec(db, sql"update thread set modified = DATETIME('now') where id = ?",
|
exec(db, sql"update thread set modified = DATETIME('now') where id = ?",
|
||||||
|
|
@ -1156,7 +1094,7 @@ proc executeRegister(c: TForumData, name, pass, antibot, userIp,
|
||||||
raise newForumError("Please choose a longer password", @["password"])
|
raise newForumError("Please choose a longer password", @["password"])
|
||||||
|
|
||||||
# captcha validation:
|
# captcha validation:
|
||||||
if useCaptcha:
|
if config.recaptchaSecretKey.len > 0:
|
||||||
var verifyFut = captcha.verify(antibot, userIp)
|
var verifyFut = captcha.verify(antibot, userIp)
|
||||||
yield verifyFut
|
yield verifyFut
|
||||||
if verifyFut.failed:
|
if verifyFut.failed:
|
||||||
|
|
@ -1409,14 +1347,22 @@ routes:
|
||||||
post "/signup":
|
post "/signup":
|
||||||
createTFD()
|
createTFD()
|
||||||
let formData = request.formData
|
let formData = request.formData
|
||||||
|
if not config.isDev:
|
||||||
|
cond "g-recaptcha-response" in formData
|
||||||
|
|
||||||
let username = formData["username"].body
|
let username = formData["username"].body
|
||||||
let password = formData["password"].body
|
let password = formData["password"].body
|
||||||
|
let recaptcha =
|
||||||
|
if "g-recaptcha-response" in formData:
|
||||||
|
formData["g-recaptcha-response"].body
|
||||||
|
else:
|
||||||
|
""
|
||||||
try:
|
try:
|
||||||
discard await executeRegister(
|
discard await executeRegister(
|
||||||
c,
|
c,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
formData["g-recaptcha-response"].body,
|
recaptcha,
|
||||||
request.host,
|
request.host,
|
||||||
formData["email"].body
|
formData["email"].body
|
||||||
)
|
)
|
||||||
|
|
@ -1446,7 +1392,7 @@ routes:
|
||||||
let status = UserStatus(
|
let status = UserStatus(
|
||||||
user: user,
|
user: user,
|
||||||
recaptchaSiteKey:
|
recaptchaSiteKey:
|
||||||
if useCaptcha:
|
if not config.isDev:
|
||||||
some(config.recaptchaSiteKey)
|
some(config.recaptchaSiteKey)
|
||||||
else:
|
else:
|
||||||
none[string]()
|
none[string]()
|
||||||
|
|
|
||||||
|
|
@ -75,8 +75,8 @@ when defined(js):
|
||||||
not getLoggedInUser().isNone
|
not getLoggedInUser().isNone
|
||||||
|
|
||||||
proc renderHeader*(): VNode =
|
proc renderHeader*(): VNode =
|
||||||
if state.data.isNone:
|
if state.data.isNone and state.status == Http200:
|
||||||
getStatus() # TODO: Call this every render?
|
getStatus()
|
||||||
|
|
||||||
let user = state.data.map(x => x.user).flatten
|
let user = state.data.map(x => x.user).flatten
|
||||||
result = buildHtml(tdiv()): # TODO: Why do some buildHtml's need this?
|
result = buildHtml(tdiv()): # TODO: Why do some buildHtml's need this?
|
||||||
|
|
|
||||||
228
setup_nimforum.nim
Normal file
228
setup_nimforum.nim
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# The Nim Forum
|
||||||
|
# (c) Copyright 2018 Andreas Rumpf, Dominik Picheta
|
||||||
|
# Look at license.txt for more info.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Script to initialise the nimforum.
|
||||||
|
|
||||||
|
import strutils, db_sqlite, os, times, json
|
||||||
|
|
||||||
|
import auth, frontend/user
|
||||||
|
|
||||||
|
proc backup(path: string) =
|
||||||
|
if existsFile(path):
|
||||||
|
let backupPath = path & "." & $getTime().toUnix()
|
||||||
|
echo(path, " already exists. Moving to ", backupPath)
|
||||||
|
moveFile(path, backupPath)
|
||||||
|
|
||||||
|
proc initialiseDb(admin: tuple[username, password, email: string]) =
|
||||||
|
let path = getCurrentDir() / "nimforum.db"
|
||||||
|
backup(path)
|
||||||
|
|
||||||
|
var db = open(connection="nimforum.db", user="", password="",
|
||||||
|
database="nimforum")
|
||||||
|
|
||||||
|
const
|
||||||
|
userNameType = "varchar(20)"
|
||||||
|
passwordType = "varchar(50)"
|
||||||
|
emailType = "varchar(254)" # https://stackoverflow.com/a/574698/492186
|
||||||
|
|
||||||
|
# -- Category
|
||||||
|
|
||||||
|
db.exec(sql"""
|
||||||
|
create table category(
|
||||||
|
id integer primary key,
|
||||||
|
name varchar(100) not null,
|
||||||
|
description varchar(500) not null,
|
||||||
|
color varchar(10) not null
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into category (id, name, description, color)
|
||||||
|
values (0, 'Default', '', '');
|
||||||
|
""")
|
||||||
|
|
||||||
|
# -- Thread
|
||||||
|
|
||||||
|
db.exec(sql"""
|
||||||
|
create table thread(
|
||||||
|
id integer primary key,
|
||||||
|
name varchar(100) not null,
|
||||||
|
views integer not null,
|
||||||
|
modified timestamp not null default (DATETIME('now')),
|
||||||
|
category integer not null default 0,
|
||||||
|
isLocked boolean not null default 0,
|
||||||
|
solution integer,
|
||||||
|
isDeleted boolean not null default 0,
|
||||||
|
|
||||||
|
foreign key (category) references category(id),
|
||||||
|
foreign key (solution) references post(id)
|
||||||
|
);""", [])
|
||||||
|
|
||||||
|
db.exec(sql"""
|
||||||
|
create unique index ThreadNameIx on thread (name);
|
||||||
|
""", [])
|
||||||
|
|
||||||
|
# -- Person
|
||||||
|
|
||||||
|
db.exec(sql("""
|
||||||
|
create table person(
|
||||||
|
id integer primary key,
|
||||||
|
name $# not null,
|
||||||
|
password $# not null,
|
||||||
|
email $# not null,
|
||||||
|
creation timestamp not null default (DATETIME('now')),
|
||||||
|
salt varbin(128) not null,
|
||||||
|
status varchar(30) not null,
|
||||||
|
lastOnline timestamp not null default (DATETIME('now')),
|
||||||
|
isDeleted boolean not null default 0
|
||||||
|
);""" % [userNameType, passwordType, emailType]), [])
|
||||||
|
|
||||||
|
db.exec(sql"""
|
||||||
|
create unique index UserNameIx on person (name);
|
||||||
|
""", [])
|
||||||
|
db.exec sql"create index PersonStatusIdx on person(status);"
|
||||||
|
|
||||||
|
# Create default user.
|
||||||
|
let salt = makeSalt()
|
||||||
|
let password = makePassword(admin.password, salt)
|
||||||
|
db.exec(sql"""
|
||||||
|
insert into person (id, name, password, email, salt, status)
|
||||||
|
values (0, ?, ?, ?, ?, ?);
|
||||||
|
""", admin.username, password, admin.email, salt, $Admin)
|
||||||
|
|
||||||
|
# -- Post
|
||||||
|
|
||||||
|
db.exec(sql"""
|
||||||
|
create table post(
|
||||||
|
id integer primary key,
|
||||||
|
author integer not null,
|
||||||
|
ip inet not null,
|
||||||
|
content varchar(1000) not null,
|
||||||
|
thread integer not null,
|
||||||
|
creation timestamp not null default (DATETIME('now')),
|
||||||
|
isDeleted boolean not null default 0,
|
||||||
|
|
||||||
|
foreign key (thread) references thread(id),
|
||||||
|
foreign key (author) references person(id)
|
||||||
|
);""", [])
|
||||||
|
|
||||||
|
db.exec sql"create index PostByAuthorIdx on post(thread, author);"
|
||||||
|
|
||||||
|
db.exec(sql"""
|
||||||
|
create table postRevision(
|
||||||
|
id integer primary key,
|
||||||
|
creation timestamp not null default (DATETIME('now')),
|
||||||
|
original integer not null,
|
||||||
|
content varchar(1000) not null,
|
||||||
|
|
||||||
|
foreign key (original) references post(id)
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# -- Session
|
||||||
|
|
||||||
|
db.exec(sql("""
|
||||||
|
create table session(
|
||||||
|
id integer primary key,
|
||||||
|
ip inet not null,
|
||||||
|
password $# not null,
|
||||||
|
userid integer not null,
|
||||||
|
lastModified timestamp not null default (DATETIME('now')),
|
||||||
|
foreign key (userid) references person(id)
|
||||||
|
);""" % [passwordType]), [])
|
||||||
|
|
||||||
|
# -- Likes
|
||||||
|
|
||||||
|
db.exec(sql("""
|
||||||
|
create table like(
|
||||||
|
id integer primary key,
|
||||||
|
author integer not null,
|
||||||
|
post integer not null,
|
||||||
|
creation timestamp not null default (DATETIME('now')),
|
||||||
|
|
||||||
|
foreign key (author) references person(id),
|
||||||
|
foreign key (post) references post(id)
|
||||||
|
)
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# -- Report
|
||||||
|
|
||||||
|
db.exec(sql("""
|
||||||
|
create table report(
|
||||||
|
id integer primary key,
|
||||||
|
author integer not null,
|
||||||
|
post integer not null,
|
||||||
|
kind varchar(30) not null,
|
||||||
|
content varchar(500) not null default '',
|
||||||
|
|
||||||
|
foreign key (author) references person(id),
|
||||||
|
foreign key (post) references post(id)
|
||||||
|
)
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# -- FTS
|
||||||
|
|
||||||
|
if not db.tryExec(sql"""
|
||||||
|
CREATE VIRTUAL TABLE thread_fts USING fts4 (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL
|
||||||
|
);""", []):
|
||||||
|
echo "thread_fts table already exists or fts4 not supported"
|
||||||
|
else:
|
||||||
|
db.exec(sql"""
|
||||||
|
INSERT INTO thread_fts
|
||||||
|
SELECT id, name FROM thread;
|
||||||
|
""", [])
|
||||||
|
if not db.tryExec(sql"""
|
||||||
|
CREATE VIRTUAL TABLE post_fts USING fts4 (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
content VARCHAR(1000) NOT NULL
|
||||||
|
);""", []):
|
||||||
|
echo "post_fts table already exists or fts4 not supported"
|
||||||
|
else:
|
||||||
|
db.exec(sql"""
|
||||||
|
INSERT INTO post_fts
|
||||||
|
SELECT id, content FROM post;
|
||||||
|
""", [])
|
||||||
|
|
||||||
|
close(db)
|
||||||
|
|
||||||
|
proc initialiseConfig(
|
||||||
|
name, hostname: string,
|
||||||
|
recaptcha: tuple[siteKey, secretKey: string],
|
||||||
|
smtp: tuple[address, user, password: string],
|
||||||
|
isDev: bool
|
||||||
|
) =
|
||||||
|
let path = getCurrentDir() / "forum.json"
|
||||||
|
backup(path)
|
||||||
|
|
||||||
|
var j = %{
|
||||||
|
"name": %name,
|
||||||
|
"hostname": %hostname,
|
||||||
|
"recaptchaSiteKey": %recaptcha.siteKey,
|
||||||
|
"recaptchaSecretKey": %recaptcha.secretKey,
|
||||||
|
"smtpAddress": %smtp.address,
|
||||||
|
"smtpUser": %smtp.user,
|
||||||
|
"smtpPassword": %smtp.password,
|
||||||
|
"isDev": %isDev
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFile(path, $j)
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
if paramCount() > 0 and paramStr(1) == "--dev":
|
||||||
|
echo("Initialising nimforum for development...")
|
||||||
|
initialiseConfig(
|
||||||
|
"Development Forum",
|
||||||
|
"localhost.local",
|
||||||
|
recaptcha=("", ""),
|
||||||
|
smtp=("", "", ""),
|
||||||
|
isDev=true
|
||||||
|
)
|
||||||
|
|
||||||
|
initialiseDb(
|
||||||
|
admin=("admin", "admin", "admin@localhost.local")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
Forum content license
|
Forum content license
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
All the content contributed to the Nimrod Forum is `cc-wiki (aka cc-by-sa)
|
All the content contributed to the Nim Forum is `cc-wiki (aka cc-by-sa)
|
||||||
<http://creativecommons.org/licenses/by-sa/3.0/>`_ licensed, intended to be
|
<http://creativecommons.org/licenses/by-sa/3.0/>`_ licensed, intended to be
|
||||||
**shared and remixed**. In the future we may even provide all this data as a
|
**shared and remixed**. In the future we may even provide all this data as a
|
||||||
convenient data dump.
|
convenient data dump.
|
||||||
|
|
@ -16,13 +16,13 @@ attribution**::
|
||||||
Let us clarify what we mean by attribution. If you republish this content, we
|
Let us clarify what we mean by attribution. If you republish this content, we
|
||||||
require that you:
|
require that you:
|
||||||
|
|
||||||
* **Visually indicate that the content is from the Nimrod Forum**. It doesn’t
|
* **Visually indicate that the content is from the Nim Forum**. It doesn’t
|
||||||
have to be obnoxious; a discreet text blurb is fine.
|
have to be obnoxious; a discreet text blurb is fine.
|
||||||
* **Hyperlink directly to the original post** (e.g.,
|
* **Hyperlink directly to the original post** (e.g.,
|
||||||
http://forum.nimrod-lang.org/t/186)
|
http://forum.nim-lang.org/t/186)
|
||||||
* **Show the author names** for every post.
|
* **Show the author names** for every post.
|
||||||
* **Hyperlink each author name** directly back to their user profile page
|
* **Hyperlink each author name** directly back to their user profile page
|
||||||
(e.g., http://forum.nimrod-lang.org/profile/Araq)
|
(e.g., http://forum.nim-lang.org/profile/Araq)
|
||||||
|
|
||||||
By “directly”, we mean each hyperlink must point directly to our domain in
|
By “directly”, we mean each hyperlink must point directly to our domain in
|
||||||
standard HTML visible even with JavaScript disabled, and not use a tinyurl or
|
standard HTML visible even with JavaScript disabled, and not use a tinyurl or
|
||||||
|
|
@ -38,5 +38,5 @@ Feel free to remix and reuse to your heart’s content, as long as a good faith
|
||||||
effort is made to attribute the content!
|
effort is made to attribute the content!
|
||||||
|
|
||||||
Content previous to the forum license change of
|
Content previous to the forum license change of
|
||||||
http://forum.nimrod-lang.org/t/186 remains under the original authors'
|
http://forum.nim-lang.org/t/186 remains under the original authors'
|
||||||
copyright, and therefore you cannot reuse it.
|
copyright, and therefore you cannot reuse it.
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ type
|
||||||
mlistAddress: string
|
mlistAddress: string
|
||||||
recaptchaSecretKey*: string
|
recaptchaSecretKey*: string
|
||||||
recaptchaSiteKey*: string
|
recaptchaSiteKey*: string
|
||||||
|
isDev*: bool
|
||||||
|
|
||||||
var docConfig: StringTableRef
|
var docConfig: StringTableRef
|
||||||
|
|
||||||
|
|
@ -43,6 +44,7 @@ proc loadConfig*(filename = getCurrentDir() / "forum.json"): Config =
|
||||||
result.mlistAddress = root{"mlistAddress"}.getStr("")
|
result.mlistAddress = root{"mlistAddress"}.getStr("")
|
||||||
result.recaptchaSecretKey = root{"recaptchaSecretKey"}.getStr("")
|
result.recaptchaSecretKey = root{"recaptchaSecretKey"}.getStr("")
|
||||||
result.recaptchaSiteKey = root{"recaptchaSiteKey"}.getStr("")
|
result.recaptchaSiteKey = root{"recaptchaSiteKey"}.getStr("")
|
||||||
|
result.isDev = root{"isDev"}.getBool()
|
||||||
except:
|
except:
|
||||||
echo("[WARNING] Couldn't read config file: ", filename)
|
echo("[WARNING] Couldn't read config file: ", filename)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue