Soft-Delete: Beiträge als [deleted] erhalten statt löschen
- entry.deleted; entrySelect LEFT JOIN + COALESCE(username)
- POST /entry/{pid}/delete: deleted=1, Inhalt/Bild/Autor geleert, Zeile bleibt
- Konto-Löschung soft-deletet die eigenen Beiträge (statt hartem DELETE),
damit fremde Antworten nicht verwaisen
- Frontend: [deleted]-Platzhalter im Feed, Löschen-Button auf Focus-Seite
- notes/migrations.md: ALTER TABLE deleted
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+69
-2
@@ -239,8 +239,11 @@ func createReply(t *testing.T, c *http.Client, srv *httptest.Server, content str
|
||||
}
|
||||
|
||||
type threadEntry struct {
|
||||
PID int64 `json:"pid"`
|
||||
ReplyCount int64 `json:"reply_count"`
|
||||
PID int64 `json:"pid"`
|
||||
ReplyCount int64 `json:"reply_count"`
|
||||
Deleted int64 `json:"deleted"`
|
||||
Content string `json:"content"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type threadView struct {
|
||||
@@ -315,6 +318,56 @@ func TestThreading(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSoftDelete(t *testing.T) {
|
||||
srv := newTestServer(t)
|
||||
alice := registerAndLogin(t, srv, "alice")
|
||||
bob := registerAndLogin(t, srv, "bob")
|
||||
|
||||
root := createEntry(t, alice, srv, "wurzel")
|
||||
reply := createReply(t, alice, srv, "antwort", root)
|
||||
|
||||
delURL := fmt.Sprintf("%s/entry/%d/delete", srv.URL, root)
|
||||
|
||||
// Fremder darf nicht löschen.
|
||||
resp := postForm(t, bob, delURL, url.Values{})
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
t.Fatalf("fremd-delete: erwartet 403, bekam %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Eigentümer löscht den Root weich.
|
||||
resp = postForm(t, alice, delURL, url.Values{})
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("delete: erwartet 200, bekam %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Root bleibt als [deleted]-Platzhalter; die Antwort verwaist nicht.
|
||||
tv := getThread(t, srv, root)
|
||||
if tv.Entry.Deleted != 1 || tv.Entry.Content != "" || tv.Entry.Username != "" {
|
||||
t.Fatalf("deleted entry nicht anonymisiert: %+v", tv.Entry)
|
||||
}
|
||||
if len(tv.Replies) != 1 || tv.Replies[0].PID != reply {
|
||||
t.Fatalf("antwort verwaist: %+v", tv.Replies)
|
||||
}
|
||||
|
||||
// Der gelöschte Root erscheint weiterhin im Hauptfeed (Thread bleibt erreichbar).
|
||||
feedResp, _ := http.Get(srv.URL + "/entry/feed/0")
|
||||
var feed []threadEntry
|
||||
json.NewDecoder(feedResp.Body).Decode(&feed)
|
||||
feedResp.Body.Close()
|
||||
if len(feed) != 1 || feed[0].PID != root || feed[0].Deleted != 1 {
|
||||
t.Fatalf("hauptfeed nach delete: %+v", feed)
|
||||
}
|
||||
|
||||
// Nicht existierender Beitrag -> 404.
|
||||
resp = postForm(t, alice, srv.URL+"/entry/999999/delete", url.Values{})
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Fatalf("delete unbekannt: erwartet 404, bekam %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenameUser(t *testing.T) {
|
||||
srv := newTestServer(t)
|
||||
alice := registerAndLogin(t, srv, "alice")
|
||||
@@ -364,6 +417,11 @@ func TestRenameUser(t *testing.T) {
|
||||
func TestDeleteUser(t *testing.T) {
|
||||
srv := newTestServer(t)
|
||||
alice := registerAndLogin(t, srv, "alice")
|
||||
bob := registerAndLogin(t, srv, "bob")
|
||||
|
||||
// alice schreibt einen Beitrag, bob antwortet darauf.
|
||||
root := createEntry(t, alice, srv, "alices beitrag")
|
||||
reply := createReply(t, bob, srv, "bobs antwort", root)
|
||||
|
||||
// Falsches Passwort -> 403.
|
||||
resp := postForm(t, alice, srv.URL+"/user/delete", url.Values{"pass1": {"falsch"}})
|
||||
@@ -388,4 +446,13 @@ func TestDeleteUser(t *testing.T) {
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
t.Fatalf("Login nach Löschen: erwartet 401, bekam %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// alices Beitrag bleibt als [deleted]-Platzhalter; bobs Antwort verwaist nicht.
|
||||
tv := getThread(t, srv, root)
|
||||
if tv.Entry.Deleted != 1 || tv.Entry.Content != "" || tv.Entry.Username != "" {
|
||||
t.Fatalf("beitrag nach konto-löschung nicht soft-deleted: %+v", tv.Entry)
|
||||
}
|
||||
if len(tv.Replies) != 1 || tv.Replies[0].PID != reply {
|
||||
t.Fatalf("bobs antwort verwaist: %+v", tv.Replies)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user