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"
|
||||
|
||||
addons:
|
||||
firefox: "60.0.1"
|
||||
firefox: "73.0"
|
||||
|
||||
before_install:
|
||||
- sudo apt-get -qq update
|
||||
|
|
@ -26,13 +26,13 @@ before_install:
|
|||
- sudo make -j5 install
|
||||
- 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
|
||||
- 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
|
||||
|
||||
install:
|
||||
- export CHOOSENIM_CHOOSE_VERSION="#f92d61b1f4e193bd"
|
||||
- export CHOOSENIM_CHOOSE_VERSION="stable"
|
||||
- |
|
||||
curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh
|
||||
sh init.sh -y
|
||||
|
|
@ -41,4 +41,5 @@ install:
|
|||
|
||||
script:
|
||||
- export MOZ_HEADLESS=1
|
||||
- nimble -y test
|
||||
- nimble -y install
|
||||
- nimble -y test
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Package
|
||||
version = "2.1.0"
|
||||
version = "2.0.2"
|
||||
author = "Dominik Picheta"
|
||||
description = "The Nim forum"
|
||||
license = "MIT"
|
||||
|
|
@ -12,16 +12,16 @@ skipExt = @["nim"]
|
|||
|
||||
# Dependencies
|
||||
|
||||
requires "nim >= 0.18.1"
|
||||
requires "jester#22f8240"
|
||||
requires "nim >= 1.0.6"
|
||||
requires "jester#d8a03aa"
|
||||
requires "bcrypt#head"
|
||||
requires "hmac#9c61ebe2fd134cf97"
|
||||
requires "recaptcha 1.0.2"
|
||||
requires "recaptcha#d06488e"
|
||||
requires "sass#649e0701fa5c"
|
||||
|
||||
requires "karax#c8c7b13"
|
||||
requires "karax#f6bda9a"
|
||||
|
||||
requires "webdriver#20f3c1b"
|
||||
requires "webdriver#c2fee57"
|
||||
|
||||
# Tasks
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ task frontend, "Builds the necessary JS frontend (with CSS)":
|
|||
exec "nimble c -r src/buildcss"
|
||||
exec "nimble js -d:release src/frontend/forum.nim"
|
||||
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":
|
||||
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 {
|
||||
@extend .grid-md;
|
||||
@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>
|
||||
```
|
||||
|
||||
Then restart nginx by running ``sudo systemctl restart nginx``.
|
||||
Then reload nginx configuration by running ``sudo nginx -s reload``.
|
||||
|
||||
### Supervisor
|
||||
|
||||
|
|
@ -168,4 +168,4 @@ You should see something like this:
|
|||
## Conclusion
|
||||
|
||||
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":
|
||||
raise newForumError("Cannot reply to a locked thread.")
|
||||
|
||||
let retID = insertID(
|
||||
db,
|
||||
crud(crCreate, "post", "author", "ip", "content", "thread", "replyingTo"),
|
||||
c.userId, c.req.ip, content, $threadId,
|
||||
if replyingTo.isSome(): $replyingTo.get()
|
||||
else: ""
|
||||
)
|
||||
var retID: int64
|
||||
|
||||
if replyingTo.isSome():
|
||||
retID = insertID(
|
||||
db,
|
||||
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(
|
||||
db,
|
||||
crud(crCreate, "post_fts", "id", "content"),
|
||||
|
|
@ -621,7 +629,7 @@ proc executeRegister(c: TForumData, name, pass, antibot, userIp,
|
|||
raise newForumError("Invalid username", @["username"])
|
||||
if getValue(
|
||||
db,
|
||||
sql"select name from person where name = ? and isDeleted = 0",
|
||||
sql"select name from person where name = ? collate nocase and isDeleted = 0",
|
||||
name
|
||||
).len > 0:
|
||||
raise newForumError("Username already exists", @["username"])
|
||||
|
|
@ -1123,7 +1131,8 @@ routes:
|
|||
except EParseError:
|
||||
let err = PostError(
|
||||
errorFields: @[],
|
||||
message: getCurrentExceptionMsg()
|
||||
message: "Message needs to be valid RST! Error: " &
|
||||
getCurrentExceptionMsg()
|
||||
)
|
||||
resp Http400, $(%err), "application/json"
|
||||
|
||||
|
|
|
|||
|
|
@ -90,4 +90,4 @@ when defined(js):
|
|||
state.error = some(PostError(
|
||||
errorFields: @[],
|
||||
message: "Unknown error occurred."
|
||||
))
|
||||
))
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ proc getInt64*(s: string, default = 0): int64 =
|
|||
|
||||
when defined(js):
|
||||
include karax/prelude
|
||||
import karax / [kdom]
|
||||
import karax / [kdom, kajax]
|
||||
|
||||
from dom import nil
|
||||
|
||||
|
|
@ -87,16 +87,10 @@ when defined(js):
|
|||
|
||||
navigateTo(url)
|
||||
|
||||
type
|
||||
FormData* = ref object
|
||||
proc newFormData*(): FormData
|
||||
{.importcpp: "new FormData()", constructor.}
|
||||
proc newFormData*(form: dom.Element): FormData
|
||||
{.importcpp: "new FormData(@)", constructor.}
|
||||
proc get*(form: FormData, key: cstring): cstring
|
||||
{.importcpp: "#.get(@)".}
|
||||
proc append*(form: FormData, key, val: cstring)
|
||||
{.importcpp: "#.append(@)".}
|
||||
|
||||
proc renderProfileUrl*(username: string): string =
|
||||
makeUri(fmt"/profile/{username}")
|
||||
|
|
@ -120,4 +114,4 @@ when defined(js):
|
|||
inc(i) # Skip =
|
||||
i += query.parseUntil(val, '&', i)
|
||||
inc(i) # Skip &
|
||||
result[$decodeUri(key)] = $decodeUri(val)
|
||||
result[$decodeUri(key)] = $decodeUri(val)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
when defined(js):
|
||||
import sugar, httpcore, options, json
|
||||
import dom except Event
|
||||
import dom except Event, KeyboardEvent
|
||||
|
||||
include karax/prelude
|
||||
import karax / [kajax, kdom]
|
||||
|
|
@ -93,4 +93,4 @@ when defined(js):
|
|||
(state.onSignUp(); state.shown = false)):
|
||||
text "Create account"
|
||||
|
||||
render(state.resetPasswordModal, recaptchaSiteKey)
|
||||
render(state.resetPasswordModal, recaptchaSiteKey)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ when defined(js):
|
|||
|
||||
proc performScroll() =
|
||||
let replyBox = dom.document.getElementById("reply-box")
|
||||
replyBox.scrollIntoView(false)
|
||||
replyBox.scrollIntoView()
|
||||
|
||||
proc show*(state: ReplyBox) =
|
||||
# Scroll to the reply box.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
when defined(js):
|
||||
import sugar, httpcore, options, json
|
||||
import dom except Event
|
||||
import dom except Event, KeyboardEvent
|
||||
|
||||
include karax/prelude
|
||||
import karax / [kajax, kdom]
|
||||
|
|
@ -152,4 +152,4 @@ when defined(js):
|
|||
),
|
||||
`type`="button",
|
||||
onClick=(ev: Event, n: VNode) => onClick(ev, n, state)):
|
||||
text "Reset password"
|
||||
text "Reset password"
|
||||
|
|
|
|||
|
|
@ -10,11 +10,6 @@ let
|
|||
import frontend/[karaxutils, error]
|
||||
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
|
||||
Config* = object
|
||||
smtpAddress*: string
|
||||
|
|
@ -56,7 +51,7 @@ proc loadConfig*(filename = getCurrentDir() / "forum.json"): Config =
|
|||
smtpPassword: "", mlistAddress: "")
|
||||
let root = parseFile(filename)
|
||||
result.smtpAddress = root{"smtpAddress"}.getStr("")
|
||||
result.smtpPort = root{"smtpPort"}.getNum(25).int
|
||||
result.smtpPort = root{"smtpPort"}.getInt(25)
|
||||
result.smtpUser = root{"smtpUser"}.getStr("")
|
||||
result.smtpPassword = root{"smtpPassword"}.getStr("")
|
||||
result.smtpFromAddr = root{"smtpFromAddr"}.getStr("")
|
||||
|
|
@ -69,7 +64,7 @@ proc loadConfig*(filename = getCurrentDir() / "forum.json"): Config =
|
|||
result.name = root["name"].getStr()
|
||||
result.title = root["title"].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) =
|
||||
result = (0, newElement(tag), tag)
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ proc login*(session: Session, user, password: string) =
|
|||
checkText "#profile-btn #profile-name", user
|
||||
click "#profile-btn"
|
||||
|
||||
proc register*(session: Session, user, password: string) =
|
||||
proc register*(session: Session, user, password: string, verify = true) =
|
||||
with session:
|
||||
click "#signup-btn"
|
||||
|
||||
|
|
@ -130,11 +130,13 @@ proc register*(session: Session, user, password: string) =
|
|||
click "#signup-modal .create-account-btn"
|
||||
wait()
|
||||
|
||||
# Verify that the user menu has been initialised properly.
|
||||
click "#profile-btn"
|
||||
checkText "#profile-btn #profile-name", user
|
||||
# close menu
|
||||
click "#profile-btn"
|
||||
if verify:
|
||||
with session:
|
||||
# Verify that the user menu has been initialised properly.
|
||||
click "#profile-btn"
|
||||
checkText "#profile-btn #profile-name", user
|
||||
# close menu
|
||||
click "#profile-btn"
|
||||
|
||||
proc createThread*(session: Session, title, content: string) =
|
||||
with session:
|
||||
|
|
|
|||
|
|
@ -30,5 +30,18 @@ proc test*(session: Session, baseUrl: string) =
|
|||
test "can register":
|
||||
with session:
|
||||
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