Add timezone support to database schema and queries

- Add timezone column (VARCHAR(50) DEFAULT 'UTC') to users table
- Add default_timezone row to system_settings seed data
- Add idx_users_timezone index for user timezone lookups
- Add UpdateUserTimezone and GetSystemTimezone queries
- Regenerate sqlc code (models, querier, queries.sql.go)
- Reuse existing UpdateSystemSetting for system timezone updates
  instead of creating a redundant UpdateSystemTimezone query
This commit is contained in:
2026-04-27 21:30:37 -04:00
parent dd1ddff08d
commit caf50ade31
5 changed files with 40 additions and 2 deletions
+4 -1
View File
@@ -32,6 +32,7 @@ CREATE TABLE IF NOT EXISTS users (
theme VARCHAR(50) DEFAULT 'tokyo-night', theme VARCHAR(50) DEFAULT 'tokyo-night',
max_devices INTEGER DEFAULT 10, max_devices INTEGER DEFAULT 10,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
timezone VARCHAR(50) DEFAULT 'UTC',
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
); );
@@ -47,7 +48,8 @@ CREATE TABLE IF NOT EXISTS system_settings (
-- Insert default system settings -- Insert default system settings
INSERT INTO system_settings (setting_key, setting_value, description) VALUES INSERT INTO system_settings (setting_key, setting_value, description) VALUES
('scan_poll_interval_seconds', '60', 'How often to scan all libraries in minutes'), ('scan_poll_interval_seconds', '60', 'How often to scan all libraries in minutes'),
('auto_scan_enabled', 'true', 'Whether auto-scanning is enabled system-wide') ('auto_scan_enabled', 'true', 'Whether auto-scanning is enabled system-wide'),
('default_timezone', 'UTC', 'System default timezone')
ON CONFLICT (setting_key) DO NOTHING; ON CONFLICT (setting_key) DO NOTHING;
-- Create refresh_tokens table -- Create refresh_tokens table
@@ -457,6 +459,7 @@ CREATE TABLE IF NOT EXISTS reading_history (
-- Create indexes for better query performance -- Create indexes for better query performance
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username); CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_users_timezone ON users(timezone);
CREATE INDEX IF NOT EXISTS idx_library_types_name ON library_types(name); CREATE INDEX IF NOT EXISTS idx_library_types_name ON library_types(name);
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_token ON refresh_tokens(token); CREATE INDEX IF NOT EXISTS idx_refresh_tokens_token ON refresh_tokens(token);
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user_id ON refresh_tokens(user_id); CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user_id ON refresh_tokens(user_id);
+1
View File
@@ -507,5 +507,6 @@ type Users struct {
Theme pgtype.Text `db:"theme" json:"theme"` Theme pgtype.Text `db:"theme" json:"theme"`
MaxDevices pgtype.Int4 `db:"max_devices" json:"max_devices"` MaxDevices pgtype.Int4 `db:"max_devices" json:"max_devices"`
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
Timezone pgtype.Text `db:"timezone" json:"timezone"`
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
} }
+2
View File
@@ -247,6 +247,7 @@ type Querier interface {
GetSystemConfig(ctx context.Context, key string) (SystemConfig, error) GetSystemConfig(ctx context.Context, key string) (SystemConfig, error)
// System Settings queries // System Settings queries
GetSystemSetting(ctx context.Context, settingKey string) (string, error) GetSystemSetting(ctx context.Context, settingKey string) (string, error)
GetSystemTimezone(ctx context.Context) (string, error)
// Get universal progress for a book // Get universal progress for a book
GetUniversalProgress(ctx context.Context, arg GetUniversalProgressParams) (GetUniversalProgressRow, error) GetUniversalProgress(ctx context.Context, arg GetUniversalProgressParams) (GetUniversalProgressRow, error)
// Get unlinked book by ContentId // Get unlinked book by ContentId
@@ -373,6 +374,7 @@ type Querier interface {
UpdateUserProfile(ctx context.Context, arg UpdateUserProfileParams) error UpdateUserProfile(ctx context.Context, arg UpdateUserProfileParams) error
UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) (UpdateUserRoleRow, error) UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) (UpdateUserRoleRow, error)
UpdateUserTheme(ctx context.Context, arg UpdateUserThemeParams) error UpdateUserTheme(ctx context.Context, arg UpdateUserThemeParams) error
UpdateUserTimezone(ctx context.Context, arg UpdateUserTimezoneParams) error
UpdateUsername(ctx context.Context, arg UpdateUsernameParams) error UpdateUsername(ctx context.Context, arg UpdateUsernameParams) error
UpsertDashboardPreferences(ctx context.Context, arg UpsertDashboardPreferencesParams) (UserDashboardPreferences, error) UpsertDashboardPreferences(ctx context.Context, arg UpsertDashboardPreferencesParams) (UserDashboardPreferences, error)
UpsertPanelData(ctx context.Context, arg UpsertPanelDataParams) (PanelData, error) UpsertPanelData(ctx context.Context, arg UpsertPanelDataParams) (PanelData, error)
+27 -1
View File
@@ -5408,6 +5408,17 @@ func (q *Queries) GetSystemSetting(ctx context.Context, settingKey string) (stri
return setting_value, err return setting_value, err
} }
const GetSystemTimezone = `-- name: GetSystemTimezone :one
SELECT setting_value FROM system_settings WHERE setting_key = 'default_timezone'
`
func (q *Queries) GetSystemTimezone(ctx context.Context) (string, error) {
row := q.db.QueryRow(ctx, GetSystemTimezone)
var setting_value string
err := row.Scan(&setting_value)
return setting_value, err
}
const GetUniversalProgress = `-- name: GetUniversalProgress :one const GetUniversalProgress = `-- name: GetUniversalProgress :one
SELECT SELECT
rp.id, rp.id,
@@ -10256,7 +10267,7 @@ func (q *Queries) UpdateUniversalProgress(ctx context.Context, arg UpdateUnivers
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, updated_at RETURNING id, email, username, password_hash, first_name, last_name, role, theme, max_devices, created_at, timezone, updated_at
` `
type UpdateUserMaxDevicesParams struct { type UpdateUserMaxDevicesParams struct {
@@ -10278,6 +10289,7 @@ func (q *Queries) UpdateUserMaxDevices(ctx context.Context, arg UpdateUserMaxDev
&i.Theme, &i.Theme,
&i.MaxDevices, &i.MaxDevices,
&i.CreatedAt, &i.CreatedAt,
&i.Timezone,
&i.UpdatedAt, &i.UpdatedAt,
) )
return i, err return i, err
@@ -10341,6 +10353,20 @@ func (q *Queries) UpdateUserTheme(ctx context.Context, arg UpdateUserThemeParams
return err return err
} }
const UpdateUserTimezone = `-- name: UpdateUserTimezone :exec
UPDATE users SET timezone = $2, updated_at = NOW() WHERE id = $1
`
type UpdateUserTimezoneParams struct {
ID pgtype.UUID `db:"id" json:"id"`
Timezone pgtype.Text `db:"timezone" json:"timezone"`
}
func (q *Queries) UpdateUserTimezone(ctx context.Context, arg UpdateUserTimezoneParams) error {
_, err := q.db.Exec(ctx, UpdateUserTimezone, arg.ID, arg.Timezone)
return err
}
const UpdateUsername = `-- name: UpdateUsername :exec const UpdateUsername = `-- name: UpdateUsername :exec
UPDATE users SET username = $2, updated_at = NOW() WHERE id = $1 UPDATE users SET username = $2, updated_at = NOW() WHERE id = $1
` `
+6
View File
@@ -300,6 +300,12 @@ RETURNING *;
UPDATE users SET role = $2, updated_at = NOW() WHERE id = $1 UPDATE users SET role = $2, updated_at = NOW() WHERE id = $1
RETURNING id, email, username, role; RETURNING id, email, username, role;
-- name: UpdateUserTimezone :exec
UPDATE users SET timezone = $2, updated_at = NOW() WHERE id = $1;
-- name: GetSystemTimezone :one
SELECT setting_value FROM system_settings WHERE setting_key = 'default_timezone';
-- name: CountUserDevices :one -- name: CountUserDevices :one
SELECT COUNT(*) FROM devices WHERE user_id = $1; SELECT COUNT(*) FROM devices WHERE user_id = $1;