diff --git a/internal/database/models.go b/internal/database/models.go index 688f7ef..340c69f 100644 --- a/internal/database/models.go +++ b/internal/database/models.go @@ -349,6 +349,14 @@ type SystemConfig struct { UpdatedBy pgtype.UUID `db:"updated_by" json:"updated_by"` } +type SystemSettings struct { + ID pgtype.UUID `db:"id" json:"id"` + SettingKey string `db:"setting_key" json:"setting_key"` + SettingValue string `db:"setting_value" json:"setting_value"` + Description pgtype.Text `db:"description" json:"description"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` +} + type UnlinkedBooks struct { ID pgtype.UUID `db:"id" json:"id"` DeviceID pgtype.UUID `db:"device_id" json:"device_id"` @@ -366,17 +374,15 @@ type UnlinkedBooks struct { } type Users struct { - ID pgtype.UUID `db:"id" json:"id"` - Email string `db:"email" json:"email"` - Username string `db:"username" json:"username"` - PasswordHash string `db:"password_hash" json:"password_hash"` - FirstName pgtype.Text `db:"first_name" json:"first_name"` - LastName pgtype.Text `db:"last_name" json:"last_name"` - Role string `db:"role" json:"role"` - Theme pgtype.Text `db:"theme" json:"theme"` - ScanFrequencyMinutes pgtype.Int4 `db:"scan_frequency_minutes" json:"scan_frequency_minutes"` - AutoScanEnabled pgtype.Bool `db:"auto_scan_enabled" json:"auto_scan_enabled"` - MaxDevices pgtype.Int4 `db:"max_devices" json:"max_devices"` - CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` - UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + ID pgtype.UUID `db:"id" json:"id"` + Email string `db:"email" json:"email"` + Username string `db:"username" json:"username"` + PasswordHash string `db:"password_hash" json:"password_hash"` + FirstName pgtype.Text `db:"first_name" json:"first_name"` + LastName pgtype.Text `db:"last_name" json:"last_name"` + Role string `db:"role" json:"role"` + Theme pgtype.Text `db:"theme" json:"theme"` + MaxDevices pgtype.Int4 `db:"max_devices" json:"max_devices"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` } diff --git a/internal/database/querier.go b/internal/database/querier.go index 7789198..cf8d644 100644 --- a/internal/database/querier.go +++ b/internal/database/querier.go @@ -113,6 +113,7 @@ type Querier interface { GenerateKoboEntitlementId(ctx context.Context) (interface{}, error) // Get all system config GetAllSystemConfig(ctx context.Context) ([]SystemConfig, error) + GetAllSystemSettings(ctx context.Context) ([]GetAllSystemSettingsRow, error) GetAnnotationsForBook(ctx context.Context, arg GetAnnotationsForBookParams) ([]GetAnnotationsForBookRow, error) // Get collection GetCollection(ctx context.Context, id pgtype.UUID) (Collections, error) @@ -196,7 +197,6 @@ type Querier interface { GetReadingHistory(ctx context.Context, arg GetReadingHistoryParams) ([]ReadingHistory, error) GetReadingProgress(ctx context.Context, arg GetReadingProgressParams) (ReadingProgress, error) GetRefreshToken(ctx context.Context, token pgtype.UUID) (GetRefreshTokenRow, error) - GetScanSettings(ctx context.Context, id pgtype.UUID) (GetScanSettingsRow, error) GetStuckSyncQueueItems(ctx context.Context) ([]SyncQueue, error) GetSyncConflict(ctx context.Context, id pgtype.UUID) (SyncConflicts, error) GetSyncQueueItem(ctx context.Context, id pgtype.UUID) (SyncQueue, error) @@ -204,6 +204,8 @@ type Querier interface { // SYSTEM CONFIG QUERIES // Get system config GetSystemConfig(ctx context.Context, key string) (SystemConfig, error) + // System Settings queries + GetSystemSetting(ctx context.Context, settingKey string) (string, error) // Get universal progress for a book GetUniversalProgress(ctx context.Context, arg GetUniversalProgressParams) (GetUniversalProgressRow, error) // Get unlinked book by ContentId @@ -259,8 +261,6 @@ type Querier interface { // Revoke OPDS token RevokeOpdsToken(ctx context.Context, token string) error RevokeRefreshToken(ctx context.Context, token pgtype.UUID) error - // Note: User ebook folders replaced by library folders system - // Legacy folder management is now handled through libraries // Search Media Items queries SearchMediaItems(ctx context.Context, arg SearchMediaItemsParams) ([]SearchMediaItemsRow, error) SearchMediaItemsFuzzy(ctx context.Context, arg SearchMediaItemsFuzzyParams) ([]SearchMediaItemsFuzzyRow, error) @@ -307,11 +307,11 @@ type Querier interface { UpdateMediaRating(ctx context.Context, arg UpdateMediaRatingParams) (MediaRatings, error) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error UpdateReadingProgress(ctx context.Context, arg UpdateReadingProgressParams) (ReadingProgress, error) - UpdateScanSettings(ctx context.Context, arg UpdateScanSettingsParams) error UpdateSyncQueueItemStatus(ctx context.Context, arg UpdateSyncQueueItemStatusParams) (SyncQueue, error) + UpdateSystemSetting(ctx context.Context, arg UpdateSystemSettingParams) error // Update universal progress UpdateUniversalProgress(ctx context.Context, arg UpdateUniversalProgressParams) (ReadingProgress, error) - UpdateUserMaxDevices(ctx context.Context, arg UpdateUserMaxDevicesParams) error + UpdateUserMaxDevices(ctx context.Context, arg UpdateUserMaxDevicesParams) (Users, error) UpdateUserProfile(ctx context.Context, arg UpdateUserProfileParams) error UpdateUserTheme(ctx context.Context, arg UpdateUserThemeParams) error UpdateUsername(ctx context.Context, arg UpdateUsernameParams) error diff --git a/internal/database/queries.sql.go b/internal/database/queries.sql.go index 6efc70e..c5c234f 100644 --- a/internal/database/queries.sql.go +++ b/internal/database/queries.sql.go @@ -1423,6 +1423,36 @@ func (q *Queries) GetAllSystemConfig(ctx context.Context) ([]SystemConfig, error return items, nil } +const GetAllSystemSettings = `-- name: GetAllSystemSettings :many +SELECT setting_key, setting_value, description FROM system_settings ORDER BY setting_key +` + +type GetAllSystemSettingsRow struct { + SettingKey string `db:"setting_key" json:"setting_key"` + SettingValue string `db:"setting_value" json:"setting_value"` + Description pgtype.Text `db:"description" json:"description"` +} + +func (q *Queries) GetAllSystemSettings(ctx context.Context) ([]GetAllSystemSettingsRow, error) { + rows, err := q.db.Query(ctx, GetAllSystemSettings) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetAllSystemSettingsRow{} + for rows.Next() { + var i GetAllSystemSettingsRow + if err := rows.Scan(&i.SettingKey, &i.SettingValue, &i.Description); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const GetAnnotationsForBook = `-- name: GetAnnotationsForBook :many SELECT mh.id, @@ -3643,22 +3673,6 @@ func (q *Queries) GetRefreshToken(ctx context.Context, token pgtype.UUID) (GetRe return i, err } -const GetScanSettings = `-- name: GetScanSettings :one -SELECT scan_frequency_minutes, auto_scan_enabled FROM users WHERE id = $1 -` - -type GetScanSettingsRow struct { - ScanFrequencyMinutes pgtype.Int4 `db:"scan_frequency_minutes" json:"scan_frequency_minutes"` - AutoScanEnabled pgtype.Bool `db:"auto_scan_enabled" json:"auto_scan_enabled"` -} - -func (q *Queries) GetScanSettings(ctx context.Context, id pgtype.UUID) (GetScanSettingsRow, error) { - row := q.db.QueryRow(ctx, GetScanSettings, id) - var i GetScanSettingsRow - err := row.Scan(&i.ScanFrequencyMinutes, &i.AutoScanEnabled) - return i, err -} - const GetStuckSyncQueueItems = `-- name: GetStuckSyncQueueItems :many SELECT id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at FROM sync_queue WHERE status = 'processing' AND created_at < NOW() - INTERVAL '1 hour' @@ -3795,6 +3809,18 @@ func (q *Queries) GetSystemConfig(ctx context.Context, key string) (SystemConfig return i, err } +const GetSystemSetting = `-- name: GetSystemSetting :one +SELECT setting_value FROM system_settings WHERE setting_key = $1 +` + +// System Settings queries +func (q *Queries) GetSystemSetting(ctx context.Context, settingKey string) (string, error) { + row := q.db.QueryRow(ctx, GetSystemSetting, settingKey) + var setting_value string + err := row.Scan(&setting_value) + return setting_value, err +} + const GetUniversalProgress = `-- name: GetUniversalProgress :one SELECT rp.id, @@ -4027,19 +4053,34 @@ func (q *Queries) GetUnlinkedBooksByDevice(ctx context.Context, deviceID pgtype. } const GetUser = `-- name: GetUser :one -SELECT id, email, username, theme, first_name, last_name, role, created_at, updated_at FROM users WHERE id = $1 +SELECT + u.id, + u.email, + u.username, + u.theme, + u.first_name, + u.last_name, + u.role, + u.max_devices, + u.created_at, + u.updated_at, + (SELECT COUNT(*) FROM devices WHERE user_id = u.id) as device_count +FROM users u +WHERE u.id = $1 ` type GetUserRow struct { - ID pgtype.UUID `db:"id" json:"id"` - Email string `db:"email" json:"email"` - Username string `db:"username" json:"username"` - Theme pgtype.Text `db:"theme" json:"theme"` - FirstName pgtype.Text `db:"first_name" json:"first_name"` - LastName pgtype.Text `db:"last_name" json:"last_name"` - Role string `db:"role" json:"role"` - CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` - UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + ID pgtype.UUID `db:"id" json:"id"` + Email string `db:"email" json:"email"` + Username string `db:"username" json:"username"` + Theme pgtype.Text `db:"theme" json:"theme"` + FirstName pgtype.Text `db:"first_name" json:"first_name"` + LastName pgtype.Text `db:"last_name" json:"last_name"` + Role string `db:"role" json:"role"` + MaxDevices pgtype.Int4 `db:"max_devices" json:"max_devices"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + DeviceCount int64 `db:"device_count" json:"device_count"` } func (q *Queries) GetUser(ctx context.Context, id pgtype.UUID) (GetUserRow, error) { @@ -4053,8 +4094,10 @@ func (q *Queries) GetUser(ctx context.Context, id pgtype.UUID) (GetUserRow, erro &i.FirstName, &i.LastName, &i.Role, + &i.MaxDevices, &i.CreatedAt, &i.UpdatedAt, + &i.DeviceCount, ) return i, err } @@ -5733,19 +5776,34 @@ func (q *Queries) ListUnresolvedUnlinkedBooks(ctx context.Context, arg ListUnres } const ListUsers = `-- name: ListUsers :many -SELECT id, email, username, theme, first_name, last_name, role, created_at, updated_at FROM users ORDER BY created_at DESC +SELECT + u.id, + u.email, + u.username, + u.theme, + u.first_name, + u.last_name, + u.role, + u.max_devices, + u.created_at, + u.updated_at, + (SELECT COUNT(*) FROM devices WHERE user_id = u.id) as device_count +FROM users u +ORDER BY u.created_at DESC ` type ListUsersRow struct { - ID pgtype.UUID `db:"id" json:"id"` - Email string `db:"email" json:"email"` - Username string `db:"username" json:"username"` - Theme pgtype.Text `db:"theme" json:"theme"` - FirstName pgtype.Text `db:"first_name" json:"first_name"` - LastName pgtype.Text `db:"last_name" json:"last_name"` - Role string `db:"role" json:"role"` - CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` - UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + ID pgtype.UUID `db:"id" json:"id"` + Email string `db:"email" json:"email"` + Username string `db:"username" json:"username"` + Theme pgtype.Text `db:"theme" json:"theme"` + FirstName pgtype.Text `db:"first_name" json:"first_name"` + LastName pgtype.Text `db:"last_name" json:"last_name"` + Role string `db:"role" json:"role"` + MaxDevices pgtype.Int4 `db:"max_devices" json:"max_devices"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + DeviceCount int64 `db:"device_count" json:"device_count"` } func (q *Queries) ListUsers(ctx context.Context) ([]ListUsersRow, error) { @@ -5765,8 +5823,10 @@ func (q *Queries) ListUsers(ctx context.Context) ([]ListUsersRow, error) { &i.FirstName, &i.LastName, &i.Role, + &i.MaxDevices, &i.CreatedAt, &i.UpdatedAt, + &i.DeviceCount, ); err != nil { return nil, err } @@ -6028,7 +6088,6 @@ func (q *Queries) RevokeRefreshToken(ctx context.Context, token pgtype.UUID) err } const SearchMediaItems = `-- name: SearchMediaItems :many - SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name FROM media_items mi JOIN libraries l ON mi.library_id = l.id @@ -6110,8 +6169,6 @@ type SearchMediaItemsRow struct { LibraryTypeName string `db:"library_type_name" json:"library_type_name"` } -// Note: User ebook folders replaced by library folders system -// Legacy folder management is now handled through libraries // Search Media Items queries func (q *Queries) SearchMediaItems(ctx context.Context, arg SearchMediaItemsParams) ([]SearchMediaItemsRow, error) { rows, err := q.db.Query(ctx, SearchMediaItems, @@ -7393,21 +7450,6 @@ func (q *Queries) UpdateReadingProgress(ctx context.Context, arg UpdateReadingPr return i, err } -const UpdateScanSettings = `-- name: UpdateScanSettings :exec -UPDATE users SET scan_frequency_minutes = $2, auto_scan_enabled = $3, updated_at = NOW() WHERE id = $1 -` - -type UpdateScanSettingsParams struct { - ID pgtype.UUID `db:"id" json:"id"` - ScanFrequencyMinutes pgtype.Int4 `db:"scan_frequency_minutes" json:"scan_frequency_minutes"` - AutoScanEnabled pgtype.Bool `db:"auto_scan_enabled" json:"auto_scan_enabled"` -} - -func (q *Queries) UpdateScanSettings(ctx context.Context, arg UpdateScanSettingsParams) error { - _, err := q.db.Exec(ctx, UpdateScanSettings, arg.ID, arg.ScanFrequencyMinutes, arg.AutoScanEnabled) - return err -} - const UpdateSyncQueueItemStatus = `-- name: UpdateSyncQueueItemStatus :one UPDATE sync_queue SET @@ -7445,6 +7487,20 @@ func (q *Queries) UpdateSyncQueueItemStatus(ctx context.Context, arg UpdateSyncQ return i, err } +const UpdateSystemSetting = `-- name: UpdateSystemSetting :exec +UPDATE system_settings SET setting_value = $2, updated_at = NOW() WHERE setting_key = $1 +` + +type UpdateSystemSettingParams struct { + SettingKey string `db:"setting_key" json:"setting_key"` + SettingValue string `db:"setting_value" json:"setting_value"` +} + +func (q *Queries) UpdateSystemSetting(ctx context.Context, arg UpdateSystemSettingParams) error { + _, err := q.db.Exec(ctx, UpdateSystemSetting, arg.SettingKey, arg.SettingValue) + return err +} + const UpdateUniversalProgress = `-- name: UpdateUniversalProgress :one INSERT INTO reading_progress ( media_item_id, @@ -7566,8 +7622,9 @@ func (q *Queries) UpdateUniversalProgress(ctx context.Context, arg UpdateUnivers return i, err } -const UpdateUserMaxDevices = `-- name: UpdateUserMaxDevices :exec +const UpdateUserMaxDevices = `-- name: UpdateUserMaxDevices :one 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 ` type UpdateUserMaxDevicesParams struct { @@ -7575,9 +7632,23 @@ type UpdateUserMaxDevicesParams struct { MaxDevices pgtype.Int4 `db:"max_devices" json:"max_devices"` } -func (q *Queries) UpdateUserMaxDevices(ctx context.Context, arg UpdateUserMaxDevicesParams) error { - _, err := q.db.Exec(ctx, UpdateUserMaxDevices, arg.ID, arg.MaxDevices) - return err +func (q *Queries) UpdateUserMaxDevices(ctx context.Context, arg UpdateUserMaxDevicesParams) (Users, error) { + row := q.db.QueryRow(ctx, UpdateUserMaxDevices, arg.ID, arg.MaxDevices) + var i Users + err := row.Scan( + &i.ID, + &i.Email, + &i.Username, + &i.PasswordHash, + &i.FirstName, + &i.LastName, + &i.Role, + &i.Theme, + &i.MaxDevices, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err } const UpdateUserProfile = `-- name: UpdateUserProfile :exec