docs: Implementierungsplan für Reorder-Validierung
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
30325dc1b6
commit
b463aeeae1
1 changed files with 252 additions and 0 deletions
252
docs/superpowers/plans/2026-03-26-reorder-validation.md
Normal file
252
docs/superpowers/plans/2026-03-26-reorder-validation.md
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
# Reorder-Validierung Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** `PlaylistStore.Reorder` soll mit `ErrReorderMismatch` abbrechen, wenn die übergebene ID-Liste nicht vollständig oder nicht korrekt ist; beide Handler geben dann 400 zurück.
|
||||||
|
|
||||||
|
**Architecture:** Sentinel-Fehler im store-Package; Vollständigkeitsprüfung per COUNT + RowsAffected-Check im Store; beide HTTP-Handler unterscheiden Validierungsfehler von DB-Fehlern via `errors.Is`.
|
||||||
|
|
||||||
|
**Tech Stack:** Go, pgx/v5, net/http
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Sentinel-Fehler + Validierung in `store.go`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `server/backend/internal/store/store.go:4-10` (imports)
|
||||||
|
- Modify: `server/backend/internal/store/store.go:533-550` (Reorder)
|
||||||
|
|
||||||
|
- [ ] **Schritt 1: `errors` zu den Imports hinzufügen**
|
||||||
|
|
||||||
|
Datei: `server/backend/internal/store/store.go`, Zeilen 4–10.
|
||||||
|
|
||||||
|
Vorher:
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Nachher:
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Schritt 2: Sentinel-Variable nach den Imports einfügen**
|
||||||
|
|
||||||
|
Direkt nach dem Import-Block (vor der ersten Typdefinition) einfügen:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// ErrReorderMismatch wird von Reorder zurückgegeben, wenn die übergebene
|
||||||
|
// ID-Liste nicht mit den tatsächlichen Items der Playlist übereinstimmt.
|
||||||
|
var ErrReorderMismatch = errors.New("reorder: item list does not match playlist")
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Schritt 3: `Reorder`-Funktion ersetzen**
|
||||||
|
|
||||||
|
Datei: `server/backend/internal/store/store.go`, die gesamte `Reorder`-Funktion (aktuell Zeilen 533–550) ersetzen durch:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Reorder sets order_index for each item ID in the given slice order.
|
||||||
|
// Returns ErrReorderMismatch if the number of provided IDs does not match
|
||||||
|
// the number of items in the playlist, or if any ID does not belong to it.
|
||||||
|
func (s *PlaylistStore) Reorder(ctx context.Context, playlistID string, itemIDs []string) error {
|
||||||
|
tx, err := s.pool.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tx.Rollback(ctx) //nolint:errcheck
|
||||||
|
|
||||||
|
var count int
|
||||||
|
if err := tx.QueryRow(ctx,
|
||||||
|
`select count(*) from playlist_items where playlist_id=$1`, playlistID,
|
||||||
|
).Scan(&count); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count != len(itemIDs) {
|
||||||
|
return fmt.Errorf("%w: got %d ids, playlist has %d items",
|
||||||
|
ErrReorderMismatch, len(itemIDs), count)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, id := range itemIDs {
|
||||||
|
tag, err := tx.Exec(ctx,
|
||||||
|
`update playlist_items set order_index=$1 where id=$2 and playlist_id=$3`,
|
||||||
|
i, id, playlistID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if tag.RowsAffected() != 1 {
|
||||||
|
return fmt.Errorf("%w: id %s not found in playlist %s",
|
||||||
|
ErrReorderMismatch, id, playlistID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tx.Commit(ctx)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Schritt 4: Kompilieren**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server/backend && go build ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
Erwartet: keine Ausgabe, Exit 0.
|
||||||
|
|
||||||
|
- [ ] **Schritt 5: Committen**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add server/backend/internal/store/store.go
|
||||||
|
git commit -m "fix(store): Reorder validiert Vollständigkeit und RowsAffected"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: `HandleReorderUI` gibt 400 bei Mismatch zurück
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `server/backend/internal/httpapi/manage/ui.go:1-20` (imports)
|
||||||
|
- Modify: `server/backend/internal/httpapi/manage/ui.go:743-746` (Fehlerbehandlung)
|
||||||
|
|
||||||
|
- [ ] **Schritt 1: `errors` zu den Imports hinzufügen**
|
||||||
|
|
||||||
|
Datei: `server/backend/internal/httpapi/manage/ui.go`. Im import-Block `"errors"` ergänzen (alphabetisch vor `"encoding/json"`):
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"html/template"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.az-it.net/az/morz-infoboard/server/backend/internal/config"
|
||||||
|
"git.az-it.net/az/morz-infoboard/server/backend/internal/fileutil"
|
||||||
|
"git.az-it.net/az/morz-infoboard/server/backend/internal/mqttnotifier"
|
||||||
|
"git.az-it.net/az/morz-infoboard/server/backend/internal/reqcontext"
|
||||||
|
"git.az-it.net/az/morz-infoboard/server/backend/internal/store"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Schritt 2: Fehlerbehandlung in `HandleReorderUI` ersetzen**
|
||||||
|
|
||||||
|
Datei: `server/backend/internal/httpapi/manage/ui.go`, Zeilen 743–746. Den Block:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := playlists.Reorder(r.Context(), playlist.ID, ids); err != nil {
|
||||||
|
http.Error(w, "db error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
ersetzen durch:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := playlists.Reorder(r.Context(), playlist.ID, ids); err != nil {
|
||||||
|
if errors.Is(err, store.ErrReorderMismatch) {
|
||||||
|
http.Error(w, "item list mismatch", http.StatusBadRequest)
|
||||||
|
} else {
|
||||||
|
http.Error(w, "db error", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Schritt 3: Kompilieren**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server/backend && go build ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
Erwartet: keine Ausgabe, Exit 0.
|
||||||
|
|
||||||
|
- [ ] **Schritt 4: Committen**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add server/backend/internal/httpapi/manage/ui.go
|
||||||
|
git commit -m "fix(manage): HandleReorderUI gibt 400 bei Mismatch zurück"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: `HandleReorder` gibt 400 bei Mismatch zurück
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `server/backend/internal/httpapi/manage/playlist.go:1-14` (imports)
|
||||||
|
- Modify: `server/backend/internal/httpapi/manage/playlist.go:244-247` (Fehlerbehandlung)
|
||||||
|
|
||||||
|
- [ ] **Schritt 1: `errors` zu den Imports hinzufügen**
|
||||||
|
|
||||||
|
Datei: `server/backend/internal/httpapi/manage/playlist.go`. Im import-Block `"errors"` ergänzen:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.az-it.net/az/morz-infoboard/server/backend/internal/mqttnotifier"
|
||||||
|
"git.az-it.net/az/morz-infoboard/server/backend/internal/reqcontext"
|
||||||
|
"git.az-it.net/az/morz-infoboard/server/backend/internal/store"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Schritt 2: Fehlerbehandlung in `HandleReorder` ersetzen**
|
||||||
|
|
||||||
|
Datei: `server/backend/internal/httpapi/manage/playlist.go`, Zeilen 244–247. Den Block:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := playlists.Reorder(r.Context(), playlistID, ids); err != nil {
|
||||||
|
http.Error(w, "db error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
ersetzen durch:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := playlists.Reorder(r.Context(), playlistID, ids); err != nil {
|
||||||
|
if errors.Is(err, store.ErrReorderMismatch) {
|
||||||
|
http.Error(w, "item list mismatch", http.StatusBadRequest)
|
||||||
|
} else {
|
||||||
|
http.Error(w, "db error", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Schritt 3: Kompilieren und Tests ausführen**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server/backend && go build ./... && go test ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
Erwartet: keine Ausgabe bei build, alle Tests grün.
|
||||||
|
|
||||||
|
- [ ] **Schritt 4: Committen**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add server/backend/internal/httpapi/manage/playlist.go
|
||||||
|
git commit -m "fix(manage): HandleReorder gibt 400 bei Mismatch zurück"
|
||||||
|
```
|
||||||
Loading…
Add table
Reference in a new issue