Implements logging in.

This commit is contained in:
Dominik Picheta 2018-05-11 13:53:26 +01:00
commit c0bbce53e9
6 changed files with 228 additions and 99 deletions

View file

@ -7,12 +7,14 @@
#
import
os, strutils, times, md5, strtabs, cgi, math, db_sqlite,
os, strutils, times, md5, strtabs, math, db_sqlite,
scgi, jester, asyncdispatch, asyncnet, cache, sequtils,
parseutils, utils, random, rst, ranks, recaptcha, json, re
import cgi except setCookie
import options
import redesign/threadlist except User
import redesign/[category, postlist]
import redesign/[category, postlist, error, header]
when not defined(windows):
import bcrypt # TODO
@ -1153,6 +1155,34 @@ routes:
resp $(%list), "application/json"
post "/karax/login":
createTFD()
let formData = request.formData
if login(c, formData["username"].body, formData["password"].body):
setCookie("sid", c.userpass)
resp Http200, "{}", "application/json"
else:
let err = PostError(
errorFields: @["username", "password"],
message: "Invalid username or password"
)
resp $(%err), "application/json"
get "/karax/status.json":
createTFD()
let user =
if c.loggedIn():
some(threadlist.User(
name: c.username,
avatarUrl: c.email.getGravatarUrl(),
isOnline: true
))
else:
none[threadlist.User]()
let status = UserStatus(user: user)
resp $(%status), "application/json"
get re"/karax/(.+)?":
resp readFile("redesign/karax.html")

View file

@ -1,17 +1,22 @@
include karax/prelude
import karax / [vstyles, kajax, kdom]
type
PostError* = object
errorFields*: seq[string] ## IDs of the fields with an error.
message*: string
when defined(js):
include karax/prelude
import karax / [vstyles, kajax, kdom]
proc renderError*(message: string): VNode =
result = buildHtml():
tdiv(class="empty error"):
tdiv(class="empty icon"):
italic(class="fas fa-bug fa-5x")
p(class="empty-title h5"):
text message
p(class="empty-subtitle"):
text "Please report this issue to us so we can fix it!"
tdiv(class="empty-action"):
a(href="https://github.com/nim-lang/nimforum/issues", target="_blank"):
button(class="btn btn-primary"):
text "Report issue"
proc renderError*(message: string): VNode =
result = buildHtml():
tdiv(class="empty error"):
tdiv(class="empty icon"):
italic(class="fas fa-bug fa-5x")
p(class="empty-title h5"):
text message
p(class="empty-subtitle"):
text "Please report this issue to us so we can fix it!"
tdiv(class="empty-action"):
a(href="https://github.com/nim-lang/nimforum/issues", target="_blank"):
button(class="btn btn-primary"):
text "Report issue"

View file

