Moderation entfernt, JSON-API unter /api
- Moderationsseite und Melde-Funktion (report) komplett ausgebaut; folgt als eigenständiges Projekt - alle API-Endpunkte unter /api, dort ausschließlich JSON: auch 404, 405, Rate-Limit (429) und Panics (500) - Fehler nur über HTTP-Status; stille DB-Fehler in stats, logout und Vote-Zählern liefern jetzt 500 statt Nullen - /auth/headerbar entfernt (Frontend nutzt /api/user/info) - Frontend auf /api und statusbasierte Auswertung umgestellt - notes/api.md neu als Referenz der aktuellen API Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TiXsPUqw7oeomZ8wZrQW5q
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2714fdb1a5
commit
030a5bd943
@@ -9,7 +9,6 @@ import (
|
||||
"image/jpeg"
|
||||
_ "image/png"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
@@ -480,48 +479,26 @@ func handleBump(w http.ResponseWriter, r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// voteTally liefert die Zähler und die eigene Auswahl (selected) für einen Beitrag.
|
||||
// Ist uid == 0 (nicht eingeloggt), ist selected immer "none".
|
||||
func voteTally(pid, uid int64) (left, right int, selected string) {
|
||||
selected = "none"
|
||||
if uid != 0 {
|
||||
var cur string
|
||||
if err := db.QueryRow(`SELECT mode FROM vote WHERE uid = $1 AND pid = $2`, uid, pid).Scan(&cur); err == nil {
|
||||
selected = cur
|
||||
}
|
||||
// writeTally schreibt die Zähler und die eigene Auswahl (selected) für einen
|
||||
// Beitrag. Ist uid == 0 (nicht eingeloggt), ist selected immer "none".
|
||||
func writeTally(w http.ResponseWriter, pid, uid int64) error {
|
||||
var left, right int64
|
||||
var selected string
|
||||
if err := db.QueryRow(
|
||||
`SELECT COUNT(*) FILTER (WHERE mode = 'left'),
|
||||
COUNT(*) FILTER (WHERE mode = 'right'),
|
||||
COALESCE(MAX(mode) FILTER (WHERE uid = $2), 'none')
|
||||
FROM vote WHERE pid = $1`, pid, uid).
|
||||
Scan(&left, &right, &selected); err != nil {
|
||||
return Internal(err)
|
||||
}
|
||||
// Beide Zähler in einem Query statt zwei COUNTs.
|
||||
rows, err := db.Query(`SELECT mode, COUNT(*) FROM vote WHERE pid = $1 GROUP BY mode`, pid)
|
||||
if err != nil {
|
||||
log.Printf("voteTally pid=%d: %v", pid, err)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var mode string
|
||||
var n int
|
||||
if err := rows.Scan(&mode, &n); err != nil {
|
||||
log.Printf("voteTally pid=%d: %v", pid, err)
|
||||
return
|
||||
}
|
||||
switch mode {
|
||||
case "left":
|
||||
left = n
|
||||
case "right":
|
||||
right = n
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func writeTally(w http.ResponseWriter, pid, uid int64) {
|
||||
left, right, selected := voteTally(pid, uid)
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"pid": pid,
|
||||
"left": left,
|
||||
"right": right,
|
||||
"selected": selected,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleVotes liest den Abstimmungsstand eines Beitrags (read-only, öffentlich).
|
||||
@@ -535,34 +512,7 @@ func handleVotes(w http.ResponseWriter, r *http.Request) error {
|
||||
if s, ok := getSession(r); ok {
|
||||
uid = s.UID
|
||||
}
|
||||
writeTally(w, pid, uid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleReport nimmt eine Meldung zu einem Beitrag entgegen (DSA-Melde-
|
||||
// verfahren). Bewusst ohne Auth -- jeder darf melden, weil es eine
|
||||
// Moderationshilfe ist. Die Melder-uid wird vorerst nicht gespeichert (immer
|
||||
// 0); nur der optionale Grund landet in der DB. Die Route ist per IP
|
||||
// rate-limited (siehe main.go), damit der offene Endpunkt kein Spam-Kanal wird.
|
||||
func handleReport(w http.ResponseWriter, r *http.Request) error {
|
||||
pid, err := pidParam(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Nur existierende Beiträge melden, sonst entstehen Waisen-Meldungen.
|
||||
if _, err := entryByPID(pid); err != nil {
|
||||
return errEntryNotFound
|
||||
}
|
||||
reason := truncateRunes(strings.TrimSpace(r.FormValue("reason")), 500)
|
||||
if _, err := db.Exec(
|
||||
`INSERT INTO report (pid, uid, reason, created_at, handled) VALUES ($1, 0, $2, $3, 0)`,
|
||||
pid, reason, time.Now().Unix(),
|
||||
); err != nil {
|
||||
return Internal(err)
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
|
||||
return nil
|
||||
return writeTally(w, pid, uid)
|
||||
}
|
||||
|
||||
// handleVote gibt die Stimme des angemeldeten Nutzers ab oder schaltet sie um:
|
||||
@@ -604,8 +554,7 @@ func handleVote(w http.ResponseWriter, r *http.Request) error {
|
||||
}
|
||||
}
|
||||
|
||||
writeTally(w, pid, uid)
|
||||
return nil
|
||||
return writeTally(w, pid, uid)
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
Reference in New Issue
Block a user