package store import ( "context" "encoding/json" "errors" "fmt" "time" "github.com/jackc/pgx/v5" ) // MaintenanceKey is the app_settings row backing maintenance mode. const MaintenanceKey = "maintenance" // Maintenance is the operator switch that takes Memby down independently of Emby. // // Deliberately durable: a restart must not quietly bring the app back up while someone // is still working on it. type Maintenance struct { Enabled bool `json:"enabled"` Message string `json:"message"` UpdatedAt time.Time `json:"updatedAt"` } // DefaultMaintenanceMessage is shown on the TV when the operator did not write one. const DefaultMaintenanceMessage = "Memby is down for maintenance. Try again shortly." func (s *Store) Maintenance(ctx context.Context) (Maintenance, error) { var raw []byte err := s.pool.QueryRow(ctx, `SELECT value FROM app_settings WHERE key = $1`, MaintenanceKey).Scan(&raw) if errors.Is(err, pgx.ErrNoRows) { return Maintenance{}, nil } if err != nil { return Maintenance{}, fmt.Errorf("store: read maintenance: %w", err) } var state Maintenance if err := json.Unmarshal(raw, &state); err != nil { return Maintenance{}, fmt.Errorf("store: decode maintenance: %w", err) } return state, nil } func (s *Store) SetMaintenance(ctx context.Context, state Maintenance) error { state.UpdatedAt = time.Now().UTC() raw, err := json.Marshal(state) if err != nil { return err } _, err = s.pool.Exec(ctx, ` INSERT INTO app_settings (key, value, updated_at) VALUES ($1, $2::jsonb, now()) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = now()`, MaintenanceKey, string(raw)) if err != nil { return fmt.Errorf("store: write maintenance: %w", err) } return nil } // NewestSession is the fallback credential for the library import: whichever TV signed // in most recently. It means a fresh deployment can import without configuring a // service account, at the cost of the import stopping if that user is ever removed. func (s *Store) NewestSession(ctx context.Context) (Session, error) { var sess Session err := s.pool.QueryRow(ctx, ` SELECT token_hash, emby_user_id, emby_token, username, server_id, device_id, last_seen_at FROM sessions ORDER BY last_seen_at DESC LIMIT 1`). Scan(&sess.TokenHash, &sess.EmbyUserID, &sess.EmbyToken, &sess.Username, &sess.ServerID, &sess.DeviceID, &sess.LastSeenAt) if errors.Is(err, pgx.ErrNoRows) { return Session{}, ErrNotFound } if err != nil { return Session{}, fmt.Errorf("store: newest session: %w", err) } return sess, nil }