Merge branch 'master' into add_categories
This commit is contained in:
commit
fac828b1e9
13 changed files with 72 additions and 51 deletions
11
.travis.yml
11
.travis.yml
|
|
@ -9,7 +9,7 @@ cache:
|
||||||
- "$HOME/.choosenim"
|
- "$HOME/.choosenim"
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
firefox: "60.0.1"
|
firefox: "73.0"
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- sudo apt-get -qq update
|
- sudo apt-get -qq update
|
||||||
|
|
@ -26,13 +26,13 @@ before_install:
|
||||||
- sudo make -j5 install
|
- sudo make -j5 install
|
||||||
- cd ..
|
- cd ..
|
||||||
|
|
||||||
- wget https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-linux64.tar.gz
|
- wget https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-linux64.tar.gz
|
||||||
- mkdir geckodriver
|
- mkdir geckodriver
|
||||||
- tar -xzf geckodriver-v0.20.1-linux64.tar.gz -C geckodriver
|
- tar -xzf geckodriver-v0.26.0-linux64.tar.gz -C geckodriver
|
||||||
- export PATH=$PATH:$PWD/geckodriver
|
- export PATH=$PATH:$PWD/geckodriver
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- export CHOOSENIM_CHOOSE_VERSION="#f92d61b1f4e193bd"
|
- export CHOOSENIM_CHOOSE_VERSION="stable"
|
||||||
- |
|
- |
|
||||||
curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh
|
curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh
|
||||||
sh init.sh -y
|
sh init.sh -y
|
||||||
|
|
@ -41,4 +41,5 @@ install:
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- export MOZ_HEADLESS=1
|
- export MOZ_HEADLESS=1
|
||||||
- nimble -y test
|
- nimble -y install
|
||||||
|
- nimble -y test
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Package
|
# Package
|
||||||
version = "2.1.0"
|
version = "2.0.2"
|
||||||
author = "Dominik Picheta"
|
author = "Dominik Picheta"
|
||||||
description = "The Nim forum"
|
description = "The Nim forum"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
@ -12,16 +12,16 @@ skipExt = @["nim"]
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
|
||||||
requires "nim >= 0.18.1"
|
requires "nim >= 1.0.6"
|
||||||
requires "jester#22f8240"
|
requires "jester#d8a03aa"
|
||||||
requires "bcrypt#head"
|
requires "bcrypt#head"
|
||||||
requires "hmac#9c61ebe2fd134cf97"
|
requires "hmac#9c61ebe2fd134cf97"
|
||||||
requires "recaptcha 1.0.2"
|
requires "recaptcha#d06488e"
|
||||||
requires "sass#649e0701fa5c"
|
requires "sass#649e0701fa5c"
|
||||||
|
|
||||||
requires "karax#c8c7b13"
|
requires "karax#f6bda9a"
|
||||||
|
|
||||||
requires "webdriver#20f3c1b"
|
requires "webdriver#c2fee57"
|
||||||
|
|
||||||
# Tasks
|
# Tasks
|
||||||
|
|
||||||
|
|
@ -36,7 +36,7 @@ task frontend, "Builds the necessary JS frontend (with CSS)":
|
||||||
exec "nimble c -r src/buildcss"
|
exec "nimble c -r src/buildcss"
|
||||||
exec "nimble js -d:release src/frontend/forum.nim"
|
exec "nimble js -d:release src/frontend/forum.nim"
|
||||||
mkDir "public/js"
|
mkDir "public/js"
|
||||||
cpFile "src/frontend/nimcache/forum.js", "public/js/forum.js"
|
cpFile "src/frontend/forum.js", "public/js/forum.js"
|
||||||
|
|
||||||
task minify, "Minifies the JS using Google's closure compiler":
|
task minify, "Minifies the JS using Google's closure compiler":
|
||||||
exec "closure-compiler public/js/forum.js --js_output_file public/js/forum.js.opt"
|
exec "closure-compiler public/js/forum.js --js_output_file public/js/forum.js.opt"
|
||||||
|
|
|
||||||
|
|
@ -269,6 +269,13 @@ $threads-meta-color: #545d70;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide all the avatars but the first on small screens.
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
#threads-list a:not(:first-child) > .avatar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.posts, .about {
|
.posts, .about {
|
||||||
@extend .grid-md;
|
@extend .grid-md;
|
||||||
@extend .container;
|
@extend .container;
|
||||||
|
|
|
||||||
4
setup.md
4
setup.md
|
|
@ -100,7 +100,7 @@ You should then create a symlink to this file inside ``/etc/nginx/sites-enabled/
|
||||||
ln -s /etc/nginx/sites-available/<forum.hostname.com> /etc/nginx/sites-enabled/<forum.hostname.com>
|
ln -s /etc/nginx/sites-available/<forum.hostname.com> /etc/nginx/sites-enabled/<forum.hostname.com>
|
||||||
```
|
```
|
||||||
|
|
||||||
Then restart nginx by running ``sudo systemctl restart nginx``.
|
Then reload nginx configuration by running ``sudo nginx -s reload``.
|
||||||
|
|
||||||
### Supervisor
|
### Supervisor
|
||||||
|
|
||||||
|
|
@ -168,4 +168,4 @@ You should see something like this:
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
That should be all you need to get started. Your forum should now be accessible
|
That should be all you need to get started. Your forum should now be accessible
|
||||||
via your hostname, assuming that it points to your VPS' IP address.
|
via your hostname, assuming that it points to your VPS' IP address.
|
||||||
|
|
|
||||||
|
|
@ -457,13 +457,21 @@ proc executeReply(c: TForumData, threadId: int, content: string,
|
||||||
if isLocked == "1":
|
if isLocked == "1":
|
||||||
raise newForumError("Cannot reply to a locked thread.")
|
raise newForumError("Cannot reply to a locked thread.")
|
||||||
|
|
||||||
let retID = insertID(
|
var retID: int64
|
||||||
db,
|
|
||||||
crud(crCreate, "post", "author", "ip", "content", "thread", "replyingTo"),
|
if replyingTo.isSome():
|
||||||
c.userId, c.req.ip, content, $threadId,
|
retID = insertID(
|
||||||
if replyingTo.isSome(): $replyingTo.get()
|
db,
|
||||||
else: ""
|
crud(crCreate, "post", "author", "ip", "content", "thread", "replyingTo"),
|
||||||
)
|
c.userId, c.req.ip, content, $threadId, $replyingTo.get()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
retID = insertID(
|
||||||
|
db,
|
||||||
|
crud(crCreate, "post", "author", "ip", "content", "thread"),
|
||||||
|
c.userId, c.req.ip, content, $threadId
|
||||||
|
)
|
||||||
|
|
||||||
discard tryExec(
|
discard tryExec(
|
||||||
db,
|
db,
|
||||||
crud(crCreate, "post_fts", "id", "content"),
|
crud(crCreate, "post_fts", "id", "content"),
|
||||||
|
|
@ -621,7 +629,7 @@ proc executeRegister(c: TForumData, name, pass, antibot, userIp,
|
||||||
raise newForumError("Invalid username", @["username"])
|
raise newForumError("Invalid username", @["username"])
|
||||||
if getValue(
|
if getValue(
|
||||||
db,
|
db,
|
||||||
sql"select name from person where name = ? and isDeleted = 0",
|
sql"select name from person where name = ? collate nocase and isDeleted = 0",
|
||||||
name
|
name
|
||||||
).len > 0:
|
).len > 0:
|
||||||
raise newForumError("Username already exists", @["username"])
|
raise newForumError("Username already exists", @["username"])
|
||||||
|
|
@ -1123,7 +1131,8 @@ routes:
|
||||||
except EParseError:
|
except EParseError:
|
||||||
let err = PostError(
|
let err = PostError(
|
||||||
errorFields: @[],
|
errorFields: @[],
|
||||||
message: getCurrentExceptionMsg()
|
message: "Message needs to be valid RST! Error: " &
|
||||||
|
getCurrentExceptionMsg()
|
||||||
)
|
)
|
||||||
resp Http400, $(%err), "application/json"
|
resp Http400, $(%err), "application/json"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,4 +90,4 @@ when defined(js):
|
||||||
state.error = some(PostError(
|
state.error = some(PostError(
|
||||||
errorFields: @[],
|
errorFields: @[],
|
||||||
message: "Unknown error occurred."
|
message: "Unknown error occurred."
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ proc getInt64*(s: string, default = 0): int64 =
|
||||||
|
|
||||||
when defined(js):
|
when defined(js):
|
||||||
include karax/prelude
|
include karax/prelude
|
||||||
import karax / [kdom]
|
import karax / [kdom, kajax]
|
||||||
|
|
||||||
from dom import nil
|
from dom import nil
|
||||||
|
|
||||||
|
|
@ -87,16 +87,10 @@ when defined(js):
|
||||||
|
|
||||||
navigateTo(url)
|
navigateTo(url)
|
||||||
|
|
||||||
type
|
|
||||||
FormData* = ref object
|
|
||||||
proc newFormData*(): FormData
|
|
||||||
{.importcpp: "new FormData()", constructor.}
|
|
||||||
proc newFormData*(form: dom.Element): FormData
|
proc newFormData*(form: dom.Element): FormData
|
||||||
{.importcpp: "new FormData(@)", constructor.}
|
{.importcpp: "new FormData(@)", constructor.}
|
||||||
proc get*(form: FormData, key: cstring): cstring
|
proc get*(form: FormData, key: cstring): cstring
|
||||||
{.importcpp: "#.get(@)".}
|
{.importcpp: "#.get(@)".}
|
||||||
proc append*(form: FormData, key, val: cstring)
|
|
||||||
{.importcpp: "#.append(@)".}
|
|
||||||
|
|
||||||
proc renderProfileUrl*(username: string): string =
|
proc renderProfileUrl*(username: string): string =
|
||||||
makeUri(fmt"/profile/{username}")
|
makeUri(fmt"/profile/{username}")
|
||||||
|
|
@ -120,4 +114,4 @@ when defined(js):
|
||||||
inc(i) # Skip =
|
inc(i) # Skip =
|
||||||
i += query.parseUntil(val, '&', i)
|
i += query.parseUntil(val, '&', i)
|
||||||
inc(i) # Skip &
|
inc(i) # Skip &
|
||||||
result[$decodeUri(key)] = $decodeUri(val)
|
result[$decodeUri(key)] = $decodeUri(val)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
when defined(js):
|
when defined(js):
|
||||||
import sugar, httpcore, options, json
|
import sugar, httpcore, options, json
|
||||||
import dom except Event
|
import dom except Event, KeyboardEvent
|
||||||
|
|
||||||
include karax/prelude
|
include karax/prelude
|
||||||
import karax / [kajax, kdom]
|
import karax / [kajax, kdom]
|
||||||
|
|
@ -93,4 +93,4 @@ when defined(js):
|
||||||
(state.onSignUp(); state.shown = false)):
|
(state.onSignUp(); state.shown = false)):
|
||||||
text "Create account"
|
text "Create account"
|
||||||
|
|
||||||
render(state.resetPasswordModal, recaptchaSiteKey)
|
render(state.resetPasswordModal, recaptchaSiteKey)
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ when defined(js):
|
||||||
|
|
||||||
proc performScroll() =
|
proc performScroll() =
|
||||||
let replyBox = dom.document.getElementById("reply-box")
|
let replyBox = dom.document.getElementById("reply-box")
|
||||||
replyBox.scrollIntoView(false)
|
replyBox.scrollIntoView()
|
||||||
|
|
||||||
proc show*(state: ReplyBox) =
|
proc show*(state: ReplyBox) =
|
||||||
# Scroll to the reply box.
|
# Scroll to the reply box.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
when defined(js):
|
when defined(js):
|
||||||
import sugar, httpcore, options, json
|
import sugar, httpcore, options, json
|
||||||
import dom except Event
|
import dom except Event, KeyboardEvent
|
||||||
|
|
||||||
include karax/prelude
|
include karax/prelude
|
||||||
import karax / [kajax, kdom]
|
import karax / [kajax, kdom]
|
||||||
|
|
@ -152,4 +152,4 @@ when defined(js):
|
||||||
),
|
),
|
||||||
`type`="button",
|
`type`="button",
|
||||||
onClick=(ev: Event, n: VNode) => onClick(ev, n, state)):
|
onClick=(ev: Event, n: VNode) => onClick(ev, n, state)):
|
||||||
text "Reset password"
|
text "Reset password"
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,6 @@ let
|
||||||
import frontend/[karaxutils, error]
|
import frontend/[karaxutils, error]
|
||||||
export parseInt
|
export parseInt
|
||||||
|
|
||||||
proc `%`*[T](opt: Option[T]): JsonNode =
|
|
||||||
## Generic constructor for JSON data. Creates a new ``JNull JsonNode``
|
|
||||||
## if ``opt`` is empty, otherwise it delegates to the underlying value.
|
|
||||||
if opt.isSome: %opt.get else: newJNull()
|
|
||||||
|
|
||||||
type
|
type
|
||||||
Config* = object
|
Config* = object
|
||||||
smtpAddress*: string
|
smtpAddress*: string
|
||||||
|
|
@ -56,7 +51,7 @@ proc loadConfig*(filename = getCurrentDir() / "forum.json"): Config =
|
||||||
smtpPassword: "", mlistAddress: "")
|
smtpPassword: "", mlistAddress: "")
|
||||||
let root = parseFile(filename)
|
let root = parseFile(filename)
|
||||||
result.smtpAddress = root{"smtpAddress"}.getStr("")
|
result.smtpAddress = root{"smtpAddress"}.getStr("")
|
||||||
result.smtpPort = root{"smtpPort"}.getNum(25).int
|
result.smtpPort = root{"smtpPort"}.getInt(25)
|
||||||
result.smtpUser = root{"smtpUser"}.getStr("")
|
result.smtpUser = root{"smtpUser"}.getStr("")
|
||||||
result.smtpPassword = root{"smtpPassword"}.getStr("")
|
result.smtpPassword = root{"smtpPassword"}.getStr("")
|
||||||
result.smtpFromAddr = root{"smtpFromAddr"}.getStr("")
|
result.smtpFromAddr = root{"smtpFromAddr"}.getStr("")
|
||||||
|
|
@ -69,7 +64,7 @@ proc loadConfig*(filename = getCurrentDir() / "forum.json"): Config =
|
||||||
result.name = root["name"].getStr()
|
result.name = root["name"].getStr()
|
||||||
result.title = root["title"].getStr()
|
result.title = root["title"].getStr()
|
||||||
result.ga = root{"ga"}.getStr()
|
result.ga = root{"ga"}.getStr()
|
||||||
result.port = root{"port"}.getNum(5000).int
|
result.port = root{"port"}.getInt(5000)
|
||||||
|
|
||||||
proc processGT(n: XmlNode, tag: string): (int, XmlNode, string) =
|
proc processGT(n: XmlNode, tag: string): (int, XmlNode, string) =
|
||||||
result = (0, newElement(tag), tag)
|
result = (0, newElement(tag), tag)
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ proc login*(session: Session, user, password: string) =
|
||||||
checkText "#profile-btn #profile-name", user
|
checkText "#profile-btn #profile-name", user
|
||||||
click "#profile-btn"
|
click "#profile-btn"
|
||||||
|
|
||||||
proc register*(session: Session, user, password: string) =
|
proc register*(session: Session, user, password: string, verify = true) =
|
||||||
with session:
|
with session:
|
||||||
click "#signup-btn"
|
click "#signup-btn"
|
||||||
|
|
||||||
|
|
@ -130,11 +130,13 @@ proc register*(session: Session, user, password: string) =
|
||||||
click "#signup-modal .create-account-btn"
|
click "#signup-modal .create-account-btn"
|
||||||
wait()
|
wait()
|
||||||
|
|
||||||
# Verify that the user menu has been initialised properly.
|
if verify:
|
||||||
click "#profile-btn"
|
with session:
|
||||||
checkText "#profile-btn #profile-name", user
|
# Verify that the user menu has been initialised properly.
|
||||||
# close menu
|
click "#profile-btn"
|
||||||
click "#profile-btn"
|
checkText "#profile-btn #profile-name", user
|
||||||
|
# close menu
|
||||||
|
click "#profile-btn"
|
||||||
|
|
||||||
proc createThread*(session: Session, title, content: string) =
|
proc createThread*(session: Session, title, content: string) =
|
||||||
with session:
|
with session:
|
||||||
|
|
|
||||||
|
|
@ -30,5 +30,18 @@ proc test*(session: Session, baseUrl: string) =
|
||||||
test "can register":
|
test "can register":
|
||||||
with session:
|
with session:
|
||||||
register("test", "test")
|
register("test", "test")
|
||||||
|
logout()
|
||||||
|
|
||||||
session.logout()
|
test "can't register same username with different case":
|
||||||
|
with session:
|
||||||
|
register "test1", "test1", verify = false
|
||||||
|
logout()
|
||||||
|
|
||||||
|
navigate baseUrl
|
||||||
|
wait()
|
||||||
|
|
||||||
|
register "TEst1", "test1", verify = false
|
||||||
|
|
||||||
|
ensureExists "#signup-form .has-error"
|
||||||
|
navigate baseUrl
|
||||||
|
wait()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue