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 import
os, strutils, times, md5, strtabs, cgi, math, db_sqlite, os, strutils, times, md5, strtabs, math, db_sqlite,
scgi, jester, asyncdispatch, asyncnet, cache, sequtils, scgi, jester, asyncdispatch, asyncnet, cache, sequtils,
parseutils, utils, random, rst, ranks, recaptcha, json, re parseutils, utils, random, rst, ranks, recaptcha, json, re
import cgi except setCookie
import options
import redesign/threadlist except User import redesign/threadlist except User
import redesign/[category, postlist] import redesign/[category, postlist, error, header]
when not defined(windows): when not defined(windows):
import bcrypt # TODO import bcrypt # TODO
@ -1153,6 +1155,34 @@ routes:
resp $(%list), "application/json" 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/(.+)?": get re"/karax/(.+)?":
resp readFile("redesign/karax.html") resp readFile("redesign/karax.html")

View file

@ -1,17 +1,22 @@
include karax/prelude type
import karax / [vstyles, kajax, kdom] 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 = proc renderError*(message: string): VNode =
result = buildHtml(): result = buildHtml():
tdiv(class="empty error"): tdiv(class="empty error"):
tdiv(class="empty icon"): tdiv(class="empty icon"):
italic(class="fas fa-bug fa-5x") italic(class="fas fa-bug fa-5x")
p(class="empty-title h5"): p(class="empty-title h5"):
text message text message
p(class="empty-subtitle"): p(class="empty-subtitle"):
text "Please report this issue to us so we can fix it!" text "Please report this issue to us so we can fix it!"
tdiv(class="empty-action"): tdiv(class="empty-action"):
a(href="https://github.com/nim-lang/nimforum/issues", target="_blank"): a(href="https://github.com/nim-lang/nimforum/issues", target="_blank"):
button(class="btn btn-primary"): button(class="btn btn-primary"):
text "Report issue" 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 from dom import window, Location
include karax/prelude include karax/prelude
import jester/patterns import jester/patterns
import threadlist, postlist, karaxutils import threadlist, postlist, header
import karaxutils
type type
State = ref object State = ref object
@ -24,83 +25,6 @@ proc onPopState(event: dom.Event) =
state.url = window.location state.url = window.location
redraw() 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" const appName = "/karax"
type Params = Table[string, string] type Params = Table[string, string]
type type
@ -118,7 +42,7 @@ proc route(routes: openarray[Route]): VNode =
proc render(): VNode = proc render(): VNode =
result = buildHtml(tdiv()): result = buildHtml(tdiv()):
genHeader() renderHeader()
route([ route([
r("/t/@id?", r("/t/@id?",
(params: Params) => (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

@ -49,3 +49,11 @@ proc anchorCB*(e: kdom.Event, n: VNode) = # TODO: Why does this need disamb?
# Fire the popState event. # 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, import asyncdispatch, smtp, strutils, json, os, rst, rstgen, xmltree, strtabs,
htmlparser, streams, parseutils htmlparser, streams, parseutils, options
from times import getTime, getGMTime, format from times import getTime, getGMTime, format
proc parseInt*(s: string, value: var int, validRange: Slice[int]) {. proc parseInt*(s: string, value: var int, validRange: Slice[int]) {.
@ -20,6 +20,11 @@ proc getInt*(s: string, default = 0): int =
result = default result = default
parseInt(s, result, 0..1_000_000_000) 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 type
Config* = object Config* = object
smtpAddress: string smtpAddress: string