Changes: - Update comments: "Bookmann UUID" → "Bookhoard UUID" - Rename sidecar struct field: Bookmann → Bookhoard - Update type names: SidecarBookmannConfig → SidecarBookhoardConfig - Fix test database name in queue_test.go - Fix uppercase env var examples in KOBO_SETUP.md Internal Go variable names (BookmannUuid, bookmannUUID) left unchanged as they're implementation details that don't affect functionality. Part of project rename to Bookhoard.
7640 lines
245 KiB
Go
7640 lines
245 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.30.0
|
|
// source: queries.sql
|
|
|
|
package database
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const AddBookToCollection = `-- name: AddBookToCollection :one
|
|
|
|
INSERT INTO collection_items (collection_id, media_item_id, added_by_user_id)
|
|
VALUES ($1, $2, $3)
|
|
ON CONFLICT (collection_id, media_item_id) DO NOTHING
|
|
RETURNING id, collection_id, media_item_id, added_at, added_by_user_id
|
|
`
|
|
|
|
type AddBookToCollectionParams struct {
|
|
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
AddedByUserID pgtype.UUID `db:"added_by_user_id" json:"added_by_user_id"`
|
|
}
|
|
|
|
// COLLECTION ITEMS QUERIES
|
|
// Add book to collection
|
|
func (q *Queries) AddBookToCollection(ctx context.Context, arg AddBookToCollectionParams) (CollectionItems, error) {
|
|
row := q.db.QueryRow(ctx, AddBookToCollection, arg.CollectionID, arg.MediaItemID, arg.AddedByUserID)
|
|
var i CollectionItems
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.CollectionID,
|
|
&i.MediaItemID,
|
|
&i.AddedAt,
|
|
&i.AddedByUserID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const AddBookToKoboShelf = `-- name: AddBookToKoboShelf :one
|
|
|
|
INSERT INTO kobo_shelves (device_id, media_item_id, shelf_name, shelf_position)
|
|
VALUES ($1, $2, $3, $4)
|
|
ON CONFLICT (device_id, media_item_id)
|
|
DO UPDATE SET
|
|
shelf_name = EXCLUDED.shelf_name,
|
|
shelf_position = EXCLUDED.shelf_position,
|
|
last_synced_at = NOW()
|
|
RETURNING id, device_id, media_item_id, shelf_name, shelf_position, added_at, last_synced_at, collection_id, position_in_collection
|
|
`
|
|
|
|
type AddBookToKoboShelfParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
ShelfName pgtype.Text `db:"shelf_name" json:"shelf_name"`
|
|
ShelfPosition pgtype.Int4 `db:"shelf_position" json:"shelf_position"`
|
|
}
|
|
|
|
// ============================================
|
|
// KOBO SHELF MANAGEMENT QUERIES (Phase 4)
|
|
// ============================================
|
|
func (q *Queries) AddBookToKoboShelf(ctx context.Context, arg AddBookToKoboShelfParams) (KoboShelves, error) {
|
|
row := q.db.QueryRow(ctx, AddBookToKoboShelf,
|
|
arg.DeviceID,
|
|
arg.MediaItemID,
|
|
arg.ShelfName,
|
|
arg.ShelfPosition,
|
|
)
|
|
var i KoboShelves
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.ShelfName,
|
|
&i.ShelfPosition,
|
|
&i.AddedAt,
|
|
&i.LastSyncedAt,
|
|
&i.CollectionID,
|
|
&i.PositionInCollection,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const AddLibraryFolder = `-- name: AddLibraryFolder :one
|
|
INSERT INTO library_folders (library_id, folder_path) VALUES ($1, $2) RETURNING id, library_id, folder_path, created_at
|
|
`
|
|
|
|
type AddLibraryFolderParams struct {
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
FolderPath string `db:"folder_path" json:"folder_path"`
|
|
}
|
|
|
|
// Library Folders queries
|
|
func (q *Queries) AddLibraryFolder(ctx context.Context, arg AddLibraryFolderParams) (LibraryFolders, error) {
|
|
row := q.db.QueryRow(ctx, AddLibraryFolder, arg.LibraryID, arg.FolderPath)
|
|
var i LibraryFolders
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.FolderPath,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const BulkUpdateFormatGroups = `-- name: BulkUpdateFormatGroups :exec
|
|
UPDATE media_items m
|
|
SET
|
|
format_group = detect_format_group(m.mime_type, m.file_path),
|
|
format_mimetype = m.mime_type,
|
|
is_reflowable = (detect_format_group(m.mime_type, m.file_path) = 'reflowable'),
|
|
has_fixed_layout = (detect_format_group(m.mime_type, m.file_path) IN ('fixed_layout', 'comic_archive')),
|
|
updated_at = NOW()
|
|
WHERE m.format_group IS NULL OR m.format_group = 'unknown'
|
|
`
|
|
|
|
// Bulk update format group for all media items
|
|
func (q *Queries) BulkUpdateFormatGroups(ctx context.Context) error {
|
|
_, err := q.db.Exec(ctx, BulkUpdateFormatGroups)
|
|
return err
|
|
}
|
|
|
|
const BulkUpdateProgressFromSync = `-- name: BulkUpdateProgressFromSync :many
|
|
SELECT bulk_update_progress_from_koreader FROM bulk_update_progress_from_koreader($1::uuid, $2::jsonb)
|
|
`
|
|
|
|
type BulkUpdateProgressFromSyncParams struct {
|
|
Column1 pgtype.UUID `db:"column_1" json:"column_1"`
|
|
Column2 []byte `db:"column_2" json:"column_2"`
|
|
}
|
|
|
|
func (q *Queries) BulkUpdateProgressFromSync(ctx context.Context, arg BulkUpdateProgressFromSyncParams) ([]interface{}, error) {
|
|
rows, err := q.db.Query(ctx, BulkUpdateProgressFromSync, arg.Column1, arg.Column2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []interface{}{}
|
|
for rows.Next() {
|
|
var bulk_update_progress_from_koreader interface{}
|
|
if err := rows.Scan(&bulk_update_progress_from_koreader); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, bulk_update_progress_from_koreader)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const CleanupExpiredOpdsTokens = `-- name: CleanupExpiredOpdsTokens :exec
|
|
DELETE FROM opds_tokens WHERE expires_at < NOW()
|
|
`
|
|
|
|
// Cleanup expired OPDS tokens
|
|
func (q *Queries) CleanupExpiredOpdsTokens(ctx context.Context) error {
|
|
_, err := q.db.Exec(ctx, CleanupExpiredOpdsTokens)
|
|
return err
|
|
}
|
|
|
|
const CleanupExpiredRefreshTokens = `-- name: CleanupExpiredRefreshTokens :exec
|
|
DELETE FROM refresh_tokens WHERE expires_at < NOW() OR (revoked_at IS NOT NULL AND revoked_at < NOW() - INTERVAL '7 days')
|
|
`
|
|
|
|
func (q *Queries) CleanupExpiredRefreshTokens(ctx context.Context) error {
|
|
_, err := q.db.Exec(ctx, CleanupExpiredRefreshTokens)
|
|
return err
|
|
}
|
|
|
|
const ClearDeviceSyncQueue = `-- name: ClearDeviceSyncQueue :exec
|
|
DELETE FROM sync_queue WHERE device_id = $1
|
|
`
|
|
|
|
func (q *Queries) ClearDeviceSyncQueue(ctx context.Context, deviceID pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, ClearDeviceSyncQueue, deviceID)
|
|
return err
|
|
}
|
|
|
|
const ClearKoboShelf = `-- name: ClearKoboShelf :exec
|
|
DELETE FROM kobo_shelves WHERE device_id = $1
|
|
`
|
|
|
|
func (q *Queries) ClearKoboShelf(ctx context.Context, deviceID pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, ClearKoboShelf, deviceID)
|
|
return err
|
|
}
|
|
|
|
const ClearKoboShelfByName = `-- name: ClearKoboShelfByName :exec
|
|
DELETE FROM kobo_shelves WHERE device_id = $1 AND shelf_name = $2
|
|
`
|
|
|
|
type ClearKoboShelfByNameParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
ShelfName pgtype.Text `db:"shelf_name" json:"shelf_name"`
|
|
}
|
|
|
|
func (q *Queries) ClearKoboShelfByName(ctx context.Context, arg ClearKoboShelfByNameParams) error {
|
|
_, err := q.db.Exec(ctx, ClearKoboShelfByName, arg.DeviceID, arg.ShelfName)
|
|
return err
|
|
}
|
|
|
|
const CountUnlinkedBooks = `-- name: CountUnlinkedBooks :one
|
|
SELECT COUNT(*) as count
|
|
FROM unlinked_books
|
|
WHERE device_id = $1 AND resolved = false
|
|
`
|
|
|
|
// Count unlinked books for a device
|
|
func (q *Queries) CountUnlinkedBooks(ctx context.Context, deviceID pgtype.UUID) (int64, error) {
|
|
row := q.db.QueryRow(ctx, CountUnlinkedBooks, deviceID)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const CountUserDevices = `-- name: CountUserDevices :one
|
|
SELECT COUNT(*) FROM devices WHERE user_id = $1
|
|
`
|
|
|
|
func (q *Queries) CountUserDevices(ctx context.Context, userID pgtype.UUID) (int64, error) {
|
|
row := q.db.QueryRow(ctx, CountUserDevices, userID)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const CreateCollection = `-- name: CreateCollection :one
|
|
|
|
INSERT INTO collections (user_id, name, description, color, icon, auto_assign_rules, view_settings)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
RETURNING id, user_id, name, description, color, icon, auto_assign_rules, view_settings, created_at
|
|
`
|
|
|
|
type CreateCollectionParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Name string `db:"name" json:"name"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
Color pgtype.Text `db:"color" json:"color"`
|
|
Icon pgtype.Text `db:"icon" json:"icon"`
|
|
AutoAssignRules []byte `db:"auto_assign_rules" json:"auto_assign_rules"`
|
|
ViewSettings []byte `db:"view_settings" json:"view_settings"`
|
|
}
|
|
|
|
// COLLECTIONS QUERIES
|
|
// Create collection
|
|
func (q *Queries) CreateCollection(ctx context.Context, arg CreateCollectionParams) (Collections, error) {
|
|
row := q.db.QueryRow(ctx, CreateCollection,
|
|
arg.UserID,
|
|
arg.Name,
|
|
arg.Description,
|
|
arg.Color,
|
|
arg.Icon,
|
|
arg.AutoAssignRules,
|
|
arg.ViewSettings,
|
|
)
|
|
var i Collections
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.Color,
|
|
&i.Icon,
|
|
&i.AutoAssignRules,
|
|
&i.ViewSettings,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateDevice = `-- name: CreateDevice :one
|
|
|
|
INSERT INTO devices (user_id, device_name, device_type, device_identifier, auth_token, sync_enabled, auto_sync, sync_frequency_minutes, device_metadata)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
RETURNING id, user_id, device_name, device_type, device_identifier, auth_token, last_sync, last_seen, sync_enabled, auto_sync, sync_frequency_minutes, device_metadata, created_at, updated_at
|
|
`
|
|
|
|
type CreateDeviceParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
DeviceName string `db:"device_name" json:"device_name"`
|
|
DeviceType string `db:"device_type" json:"device_type"`
|
|
DeviceIdentifier string `db:"device_identifier" json:"device_identifier"`
|
|
AuthToken string `db:"auth_token" json:"auth_token"`
|
|
SyncEnabled pgtype.Bool `db:"sync_enabled" json:"sync_enabled"`
|
|
AutoSync pgtype.Bool `db:"auto_sync" json:"auto_sync"`
|
|
SyncFrequencyMinutes pgtype.Int4 `db:"sync_frequency_minutes" json:"sync_frequency_minutes"`
|
|
DeviceMetadata []byte `db:"device_metadata" json:"device_metadata"`
|
|
}
|
|
|
|
// ============================================
|
|
// PHASE 2: DEVICE MANAGEMENT & AUTH (Weeks 5-6)
|
|
// ============================================
|
|
// Device Registration & Management
|
|
func (q *Queries) CreateDevice(ctx context.Context, arg CreateDeviceParams) (Devices, error) {
|
|
row := q.db.QueryRow(ctx, CreateDevice,
|
|
arg.UserID,
|
|
arg.DeviceName,
|
|
arg.DeviceType,
|
|
arg.DeviceIdentifier,
|
|
arg.AuthToken,
|
|
arg.SyncEnabled,
|
|
arg.AutoSync,
|
|
arg.SyncFrequencyMinutes,
|
|
arg.DeviceMetadata,
|
|
)
|
|
var i Devices
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
&i.DeviceIdentifier,
|
|
&i.AuthToken,
|
|
&i.LastSync,
|
|
&i.LastSeen,
|
|
&i.SyncEnabled,
|
|
&i.AutoSync,
|
|
&i.SyncFrequencyMinutes,
|
|
&i.DeviceMetadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateDeviceCatalog = `-- name: CreateDeviceCatalog :one
|
|
|
|
INSERT INTO device_catalogs (device_id, media_item_id, bookhoard_uuid, kobo_content_id, content_id_type, available, delivery_date, delivery_method)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
ON CONFLICT (device_id, kobo_content_id)
|
|
DO UPDATE SET
|
|
available = EXCLUDED.available,
|
|
delivery_date = COALESCE(EXCLUDED.delivery_date, device_catalogs.delivery_date),
|
|
delivery_method = EXCLUDED.delivery_method
|
|
RETURNING id, device_id, media_item_id, bookhoard_uuid, kobo_content_id, content_id_type, available, delivery_date, delivery_method
|
|
`
|
|
|
|
type CreateDeviceCatalogParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
BookmannUuid pgtype.UUID `db:"bookhoard_uuid" json:"bookhoard_uuid"`
|
|
KoboContentID string `db:"kobo_content_id" json:"kobo_content_id"`
|
|
ContentIDType pgtype.Text `db:"content_id_type" json:"content_id_type"`
|
|
Available pgtype.Bool `db:"available" json:"available"`
|
|
DeliveryDate pgtype.Timestamptz `db:"delivery_date" json:"delivery_date"`
|
|
DeliveryMethod pgtype.Text `db:"delivery_method" json:"delivery_method"`
|
|
}
|
|
|
|
// DEVICE CATALOGS QUERIES
|
|
// Create device catalog entry
|
|
func (q *Queries) CreateDeviceCatalog(ctx context.Context, arg CreateDeviceCatalogParams) (DeviceCatalogs, error) {
|
|
row := q.db.QueryRow(ctx, CreateDeviceCatalog,
|
|
arg.DeviceID,
|
|
arg.MediaItemID,
|
|
arg.BookmannUuid,
|
|
arg.KoboContentID,
|
|
arg.ContentIDType,
|
|
arg.Available,
|
|
arg.DeliveryDate,
|
|
arg.DeliveryMethod,
|
|
)
|
|
var i DeviceCatalogs
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.BookmannUuid,
|
|
&i.KoboContentID,
|
|
&i.ContentIDType,
|
|
&i.Available,
|
|
&i.DeliveryDate,
|
|
&i.DeliveryMethod,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateDeviceFileAlias = `-- name: CreateDeviceFileAlias :one
|
|
|
|
INSERT INTO device_file_aliases (media_item_id, device_id, file_path, file_sha256, confidence_score)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
RETURNING id, media_item_id, device_id, file_path, file_sha256, confidence_score, last_seen_at
|
|
`
|
|
|
|
type CreateDeviceFileAliasParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"`
|
|
}
|
|
|
|
// DEVICE FILE ALIASES QUERIES
|
|
// Create device file alias
|
|
func (q *Queries) CreateDeviceFileAlias(ctx context.Context, arg CreateDeviceFileAliasParams) (DeviceFileAliases, error) {
|
|
row := q.db.QueryRow(ctx, CreateDeviceFileAlias,
|
|
arg.MediaItemID,
|
|
arg.DeviceID,
|
|
arg.FilePath,
|
|
arg.FileSha256,
|
|
arg.ConfidenceScore,
|
|
)
|
|
var i DeviceFileAliases
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.DeviceID,
|
|
&i.FilePath,
|
|
&i.FileSha256,
|
|
&i.ConfidenceScore,
|
|
&i.LastSeenAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateDeviceShelfMapping = `-- name: CreateDeviceShelfMapping :one
|
|
|
|
INSERT INTO device_shelf_mappings (collection_id, device_id, device_shelf_name, sync_direction)
|
|
VALUES ($1, $2, $3, $4)
|
|
ON CONFLICT (collection_id, device_id)
|
|
DO UPDATE SET
|
|
device_shelf_name = EXCLUDED.device_shelf_name,
|
|
sync_direction = EXCLUDED.sync_direction
|
|
RETURNING id, collection_id, device_id, device_shelf_name, sync_direction, created_at
|
|
`
|
|
|
|
type CreateDeviceShelfMappingParams struct {
|
|
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
DeviceShelfName pgtype.Text `db:"device_shelf_name" json:"device_shelf_name"`
|
|
SyncDirection pgtype.Text `db:"sync_direction" json:"sync_direction"`
|
|
}
|
|
|
|
// DEVICE SHELF MAPPINGS QUERIES
|
|
// Create device shelf mapping
|
|
func (q *Queries) CreateDeviceShelfMapping(ctx context.Context, arg CreateDeviceShelfMappingParams) (DeviceShelfMappings, error) {
|
|
row := q.db.QueryRow(ctx, CreateDeviceShelfMapping,
|
|
arg.CollectionID,
|
|
arg.DeviceID,
|
|
arg.DeviceShelfName,
|
|
arg.SyncDirection,
|
|
)
|
|
var i DeviceShelfMappings
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.CollectionID,
|
|
&i.DeviceID,
|
|
&i.DeviceShelfName,
|
|
&i.SyncDirection,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateEbookNote = `-- name: CreateEbookNote :one
|
|
INSERT INTO media_notes (media_item_id, user_id, content, position)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING id, media_item_id, user_id, content, position, created_at, updated_at, percentage_location, character_start, character_end, epubcfi_location, chapter_reference, paragraph_reference, device_sync_data
|
|
`
|
|
|
|
type CreateEbookNoteParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Content string `db:"content" json:"content"`
|
|
Position pgtype.Text `db:"position" json:"position"`
|
|
}
|
|
|
|
// Backward compatibility - Ebook Notes queries (using views)
|
|
func (q *Queries) CreateEbookNote(ctx context.Context, arg CreateEbookNoteParams) (MediaNotes, error) {
|
|
row := q.db.QueryRow(ctx, CreateEbookNote,
|
|
arg.MediaItemID,
|
|
arg.UserID,
|
|
arg.Content,
|
|
arg.Position,
|
|
)
|
|
var i MediaNotes
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.Content,
|
|
&i.Position,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.PercentageLocation,
|
|
&i.CharacterStart,
|
|
&i.CharacterEnd,
|
|
&i.EpubcfiLocation,
|
|
&i.ChapterReference,
|
|
&i.ParagraphReference,
|
|
&i.DeviceSyncData,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateLibrary = `-- name: CreateLibrary :one
|
|
INSERT INTO libraries (name, description, library_type_id, created_by_admin_id)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING id, name, description, library_type_id, created_by_admin_id, created_at, updated_at
|
|
`
|
|
|
|
type CreateLibraryParams struct {
|
|
Name string `db:"name" json:"name"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
LibraryTypeID pgtype.UUID `db:"library_type_id" json:"library_type_id"`
|
|
CreatedByAdminID pgtype.UUID `db:"created_by_admin_id" json:"created_by_admin_id"`
|
|
}
|
|
|
|
// Libraries queries
|
|
func (q *Queries) CreateLibrary(ctx context.Context, arg CreateLibraryParams) (Libraries, error) {
|
|
row := q.db.QueryRow(ctx, CreateLibrary,
|
|
arg.Name,
|
|
arg.Description,
|
|
arg.LibraryTypeID,
|
|
arg.CreatedByAdminID,
|
|
)
|
|
var i Libraries
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.LibraryTypeID,
|
|
&i.CreatedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateMediaHighlight = `-- name: CreateMediaHighlight :one
|
|
INSERT INTO media_highlights (media_item_id, user_id, selection_text, start_position, end_position, color, note_id)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
RETURNING id, media_item_id, user_id, selection_text, start_position, end_position, color, note_id, created_at, updated_at, percentage_start, percentage_end, character_start, character_end, epubcfi_start, epubcfi_end, chapter_reference, paragraph_start, paragraph_end, panel_number, device_sync_data
|
|
`
|
|
|
|
type CreateMediaHighlightParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
SelectionText string `db:"selection_text" json:"selection_text"`
|
|
StartPosition pgtype.Text `db:"start_position" json:"start_position"`
|
|
EndPosition pgtype.Text `db:"end_position" json:"end_position"`
|
|
Color pgtype.Text `db:"color" json:"color"`
|
|
NoteID pgtype.UUID `db:"note_id" json:"note_id"`
|
|
}
|
|
|
|
// Media Highlights queries
|
|
func (q *Queries) CreateMediaHighlight(ctx context.Context, arg CreateMediaHighlightParams) (MediaHighlights, error) {
|
|
row := q.db.QueryRow(ctx, CreateMediaHighlight,
|
|
arg.MediaItemID,
|
|
arg.UserID,
|
|
arg.SelectionText,
|
|
arg.StartPosition,
|
|
arg.EndPosition,
|
|
arg.Color,
|
|
arg.NoteID,
|
|
)
|
|
var i MediaHighlights
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.SelectionText,
|
|
&i.StartPosition,
|
|
&i.EndPosition,
|
|
&i.Color,
|
|
&i.NoteID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.PercentageStart,
|
|
&i.PercentageEnd,
|
|
&i.CharacterStart,
|
|
&i.CharacterEnd,
|
|
&i.EpubcfiStart,
|
|
&i.EpubcfiEnd,
|
|
&i.ChapterReference,
|
|
&i.ParagraphStart,
|
|
&i.ParagraphEnd,
|
|
&i.PanelNumber,
|
|
&i.DeviceSyncData,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateMediaItem = `-- name: CreateMediaItem :one
|
|
INSERT INTO media_items (library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25)
|
|
RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, file_sha256, opf_identifier, opf_uuid, hash_confidence
|
|
`
|
|
|
|
type CreateMediaItemParams struct {
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
Isbn pgtype.Text `db:"isbn" json:"isbn"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
FileSize pgtype.Int8 `db:"file_size" json:"file_size"`
|
|
MimeType pgtype.Text `db:"mime_type" json:"mime_type"`
|
|
CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"`
|
|
Series pgtype.Text `db:"series" json:"series"`
|
|
SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"`
|
|
Tags pgtype.Text `db:"tags" json:"tags"`
|
|
Asin pgtype.Text `db:"asin" json:"asin"`
|
|
DatePublished pgtype.Date `db:"date_published" json:"date_published"`
|
|
Publisher pgtype.Text `db:"publisher" json:"publisher"`
|
|
Contributors pgtype.Text `db:"contributors" json:"contributors"`
|
|
Language pgtype.Text `db:"language" json:"language"`
|
|
Edition pgtype.Text `db:"edition" json:"edition"`
|
|
PageCount pgtype.Int4 `db:"page_count" json:"page_count"`
|
|
Genre pgtype.Text `db:"genre" json:"genre"`
|
|
CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"`
|
|
GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"`
|
|
OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"`
|
|
GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"`
|
|
AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"`
|
|
}
|
|
|
|
// Media Items queries
|
|
func (q *Queries) CreateMediaItem(ctx context.Context, arg CreateMediaItemParams) (MediaItems, error) {
|
|
row := q.db.QueryRow(ctx, CreateMediaItem,
|
|
arg.LibraryID,
|
|
arg.Title,
|
|
arg.Author,
|
|
arg.Isbn,
|
|
arg.Description,
|
|
arg.FilePath,
|
|
arg.FileSize,
|
|
arg.MimeType,
|
|
arg.CoverImagePath,
|
|
arg.Series,
|
|
arg.SeriesNumber,
|
|
arg.Tags,
|
|
arg.Asin,
|
|
arg.DatePublished,
|
|
arg.Publisher,
|
|
arg.Contributors,
|
|
arg.Language,
|
|
arg.Edition,
|
|
arg.PageCount,
|
|
arg.Genre,
|
|
arg.CopyrightYear,
|
|
arg.GoodreadsID,
|
|
arg.OpenlibraryID,
|
|
arg.GoogleBooksID,
|
|
arg.AddedByAdminID,
|
|
)
|
|
var i MediaItems
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateMediaItemFormat = `-- name: CreateMediaItemFormat :one
|
|
|
|
INSERT INTO media_item_formats (media_item_id, format_type, file_path, file_sha256, file_size_bytes, mime_type, converted_from_format_id)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
RETURNING id, media_item_id, format_type, file_path, file_sha256, file_size_bytes, mime_type, created_at, converted_from_format_id
|
|
`
|
|
|
|
type CreateMediaItemFormatParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
FormatType string `db:"format_type" json:"format_type"`
|
|
FilePath pgtype.Text `db:"file_path" json:"file_path"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
FileSizeBytes pgtype.Int8 `db:"file_size_bytes" json:"file_size_bytes"`
|
|
MimeType pgtype.Text `db:"mime_type" json:"mime_type"`
|
|
ConvertedFromFormatID pgtype.UUID `db:"converted_from_format_id" json:"converted_from_format_id"`
|
|
}
|
|
|
|
// MEDIA ITEM FORMATS QUERIES
|
|
// Create media item format
|
|
func (q *Queries) CreateMediaItemFormat(ctx context.Context, arg CreateMediaItemFormatParams) (MediaItemFormats, error) {
|
|
row := q.db.QueryRow(ctx, CreateMediaItemFormat,
|
|
arg.MediaItemID,
|
|
arg.FormatType,
|
|
arg.FilePath,
|
|
arg.FileSha256,
|
|
arg.FileSizeBytes,
|
|
arg.MimeType,
|
|
arg.ConvertedFromFormatID,
|
|
)
|
|
var i MediaItemFormats
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.FormatType,
|
|
&i.FilePath,
|
|
&i.FileSha256,
|
|
&i.FileSizeBytes,
|
|
&i.MimeType,
|
|
&i.CreatedAt,
|
|
&i.ConvertedFromFormatID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateMediaNote = `-- name: CreateMediaNote :one
|
|
INSERT INTO media_notes (media_item_id, user_id, content, position)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING id, media_item_id, user_id, content, position, created_at, updated_at, percentage_location, character_start, character_end, epubcfi_location, chapter_reference, paragraph_reference, device_sync_data
|
|
`
|
|
|
|
type CreateMediaNoteParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Content string `db:"content" json:"content"`
|
|
Position pgtype.Text `db:"position" json:"position"`
|
|
}
|
|
|
|
// Media Notes queries
|
|
func (q *Queries) CreateMediaNote(ctx context.Context, arg CreateMediaNoteParams) (MediaNotes, error) {
|
|
row := q.db.QueryRow(ctx, CreateMediaNote,
|
|
arg.MediaItemID,
|
|
arg.UserID,
|
|
arg.Content,
|
|
arg.Position,
|
|
)
|
|
var i MediaNotes
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.Content,
|
|
&i.Position,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.PercentageLocation,
|
|
&i.CharacterStart,
|
|
&i.CharacterEnd,
|
|
&i.EpubcfiLocation,
|
|
&i.ChapterReference,
|
|
&i.ParagraphReference,
|
|
&i.DeviceSyncData,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateMediaRating = `-- name: CreateMediaRating :one
|
|
INSERT INTO media_ratings (media_item_id, user_id, rating)
|
|
VALUES ($1, $2, $3)
|
|
ON CONFLICT (media_item_id, user_id)
|
|
DO UPDATE SET
|
|
rating = EXCLUDED.rating,
|
|
updated_at = NOW()
|
|
RETURNING id, media_item_id, user_id, rating, created_at, updated_at
|
|
`
|
|
|
|
type CreateMediaRatingParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Rating int32 `db:"rating" json:"rating"`
|
|
}
|
|
|
|
func (q *Queries) CreateMediaRating(ctx context.Context, arg CreateMediaRatingParams) (MediaRatings, error) {
|
|
row := q.db.QueryRow(ctx, CreateMediaRating, arg.MediaItemID, arg.UserID, arg.Rating)
|
|
var i MediaRatings
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.Rating,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateOpdsToken = `-- name: CreateOpdsToken :one
|
|
|
|
INSERT INTO opds_tokens (device_id, token, token_type, expires_at)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING id, device_id, token, token_type, expires_at, created_at
|
|
`
|
|
|
|
type CreateOpdsTokenParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
Token string `db:"token" json:"token"`
|
|
TokenType pgtype.Text `db:"token_type" json:"token_type"`
|
|
ExpiresAt pgtype.Timestamptz `db:"expires_at" json:"expires_at"`
|
|
}
|
|
|
|
// OPDS TOKENS QUERIES
|
|
// Create OPDS token
|
|
func (q *Queries) CreateOpdsToken(ctx context.Context, arg CreateOpdsTokenParams) (OpdsTokens, error) {
|
|
row := q.db.QueryRow(ctx, CreateOpdsToken,
|
|
arg.DeviceID,
|
|
arg.Token,
|
|
arg.TokenType,
|
|
arg.ExpiresAt,
|
|
)
|
|
var i OpdsTokens
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.Token,
|
|
&i.TokenType,
|
|
&i.ExpiresAt,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateOrUpdateKoboEntitlement = `-- name: CreateOrUpdateKoboEntitlement :one
|
|
|
|
INSERT INTO kobo_entitlements (device_id, media_item_id, entitlement_id, content_id, revision_number, purchase_date, kobo_metadata)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
ON CONFLICT (device_id, entitlement_id)
|
|
DO UPDATE SET
|
|
content_id = EXCLUDED.content_id,
|
|
revision_number = EXCLUDED.revision_number,
|
|
purchase_date = COALESCE(EXCLUDED.purchase_date, kobo_entitlements.purchase_date),
|
|
book_status = 'installed',
|
|
sync_status = 'synced',
|
|
kobo_metadata = EXCLUDED.kobo_metadata,
|
|
updated_at = NOW()
|
|
RETURNING id, device_id, media_item_id, entitlement_id, content_id, revision_number, purchase_date, accession_date, book_status, sync_status, kobo_metadata, created_at, updated_at
|
|
`
|
|
|
|
type CreateOrUpdateKoboEntitlementParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
EntitlementID string `db:"entitlement_id" json:"entitlement_id"`
|
|
ContentID string `db:"content_id" json:"content_id"`
|
|
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
|
PurchaseDate pgtype.Timestamptz `db:"purchase_date" json:"purchase_date"`
|
|
KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"`
|
|
}
|
|
|
|
// ============================================
|
|
// KOBO ENTITLEMENT QUERIES (Phase 4)
|
|
// ============================================
|
|
func (q *Queries) CreateOrUpdateKoboEntitlement(ctx context.Context, arg CreateOrUpdateKoboEntitlementParams) (KoboEntitlements, error) {
|
|
row := q.db.QueryRow(ctx, CreateOrUpdateKoboEntitlement,
|
|
arg.DeviceID,
|
|
arg.MediaItemID,
|
|
arg.EntitlementID,
|
|
arg.ContentID,
|
|
arg.RevisionNumber,
|
|
arg.PurchaseDate,
|
|
arg.KoboMetadata,
|
|
)
|
|
var i KoboEntitlements
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.EntitlementID,
|
|
&i.ContentID,
|
|
&i.RevisionNumber,
|
|
&i.PurchaseDate,
|
|
&i.AccessionDate,
|
|
&i.BookStatus,
|
|
&i.SyncStatus,
|
|
&i.KoboMetadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateReadingHistory = `-- name: CreateReadingHistory :one
|
|
INSERT INTO reading_history (
|
|
user_id,
|
|
media_item_id,
|
|
device_id,
|
|
progress_percentage,
|
|
reading_session_start,
|
|
reading_session_end,
|
|
pages_read,
|
|
time_spent_seconds,
|
|
device_metadata
|
|
)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
RETURNING id, user_id, media_item_id, device_id, progress_percentage, reading_session_start, reading_session_end, pages_read, time_spent_seconds, device_metadata, created_at
|
|
`
|
|
|
|
type CreateReadingHistoryParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
ProgressPercentage pgtype.Float8 `db:"progress_percentage" json:"progress_percentage"`
|
|
ReadingSessionStart pgtype.Timestamptz `db:"reading_session_start" json:"reading_session_start"`
|
|
ReadingSessionEnd pgtype.Timestamptz `db:"reading_session_end" json:"reading_session_end"`
|
|
PagesRead pgtype.Int4 `db:"pages_read" json:"pages_read"`
|
|
TimeSpentSeconds pgtype.Int4 `db:"time_spent_seconds" json:"time_spent_seconds"`
|
|
DeviceMetadata []byte `db:"device_metadata" json:"device_metadata"`
|
|
}
|
|
|
|
// Create reading history entry
|
|
func (q *Queries) CreateReadingHistory(ctx context.Context, arg CreateReadingHistoryParams) (ReadingHistory, error) {
|
|
row := q.db.QueryRow(ctx, CreateReadingHistory,
|
|
arg.UserID,
|
|
arg.MediaItemID,
|
|
arg.DeviceID,
|
|
arg.ProgressPercentage,
|
|
arg.ReadingSessionStart,
|
|
arg.ReadingSessionEnd,
|
|
arg.PagesRead,
|
|
arg.TimeSpentSeconds,
|
|
arg.DeviceMetadata,
|
|
)
|
|
var i ReadingHistory
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.MediaItemID,
|
|
&i.DeviceID,
|
|
&i.ProgressPercentage,
|
|
&i.ReadingSessionStart,
|
|
&i.ReadingSessionEnd,
|
|
&i.PagesRead,
|
|
&i.TimeSpentSeconds,
|
|
&i.DeviceMetadata,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateRefreshToken = `-- name: CreateRefreshToken :one
|
|
INSERT INTO refresh_tokens (user_id, token, expires_at)
|
|
VALUES ($1, $2, $3)
|
|
RETURNING id, user_id, token, expires_at, created_at, revoked_at
|
|
`
|
|
|
|
type CreateRefreshTokenParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Token pgtype.UUID `db:"token" json:"token"`
|
|
ExpiresAt pgtype.Timestamptz `db:"expires_at" json:"expires_at"`
|
|
}
|
|
|
|
// Refresh Tokens queries
|
|
func (q *Queries) CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshTokens, error) {
|
|
row := q.db.QueryRow(ctx, CreateRefreshToken, arg.UserID, arg.Token, arg.ExpiresAt)
|
|
var i RefreshTokens
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Token,
|
|
&i.ExpiresAt,
|
|
&i.CreatedAt,
|
|
&i.RevokedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateSyncConflict = `-- name: CreateSyncConflict :one
|
|
INSERT INTO sync_conflicts (media_item_id, user_id, conflict_type, conflict_data)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING id, media_item_id, user_id, conflict_type, conflict_data, resolution_status, resolution_data, resolved_by, resolved_at, created_at
|
|
`
|
|
|
|
type CreateSyncConflictParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
ConflictType string `db:"conflict_type" json:"conflict_type"`
|
|
ConflictData []byte `db:"conflict_data" json:"conflict_data"`
|
|
}
|
|
|
|
// Conflict Resolution
|
|
func (q *Queries) CreateSyncConflict(ctx context.Context, arg CreateSyncConflictParams) (SyncConflicts, error) {
|
|
row := q.db.QueryRow(ctx, CreateSyncConflict,
|
|
arg.MediaItemID,
|
|
arg.UserID,
|
|
arg.ConflictType,
|
|
arg.ConflictData,
|
|
)
|
|
var i SyncConflicts
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.ConflictType,
|
|
&i.ConflictData,
|
|
&i.ResolutionStatus,
|
|
&i.ResolutionData,
|
|
&i.ResolvedBy,
|
|
&i.ResolvedAt,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateSyncHistoryEntry = `-- name: CreateSyncHistoryEntry :one
|
|
INSERT INTO sync_queue (device_id, media_item_id, sync_type, sync_data, priority, status)
|
|
VALUES ($1, $2, 'koreader_progress', $3, 5, 'completed')
|
|
RETURNING id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at
|
|
`
|
|
|
|
type CreateSyncHistoryEntryParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
SyncData []byte `db:"sync_data" json:"sync_data"`
|
|
}
|
|
|
|
func (q *Queries) CreateSyncHistoryEntry(ctx context.Context, arg CreateSyncHistoryEntryParams) (SyncQueue, error) {
|
|
row := q.db.QueryRow(ctx, CreateSyncHistoryEntry, arg.DeviceID, arg.MediaItemID, arg.SyncData)
|
|
var i SyncQueue
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.SyncType,
|
|
&i.SyncData,
|
|
&i.Priority,
|
|
&i.Attempts,
|
|
&i.MaxAttempts,
|
|
&i.Status,
|
|
&i.ErrorMessage,
|
|
&i.CreatedAt,
|
|
&i.ProcessedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateSyncQueueItem = `-- name: CreateSyncQueueItem :one
|
|
INSERT INTO sync_queue (device_id, media_item_id, sync_type, sync_data, priority, max_attempts, status)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
RETURNING id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at
|
|
`
|
|
|
|
type CreateSyncQueueItemParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
SyncType string `db:"sync_type" json:"sync_type"`
|
|
SyncData []byte `db:"sync_data" json:"sync_data"`
|
|
Priority pgtype.Int4 `db:"priority" json:"priority"`
|
|
MaxAttempts pgtype.Int4 `db:"max_attempts" json:"max_attempts"`
|
|
Status pgtype.Text `db:"status" json:"status"`
|
|
}
|
|
|
|
// Sync Queue Management
|
|
func (q *Queries) CreateSyncQueueItem(ctx context.Context, arg CreateSyncQueueItemParams) (SyncQueue, error) {
|
|
row := q.db.QueryRow(ctx, CreateSyncQueueItem,
|
|
arg.DeviceID,
|
|
arg.MediaItemID,
|
|
arg.SyncType,
|
|
arg.SyncData,
|
|
arg.Priority,
|
|
arg.MaxAttempts,
|
|
arg.Status,
|
|
)
|
|
var i SyncQueue
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.SyncType,
|
|
&i.SyncData,
|
|
&i.Priority,
|
|
&i.Attempts,
|
|
&i.MaxAttempts,
|
|
&i.Status,
|
|
&i.ErrorMessage,
|
|
&i.CreatedAt,
|
|
&i.ProcessedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateUnlinkedBook = `-- name: CreateUnlinkedBook :one
|
|
|
|
INSERT INTO unlinked_books (device_id, content_id, file_path, title, author, confidence_score)
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
RETURNING id, device_id, content_id, file_path, title, author, confidence_score, resolved, media_item_id, resolved_at, resolution_method, last_seen_at, created_at
|
|
`
|
|
|
|
type CreateUnlinkedBookParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
ContentID string `db:"content_id" json:"content_id"`
|
|
FilePath pgtype.Text `db:"file_path" json:"file_path"`
|
|
Title pgtype.Text `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"`
|
|
}
|
|
|
|
// ============================================
|
|
// PHASE 6: ENHANCED KOBO SYNC (Week 3-4)
|
|
// ============================================
|
|
// Create unlinked book entry
|
|
func (q *Queries) CreateUnlinkedBook(ctx context.Context, arg CreateUnlinkedBookParams) (UnlinkedBooks, error) {
|
|
row := q.db.QueryRow(ctx, CreateUnlinkedBook,
|
|
arg.DeviceID,
|
|
arg.ContentID,
|
|
arg.FilePath,
|
|
arg.Title,
|
|
arg.Author,
|
|
arg.ConfidenceScore,
|
|
)
|
|
var i UnlinkedBooks
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.ContentID,
|
|
&i.FilePath,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.ConfidenceScore,
|
|
&i.Resolved,
|
|
&i.MediaItemID,
|
|
&i.ResolvedAt,
|
|
&i.ResolutionMethod,
|
|
&i.LastSeenAt,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateUser = `-- name: CreateUser :one
|
|
INSERT INTO users (email, username, password_hash, first_name, last_name, theme, role)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
RETURNING id, email, username, theme, first_name, last_name, role, created_at, updated_at
|
|
`
|
|
|
|
type CreateUserParams struct {
|
|
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"`
|
|
Theme pgtype.Text `db:"theme" json:"theme"`
|
|
Role string `db:"role" json:"role"`
|
|
}
|
|
|
|
type CreateUserRow 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"`
|
|
}
|
|
|
|
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateUserRow, error) {
|
|
row := q.db.QueryRow(ctx, CreateUser,
|
|
arg.Email,
|
|
arg.Username,
|
|
arg.PasswordHash,
|
|
arg.FirstName,
|
|
arg.LastName,
|
|
arg.Theme,
|
|
arg.Role,
|
|
)
|
|
var i CreateUserRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Email,
|
|
&i.Username,
|
|
&i.Theme,
|
|
&i.FirstName,
|
|
&i.LastName,
|
|
&i.Role,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const DeleteCollection = `-- name: DeleteCollection :exec
|
|
DELETE FROM collections WHERE id = $1
|
|
`
|
|
|
|
// Delete collection
|
|
func (q *Queries) DeleteCollection(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteCollection, id)
|
|
return err
|
|
}
|
|
|
|
const DeleteDevice = `-- name: DeleteDevice :exec
|
|
DELETE FROM devices WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) DeleteDevice(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteDevice, id)
|
|
return err
|
|
}
|
|
|
|
const DeleteDeviceByToken = `-- name: DeleteDeviceByToken :exec
|
|
DELETE FROM devices WHERE auth_token = $1
|
|
`
|
|
|
|
func (q *Queries) DeleteDeviceByToken(ctx context.Context, authToken string) error {
|
|
_, err := q.db.Exec(ctx, DeleteDeviceByToken, authToken)
|
|
return err
|
|
}
|
|
|
|
const DeleteDeviceCatalog = `-- name: DeleteDeviceCatalog :exec
|
|
DELETE FROM device_catalogs WHERE id = $1
|
|
`
|
|
|
|
// Delete device catalog entry
|
|
func (q *Queries) DeleteDeviceCatalog(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteDeviceCatalog, id)
|
|
return err
|
|
}
|
|
|
|
const DeleteDeviceFileAlias = `-- name: DeleteDeviceFileAlias :exec
|
|
DELETE FROM device_file_aliases WHERE id = $1
|
|
`
|
|
|
|
// Delete device file alias
|
|
func (q *Queries) DeleteDeviceFileAlias(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteDeviceFileAlias, id)
|
|
return err
|
|
}
|
|
|
|
const DeleteDeviceShelfMapping = `-- name: DeleteDeviceShelfMapping :exec
|
|
DELETE FROM device_shelf_mappings WHERE id = $1
|
|
`
|
|
|
|
// Delete device shelf mapping
|
|
func (q *Queries) DeleteDeviceShelfMapping(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteDeviceShelfMapping, id)
|
|
return err
|
|
}
|
|
|
|
const DeleteEbookNote = `-- name: DeleteEbookNote :exec
|
|
DELETE FROM media_notes WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) DeleteEbookNote(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteEbookNote, id)
|
|
return err
|
|
}
|
|
|
|
const DeleteKoboEntitlement = `-- name: DeleteKoboEntitlement :exec
|
|
DELETE FROM kobo_entitlements WHERE device_id = $1 AND entitlement_id = $2
|
|
`
|
|
|
|
type DeleteKoboEntitlementParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
EntitlementID string `db:"entitlement_id" json:"entitlement_id"`
|
|
}
|
|
|
|
func (q *Queries) DeleteKoboEntitlement(ctx context.Context, arg DeleteKoboEntitlementParams) error {
|
|
_, err := q.db.Exec(ctx, DeleteKoboEntitlement, arg.DeviceID, arg.EntitlementID)
|
|
return err
|
|
}
|
|
|
|
const DeleteLibrary = `-- name: DeleteLibrary :exec
|
|
DELETE FROM libraries WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) DeleteLibrary(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteLibrary, id)
|
|
return err
|
|
}
|
|
|
|
const DeleteLibraryFolder = `-- name: DeleteLibraryFolder :one
|
|
DELETE FROM library_folders WHERE library_id = $1 AND folder_path = $2 RETURNING id, library_id, folder_path, created_at
|
|
`
|
|
|
|
type DeleteLibraryFolderParams struct {
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
FolderPath string `db:"folder_path" json:"folder_path"`
|
|
}
|
|
|
|
func (q *Queries) DeleteLibraryFolder(ctx context.Context, arg DeleteLibraryFolderParams) (LibraryFolders, error) {
|
|
row := q.db.QueryRow(ctx, DeleteLibraryFolder, arg.LibraryID, arg.FolderPath)
|
|
var i LibraryFolders
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.FolderPath,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const DeleteMediaHighlight = `-- name: DeleteMediaHighlight :exec
|
|
DELETE FROM media_highlights WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) DeleteMediaHighlight(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteMediaHighlight, id)
|
|
return err
|
|
}
|
|
|
|
const DeleteMediaItem = `-- name: DeleteMediaItem :exec
|
|
DELETE FROM media_items WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) DeleteMediaItem(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteMediaItem, id)
|
|
return err
|
|
}
|
|
|
|
const DeleteMediaItemFormat = `-- name: DeleteMediaItemFormat :exec
|
|
DELETE FROM media_item_formats WHERE id = $1
|
|
`
|
|
|
|
// Delete media item format
|
|
func (q *Queries) DeleteMediaItemFormat(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteMediaItemFormat, id)
|
|
return err
|
|
}
|
|
|
|
const DeleteMediaNote = `-- name: DeleteMediaNote :exec
|
|
DELETE FROM media_notes WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) DeleteMediaNote(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteMediaNote, id)
|
|
return err
|
|
}
|
|
|
|
const DeleteMediaRating = `-- name: DeleteMediaRating :exec
|
|
DELETE FROM media_ratings WHERE media_item_id = $1 AND user_id = $2
|
|
`
|
|
|
|
type DeleteMediaRatingParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
func (q *Queries) DeleteMediaRating(ctx context.Context, arg DeleteMediaRatingParams) error {
|
|
_, err := q.db.Exec(ctx, DeleteMediaRating, arg.MediaItemID, arg.UserID)
|
|
return err
|
|
}
|
|
|
|
const DeleteReadingProgress = `-- name: DeleteReadingProgress :exec
|
|
DELETE FROM reading_progress WHERE media_item_id = $1 AND user_id = $2
|
|
`
|
|
|
|
type DeleteReadingProgressParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
func (q *Queries) DeleteReadingProgress(ctx context.Context, arg DeleteReadingProgressParams) error {
|
|
_, err := q.db.Exec(ctx, DeleteReadingProgress, arg.MediaItemID, arg.UserID)
|
|
return err
|
|
}
|
|
|
|
const DeleteSyncConflict = `-- name: DeleteSyncConflict :exec
|
|
DELETE FROM sync_conflicts WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) DeleteSyncConflict(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteSyncConflict, id)
|
|
return err
|
|
}
|
|
|
|
const DeleteSyncQueueItem = `-- name: DeleteSyncQueueItem :exec
|
|
DELETE FROM sync_queue WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) DeleteSyncQueueItem(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteSyncQueueItem, id)
|
|
return err
|
|
}
|
|
|
|
const DeleteSystemConfig = `-- name: DeleteSystemConfig :exec
|
|
DELETE FROM system_config WHERE key = $1
|
|
`
|
|
|
|
// Delete system config
|
|
func (q *Queries) DeleteSystemConfig(ctx context.Context, key string) error {
|
|
_, err := q.db.Exec(ctx, DeleteSystemConfig, key)
|
|
return err
|
|
}
|
|
|
|
const DeleteUnlinkedBook = `-- name: DeleteUnlinkedBook :exec
|
|
DELETE FROM unlinked_books WHERE id = $1
|
|
`
|
|
|
|
// Delete unlinked book
|
|
func (q *Queries) DeleteUnlinkedBook(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteUnlinkedBook, id)
|
|
return err
|
|
}
|
|
|
|
const DeleteUser = `-- name: DeleteUser :exec
|
|
DELETE FROM users WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) DeleteUser(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteUser, id)
|
|
return err
|
|
}
|
|
|
|
const GenerateKoboEntitlementId = `-- name: GenerateKoboEntitlementId :one
|
|
SELECT 'kobo_' || uuid_generate_v4()::TEXT as entitlement_id
|
|
`
|
|
|
|
func (q *Queries) GenerateKoboEntitlementId(ctx context.Context) (interface{}, error) {
|
|
row := q.db.QueryRow(ctx, GenerateKoboEntitlementId)
|
|
var entitlement_id interface{}
|
|
err := row.Scan(&entitlement_id)
|
|
return entitlement_id, err
|
|
}
|
|
|
|
const GetAllSystemConfig = `-- name: GetAllSystemConfig :many
|
|
SELECT key, value, updated_at, updated_by FROM system_config ORDER BY key
|
|
`
|
|
|
|
// Get all system config
|
|
func (q *Queries) GetAllSystemConfig(ctx context.Context) ([]SystemConfig, error) {
|
|
rows, err := q.db.Query(ctx, GetAllSystemConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SystemConfig{}
|
|
for rows.Next() {
|
|
var i SystemConfig
|
|
if err := rows.Scan(
|
|
&i.Key,
|
|
&i.Value,
|
|
&i.UpdatedAt,
|
|
&i.UpdatedBy,
|
|
); 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,
|
|
mh.selection_text,
|
|
mh.start_position,
|
|
mh.end_position,
|
|
mh.color,
|
|
mh.created_at,
|
|
mh.updated_at,
|
|
'highlight' as annotation_type,
|
|
mh.percentage_start,
|
|
mh.percentage_end,
|
|
mh.epubcfi_start,
|
|
mh.epubcfi_end
|
|
FROM media_highlights mh
|
|
WHERE mh.media_item_id = $1 AND mh.user_id = $2
|
|
UNION ALL
|
|
SELECT
|
|
mn.id,
|
|
mn.content,
|
|
mn.position,
|
|
NULL as end_position,
|
|
NULL as color,
|
|
mn.created_at,
|
|
mn.updated_at,
|
|
'note' as annotation_type,
|
|
mn.percentage_location as percentage_start,
|
|
NULL as percentage_end,
|
|
mn.epubcfi_location as epubcfi_start,
|
|
NULL as epubcfi_end
|
|
FROM media_notes mn
|
|
WHERE mn.media_item_id = $1 AND mn.user_id = $2
|
|
ORDER BY created_at DESC
|
|
`
|
|
|
|
type GetAnnotationsForBookParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
type GetAnnotationsForBookRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
SelectionText string `db:"selection_text" json:"selection_text"`
|
|
StartPosition pgtype.Text `db:"start_position" json:"start_position"`
|
|
EndPosition pgtype.Text `db:"end_position" json:"end_position"`
|
|
Color pgtype.Text `db:"color" json:"color"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
AnnotationType string `db:"annotation_type" json:"annotation_type"`
|
|
PercentageStart pgtype.Float8 `db:"percentage_start" json:"percentage_start"`
|
|
PercentageEnd pgtype.Float8 `db:"percentage_end" json:"percentage_end"`
|
|
EpubcfiStart pgtype.Text `db:"epubcfi_start" json:"epubcfi_start"`
|
|
EpubcfiEnd pgtype.Text `db:"epubcfi_end" json:"epubcfi_end"`
|
|
}
|
|
|
|
func (q *Queries) GetAnnotationsForBook(ctx context.Context, arg GetAnnotationsForBookParams) ([]GetAnnotationsForBookRow, error) {
|
|
rows, err := q.db.Query(ctx, GetAnnotationsForBook, arg.MediaItemID, arg.UserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetAnnotationsForBookRow{}
|
|
for rows.Next() {
|
|
var i GetAnnotationsForBookRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.SelectionText,
|
|
&i.StartPosition,
|
|
&i.EndPosition,
|
|
&i.Color,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.AnnotationType,
|
|
&i.PercentageStart,
|
|
&i.PercentageEnd,
|
|
&i.EpubcfiStart,
|
|
&i.EpubcfiEnd,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetCollection = `-- name: GetCollection :one
|
|
SELECT id, user_id, name, description, color, icon, auto_assign_rules, view_settings, created_at FROM collections WHERE id = $1
|
|
`
|
|
|
|
// Get collection
|
|
func (q *Queries) GetCollection(ctx context.Context, id pgtype.UUID) (Collections, error) {
|
|
row := q.db.QueryRow(ctx, GetCollection, id)
|
|
var i Collections
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.Color,
|
|
&i.Icon,
|
|
&i.AutoAssignRules,
|
|
&i.ViewSettings,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetCollectionItems = `-- name: GetCollectionItems :many
|
|
SELECT ci.id, ci.collection_id, ci.media_item_id, ci.added_at, ci.added_by_user_id, mi.title, mi.author, mi.cover_image_path
|
|
FROM collection_items ci
|
|
JOIN media_items mi ON ci.media_item_id = mi.id
|
|
WHERE ci.collection_id = $1
|
|
ORDER BY ci.added_at DESC
|
|
`
|
|
|
|
type GetCollectionItemsRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
AddedAt pgtype.Timestamptz `db:"added_at" json:"added_at"`
|
|
AddedByUserID pgtype.UUID `db:"added_by_user_id" json:"added_by_user_id"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"`
|
|
}
|
|
|
|
// Get collection items
|
|
func (q *Queries) GetCollectionItems(ctx context.Context, collectionID pgtype.UUID) ([]GetCollectionItemsRow, error) {
|
|
rows, err := q.db.Query(ctx, GetCollectionItems, collectionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetCollectionItemsRow{}
|
|
for rows.Next() {
|
|
var i GetCollectionItemsRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.CollectionID,
|
|
&i.MediaItemID,
|
|
&i.AddedAt,
|
|
&i.AddedByUserID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.CoverImagePath,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetCollectionWithBookCount = `-- name: GetCollectionWithBookCount :one
|
|
SELECT
|
|
c.id, c.user_id, c.name, c.description, c.color, c.icon, c.auto_assign_rules, c.view_settings, c.created_at,
|
|
COUNT(ci.id) as book_count
|
|
FROM collections c
|
|
LEFT JOIN collection_items ci ON c.id = ci.collection_id
|
|
WHERE c.id = $1
|
|
GROUP BY c.id
|
|
`
|
|
|
|
type GetCollectionWithBookCountRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Name string `db:"name" json:"name"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
Color pgtype.Text `db:"color" json:"color"`
|
|
Icon pgtype.Text `db:"icon" json:"icon"`
|
|
AutoAssignRules []byte `db:"auto_assign_rules" json:"auto_assign_rules"`
|
|
ViewSettings []byte `db:"view_settings" json:"view_settings"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
BookCount int64 `db:"book_count" json:"book_count"`
|
|
}
|
|
|
|
// Get collection with book count
|
|
func (q *Queries) GetCollectionWithBookCount(ctx context.Context, id pgtype.UUID) (GetCollectionWithBookCountRow, error) {
|
|
row := q.db.QueryRow(ctx, GetCollectionWithBookCount, id)
|
|
var i GetCollectionWithBookCountRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.Color,
|
|
&i.Icon,
|
|
&i.AutoAssignRules,
|
|
&i.ViewSettings,
|
|
&i.CreatedAt,
|
|
&i.BookCount,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetCollectionsByUser = `-- name: GetCollectionsByUser :many
|
|
SELECT id, user_id, name, description, color, icon, auto_assign_rules, view_settings, created_at FROM collections WHERE user_id = $1 ORDER BY created_at DESC
|
|
`
|
|
|
|
// Get collections by user
|
|
func (q *Queries) GetCollectionsByUser(ctx context.Context, userID pgtype.UUID) ([]Collections, error) {
|
|
rows, err := q.db.Query(ctx, GetCollectionsByUser, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Collections{}
|
|
for rows.Next() {
|
|
var i Collections
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.Color,
|
|
&i.Icon,
|
|
&i.AutoAssignRules,
|
|
&i.ViewSettings,
|
|
&i.CreatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetCollectionsForBook = `-- name: GetCollectionsForBook :many
|
|
SELECT c.id, c.user_id, c.name, c.description, c.color, c.icon, c.auto_assign_rules, c.view_settings, c.created_at
|
|
FROM collections c
|
|
JOIN collection_items ci ON c.id = ci.collection_id
|
|
WHERE ci.media_item_id = $1
|
|
`
|
|
|
|
// Get collections for book
|
|
func (q *Queries) GetCollectionsForBook(ctx context.Context, mediaItemID pgtype.UUID) ([]Collections, error) {
|
|
rows, err := q.db.Query(ctx, GetCollectionsForBook, mediaItemID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Collections{}
|
|
for rows.Next() {
|
|
var i Collections
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.Color,
|
|
&i.Icon,
|
|
&i.AutoAssignRules,
|
|
&i.ViewSettings,
|
|
&i.CreatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetDevice = `-- name: GetDevice :one
|
|
SELECT id, user_id, device_name, device_type, device_identifier, auth_token, last_sync, last_seen, sync_enabled, auto_sync, sync_frequency_minutes, device_metadata, created_at, updated_at FROM devices WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetDevice(ctx context.Context, id pgtype.UUID) (Devices, error) {
|
|
row := q.db.QueryRow(ctx, GetDevice, id)
|
|
var i Devices
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
&i.DeviceIdentifier,
|
|
&i.AuthToken,
|
|
&i.LastSync,
|
|
&i.LastSeen,
|
|
&i.SyncEnabled,
|
|
&i.AutoSync,
|
|
&i.SyncFrequencyMinutes,
|
|
&i.DeviceMetadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetDeviceByAuthToken = `-- name: GetDeviceByAuthToken :one
|
|
SELECT id, user_id, device_name, device_type, device_identifier, auth_token, last_sync, last_seen, sync_enabled, auto_sync, sync_frequency_minutes, device_metadata, created_at, updated_at FROM devices WHERE auth_token = $1
|
|
`
|
|
|
|
func (q *Queries) GetDeviceByAuthToken(ctx context.Context, authToken string) (Devices, error) {
|
|
row := q.db.QueryRow(ctx, GetDeviceByAuthToken, authToken)
|
|
var i Devices
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
&i.DeviceIdentifier,
|
|
&i.AuthToken,
|
|
&i.LastSync,
|
|
&i.LastSeen,
|
|
&i.SyncEnabled,
|
|
&i.AutoSync,
|
|
&i.SyncFrequencyMinutes,
|
|
&i.DeviceMetadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetDeviceByIdentifier = `-- name: GetDeviceByIdentifier :one
|
|
SELECT id, user_id, device_name, device_type, device_identifier, auth_token, last_sync, last_seen, sync_enabled, auto_sync, sync_frequency_minutes, device_metadata, created_at, updated_at FROM devices WHERE device_identifier = $1
|
|
`
|
|
|
|
func (q *Queries) GetDeviceByIdentifier(ctx context.Context, deviceIdentifier string) (Devices, error) {
|
|
row := q.db.QueryRow(ctx, GetDeviceByIdentifier, deviceIdentifier)
|
|
var i Devices
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
&i.DeviceIdentifier,
|
|
&i.AuthToken,
|
|
&i.LastSync,
|
|
&i.LastSeen,
|
|
&i.SyncEnabled,
|
|
&i.AutoSync,
|
|
&i.SyncFrequencyMinutes,
|
|
&i.DeviceMetadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetDeviceCatalogByBookhoardUUID = `-- name: GetDeviceCatalogByBookhoardUUID :one
|
|
SELECT id, device_id, media_item_id, bookhoard_uuid, kobo_content_id, content_id_type, available, delivery_date, delivery_method FROM device_catalogs WHERE device_id = $1 AND bookhoard_uuid = $2
|
|
`
|
|
|
|
type GetDeviceCatalogByBookhoardUUIDParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
BookmannUuid pgtype.UUID `db:"bookhoard_uuid" json:"bookhoard_uuid"`
|
|
}
|
|
|
|
// Get device catalog by Bookhoard UUID
|
|
func (q *Queries) GetDeviceCatalogByBookhoardUUID(ctx context.Context, arg GetDeviceCatalogByBookhoardUUIDParams) (DeviceCatalogs, error) {
|
|
row := q.db.QueryRow(ctx, GetDeviceCatalogByBookhoardUUID, arg.DeviceID, arg.BookmannUuid)
|
|
var i DeviceCatalogs
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.BookmannUuid,
|
|
&i.KoboContentID,
|
|
&i.ContentIDType,
|
|
&i.Available,
|
|
&i.DeliveryDate,
|
|
&i.DeliveryMethod,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetDeviceCatalogByKoboContentId = `-- name: GetDeviceCatalogByKoboContentId :one
|
|
SELECT id, device_id, media_item_id, bookhoard_uuid, kobo_content_id, content_id_type, available, delivery_date, delivery_method FROM device_catalogs WHERE kobo_content_id = $1
|
|
`
|
|
|
|
// Get device catalog by Kobo ContentId
|
|
func (q *Queries) GetDeviceCatalogByKoboContentId(ctx context.Context, koboContentID string) (DeviceCatalogs, error) {
|
|
row := q.db.QueryRow(ctx, GetDeviceCatalogByKoboContentId, koboContentID)
|
|
var i DeviceCatalogs
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.BookmannUuid,
|
|
&i.KoboContentID,
|
|
&i.ContentIDType,
|
|
&i.Available,
|
|
&i.DeliveryDate,
|
|
&i.DeliveryMethod,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetDeviceCatalogEntries = `-- name: GetDeviceCatalogEntries :many
|
|
SELECT dc.id, dc.device_id, dc.media_item_id, dc.bookhoard_uuid, dc.kobo_content_id, dc.content_id_type, dc.available, dc.delivery_date, dc.delivery_method, mi.title, mi.author
|
|
FROM device_catalogs dc
|
|
JOIN media_items mi ON dc.media_item_id = mi.id
|
|
WHERE dc.device_id = $1 AND dc.available = true
|
|
ORDER BY dc.delivery_date DESC
|
|
`
|
|
|
|
type GetDeviceCatalogEntriesRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
BookmannUuid pgtype.UUID `db:"bookhoard_uuid" json:"bookhoard_uuid"`
|
|
KoboContentID string `db:"kobo_content_id" json:"kobo_content_id"`
|
|
ContentIDType pgtype.Text `db:"content_id_type" json:"content_id_type"`
|
|
Available pgtype.Bool `db:"available" json:"available"`
|
|
DeliveryDate pgtype.Timestamptz `db:"delivery_date" json:"delivery_date"`
|
|
DeliveryMethod pgtype.Text `db:"delivery_method" json:"delivery_method"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
}
|
|
|
|
// Get device catalog entries
|
|
func (q *Queries) GetDeviceCatalogEntries(ctx context.Context, deviceID pgtype.UUID) ([]GetDeviceCatalogEntriesRow, error) {
|
|
rows, err := q.db.Query(ctx, GetDeviceCatalogEntries, deviceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetDeviceCatalogEntriesRow{}
|
|
for rows.Next() {
|
|
var i GetDeviceCatalogEntriesRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.BookmannUuid,
|
|
&i.KoboContentID,
|
|
&i.ContentIDType,
|
|
&i.Available,
|
|
&i.DeliveryDate,
|
|
&i.DeliveryMethod,
|
|
&i.Title,
|
|
&i.Author,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetDeviceFileAlias = `-- name: GetDeviceFileAlias :one
|
|
SELECT id, media_item_id, device_id, file_path, file_sha256, confidence_score, last_seen_at FROM device_file_aliases WHERE device_id = $1 AND file_path = $2
|
|
`
|
|
|
|
type GetDeviceFileAliasParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
}
|
|
|
|
// Get device file alias
|
|
func (q *Queries) GetDeviceFileAlias(ctx context.Context, arg GetDeviceFileAliasParams) (DeviceFileAliases, error) {
|
|
row := q.db.QueryRow(ctx, GetDeviceFileAlias, arg.DeviceID, arg.FilePath)
|
|
var i DeviceFileAliases
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.DeviceID,
|
|
&i.FilePath,
|
|
&i.FileSha256,
|
|
&i.ConfidenceScore,
|
|
&i.LastSeenAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetDeviceFileAliasBySHA256 = `-- name: GetDeviceFileAliasBySHA256 :one
|
|
SELECT dfa.id, dfa.media_item_id, dfa.device_id, dfa.file_path, dfa.file_sha256, dfa.confidence_score, dfa.last_seen_at, mi.title, mi.author
|
|
FROM device_file_aliases dfa
|
|
JOIN media_items mi ON dfa.media_item_id = mi.id
|
|
WHERE dfa.file_sha256 = $1
|
|
`
|
|
|
|
type GetDeviceFileAliasBySHA256Row struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"`
|
|
LastSeenAt pgtype.Timestamptz `db:"last_seen_at" json:"last_seen_at"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
}
|
|
|
|
// Get device file alias by SHA-256
|
|
func (q *Queries) GetDeviceFileAliasBySHA256(ctx context.Context, fileSha256 pgtype.Text) (GetDeviceFileAliasBySHA256Row, error) {
|
|
row := q.db.QueryRow(ctx, GetDeviceFileAliasBySHA256, fileSha256)
|
|
var i GetDeviceFileAliasBySHA256Row
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.DeviceID,
|
|
&i.FilePath,
|
|
&i.FileSha256,
|
|
&i.ConfidenceScore,
|
|
&i.LastSeenAt,
|
|
&i.Title,
|
|
&i.Author,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetDeviceFileAliasesByDevice = `-- name: GetDeviceFileAliasesByDevice :many
|
|
SELECT dfa.id, dfa.media_item_id, dfa.device_id, dfa.file_path, dfa.file_sha256, dfa.confidence_score, dfa.last_seen_at, mi.title, mi.author
|
|
FROM device_file_aliases dfa
|
|
JOIN media_items mi ON dfa.media_item_id = mi.id
|
|
WHERE dfa.device_id = $1
|
|
ORDER BY dfa.last_seen_at DESC
|
|
`
|
|
|
|
type GetDeviceFileAliasesByDeviceRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"`
|
|
LastSeenAt pgtype.Timestamptz `db:"last_seen_at" json:"last_seen_at"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
}
|
|
|
|
// Get device file aliases by device
|
|
func (q *Queries) GetDeviceFileAliasesByDevice(ctx context.Context, deviceID pgtype.UUID) ([]GetDeviceFileAliasesByDeviceRow, error) {
|
|
rows, err := q.db.Query(ctx, GetDeviceFileAliasesByDevice, deviceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetDeviceFileAliasesByDeviceRow{}
|
|
for rows.Next() {
|
|
var i GetDeviceFileAliasesByDeviceRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.DeviceID,
|
|
&i.FilePath,
|
|
&i.FileSha256,
|
|
&i.ConfidenceScore,
|
|
&i.LastSeenAt,
|
|
&i.Title,
|
|
&i.Author,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetDeviceShelfMapping = `-- name: GetDeviceShelfMapping :one
|
|
SELECT id, collection_id, device_id, device_shelf_name, sync_direction, created_at FROM device_shelf_mappings WHERE device_id = $1 AND collection_id = $2
|
|
`
|
|
|
|
type GetDeviceShelfMappingParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
|
}
|
|
|
|
// Get device shelf mapping
|
|
func (q *Queries) GetDeviceShelfMapping(ctx context.Context, arg GetDeviceShelfMappingParams) (DeviceShelfMappings, error) {
|
|
row := q.db.QueryRow(ctx, GetDeviceShelfMapping, arg.DeviceID, arg.CollectionID)
|
|
var i DeviceShelfMappings
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.CollectionID,
|
|
&i.DeviceID,
|
|
&i.DeviceShelfName,
|
|
&i.SyncDirection,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetDeviceShelfMappings = `-- name: GetDeviceShelfMappings :many
|
|
SELECT dsm.id, dsm.collection_id, dsm.device_id, dsm.device_shelf_name, dsm.sync_direction, dsm.created_at, c.name as collection_name, c.icon as collection_icon
|
|
FROM device_shelf_mappings dsm
|
|
JOIN collections c ON dsm.collection_id = c.id
|
|
WHERE dsm.device_id = $1
|
|
`
|
|
|
|
type GetDeviceShelfMappingsRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
DeviceShelfName pgtype.Text `db:"device_shelf_name" json:"device_shelf_name"`
|
|
SyncDirection pgtype.Text `db:"sync_direction" json:"sync_direction"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
CollectionName string `db:"collection_name" json:"collection_name"`
|
|
CollectionIcon pgtype.Text `db:"collection_icon" json:"collection_icon"`
|
|
}
|
|
|
|
// Get device shelf mappings
|
|
func (q *Queries) GetDeviceShelfMappings(ctx context.Context, deviceID pgtype.UUID) ([]GetDeviceShelfMappingsRow, error) {
|
|
rows, err := q.db.Query(ctx, GetDeviceShelfMappings, deviceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetDeviceShelfMappingsRow{}
|
|
for rows.Next() {
|
|
var i GetDeviceShelfMappingsRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.CollectionID,
|
|
&i.DeviceID,
|
|
&i.DeviceShelfName,
|
|
&i.SyncDirection,
|
|
&i.CreatedAt,
|
|
&i.CollectionName,
|
|
&i.CollectionIcon,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetFailedSyncQueueItems = `-- name: GetFailedSyncQueueItems :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 = 'failed' AND attempts < max_attempts
|
|
ORDER BY priority ASC, created_at ASC
|
|
LIMIT $1
|
|
`
|
|
|
|
func (q *Queries) GetFailedSyncQueueItems(ctx context.Context, limit int32) ([]SyncQueue, error) {
|
|
rows, err := q.db.Query(ctx, GetFailedSyncQueueItems, limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SyncQueue{}
|
|
for rows.Next() {
|
|
var i SyncQueue
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.SyncType,
|
|
&i.SyncData,
|
|
&i.Priority,
|
|
&i.Attempts,
|
|
&i.MaxAttempts,
|
|
&i.Status,
|
|
&i.ErrorMessage,
|
|
&i.CreatedAt,
|
|
&i.ProcessedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetKoboEntitlementByContentId = `-- name: GetKoboEntitlementByContentId :one
|
|
SELECT ke.id, ke.device_id, ke.media_item_id, ke.entitlement_id, ke.content_id, ke.revision_number, ke.purchase_date, ke.accession_date, ke.book_status, ke.sync_status, ke.kobo_metadata, ke.created_at, ke.updated_at, mi.title, mi.author, mi.file_path
|
|
FROM kobo_entitlements ke
|
|
JOIN media_items mi ON ke.media_item_id = mi.id
|
|
WHERE ke.device_id = $1 AND ke.content_id = $2
|
|
`
|
|
|
|
type GetKoboEntitlementByContentIdParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
ContentID string `db:"content_id" json:"content_id"`
|
|
}
|
|
|
|
type GetKoboEntitlementByContentIdRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
EntitlementID string `db:"entitlement_id" json:"entitlement_id"`
|
|
ContentID string `db:"content_id" json:"content_id"`
|
|
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
|
PurchaseDate pgtype.Timestamptz `db:"purchase_date" json:"purchase_date"`
|
|
AccessionDate pgtype.Timestamptz `db:"accession_date" json:"accession_date"`
|
|
BookStatus pgtype.Text `db:"book_status" json:"book_status"`
|
|
SyncStatus pgtype.Text `db:"sync_status" json:"sync_status"`
|
|
KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
}
|
|
|
|
func (q *Queries) GetKoboEntitlementByContentId(ctx context.Context, arg GetKoboEntitlementByContentIdParams) (GetKoboEntitlementByContentIdRow, error) {
|
|
row := q.db.QueryRow(ctx, GetKoboEntitlementByContentId, arg.DeviceID, arg.ContentID)
|
|
var i GetKoboEntitlementByContentIdRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.EntitlementID,
|
|
&i.ContentID,
|
|
&i.RevisionNumber,
|
|
&i.PurchaseDate,
|
|
&i.AccessionDate,
|
|
&i.BookStatus,
|
|
&i.SyncStatus,
|
|
&i.KoboMetadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.FilePath,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetKoboEntitlementByEntitlementId = `-- name: GetKoboEntitlementByEntitlementId :one
|
|
SELECT ke.id, ke.device_id, ke.media_item_id, ke.entitlement_id, ke.content_id, ke.revision_number, ke.purchase_date, ke.accession_date, ke.book_status, ke.sync_status, ke.kobo_metadata, ke.created_at, ke.updated_at, mi.title, mi.author, mi.file_path
|
|
FROM kobo_entitlements ke
|
|
JOIN media_items mi ON ke.media_item_id = mi.id
|
|
WHERE ke.device_id = $1 AND ke.entitlement_id = $2
|
|
`
|
|
|
|
type GetKoboEntitlementByEntitlementIdParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
EntitlementID string `db:"entitlement_id" json:"entitlement_id"`
|
|
}
|
|
|
|
type GetKoboEntitlementByEntitlementIdRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
EntitlementID string `db:"entitlement_id" json:"entitlement_id"`
|
|
ContentID string `db:"content_id" json:"content_id"`
|
|
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
|
PurchaseDate pgtype.Timestamptz `db:"purchase_date" json:"purchase_date"`
|
|
AccessionDate pgtype.Timestamptz `db:"accession_date" json:"accession_date"`
|
|
BookStatus pgtype.Text `db:"book_status" json:"book_status"`
|
|
SyncStatus pgtype.Text `db:"sync_status" json:"sync_status"`
|
|
KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
}
|
|
|
|
func (q *Queries) GetKoboEntitlementByEntitlementId(ctx context.Context, arg GetKoboEntitlementByEntitlementIdParams) (GetKoboEntitlementByEntitlementIdRow, error) {
|
|
row := q.db.QueryRow(ctx, GetKoboEntitlementByEntitlementId, arg.DeviceID, arg.EntitlementID)
|
|
var i GetKoboEntitlementByEntitlementIdRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.EntitlementID,
|
|
&i.ContentID,
|
|
&i.RevisionNumber,
|
|
&i.PurchaseDate,
|
|
&i.AccessionDate,
|
|
&i.BookStatus,
|
|
&i.SyncStatus,
|
|
&i.KoboMetadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.FilePath,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetKoboEntitlementsForDevice = `-- name: GetKoboEntitlementsForDevice :many
|
|
SELECT ke.id, ke.device_id, ke.media_item_id, ke.entitlement_id, ke.content_id, ke.revision_number, ke.purchase_date, ke.accession_date, ke.book_status, ke.sync_status, ke.kobo_metadata, ke.created_at, ke.updated_at, mi.title, mi.author, mi.file_path
|
|
FROM kobo_entitlements ke
|
|
JOIN media_items mi ON ke.media_item_id = mi.id
|
|
WHERE ke.device_id = $1
|
|
ORDER BY ke.accession_date DESC
|
|
`
|
|
|
|
type GetKoboEntitlementsForDeviceRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
EntitlementID string `db:"entitlement_id" json:"entitlement_id"`
|
|
ContentID string `db:"content_id" json:"content_id"`
|
|
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
|
PurchaseDate pgtype.Timestamptz `db:"purchase_date" json:"purchase_date"`
|
|
AccessionDate pgtype.Timestamptz `db:"accession_date" json:"accession_date"`
|
|
BookStatus pgtype.Text `db:"book_status" json:"book_status"`
|
|
SyncStatus pgtype.Text `db:"sync_status" json:"sync_status"`
|
|
KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
}
|
|
|
|
func (q *Queries) GetKoboEntitlementsForDevice(ctx context.Context, deviceID pgtype.UUID) ([]GetKoboEntitlementsForDeviceRow, error) {
|
|
rows, err := q.db.Query(ctx, GetKoboEntitlementsForDevice, deviceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetKoboEntitlementsForDeviceRow{}
|
|
for rows.Next() {
|
|
var i GetKoboEntitlementsForDeviceRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.EntitlementID,
|
|
&i.ContentID,
|
|
&i.RevisionNumber,
|
|
&i.PurchaseDate,
|
|
&i.AccessionDate,
|
|
&i.BookStatus,
|
|
&i.SyncStatus,
|
|
&i.KoboMetadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.FilePath,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetKoboShelfBookCount = `-- name: GetKoboShelfBookCount :one
|
|
SELECT COUNT(*) as book_count
|
|
FROM kobo_shelves
|
|
WHERE device_id = $1
|
|
`
|
|
|
|
func (q *Queries) GetKoboShelfBookCount(ctx context.Context, deviceID pgtype.UUID) (int64, error) {
|
|
row := q.db.QueryRow(ctx, GetKoboShelfBookCount, deviceID)
|
|
var book_count int64
|
|
err := row.Scan(&book_count)
|
|
return book_count, err
|
|
}
|
|
|
|
const GetKoboShelfBooks = `-- name: GetKoboShelfBooks :many
|
|
SELECT ks.id, ks.device_id, ks.media_item_id, ks.shelf_name, ks.shelf_position, ks.added_at, ks.last_synced_at, ks.collection_id, ks.position_in_collection, mi.title, mi.author, mi.file_path, mi.mime_type, mi.entitlement_id, mi.kobo_content_id, mi.revision_number
|
|
FROM kobo_shelves ks
|
|
JOIN media_items mi ON ks.media_item_id = mi.id
|
|
WHERE ks.device_id = $1
|
|
ORDER BY ks.shelf_position ASC, ks.added_at ASC
|
|
`
|
|
|
|
type GetKoboShelfBooksRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
ShelfName pgtype.Text `db:"shelf_name" json:"shelf_name"`
|
|
ShelfPosition pgtype.Int4 `db:"shelf_position" json:"shelf_position"`
|
|
AddedAt pgtype.Timestamptz `db:"added_at" json:"added_at"`
|
|
LastSyncedAt pgtype.Timestamptz `db:"last_synced_at" json:"last_synced_at"`
|
|
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
|
PositionInCollection pgtype.Int4 `db:"position_in_collection" json:"position_in_collection"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
MimeType pgtype.Text `db:"mime_type" json:"mime_type"`
|
|
EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"`
|
|
KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"`
|
|
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
|
}
|
|
|
|
func (q *Queries) GetKoboShelfBooks(ctx context.Context, deviceID pgtype.UUID) ([]GetKoboShelfBooksRow, error) {
|
|
rows, err := q.db.Query(ctx, GetKoboShelfBooks, deviceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetKoboShelfBooksRow{}
|
|
for rows.Next() {
|
|
var i GetKoboShelfBooksRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.ShelfName,
|
|
&i.ShelfPosition,
|
|
&i.AddedAt,
|
|
&i.LastSyncedAt,
|
|
&i.CollectionID,
|
|
&i.PositionInCollection,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.FilePath,
|
|
&i.MimeType,
|
|
&i.EntitlementID,
|
|
&i.KoboContentID,
|
|
&i.RevisionNumber,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetKoboShelfBooksByShelfName = `-- name: GetKoboShelfBooksByShelfName :many
|
|
SELECT ks.id, ks.device_id, ks.media_item_id, ks.shelf_name, ks.shelf_position, ks.added_at, ks.last_synced_at, ks.collection_id, ks.position_in_collection, mi.title, mi.author, mi.file_path, mi.mime_type, mi.entitlement_id, mi.kobo_content_id, mi.revision_number
|
|
FROM kobo_shelves ks
|
|
JOIN media_items mi ON ks.media_item_id = mi.id
|
|
WHERE ks.device_id = $1 AND ks.shelf_name = $2
|
|
ORDER BY ks.shelf_position ASC, ks.added_at ASC
|
|
`
|
|
|
|
type GetKoboShelfBooksByShelfNameParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
ShelfName pgtype.Text `db:"shelf_name" json:"shelf_name"`
|
|
}
|
|
|
|
type GetKoboShelfBooksByShelfNameRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
ShelfName pgtype.Text `db:"shelf_name" json:"shelf_name"`
|
|
ShelfPosition pgtype.Int4 `db:"shelf_position" json:"shelf_position"`
|
|
AddedAt pgtype.Timestamptz `db:"added_at" json:"added_at"`
|
|
LastSyncedAt pgtype.Timestamptz `db:"last_synced_at" json:"last_synced_at"`
|
|
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
|
PositionInCollection pgtype.Int4 `db:"position_in_collection" json:"position_in_collection"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
MimeType pgtype.Text `db:"mime_type" json:"mime_type"`
|
|
EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"`
|
|
KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"`
|
|
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
|
}
|
|
|
|
func (q *Queries) GetKoboShelfBooksByShelfName(ctx context.Context, arg GetKoboShelfBooksByShelfNameParams) ([]GetKoboShelfBooksByShelfNameRow, error) {
|
|
rows, err := q.db.Query(ctx, GetKoboShelfBooksByShelfName, arg.DeviceID, arg.ShelfName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetKoboShelfBooksByShelfNameRow{}
|
|
for rows.Next() {
|
|
var i GetKoboShelfBooksByShelfNameRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.ShelfName,
|
|
&i.ShelfPosition,
|
|
&i.AddedAt,
|
|
&i.LastSyncedAt,
|
|
&i.CollectionID,
|
|
&i.PositionInCollection,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.FilePath,
|
|
&i.MimeType,
|
|
&i.EntitlementID,
|
|
&i.KoboContentID,
|
|
&i.RevisionNumber,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetKoboShelvesByCollection = `-- name: GetKoboShelvesByCollection :many
|
|
SELECT ks.id, ks.device_id, ks.media_item_id, ks.shelf_name, ks.shelf_position, ks.added_at, ks.last_synced_at, ks.collection_id, ks.position_in_collection, mi.title, mi.author
|
|
FROM kobo_shelves ks
|
|
JOIN media_items mi ON ks.media_item_id = mi.id
|
|
WHERE ks.device_id = $1 AND ks.collection_id = $2
|
|
ORDER BY ks.position_in_collection ASC, ks.shelf_position ASC
|
|
`
|
|
|
|
type GetKoboShelvesByCollectionParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
|
}
|
|
|
|
type GetKoboShelvesByCollectionRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
ShelfName pgtype.Text `db:"shelf_name" json:"shelf_name"`
|
|
ShelfPosition pgtype.Int4 `db:"shelf_position" json:"shelf_position"`
|
|
AddedAt pgtype.Timestamptz `db:"added_at" json:"added_at"`
|
|
LastSyncedAt pgtype.Timestamptz `db:"last_synced_at" json:"last_synced_at"`
|
|
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
|
PositionInCollection pgtype.Int4 `db:"position_in_collection" json:"position_in_collection"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
}
|
|
|
|
// Get Kobo shelves by collection
|
|
func (q *Queries) GetKoboShelvesByCollection(ctx context.Context, arg GetKoboShelvesByCollectionParams) ([]GetKoboShelvesByCollectionRow, error) {
|
|
rows, err := q.db.Query(ctx, GetKoboShelvesByCollection, arg.DeviceID, arg.CollectionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetKoboShelvesByCollectionRow{}
|
|
for rows.Next() {
|
|
var i GetKoboShelvesByCollectionRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.ShelfName,
|
|
&i.ShelfPosition,
|
|
&i.AddedAt,
|
|
&i.LastSyncedAt,
|
|
&i.CollectionID,
|
|
&i.PositionInCollection,
|
|
&i.Title,
|
|
&i.Author,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetLibrary = `-- name: GetLibrary :one
|
|
SELECT l.id, l.name, l.description, l.library_type_id, l.created_by_admin_id, l.created_at, l.updated_at, lt.name as type_name, lt.description as type_description
|
|
FROM libraries l
|
|
JOIN library_types lt ON l.library_type_id = lt.id
|
|
WHERE l.id = $1
|
|
`
|
|
|
|
type GetLibraryRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Name string `db:"name" json:"name"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
LibraryTypeID pgtype.UUID `db:"library_type_id" json:"library_type_id"`
|
|
CreatedByAdminID pgtype.UUID `db:"created_by_admin_id" json:"created_by_admin_id"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
TypeName string `db:"type_name" json:"type_name"`
|
|
TypeDescription pgtype.Text `db:"type_description" json:"type_description"`
|
|
}
|
|
|
|
func (q *Queries) GetLibrary(ctx context.Context, id pgtype.UUID) (GetLibraryRow, error) {
|
|
row := q.db.QueryRow(ctx, GetLibrary, id)
|
|
var i GetLibraryRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.LibraryTypeID,
|
|
&i.CreatedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.TypeName,
|
|
&i.TypeDescription,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetLibraryByFolder = `-- name: GetLibraryByFolder :one
|
|
SELECT lf.library_id, l.id, l.name, l.description, l.library_type_id, l.created_by_admin_id, l.created_at, l.updated_at FROM library_folders lf
|
|
JOIN libraries l ON lf.library_id = l.id
|
|
WHERE lf.folder_path = $1
|
|
`
|
|
|
|
type GetLibraryByFolderRow struct {
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Name string `db:"name" json:"name"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
LibraryTypeID pgtype.UUID `db:"library_type_id" json:"library_type_id"`
|
|
CreatedByAdminID pgtype.UUID `db:"created_by_admin_id" json:"created_by_admin_id"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
}
|
|
|
|
func (q *Queries) GetLibraryByFolder(ctx context.Context, folderPath string) (GetLibraryByFolderRow, error) {
|
|
row := q.db.QueryRow(ctx, GetLibraryByFolder, folderPath)
|
|
var i GetLibraryByFolderRow
|
|
err := row.Scan(
|
|
&i.LibraryID,
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.LibraryTypeID,
|
|
&i.CreatedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetLibraryFolders = `-- name: GetLibraryFolders :many
|
|
SELECT id, library_id, folder_path, created_at FROM library_folders WHERE library_id = $1 ORDER BY created_at
|
|
`
|
|
|
|
func (q *Queries) GetLibraryFolders(ctx context.Context, libraryID pgtype.UUID) ([]LibraryFolders, error) {
|
|
rows, err := q.db.Query(ctx, GetLibraryFolders, libraryID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []LibraryFolders{}
|
|
for rows.Next() {
|
|
var i LibraryFolders
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.FolderPath,
|
|
&i.CreatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetLibraryType = `-- name: GetLibraryType :one
|
|
SELECT id, name, description, allowed_extensions, created_at FROM library_types WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetLibraryType(ctx context.Context, id pgtype.UUID) (LibraryTypes, error) {
|
|
row := q.db.QueryRow(ctx, GetLibraryType, id)
|
|
var i LibraryTypes
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.AllowedExtensions,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetLibraryTypeByName = `-- name: GetLibraryTypeByName :one
|
|
SELECT id, name, description, allowed_extensions, created_at FROM library_types WHERE name = $1
|
|
`
|
|
|
|
func (q *Queries) GetLibraryTypeByName(ctx context.Context, name string) (LibraryTypes, error) {
|
|
row := q.db.QueryRow(ctx, GetLibraryTypeByName, name)
|
|
var i LibraryTypes
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.AllowedExtensions,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetLibraryTypes = `-- name: GetLibraryTypes :many
|
|
SELECT id, name, description, allowed_extensions, created_at FROM library_types ORDER BY name
|
|
`
|
|
|
|
// Library Types queries
|
|
func (q *Queries) GetLibraryTypes(ctx context.Context) ([]LibraryTypes, error) {
|
|
rows, err := q.db.Query(ctx, GetLibraryTypes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []LibraryTypes{}
|
|
for rows.Next() {
|
|
var i LibraryTypes
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.AllowedExtensions,
|
|
&i.CreatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetLibraryVisibility = `-- name: GetLibraryVisibility :one
|
|
SELECT id, user_id, library_id, is_visible, created_at, updated_at FROM library_visibility WHERE user_id = $1 AND library_id = $2
|
|
`
|
|
|
|
type GetLibraryVisibilityParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
}
|
|
|
|
func (q *Queries) GetLibraryVisibility(ctx context.Context, arg GetLibraryVisibilityParams) (LibraryVisibility, error) {
|
|
row := q.db.QueryRow(ctx, GetLibraryVisibility, arg.UserID, arg.LibraryID)
|
|
var i LibraryVisibility
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.LibraryID,
|
|
&i.IsVisible,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaHighlight = `-- name: GetMediaHighlight :one
|
|
SELECT id, media_item_id, user_id, selection_text, start_position, end_position, color, note_id, created_at, updated_at, percentage_start, percentage_end, character_start, character_end, epubcfi_start, epubcfi_end, chapter_reference, paragraph_start, paragraph_end, panel_number, device_sync_data FROM media_highlights WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetMediaHighlight(ctx context.Context, id pgtype.UUID) (MediaHighlights, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaHighlight, id)
|
|
var i MediaHighlights
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.SelectionText,
|
|
&i.StartPosition,
|
|
&i.EndPosition,
|
|
&i.Color,
|
|
&i.NoteID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.PercentageStart,
|
|
&i.PercentageEnd,
|
|
&i.CharacterStart,
|
|
&i.CharacterEnd,
|
|
&i.EpubcfiStart,
|
|
&i.EpubcfiEnd,
|
|
&i.ChapterReference,
|
|
&i.ParagraphStart,
|
|
&i.ParagraphEnd,
|
|
&i.PanelNumber,
|
|
&i.DeviceSyncData,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaHighlights = `-- name: GetMediaHighlights :many
|
|
SELECT id, media_item_id, user_id, selection_text, start_position, end_position, color, note_id, created_at, updated_at, percentage_start, percentage_end, character_start, character_end, epubcfi_start, epubcfi_end, chapter_reference, paragraph_start, paragraph_end, panel_number, device_sync_data FROM media_highlights WHERE media_item_id = $1 AND user_id = $2 ORDER BY created_at DESC
|
|
`
|
|
|
|
type GetMediaHighlightsParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
func (q *Queries) GetMediaHighlights(ctx context.Context, arg GetMediaHighlightsParams) ([]MediaHighlights, error) {
|
|
rows, err := q.db.Query(ctx, GetMediaHighlights, arg.MediaItemID, arg.UserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []MediaHighlights{}
|
|
for rows.Next() {
|
|
var i MediaHighlights
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.SelectionText,
|
|
&i.StartPosition,
|
|
&i.EndPosition,
|
|
&i.Color,
|
|
&i.NoteID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.PercentageStart,
|
|
&i.PercentageEnd,
|
|
&i.CharacterStart,
|
|
&i.CharacterEnd,
|
|
&i.EpubcfiStart,
|
|
&i.EpubcfiEnd,
|
|
&i.ChapterReference,
|
|
&i.ParagraphStart,
|
|
&i.ParagraphEnd,
|
|
&i.PanelNumber,
|
|
&i.DeviceSyncData,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetMediaItem = `-- name: GetMediaItem :one
|
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaItem, id)
|
|
var i MediaItems
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaItemByFilePath = `-- name: GetMediaItemByFilePath :one
|
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1
|
|
`
|
|
|
|
func (q *Queries) GetMediaItemByFilePath(ctx context.Context, filePath string) (MediaItems, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaItemByFilePath, filePath)
|
|
var i MediaItems
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaItemByFilePathForSync = `-- name: GetMediaItemByFilePathForSync :one
|
|
|
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1
|
|
`
|
|
|
|
// ============================================
|
|
// PHASE 3: KOREADER SYNC PROTOCOL (Weeks 7-9)
|
|
// ============================================
|
|
func (q *Queries) GetMediaItemByFilePathForSync(ctx context.Context, filePath string) (MediaItems, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaItemByFilePathForSync, filePath)
|
|
var i MediaItems
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaItemByKoboContentId = `-- name: GetMediaItemByKoboContentId :one
|
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE kobo_content_id = $1
|
|
`
|
|
|
|
func (q *Queries) GetMediaItemByKoboContentId(ctx context.Context, koboContentID pgtype.Text) (MediaItems, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaItemByKoboContentId, koboContentID)
|
|
var i MediaItems
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaItemByOPFIdentifier = `-- name: GetMediaItemByOPFIdentifier :one
|
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE opf_identifier = $1
|
|
`
|
|
|
|
// Get media item by OPF identifier
|
|
func (q *Queries) GetMediaItemByOPFIdentifier(ctx context.Context, opfIdentifier pgtype.Text) (MediaItems, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaItemByOPFIdentifier, opfIdentifier)
|
|
var i MediaItems
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaItemByOPFUUID = `-- name: GetMediaItemByOPFUUID :one
|
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE opf_uuid = $1
|
|
`
|
|
|
|
// Get media item by OPF UUID
|
|
func (q *Queries) GetMediaItemByOPFUUID(ctx context.Context, opfUuid pgtype.Text) (MediaItems, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaItemByOPFUUID, opfUuid)
|
|
var i MediaItems
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaItemBySHA256 = `-- name: GetMediaItemBySHA256 :one
|
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_sha256 = $1
|
|
`
|
|
|
|
// Get media item by SHA-256 hash
|
|
func (q *Queries) GetMediaItemBySHA256(ctx context.Context, fileSha256 pgtype.Text) (MediaItems, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaItemBySHA256, fileSha256)
|
|
var i MediaItems
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaItemFormatBySHA256 = `-- name: GetMediaItemFormatBySHA256 :one
|
|
SELECT id, media_item_id, format_type, file_path, file_sha256, file_size_bytes, mime_type, created_at, converted_from_format_id FROM media_item_formats WHERE file_sha256 = $1
|
|
`
|
|
|
|
// Get media item format by SHA-256
|
|
func (q *Queries) GetMediaItemFormatBySHA256(ctx context.Context, fileSha256 pgtype.Text) (MediaItemFormats, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaItemFormatBySHA256, fileSha256)
|
|
var i MediaItemFormats
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.FormatType,
|
|
&i.FilePath,
|
|
&i.FileSha256,
|
|
&i.FileSizeBytes,
|
|
&i.MimeType,
|
|
&i.CreatedAt,
|
|
&i.ConvertedFromFormatID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaItemFormatByType = `-- name: GetMediaItemFormatByType :one
|
|
SELECT id, media_item_id, format_type, file_path, file_sha256, file_size_bytes, mime_type, created_at, converted_from_format_id FROM media_item_formats WHERE media_item_id = $1 AND format_type = $2
|
|
`
|
|
|
|
type GetMediaItemFormatByTypeParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
FormatType string `db:"format_type" json:"format_type"`
|
|
}
|
|
|
|
// Get media item format by type
|
|
func (q *Queries) GetMediaItemFormatByType(ctx context.Context, arg GetMediaItemFormatByTypeParams) (MediaItemFormats, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaItemFormatByType, arg.MediaItemID, arg.FormatType)
|
|
var i MediaItemFormats
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.FormatType,
|
|
&i.FilePath,
|
|
&i.FileSha256,
|
|
&i.FileSizeBytes,
|
|
&i.MimeType,
|
|
&i.CreatedAt,
|
|
&i.ConvertedFromFormatID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaItemFormats = `-- name: GetMediaItemFormats :many
|
|
SELECT id, media_item_id, format_type, file_path, file_sha256, file_size_bytes, mime_type, created_at, converted_from_format_id FROM media_item_formats WHERE media_item_id = $1
|
|
`
|
|
|
|
// Get media item formats
|
|
func (q *Queries) GetMediaItemFormats(ctx context.Context, mediaItemID pgtype.UUID) ([]MediaItemFormats, error) {
|
|
rows, err := q.db.Query(ctx, GetMediaItemFormats, mediaItemID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []MediaItemFormats{}
|
|
for rows.Next() {
|
|
var i MediaItemFormats
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.FormatType,
|
|
&i.FilePath,
|
|
&i.FileSha256,
|
|
&i.FileSizeBytes,
|
|
&i.MimeType,
|
|
&i.CreatedAt,
|
|
&i.ConvertedFromFormatID,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetMediaNote = `-- name: GetMediaNote :one
|
|
SELECT id, media_item_id, user_id, content, position, created_at, updated_at, percentage_location, character_start, character_end, epubcfi_location, chapter_reference, paragraph_reference, device_sync_data FROM media_notes WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetMediaNote(ctx context.Context, id pgtype.UUID) (MediaNotes, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaNote, id)
|
|
var i MediaNotes
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.Content,
|
|
&i.Position,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.PercentageLocation,
|
|
&i.CharacterStart,
|
|
&i.CharacterEnd,
|
|
&i.EpubcfiLocation,
|
|
&i.ChapterReference,
|
|
&i.ParagraphReference,
|
|
&i.DeviceSyncData,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaNotes = `-- name: GetMediaNotes :many
|
|
SELECT id, media_item_id, user_id, content, position, created_at, updated_at, percentage_location, character_start, character_end, epubcfi_location, chapter_reference, paragraph_reference, device_sync_data FROM media_notes WHERE media_item_id = $1 AND user_id = $2 ORDER BY created_at DESC
|
|
`
|
|
|
|
type GetMediaNotesParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
func (q *Queries) GetMediaNotes(ctx context.Context, arg GetMediaNotesParams) ([]MediaNotes, error) {
|
|
rows, err := q.db.Query(ctx, GetMediaNotes, arg.MediaItemID, arg.UserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []MediaNotes{}
|
|
for rows.Next() {
|
|
var i MediaNotes
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.Content,
|
|
&i.Position,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.PercentageLocation,
|
|
&i.CharacterStart,
|
|
&i.CharacterEnd,
|
|
&i.EpubcfiLocation,
|
|
&i.ChapterReference,
|
|
&i.ParagraphReference,
|
|
&i.DeviceSyncData,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetMediaRating = `-- name: GetMediaRating :one
|
|
SELECT id, media_item_id, user_id, rating, created_at, updated_at FROM media_ratings WHERE media_item_id = $1 AND user_id = $2
|
|
`
|
|
|
|
type GetMediaRatingParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
func (q *Queries) GetMediaRating(ctx context.Context, arg GetMediaRatingParams) (MediaRatings, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaRating, arg.MediaItemID, arg.UserID)
|
|
var i MediaRatings
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.Rating,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaRatings = `-- name: GetMediaRatings :many
|
|
SELECT mr.id, mr.media_item_id, mr.user_id, mr.rating, mr.created_at, mr.updated_at, u.username
|
|
FROM media_ratings mr
|
|
JOIN users u ON mr.user_id = u.id
|
|
WHERE mr.media_item_id = $1
|
|
ORDER BY mr.created_at DESC
|
|
`
|
|
|
|
type GetMediaRatingsRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Rating int32 `db:"rating" json:"rating"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
Username string `db:"username" json:"username"`
|
|
}
|
|
|
|
func (q *Queries) GetMediaRatings(ctx context.Context, mediaItemID pgtype.UUID) ([]GetMediaRatingsRow, error) {
|
|
rows, err := q.db.Query(ctx, GetMediaRatings, mediaItemID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetMediaRatingsRow{}
|
|
for rows.Next() {
|
|
var i GetMediaRatingsRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.Rating,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Username,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetNextRetryTime = `-- name: GetNextRetryTime :one
|
|
SELECT
|
|
CASE
|
|
WHEN attempts = 0 THEN NOW()
|
|
WHEN attempts = 1 THEN NOW() + INTERVAL '1 minute'
|
|
WHEN attempts = 2 THEN NOW() + INTERVAL '5 minutes'
|
|
WHEN attempts = 3 THEN NOW() + INTERVAL '15 minutes'
|
|
WHEN attempts = 4 THEN NOW() + INTERVAL '1 hour'
|
|
ELSE NOW() + INTERVAL '24 hours'
|
|
END as next_retry_time
|
|
`
|
|
|
|
func (q *Queries) GetNextRetryTime(ctx context.Context) (interface{}, error) {
|
|
row := q.db.QueryRow(ctx, GetNextRetryTime)
|
|
var next_retry_time interface{}
|
|
err := row.Scan(&next_retry_time)
|
|
return next_retry_time, err
|
|
}
|
|
|
|
const GetOpdsToken = `-- name: GetOpdsToken :one
|
|
SELECT ot.id, ot.device_id, ot.token, ot.token_type, ot.expires_at, ot.created_at, d.device_name, d.device_type
|
|
FROM opds_tokens ot
|
|
JOIN devices d ON ot.device_id = d.id
|
|
WHERE ot.token = $1 AND ot.expires_at > NOW()
|
|
`
|
|
|
|
type GetOpdsTokenRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
Token string `db:"token" json:"token"`
|
|
TokenType pgtype.Text `db:"token_type" json:"token_type"`
|
|
ExpiresAt pgtype.Timestamptz `db:"expires_at" json:"expires_at"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
DeviceName string `db:"device_name" json:"device_name"`
|
|
DeviceType string `db:"device_type" json:"device_type"`
|
|
}
|
|
|
|
// Get OPDS token
|
|
func (q *Queries) GetOpdsToken(ctx context.Context, token string) (GetOpdsTokenRow, error) {
|
|
row := q.db.QueryRow(ctx, GetOpdsToken, token)
|
|
var i GetOpdsTokenRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.Token,
|
|
&i.TokenType,
|
|
&i.ExpiresAt,
|
|
&i.CreatedAt,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetOpdsTokensByDevice = `-- name: GetOpdsTokensByDevice :many
|
|
SELECT id, device_id, token, token_type, expires_at, created_at FROM opds_tokens WHERE device_id = $1 AND expires_at > NOW()
|
|
`
|
|
|
|
// Get OPDS tokens by device
|
|
func (q *Queries) GetOpdsTokensByDevice(ctx context.Context, deviceID pgtype.UUID) ([]OpdsTokens, error) {
|
|
rows, err := q.db.Query(ctx, GetOpdsTokensByDevice, deviceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []OpdsTokens{}
|
|
for rows.Next() {
|
|
var i OpdsTokens
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.Token,
|
|
&i.TokenType,
|
|
&i.ExpiresAt,
|
|
&i.CreatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetPopularBooks = `-- name: GetPopularBooks :many
|
|
SELECT
|
|
mi.id,
|
|
mi.title,
|
|
mi.author,
|
|
COUNT(*) as read_count,
|
|
AVG(rh.progress_percentage) as avg_completion,
|
|
MAX(rh.created_at) as last_read
|
|
FROM media_items mi
|
|
JOIN reading_history rh ON rh.media_item_id = mi.id
|
|
WHERE rh.user_id = $1
|
|
GROUP BY mi.id, mi.title, mi.author
|
|
ORDER BY read_count DESC
|
|
LIMIT $2
|
|
`
|
|
|
|
type GetPopularBooksParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Limit int32 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
type GetPopularBooksRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
ReadCount int64 `db:"read_count" json:"read_count"`
|
|
AvgCompletion float64 `db:"avg_completion" json:"avg_completion"`
|
|
LastRead interface{} `db:"last_read" json:"last_read"`
|
|
}
|
|
|
|
// Get most popular books for a user
|
|
func (q *Queries) GetPopularBooks(ctx context.Context, arg GetPopularBooksParams) ([]GetPopularBooksRow, error) {
|
|
rows, err := q.db.Query(ctx, GetPopularBooks, arg.UserID, arg.Limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetPopularBooksRow{}
|
|
for rows.Next() {
|
|
var i GetPopularBooksRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.ReadCount,
|
|
&i.AvgCompletion,
|
|
&i.LastRead,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetReadingHistory = `-- name: GetReadingHistory :many
|
|
SELECT id, user_id, media_item_id, device_id, progress_percentage, reading_session_start, reading_session_end, pages_read, time_spent_seconds, device_metadata, created_at
|
|
FROM reading_history
|
|
WHERE user_id = $1 AND media_item_id = $2
|
|
ORDER BY created_at DESC
|
|
LIMIT $3
|
|
`
|
|
|
|
type GetReadingHistoryParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
Limit int32 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
// Get reading history for a user and book
|
|
func (q *Queries) GetReadingHistory(ctx context.Context, arg GetReadingHistoryParams) ([]ReadingHistory, error) {
|
|
rows, err := q.db.Query(ctx, GetReadingHistory, arg.UserID, arg.MediaItemID, arg.Limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ReadingHistory{}
|
|
for rows.Next() {
|
|
var i ReadingHistory
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.MediaItemID,
|
|
&i.DeviceID,
|
|
&i.ProgressPercentage,
|
|
&i.ReadingSessionStart,
|
|
&i.ReadingSessionEnd,
|
|
&i.PagesRead,
|
|
&i.TimeSpentSeconds,
|
|
&i.DeviceMetadata,
|
|
&i.CreatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetReadingProgress = `-- name: GetReadingProgress :one
|
|
SELECT id, media_item_id, user_id, current_page, total_pages, last_read_at, percentage, character_offset, epubcfi, chapter, chapter_progress, viewport_x, viewport_y, zoom_level, scroll_position_x, scroll_position_y, panel_number, reading_mode, last_sync_device, last_sync_source, last_sync_timestamp, conflict_detected, conflict_resolved FROM reading_progress WHERE media_item_id = $1 AND user_id = $2
|
|
`
|
|
|
|
type GetReadingProgressParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
func (q *Queries) GetReadingProgress(ctx context.Context, arg GetReadingProgressParams) (ReadingProgress, error) {
|
|
row := q.db.QueryRow(ctx, GetReadingProgress, arg.MediaItemID, arg.UserID)
|
|
var i ReadingProgress
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.CurrentPage,
|
|
&i.TotalPages,
|
|
&i.LastReadAt,
|
|
&i.Percentage,
|
|
&i.CharacterOffset,
|
|
&i.Epubcfi,
|
|
&i.Chapter,
|
|
&i.ChapterProgress,
|
|
&i.ViewportX,
|
|
&i.ViewportY,
|
|
&i.ZoomLevel,
|
|
&i.ScrollPositionX,
|
|
&i.ScrollPositionY,
|
|
&i.PanelNumber,
|
|
&i.ReadingMode,
|
|
&i.LastSyncDevice,
|
|
&i.LastSyncSource,
|
|
&i.LastSyncTimestamp,
|
|
&i.ConflictDetected,
|
|
&i.ConflictResolved,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetRefreshToken = `-- name: GetRefreshToken :one
|
|
SELECT rt.id, rt.user_id, rt.token, rt.expires_at, rt.created_at, rt.revoked_at, u.email, u.username, u.role
|
|
FROM refresh_tokens rt
|
|
JOIN users u ON rt.user_id = u.id
|
|
WHERE rt.token = $1 AND rt.revoked_at IS NULL AND rt.expires_at > NOW()
|
|
`
|
|
|
|
type GetRefreshTokenRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Token pgtype.UUID `db:"token" json:"token"`
|
|
ExpiresAt pgtype.Timestamptz `db:"expires_at" json:"expires_at"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
RevokedAt pgtype.Timestamptz `db:"revoked_at" json:"revoked_at"`
|
|
Email string `db:"email" json:"email"`
|
|
Username string `db:"username" json:"username"`
|
|
Role string `db:"role" json:"role"`
|
|
}
|
|
|
|
func (q *Queries) GetRefreshToken(ctx context.Context, token pgtype.UUID) (GetRefreshTokenRow, error) {
|
|
row := q.db.QueryRow(ctx, GetRefreshToken, token)
|
|
var i GetRefreshTokenRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Token,
|
|
&i.ExpiresAt,
|
|
&i.CreatedAt,
|
|
&i.RevokedAt,
|
|
&i.Email,
|
|
&i.Username,
|
|
&i.Role,
|
|
)
|
|
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'
|
|
ORDER BY priority ASC, created_at ASC
|
|
`
|
|
|
|
func (q *Queries) GetStuckSyncQueueItems(ctx context.Context) ([]SyncQueue, error) {
|
|
rows, err := q.db.Query(ctx, GetStuckSyncQueueItems)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SyncQueue{}
|
|
for rows.Next() {
|
|
var i SyncQueue
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.SyncType,
|
|
&i.SyncData,
|
|
&i.Priority,
|
|
&i.Attempts,
|
|
&i.MaxAttempts,
|
|
&i.Status,
|
|
&i.ErrorMessage,
|
|
&i.CreatedAt,
|
|
&i.ProcessedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetSyncConflict = `-- name: GetSyncConflict :one
|
|
SELECT id, media_item_id, user_id, conflict_type, conflict_data, resolution_status, resolution_data, resolved_by, resolved_at, created_at FROM sync_conflicts WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetSyncConflict(ctx context.Context, id pgtype.UUID) (SyncConflicts, error) {
|
|
row := q.db.QueryRow(ctx, GetSyncConflict, id)
|
|
var i SyncConflicts
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.ConflictType,
|
|
&i.ConflictData,
|
|
&i.ResolutionStatus,
|
|
&i.ResolutionData,
|
|
&i.ResolvedBy,
|
|
&i.ResolvedAt,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetSyncQueueItem = `-- name: GetSyncQueueItem :one
|
|
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 id = $1
|
|
`
|
|
|
|
func (q *Queries) GetSyncQueueItem(ctx context.Context, id pgtype.UUID) (SyncQueue, error) {
|
|
row := q.db.QueryRow(ctx, GetSyncQueueItem, id)
|
|
var i SyncQueue
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.SyncType,
|
|
&i.SyncData,
|
|
&i.Priority,
|
|
&i.Attempts,
|
|
&i.MaxAttempts,
|
|
&i.Status,
|
|
&i.ErrorMessage,
|
|
&i.CreatedAt,
|
|
&i.ProcessedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetSyncQueueStats = `-- name: GetSyncQueueStats :one
|
|
SELECT
|
|
COUNT(*) FILTER (WHERE status = 'pending') as pending_count,
|
|
COUNT(*) FILTER (WHERE status = 'processing') as processing_count,
|
|
COUNT(*) FILTER (WHERE status = 'failed') as failed_count,
|
|
COUNT(*) FILTER (WHERE status = 'completed') as completed_count,
|
|
COUNT(*) as total_count
|
|
FROM sync_queue
|
|
WHERE device_id = $1
|
|
`
|
|
|
|
type GetSyncQueueStatsRow struct {
|
|
PendingCount int64 `db:"pending_count" json:"pending_count"`
|
|
ProcessingCount int64 `db:"processing_count" json:"processing_count"`
|
|
FailedCount int64 `db:"failed_count" json:"failed_count"`
|
|
CompletedCount int64 `db:"completed_count" json:"completed_count"`
|
|
TotalCount int64 `db:"total_count" json:"total_count"`
|
|
}
|
|
|
|
func (q *Queries) GetSyncQueueStats(ctx context.Context, deviceID pgtype.UUID) (GetSyncQueueStatsRow, error) {
|
|
row := q.db.QueryRow(ctx, GetSyncQueueStats, deviceID)
|
|
var i GetSyncQueueStatsRow
|
|
err := row.Scan(
|
|
&i.PendingCount,
|
|
&i.ProcessingCount,
|
|
&i.FailedCount,
|
|
&i.CompletedCount,
|
|
&i.TotalCount,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetSystemConfig = `-- name: GetSystemConfig :one
|
|
|
|
SELECT key, value, updated_at, updated_by FROM system_config WHERE key = $1
|
|
`
|
|
|
|
// SYSTEM CONFIG QUERIES
|
|
// Get system config
|
|
func (q *Queries) GetSystemConfig(ctx context.Context, key string) (SystemConfig, error) {
|
|
row := q.db.QueryRow(ctx, GetSystemConfig, key)
|
|
var i SystemConfig
|
|
err := row.Scan(
|
|
&i.Key,
|
|
&i.Value,
|
|
&i.UpdatedAt,
|
|
&i.UpdatedBy,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetUniversalProgress = `-- name: GetUniversalProgress :one
|
|
SELECT
|
|
rp.id,
|
|
rp.media_item_id,
|
|
rp.user_id,
|
|
rp.current_page,
|
|
rp.total_pages,
|
|
rp.last_read_at,
|
|
rp.percentage,
|
|
rp.character_offset,
|
|
rp.epubcfi,
|
|
rp.chapter,
|
|
rp.chapter_progress,
|
|
rp.viewport_x,
|
|
rp.viewport_y,
|
|
rp.zoom_level,
|
|
rp.scroll_position_x,
|
|
rp.scroll_position_y,
|
|
rp.panel_number,
|
|
rp.reading_mode,
|
|
rp.last_sync_device,
|
|
rp.last_sync_source,
|
|
rp.last_sync_timestamp,
|
|
rp.conflict_detected,
|
|
rp.conflict_resolved,
|
|
mi.format_group,
|
|
mi.format_mimetype,
|
|
mi.is_reflowable,
|
|
mi.has_fixed_layout,
|
|
mi.total_characters,
|
|
mi.chapter_count
|
|
FROM reading_progress rp
|
|
JOIN media_items mi ON rp.media_item_id = mi.id
|
|
WHERE rp.media_item_id = $1 AND rp.user_id = $2
|
|
`
|
|
|
|
type GetUniversalProgressParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
type GetUniversalProgressRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
CurrentPage pgtype.Int4 `db:"current_page" json:"current_page"`
|
|
TotalPages pgtype.Int4 `db:"total_pages" json:"total_pages"`
|
|
LastReadAt pgtype.Timestamptz `db:"last_read_at" json:"last_read_at"`
|
|
Percentage pgtype.Float8 `db:"percentage" json:"percentage"`
|
|
CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"`
|
|
Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"`
|
|
Chapter pgtype.Int4 `db:"chapter" json:"chapter"`
|
|
ChapterProgress pgtype.Float8 `db:"chapter_progress" json:"chapter_progress"`
|
|
ViewportX pgtype.Float8 `db:"viewport_x" json:"viewport_x"`
|
|
ViewportY pgtype.Float8 `db:"viewport_y" json:"viewport_y"`
|
|
ZoomLevel pgtype.Float8 `db:"zoom_level" json:"zoom_level"`
|
|
ScrollPositionX pgtype.Float8 `db:"scroll_position_x" json:"scroll_position_x"`
|
|
ScrollPositionY pgtype.Float8 `db:"scroll_position_y" json:"scroll_position_y"`
|
|
PanelNumber pgtype.Int4 `db:"panel_number" json:"panel_number"`
|
|
ReadingMode pgtype.Text `db:"reading_mode" json:"reading_mode"`
|
|
LastSyncDevice pgtype.Text `db:"last_sync_device" json:"last_sync_device"`
|
|
LastSyncSource pgtype.Text `db:"last_sync_source" json:"last_sync_source"`
|
|
LastSyncTimestamp pgtype.Timestamptz `db:"last_sync_timestamp" json:"last_sync_timestamp"`
|
|
ConflictDetected pgtype.Bool `db:"conflict_detected" json:"conflict_detected"`
|
|
ConflictResolved pgtype.Bool `db:"conflict_resolved" json:"conflict_resolved"`
|
|
FormatGroup string `db:"format_group" json:"format_group"`
|
|
FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"`
|
|
IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"`
|
|
HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"`
|
|
TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"`
|
|
ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"`
|
|
}
|
|
|
|
// Get universal progress for a book
|
|
func (q *Queries) GetUniversalProgress(ctx context.Context, arg GetUniversalProgressParams) (GetUniversalProgressRow, error) {
|
|
row := q.db.QueryRow(ctx, GetUniversalProgress, arg.MediaItemID, arg.UserID)
|
|
var i GetUniversalProgressRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.CurrentPage,
|
|
&i.TotalPages,
|
|
&i.LastReadAt,
|
|
&i.Percentage,
|
|
&i.CharacterOffset,
|
|
&i.Epubcfi,
|
|
&i.Chapter,
|
|
&i.ChapterProgress,
|
|
&i.ViewportX,
|
|
&i.ViewportY,
|
|
&i.ZoomLevel,
|
|
&i.ScrollPositionX,
|
|
&i.ScrollPositionY,
|
|
&i.PanelNumber,
|
|
&i.ReadingMode,
|
|
&i.LastSyncDevice,
|
|
&i.LastSyncSource,
|
|
&i.LastSyncTimestamp,
|
|
&i.ConflictDetected,
|
|
&i.ConflictResolved,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetUnlinkedBookByContentId = `-- name: GetUnlinkedBookByContentId :one
|
|
SELECT id, device_id, content_id, file_path, title, author, confidence_score, resolved, media_item_id, resolved_at, resolution_method, last_seen_at, created_at FROM unlinked_books WHERE device_id = $1 AND content_id = $2
|
|
`
|
|
|
|
type GetUnlinkedBookByContentIdParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
ContentID string `db:"content_id" json:"content_id"`
|
|
}
|
|
|
|
// Get unlinked book by ContentId
|
|
func (q *Queries) GetUnlinkedBookByContentId(ctx context.Context, arg GetUnlinkedBookByContentIdParams) (UnlinkedBooks, error) {
|
|
row := q.db.QueryRow(ctx, GetUnlinkedBookByContentId, arg.DeviceID, arg.ContentID)
|
|
var i UnlinkedBooks
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.ContentID,
|
|
&i.FilePath,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.ConfidenceScore,
|
|
&i.Resolved,
|
|
&i.MediaItemID,
|
|
&i.ResolvedAt,
|
|
&i.ResolutionMethod,
|
|
&i.LastSeenAt,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetUnlinkedBookByID = `-- name: GetUnlinkedBookByID :one
|
|
SELECT id, device_id, content_id, file_path, title, author, confidence_score, resolved, media_item_id, resolved_at, resolution_method, last_seen_at, created_at FROM unlinked_books WHERE id = $1
|
|
`
|
|
|
|
// Get unlinked book by ID
|
|
func (q *Queries) GetUnlinkedBookByID(ctx context.Context, id pgtype.UUID) (UnlinkedBooks, error) {
|
|
row := q.db.QueryRow(ctx, GetUnlinkedBookByID, id)
|
|
var i UnlinkedBooks
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.ContentID,
|
|
&i.FilePath,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.ConfidenceScore,
|
|
&i.Resolved,
|
|
&i.MediaItemID,
|
|
&i.ResolvedAt,
|
|
&i.ResolutionMethod,
|
|
&i.LastSeenAt,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetUnlinkedBooksByDevice = `-- name: GetUnlinkedBooksByDevice :many
|
|
SELECT ub.id, ub.device_id, ub.content_id, ub.file_path, ub.title, ub.author, ub.confidence_score, ub.resolved, ub.media_item_id, ub.resolved_at, ub.resolution_method, ub.last_seen_at, ub.created_at, d.device_name, d.device_type
|
|
FROM unlinked_books ub
|
|
JOIN devices d ON ub.device_id = d.id
|
|
WHERE ub.device_id = $1 AND ub.resolved = false
|
|
ORDER BY ub.last_seen_at DESC
|
|
`
|
|
|
|
type GetUnlinkedBooksByDeviceRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
ContentID string `db:"content_id" json:"content_id"`
|
|
FilePath pgtype.Text `db:"file_path" json:"file_path"`
|
|
Title pgtype.Text `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"`
|
|
Resolved pgtype.Bool `db:"resolved" json:"resolved"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
ResolvedAt pgtype.Timestamptz `db:"resolved_at" json:"resolved_at"`
|
|
ResolutionMethod pgtype.Text `db:"resolution_method" json:"resolution_method"`
|
|
LastSeenAt pgtype.Timestamptz `db:"last_seen_at" json:"last_seen_at"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
DeviceName string `db:"device_name" json:"device_name"`
|
|
DeviceType string `db:"device_type" json:"device_type"`
|
|
}
|
|
|
|
// Get unlinked books for a device
|
|
func (q *Queries) GetUnlinkedBooksByDevice(ctx context.Context, deviceID pgtype.UUID) ([]GetUnlinkedBooksByDeviceRow, error) {
|
|
rows, err := q.db.Query(ctx, GetUnlinkedBooksByDevice, deviceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetUnlinkedBooksByDeviceRow{}
|
|
for rows.Next() {
|
|
var i GetUnlinkedBooksByDeviceRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.ContentID,
|
|
&i.FilePath,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.ConfidenceScore,
|
|
&i.Resolved,
|
|
&i.MediaItemID,
|
|
&i.ResolvedAt,
|
|
&i.ResolutionMethod,
|
|
&i.LastSeenAt,
|
|
&i.CreatedAt,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetUser = `-- name: GetUser :one
|
|
SELECT id, email, username, theme, first_name, last_name, role, created_at, updated_at FROM users WHERE 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"`
|
|
}
|
|
|
|
func (q *Queries) GetUser(ctx context.Context, id pgtype.UUID) (GetUserRow, error) {
|
|
row := q.db.QueryRow(ctx, GetUser, id)
|
|
var i GetUserRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Email,
|
|
&i.Username,
|
|
&i.Theme,
|
|
&i.FirstName,
|
|
&i.LastName,
|
|
&i.Role,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetUserByEmail = `-- name: GetUserByEmail :one
|
|
SELECT id, email, username, theme, first_name, last_name, role, created_at, updated_at FROM users WHERE email = $1
|
|
`
|
|
|
|
type GetUserByEmailRow 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"`
|
|
}
|
|
|
|
func (q *Queries) GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error) {
|
|
row := q.db.QueryRow(ctx, GetUserByEmail, email)
|
|
var i GetUserByEmailRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Email,
|
|
&i.Username,
|
|
&i.Theme,
|
|
&i.FirstName,
|
|
&i.LastName,
|
|
&i.Role,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetUserByEmailOrUsername = `-- name: GetUserByEmailOrUsername :one
|
|
SELECT id, email, username, theme, first_name, last_name, role, created_at, updated_at FROM users WHERE email = $1 OR username = $1
|
|
`
|
|
|
|
type GetUserByEmailOrUsernameRow 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"`
|
|
}
|
|
|
|
func (q *Queries) GetUserByEmailOrUsername(ctx context.Context, email string) (GetUserByEmailOrUsernameRow, error) {
|
|
row := q.db.QueryRow(ctx, GetUserByEmailOrUsername, email)
|
|
var i GetUserByEmailOrUsernameRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Email,
|
|
&i.Username,
|
|
&i.Theme,
|
|
&i.FirstName,
|
|
&i.LastName,
|
|
&i.Role,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetUserByUsername = `-- name: GetUserByUsername :one
|
|
SELECT id, email, username, theme, first_name, last_name, role, created_at, updated_at FROM users WHERE username = $1
|
|
`
|
|
|
|
type GetUserByUsernameRow 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"`
|
|
}
|
|
|
|
func (q *Queries) GetUserByUsername(ctx context.Context, username string) (GetUserByUsernameRow, error) {
|
|
row := q.db.QueryRow(ctx, GetUserByUsername, username)
|
|
var i GetUserByUsernameRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Email,
|
|
&i.Username,
|
|
&i.Theme,
|
|
&i.FirstName,
|
|
&i.LastName,
|
|
&i.Role,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetUserDeviceUsage = `-- name: GetUserDeviceUsage :many
|
|
SELECT
|
|
d.id,
|
|
d.device_name,
|
|
d.device_type,
|
|
COUNT(*) as sync_count,
|
|
MAX(rh.created_at) as last_sync,
|
|
SUM(rh.time_spent_seconds) as total_time_seconds
|
|
FROM devices d
|
|
JOIN reading_history rh ON rh.device_id = d.id
|
|
WHERE d.user_id = $1
|
|
GROUP BY d.id, d.device_name, d.device_type
|
|
ORDER BY sync_count DESC
|
|
`
|
|
|
|
type GetUserDeviceUsageRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
DeviceName string `db:"device_name" json:"device_name"`
|
|
DeviceType string `db:"device_type" json:"device_type"`
|
|
SyncCount int64 `db:"sync_count" json:"sync_count"`
|
|
LastSync interface{} `db:"last_sync" json:"last_sync"`
|
|
TotalTimeSeconds int64 `db:"total_time_seconds" json:"total_time_seconds"`
|
|
}
|
|
|
|
// Get user device usage statistics
|
|
func (q *Queries) GetUserDeviceUsage(ctx context.Context, userID pgtype.UUID) ([]GetUserDeviceUsageRow, error) {
|
|
rows, err := q.db.Query(ctx, GetUserDeviceUsage, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetUserDeviceUsageRow{}
|
|
for rows.Next() {
|
|
var i GetUserDeviceUsageRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
&i.SyncCount,
|
|
&i.LastSync,
|
|
&i.TotalTimeSeconds,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetUserForLogin = `-- name: GetUserForLogin :one
|
|
SELECT id, email, username, password_hash, theme, first_name, last_name, role, created_at, updated_at FROM users WHERE email = $1 OR username = $1
|
|
`
|
|
|
|
type GetUserForLoginRow 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"`
|
|
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"`
|
|
}
|
|
|
|
func (q *Queries) GetUserForLogin(ctx context.Context, email string) (GetUserForLoginRow, error) {
|
|
row := q.db.QueryRow(ctx, GetUserForLogin, email)
|
|
var i GetUserForLoginRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Email,
|
|
&i.Username,
|
|
&i.PasswordHash,
|
|
&i.Theme,
|
|
&i.FirstName,
|
|
&i.LastName,
|
|
&i.Role,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetUserMediaItemsForSync = `-- name: GetUserMediaItemsForSync :many
|
|
SELECT
|
|
mi.id,
|
|
mi.title,
|
|
mi.author,
|
|
mi.file_path,
|
|
mi.mime_type,
|
|
mi.page_count,
|
|
mi.format_group,
|
|
mi.total_characters,
|
|
mi.chapter_count,
|
|
mi.entitlement_id,
|
|
mi.revision_number,
|
|
mi.file_size,
|
|
mi.file_sha256
|
|
FROM media_items mi
|
|
JOIN library_visibility lv ON mi.library_id = lv.library_id
|
|
WHERE lv.user_id = $1
|
|
AND lv.is_visible = true
|
|
ORDER BY mi.title ASC
|
|
LIMIT 1000
|
|
`
|
|
|
|
type GetUserMediaItemsForSyncRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
MimeType pgtype.Text `db:"mime_type" json:"mime_type"`
|
|
PageCount pgtype.Int4 `db:"page_count" json:"page_count"`
|
|
FormatGroup string `db:"format_group" json:"format_group"`
|
|
TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"`
|
|
ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"`
|
|
EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"`
|
|
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
|
FileSize pgtype.Int8 `db:"file_size" json:"file_size"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
}
|
|
|
|
func (q *Queries) GetUserMediaItemsForSync(ctx context.Context, userID pgtype.UUID) ([]GetUserMediaItemsForSyncRow, error) {
|
|
rows, err := q.db.Query(ctx, GetUserMediaItemsForSync, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetUserMediaItemsForSyncRow{}
|
|
for rows.Next() {
|
|
var i GetUserMediaItemsForSyncRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.FilePath,
|
|
&i.MimeType,
|
|
&i.PageCount,
|
|
&i.FormatGroup,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.FileSize,
|
|
&i.FileSha256,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetUserPasswordHash = `-- name: GetUserPasswordHash :one
|
|
SELECT password_hash FROM users WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetUserPasswordHash(ctx context.Context, id pgtype.UUID) (string, error) {
|
|
row := q.db.QueryRow(ctx, GetUserPasswordHash, id)
|
|
var password_hash string
|
|
err := row.Scan(&password_hash)
|
|
return password_hash, err
|
|
}
|
|
|
|
const GetUserProgressForBooks = `-- name: GetUserProgressForBooks :many
|
|
SELECT
|
|
rp.media_item_id,
|
|
rp.user_id,
|
|
rp.percentage,
|
|
rp.character_offset,
|
|
rp.epubcfi,
|
|
rp.chapter,
|
|
rp.chapter_progress,
|
|
rp.current_page,
|
|
rp.total_pages,
|
|
rp.last_read_at,
|
|
rp.last_sync_device,
|
|
rp.last_sync_source,
|
|
mi.title,
|
|
mi.author,
|
|
mi.file_path
|
|
FROM reading_progress rp
|
|
JOIN media_items mi ON rp.media_item_id = mi.id
|
|
WHERE rp.user_id = $1
|
|
AND rp.media_item_id = ANY($2::uuid[])
|
|
ORDER BY rp.last_read_at DESC
|
|
`
|
|
|
|
type GetUserProgressForBooksParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Column2 []pgtype.UUID `db:"column_2" json:"column_2"`
|
|
}
|
|
|
|
type GetUserProgressForBooksRow struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Percentage pgtype.Float8 `db:"percentage" json:"percentage"`
|
|
CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"`
|
|
Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"`
|
|
Chapter pgtype.Int4 `db:"chapter" json:"chapter"`
|
|
ChapterProgress pgtype.Float8 `db:"chapter_progress" json:"chapter_progress"`
|
|
CurrentPage pgtype.Int4 `db:"current_page" json:"current_page"`
|
|
TotalPages pgtype.Int4 `db:"total_pages" json:"total_pages"`
|
|
LastReadAt pgtype.Timestamptz `db:"last_read_at" json:"last_read_at"`
|
|
LastSyncDevice pgtype.Text `db:"last_sync_device" json:"last_sync_device"`
|
|
LastSyncSource pgtype.Text `db:"last_sync_source" json:"last_sync_source"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
}
|
|
|
|
func (q *Queries) GetUserProgressForBooks(ctx context.Context, arg GetUserProgressForBooksParams) ([]GetUserProgressForBooksRow, error) {
|
|
rows, err := q.db.Query(ctx, GetUserProgressForBooks, arg.UserID, arg.Column2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetUserProgressForBooksRow{}
|
|
for rows.Next() {
|
|
var i GetUserProgressForBooksRow
|
|
if err := rows.Scan(
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.Percentage,
|
|
&i.CharacterOffset,
|
|
&i.Epubcfi,
|
|
&i.Chapter,
|
|
&i.ChapterProgress,
|
|
&i.CurrentPage,
|
|
&i.TotalPages,
|
|
&i.LastReadAt,
|
|
&i.LastSyncDevice,
|
|
&i.LastSyncSource,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.FilePath,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetUserReadingHistory = `-- name: GetUserReadingHistory :many
|
|
|
|
SELECT
|
|
rh.id,
|
|
rh.user_id,
|
|
rh.media_item_id,
|
|
rh.device_id,
|
|
rh.progress_percentage,
|
|
rh.reading_session_start,
|
|
rh.reading_session_end,
|
|
rh.pages_read,
|
|
rh.time_spent_seconds,
|
|
rh.device_metadata,
|
|
rh.created_at,
|
|
mi.title,
|
|
mi.author,
|
|
d.device_name,
|
|
d.device_type
|
|
FROM reading_history rh
|
|
JOIN media_items mi ON rh.media_item_id = mi.id
|
|
LEFT JOIN devices d ON rh.device_id = d.id
|
|
WHERE rh.user_id = $1
|
|
AND rh.created_at >= $2
|
|
AND rh.created_at <= $3
|
|
ORDER BY rh.created_at DESC
|
|
`
|
|
|
|
type GetUserReadingHistoryParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
CreatedAt_2 pgtype.Timestamptz `db:"created_at_2" json:"created_at_2"`
|
|
}
|
|
|
|
type GetUserReadingHistoryRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
ProgressPercentage pgtype.Float8 `db:"progress_percentage" json:"progress_percentage"`
|
|
ReadingSessionStart pgtype.Timestamptz `db:"reading_session_start" json:"reading_session_start"`
|
|
ReadingSessionEnd pgtype.Timestamptz `db:"reading_session_end" json:"reading_session_end"`
|
|
PagesRead pgtype.Int4 `db:"pages_read" json:"pages_read"`
|
|
TimeSpentSeconds pgtype.Int4 `db:"time_spent_seconds" json:"time_spent_seconds"`
|
|
DeviceMetadata []byte `db:"device_metadata" json:"device_metadata"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
DeviceName pgtype.Text `db:"device_name" json:"device_name"`
|
|
DeviceType pgtype.Text `db:"device_type" json:"device_type"`
|
|
}
|
|
|
|
// Analytics queries
|
|
// Get user reading history for analytics
|
|
func (q *Queries) GetUserReadingHistory(ctx context.Context, arg GetUserReadingHistoryParams) ([]GetUserReadingHistoryRow, error) {
|
|
rows, err := q.db.Query(ctx, GetUserReadingHistory, arg.UserID, arg.CreatedAt, arg.CreatedAt_2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetUserReadingHistoryRow{}
|
|
for rows.Next() {
|
|
var i GetUserReadingHistoryRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.MediaItemID,
|
|
&i.DeviceID,
|
|
&i.ProgressPercentage,
|
|
&i.ReadingSessionStart,
|
|
&i.ReadingSessionEnd,
|
|
&i.PagesRead,
|
|
&i.TimeSpentSeconds,
|
|
&i.DeviceMetadata,
|
|
&i.CreatedAt,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetUserVisibleLibraries = `-- name: GetUserVisibleLibraries :many
|
|
SELECT l.id, l.name, l.description, l.library_type_id, l.created_by_admin_id, l.created_at, l.updated_at, lt.name as type_name, lt.description as type_description,
|
|
COALESCE(lv.is_visible, true) as is_visible
|
|
FROM libraries l
|
|
JOIN library_types lt ON l.library_type_id = lt.id
|
|
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $1
|
|
WHERE COALESCE(lv.is_visible, true) = true
|
|
ORDER BY l.created_at DESC
|
|
`
|
|
|
|
type GetUserVisibleLibrariesRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Name string `db:"name" json:"name"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
LibraryTypeID pgtype.UUID `db:"library_type_id" json:"library_type_id"`
|
|
CreatedByAdminID pgtype.UUID `db:"created_by_admin_id" json:"created_by_admin_id"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
TypeName string `db:"type_name" json:"type_name"`
|
|
TypeDescription pgtype.Text `db:"type_description" json:"type_description"`
|
|
IsVisible bool `db:"is_visible" json:"is_visible"`
|
|
}
|
|
|
|
func (q *Queries) GetUserVisibleLibraries(ctx context.Context, userID pgtype.UUID) ([]GetUserVisibleLibrariesRow, error) {
|
|
rows, err := q.db.Query(ctx, GetUserVisibleLibraries, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetUserVisibleLibrariesRow{}
|
|
for rows.Next() {
|
|
var i GetUserVisibleLibrariesRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.LibraryTypeID,
|
|
&i.CreatedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.TypeName,
|
|
&i.TypeDescription,
|
|
&i.IsVisible,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const IncrementSyncQueueAttempts = `-- name: IncrementSyncQueueAttempts :one
|
|
UPDATE sync_queue
|
|
SET
|
|
attempts = attempts + 1,
|
|
status = CASE
|
|
WHEN attempts + 1 >= max_attempts THEN 'failed'
|
|
ELSE 'pending'
|
|
END,
|
|
error_message = $2
|
|
WHERE id = $1
|
|
RETURNING id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at
|
|
`
|
|
|
|
type IncrementSyncQueueAttemptsParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
ErrorMessage pgtype.Text `db:"error_message" json:"error_message"`
|
|
}
|
|
|
|
func (q *Queries) IncrementSyncQueueAttempts(ctx context.Context, arg IncrementSyncQueueAttemptsParams) (SyncQueue, error) {
|
|
row := q.db.QueryRow(ctx, IncrementSyncQueueAttempts, arg.ID, arg.ErrorMessage)
|
|
var i SyncQueue
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.SyncType,
|
|
&i.SyncData,
|
|
&i.Priority,
|
|
&i.Attempts,
|
|
&i.MaxAttempts,
|
|
&i.Status,
|
|
&i.ErrorMessage,
|
|
&i.CreatedAt,
|
|
&i.ProcessedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const IsBookInCollection = `-- name: IsBookInCollection :one
|
|
SELECT EXISTS(
|
|
SELECT 1 FROM collection_items
|
|
WHERE collection_id = $1 AND media_item_id = $2
|
|
) as in_collection
|
|
`
|
|
|
|
type IsBookInCollectionParams struct {
|
|
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
}
|
|
|
|
// Check if book is in collection
|
|
func (q *Queries) IsBookInCollection(ctx context.Context, arg IsBookInCollectionParams) (bool, error) {
|
|
row := q.db.QueryRow(ctx, IsBookInCollection, arg.CollectionID, arg.MediaItemID)
|
|
var in_collection bool
|
|
err := row.Scan(&in_collection)
|
|
return in_collection, err
|
|
}
|
|
|
|
const IsBookOnKoboShelf = `-- name: IsBookOnKoboShelf :one
|
|
SELECT EXISTS(
|
|
SELECT 1 FROM kobo_shelves
|
|
WHERE device_id = $1 AND media_item_id = $2
|
|
) as on_shelf
|
|
`
|
|
|
|
type IsBookOnKoboShelfParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
}
|
|
|
|
func (q *Queries) IsBookOnKoboShelf(ctx context.Context, arg IsBookOnKoboShelfParams) (bool, error) {
|
|
row := q.db.QueryRow(ctx, IsBookOnKoboShelf, arg.DeviceID, arg.MediaItemID)
|
|
var on_shelf bool
|
|
err := row.Scan(&on_shelf)
|
|
return on_shelf, err
|
|
}
|
|
|
|
const LinkUnlinkedBook = `-- name: LinkUnlinkedBook :one
|
|
UPDATE unlinked_books
|
|
SET
|
|
media_item_id = $2,
|
|
confidence_score = $3,
|
|
resolved = true,
|
|
resolved_at = NOW(),
|
|
resolution_method = 'manual_link'
|
|
WHERE id = $1
|
|
RETURNING id, device_id, content_id, file_path, title, author, confidence_score, resolved, media_item_id, resolved_at, resolution_method, last_seen_at, created_at
|
|
`
|
|
|
|
type LinkUnlinkedBookParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"`
|
|
}
|
|
|
|
// Link unlinked book to media item
|
|
func (q *Queries) LinkUnlinkedBook(ctx context.Context, arg LinkUnlinkedBookParams) (UnlinkedBooks, error) {
|
|
row := q.db.QueryRow(ctx, LinkUnlinkedBook, arg.ID, arg.MediaItemID, arg.ConfidenceScore)
|
|
var i UnlinkedBooks
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.ContentID,
|
|
&i.FilePath,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.ConfidenceScore,
|
|
&i.Resolved,
|
|
&i.MediaItemID,
|
|
&i.ResolvedAt,
|
|
&i.ResolutionMethod,
|
|
&i.LastSeenAt,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const ListAllSyncQueueItems = `-- name: ListAllSyncQueueItems :many
|
|
SELECT
|
|
sq.id, sq.device_id, sq.media_item_id, sq.sync_type, sq.sync_data, sq.priority, sq.attempts, sq.max_attempts, sq.status, sq.error_message, sq.created_at, sq.processed_at,
|
|
d.device_name,
|
|
d.device_type,
|
|
u.email as user_email,
|
|
mi.title as media_title
|
|
FROM sync_queue sq
|
|
JOIN devices d ON sq.device_id = d.id
|
|
JOIN users u ON d.user_id = u.id
|
|
LEFT JOIN media_items mi ON sq.media_item_id = mi.id
|
|
ORDER BY sq.priority ASC, sq.created_at DESC
|
|
LIMIT $1 OFFSET $2
|
|
`
|
|
|
|
type ListAllSyncQueueItemsParams struct {
|
|
Limit int32 `db:"limit" json:"limit"`
|
|
Offset int32 `db:"offset" json:"offset"`
|
|
}
|
|
|
|
type ListAllSyncQueueItemsRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
SyncType string `db:"sync_type" json:"sync_type"`
|
|
SyncData []byte `db:"sync_data" json:"sync_data"`
|
|
Priority pgtype.Int4 `db:"priority" json:"priority"`
|
|
Attempts pgtype.Int4 `db:"attempts" json:"attempts"`
|
|
MaxAttempts pgtype.Int4 `db:"max_attempts" json:"max_attempts"`
|
|
Status pgtype.Text `db:"status" json:"status"`
|
|
ErrorMessage pgtype.Text `db:"error_message" json:"error_message"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
ProcessedAt pgtype.Timestamptz `db:"processed_at" json:"processed_at"`
|
|
DeviceName string `db:"device_name" json:"device_name"`
|
|
DeviceType string `db:"device_type" json:"device_type"`
|
|
UserEmail string `db:"user_email" json:"user_email"`
|
|
MediaTitle pgtype.Text `db:"media_title" json:"media_title"`
|
|
}
|
|
|
|
func (q *Queries) ListAllSyncQueueItems(ctx context.Context, arg ListAllSyncQueueItemsParams) ([]ListAllSyncQueueItemsRow, error) {
|
|
rows, err := q.db.Query(ctx, ListAllSyncQueueItems, arg.Limit, arg.Offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListAllSyncQueueItemsRow{}
|
|
for rows.Next() {
|
|
var i ListAllSyncQueueItemsRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.SyncType,
|
|
&i.SyncData,
|
|
&i.Priority,
|
|
&i.Attempts,
|
|
&i.MaxAttempts,
|
|
&i.Status,
|
|
&i.ErrorMessage,
|
|
&i.CreatedAt,
|
|
&i.ProcessedAt,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
&i.UserEmail,
|
|
&i.MediaTitle,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const ListDevicesByType = `-- name: ListDevicesByType :many
|
|
SELECT id, user_id, device_name, device_type, device_identifier, auth_token, last_sync, last_seen, sync_enabled, auto_sync, sync_frequency_minutes, device_metadata, created_at, updated_at FROM devices WHERE device_type = $1 ORDER BY created_at DESC
|
|
`
|
|
|
|
func (q *Queries) ListDevicesByType(ctx context.Context, deviceType string) ([]Devices, error) {
|
|
rows, err := q.db.Query(ctx, ListDevicesByType, deviceType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Devices{}
|
|
for rows.Next() {
|
|
var i Devices
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
&i.DeviceIdentifier,
|
|
&i.AuthToken,
|
|
&i.LastSync,
|
|
&i.LastSeen,
|
|
&i.SyncEnabled,
|
|
&i.AutoSync,
|
|
&i.SyncFrequencyMinutes,
|
|
&i.DeviceMetadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const ListDevicesByUser = `-- name: ListDevicesByUser :many
|
|
SELECT id, user_id, device_name, device_type, device_identifier, auth_token, last_sync, last_seen, sync_enabled, auto_sync, sync_frequency_minutes, device_metadata, created_at, updated_at FROM devices WHERE user_id = $1 ORDER BY created_at DESC
|
|
`
|
|
|
|
func (q *Queries) ListDevicesByUser(ctx context.Context, userID pgtype.UUID) ([]Devices, error) {
|
|
rows, err := q.db.Query(ctx, ListDevicesByUser, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Devices{}
|
|
for rows.Next() {
|
|
var i Devices
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
&i.DeviceIdentifier,
|
|
&i.AuthToken,
|
|
&i.LastSync,
|
|
&i.LastSeen,
|
|
&i.SyncEnabled,
|
|
&i.AutoSync,
|
|
&i.SyncFrequencyMinutes,
|
|
&i.DeviceMetadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const ListLibraries = `-- name: ListLibraries :many
|
|
SELECT l.id, l.name, l.description, l.library_type_id, l.created_by_admin_id, l.created_at, l.updated_at, lt.name as type_name, lt.description as type_description
|
|
FROM libraries l
|
|
JOIN library_types lt ON l.library_type_id = lt.id
|
|
ORDER BY l.created_at DESC
|
|
`
|
|
|
|
type ListLibrariesRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Name string `db:"name" json:"name"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
LibraryTypeID pgtype.UUID `db:"library_type_id" json:"library_type_id"`
|
|
CreatedByAdminID pgtype.UUID `db:"created_by_admin_id" json:"created_by_admin_id"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
TypeName string `db:"type_name" json:"type_name"`
|
|
TypeDescription pgtype.Text `db:"type_description" json:"type_description"`
|
|
}
|
|
|
|
func (q *Queries) ListLibraries(ctx context.Context) ([]ListLibrariesRow, error) {
|
|
rows, err := q.db.Query(ctx, ListLibraries)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListLibrariesRow{}
|
|
for rows.Next() {
|
|
var i ListLibrariesRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.LibraryTypeID,
|
|
&i.CreatedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.TypeName,
|
|
&i.TypeDescription,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const ListMediaItems = `-- name: ListMediaItems :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.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
|
|
JOIN library_types lt ON l.library_type_id = lt.id
|
|
ORDER BY mi.created_at DESC LIMIT $1 OFFSET $2
|
|
`
|
|
|
|
type ListMediaItemsParams struct {
|
|
Limit int32 `db:"limit" json:"limit"`
|
|
Offset int32 `db:"offset" json:"offset"`
|
|
}
|
|
|
|
type ListMediaItemsRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
Isbn pgtype.Text `db:"isbn" json:"isbn"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
FileSize pgtype.Int8 `db:"file_size" json:"file_size"`
|
|
MimeType pgtype.Text `db:"mime_type" json:"mime_type"`
|
|
CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"`
|
|
Series pgtype.Text `db:"series" json:"series"`
|
|
SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"`
|
|
Tags pgtype.Text `db:"tags" json:"tags"`
|
|
Asin pgtype.Text `db:"asin" json:"asin"`
|
|
DatePublished pgtype.Date `db:"date_published" json:"date_published"`
|
|
Publisher pgtype.Text `db:"publisher" json:"publisher"`
|
|
Contributors pgtype.Text `db:"contributors" json:"contributors"`
|
|
Language pgtype.Text `db:"language" json:"language"`
|
|
Edition pgtype.Text `db:"edition" json:"edition"`
|
|
PageCount pgtype.Int4 `db:"page_count" json:"page_count"`
|
|
Genre pgtype.Text `db:"genre" json:"genre"`
|
|
CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"`
|
|
GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"`
|
|
OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"`
|
|
GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"`
|
|
AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
FormatGroup string `db:"format_group" json:"format_group"`
|
|
FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"`
|
|
IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"`
|
|
HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"`
|
|
TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"`
|
|
ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"`
|
|
EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"`
|
|
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
|
KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"`
|
|
KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"`
|
|
OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"`
|
|
HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"`
|
|
LibraryName string `db:"library_name" json:"library_name"`
|
|
LibraryTypeName string `db:"library_type_name" json:"library_type_name"`
|
|
}
|
|
|
|
func (q *Queries) ListMediaItems(ctx context.Context, arg ListMediaItemsParams) ([]ListMediaItemsRow, error) {
|
|
rows, err := q.db.Query(ctx, ListMediaItems, arg.Limit, arg.Offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListMediaItemsRow{}
|
|
for rows.Next() {
|
|
var i ListMediaItemsRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
&i.LibraryName,
|
|
&i.LibraryTypeName,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const ListMediaItemsByLibrary = `-- name: ListMediaItemsByLibrary :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.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
|
|
JOIN library_types lt ON l.library_type_id = lt.id
|
|
WHERE mi.library_id = $1
|
|
ORDER BY mi.created_at DESC
|
|
`
|
|
|
|
type ListMediaItemsByLibraryRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
Isbn pgtype.Text `db:"isbn" json:"isbn"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
FileSize pgtype.Int8 `db:"file_size" json:"file_size"`
|
|
MimeType pgtype.Text `db:"mime_type" json:"mime_type"`
|
|
CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"`
|
|
Series pgtype.Text `db:"series" json:"series"`
|
|
SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"`
|
|
Tags pgtype.Text `db:"tags" json:"tags"`
|
|
Asin pgtype.Text `db:"asin" json:"asin"`
|
|
DatePublished pgtype.Date `db:"date_published" json:"date_published"`
|
|
Publisher pgtype.Text `db:"publisher" json:"publisher"`
|
|
Contributors pgtype.Text `db:"contributors" json:"contributors"`
|
|
Language pgtype.Text `db:"language" json:"language"`
|
|
Edition pgtype.Text `db:"edition" json:"edition"`
|
|
PageCount pgtype.Int4 `db:"page_count" json:"page_count"`
|
|
Genre pgtype.Text `db:"genre" json:"genre"`
|
|
CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"`
|
|
GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"`
|
|
OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"`
|
|
GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"`
|
|
AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
FormatGroup string `db:"format_group" json:"format_group"`
|
|
FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"`
|
|
IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"`
|
|
HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"`
|
|
TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"`
|
|
ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"`
|
|
EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"`
|
|
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
|
KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"`
|
|
KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"`
|
|
OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"`
|
|
HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"`
|
|
LibraryName string `db:"library_name" json:"library_name"`
|
|
LibraryTypeName string `db:"library_type_name" json:"library_type_name"`
|
|
}
|
|
|
|
func (q *Queries) ListMediaItemsByLibrary(ctx context.Context, libraryID pgtype.UUID) ([]ListMediaItemsByLibraryRow, error) {
|
|
rows, err := q.db.Query(ctx, ListMediaItemsByLibrary, libraryID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListMediaItemsByLibraryRow{}
|
|
for rows.Next() {
|
|
var i ListMediaItemsByLibraryRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
&i.LibraryName,
|
|
&i.LibraryTypeName,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const ListMediaItemsFiltered = `-- name: ListMediaItemsFiltered :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.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
|
|
JOIN library_types lt ON l.library_type_id = lt.id
|
|
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $1
|
|
WHERE mi.library_id = $2
|
|
AND COALESCE(lv.is_visible, true) = true
|
|
AND ($3 = '' OR mi.author ILIKE $3)
|
|
AND ($4 = '' OR mi.series ILIKE $4)
|
|
AND ($5 = '' OR mi.genre = $5)
|
|
AND ($6 = '' OR mi.language = $6)
|
|
AND ($7 = 0 OR mi.copyright_year >= $7)
|
|
AND ($8 = 0 OR mi.copyright_year <= $8)
|
|
AND ($9 = false OR mi.cover_image_path IS NOT NULL)
|
|
ORDER BY
|
|
CASE
|
|
WHEN $10 = 'title ASC' THEN mi.title
|
|
ELSE ''
|
|
END ASC,
|
|
CASE
|
|
WHEN $10 = 'title DESC' THEN mi.title
|
|
ELSE ''
|
|
END DESC,
|
|
CASE
|
|
WHEN $10 = 'author ASC' THEN COALESCE(mi.author, '')
|
|
ELSE ''
|
|
END ASC,
|
|
CASE
|
|
WHEN $10 = 'author DESC' THEN COALESCE(mi.author, '')
|
|
ELSE ''
|
|
END DESC,
|
|
CASE
|
|
WHEN $10 = 'created_at ASC' THEN mi.created_at
|
|
ELSE '1970-01-01'::timestamp
|
|
END ASC,
|
|
CASE
|
|
WHEN $10 = 'created_at DESC' THEN mi.created_at
|
|
ELSE '1970-01-01'::timestamp
|
|
END DESC,
|
|
mi.created_at DESC
|
|
LIMIT $12 OFFSET $11
|
|
`
|
|
|
|
type ListMediaItemsFilteredParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
AuthorFilter interface{} `db:"author_filter" json:"author_filter"`
|
|
SeriesFilter interface{} `db:"series_filter" json:"series_filter"`
|
|
GenreFilter interface{} `db:"genre_filter" json:"genre_filter"`
|
|
LanguageFilter interface{} `db:"language_filter" json:"language_filter"`
|
|
YearMin interface{} `db:"year_min" json:"year_min"`
|
|
YearMax interface{} `db:"year_max" json:"year_max"`
|
|
HasCover interface{} `db:"has_cover" json:"has_cover"`
|
|
Sort interface{} `db:"sort" json:"sort"`
|
|
Offset pgtype.Int4 `db:"offset" json:"offset"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
type ListMediaItemsFilteredRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
Isbn pgtype.Text `db:"isbn" json:"isbn"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
FileSize pgtype.Int8 `db:"file_size" json:"file_size"`
|
|
MimeType pgtype.Text `db:"mime_type" json:"mime_type"`
|
|
CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"`
|
|
Series pgtype.Text `db:"series" json:"series"`
|
|
SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"`
|
|
Tags pgtype.Text `db:"tags" json:"tags"`
|
|
Asin pgtype.Text `db:"asin" json:"asin"`
|
|
DatePublished pgtype.Date `db:"date_published" json:"date_published"`
|
|
Publisher pgtype.Text `db:"publisher" json:"publisher"`
|
|
Contributors pgtype.Text `db:"contributors" json:"contributors"`
|
|
Language pgtype.Text `db:"language" json:"language"`
|
|
Edition pgtype.Text `db:"edition" json:"edition"`
|
|
PageCount pgtype.Int4 `db:"page_count" json:"page_count"`
|
|
Genre pgtype.Text `db:"genre" json:"genre"`
|
|
CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"`
|
|
GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"`
|
|
OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"`
|
|
GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"`
|
|
AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
FormatGroup string `db:"format_group" json:"format_group"`
|
|
FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"`
|
|
IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"`
|
|
HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"`
|
|
TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"`
|
|
ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"`
|
|
EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"`
|
|
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
|
KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"`
|
|
KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"`
|
|
OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"`
|
|
HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"`
|
|
LibraryName string `db:"library_name" json:"library_name"`
|
|
LibraryTypeName string `db:"library_type_name" json:"library_type_name"`
|
|
}
|
|
|
|
func (q *Queries) ListMediaItemsFiltered(ctx context.Context, arg ListMediaItemsFilteredParams) ([]ListMediaItemsFilteredRow, error) {
|
|
rows, err := q.db.Query(ctx, ListMediaItemsFiltered,
|
|
arg.UserID,
|
|
arg.LibraryID,
|
|
arg.AuthorFilter,
|
|
arg.SeriesFilter,
|
|
arg.GenreFilter,
|
|
arg.LanguageFilter,
|
|
arg.YearMin,
|
|
arg.YearMax,
|
|
arg.HasCover,
|
|
arg.Sort,
|
|
arg.Offset,
|
|
arg.Limit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListMediaItemsFilteredRow{}
|
|
for rows.Next() {
|
|
var i ListMediaItemsFilteredRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
&i.LibraryName,
|
|
&i.LibraryTypeName,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const ListMediaItemsSorted = `-- name: ListMediaItemsSorted :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.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
|
|
JOIN library_types lt ON l.library_type_id = lt.id
|
|
WHERE mi.library_id = $1
|
|
ORDER BY
|
|
CASE
|
|
WHEN $2 = 'title ASC' THEN mi.title
|
|
ELSE ''
|
|
END ASC,
|
|
CASE
|
|
WHEN $2 = 'title DESC' THEN mi.title
|
|
ELSE ''
|
|
END DESC,
|
|
CASE
|
|
WHEN $2 = 'author ASC' THEN COALESCE(mi.author, '')
|
|
ELSE ''
|
|
END ASC,
|
|
CASE
|
|
WHEN $2 = 'author DESC' THEN COALESCE(mi.author, '')
|
|
ELSE ''
|
|
END DESC,
|
|
CASE
|
|
WHEN $2 = 'created_at ASC' THEN mi.created_at
|
|
ELSE '1970-01-01'::timestamp
|
|
END ASC,
|
|
CASE
|
|
WHEN $2 = 'created_at DESC' THEN mi.created_at
|
|
ELSE '1970-01-01'::timestamp
|
|
END DESC,
|
|
CASE
|
|
WHEN $2 = 'series ASC' THEN COALESCE(mi.series, '')
|
|
ELSE ''
|
|
END ASC,
|
|
CASE
|
|
WHEN $2 = 'series DESC' THEN COALESCE(mi.series, '')
|
|
ELSE ''
|
|
END DESC,
|
|
CASE
|
|
WHEN $2 = 'date_published ASC' THEN COALESCE(mi.date_published::text, '')
|
|
ELSE ''
|
|
END ASC,
|
|
CASE
|
|
WHEN $2 = 'date_published DESC' THEN COALESCE(mi.date_published::text, '')
|
|
ELSE ''
|
|
END DESC,
|
|
CASE
|
|
WHEN $2 = 'copyright_year ASC' THEN COALESCE(mi.copyright_year::text, '0')
|
|
ELSE ''
|
|
END ASC,
|
|
CASE
|
|
WHEN $2 = 'copyright_year DESC' THEN COALESCE(mi.copyright_year::text, '0')
|
|
ELSE ''
|
|
END DESC,
|
|
CASE
|
|
WHEN $2 = 'page_count ASC' THEN COALESCE(mi.page_count::text, '0')
|
|
ELSE ''
|
|
END ASC,
|
|
CASE
|
|
WHEN $2 = 'page_count DESC' THEN COALESCE(mi.page_count::text, '0')
|
|
ELSE ''
|
|
END DESC,
|
|
CASE
|
|
WHEN $2 = 'genre ASC' THEN COALESCE(mi.genre, '')
|
|
ELSE ''
|
|
END ASC,
|
|
CASE
|
|
WHEN $2 = 'genre DESC' THEN COALESCE(mi.genre, '')
|
|
ELSE ''
|
|
END DESC,
|
|
mi.created_at DESC
|
|
LIMIT $4 OFFSET $3
|
|
`
|
|
|
|
type ListMediaItemsSortedParams struct {
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Sort interface{} `db:"sort" json:"sort"`
|
|
Offset pgtype.Int4 `db:"offset" json:"offset"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
type ListMediaItemsSortedRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
Isbn pgtype.Text `db:"isbn" json:"isbn"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
FileSize pgtype.Int8 `db:"file_size" json:"file_size"`
|
|
MimeType pgtype.Text `db:"mime_type" json:"mime_type"`
|
|
CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"`
|
|
Series pgtype.Text `db:"series" json:"series"`
|
|
SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"`
|
|
Tags pgtype.Text `db:"tags" json:"tags"`
|
|
Asin pgtype.Text `db:"asin" json:"asin"`
|
|
DatePublished pgtype.Date `db:"date_published" json:"date_published"`
|
|
Publisher pgtype.Text `db:"publisher" json:"publisher"`
|
|
Contributors pgtype.Text `db:"contributors" json:"contributors"`
|
|
Language pgtype.Text `db:"language" json:"language"`
|
|
Edition pgtype.Text `db:"edition" json:"edition"`
|
|
PageCount pgtype.Int4 `db:"page_count" json:"page_count"`
|
|
Genre pgtype.Text `db:"genre" json:"genre"`
|
|
CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"`
|
|
GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"`
|
|
OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"`
|
|
GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"`
|
|
AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
FormatGroup string `db:"format_group" json:"format_group"`
|
|
FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"`
|
|
IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"`
|
|
HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"`
|
|
TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"`
|
|
ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"`
|
|
EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"`
|
|
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
|
KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"`
|
|
KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"`
|
|
OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"`
|
|
HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"`
|
|
LibraryName string `db:"library_name" json:"library_name"`
|
|
LibraryTypeName string `db:"library_type_name" json:"library_type_name"`
|
|
}
|
|
|
|
func (q *Queries) ListMediaItemsSorted(ctx context.Context, arg ListMediaItemsSortedParams) ([]ListMediaItemsSortedRow, error) {
|
|
rows, err := q.db.Query(ctx, ListMediaItemsSorted,
|
|
arg.LibraryID,
|
|
arg.Sort,
|
|
arg.Offset,
|
|
arg.Limit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListMediaItemsSortedRow{}
|
|
for rows.Next() {
|
|
var i ListMediaItemsSortedRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
&i.LibraryName,
|
|
&i.LibraryTypeName,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const ListPendingSyncQueueItems = `-- name: ListPendingSyncQueueItems :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 device_id = $1 AND status = 'pending'
|
|
ORDER BY priority ASC, created_at ASC
|
|
LIMIT $2
|
|
`
|
|
|
|
type ListPendingSyncQueueItemsParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
Limit int32 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
func (q *Queries) ListPendingSyncQueueItems(ctx context.Context, arg ListPendingSyncQueueItemsParams) ([]SyncQueue, error) {
|
|
rows, err := q.db.Query(ctx, ListPendingSyncQueueItems, arg.DeviceID, arg.Limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SyncQueue{}
|
|
for rows.Next() {
|
|
var i SyncQueue
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.SyncType,
|
|
&i.SyncData,
|
|
&i.Priority,
|
|
&i.Attempts,
|
|
&i.MaxAttempts,
|
|
&i.Status,
|
|
&i.ErrorMessage,
|
|
&i.CreatedAt,
|
|
&i.ProcessedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const ListSyncConflictsByMediaItem = `-- name: ListSyncConflictsByMediaItem :many
|
|
SELECT id, media_item_id, user_id, conflict_type, conflict_data, resolution_status, resolution_data, resolved_by, resolved_at, created_at FROM sync_conflicts
|
|
WHERE media_item_id = $1 AND user_id = $2
|
|
ORDER BY created_at DESC
|
|
`
|
|
|
|
type ListSyncConflictsByMediaItemParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
func (q *Queries) ListSyncConflictsByMediaItem(ctx context.Context, arg ListSyncConflictsByMediaItemParams) ([]SyncConflicts, error) {
|
|
rows, err := q.db.Query(ctx, ListSyncConflictsByMediaItem, arg.MediaItemID, arg.UserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SyncConflicts{}
|
|
for rows.Next() {
|
|
var i SyncConflicts
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.ConflictType,
|
|
&i.ConflictData,
|
|
&i.ResolutionStatus,
|
|
&i.ResolutionData,
|
|
&i.ResolvedBy,
|
|
&i.ResolvedAt,
|
|
&i.CreatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const ListSyncConflictsByUser = `-- name: ListSyncConflictsByUser :many
|
|
SELECT sc.id, sc.media_item_id, sc.user_id, sc.conflict_type, sc.conflict_data, sc.resolution_status, sc.resolution_data, sc.resolved_by, sc.resolved_at, sc.created_at, mi.title, mi.author
|
|
FROM sync_conflicts sc
|
|
JOIN media_items mi ON sc.media_item_id = mi.id
|
|
WHERE sc.user_id = $1 AND sc.resolution_status = 'unresolved'
|
|
ORDER BY sc.created_at DESC
|
|
`
|
|
|
|
type ListSyncConflictsByUserRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
ConflictType string `db:"conflict_type" json:"conflict_type"`
|
|
ConflictData []byte `db:"conflict_data" json:"conflict_data"`
|
|
ResolutionStatus pgtype.Text `db:"resolution_status" json:"resolution_status"`
|
|
ResolutionData []byte `db:"resolution_data" json:"resolution_data"`
|
|
ResolvedBy pgtype.UUID `db:"resolved_by" json:"resolved_by"`
|
|
ResolvedAt pgtype.Timestamptz `db:"resolved_at" json:"resolved_at"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
}
|
|
|
|
func (q *Queries) ListSyncConflictsByUser(ctx context.Context, userID pgtype.UUID) ([]ListSyncConflictsByUserRow, error) {
|
|
rows, err := q.db.Query(ctx, ListSyncConflictsByUser, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListSyncConflictsByUserRow{}
|
|
for rows.Next() {
|
|
var i ListSyncConflictsByUserRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.ConflictType,
|
|
&i.ConflictData,
|
|
&i.ResolutionStatus,
|
|
&i.ResolutionData,
|
|
&i.ResolvedBy,
|
|
&i.ResolvedAt,
|
|
&i.CreatedAt,
|
|
&i.Title,
|
|
&i.Author,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const ListUnresolvedUnlinkedBooks = `-- name: ListUnresolvedUnlinkedBooks :many
|
|
SELECT ub.id, ub.device_id, ub.content_id, ub.file_path, ub.title, ub.author, ub.confidence_score, ub.resolved, ub.media_item_id, ub.resolved_at, ub.resolution_method, ub.last_seen_at, ub.created_at, d.device_name, d.device_type
|
|
FROM unlinked_books ub
|
|
JOIN devices d ON ub.device_id = d.id
|
|
WHERE ub.resolved = false
|
|
ORDER BY ub.last_seen_at DESC
|
|
LIMIT $1 OFFSET $2
|
|
`
|
|
|
|
type ListUnresolvedUnlinkedBooksParams struct {
|
|
Limit int32 `db:"limit" json:"limit"`
|
|
Offset int32 `db:"offset" json:"offset"`
|
|
}
|
|
|
|
type ListUnresolvedUnlinkedBooksRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
ContentID string `db:"content_id" json:"content_id"`
|
|
FilePath pgtype.Text `db:"file_path" json:"file_path"`
|
|
Title pgtype.Text `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"`
|
|
Resolved pgtype.Bool `db:"resolved" json:"resolved"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
ResolvedAt pgtype.Timestamptz `db:"resolved_at" json:"resolved_at"`
|
|
ResolutionMethod pgtype.Text `db:"resolution_method" json:"resolution_method"`
|
|
LastSeenAt pgtype.Timestamptz `db:"last_seen_at" json:"last_seen_at"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
DeviceName string `db:"device_name" json:"device_name"`
|
|
DeviceType string `db:"device_type" json:"device_type"`
|
|
}
|
|
|
|
// List unresolved unlinked books with pagination
|
|
func (q *Queries) ListUnresolvedUnlinkedBooks(ctx context.Context, arg ListUnresolvedUnlinkedBooksParams) ([]ListUnresolvedUnlinkedBooksRow, error) {
|
|
rows, err := q.db.Query(ctx, ListUnresolvedUnlinkedBooks, arg.Limit, arg.Offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListUnresolvedUnlinkedBooksRow{}
|
|
for rows.Next() {
|
|
var i ListUnresolvedUnlinkedBooksRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.ContentID,
|
|
&i.FilePath,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.ConfidenceScore,
|
|
&i.Resolved,
|
|
&i.MediaItemID,
|
|
&i.ResolvedAt,
|
|
&i.ResolutionMethod,
|
|
&i.LastSeenAt,
|
|
&i.CreatedAt,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
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
|
|
`
|
|
|
|
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"`
|
|
}
|
|
|
|
func (q *Queries) ListUsers(ctx context.Context) ([]ListUsersRow, error) {
|
|
rows, err := q.db.Query(ctx, ListUsers)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListUsersRow{}
|
|
for rows.Next() {
|
|
var i ListUsersRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Email,
|
|
&i.Username,
|
|
&i.Theme,
|
|
&i.FirstName,
|
|
&i.LastName,
|
|
&i.Role,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const QueryMediaItemsByIdentifiers = `-- name: QueryMediaItemsByIdentifiers :many
|
|
SELECT
|
|
mi.id,
|
|
mi.file_sha256,
|
|
mi.opf_identifier,
|
|
mi.opf_uuid,
|
|
mi.isbn,
|
|
mi.asin,
|
|
mi.title,
|
|
mi.author,
|
|
mi.file_size,
|
|
mi.hash_confidence,
|
|
CASE
|
|
WHEN $1::uuid IS NOT NULL AND mi.id = $1::uuid THEN 1.0
|
|
WHEN mi.opf_uuid IS NOT NULL AND mi.opf_uuid = $2::text THEN 0.95
|
|
WHEN mi.file_sha256 IS NOT NULL AND mi.file_sha256 = $3::text THEN 0.9
|
|
WHEN mi.opf_identifier IS NOT NULL AND mi.opf_identifier = $4::text THEN 0.85
|
|
WHEN mi.isbn IS NOT NULL AND mi.isbn = $5::text THEN 0.8
|
|
WHEN mi.asin IS NOT NULL AND mi.asin = $6::text THEN 0.8
|
|
ELSE 0.5
|
|
END as confidence_score
|
|
FROM media_items mi
|
|
WHERE
|
|
($1::uuid IS NULL OR mi.id = $1::uuid)
|
|
OR ($2::text IS NULL OR mi.opf_uuid = $2::text)
|
|
OR ($3::text IS NULL OR mi.file_sha256 = $3::text)
|
|
OR ($4::text IS NULL OR mi.opf_identifier = $4::text)
|
|
OR ($5::text IS NULL OR mi.isbn = $5::text)
|
|
OR ($6::text IS NULL OR mi.asin = $6::text)
|
|
OR ($7::text IS NULL OR mi.title ILIKE '%' || $7::text || '%')
|
|
ORDER BY confidence_score DESC
|
|
`
|
|
|
|
type QueryMediaItemsByIdentifiersParams struct {
|
|
Column1 pgtype.UUID `db:"column_1" json:"column_1"`
|
|
Column2 string `db:"column_2" json:"column_2"`
|
|
Column3 string `db:"column_3" json:"column_3"`
|
|
Column4 string `db:"column_4" json:"column_4"`
|
|
Column5 string `db:"column_5" json:"column_5"`
|
|
Column6 string `db:"column_6" json:"column_6"`
|
|
Column7 string `db:"column_7" json:"column_7"`
|
|
}
|
|
|
|
type QueryMediaItemsByIdentifiersRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"`
|
|
OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"`
|
|
Isbn pgtype.Text `db:"isbn" json:"isbn"`
|
|
Asin pgtype.Text `db:"asin" json:"asin"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
FileSize pgtype.Int8 `db:"file_size" json:"file_size"`
|
|
HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"`
|
|
ConfidenceScore float64 `db:"confidence_score" json:"confidence_score"`
|
|
}
|
|
|
|
// Query media items by multiple identifiers with confidence scoring
|
|
func (q *Queries) QueryMediaItemsByIdentifiers(ctx context.Context, arg QueryMediaItemsByIdentifiersParams) ([]QueryMediaItemsByIdentifiersRow, error) {
|
|
rows, err := q.db.Query(ctx, QueryMediaItemsByIdentifiers,
|
|
arg.Column1,
|
|
arg.Column2,
|
|
arg.Column3,
|
|
arg.Column4,
|
|
arg.Column5,
|
|
arg.Column6,
|
|
arg.Column7,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []QueryMediaItemsByIdentifiersRow{}
|
|
for rows.Next() {
|
|
var i QueryMediaItemsByIdentifiersRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.Isbn,
|
|
&i.Asin,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.FileSize,
|
|
&i.HashConfidence,
|
|
&i.ConfidenceScore,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const RemoveBookFromCollection = `-- name: RemoveBookFromCollection :exec
|
|
DELETE FROM collection_items WHERE collection_id = $1 AND media_item_id = $2
|
|
`
|
|
|
|
type RemoveBookFromCollectionParams struct {
|
|
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
}
|
|
|
|
// Remove book from collection
|
|
func (q *Queries) RemoveBookFromCollection(ctx context.Context, arg RemoveBookFromCollectionParams) error {
|
|
_, err := q.db.Exec(ctx, RemoveBookFromCollection, arg.CollectionID, arg.MediaItemID)
|
|
return err
|
|
}
|
|
|
|
const RemoveBookFromKoboShelf = `-- name: RemoveBookFromKoboShelf :exec
|
|
DELETE FROM kobo_shelves WHERE device_id = $1 AND media_item_id = $2
|
|
`
|
|
|
|
type RemoveBookFromKoboShelfParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
}
|
|
|
|
func (q *Queries) RemoveBookFromKoboShelf(ctx context.Context, arg RemoveBookFromKoboShelfParams) error {
|
|
_, err := q.db.Exec(ctx, RemoveBookFromKoboShelf, arg.DeviceID, arg.MediaItemID)
|
|
return err
|
|
}
|
|
|
|
const ResolveSyncConflict = `-- name: ResolveSyncConflict :one
|
|
UPDATE sync_conflicts
|
|
SET
|
|
resolution_status = $2,
|
|
resolution_data = $3,
|
|
resolved_by = $4,
|
|
resolved_at = NOW()
|
|
WHERE id = $1
|
|
RETURNING id, media_item_id, user_id, conflict_type, conflict_data, resolution_status, resolution_data, resolved_by, resolved_at, created_at
|
|
`
|
|
|
|
type ResolveSyncConflictParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
ResolutionStatus pgtype.Text `db:"resolution_status" json:"resolution_status"`
|
|
ResolutionData []byte `db:"resolution_data" json:"resolution_data"`
|
|
ResolvedBy pgtype.UUID `db:"resolved_by" json:"resolved_by"`
|
|
}
|
|
|
|
func (q *Queries) ResolveSyncConflict(ctx context.Context, arg ResolveSyncConflictParams) (SyncConflicts, error) {
|
|
row := q.db.QueryRow(ctx, ResolveSyncConflict,
|
|
arg.ID,
|
|
arg.ResolutionStatus,
|
|
arg.ResolutionData,
|
|
arg.ResolvedBy,
|
|
)
|
|
var i SyncConflicts
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.ConflictType,
|
|
&i.ConflictData,
|
|
&i.ResolutionStatus,
|
|
&i.ResolutionData,
|
|
&i.ResolvedBy,
|
|
&i.ResolvedAt,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const ResolveUnlinkedBook = `-- name: ResolveUnlinkedBook :one
|
|
UPDATE unlinked_books
|
|
SET
|
|
resolved = true,
|
|
media_item_id = $2,
|
|
resolved_at = NOW(),
|
|
resolution_method = $3
|
|
WHERE id = $1
|
|
RETURNING id, device_id, content_id, file_path, title, author, confidence_score, resolved, media_item_id, resolved_at, resolution_method, last_seen_at, created_at
|
|
`
|
|
|
|
type ResolveUnlinkedBookParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
ResolutionMethod pgtype.Text `db:"resolution_method" json:"resolution_method"`
|
|
}
|
|
|
|
// Resolve unlinked book
|
|
func (q *Queries) ResolveUnlinkedBook(ctx context.Context, arg ResolveUnlinkedBookParams) (UnlinkedBooks, error) {
|
|
row := q.db.QueryRow(ctx, ResolveUnlinkedBook, arg.ID, arg.MediaItemID, arg.ResolutionMethod)
|
|
var i UnlinkedBooks
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.ContentID,
|
|
&i.FilePath,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.ConfidenceScore,
|
|
&i.Resolved,
|
|
&i.MediaItemID,
|
|
&i.ResolvedAt,
|
|
&i.ResolutionMethod,
|
|
&i.LastSeenAt,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const RevokeAllUserRefreshTokens = `-- name: RevokeAllUserRefreshTokens :exec
|
|
UPDATE refresh_tokens SET revoked_at = NOW() WHERE user_id = $1 AND revoked_at IS NULL
|
|
`
|
|
|
|
func (q *Queries) RevokeAllUserRefreshTokens(ctx context.Context, userID pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, RevokeAllUserRefreshTokens, userID)
|
|
return err
|
|
}
|
|
|
|
const RevokeDevice = `-- name: RevokeDevice :exec
|
|
UPDATE devices
|
|
SET
|
|
auth_token = NULL,
|
|
sync_enabled = false,
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) RevokeDevice(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, RevokeDevice, id)
|
|
return err
|
|
}
|
|
|
|
const RevokeOpdsToken = `-- name: RevokeOpdsToken :exec
|
|
DELETE FROM opds_tokens WHERE token = $1
|
|
`
|
|
|
|
// Revoke OPDS token
|
|
func (q *Queries) RevokeOpdsToken(ctx context.Context, token string) error {
|
|
_, err := q.db.Exec(ctx, RevokeOpdsToken, token)
|
|
return err
|
|
}
|
|
|
|
const RevokeRefreshToken = `-- name: RevokeRefreshToken :exec
|
|
UPDATE refresh_tokens SET revoked_at = NOW() WHERE token = $1
|
|
`
|
|
|
|
func (q *Queries) RevokeRefreshToken(ctx context.Context, token pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, RevokeRefreshToken, token)
|
|
return 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.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
|
|
JOIN library_types lt ON l.library_type_id = lt.id
|
|
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $1
|
|
WHERE COALESCE(lv.is_visible, true) = true
|
|
AND (
|
|
mi.title ILIKE $2 OR
|
|
mi.author ILIKE $2 OR
|
|
mi.series ILIKE $2 OR
|
|
mi.tags ILIKE $2 OR
|
|
mi.contributors ILIKE $2
|
|
)
|
|
ORDER BY
|
|
CASE
|
|
WHEN mi.title ILIKE $2 THEN 1
|
|
WHEN mi.author ILIKE $2 THEN 2
|
|
WHEN mi.series ILIKE $2 THEN 3
|
|
WHEN mi.tags ILIKE $2 THEN 4
|
|
ELSE 5
|
|
END,
|
|
mi.title ASC
|
|
LIMIT $4 OFFSET $3
|
|
`
|
|
|
|
type SearchMediaItemsParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
SearchPattern pgtype.Text `db:"search_pattern" json:"search_pattern"`
|
|
Offset pgtype.Int4 `db:"offset" json:"offset"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
type SearchMediaItemsRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
Isbn pgtype.Text `db:"isbn" json:"isbn"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
FileSize pgtype.Int8 `db:"file_size" json:"file_size"`
|
|
MimeType pgtype.Text `db:"mime_type" json:"mime_type"`
|
|
CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"`
|
|
Series pgtype.Text `db:"series" json:"series"`
|
|
SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"`
|
|
Tags pgtype.Text `db:"tags" json:"tags"`
|
|
Asin pgtype.Text `db:"asin" json:"asin"`
|
|
DatePublished pgtype.Date `db:"date_published" json:"date_published"`
|
|
Publisher pgtype.Text `db:"publisher" json:"publisher"`
|
|
Contributors pgtype.Text `db:"contributors" json:"contributors"`
|
|
Language pgtype.Text `db:"language" json:"language"`
|
|
Edition pgtype.Text `db:"edition" json:"edition"`
|
|
PageCount pgtype.Int4 `db:"page_count" json:"page_count"`
|
|
Genre pgtype.Text `db:"genre" json:"genre"`
|
|
CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"`
|
|
GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"`
|
|
OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"`
|
|
GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"`
|
|
AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
FormatGroup string `db:"format_group" json:"format_group"`
|
|
FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"`
|
|
IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"`
|
|
HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"`
|
|
TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"`
|
|
ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"`
|
|
EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"`
|
|
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
|
KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"`
|
|
KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"`
|
|
OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"`
|
|
HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"`
|
|
LibraryName string `db:"library_name" json:"library_name"`
|
|
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,
|
|
arg.UserID,
|
|
arg.SearchPattern,
|
|
arg.Offset,
|
|
arg.Limit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SearchMediaItemsRow{}
|
|
for rows.Next() {
|
|
var i SearchMediaItemsRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
&i.LibraryName,
|
|
&i.LibraryTypeName,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const SearchMediaItemsFuzzy = `-- name: SearchMediaItemsFuzzy :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.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
|
|
JOIN library_types lt ON l.library_type_id = lt.id
|
|
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $1
|
|
WHERE COALESCE(lv.is_visible, true) = true
|
|
AND (
|
|
word_similarity($2, mi.title) > 0.3 OR
|
|
word_similarity($2, COALESCE(mi.author, '')) > 0.3 OR
|
|
word_similarity($2, COALESCE(mi.series, '')) > 0.3 OR
|
|
word_similarity($2, COALESCE(mi.tags, '')) > 0.3 OR
|
|
word_similarity($2, COALESCE(mi.contributors, '')) > 0.3
|
|
)
|
|
ORDER BY
|
|
GREATEST(
|
|
word_similarity($2, mi.title),
|
|
word_similarity($2, COALESCE(mi.author, '')),
|
|
word_similarity($2, COALESCE(mi.series, '')),
|
|
word_similarity($2, COALESCE(mi.tags, '')),
|
|
word_similarity($2, COALESCE(mi.contributors, ''))
|
|
) DESC,
|
|
mi.title ASC
|
|
LIMIT $4 OFFSET $3
|
|
`
|
|
|
|
type SearchMediaItemsFuzzyParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
SearchQuery interface{} `db:"search_query" json:"search_query"`
|
|
Offset pgtype.Int4 `db:"offset" json:"offset"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
type SearchMediaItemsFuzzyRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
Isbn pgtype.Text `db:"isbn" json:"isbn"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
FileSize pgtype.Int8 `db:"file_size" json:"file_size"`
|
|
MimeType pgtype.Text `db:"mime_type" json:"mime_type"`
|
|
CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"`
|
|
Series pgtype.Text `db:"series" json:"series"`
|
|
SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"`
|
|
Tags pgtype.Text `db:"tags" json:"tags"`
|
|
Asin pgtype.Text `db:"asin" json:"asin"`
|
|
DatePublished pgtype.Date `db:"date_published" json:"date_published"`
|
|
Publisher pgtype.Text `db:"publisher" json:"publisher"`
|
|
Contributors pgtype.Text `db:"contributors" json:"contributors"`
|
|
Language pgtype.Text `db:"language" json:"language"`
|
|
Edition pgtype.Text `db:"edition" json:"edition"`
|
|
PageCount pgtype.Int4 `db:"page_count" json:"page_count"`
|
|
Genre pgtype.Text `db:"genre" json:"genre"`
|
|
CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"`
|
|
GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"`
|
|
OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"`
|
|
GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"`
|
|
AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
FormatGroup string `db:"format_group" json:"format_group"`
|
|
FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"`
|
|
IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"`
|
|
HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"`
|
|
TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"`
|
|
ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"`
|
|
EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"`
|
|
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
|
KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"`
|
|
KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"`
|
|
OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"`
|
|
HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"`
|
|
LibraryName string `db:"library_name" json:"library_name"`
|
|
LibraryTypeName string `db:"library_type_name" json:"library_type_name"`
|
|
}
|
|
|
|
func (q *Queries) SearchMediaItemsFuzzy(ctx context.Context, arg SearchMediaItemsFuzzyParams) ([]SearchMediaItemsFuzzyRow, error) {
|
|
rows, err := q.db.Query(ctx, SearchMediaItemsFuzzy,
|
|
arg.UserID,
|
|
arg.SearchQuery,
|
|
arg.Offset,
|
|
arg.Limit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SearchMediaItemsFuzzyRow{}
|
|
for rows.Next() {
|
|
var i SearchMediaItemsFuzzyRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
&i.LibraryName,
|
|
&i.LibraryTypeName,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const SetLibraryVisibility = `-- name: SetLibraryVisibility :one
|
|
INSERT INTO library_visibility (user_id, library_id, is_visible)
|
|
VALUES ($1, $2, $3)
|
|
ON CONFLICT (user_id, library_id)
|
|
DO UPDATE SET
|
|
is_visible = EXCLUDED.is_visible,
|
|
updated_at = NOW()
|
|
RETURNING id, user_id, library_id, is_visible, created_at, updated_at
|
|
`
|
|
|
|
type SetLibraryVisibilityParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
IsVisible bool `db:"is_visible" json:"is_visible"`
|
|
}
|
|
|
|
// Library Visibility queries
|
|
func (q *Queries) SetLibraryVisibility(ctx context.Context, arg SetLibraryVisibilityParams) (LibraryVisibility, error) {
|
|
row := q.db.QueryRow(ctx, SetLibraryVisibility, arg.UserID, arg.LibraryID, arg.IsVisible)
|
|
var i LibraryVisibility
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.LibraryID,
|
|
&i.IsVisible,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const SetSystemConfig = `-- name: SetSystemConfig :one
|
|
INSERT INTO system_config (key, value, updated_by)
|
|
VALUES ($1, $2, $3)
|
|
ON CONFLICT (key)
|
|
DO UPDATE SET
|
|
value = EXCLUDED.value,
|
|
updated_by = EXCLUDED.updated_by,
|
|
updated_at = NOW()
|
|
RETURNING key, value, updated_at, updated_by
|
|
`
|
|
|
|
type SetSystemConfigParams struct {
|
|
Key string `db:"key" json:"key"`
|
|
Value string `db:"value" json:"value"`
|
|
UpdatedBy pgtype.UUID `db:"updated_by" json:"updated_by"`
|
|
}
|
|
|
|
// Set system config
|
|
func (q *Queries) SetSystemConfig(ctx context.Context, arg SetSystemConfigParams) (SystemConfig, error) {
|
|
row := q.db.QueryRow(ctx, SetSystemConfig, arg.Key, arg.Value, arg.UpdatedBy)
|
|
var i SystemConfig
|
|
err := row.Scan(
|
|
&i.Key,
|
|
&i.Value,
|
|
&i.UpdatedAt,
|
|
&i.UpdatedBy,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateCollection = `-- name: UpdateCollection :one
|
|
UPDATE collections
|
|
SET
|
|
name = $2,
|
|
description = $3,
|
|
color = $4,
|
|
icon = $5,
|
|
auto_assign_rules = $6,
|
|
view_settings = $7
|
|
WHERE id = $1
|
|
RETURNING id, user_id, name, description, color, icon, auto_assign_rules, view_settings, created_at
|
|
`
|
|
|
|
type UpdateCollectionParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Name string `db:"name" json:"name"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
Color pgtype.Text `db:"color" json:"color"`
|
|
Icon pgtype.Text `db:"icon" json:"icon"`
|
|
AutoAssignRules []byte `db:"auto_assign_rules" json:"auto_assign_rules"`
|
|
ViewSettings []byte `db:"view_settings" json:"view_settings"`
|
|
}
|
|
|
|
// Update collection
|
|
func (q *Queries) UpdateCollection(ctx context.Context, arg UpdateCollectionParams) (Collections, error) {
|
|
row := q.db.QueryRow(ctx, UpdateCollection,
|
|
arg.ID,
|
|
arg.Name,
|
|
arg.Description,
|
|
arg.Color,
|
|
arg.Icon,
|
|
arg.AutoAssignRules,
|
|
arg.ViewSettings,
|
|
)
|
|
var i Collections
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.Color,
|
|
&i.Icon,
|
|
&i.AutoAssignRules,
|
|
&i.ViewSettings,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateDevice = `-- name: UpdateDevice :one
|
|
UPDATE devices
|
|
SET
|
|
device_name = $2,
|
|
sync_enabled = $3,
|
|
auto_sync = $4,
|
|
sync_frequency_minutes = $5,
|
|
device_metadata = $6,
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
RETURNING id, user_id, device_name, device_type, device_identifier, auth_token, last_sync, last_seen, sync_enabled, auto_sync, sync_frequency_minutes, device_metadata, created_at, updated_at
|
|
`
|
|
|
|
type UpdateDeviceParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
DeviceName string `db:"device_name" json:"device_name"`
|
|
SyncEnabled pgtype.Bool `db:"sync_enabled" json:"sync_enabled"`
|
|
AutoSync pgtype.Bool `db:"auto_sync" json:"auto_sync"`
|
|
SyncFrequencyMinutes pgtype.Int4 `db:"sync_frequency_minutes" json:"sync_frequency_minutes"`
|
|
DeviceMetadata []byte `db:"device_metadata" json:"device_metadata"`
|
|
}
|
|
|
|
func (q *Queries) UpdateDevice(ctx context.Context, arg UpdateDeviceParams) (Devices, error) {
|
|
row := q.db.QueryRow(ctx, UpdateDevice,
|
|
arg.ID,
|
|
arg.DeviceName,
|
|
arg.SyncEnabled,
|
|
arg.AutoSync,
|
|
arg.SyncFrequencyMinutes,
|
|
arg.DeviceMetadata,
|
|
)
|
|
var i Devices
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
&i.DeviceIdentifier,
|
|
&i.AuthToken,
|
|
&i.LastSync,
|
|
&i.LastSeen,
|
|
&i.SyncEnabled,
|
|
&i.AutoSync,
|
|
&i.SyncFrequencyMinutes,
|
|
&i.DeviceMetadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateDeviceCatalogAvailability = `-- name: UpdateDeviceCatalogAvailability :exec
|
|
UPDATE device_catalogs
|
|
SET available = $2
|
|
WHERE id = $1
|
|
`
|
|
|
|
type UpdateDeviceCatalogAvailabilityParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Available pgtype.Bool `db:"available" json:"available"`
|
|
}
|
|
|
|
// Update device catalog availability
|
|
func (q *Queries) UpdateDeviceCatalogAvailability(ctx context.Context, arg UpdateDeviceCatalogAvailabilityParams) error {
|
|
_, err := q.db.Exec(ctx, UpdateDeviceCatalogAvailability, arg.ID, arg.Available)
|
|
return err
|
|
}
|
|
|
|
const UpdateDeviceFileAlias = `-- name: UpdateDeviceFileAlias :one
|
|
UPDATE device_file_aliases
|
|
SET
|
|
media_item_id = $2,
|
|
file_sha256 = $3,
|
|
confidence_score = $4,
|
|
last_seen_at = NOW()
|
|
WHERE id = $1
|
|
RETURNING id, media_item_id, device_id, file_path, file_sha256, confidence_score, last_seen_at
|
|
`
|
|
|
|
type UpdateDeviceFileAliasParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"`
|
|
}
|
|
|
|
// Update device file alias
|
|
func (q *Queries) UpdateDeviceFileAlias(ctx context.Context, arg UpdateDeviceFileAliasParams) (DeviceFileAliases, error) {
|
|
row := q.db.QueryRow(ctx, UpdateDeviceFileAlias,
|
|
arg.ID,
|
|
arg.MediaItemID,
|
|
arg.FileSha256,
|
|
arg.ConfidenceScore,
|
|
)
|
|
var i DeviceFileAliases
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.DeviceID,
|
|
&i.FilePath,
|
|
&i.FileSha256,
|
|
&i.ConfidenceScore,
|
|
&i.LastSeenAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateDeviceLastSeen = `-- name: UpdateDeviceLastSeen :one
|
|
UPDATE devices
|
|
SET
|
|
last_seen = NOW(),
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
RETURNING id, user_id, device_name, device_type, device_identifier, auth_token, last_sync, last_seen, sync_enabled, auto_sync, sync_frequency_minutes, device_metadata, created_at, updated_at
|
|
`
|
|
|
|
func (q *Queries) UpdateDeviceLastSeen(ctx context.Context, id pgtype.UUID) (Devices, error) {
|
|
row := q.db.QueryRow(ctx, UpdateDeviceLastSeen, id)
|
|
var i Devices
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
&i.DeviceIdentifier,
|
|
&i.AuthToken,
|
|
&i.LastSync,
|
|
&i.LastSeen,
|
|
&i.SyncEnabled,
|
|
&i.AutoSync,
|
|
&i.SyncFrequencyMinutes,
|
|
&i.DeviceMetadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateDeviceLastSync = `-- name: UpdateDeviceLastSync :one
|
|
UPDATE devices
|
|
SET
|
|
last_sync = NOW(),
|
|
last_seen = NOW(),
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
RETURNING id, user_id, device_name, device_type, device_identifier, auth_token, last_sync, last_seen, sync_enabled, auto_sync, sync_frequency_minutes, device_metadata, created_at, updated_at
|
|
`
|
|
|
|
func (q *Queries) UpdateDeviceLastSync(ctx context.Context, id pgtype.UUID) (Devices, error) {
|
|
row := q.db.QueryRow(ctx, UpdateDeviceLastSync, id)
|
|
var i Devices
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
&i.DeviceIdentifier,
|
|
&i.AuthToken,
|
|
&i.LastSync,
|
|
&i.LastSeen,
|
|
&i.SyncEnabled,
|
|
&i.AutoSync,
|
|
&i.SyncFrequencyMinutes,
|
|
&i.DeviceMetadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateDeviceShelfMapping = `-- name: UpdateDeviceShelfMapping :one
|
|
UPDATE device_shelf_mappings
|
|
SET
|
|
device_shelf_name = $2,
|
|
sync_direction = $3
|
|
WHERE id = $1
|
|
RETURNING id, collection_id, device_id, device_shelf_name, sync_direction, created_at
|
|
`
|
|
|
|
type UpdateDeviceShelfMappingParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
DeviceShelfName pgtype.Text `db:"device_shelf_name" json:"device_shelf_name"`
|
|
SyncDirection pgtype.Text `db:"sync_direction" json:"sync_direction"`
|
|
}
|
|
|
|
// Update device shelf mapping
|
|
func (q *Queries) UpdateDeviceShelfMapping(ctx context.Context, arg UpdateDeviceShelfMappingParams) (DeviceShelfMappings, error) {
|
|
row := q.db.QueryRow(ctx, UpdateDeviceShelfMapping, arg.ID, arg.DeviceShelfName, arg.SyncDirection)
|
|
var i DeviceShelfMappings
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.CollectionID,
|
|
&i.DeviceID,
|
|
&i.DeviceShelfName,
|
|
&i.SyncDirection,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateDeviceSyncTimestamp = `-- name: UpdateDeviceSyncTimestamp :one
|
|
UPDATE devices
|
|
SET
|
|
last_sync = NOW(),
|
|
last_seen = NOW(),
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
RETURNING id, user_id, device_name, device_type, device_identifier, auth_token, last_sync, last_seen, sync_enabled, auto_sync, sync_frequency_minutes, device_metadata, created_at, updated_at
|
|
`
|
|
|
|
func (q *Queries) UpdateDeviceSyncTimestamp(ctx context.Context, id pgtype.UUID) (Devices, error) {
|
|
row := q.db.QueryRow(ctx, UpdateDeviceSyncTimestamp, id)
|
|
var i Devices
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.DeviceName,
|
|
&i.DeviceType,
|
|
&i.DeviceIdentifier,
|
|
&i.AuthToken,
|
|
&i.LastSync,
|
|
&i.LastSeen,
|
|
&i.SyncEnabled,
|
|
&i.AutoSync,
|
|
&i.SyncFrequencyMinutes,
|
|
&i.DeviceMetadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateEbookNote = `-- name: UpdateEbookNote :one
|
|
UPDATE media_notes SET
|
|
content = $2,
|
|
position = $3,
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
RETURNING id, media_item_id, user_id, content, position, created_at, updated_at, percentage_location, character_start, character_end, epubcfi_location, chapter_reference, paragraph_reference, device_sync_data
|
|
`
|
|
|
|
type UpdateEbookNoteParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Content string `db:"content" json:"content"`
|
|
Position pgtype.Text `db:"position" json:"position"`
|
|
}
|
|
|
|
func (q *Queries) UpdateEbookNote(ctx context.Context, arg UpdateEbookNoteParams) (MediaNotes, error) {
|
|
row := q.db.QueryRow(ctx, UpdateEbookNote, arg.ID, arg.Content, arg.Position)
|
|
var i MediaNotes
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.Content,
|
|
&i.Position,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.PercentageLocation,
|
|
&i.CharacterStart,
|
|
&i.CharacterEnd,
|
|
&i.EpubcfiLocation,
|
|
&i.ChapterReference,
|
|
&i.ParagraphReference,
|
|
&i.DeviceSyncData,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateEmail = `-- name: UpdateEmail :exec
|
|
UPDATE users SET email = $2, updated_at = NOW() WHERE id = $1
|
|
`
|
|
|
|
type UpdateEmailParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Email string `db:"email" json:"email"`
|
|
}
|
|
|
|
func (q *Queries) UpdateEmail(ctx context.Context, arg UpdateEmailParams) error {
|
|
_, err := q.db.Exec(ctx, UpdateEmail, arg.ID, arg.Email)
|
|
return err
|
|
}
|
|
|
|
const UpdateKoboEntitlementRevision = `-- name: UpdateKoboEntitlementRevision :exec
|
|
UPDATE kobo_entitlements
|
|
SET revision_number = $3, updated_at = NOW()
|
|
WHERE device_id = $1 AND entitlement_id = $2
|
|
`
|
|
|
|
type UpdateKoboEntitlementRevisionParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
EntitlementID string `db:"entitlement_id" json:"entitlement_id"`
|
|
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
|
}
|
|
|
|
func (q *Queries) UpdateKoboEntitlementRevision(ctx context.Context, arg UpdateKoboEntitlementRevisionParams) error {
|
|
_, err := q.db.Exec(ctx, UpdateKoboEntitlementRevision, arg.DeviceID, arg.EntitlementID, arg.RevisionNumber)
|
|
return err
|
|
}
|
|
|
|
const UpdateKoboEntitlementStatus = `-- name: UpdateKoboEntitlementStatus :exec
|
|
UPDATE kobo_entitlements
|
|
SET book_status = $3, sync_status = 'synced', updated_at = NOW()
|
|
WHERE device_id = $1 AND entitlement_id = $2
|
|
`
|
|
|
|
type UpdateKoboEntitlementStatusParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
EntitlementID string `db:"entitlement_id" json:"entitlement_id"`
|
|
BookStatus pgtype.Text `db:"book_status" json:"book_status"`
|
|
}
|
|
|
|
func (q *Queries) UpdateKoboEntitlementStatus(ctx context.Context, arg UpdateKoboEntitlementStatusParams) error {
|
|
_, err := q.db.Exec(ctx, UpdateKoboEntitlementStatus, arg.DeviceID, arg.EntitlementID, arg.BookStatus)
|
|
return err
|
|
}
|
|
|
|
const UpdateKoboShelfBookPosition = `-- name: UpdateKoboShelfBookPosition :exec
|
|
UPDATE kobo_shelves
|
|
SET shelf_position = $3, last_synced_at = NOW()
|
|
WHERE device_id = $1 AND media_item_id = $2
|
|
`
|
|
|
|
type UpdateKoboShelfBookPositionParams struct {
|
|
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
ShelfPosition pgtype.Int4 `db:"shelf_position" json:"shelf_position"`
|
|
}
|
|
|
|
func (q *Queries) UpdateKoboShelfBookPosition(ctx context.Context, arg UpdateKoboShelfBookPositionParams) error {
|
|
_, err := q.db.Exec(ctx, UpdateKoboShelfBookPosition, arg.DeviceID, arg.MediaItemID, arg.ShelfPosition)
|
|
return err
|
|
}
|
|
|
|
const UpdateKoboShelfCollection = `-- name: UpdateKoboShelfCollection :one
|
|
UPDATE kobo_shelves
|
|
SET
|
|
collection_id = $2,
|
|
position_in_collection = $3,
|
|
last_synced_at = NOW()
|
|
WHERE id = $1
|
|
RETURNING id, device_id, media_item_id, shelf_name, shelf_position, added_at, last_synced_at, collection_id, position_in_collection
|
|
`
|
|
|
|
type UpdateKoboShelfCollectionParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
|
PositionInCollection pgtype.Int4 `db:"position_in_collection" json:"position_in_collection"`
|
|
}
|
|
|
|
// Update Kobo shelves to support collections
|
|
func (q *Queries) UpdateKoboShelfCollection(ctx context.Context, arg UpdateKoboShelfCollectionParams) (KoboShelves, error) {
|
|
row := q.db.QueryRow(ctx, UpdateKoboShelfCollection, arg.ID, arg.CollectionID, arg.PositionInCollection)
|
|
var i KoboShelves
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.ShelfName,
|
|
&i.ShelfPosition,
|
|
&i.AddedAt,
|
|
&i.LastSyncedAt,
|
|
&i.CollectionID,
|
|
&i.PositionInCollection,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateLibrary = `-- name: UpdateLibrary :one
|
|
UPDATE libraries SET
|
|
name = $2,
|
|
description = $3,
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
RETURNING id, name, description, library_type_id, created_by_admin_id, created_at, updated_at
|
|
`
|
|
|
|
type UpdateLibraryParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Name string `db:"name" json:"name"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
}
|
|
|
|
func (q *Queries) UpdateLibrary(ctx context.Context, arg UpdateLibraryParams) (Libraries, error) {
|
|
row := q.db.QueryRow(ctx, UpdateLibrary, arg.ID, arg.Name, arg.Description)
|
|
var i Libraries
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.LibraryTypeID,
|
|
&i.CreatedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateMediaHighlight = `-- name: UpdateMediaHighlight :one
|
|
UPDATE media_highlights SET
|
|
selection_text = $2,
|
|
start_position = $3,
|
|
end_position = $4,
|
|
color = $5,
|
|
note_id = $6,
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
RETURNING id, media_item_id, user_id, selection_text, start_position, end_position, color, note_id, created_at, updated_at, percentage_start, percentage_end, character_start, character_end, epubcfi_start, epubcfi_end, chapter_reference, paragraph_start, paragraph_end, panel_number, device_sync_data
|
|
`
|
|
|
|
type UpdateMediaHighlightParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
SelectionText string `db:"selection_text" json:"selection_text"`
|
|
StartPosition pgtype.Text `db:"start_position" json:"start_position"`
|
|
EndPosition pgtype.Text `db:"end_position" json:"end_position"`
|
|
Color pgtype.Text `db:"color" json:"color"`
|
|
NoteID pgtype.UUID `db:"note_id" json:"note_id"`
|
|
}
|
|
|
|
func (q *Queries) UpdateMediaHighlight(ctx context.Context, arg UpdateMediaHighlightParams) (MediaHighlights, error) {
|
|
row := q.db.QueryRow(ctx, UpdateMediaHighlight,
|
|
arg.ID,
|
|
arg.SelectionText,
|
|
arg.StartPosition,
|
|
arg.EndPosition,
|
|
arg.Color,
|
|
arg.NoteID,
|
|
)
|
|
var i MediaHighlights
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.SelectionText,
|
|
&i.StartPosition,
|
|
&i.EndPosition,
|
|
&i.Color,
|
|
&i.NoteID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.PercentageStart,
|
|
&i.PercentageEnd,
|
|
&i.CharacterStart,
|
|
&i.CharacterEnd,
|
|
&i.EpubcfiStart,
|
|
&i.EpubcfiEnd,
|
|
&i.ChapterReference,
|
|
&i.ParagraphStart,
|
|
&i.ParagraphEnd,
|
|
&i.PanelNumber,
|
|
&i.DeviceSyncData,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateMediaItem = `-- name: UpdateMediaItem :one
|
|
UPDATE media_items SET
|
|
title = $2,
|
|
author = $3,
|
|
isbn = $4,
|
|
description = $5,
|
|
cover_image_path = $6,
|
|
series = $7,
|
|
series_number = $8,
|
|
tags = $9,
|
|
asin = $10,
|
|
date_published = $11,
|
|
publisher = $12,
|
|
contributors = $13,
|
|
language = $14,
|
|
edition = $15,
|
|
page_count = $16,
|
|
genre = $17,
|
|
copyright_year = $18,
|
|
goodreads_id = $19,
|
|
openlibrary_id = $20,
|
|
google_books_id = $21,
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, file_sha256, opf_identifier, opf_uuid, hash_confidence
|
|
`
|
|
|
|
type UpdateMediaItemParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
Isbn pgtype.Text `db:"isbn" json:"isbn"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"`
|
|
Series pgtype.Text `db:"series" json:"series"`
|
|
SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"`
|
|
Tags pgtype.Text `db:"tags" json:"tags"`
|
|
Asin pgtype.Text `db:"asin" json:"asin"`
|
|
DatePublished pgtype.Date `db:"date_published" json:"date_published"`
|
|
Publisher pgtype.Text `db:"publisher" json:"publisher"`
|
|
Contributors pgtype.Text `db:"contributors" json:"contributors"`
|
|
Language pgtype.Text `db:"language" json:"language"`
|
|
Edition pgtype.Text `db:"edition" json:"edition"`
|
|
PageCount pgtype.Int4 `db:"page_count" json:"page_count"`
|
|
Genre pgtype.Text `db:"genre" json:"genre"`
|
|
CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"`
|
|
GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"`
|
|
OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"`
|
|
GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"`
|
|
}
|
|
|
|
func (q *Queries) UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams) (MediaItems, error) {
|
|
row := q.db.QueryRow(ctx, UpdateMediaItem,
|
|
arg.ID,
|
|
arg.Title,
|
|
arg.Author,
|
|
arg.Isbn,
|
|
arg.Description,
|
|
arg.CoverImagePath,
|
|
arg.Series,
|
|
arg.SeriesNumber,
|
|
arg.Tags,
|
|
arg.Asin,
|
|
arg.DatePublished,
|
|
arg.Publisher,
|
|
arg.Contributors,
|
|
arg.Language,
|
|
arg.Edition,
|
|
arg.PageCount,
|
|
arg.Genre,
|
|
arg.CopyrightYear,
|
|
arg.GoodreadsID,
|
|
arg.OpenlibraryID,
|
|
arg.GoogleBooksID,
|
|
)
|
|
var i MediaItems
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateMediaItemFormat = `-- name: UpdateMediaItemFormat :one
|
|
UPDATE media_item_formats
|
|
SET
|
|
file_path = $2,
|
|
file_sha256 = $3,
|
|
file_size_bytes = $4,
|
|
mime_type = $5
|
|
WHERE id = $1
|
|
RETURNING id, media_item_id, format_type, file_path, file_sha256, file_size_bytes, mime_type, created_at, converted_from_format_id
|
|
`
|
|
|
|
type UpdateMediaItemFormatParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
FilePath pgtype.Text `db:"file_path" json:"file_path"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
FileSizeBytes pgtype.Int8 `db:"file_size_bytes" json:"file_size_bytes"`
|
|
MimeType pgtype.Text `db:"mime_type" json:"mime_type"`
|
|
}
|
|
|
|
// Update media item format
|
|
func (q *Queries) UpdateMediaItemFormat(ctx context.Context, arg UpdateMediaItemFormatParams) (MediaItemFormats, error) {
|
|
row := q.db.QueryRow(ctx, UpdateMediaItemFormat,
|
|
arg.ID,
|
|
arg.FilePath,
|
|
arg.FileSha256,
|
|
arg.FileSizeBytes,
|
|
arg.MimeType,
|
|
)
|
|
var i MediaItemFormats
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.FormatType,
|
|
&i.FilePath,
|
|
&i.FileSha256,
|
|
&i.FileSizeBytes,
|
|
&i.MimeType,
|
|
&i.CreatedAt,
|
|
&i.ConvertedFromFormatID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateMediaItemFormatGroup = `-- name: UpdateMediaItemFormatGroup :exec
|
|
|
|
UPDATE media_items
|
|
SET
|
|
format_group = $2,
|
|
format_mimetype = $3,
|
|
is_reflowable = $4,
|
|
has_fixed_layout = $5,
|
|
total_characters = $6,
|
|
chapter_count = $7,
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
`
|
|
|
|
type UpdateMediaItemFormatGroupParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
FormatGroup string `db:"format_group" json:"format_group"`
|
|
FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"`
|
|
IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"`
|
|
HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"`
|
|
TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"`
|
|
ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"`
|
|
}
|
|
|
|
// ============================================
|
|
// PHASE 1: FORMAT DETECTION & PROGRESS (Week 2)
|
|
// ============================================
|
|
// Update media item format group information
|
|
func (q *Queries) UpdateMediaItemFormatGroup(ctx context.Context, arg UpdateMediaItemFormatGroupParams) error {
|
|
_, err := q.db.Exec(ctx, UpdateMediaItemFormatGroup,
|
|
arg.ID,
|
|
arg.FormatGroup,
|
|
arg.FormatMimetype,
|
|
arg.IsReflowable,
|
|
arg.HasFixedLayout,
|
|
arg.TotalCharacters,
|
|
arg.ChapterCount,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const UpdateMediaItemIdentifiers = `-- name: UpdateMediaItemIdentifiers :one
|
|
|
|
|
|
UPDATE media_items
|
|
SET
|
|
file_sha256 = $2,
|
|
opf_identifier = $3,
|
|
opf_uuid = $4,
|
|
hash_confidence = $5,
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, file_sha256, opf_identifier, opf_uuid, hash_confidence
|
|
`
|
|
|
|
type UpdateMediaItemIdentifiersParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"`
|
|
OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"`
|
|
HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"`
|
|
}
|
|
|
|
// Media Items Admin Operations
|
|
// ============================================
|
|
// PHASE 1: UNIVERSAL BOOK IDENTIFIERS (Week 1)
|
|
// ============================================
|
|
// Update media item with universal identifiers
|
|
func (q *Queries) UpdateMediaItemIdentifiers(ctx context.Context, arg UpdateMediaItemIdentifiersParams) (MediaItems, error) {
|
|
row := q.db.QueryRow(ctx, UpdateMediaItemIdentifiers,
|
|
arg.ID,
|
|
arg.FileSha256,
|
|
arg.OpfIdentifier,
|
|
arg.OpfUuid,
|
|
arg.HashConfidence,
|
|
)
|
|
var i MediaItems
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateMediaItemKoboMetadata = `-- name: UpdateMediaItemKoboMetadata :one
|
|
UPDATE media_items
|
|
SET entitlement_id = $2,
|
|
kobo_content_id = $3,
|
|
revision_number = COALESCE($4, revision_number) + 1,
|
|
kobo_metadata = $5,
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, file_sha256, opf_identifier, opf_uuid, hash_confidence
|
|
`
|
|
|
|
type UpdateMediaItemKoboMetadataParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"`
|
|
KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"`
|
|
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
|
KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"`
|
|
}
|
|
|
|
func (q *Queries) UpdateMediaItemKoboMetadata(ctx context.Context, arg UpdateMediaItemKoboMetadataParams) (MediaItems, error) {
|
|
row := q.db.QueryRow(ctx, UpdateMediaItemKoboMetadata,
|
|
arg.ID,
|
|
arg.EntitlementID,
|
|
arg.KoboContentID,
|
|
arg.RevisionNumber,
|
|
arg.KoboMetadata,
|
|
)
|
|
var i MediaItems
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.Isbn,
|
|
&i.Description,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.MimeType,
|
|
&i.CoverImagePath,
|
|
&i.Series,
|
|
&i.SeriesNumber,
|
|
&i.Tags,
|
|
&i.Asin,
|
|
&i.DatePublished,
|
|
&i.Publisher,
|
|
&i.Contributors,
|
|
&i.Language,
|
|
&i.Edition,
|
|
&i.PageCount,
|
|
&i.Genre,
|
|
&i.CopyrightYear,
|
|
&i.GoodreadsID,
|
|
&i.OpenlibraryID,
|
|
&i.GoogleBooksID,
|
|
&i.AddedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateMediaNote = `-- name: UpdateMediaNote :one
|
|
UPDATE media_notes SET
|
|
content = $2,
|
|
position = $3,
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
RETURNING id, media_item_id, user_id, content, position, created_at, updated_at, percentage_location, character_start, character_end, epubcfi_location, chapter_reference, paragraph_reference, device_sync_data
|
|
`
|
|
|
|
type UpdateMediaNoteParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Content string `db:"content" json:"content"`
|
|
Position pgtype.Text `db:"position" json:"position"`
|
|
}
|
|
|
|
func (q *Queries) UpdateMediaNote(ctx context.Context, arg UpdateMediaNoteParams) (MediaNotes, error) {
|
|
row := q.db.QueryRow(ctx, UpdateMediaNote, arg.ID, arg.Content, arg.Position)
|
|
var i MediaNotes
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.Content,
|
|
&i.Position,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.PercentageLocation,
|
|
&i.CharacterStart,
|
|
&i.CharacterEnd,
|
|
&i.EpubcfiLocation,
|
|
&i.ChapterReference,
|
|
&i.ParagraphReference,
|
|
&i.DeviceSyncData,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateMediaRating = `-- name: UpdateMediaRating :one
|
|
UPDATE media_ratings SET
|
|
rating = $3,
|
|
updated_at = NOW()
|
|
WHERE media_item_id = $1 AND user_id = $2
|
|
RETURNING id, media_item_id, user_id, rating, created_at, updated_at
|
|
`
|
|
|
|
type UpdateMediaRatingParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Rating int32 `db:"rating" json:"rating"`
|
|
}
|
|
|
|
func (q *Queries) UpdateMediaRating(ctx context.Context, arg UpdateMediaRatingParams) (MediaRatings, error) {
|
|
row := q.db.QueryRow(ctx, UpdateMediaRating, arg.MediaItemID, arg.UserID, arg.Rating)
|
|
var i MediaRatings
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.Rating,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdatePassword = `-- name: UpdatePassword :exec
|
|
UPDATE users SET password_hash = $2, updated_at = NOW() WHERE id = $1
|
|
`
|
|
|
|
type UpdatePasswordParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
PasswordHash string `db:"password_hash" json:"password_hash"`
|
|
}
|
|
|
|
func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error {
|
|
_, err := q.db.Exec(ctx, UpdatePassword, arg.ID, arg.PasswordHash)
|
|
return err
|
|
}
|
|
|
|
const UpdateReadingProgress = `-- name: UpdateReadingProgress :one
|
|
INSERT INTO reading_progress (media_item_id, user_id, current_page, total_pages, last_read_at)
|
|
VALUES ($1, $2, $3, $4, NOW())
|
|
ON CONFLICT (media_item_id, user_id)
|
|
DO UPDATE SET
|
|
current_page = EXCLUDED.current_page,
|
|
total_pages = EXCLUDED.total_pages,
|
|
last_read_at = NOW()
|
|
RETURNING id, media_item_id, user_id, current_page, total_pages, last_read_at, percentage, character_offset, epubcfi, chapter, chapter_progress, viewport_x, viewport_y, zoom_level, scroll_position_x, scroll_position_y, panel_number, reading_mode, last_sync_device, last_sync_source, last_sync_timestamp, conflict_detected, conflict_resolved
|
|
`
|
|
|
|
type UpdateReadingProgressParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
CurrentPage pgtype.Int4 `db:"current_page" json:"current_page"`
|
|
TotalPages pgtype.Int4 `db:"total_pages" json:"total_pages"`
|
|
}
|
|
|
|
func (q *Queries) UpdateReadingProgress(ctx context.Context, arg UpdateReadingProgressParams) (ReadingProgress, error) {
|
|
row := q.db.QueryRow(ctx, UpdateReadingProgress,
|
|
arg.MediaItemID,
|
|
arg.UserID,
|
|
arg.CurrentPage,
|
|
arg.TotalPages,
|
|
)
|
|
var i ReadingProgress
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.CurrentPage,
|
|
&i.TotalPages,
|
|
&i.LastReadAt,
|
|
&i.Percentage,
|
|
&i.CharacterOffset,
|
|
&i.Epubcfi,
|
|
&i.Chapter,
|
|
&i.ChapterProgress,
|
|
&i.ViewportX,
|
|
&i.ViewportY,
|
|
&i.ZoomLevel,
|
|
&i.ScrollPositionX,
|
|
&i.ScrollPositionY,
|
|
&i.PanelNumber,
|
|
&i.ReadingMode,
|
|
&i.LastSyncDevice,
|
|
&i.LastSyncSource,
|
|
&i.LastSyncTimestamp,
|
|
&i.ConflictDetected,
|
|
&i.ConflictResolved,
|
|
)
|
|
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
|
|
status = $2,
|
|
attempts = attempts + 1,
|
|
error_message = $3,
|
|
processed_at = CASE WHEN $2 = 'completed' THEN NOW() ELSE NULL END
|
|
WHERE id = $1
|
|
RETURNING id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at
|
|
`
|
|
|
|
type UpdateSyncQueueItemStatusParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Status pgtype.Text `db:"status" json:"status"`
|
|
ErrorMessage pgtype.Text `db:"error_message" json:"error_message"`
|
|
}
|
|
|
|
func (q *Queries) UpdateSyncQueueItemStatus(ctx context.Context, arg UpdateSyncQueueItemStatusParams) (SyncQueue, error) {
|
|
row := q.db.QueryRow(ctx, UpdateSyncQueueItemStatus, arg.ID, arg.Status, arg.ErrorMessage)
|
|
var i SyncQueue
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.SyncType,
|
|
&i.SyncData,
|
|
&i.Priority,
|
|
&i.Attempts,
|
|
&i.MaxAttempts,
|
|
&i.Status,
|
|
&i.ErrorMessage,
|
|
&i.CreatedAt,
|
|
&i.ProcessedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateUniversalProgress = `-- name: UpdateUniversalProgress :one
|
|
INSERT INTO reading_progress (
|
|
media_item_id,
|
|
user_id,
|
|
percentage,
|
|
character_offset,
|
|
epubcfi,
|
|
chapter,
|
|
chapter_progress,
|
|
viewport_x,
|
|
viewport_y,
|
|
zoom_level,
|
|
scroll_position_x,
|
|
scroll_position_y,
|
|
panel_number,
|
|
reading_mode,
|
|
last_sync_device,
|
|
last_sync_source,
|
|
last_sync_timestamp,
|
|
current_page,
|
|
total_pages,
|
|
last_read_at
|
|
)
|
|
VALUES (
|
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, NOW(), $17, $18, NOW()
|
|
)
|
|
ON CONFLICT (media_item_id, user_id)
|
|
DO UPDATE SET
|
|
percentage = EXCLUDED.percentage,
|
|
character_offset = EXCLUDED.character_offset,
|
|
epubcfi = EXCLUDED.epubcfi,
|
|
chapter = EXCLUDED.chapter,
|
|
chapter_progress = EXCLUDED.chapter_progress,
|
|
viewport_x = EXCLUDED.viewport_x,
|
|
viewport_y = EXCLUDED.viewport_y,
|
|
zoom_level = EXCLUDED.zoom_level,
|
|
scroll_position_x = EXCLUDED.scroll_position_x,
|
|
scroll_position_y = EXCLUDED.scroll_position_y,
|
|
panel_number = EXCLUDED.panel_number,
|
|
reading_mode = EXCLUDED.reading_mode,
|
|
last_sync_device = EXCLUDED.last_sync_device,
|
|
last_sync_source = EXCLUDED.last_sync_source,
|
|
last_sync_timestamp = EXCLUDED.last_sync_timestamp,
|
|
current_page = EXCLUDED.current_page,
|
|
total_pages = EXCLUDED.total_pages,
|
|
last_read_at = NOW()
|
|
RETURNING id, media_item_id, user_id, current_page, total_pages, last_read_at, percentage, character_offset, epubcfi, chapter, chapter_progress, viewport_x, viewport_y, zoom_level, scroll_position_x, scroll_position_y, panel_number, reading_mode, last_sync_device, last_sync_source, last_sync_timestamp, conflict_detected, conflict_resolved
|
|
`
|
|
|
|
type UpdateUniversalProgressParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Percentage pgtype.Float8 `db:"percentage" json:"percentage"`
|
|
CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"`
|
|
Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"`
|
|
Chapter pgtype.Int4 `db:"chapter" json:"chapter"`
|
|
ChapterProgress pgtype.Float8 `db:"chapter_progress" json:"chapter_progress"`
|
|
ViewportX pgtype.Float8 `db:"viewport_x" json:"viewport_x"`
|
|
ViewportY pgtype.Float8 `db:"viewport_y" json:"viewport_y"`
|
|
ZoomLevel pgtype.Float8 `db:"zoom_level" json:"zoom_level"`
|
|
ScrollPositionX pgtype.Float8 `db:"scroll_position_x" json:"scroll_position_x"`
|
|
ScrollPositionY pgtype.Float8 `db:"scroll_position_y" json:"scroll_position_y"`
|
|
PanelNumber pgtype.Int4 `db:"panel_number" json:"panel_number"`
|
|
ReadingMode pgtype.Text `db:"reading_mode" json:"reading_mode"`
|
|
LastSyncDevice pgtype.Text `db:"last_sync_device" json:"last_sync_device"`
|
|
LastSyncSource pgtype.Text `db:"last_sync_source" json:"last_sync_source"`
|
|
CurrentPage pgtype.Int4 `db:"current_page" json:"current_page"`
|
|
TotalPages pgtype.Int4 `db:"total_pages" json:"total_pages"`
|
|
}
|
|
|
|
// Update universal progress
|
|
func (q *Queries) UpdateUniversalProgress(ctx context.Context, arg UpdateUniversalProgressParams) (ReadingProgress, error) {
|
|
row := q.db.QueryRow(ctx, UpdateUniversalProgress,
|
|
arg.MediaItemID,
|
|
arg.UserID,
|
|
arg.Percentage,
|
|
arg.CharacterOffset,
|
|
arg.Epubcfi,
|
|
arg.Chapter,
|
|
arg.ChapterProgress,
|
|
arg.ViewportX,
|
|
arg.ViewportY,
|
|
arg.ZoomLevel,
|
|
arg.ScrollPositionX,
|
|
arg.ScrollPositionY,
|
|
arg.PanelNumber,
|
|
arg.ReadingMode,
|
|
arg.LastSyncDevice,
|
|
arg.LastSyncSource,
|
|
arg.CurrentPage,
|
|
arg.TotalPages,
|
|
)
|
|
var i ReadingProgress
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.CurrentPage,
|
|
&i.TotalPages,
|
|
&i.LastReadAt,
|
|
&i.Percentage,
|
|
&i.CharacterOffset,
|
|
&i.Epubcfi,
|
|
&i.Chapter,
|
|
&i.ChapterProgress,
|
|
&i.ViewportX,
|
|
&i.ViewportY,
|
|
&i.ZoomLevel,
|
|
&i.ScrollPositionX,
|
|
&i.ScrollPositionY,
|
|
&i.PanelNumber,
|
|
&i.ReadingMode,
|
|
&i.LastSyncDevice,
|
|
&i.LastSyncSource,
|
|
&i.LastSyncTimestamp,
|
|
&i.ConflictDetected,
|
|
&i.ConflictResolved,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateUserMaxDevices = `-- name: UpdateUserMaxDevices :exec
|
|
UPDATE users SET max_devices = $2, updated_at = NOW() WHERE id = $1
|
|
`
|
|
|
|
type UpdateUserMaxDevicesParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
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
|
|
}
|
|
|
|
const UpdateUserProfile = `-- name: UpdateUserProfile :exec
|
|
UPDATE users SET first_name = $2, last_name = $3, updated_at = NOW() WHERE id = $1
|
|
`
|
|
|
|
type UpdateUserProfileParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
FirstName pgtype.Text `db:"first_name" json:"first_name"`
|
|
LastName pgtype.Text `db:"last_name" json:"last_name"`
|
|
}
|
|
|
|
func (q *Queries) UpdateUserProfile(ctx context.Context, arg UpdateUserProfileParams) error {
|
|
_, err := q.db.Exec(ctx, UpdateUserProfile, arg.ID, arg.FirstName, arg.LastName)
|
|
return err
|
|
}
|
|
|
|
const UpdateUserTheme = `-- name: UpdateUserTheme :exec
|
|
UPDATE users SET theme = $2, updated_at = NOW() WHERE id = $1
|
|
`
|
|
|
|
type UpdateUserThemeParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Theme pgtype.Text `db:"theme" json:"theme"`
|
|
}
|
|
|
|
func (q *Queries) UpdateUserTheme(ctx context.Context, arg UpdateUserThemeParams) error {
|
|
_, err := q.db.Exec(ctx, UpdateUserTheme, arg.ID, arg.Theme)
|
|
return err
|
|
}
|
|
|
|
const UpdateUsername = `-- name: UpdateUsername :exec
|
|
UPDATE users SET username = $2, updated_at = NOW() WHERE id = $1
|
|
`
|
|
|
|
type UpdateUsernameParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Username string `db:"username" json:"username"`
|
|
}
|
|
|
|
func (q *Queries) UpdateUsername(ctx context.Context, arg UpdateUsernameParams) error {
|
|
_, err := q.db.Exec(ctx, UpdateUsername, arg.ID, arg.Username)
|
|
return err
|
|
}
|