From c5453ed50a241dc1bb705ad52873234910c6b4cc Mon Sep 17 00:00:00 2001 From: Dominic Ferrando Date: Wed, 23 Apr 2025 17:10:59 -0400 Subject: [PATCH] Add ability to delete posts --- internal/database/handlers.go | 58 +++++++++++++++++++++++++++++++---- internal/util/posts.go | 2 +- web/main.go | 31 +++++++++++++++---- web/views/board.templ | 2 +- web/views/thread.templ | 51 +++++++++++++++++++++++++----- 5 files changed, 123 insertions(+), 21 deletions(-) diff --git a/internal/database/handlers.go b/internal/database/handlers.go index 7417560..62af2ea 100644 --- a/internal/database/handlers.go +++ b/internal/database/handlers.go @@ -242,14 +242,18 @@ func DeleteThread(db Queryer, threadId int) error { return err } - if err := os.Remove(path.Join(util.POST_MEDIA_FULL_PATH, pruneMediaFullPath)); err != nil { - if !errors.Is(err, fs.ErrNotExist) { - return err + if pruneMediaFullPath != "" { + if err := os.Remove(path.Join(util.POST_MEDIA_FULL_PATH, pruneMediaFullPath)); err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return err + } } } - if err := os.Remove(path.Join(util.POST_MEDIA_THUMB_PATH, pruneMediaThumbPath)); err != nil { - if !errors.Is(err, fs.ErrNotExist) { - return err + if pruneMediaThumbPath != "" { + if err := os.Remove(path.Join(util.POST_MEDIA_THUMB_PATH, pruneMediaThumbPath)); err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return err + } } } } @@ -265,6 +269,48 @@ func DeleteThread(db Queryer, threadId int) error { return nil } +func DeletePost(db *sql.DB, postId int) error { + // cleanup images + row := db.QueryRow(` + SELECT media_path, thumb_path + FROM posts + WHERE id = ?`, postId) + + var ( + pruneMediaThumbPath string + pruneMediaFullPath string + ) + if err := row.Scan(&pruneMediaFullPath, &pruneMediaThumbPath); err != nil { + return err + } + + if pruneMediaFullPath != "" { + if err := os.Remove(path.Join(util.POST_MEDIA_FULL_PATH, pruneMediaFullPath)); err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return err + } + } + } + + if pruneMediaThumbPath != "" { + if err := os.Remove(path.Join(util.POST_MEDIA_THUMB_PATH, pruneMediaThumbPath)); err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return err + } + } + } + + // delete post + _, err := db.Exec(` + DELETE FROM posts + WHERE id = ?`, postId) + if err != nil { + return err + } + + return nil +} + func GetAdmin(db *sql.DB, username string) (Admin, error) { row := db.QueryRow(` SELECT username, password diff --git a/internal/util/posts.go b/internal/util/posts.go index 5b5f800..25424a0 100644 --- a/internal/util/posts.go +++ b/internal/util/posts.go @@ -30,7 +30,7 @@ const MAX_THREAD_COUNT = 50 const MAX_BODY_LEN = 3000 const MAX_SUBJECT_LEN = 50 -var SUPPORTED_IMAGE_MIME_TYPES = []string{"image/jpeg", "image/png", "image/gif", "image/webp"} +var SUPPORTED_IMAGE_MIME_TYPES = []string{"image/jpeg", "image/png", "image/gif"} var SUPPORTED_VIDEO_MIME_TYPES = []string{"video/webm", "video/mp4", "video/ogg"} type PostMediaType int64 diff --git a/web/main.go b/web/main.go index 7915b9f..ac9dbba 100644 --- a/web/main.go +++ b/web/main.go @@ -150,7 +150,9 @@ func main() { return } - views.Thread(board, thread, posts).Render(r.Context(), w) + views.Thread(board, thread, posts, views.ThreadContext{ + IsAdmin: isAdmin(r), + }).Render(r.Context(), w) }) // CREATE THREAD @@ -428,7 +430,9 @@ func main() { } // dont pass the op post. only replies - views.Posts(posts, thread).Render(r.Context(), w) + views.Posts(posts, thread, views.ThreadContext{ + IsAdmin: isAdmin(r), + }).Render(r.Context(), w) }) // ----------------- @@ -509,8 +513,7 @@ func main() { }) }) - r.Delete("/{slug}/threads/{threadId}", func(w http.ResponseWriter, r *http.Request) { - // slug := chi.URLParam(r, "slug") + r.Delete("/threads/{threadId}", func(w http.ResponseWriter, r *http.Request) { threadIdStr := chi.URLParam(r, "threadId") threadId, err := strconv.Atoi(threadIdStr) if err != nil { @@ -520,11 +523,27 @@ func main() { err = database.DeleteThread(db, threadId) if err != nil { - log.Println("DeleteThread: %v", err) - http.Error(w, "Error deleting thread: "+threadIdStr, http.StatusInternalServerError) + log.Println("DeleteThread: ", err) + http.Error(w, "Failed to delete thread: "+threadIdStr, http.StatusInternalServerError) + return } }) + r.Delete("/posts/{postId}", func(w http.ResponseWriter, r *http.Request) { + postIdStr := chi.URLParam(r, "postId") + postId, err := strconv.Atoi(postIdStr) + if err != nil { + http.Error(w, "Invalid post id", http.StatusBadRequest) + return + } + + err = database.DeletePost(db, postId) + if err != nil { + log.Println("DeletePost: ", err) + http.Error(w, "Failed to delete post: "+postIdStr, http.StatusInternalServerError) + return + } + }) }) // ----------------- diff --git a/web/views/board.templ b/web/views/board.templ index eae054e..44e21c4 100644 --- a/web/views/board.templ +++ b/web/views/board.templ @@ -105,7 +105,7 @@ templ ThreadsCatalog(previews []CatalogThreadPreview, catalogContext CatalogCont

{ elThreadId }

+ + +} + +templ PostOriginal(post database.Post, thread database.Thread) {
} -templ PostReply(post database.Post) { +templ PostReply(post database.Post, threadContext ThreadContext) { + {{ elPostId := fmt.Sprintf("post-%d", post.Id) }}
+ if threadContext.IsAdmin { + + M + + } { post.CreatedAt.Format("15:04") } @@ -99,20 +131,25 @@ templ PostReply(post database.Post) {

@templ.Raw(util.EnrichPost(post.Body))

+ @PostAdminDialog(post)
} -templ Posts(posts []database.Post, thread database.Thread) { - @PostOriginal(posts[0], &thread) +templ Posts(posts []database.Post, thread database.Thread, threadContext ThreadContext) { + @PostOriginal(posts[0], thread)
for _, post := range (posts[1:]) { - @PostReply(post) + @PostReply(post, threadContext)
}
} -templ Thread(board database.Board, thread database.Thread, posts []database.Post) { +type ThreadContext struct { + IsAdmin bool +} + +templ Thread(board database.Board, thread database.Thread, posts []database.Post, threadContext ThreadContext) { @shared.Layout() { @BoardHeader(board) @@ -131,7 +168,7 @@ templ Thread(board database.Board, thread database.Thread, posts []database.Post hx-trigger="refreshPosts from:body" _="on htmx:afterSwap call insertHeaderReplies()" > - @Posts(posts, thread) + @Posts(posts, thread, threadContext)
} }