Add ability to delete threads. Fix thread deletion logic

This commit is contained in:
Dominic Ferrando
2025-04-23 15:47:15 -04:00
parent a28cb483fa
commit e341c5d8c9
4 changed files with 57 additions and 17 deletions
+14 -9
View File
@@ -2,6 +2,8 @@ package database
import ( import (
"database/sql" "database/sql"
"errors"
"io/fs"
"os" "os"
"path" "path"
@@ -223,7 +225,7 @@ func PutPost(db Queryer, boardSlug string, threadId int, body string, mediaPath
func DeleteThread(db Queryer, threadId int) error { func DeleteThread(db Queryer, threadId int) error {
// cleanup images // cleanup images
rows, err := db.Query(` rows, err := db.Query(`
SELECT media_path SELECT media_path, thumb_path
FROM posts FROM posts
WHERE thread_id = ?`, threadId) WHERE thread_id = ?`, threadId)
if err != nil { if err != nil {
@@ -231,23 +233,26 @@ func DeleteThread(db Queryer, threadId int) error {
} }
defer rows.Close() defer rows.Close()
var pruneMediaPaths []string
for rows.Next() { for rows.Next() {
var pruneMediaPath string var (
if err := rows.Scan(&pruneMediaPath); err != nil { pruneMediaThumbPath string
pruneMediaFullPath string
)
if err := rows.Scan(&pruneMediaFullPath, &pruneMediaThumbPath); err != nil {
return err return err
} }
pruneMediaPaths = append(pruneMediaPaths, pruneMediaPath)
}
for _, pruneMediaPath := range pruneMediaPaths { if err := os.Remove(path.Join(util.POST_MEDIA_FULL_PATH, pruneMediaFullPath)); err != nil {
if err := os.Remove(path.Join(util.POST_MEDIA_FULL_PATH, pruneMediaPath)); err != nil && !os.IsNotExist(err) { if !errors.Is(err, fs.ErrNotExist) {
return err return err
} }
if err := os.Remove(path.Join(util.POST_MEDIA_THUMB_PATH, pruneMediaPath)); err != nil && !os.IsNotExist(err) { }
if err := os.Remove(path.Join(util.POST_MEDIA_THUMB_PATH, pruneMediaThumbPath)); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return err return err
} }
} }
}
// delete thread // delete thread
_, err = db.Exec(` _, err = db.Exec(`
+24 -3
View File
@@ -154,7 +154,7 @@ func main() {
// check cooldown // check cooldown
timeRemaining := util.GetRemainingCooldown(ip, util.ThreadCooldowns, util.THREAD_COOLDOWN) timeRemaining := util.GetRemainingCooldown(ip, util.ThreadCooldowns, util.THREAD_COOLDOWN)
if timeRemaining > 0 { if timeRemaining > 0 && !isAdmin(r) {
response := fmt.Sprintf("Please wait %.0f seconds", timeRemaining.Seconds()) response := fmt.Sprintf("Please wait %.0f seconds", timeRemaining.Seconds())
io.Copy(io.Discard, r.Body) io.Copy(io.Discard, r.Body)
http.Error(w, response, http.StatusTooManyRequests) http.Error(w, response, http.StatusTooManyRequests)
@@ -247,7 +247,7 @@ func main() {
// check cooldown // check cooldown
timeRemaining := util.GetRemainingCooldown(ip, util.PostCooldowns, util.POST_COOLDOWN) timeRemaining := util.GetRemainingCooldown(ip, util.PostCooldowns, util.POST_COOLDOWN)
if timeRemaining > 0 { if timeRemaining > 0 && !isAdmin(r) {
response := fmt.Sprintf("Please wait %.0f seconds", timeRemaining.Seconds()) response := fmt.Sprintf("Please wait %.0f seconds", timeRemaining.Seconds())
io.Copy(io.Discard, r.Body) io.Copy(io.Discard, r.Body)
http.Error(w, response, http.StatusTooManyRequests) http.Error(w, response, http.StatusTooManyRequests)
@@ -377,6 +377,7 @@ func main() {
previews = append(previews, views.CatalogThreadPreview{ previews = append(previews, views.CatalogThreadPreview{
Subject: thread.Subject, Subject: thread.Subject,
Body: op.Body, Body: op.Body,
ThreadId: thread.Id,
ThreadURL: fmt.Sprintf("/%s/threads/%d", slug, thread.Id), ThreadURL: fmt.Sprintf("/%s/threads/%d", slug, thread.Id),
ThumbPath: op.ThumbPath, ThumbPath: op.ThumbPath,
ReplyCount: len(posts), ReplyCount: len(posts),
@@ -384,7 +385,10 @@ func main() {
}) })
} }
views.ThreadsCatalog(previews, isAdmin(r)).Render(r.Context(), w) views.ThreadsCatalog(previews, views.CatalogContext{
IsAdmin: isAdmin(r),
BoardSlug: slug,
}).Render(r.Context(), w)
}) })
// THREAD POSTS // THREAD POSTS
@@ -498,6 +502,23 @@ func main() {
Path: "/", Path: "/",
}) })
}) })
r.Delete("/{slug}/threads/{threadId}", func(w http.ResponseWriter, r *http.Request) {
// slug := chi.URLParam(r, "slug")
threadIdStr := chi.URLParam(r, "threadId")
threadId, err := strconv.Atoi(threadIdStr)
if err != nil {
http.Error(w, "Invalid thread id", http.StatusBadRequest)
return
}
err = database.DeleteThread(db, threadId)
if err != nil {
log.Println("DeleteThread: %v", err)
http.Error(w, "Error deleting thread: "+threadIdStr, http.StatusInternalServerError)
}
})
}) })
// ----------------- // -----------------
+4
View File
@@ -95,3 +95,7 @@ function applyCatalogSearch() {
} }
} }
} }
function currentBoardSlug() {
return window.location.pathname.split("/")[1] || "";
}
+13 -3
View File
@@ -52,13 +52,19 @@ templ Board(board database.Board, isAdmin bool) {
type CatalogThreadPreview struct { type CatalogThreadPreview struct {
Subject string Subject string
Body string Body string
ThreadId int
ThreadURL string ThreadURL string
ThumbPath string ThumbPath string
ReplyCount int ReplyCount int
IpCount int IpCount int
} }
templ ThreadsCatalog(previews []CatalogThreadPreview, isAdmin bool) { type CatalogContext struct {
IsAdmin bool
BoardSlug string
}
templ ThreadsCatalog(previews []CatalogThreadPreview, catalogContext CatalogContext) {
<div id="catalog"> <div id="catalog">
for _, preview := range previews { for _, preview := range previews {
{{ replyCount := strconv.FormatInt(int64(preview.ReplyCount), 10) }} {{ replyCount := strconv.FormatInt(int64(preview.ReplyCount), 10) }}
@@ -79,8 +85,12 @@ templ ThreadsCatalog(previews []CatalogThreadPreview, isAdmin bool) {
<p> <p>
@templ.Raw(util.EnrichPost(preview.Body)) @templ.Raw(util.EnrichPost(preview.Body))
</p> </p>
if isAdmin { if catalogContext.IsAdmin {
<button>X</button> <button
hx-delete={ fmt.Sprintf("/admin/%s/threads/%d", catalogContext.BoardSlug, preview.ThreadId) }
hx-swap="none"
_="on htmx:afterRequest trigger refreshPosts on body"
>X</button>
} }
</div> </div>
} }