84 lines
3.2 KiB
Go
84 lines
3.2 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
// Which colour schemes an operator has decided a viewer may choose from.
|
|
//
|
|
// The store deliberately knows nothing about what a theme *is*: the catalogue, the palettes
|
|
// and the rule about seasons all live in internal/api next to the client contract, the same
|
|
// division user_preferences draws. What is here is only the set of ids, and the one property
|
|
// that has to be true at this level — that an empty result means "unrestricted", never "no
|
|
// themes at all". See the table comment in schema.sql.
|
|
|
|
// UserThemes is the ids this viewer may pick between, or an empty slice for anybody the
|
|
// operator has never restricted — which is everybody, until they do.
|
|
func (s *Store) UserThemes(ctx context.Context, userID string) ([]string, error) {
|
|
rows, err := s.pool.Query(ctx,
|
|
`SELECT theme_id FROM user_themes WHERE emby_user_id = $1 ORDER BY theme_id`, userID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("store: read user themes: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
themes := []string{}
|
|
for rows.Next() {
|
|
var id string
|
|
if err := rows.Scan(&id); err != nil {
|
|
return nil, fmt.Errorf("store: scan user theme: %w", err)
|
|
}
|
|
themes = append(themes, id)
|
|
}
|
|
return themes, rows.Err()
|
|
}
|
|
|
|
// AllUserThemes is the admin console's read: one query for the whole accounts page rather
|
|
// than one per person, since that page already fans out over every account it lists.
|
|
func (s *Store) AllUserThemes(ctx context.Context) (map[string][]string, error) {
|
|
rows, err := s.pool.Query(ctx,
|
|
`SELECT emby_user_id, theme_id FROM user_themes ORDER BY emby_user_id, theme_id`)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("store: list user themes: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
all := map[string][]string{}
|
|
for rows.Next() {
|
|
var userID, themeID string
|
|
if err := rows.Scan(&userID, &themeID); err != nil {
|
|
return nil, fmt.Errorf("store: scan user themes: %w", err)
|
|
}
|
|
all[userID] = append(all[userID], themeID)
|
|
}
|
|
return all, rows.Err()
|
|
}
|
|
|
|
// SetUserThemes replaces this viewer's allowlist wholesale.
|
|
//
|
|
// Replace rather than merge because the console sends the whole set of ticked boxes, and a
|
|
// merge would make unticking one impossible. It is one transaction so a viewer is never
|
|
// momentarily allowed nothing — a television resolving its theme in that window would be
|
|
// told the default and would repaint itself for no reason.
|
|
//
|
|
// An empty list deletes the rows rather than storing anything, which is what keeps
|
|
// "unrestricted" a single representation. api.normalizeThemeAllowlist is what turns "every
|
|
// box ticked" into that empty list before it arrives here.
|
|
func (s *Store) SetUserThemes(ctx context.Context, userID string, themes []string) error {
|
|
return pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
|
if _, err := tx.Exec(ctx,
|
|
`DELETE FROM user_themes WHERE emby_user_id = $1`, userID); err != nil {
|
|
return fmt.Errorf("store: clear user themes: %w", err)
|
|
}
|
|
for _, id := range themes {
|
|
if _, err := tx.Exec(ctx, `
|
|
INSERT INTO user_themes (emby_user_id, theme_id) VALUES ($1, $2)
|
|
ON CONFLICT DO NOTHING`, userID, id); err != nil {
|
|
return fmt.Errorf("store: write user theme: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|