fix: Upsert löscht override_on_until nicht mehr; README + Auth-Kommentar
- ScreenScheduleStore.Upsert: override_on_until aus INSERT und ON CONFLICT entfernt — verhindert stillen Datenverlust beim Speichern eines Zeitplans. SetOverrideOnUntil bleibt alleinig zuständig für diese Spalte. - README.md: GlobalOverrideStore, vier neue API-Routen, Wochenend-Sperre und Migration 006_override.sql dokumentiert. - override.go: Auth-Scope-Kommentar über HandleSetGlobalOverride ergänzt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
db68c84d45
commit
2bf82eed53
3 changed files with 33 additions and 9 deletions
|
|
@ -17,7 +17,7 @@ Dieses Verzeichnis enthaelt das zentrale Go-Backend fuer das Info-Board-System.
|
||||||
- `internal/app/` — App-Initialisierung und Lifecycle
|
- `internal/app/` — App-Initialisierung und Lifecycle
|
||||||
- `internal/config/` — Konfiguration via Umgebungsvariablen
|
- `internal/config/` — Konfiguration via Umgebungsvariablen
|
||||||
- `internal/db/` — PostgreSQL-Anbindung und Migrations-Runner
|
- `internal/db/` — PostgreSQL-Anbindung und Migrations-Runner
|
||||||
- `internal/store/` — Datenbankzugriff (TenantStore, ScreenStore, MediaStore, PlaylistStore, AuthStore)
|
- `internal/store/` — Datenbankzugriff (TenantStore, ScreenStore, MediaStore, PlaylistStore, AuthStore, ScreenScheduleStore, GlobalOverrideStore)
|
||||||
- `internal/fileutil/` — Upload-Hilfsfunktionen (SaveUploadedFile mit Tenant-Isolation)
|
- `internal/fileutil/` — Upload-Hilfsfunktionen (SaveUploadedFile mit Tenant-Isolation)
|
||||||
- `internal/httpapi/` — HTTP-Routing, Middleware und Handler
|
- `internal/httpapi/` — HTTP-Routing, Middleware und Handler
|
||||||
- `internal/httpapi/csrf.go` — Double-Submit-Cookie CSRF-Schutz
|
- `internal/httpapi/csrf.go` — Double-Submit-Cookie CSRF-Schutz
|
||||||
|
|
@ -42,6 +42,10 @@ Uhrzeit übereinstimmt — per MQTT den Befehl `display_on` bzw. `display_off` s
|
||||||
Der Scheduler wird in `internal/app/app.go` als Goroutine gestartet und laeuft bis zum
|
Der Scheduler wird in `internal/app/app.go` als Goroutine gestartet und laeuft bis zum
|
||||||
Kontext-Abbruch beim Server-Shutdown.
|
Kontext-Abbruch beim Server-Shutdown.
|
||||||
|
|
||||||
|
**Wochenend-Sperre:** An Samstagen und Sonntagen werden Zeitplaene ignoriert — der Reconciler
|
||||||
|
sendet dann keine automatischen Ein-/Ausschalt-Kommandos. Manuelle Overrides (global oder
|
||||||
|
per-Screen) wirken jedoch auch am Wochenende.
|
||||||
|
|
||||||
## Datenbank-Stores
|
## Datenbank-Stores
|
||||||
|
|
||||||
### AuthStore (`internal/store/auth.go`)
|
### AuthStore (`internal/store/auth.go`)
|
||||||
|
|
@ -60,6 +64,16 @@ Kontext-Abbruch beim Server-Shutdown.
|
||||||
- `EnsureAdminUser(ctx, tenantSlug, password)` — Admin-User beim Start anlegen
|
- `EnsureAdminUser(ctx, tenantSlug, password)` — Admin-User beim Start anlegen
|
||||||
- `VerifyPassword(ctx, userID, password)` — Passwort gegen bcrypt-Hash pruefen
|
- `VerifyPassword(ctx, userID, password)` — Passwort gegen bcrypt-Hash pruefen
|
||||||
|
|
||||||
|
### GlobalOverrideStore (`internal/store/store.go`)
|
||||||
|
|
||||||
|
Verwaltet einen systemweiten Display-Override (max. 1 Zeile in `global_override`):
|
||||||
|
|
||||||
|
- `Get(ctx)` — aktuellen globalen Override laden (nil wenn keiner gesetzt)
|
||||||
|
- `Upsert(ctx, type, until)` — Override setzen oder ueberschreiben (`type`: `"on"` | `"off"`)
|
||||||
|
- `Delete(ctx)` — Override entfernen
|
||||||
|
|
||||||
|
Der Reconciler im Scheduler wertet den globalen Override aus und wendet ihn auf alle Screens an.
|
||||||
|
|
||||||
### ScreenStore (`internal/store/screen.go`)
|
### ScreenStore (`internal/store/screen.go`)
|
||||||
|
|
||||||
**Screen-User Zugriffskontrolle:**
|
**Screen-User Zugriffskontrolle:**
|
||||||
|
|
@ -117,6 +131,10 @@ Kontext-Abbruch beim Server-Shutdown.
|
||||||
| GET | `/api/v1/screens/{screenId}/screenshot` | Screenshot eines Screens abrufen |
|
| GET | `/api/v1/screens/{screenId}/screenshot` | Screenshot eines Screens abrufen |
|
||||||
| POST | `/api/v1/screens/{screenSlug}/display` | Display ein-/ausschalten (MQTT) |
|
| POST | `/api/v1/screens/{screenSlug}/display` | Display ein-/ausschalten (MQTT) |
|
||||||
| POST | `/api/v1/screens/{screenSlug}/schedule` | Display-Zeitplan speichern |
|
| POST | `/api/v1/screens/{screenSlug}/schedule` | Display-Zeitplan speichern |
|
||||||
|
| GET | `/api/v1/global-override` | Globalen Override abrufen (204 = kein aktiver Override) |
|
||||||
|
| POST | `/api/v1/global-override` | Globalen Override setzen (type + until); sendet sofort MQTT |
|
||||||
|
| DELETE | `/api/v1/global-override` | Globalen Override loeschen |
|
||||||
|
| POST | `/api/v1/screens/{screenSlug}/override` | Per-Screen-Override setzen oder loeschen (on_until: null = loeschen) |
|
||||||
|
|
||||||
### Nur Admins (`RequireAuth` + `RequireAdmin`)
|
### Nur Admins (`RequireAuth` + `RequireAdmin`)
|
||||||
|
|
||||||
|
|
@ -182,8 +200,9 @@ Middleware zur rollenbasierten Zugriffskontrolle auf Screen-Ressourcen.
|
||||||
|
|
||||||
## Migrationen
|
## Migrationen
|
||||||
|
|
||||||
- `001_core.sql` — initiales Schema (Tenants, Screens, Playlists, Media, etc.)
|
- `001_initial.sql` — initiales Schema (Tenants, Screens, Playlists, Media, etc.)
|
||||||
- `002_auth.sql` — Auth-Tabellen (`users`, `sessions`)
|
- `002_auth.sql` — Auth-Tabellen (`users`, `sessions`)
|
||||||
- `003_user_screen_permissions.sql` — Screen-User Management (`user_screen_permissions`)
|
- `003_user_screen_permissions.sql` — Screen-User Management (`user_screen_permissions`)
|
||||||
- `004_screen_status.sql` — Display-Zustand pro Screen (`screen_status`: screen_id, display_state, reported_at)
|
- `004_screen_status.sql` — Display-Zustand pro Screen (`screen_status`: screen_id, display_state, reported_at)
|
||||||
- `005_screen_schedules.sql` — Zeitplan pro Screen (`screen_schedules`: screen_id, schedule_enabled, power_on_time, power_off_time)
|
- `005_screen_schedules.sql` — Zeitplan pro Screen (`screen_schedules`: screen_id, schedule_enabled, power_on_time, power_off_time)
|
||||||
|
- `006_override.sql` — Spalte `override_on_until` in `screen_schedules` (per-Screen-Override) und Tabelle `global_override` (systemweiter Display-Override)
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ func HandleGetGlobalOverride(overrides globalOverrideStore) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleSetGlobalOverride setzt den globalen Override und schickt sofort MQTT an alle Screens.
|
// HandleSetGlobalOverride setzt den globalen Override und schickt sofort MQTT an alle Screens.
|
||||||
|
// Hinweis: Der Override wird global gespeichert und vom Reconciler auf alle Screens angewendet.
|
||||||
|
// Über authOnly haben alle eingeloggten Nutzer Zugriff; die sofortigen MQTT-Kommandos gehen
|
||||||
|
// jedoch nur an ihre zugänglichen Screens. Soll der Zugriff auf Admins beschränkt werden,
|
||||||
|
// authOnly durch authAdmin ersetzen.
|
||||||
func HandleSetGlobalOverride(overrides globalOverrideStore, screens *store.ScreenStore, notifier *mqttnotifier.Notifier) http.HandlerFunc {
|
func HandleSetGlobalOverride(overrides globalOverrideStore, screens *store.ScreenStore, notifier *mqttnotifier.Notifier) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var body struct {
|
var body struct {
|
||||||
|
|
|
||||||
|
|
@ -667,16 +667,17 @@ func (s *ScreenScheduleStore) Get(ctx context.Context, screenID string) (*Screen
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upsert speichert oder aktualisiert den Zeitplan eines Screens.
|
// Upsert speichert oder aktualisiert den Zeitplan eines Screens.
|
||||||
|
// Hinweis: override_on_until wird hier bewusst nicht angefasst – das ist
|
||||||
|
// ausschließlich Aufgabe von SetOverrideOnUntil (saubere Trennung, kein Datenverlust).
|
||||||
func (s *ScreenScheduleStore) Upsert(ctx context.Context, sc *ScreenSchedule) error {
|
func (s *ScreenScheduleStore) Upsert(ctx context.Context, sc *ScreenSchedule) error {
|
||||||
_, err := s.pool.Exec(ctx,
|
_, err := s.pool.Exec(ctx,
|
||||||
`insert into screen_schedules (screen_id, schedule_enabled, power_on_time, power_off_time, override_on_until)
|
`insert into screen_schedules (screen_id, schedule_enabled, power_on_time, power_off_time)
|
||||||
values ($1, $2, $3, $4, $5)
|
values ($1, $2, $3, $4)
|
||||||
on conflict (screen_id) do update
|
on conflict (screen_id) do update
|
||||||
set schedule_enabled = excluded.schedule_enabled,
|
set schedule_enabled = excluded.schedule_enabled,
|
||||||
power_on_time = excluded.power_on_time,
|
power_on_time = excluded.power_on_time,
|
||||||
power_off_time = excluded.power_off_time,
|
power_off_time = excluded.power_off_time`,
|
||||||
override_on_until = excluded.override_on_until`,
|
sc.ScreenID, sc.ScheduleEnabled, sc.PowerOnTime, sc.PowerOffTime)
|
||||||
sc.ScreenID, sc.ScheduleEnabled, sc.PowerOnTime, sc.PowerOffTime, sc.OverrideOnUntil)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue