Phase 6b/7b: dashboard codes editor, decode events, human-readable briefs (AUD-115)

- New /codes dashboard page (behind the session login, linked in the top bar):
  lists every mapping by kind with Add, inline Edit+Save and Delete. Sam can put
  real teacher surnames in without a rebuild (FR-17).
- Decode timetable EVENTS through the same map as periods, so an ensemble reads
  'JAZZ ENSEMBLE - Ensemble Room - Mr KWO' instead of raw codes.
- teacherName splits comma- or space-separated codes ('HHL, OFG').
- SeedCodes is a one-time bootstrap while the table is empty, so a correction or
  a delete is never overwritten or restored.
- Tests: decode Label/Event, the /codes page (save, refuse, csrf, delete, auth),
  store re-seed behaviour. go test ./internal/... passes.
This commit is contained in:
2026-10-16 12:50:31 +11:00
parent 80b54524ea
commit 3e25011399
11 changed files with 433 additions and 28 deletions

View File

@@ -134,10 +134,18 @@ var seedCodes = []struct {
{CodePeriod, "RLE GROUP D", "RLE Group D"},
}
// SeedCodes inserts the shipped mappings that are not present yet. It is
// idempotent and never overwrites a value, so Sam's corrections survive every
// start and every scan.
// SeedCodes inserts the shipped mappings once, when the map is still empty. It
// never overwrites a value, so Sam's corrections survive every start and every
// scan — and because it is a one-time bootstrap, a mapping Sam deletes in the
// dashboard stays deleted instead of reappearing on the next read.
func (s *Store) SeedCodes(ctx context.Context) error {
var n int
if err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM codes`).Scan(&n); err != nil {
return fmt.Errorf("seed codes: count: %w", err)
}
if n > 0 {
return nil
}
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("seed codes: begin: %w", err)
@@ -157,8 +165,9 @@ func (s *Store) SeedCodes(ctx context.Context) error {
return nil
}
// Codes returns the whole decode map. It seeds the shipped defaults first, so a
// fresh database renders the known captures and a corrected row is kept.
// Codes returns the whole decode map. It seeds the shipped defaults first (once)
// so a fresh database renders the known captures, and a corrected or deleted row
// is kept.
func (s *Store) Codes(ctx context.Context) (CodeMap, error) {
if err := s.SeedCodes(ctx); err != nil {
return CodeMap{}, err