@ -1,10 +1,11 @@
import strformat, times, options, json, tables, future
import strformat, times, options, json, tables, sugar
from dom import window, Location
include karax/prelude
import jester/patterns
import threadlist, postlist, karaxutils
import threadlist, postlist, header
import karaxutils
type
State = ref object
@ -24,83 +25,6 @@ proc onPopState(event: dom.Event) =
state.url = window.location
redraw()
proc genHeader(): VNode =
result = buildHtml(tdiv()):
header(id="main-navbar"):
tdiv(class="navbar container grid-xl"):
section(class="navbar-section"):
a(href=makeUri("/")):
img(src="images/crown.png", id="img-logo") # TODO: Customisation.
section(class="navbar-section"):
tdiv(class="input-group input-inline"):
input(class="search-input input-sm", `type`="text", placeholder="search")
a(href="#signup-modal", id="signup-btn"):
button(class="btn btn-primary btn-sm"):
italic(class="fas fa-user-plus")
text " Sign up"
a(href="#login-modal", id="login-btn"):
button(class="btn btn-primary btn-sm"):
italic(class="fas fa-sign-in-alt")
text " Log in"
# Modals
tdiv(class="modal modal-sm", id="login-modal"):
a(href="#", class="modal-overlay", "aria-label"="close")
tdiv(class="modal-container"):
tdiv(class="modal-header"):
a(href="#", class="btn btn-clear float-right", "aria-label"="close")
tdiv(class="modal-title h5"):
text "Log in"
tdiv(class="modal-body"):
tdiv(class="content"):
form():
tdiv(class="form-group"):
label(class="form-label", `for`="username"):
text "Username"
input(class="form-input", `type`="text", id="username")
tdiv(class="form-group"):
label(class="form-label", `for`="password"):
text "Password"
input(class="form-input", `type`="password", id="password")
button(class="btn btn-link"):
text "Reset your password"
tdiv(class="modal-footer"):
button(class="btn btn-primary"):
text "Log in"
a(href="#signup-modal"):
button(class="btn"):
text "Create account"
tdiv(class="modal", id="signup-modal"):
a(href="#", class="modal-overlay", "aria-label"="close")
tdiv(class="modal-container"):
tdiv(class="modal-header"):
a(href="#", class="btn btn-clear float-right", "aria-label"="close")
tdiv(class="modal-title h5"):
text "Create a new account"
tdiv(class="modal-body"):
tdiv(class="content"):
form():
tdiv(class="form-group"):
label(class="form-label", `for`="email"):
text "Email"
input(class="form-input", `type`="text", id="email")
tdiv(class="form-group"):
label(class="form-label", `for`="username"):
text "Username"
input(class="form-input", `type`="text", id="username")
tdiv(class="form-group"):
label(class="form-label", `for`="password"):
text "Password"
input(class="form-input", `type`="password", id="password")
tdiv(class="modal-footer"):
button(class="btn btn-primary"):
text "Create account"
a(href="#login-modal"):
button(class="btn"):
text "Log in"
const appName = "/karax"
type Params = Table[string, string]
type
@ -118,7 +42,7 @@ proc route(routes: openarray[Route]): VNode =
proc render(): VNode =
result = buildHtml(tdiv()):
genHeader()
renderHeader()
route([
r("/t/@id?",
(params: Params) =>

157
redesign/header.nim Normal file
View file

@ -0,0 +1,157 @@
import options, times, httpcore, json, sugar
import threadlist
type
UserStatus* = object
user*: Option[User]
when defined(js):
include karax/prelude
import karax / [kajax]
import karaxutils
from dom import setTimeout, window, document, getElementById
type
State = ref object
data: Option[UserStatus]
loading: bool
status: HttpCode
lastUpdate: Time
proc newState(): State =
State(
data: none[UserStatus](),
loading: false,
status: Http200
)
var
state = newState()
proc getStatus
proc onStatus(httpStatus: int, response: kstring) =
state.loading = false
state.status = httpStatus.HttpCode
if state.status != Http200: return
let parsed = parseJson($response)
state.data = some(to(parsed, UserStatus))
state.lastUpdate = getTime()
proc getStatus =
if state.loading: return
let diff = getTime() - state.lastUpdate
if diff.minutes < 5:
return
state.loading = true
let uri = makeUri("status.json")
ajaxGet(uri, @[], onStatus)
proc onLogInPost(httpStatus: int, response: kstring) =
kout(response)
proc onLogInClick(ev: Event, n: VNode) =
let uri = makeUri("login")
let form = document.getElementById("login-form")
# TODO: This is a hack, karax should support this.
let formData = newFormData(form)
kout(formData.get("username"))
ajaxPost(uri, @[], cast[cstring](formData), onLogInPost)
proc genLoginModal(): VNode =
result = buildHtml():
tdiv(class="modal modal-sm", id="login-modal"):
a(href="#", class="modal-overlay", "aria-label"="close")
tdiv(class="modal-container"):
tdiv(class="modal-header"):
a(href="#", class="btn btn-clear float-right", "aria-label"="close")
tdiv(class="modal-title h5"):
text "Log in"
tdiv(class="modal-body"):
tdiv(class="content"):
form(id="login-form"):
tdiv(class="form-group"):
label(class="form-label", `for`="username"):
text "Username"
input(class="form-input", `type`="text", name="username")
tdiv(class="form-group"):
label(class="form-label", `for`="password"):
text "Password"
input(class="form-input", `type`="password", name="password")
a(href="#reset-password-modal"):
text "Reset your password"
tdiv(class="modal-footer"):
button(class="btn btn-primary", onClick=onLogInClick):
text "Log in"
a(href="#signup-modal"):
button(class="btn"):
text "Create account"
proc genSignUpModal(): VNode =
result = buildHtml():
tdiv(class="modal", id="signup-modal"):
a(href="#", class="modal-overlay", "aria-label"="close")
tdiv(class="modal-container"):
tdiv(class="modal-header"):
a(href="#", class="btn btn-clear float-right", "aria-label"="close")
tdiv(class="modal-title h5"):
text "Create a new account"
tdiv(class="modal-body"):
tdiv(class="content"):
form():
tdiv(class="form-group"):
label(class="form-label", `for`="email"):
text "Email"
input(class="form-input", `type`="text", name="email")
tdiv(class="form-group"):
label(class="form-label", `for`="regusername"):
text "Username"
input(class="form-input", `type`="text", name="username")
tdiv(class="form-group"):
label(class="form-label", `for`="regpassword"):
text "Password"
input(class="form-input", `type`="password", name="password")
tdiv(class="modal-footer"):
button(class="btn btn-primary"):
text "Create account"
a(href="#login-modal"):
button(class="btn"):
text "Log in"
proc renderHeader*(): VNode =
if state.data.isNone:
getStatus()
let user = state.data.map(x => x.user).flatten
result = buildHtml(tdiv()): # TODO: Why do some buildHtml's need this?
header(id="main-navbar"):
tdiv(class="navbar container grid-xl"):
section(class="navbar-section"):
a(href=makeUri("/")):
img(src="images/crown.png", id="img-logo") # TODO: Customisation.
section(class="navbar-section"):
tdiv(class="input-group input-inline"):
input(class="search-input input-sm", `type`="text", placeholder="search")
if state.loading:
tdiv(class="loading")
elif user.isNone:
a(href="#signup-modal", id="signup-btn"):
button(class="btn btn-primary btn-sm"):
italic(class="fas fa-user-plus")
text " Sign up"
a(href="#login-modal", id="login-btn"):
button(class="btn btn-primary btn-sm"):
italic(class="fas fa-sign-in-alt")
text " Log in"
else:
render(user.get(), "avatar")
# Modals
genLoginModal()
genSignUpModal()

View file

@ -48,4 +48,12 @@ proc anchorCB*(e: kdom.Event, n: VNode) = # TODO: Why does this need disamb?
dom.pushState(dom.window.history, 5, cstring"Thread", url)
# Fire the popState event.
dom.window.dispatchEvent(newEvent("popstate"))
dom.window.dispatchEvent(newEvent("popstate"))
type
FormData* = ref object
proc newFormData*(form: dom.Element): FormData
{.importcpp: "new FormData(@)", constructor.}
proc get*(form: FormData, key: cstring): cstring
{.importcpp: "#.get(@)".}

View file

@ -1,5 +1,5 @@
import asyncdispatch, smtp, strutils, json, os, rst, rstgen, xmltree, strtabs,
htmlparser, streams, parseutils
htmlparser, streams, parseutils, options
from times import getTime, getGMTime, format
proc parseInt*(s: string, value: var int, validRange: Slice[int]) {.
@ -20,6 +20,11 @@ proc getInt*(s: string, default = 0): int =
result = default
parseInt(s, result, 0..1_000_000_000)
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