Implements delete button fully in frontend and backend for posts and thread.

This commit is contained in:
Dominik Picheta 2018-05-18 19:54:33 +01:00
commit 41a6790fe8
3 changed files with 224 additions and 15 deletions

View file

@ -953,6 +953,20 @@ proc selectLikes(postId: int): seq[User] =
for row in getAllRows(db, likeQuery, $postId):
result.add(selectUser(row))
proc selectThreadAuthor(threadId: int): User =
const authorQuery =
sql"""
select name, email, strftime('%s', lastOnline), status
from person where id in (
select author from post
where thread = ?
order by id
limit 1
)
"""
return selectUser(getRow(db, authorQuery, threadId))
proc selectThread(threadRow: seq[string]): Thread =
const postsQuery =
sql"""select count(*), strftime('%s', creation) from post
@ -964,16 +978,6 @@ proc selectThread(threadRow: seq[string]): Thread =
from person u, post p where p.author = u.id and p.thread = ?
group by name order by count(*) desc limit 5;
"""
const authorQuery =
sql"""
select name, email, strftime('%s', lastOnline), status
from person where id in (
select author from post
where thread = ?
order by id
limit 1
)
"""
let posts = getRow(db, postsQuery, threadRow[0])
@ -1000,7 +1004,7 @@ proc selectThread(threadRow: seq[string]): Thread =
thread.users.add(selectUser(user))
# Grab the author.
thread.author = selectUser(getRow(db, authorQuery, thread.id))
thread.author = selectThreadAuthor(thread.id)
return thread
@ -1220,6 +1224,29 @@ proc executeUnlike(c: TForumData, postId: int) =
# Delete the like.
exec(db, crud(crDelete, "like"), likeId)
proc executeDeletePost(c: TForumData, postId: int) =
# Verify that this post belongs to the user.
const postQuery = sql"""
select p.id from post p
where p.author = ? and p.id = ?
"""
let id = getValue(db, postQuery, postId, c.username)
if id.len == 0 and c.rank < Admin:
raise newForumError("You cannot delete this post")
# Set the `isDeleted` flag.
exec(db, crud(crUpdate, "post", "isDeleted"), "1", postId)
proc executeDeleteThread(c: TForumData, threadId: int) =
# Verify that this thread belongs to the user.
let author = selectThreadAuthor(threadId)
if author.name != c.username and c.rank < Admin:
raise newForumError("You cannot delete this thread")
# Set the `isDeleted` flag.
exec(db, crud(crUpdate, "thread", "isDeleted"), "1", threadId)
initialise()
routes:
@ -1645,6 +1672,33 @@ routes:
except ForumError as exc:
resp Http400, $(%exc.data), "application/json"
post re"/delete(Post|Thread)":
createTFD()
if not c.loggedIn():
let err = PostError(
errorFields: @[],
message: "Not logged in."
)
resp Http401, $(%err), "application/json"
let formData = request.formData
cond "id" in formData
let id = getInt(formData["id"].body, -1)
cond id != -1
try:
case request.path
of "/deletePost":
executeDeletePost(c, id)
of "/deleteThread":
executeDeleteThread(c, id)
else:
assert false
resp Http200, "{}", "application/json"
except ForumError as exc:
resp Http400, $(%exc.data), "application/json"
get "/t/@id":
cond "id" in request.params

132
frontend/delete.nim Normal file
View file

