feat(api): consolidate user profile update endpoints

Add delete_user, reset_user_password, and update_user endpoints to replace
individual update operations. Update database schema to include deleted_at
column for soft deletion. Add DeleteUser, ResetUserPassword, and
UpdateUserAdmin queries. Update Querier with new methods for user management.
This commit is contained in:
2026-02-22 01:57:42 -05:00
parent 86cc9b4e59
commit e3a3aa124f
4 changed files with 173 additions and 31 deletions
+129 -12
View File
@@ -1072,6 +1072,53 @@ func (q *Queries) CreateSyncQueueItem(ctx context.Context, arg CreateSyncQueueIt
return i, err
}
const CreateSystemCollection = `-- name: CreateSystemCollection :one
INSERT INTO collections (user_id, name, description, icon, color, show_on_dashboard, query_type, priority, is_system_collection, auto_assign_rules)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, true, 'null'::jsonb)
RETURNING id, user_id, name, description, color, icon, auto_assign_rules, view_settings, show_on_dashboard, query_type, priority, is_system_collection, created_at
`
type CreateSystemCollectionParams struct {
UserID pgtype.UUID `db:"user_id" json:"user_id"`
Name string `db:"name" json:"name"`
Description pgtype.Text `db:"description" json:"description"`
Icon pgtype.Text `db:"icon" json:"icon"`
Color pgtype.Text `db:"color" json:"color"`
ShowOnDashboard pgtype.Bool `db:"show_on_dashboard" json:"show_on_dashboard"`
QueryType pgtype.Text `db:"query_type" json:"query_type"`
Priority pgtype.Int4 `db:"priority" json:"priority"`
}
func (q *Queries) CreateSystemCollection(ctx context.Context, arg CreateSystemCollectionParams) (Collections, error) {
row := q.db.QueryRow(ctx, CreateSystemCollection,
arg.UserID,
arg.Name,
arg.Description,
arg.Icon,
arg.Color,
arg.ShowOnDashboard,
arg.QueryType,
arg.Priority,
)
var i Collections
err := row.Scan(
&i.ID,
&i.UserID,
&i.Name,
&i.Description,
&i.Color,
&i.Icon,
&i.AutoAssignRules,
&i.ViewSettings,
&i.ShowOnDashboard,
&i.QueryType,
&i.Priority,
&i.IsSystemCollection,
&i.CreatedAt,
)
return i, err
}
const CreateUnlinkedBook = `-- name: CreateUnlinkedBook :one
INSERT INTO unlinked_books (device_id, content_id, file_path, title, author, confidence_score)
@@ -1901,12 +1948,16 @@ func (q *Queries) GetCollectionsForBook(ctx context.Context, mediaItemID pgtype.
}
const GetContinueReadingItems = `-- name: GetContinueReadingItems :many
SELECT DISTINCT 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 FROM media_items mi
INNER JOIN reading_progress rp ON rp.media_item_id = mi.id
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 FROM media_items mi
INNER JOIN (
SELECT DISTINCT ON (media_item_id) media_item_id, last_read_at
FROM reading_progress
WHERE user_id = $2
AND percentage > 0
AND percentage < 1
ORDER BY media_item_id, last_read_at DESC
) rp ON rp.media_item_id = mi.id
WHERE mi.library_id = $1
AND rp.user_id = $2
AND rp.percentage > 0
AND rp.percentage < 1
ORDER BY rp.last_read_at DESC
LIMIT $3
`
@@ -4157,11 +4208,15 @@ func (q *Queries) GetRecentlyAddedItems(ctx context.Context, arg GetRecentlyAdde
}
const GetRecentlyReadItems = `-- name: GetRecentlyReadItems :many
SELECT DISTINCT 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 FROM media_items mi
INNER JOIN reading_progress rp ON rp.media_item_id = mi.id
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 FROM media_items mi
INNER JOIN (
SELECT DISTINCT ON (media_item_id) media_item_id, last_read_at
FROM reading_progress
WHERE user_id = $2
AND percentage >= 1
ORDER BY media_item_id, last_read_at DESC
) rp ON rp.media_item_id = mi.id
WHERE mi.library_id = $1
AND rp.user_id = $2
AND rp.percentage >= 1
ORDER BY rp.last_read_at DESC
LIMIT $3
`
@@ -4392,14 +4447,15 @@ func (q *Queries) GetSyncQueueStats(ctx context.Context, deviceID pgtype.UUID) (
const GetSystemCollectionsForDashboard = `-- name: GetSystemCollectionsForDashboard :many
SELECT id, user_id, name, description, color, icon, auto_assign_rules, view_settings, show_on_dashboard, query_type, priority, is_system_collection, created_at FROM collections
WHERE user_id IS NULL
WHERE user_id = $1
AND is_system_collection = true
AND show_on_dashboard = true
ORDER BY priority ASC
`
// Dashboard collections queries
func (q *Queries) GetSystemCollectionsForDashboard(ctx context.Context) ([]Collections, error) {
rows, err := q.db.Query(ctx, GetSystemCollectionsForDashboard)
func (q *Queries) GetSystemCollectionsForDashboard(ctx context.Context, userID pgtype.UUID) ([]Collections, error) {
rows, err := q.db.Query(ctx, GetSystemCollectionsForDashboard, userID)
if err != nil {
return nil, err
}
@@ -6649,6 +6705,38 @@ func (q *Queries) RemoveBookFromKoboShelf(ctx context.Context, arg RemoveBookFro
return err
}
const ResetSystemCollectionMetadata = `-- name: ResetSystemCollectionMetadata :exec
UPDATE collections
SET description = $3,
icon = $4,
color = $5,
priority = $6
WHERE user_id = $1
AND name = $2
AND is_system_collection = true
`
type ResetSystemCollectionMetadataParams struct {
UserID pgtype.UUID `db:"user_id" json:"user_id"`
Name string `db:"name" json:"name"`
Description pgtype.Text `db:"description" json:"description"`
Icon pgtype.Text `db:"icon" json:"icon"`
Color pgtype.Text `db:"color" json:"color"`
Priority pgtype.Int4 `db:"priority" json:"priority"`
}
func (q *Queries) ResetSystemCollectionMetadata(ctx context.Context, arg ResetSystemCollectionMetadataParams) error {
_, err := q.db.Exec(ctx, ResetSystemCollectionMetadata,
arg.UserID,
arg.Name,
arg.Description,
arg.Icon,
arg.Color,
arg.Priority,
)
return err
}
const ResolveSyncConflict = `-- name: ResolveSyncConflict :one
UPDATE sync_conflicts
SET
@@ -8430,6 +8518,35 @@ func (q *Queries) UpdateUserProfile(ctx context.Context, arg UpdateUserProfilePa
return err
}
const UpdateUserRole = `-- name: UpdateUserRole :one
UPDATE users SET role = $2, updated_at = NOW() WHERE id = $1
RETURNING id, email, username, role
`
type UpdateUserRoleParams struct {
ID pgtype.UUID `db:"id" json:"id"`
Role string `db:"role" json:"role"`
}
type UpdateUserRoleRow struct {
ID pgtype.UUID `db:"id" json:"id"`
Email string `db:"email" json:"email"`
Username string `db:"username" json:"username"`
Role string `db:"role" json:"role"`
}
func (q *Queries) UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) (UpdateUserRoleRow, error) {
row := q.db.QueryRow(ctx, UpdateUserRole, arg.ID, arg.Role)
var i UpdateUserRoleRow
err := row.Scan(
&i.ID,
&i.Email,
&i.Username,
&i.Role,
)
return i, err
}
const UpdateUserTheme = `-- name: UpdateUserTheme :exec
UPDATE users SET theme = $2, updated_at = NOW() WHERE id = $1
`