feat(users): audio_resume_rewind_seconds column

Per-user resume rewind for the audiobook/podcast player: how many
seconds (0/5/10) before the saved position playback starts, both on
open-resume and again on every genuine unpause. Motivated by the car
startup case — exact resume loses the listener's place, and the
PlaybackRequestResolver path (phone UI, Android Auto, media buttons)
all funnel through one start position.

0 = exact resume (default), so the column is inert until the profile
page sets it. Follows the default_audio_speed / keep_screen_awake
precedent: idempotent ALTER in schema.sql (re-runs on every boot),
explicit GetUser SELECT, dedicated :exec update. Handler-side strict
allow-list {0,5,10} lands with the API slice.
This commit is contained in:
John O'Keefe committed 2026-11-01 17:16:35 -05:00
1 parent 0d613ba0e3
commit daffe499eb
5 files changed
+33 -1

No files matched your search

+8
View File
@@ -48,6 +48,14 @@ ALTER TABLE users ADD COLUMN IF NOT EXISTS default_audio_speed REAL NOT NULL DEF
-- mid-session. On by default; the profile page's On/Off dropdown owns it. -- mid-session. On by default; the profile page's On/Off dropdown owns it.
ALTER TABLE users ADD COLUMN IF NOT EXISTS keep_screen_awake BOOLEAN NOT NULL DEFAULT TRUE; ALTER TABLE users ADD COLUMN IF NOT EXISTS keep_screen_awake BOOLEAN NOT NULL DEFAULT TRUE;
-- Audio resume rewind: seconds before the saved position the
-- audiobook/podcast player starts (on open-resume AND again on every
-- genuine unpause — the re-orientation gap, never stacked). Strict
-- allow-list 0/5/10 enforced handler-side; 0 = exact resume. The
-- profile page's dropdown owns it; the chapters payload carries it to
-- every client (web + Android, incl. the car path).
ALTER TABLE users ADD COLUMN IF NOT EXISTS audio_resume_rewind_seconds INT NOT NULL DEFAULT 0;
-- Create system_settings table -- Create system_settings table
CREATE TABLE IF NOT EXISTS system_settings ( CREATE TABLE IF NOT EXISTS system_settings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+1
View File
@@ -654,4 +654,5 @@ type Users struct {
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
DefaultAudioSpeed float32 `db:"default_audio_speed" json:"default_audio_speed"` DefaultAudioSpeed float32 `db:"default_audio_speed" json:"default_audio_speed"`
KeepScreenAwake bool `db:"keep_screen_awake" json:"keep_screen_awake"` KeepScreenAwake bool `db:"keep_screen_awake" json:"keep_screen_awake"`
AudioResumeRewindSeconds int32 `db:"audio_resume_rewind_seconds" json:"audio_resume_rewind_seconds"`
} }
+1
View File
@@ -894,6 +894,7 @@ type Querier interface {
UpdateSystemSetting(ctx context.Context, arg UpdateSystemSettingParams) error UpdateSystemSetting(ctx context.Context, arg UpdateSystemSettingParams) error
// Update universal progress // Update universal progress
UpdateUniversalProgress(ctx context.Context, arg UpdateUniversalProgressParams) (ReadingProgress, error) UpdateUniversalProgress(ctx context.Context, arg UpdateUniversalProgressParams) (ReadingProgress, error)
UpdateUserAudioResumeRewind(ctx context.Context, arg UpdateUserAudioResumeRewindParams) error
UpdateUserDefaultAudioSpeed(ctx context.Context, arg UpdateUserDefaultAudioSpeedParams) error UpdateUserDefaultAudioSpeed(ctx context.Context, arg UpdateUserDefaultAudioSpeedParams) error
UpdateUserKeepScreenAwake(ctx context.Context, arg UpdateUserKeepScreenAwakeParams) error UpdateUserKeepScreenAwake(ctx context.Context, arg UpdateUserKeepScreenAwakeParams) error
UpdateUserMaxDevices(ctx context.Context, arg UpdateUserMaxDevicesParams) (Users, error) UpdateUserMaxDevices(ctx context.Context, arg UpdateUserMaxDevicesParams) (Users, error)
+19 -1
View File
@@ -11407,6 +11407,7 @@ SELECT
u.timezone, u.timezone,
u.default_audio_speed, u.default_audio_speed,
u.keep_screen_awake, u.keep_screen_awake,
u.audio_resume_rewind_seconds,
(SELECT COUNT(*) FROM devices WHERE user_id = u.id) as device_count (SELECT COUNT(*) FROM devices WHERE user_id = u.id) as device_count
FROM users u FROM users u
WHERE u.id = $1 WHERE u.id = $1
@@ -11426,6 +11427,7 @@ type GetUserRow struct {
Timezone pgtype.Text `db:"timezone" json:"timezone"` Timezone pgtype.Text `db:"timezone" json:"timezone"`
DefaultAudioSpeed float32 `db:"default_audio_speed" json:"default_audio_speed"` DefaultAudioSpeed float32 `db:"default_audio_speed" json:"default_audio_speed"`
KeepScreenAwake bool `db:"keep_screen_awake" json:"keep_screen_awake"` KeepScreenAwake bool `db:"keep_screen_awake" json:"keep_screen_awake"`
AudioResumeRewindSeconds int32 `db:"audio_resume_rewind_seconds" json:"audio_resume_rewind_seconds"`
DeviceCount int64 `db:"device_count" json:"device_count"` DeviceCount int64 `db:"device_count" json:"device_count"`
} }
@@ -11446,6 +11448,7 @@ func (q *Queries) GetUser(ctx context.Context, id pgtype.UUID) (GetUserRow, erro
&i.Timezone, &i.Timezone,
&i.DefaultAudioSpeed, &i.DefaultAudioSpeed,
&i.KeepScreenAwake, &i.KeepScreenAwake,
&i.AudioResumeRewindSeconds,
&i.DeviceCount, &i.DeviceCount,
) )
return i, err return i, err
@@ -20287,6 +20290,20 @@ func (q *Queries) UpdateUniversalProgress(ctx context.Context, arg UpdateUnivers
return i, err return i, err
} }
const UpdateUserAudioResumeRewind = `-- name: UpdateUserAudioResumeRewind :exec
UPDATE users SET audio_resume_rewind_seconds = $2, updated_at = NOW() WHERE id = $1
`
type UpdateUserAudioResumeRewindParams struct {
ID pgtype.UUID `db:"id" json:"id"`
AudioResumeRewindSeconds int32 `db:"audio_resume_rewind_seconds" json:"audio_resume_rewind_seconds"`
}
func (q *Queries) UpdateUserAudioResumeRewind(ctx context.Context, arg UpdateUserAudioResumeRewindParams) error {
_, err := q.db.Exec(ctx, UpdateUserAudioResumeRewind, arg.ID, arg.AudioResumeRewindSeconds)
return err
}
const UpdateUserDefaultAudioSpeed = `-- name: UpdateUserDefaultAudioSpeed :exec const UpdateUserDefaultAudioSpeed = `-- name: UpdateUserDefaultAudioSpeed :exec
UPDATE users SET default_audio_speed = $2, updated_at = NOW() WHERE id = $1 UPDATE users SET default_audio_speed = $2, updated_at = NOW() WHERE id = $1
` `
@@ -20317,7 +20334,7 @@ func (q *Queries) UpdateUserKeepScreenAwake(ctx context.Context, arg UpdateUserK
const UpdateUserMaxDevices = `-- name: UpdateUserMaxDevices :one const UpdateUserMaxDevices = `-- name: UpdateUserMaxDevices :one
UPDATE users SET max_devices = $2, updated_at = NOW() WHERE id = $1 UPDATE users SET max_devices = $2, updated_at = NOW() WHERE id = $1
RETURNING id, email, username, password_hash, first_name, last_name, role, theme, max_devices, created_at, timezone, updated_at, default_audio_speed, keep_screen_awake RETURNING id, email, username, password_hash, first_name, last_name, role, theme, max_devices, created_at, timezone, updated_at, default_audio_speed, keep_screen_awake, audio_resume_rewind_seconds
` `
type UpdateUserMaxDevicesParams struct { type UpdateUserMaxDevicesParams struct {
@@ -20343,6 +20360,7 @@ func (q *Queries) UpdateUserMaxDevices(ctx context.Context, arg UpdateUserMaxDev
&i.UpdatedAt, &i.UpdatedAt,
&i.DefaultAudioSpeed, &i.DefaultAudioSpeed,
&i.KeepScreenAwake, &i.KeepScreenAwake,
&i.AudioResumeRewindSeconds,
) )
return i, err return i, err
} }
+4
View File
@@ -30,6 +30,7 @@ SELECT
u.timezone, u.timezone,
u.default_audio_speed, u.default_audio_speed,
u.keep_screen_awake, u.keep_screen_awake,
u.audio_resume_rewind_seconds,
(SELECT COUNT(*) FROM devices WHERE user_id = u.id) as device_count (SELECT COUNT(*) FROM devices WHERE user_id = u.id) as device_count
FROM users u FROM users u
WHERE u.id = $1; WHERE u.id = $1;
@@ -526,6 +527,9 @@ UPDATE users SET default_audio_speed = $2, updated_at = NOW() WHERE id = $1;
-- name: UpdateUserKeepScreenAwake :exec -- name: UpdateUserKeepScreenAwake :exec
UPDATE users SET keep_screen_awake = $2, updated_at = NOW() WHERE id = $1; UPDATE users SET keep_screen_awake = $2, updated_at = NOW() WHERE id = $1;
-- name: UpdateUserAudioResumeRewind :exec
UPDATE users SET audio_resume_rewind_seconds = $2, updated_at = NOW() WHERE id = $1;
-- Per-book playback speed preference (player plan §8). -- Per-book playback speed preference (player plan §8).
-- name: GetAudioPlaybackSpeed :one -- name: GetAudioPlaybackSpeed :one