@ -0,0 +1,132 @@
when defined(js):
import sugar, httpcore, options, json
import dom except Event
include karax/prelude
import karax / [kajax, kdom]
import error, post, threadlist, user
import karaxutils
type
DeleteKind* = enum
DeleteUser, DeletePost, DeleteThread
DeleteModal* = ref object
shown: bool
loading: bool
onDeletePost: proc (post: Post)
onDeleteThread: proc (thread: Thread)
onDeleteUser: proc (user: User)
error: Option[PostError]
case kind: DeleteKind
of DeleteUser:
user: User
of DeletePost:
post: Post
of DeleteThread:
thread: Thread
proc onDeletePost(httpStatus: int, response: kstring, state: DeleteModal) =
postFinished:
state.shown = false
case state.kind
of DeleteUser:
state.onDeleteUser(state.user)
of DeletePost:
state.onDeletePost(state.post)
of DeleteThread:
state.onDeleteThread(state.thread)
proc onDelete(ev: Event, n: VNode, state: DeleteModal) =
state.loading = true
state.error = none[PostError]()
let uri =
case state.kind
of DeleteUser:
makeUri("/deleteUser")
of DeleteThread:
makeUri("/deleteThread")
of DeletePost:
makeUri("/deletePost")
# TODO: This is a hack, karax should support this.
let formData = newFormData()
case state.kind
of DeleteUser:
formData.append("username", state.user.name)
of DeletePost:
formData.append("id", $state.post.id)
of DeleteThread:
formData.append("id", $state.thread.id)
ajaxPost(uri, @[], cast[cstring](formData),
(s: int, r: kstring) => onDeletePost(s, r, state))
proc onClose(ev: Event, n: VNode, state: DeleteModal) =
state.shown = false
ev.preventDefault()
proc newDeleteModal*(
onDeletePost: proc (post: Post),
onDeleteThread: proc (thread: Thread),
onDeleteUser: proc (user: User),
): DeleteModal =
DeleteModal(
shown: false,
onDeletePost: onDeletePost,
onDeleteThread: onDeleteThread,
onDeleteUser: onDeleteUser,
)
proc show*(state: DeleteModal, thing: User | Post | Thread) =
state.shown = true
state.error = none[PostError]()
when thing is User:
state.kind = DeleteUser
state.user = thing
when thing is Post:
state.kind = DeletePost
state.post = thing
when thing is Thread:
state.kind = DeleteThread
state.thread = thing
proc render*(state: DeleteModal): VNode =
result = buildHtml():
tdiv(class=class({"active": state.shown}, "modal modal-sm"),
id="login-modal"):
a(href="", class="modal-overlay", "aria-label"="close",
onClick=(ev: Event, n: VNode) => onClose(ev, n, state))
tdiv(class="modal-container"):
tdiv(class="modal-header"):
a(href="", class="btn btn-clear float-right",
"aria-label"="close",
onClick=(ev: Event, n: VNode) => onClose(ev, n, state))
tdiv(class="modal-title h5"):
text "Delete"
tdiv(class="modal-body"):
tdiv(class="content"):
p():
text "Are you sure you want to delete this "
case state.kind
of DeleteUser:
text "user account?"
of DeleteThread:
text "thread?"
of DeletePost:
text "post?"
tdiv(class="modal-footer"):
if state.error.isSome():
p(class="text-error"):
text state.error.get().message
button(class=class(
{"loading": state.loading},
"btn btn-primary"
),
onClick=(ev: Event, n: VNode) => onDelete(ev, n, state)):
italic(class="fas fa-trash-alt")
text " Delete"
button(class="btn",
onClick=(ev: Event, n: VNode) => (state.shown = false)):
text "Cancel"

View file

@ -1,5 +1,6 @@
import options, json, times, httpcore, strformat, sugar, math, strutils
import sequtils
import threadlist, category, post, user
type
@ -17,7 +18,7 @@ when defined(js):
include karax/prelude
import karax / [vstyles, kajax, kdom]
import karaxutils, error, replybox, editbox, postbutton
import karaxutils, error, replybox, editbox, postbutton, delete
type
State = ref object
@ -29,10 +30,13 @@ when defined(js):
editing: Option[Post] ## If in edit mode, this contains the post.
editBox: EditBox
likeButton: LikeButton
deleteModal: DeleteModal
proc onReplyPosted(id: int)
proc onEditPosted(id: int, content: string, subject: Option[string])
proc onEditCancelled()
proc onDeletePost(post: Post)
proc onDeleteThread(thread: Thread)
proc newState(): State =
State(
list: none[PostList](),
@ -41,7 +45,8 @@ when defined(js):
replyingTo: none[Post](),
replyBox: newReplyBox(onReplyPosted),
editBox: newEditBox(onEditPosted, onEditCancelled),
likeButton: newLikeButton()
likeButton: newLikeButton(),
deleteModal: newDeleteModal(onDeletePost, onDeleteThread, nil)
)
var
@ -137,6 +142,21 @@ when defined(js):
# TODO: Ensure the edit box is as big as its content. Auto resize the
# text area.
proc onDeletePost(post: Post) =
state.list.get().posts.keepIf(
x => x.id != post.id
)
proc onDeleteThread(thread: Thread) =
window.location.href = makeUri("/")
proc onDeleteClick(e: Event, n: VNode, p: Post) =
let list = state.list.get()
if list.posts[0].id == p.id:
state.deleteModal.show(list.thread)
else:
state.deleteModal.show(p)
proc onLoadMore(ev: Event, n: VNode, start: int, post: Post) =
loadMore(start, post.moreBefore) # TODO: Don't load all!
@ -173,7 +193,8 @@ when defined(js):
onEditClick(e, n, post)):
button(class="btn"):
italic(class="far fa-edit")
tdiv(class="delete-button"):
tdiv(class="delete-button",
onClick=(e: Event, n: VNode) => onDeleteClick(e, n, post)):
button(class="btn"):
italic(class="far fa-trash-alt")
@ -338,4 +359,6 @@ when defined(js):
italic(class="fas fa-reply")
text " Reply"
render(state.replyBox, list.thread, state.replyingTo, false)
render(state.replyBox, list.thread, state.replyingTo, false)
render(state.deleteModal)