Implements delete button fully in frontend and backend for posts and thread.
This commit is contained in:
parent
dd9be8f639
commit
41a6790fe8
3 changed files with 224 additions and 15 deletions
76
forum.nim
76
forum.nim
|
|
@ -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
132
frontend/delete.nim
Normal 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"
|
||||
|
|
@ -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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue