package store import ( "context" "database/sql" "errors" "time" ) // Setting keys. const ( SettingSiteTitle = "site_title" SettingOwnerName = "owner_name" SettingTagline = "tagline" SettingAvatar = "avatar" // cached image file name SettingTheme = "theme" SettingCustomCSS = "custom_css" SettingPreviewCount = "preview_count" // items shown per list on the home page SettingShowNotes = "show_notes" SettingShowPreviews = "show_previews" SettingMusicProvider = "music_provider" SettingTVProvider = "tv_provider" SettingSitePassword = "site_password" // bcrypt hash; empty = public SettingBackupCron = "backup_cron" // cron expression, or "off" SettingBackupKeep = "backup_keep" // how many scheduled backups to keep SettingTMDBKey = "tmdb_api_key" SettingTVDBKey = "tvdb_api_key" SettingTVDBPin = "tvdb_pin" SettingIGDBClientID = "igdb_client_id" SettingIGDBClientSecret = "igdb_client_secret" ) func (s *Store) Settings(ctx context.Context) (map[string]string, error) { rows, err := s.db.QueryContext(ctx, `SELECT key, value FROM settings`) if err != nil { return nil, err } defer rows.Close() out := map[string]string{} for rows.Next() { var k, v string if err := rows.Scan(&k, &v); err != nil { return nil, err } out[k] = v } return out, rows.Err() } func (s *Store) SetSettings(ctx context.Context, kv map[string]string) error { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return err } defer tx.Rollback() for k, v := range kv { if _, err := tx.ExecContext(ctx, `INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value`, k, v); err != nil { return err } } return tx.Commit() } // ---- users & sessions ---- type User struct { ID int64 Username string PasswordHash string } func (s *Store) UserCount(ctx context.Context) (int, error) { var n int err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM users`).Scan(&n) return n, err } func (s *Store) CreateUser(ctx context.Context, username, hash string) error { _, err := s.db.ExecContext(ctx, `INSERT INTO users (username, password_hash) VALUES (?, ?)`, username, hash) return err } func (s *Store) UserByName(ctx context.Context, username string) (User, error) { var u User err := s.db.QueryRowContext(ctx, `SELECT id, username, password_hash FROM users WHERE username = ?`, username).Scan(&u.ID, &u.Username, &u.PasswordHash) if errors.Is(err, sql.ErrNoRows) { return u, ErrNotFound } return u, err } func (s *Store) UserByID(ctx context.Context, id int64) (User, error) { var u User err := s.db.QueryRowContext(ctx, `SELECT id, username, password_hash FROM users WHERE id = ?`, id).Scan(&u.ID, &u.Username, &u.PasswordHash) if errors.Is(err, sql.ErrNoRows) { return u, ErrNotFound } return u, err } func (s *Store) UpdateUser(ctx context.Context, id int64, username, hash string) error { _, err := s.db.ExecContext(ctx, `UPDATE users SET username = ?, password_hash = ? WHERE id = ?`, username, hash, id) return err } func (s *Store) CreateSession(ctx context.Context, tokenHash string, userID int64, expires time.Time) error { _, err := s.db.ExecContext(ctx, `INSERT INTO sessions (token_hash, user_id, expires_at) VALUES (?, ?, ?)`, tokenHash, userID, expires.Unix()) return err } // SessionUser returns the user for a live session. func (s *Store) SessionUser(ctx context.Context, tokenHash string) (User, error) { var u User err := s.db.QueryRowContext(ctx, `SELECT u.id, u.username, u.password_hash FROM sessions s JOIN users u ON u.id = s.user_id WHERE s.token_hash = ? AND s.expires_at > unixepoch()`, tokenHash).Scan(&u.ID, &u.Username, &u.PasswordHash) if errors.Is(err, sql.ErrNoRows) { return u, ErrNotFound } return u, err } func (s *Store) DeleteSession(ctx context.Context, tokenHash string) error { _, err := s.db.ExecContext(ctx, `DELETE FROM sessions WHERE token_hash = ?`, tokenHash) return err } // DeleteUserSessions logs a user out everywhere except the given session. func (s *Store) DeleteUserSessions(ctx context.Context, userID int64, keepTokenHash string) error { _, err := s.db.ExecContext(ctx, `DELETE FROM sessions WHERE user_id = ? AND token_hash != ?`, userID, keepTokenHash) return err } func (s *Store) PruneSessions(ctx context.Context) error { _, err := s.db.ExecContext(ctx, `DELETE FROM sessions WHERE expires_at <= unixepoch()`) return err }