Add ability to delete threads. Fix thread deletion logic
This commit is contained in:
@@ -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
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// -----------------
|
// -----------------
|
||||||
|
|||||||
@@ -95,3 +95,7 @@ function applyCatalogSearch() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function currentBoardSlug() {
|
||||||
|
return window.location.pathname.split("/")[1] || "";
|
||||||
|
}
|
||||||
|
|||||||
+13
-3
@@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user