Content duplicates (same library + file_sha256 at different paths, e.g. the same book imported twice under two names on a preexisting database) cannot be auto-collapsed the way path duplicates were: keeping both copies may be intentional. Surface them for an explicit admin decision instead. Schema: - new hash_conflicts table keyed (library_id, file_sha256) with a status/resolution lifecycle: 'pending' until an admin resolves via 'keep_all' or 'kept:<uuid>' (which copy was kept after merging) - resolution is VARCHAR(50) - 'kept:<uuid>' is 41 chars; include a widening ALTER for databases created with the initial 30-char width - resolution/resolved_by/resolved_at record who decided what and when Queries: - ListMediaItemsMissingHash: items imported before hashing existed (file_sha256 IS NULL), ordered oldest-first for the backfill pass - FindHashConflictGroups: the content-duplicate group detection (GROUP BY library_id, file_sha256 HAVING COUNT(*) > 1) - ListMediaItemsBySHA256AndLibrary: full membership of one group - CreateHashConflict: upsert with DO NOTHING so already-tracked groups are untouched - critical behavior: a group an admin resolved as 'keep both' is never re-flagged by later sweeps - ListPendingHashConflicts: admin listing with library name and live item counts (items may have been deleted since flagging) - GetHashConflict / ResolveHashConflict: lifecycle - GetMediaItemUsageCounts: per-item progress/highlight/bookmark/note/ collection counts so the admin can make an informed keep choice - ReparentMediaItemChildren: sqlc binding for the existing reparent_media_item_children() migration function, used to merge a losing copy's child rows into the kept copy
12846 lines
418 KiB
Go
12846 lines
418 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// 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, excluded
|
|
`
|
|
|
|
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,
|
|
&i.Excluded,
|
|
)
|
|
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
|
|
// ============================================
|
|
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 CheckForProgressConflicts = `-- name: CheckForProgressConflicts :one
|
|
SELECT COUNT(*) as conflict_count
|
|
FROM reading_progress
|
|
WHERE media_item_id = $1
|
|
AND user_id = $2
|
|
AND last_sync_timestamp > NOW() - INTERVAL '5 minutes'
|
|
AND last_sync_source != $3
|
|
`
|
|
|
|
type CheckForProgressConflictsParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
LastSyncSource pgtype.Text `db:"last_sync_source" json:"last_sync_source"`
|
|
}
|
|
|
|
func (q *Queries) CheckForProgressConflicts(ctx context.Context, arg CheckForProgressConflictsParams) (int64, error) {
|
|
row := q.db.QueryRow(ctx, CheckForProgressConflicts, arg.MediaItemID, arg.UserID, arg.LastSyncSource)
|
|
var conflict_count int64
|
|
err := row.Scan(&conflict_count)
|
|
return conflict_count, err
|
|
}
|
|
|
|
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() - make_interval(secs => $1::double precision))
|
|
`
|
|
|
|
func (q *Queries) CleanupExpiredRefreshTokens(ctx context.Context, dollar_1 float64) error {
|
|
_, err := q.db.Exec(ctx, CleanupExpiredRefreshTokens, dollar_1)
|
|
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 CountAdmins = `-- name: CountAdmins :one
|
|
SELECT COUNT(*) FROM users WHERE role = 'admin'
|
|
`
|
|
|
|
func (q *Queries) CountAdmins(ctx context.Context) (int64, error) {
|
|
row := q.db.QueryRow(ctx, CountAdmins)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, 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 CreateAutoResolvedSyncConflict = `-- name: CreateAutoResolvedSyncConflict :one
|
|
INSERT INTO sync_conflicts (media_item_id, user_id, conflict_type, conflict_data, resolution_status, resolution_data, resolved_at)
|
|
VALUES ($1, $2, $3, $4, 'auto_resolved', $5, NOW())
|
|
RETURNING id, media_item_id, user_id, conflict_type, conflict_data, resolution_status, resolution_data, resolved_by, resolved_at, created_at
|
|
`
|
|
|
|
type CreateAutoResolvedSyncConflictParams 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"`
|
|
ResolutionData []byte `db:"resolution_data" json:"resolution_data"`
|
|
}
|
|
|
|
func (q *Queries) CreateAutoResolvedSyncConflict(ctx context.Context, arg CreateAutoResolvedSyncConflictParams) (SyncConflicts, error) {
|
|
row := q.db.QueryRow(ctx, CreateAutoResolvedSyncConflict,
|
|
arg.MediaItemID,
|
|
arg.UserID,
|
|
arg.ConflictType,
|
|
arg.ConflictData,
|
|
arg.ResolutionData,
|
|
)
|
|
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 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, show_on_dashboard, query_type, priority, is_system_collection, 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.ShowOnDashboard,
|
|
&i.QueryType,
|
|
&i.Priority,
|
|
&i.IsSystemCollection,
|
|
&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"`
|
|
}
|
|
|
|
// ============================================
|
|
// DEVICE MANAGEMENT & AUTH
|
|
// ============================================
|
|
// 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"`
|
|
BookhoardUuid 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.BookhoardUuid,
|
|
arg.KoboContentID,
|
|
arg.ContentIDType,
|
|
arg.Available,
|
|
arg.DeliveryDate,
|
|
arg.DeliveryMethod,
|
|
)
|
|
var i DeviceCatalogs
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.BookhoardUuid,
|
|
&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 CreateDictionaryEntry = `-- name: CreateDictionaryEntry :one
|
|
INSERT INTO dictionary_cache (word, definition, part_of_speech, example, etymology, accessed_at)
|
|
VALUES ($1, $2, $3, $4, $5, NOW())
|
|
RETURNING id, word, definition, part_of_speech, example, etymology, created_at, accessed_at
|
|
`
|
|
|
|
type CreateDictionaryEntryParams struct {
|
|
Word string `db:"word" json:"word"`
|
|
Definition string `db:"definition" json:"definition"`
|
|
PartOfSpeech pgtype.Text `db:"part_of_speech" json:"part_of_speech"`
|
|
Example pgtype.Text `db:"example" json:"example"`
|
|
Etymology pgtype.Text `db:"etymology" json:"etymology"`
|
|
}
|
|
|
|
func (q *Queries) CreateDictionaryEntry(ctx context.Context, arg CreateDictionaryEntryParams) (DictionaryCache, error) {
|
|
row := q.db.QueryRow(ctx, CreateDictionaryEntry,
|
|
arg.Word,
|
|
arg.Definition,
|
|
arg.PartOfSpeech,
|
|
arg.Example,
|
|
arg.Etymology,
|
|
)
|
|
var i DictionaryCache
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Word,
|
|
&i.Definition,
|
|
&i.PartOfSpeech,
|
|
&i.Example,
|
|
&i.Etymology,
|
|
&i.CreatedAt,
|
|
&i.AccessedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateHashConflict = `-- name: CreateHashConflict :exec
|
|
|
|
INSERT INTO hash_conflicts (library_id, file_sha256)
|
|
VALUES ($1, $2)
|
|
ON CONFLICT (library_id, file_sha256) DO NOTHING
|
|
`
|
|
|
|
type CreateHashConflictParams struct {
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
FileSha256 string `db:"file_sha256" json:"file_sha256"`
|
|
}
|
|
|
|
// HASH CONFLICTS QUERIES
|
|
// Record a pending hash conflict (no-op if the group is already tracked, so
|
|
// resolved groups stay resolved and are never re-flagged)
|
|
func (q *Queries) CreateHashConflict(ctx context.Context, arg CreateHashConflictParams) error {
|
|
_, err := q.db.Exec(ctx, CreateHashConflict, arg.LibraryID, arg.FileSha256)
|
|
return 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 CreateMediaBookmark = `-- name: CreateMediaBookmark :one
|
|
INSERT INTO media_bookmarks (media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
RETURNING id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at, dedup_key, last_modified_at, last_modified_source, device_sync_data, percentage_location, epubcfi_location, chapter_reference, deleted, deleted_at
|
|
`
|
|
|
|
type CreateMediaBookmarkParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
PageNumber pgtype.Int4 `db:"page_number" json:"page_number"`
|
|
ChapterNumber pgtype.Int4 `db:"chapter_number" json:"chapter_number"`
|
|
CfiPosition pgtype.Text `db:"cfi_position" json:"cfi_position"`
|
|
Title string `db:"title" json:"title"`
|
|
Position pgtype.Text `db:"position" json:"position"`
|
|
Notes pgtype.Text `db:"notes" json:"notes"`
|
|
}
|
|
|
|
func (q *Queries) CreateMediaBookmark(ctx context.Context, arg CreateMediaBookmarkParams) (MediaBookmarks, error) {
|
|
row := q.db.QueryRow(ctx, CreateMediaBookmark,
|
|
arg.MediaItemID,
|
|
arg.UserID,
|
|
arg.PageNumber,
|
|
arg.ChapterNumber,
|
|
arg.CfiPosition,
|
|
arg.Title,
|
|
arg.Position,
|
|
arg.Notes,
|
|
)
|
|
var i MediaBookmarks
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.PageNumber,
|
|
&i.ChapterNumber,
|
|
&i.CfiPosition,
|
|
&i.Title,
|
|
&i.Position,
|
|
&i.Notes,
|
|
&i.CreatedAt,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.DeviceSyncData,
|
|
&i.PercentageLocation,
|
|
&i.EpubcfiLocation,
|
|
&i.ChapterReference,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateMediaBookmarkFull = `-- name: CreateMediaBookmarkFull :one
|
|
INSERT INTO media_bookmarks (
|
|
media_item_id, user_id, page_number, chapter_number,
|
|
cfi_position, title, position, notes,
|
|
percentage_location, epubcfi_location, chapter_reference,
|
|
dedup_key, last_modified_at, last_modified_source,
|
|
device_sync_data
|
|
) VALUES (
|
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15
|
|
) RETURNING id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at, dedup_key, last_modified_at, last_modified_source, device_sync_data, percentage_location, epubcfi_location, chapter_reference, deleted, deleted_at
|
|
`
|
|
|
|
type CreateMediaBookmarkFullParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
PageNumber pgtype.Int4 `db:"page_number" json:"page_number"`
|
|
ChapterNumber pgtype.Int4 `db:"chapter_number" json:"chapter_number"`
|
|
CfiPosition pgtype.Text `db:"cfi_position" json:"cfi_position"`
|
|
Title string `db:"title" json:"title"`
|
|
Position pgtype.Text `db:"position" json:"position"`
|
|
Notes pgtype.Text `db:"notes" json:"notes"`
|
|
PercentageLocation pgtype.Float8 `db:"percentage_location" json:"percentage_location"`
|
|
EpubcfiLocation pgtype.Text `db:"epubcfi_location" json:"epubcfi_location"`
|
|
ChapterReference pgtype.Int4 `db:"chapter_reference" json:"chapter_reference"`
|
|
DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"`
|
|
LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"`
|
|
LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"`
|
|
DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"`
|
|
}
|
|
|
|
func (q *Queries) CreateMediaBookmarkFull(ctx context.Context, arg CreateMediaBookmarkFullParams) (MediaBookmarks, error) {
|
|
row := q.db.QueryRow(ctx, CreateMediaBookmarkFull,
|
|
arg.MediaItemID,
|
|
arg.UserID,
|
|
arg.PageNumber,
|
|
arg.ChapterNumber,
|
|
arg.CfiPosition,
|
|
arg.Title,
|
|
arg.Position,
|
|
arg.Notes,
|
|
arg.PercentageLocation,
|
|
arg.EpubcfiLocation,
|
|
arg.ChapterReference,
|
|
arg.DedupKey,
|
|
arg.LastModifiedAt,
|
|
arg.LastModifiedSource,
|
|
arg.DeviceSyncData,
|
|
)
|
|
var i MediaBookmarks
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.PageNumber,
|
|
&i.ChapterNumber,
|
|
&i.CfiPosition,
|
|
&i.Title,
|
|
&i.Position,
|
|
&i.Notes,
|
|
&i.CreatedAt,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.DeviceSyncData,
|
|
&i.PercentageLocation,
|
|
&i.EpubcfiLocation,
|
|
&i.ChapterReference,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
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, dedup_key, last_modified_at, last_modified_source, note_text, deleted, deleted_at
|
|
`
|
|
|
|
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,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.NoteText,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateMediaHighlightFull = `-- name: CreateMediaHighlightFull :one
|
|
INSERT INTO media_highlights (
|
|
media_item_id, user_id, selection_text,
|
|
start_position, end_position, color, note_text,
|
|
percentage_start, percentage_end,
|
|
epubcfi_start, epubcfi_end,
|
|
chapter_reference,
|
|
dedup_key, last_modified_at, last_modified_source,
|
|
device_sync_data
|
|
) VALUES (
|
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16
|
|
) 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, dedup_key, last_modified_at, last_modified_source, note_text, deleted, deleted_at
|
|
`
|
|
|
|
type CreateMediaHighlightFullParams 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"`
|
|
NoteText pgtype.Text `db:"note_text" json:"note_text"`
|
|
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"`
|
|
ChapterReference pgtype.Int4 `db:"chapter_reference" json:"chapter_reference"`
|
|
DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"`
|
|
LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"`
|
|
LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"`
|
|
DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"`
|
|
}
|
|
|
|
func (q *Queries) CreateMediaHighlightFull(ctx context.Context, arg CreateMediaHighlightFullParams) (MediaHighlights, error) {
|
|
row := q.db.QueryRow(ctx, CreateMediaHighlightFull,
|
|
arg.MediaItemID,
|
|
arg.UserID,
|
|
arg.SelectionText,
|
|
arg.StartPosition,
|
|
arg.EndPosition,
|
|
arg.Color,
|
|
arg.NoteText,
|
|
arg.PercentageStart,
|
|
arg.PercentageEnd,
|
|
arg.EpubcfiStart,
|
|
arg.EpubcfiEnd,
|
|
arg.ChapterReference,
|
|
arg.DedupKey,
|
|
arg.LastModifiedAt,
|
|
arg.LastModifiedSource,
|
|
arg.DeviceSyncData,
|
|
)
|
|
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,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.NoteText,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
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, tags_search, asin, date_published, publisher, contributors, contributors_search, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, library_type_name)
|
|
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, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44)
|
|
ON CONFLICT (library_id, file_path) DO UPDATE SET updated_at = NOW()
|
|
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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, 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 []string `db:"tags" json:"tags"`
|
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
|
Asin pgtype.Text `db:"asin" json:"asin"`
|
|
DatePublished pgtype.Date `db:"date_published" json:"date_published"`
|
|
Publisher pgtype.Text `db:"publisher" json:"publisher"`
|
|
Contributors []string `db:"contributors" json:"contributors"`
|
|
ContributorsSearch []string `db:"contributors_search" json:"contributors_search"`
|
|
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"`
|
|
ImportedAt pgtype.Timestamptz `db:"imported_at" json:"imported_at"`
|
|
MangaType pgtype.Text `db:"manga_type" json:"manga_type"`
|
|
ReadingDirection pgtype.Text `db:"reading_direction" json:"reading_direction"`
|
|
SeriesCount pgtype.Int4 `db:"series_count" json:"series_count"`
|
|
Volume pgtype.Int4 `db:"volume" json:"volume"`
|
|
Imprint pgtype.Text `db:"imprint" json:"imprint"`
|
|
AgeRating pgtype.Text `db:"age_rating" json:"age_rating"`
|
|
WebUrl pgtype.Text `db:"web_url" json:"web_url"`
|
|
StoryArc pgtype.Text `db:"story_arc" json:"story_arc"`
|
|
IsBlackAndWhite pgtype.Bool `db:"is_black_and_white" json:"is_black_and_white"`
|
|
MetadataNotes pgtype.Text `db:"metadata_notes" json:"metadata_notes"`
|
|
CommunityRating pgtype.Float8 `db:"community_rating" json:"community_rating"`
|
|
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
|
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
|
}
|
|
|
|
// 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.TagsSearch,
|
|
arg.Asin,
|
|
arg.DatePublished,
|
|
arg.Publisher,
|
|
arg.Contributors,
|
|
arg.ContributorsSearch,
|
|
arg.Language,
|
|
arg.Edition,
|
|
arg.PageCount,
|
|
arg.Genre,
|
|
arg.CopyrightYear,
|
|
arg.GoodreadsID,
|
|
arg.OpenlibraryID,
|
|
arg.GoogleBooksID,
|
|
arg.AddedByAdminID,
|
|
arg.CreatedAt,
|
|
arg.ImportedAt,
|
|
arg.MangaType,
|
|
arg.ReadingDirection,
|
|
arg.SeriesCount,
|
|
arg.Volume,
|
|
arg.Imprint,
|
|
arg.AgeRating,
|
|
arg.WebUrl,
|
|
arg.StoryArc,
|
|
arg.IsBlackAndWhite,
|
|
arg.MetadataNotes,
|
|
arg.CommunityRating,
|
|
arg.AlternateInfo,
|
|
arg.ScanInformation,
|
|
arg.Summary,
|
|
arg.LibraryTypeName,
|
|
)
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&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)
|
|
ON CONFLICT (media_item_id, format_type) DO UPDATE SET
|
|
file_path = EXCLUDED.file_path,
|
|
file_sha256 = EXCLUDED.file_sha256,
|
|
file_size_bytes = EXCLUDED.file_size_bytes,
|
|
mime_type = EXCLUDED.mime_type
|
|
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, dedup_key, last_modified_at, last_modified_source, deleted, deleted_at
|
|
`
|
|
|
|
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,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateMediaNoteFull = `-- name: CreateMediaNoteFull :one
|
|
INSERT INTO media_notes (
|
|
media_item_id, user_id, content, position,
|
|
percentage_location, character_start, character_end,
|
|
epubcfi_location, chapter_reference, paragraph_reference,
|
|
dedup_key, last_modified_at, last_modified_source,
|
|
device_sync_data
|
|
) VALUES (
|
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14
|
|
) 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, dedup_key, last_modified_at, last_modified_source, deleted, deleted_at
|
|
`
|
|
|
|
type CreateMediaNoteFullParams 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"`
|
|
PercentageLocation pgtype.Float8 `db:"percentage_location" json:"percentage_location"`
|
|
CharacterStart pgtype.Int4 `db:"character_start" json:"character_start"`
|
|
CharacterEnd pgtype.Int4 `db:"character_end" json:"character_end"`
|
|
EpubcfiLocation pgtype.Text `db:"epubcfi_location" json:"epubcfi_location"`
|
|
ChapterReference pgtype.Int4 `db:"chapter_reference" json:"chapter_reference"`
|
|
ParagraphReference pgtype.Int4 `db:"paragraph_reference" json:"paragraph_reference"`
|
|
DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"`
|
|
LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"`
|
|
LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"`
|
|
DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"`
|
|
}
|
|
|
|
func (q *Queries) CreateMediaNoteFull(ctx context.Context, arg CreateMediaNoteFullParams) (MediaNotes, error) {
|
|
row := q.db.QueryRow(ctx, CreateMediaNoteFull,
|
|
arg.MediaItemID,
|
|
arg.UserID,
|
|
arg.Content,
|
|
arg.Position,
|
|
arg.PercentageLocation,
|
|
arg.CharacterStart,
|
|
arg.CharacterEnd,
|
|
arg.EpubcfiLocation,
|
|
arg.ChapterReference,
|
|
arg.ParagraphReference,
|
|
arg.DedupKey,
|
|
arg.LastModifiedAt,
|
|
arg.LastModifiedSource,
|
|
arg.DeviceSyncData,
|
|
)
|
|
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,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
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
|
|
// ============================================
|
|
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 CreateProcessingIssue = `-- name: CreateProcessingIssue :one
|
|
|
|
INSERT INTO processing_issues (media_item_id, library_id, issue_type, issue_description, severity)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
ON CONFLICT (media_item_id, issue_type)
|
|
DO UPDATE SET issue_description = EXCLUDED.issue_description,
|
|
severity = EXCLUDED.severity,
|
|
resolved = false,
|
|
resolved_at = NULL
|
|
RETURNING id, media_item_id, library_id, issue_type, issue_description, severity, resolved, resolved_at, created_at
|
|
`
|
|
|
|
type CreateProcessingIssueParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
IssueType string `db:"issue_type" json:"issue_type"`
|
|
IssueDescription string `db:"issue_description" json:"issue_description"`
|
|
Severity string `db:"severity" json:"severity"`
|
|
}
|
|
|
|
// ============================================================================
|
|
// PROCESSING ISSUES QUERIES
|
|
// ============================================================================
|
|
func (q *Queries) CreateProcessingIssue(ctx context.Context, arg CreateProcessingIssueParams) (ProcessingIssues, error) {
|
|
row := q.db.QueryRow(ctx, CreateProcessingIssue,
|
|
arg.MediaItemID,
|
|
arg.LibraryID,
|
|
arg.IssueType,
|
|
arg.IssueDescription,
|
|
arg.Severity,
|
|
)
|
|
var i ProcessingIssues
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.LibraryID,
|
|
&i.IssueType,
|
|
&i.IssueDescription,
|
|
&i.Severity,
|
|
&i.Resolved,
|
|
&i.ResolvedAt,
|
|
&i.CreatedAt,
|
|
)
|
|
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 CreateReadingSpeed = `-- name: CreateReadingSpeed :one
|
|
INSERT INTO reading_speed (user_id, media_item_id, pages_per_minute, pages_read, total_reading_minutes, last_read_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
RETURNING id, user_id, media_item_id, words_per_minute, pages_per_minute, pages_read, total_reading_minutes, last_read_at, updated_at
|
|
`
|
|
|
|
type CreateReadingSpeedParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
PagesPerMinute pgtype.Float4 `db:"pages_per_minute" json:"pages_per_minute"`
|
|
PagesRead pgtype.Int4 `db:"pages_read" json:"pages_read"`
|
|
TotalReadingMinutes pgtype.Float4 `db:"total_reading_minutes" json:"total_reading_minutes"`
|
|
LastReadAt pgtype.Timestamptz `db:"last_read_at" json:"last_read_at"`
|
|
}
|
|
|
|
func (q *Queries) CreateReadingSpeed(ctx context.Context, arg CreateReadingSpeedParams) (ReadingSpeed, error) {
|
|
row := q.db.QueryRow(ctx, CreateReadingSpeed,
|
|
arg.UserID,
|
|
arg.MediaItemID,
|
|
arg.PagesPerMinute,
|
|
arg.PagesRead,
|
|
arg.TotalReadingMinutes,
|
|
arg.LastReadAt,
|
|
)
|
|
var i ReadingSpeed
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.MediaItemID,
|
|
&i.WordsPerMinute,
|
|
&i.PagesPerMinute,
|
|
&i.PagesRead,
|
|
&i.TotalReadingMinutes,
|
|
&i.LastReadAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
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 CreateSavedFilter = `-- name: CreateSavedFilter :one
|
|
INSERT INTO saved_filters (user_id, name, resource_type, filters)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING id, user_id, name, resource_type, filters, created_at, updated_at
|
|
`
|
|
|
|
type CreateSavedFilterParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Name string `db:"name" json:"name"`
|
|
ResourceType string `db:"resource_type" json:"resource_type"`
|
|
Filters []byte `db:"filters" json:"filters"`
|
|
}
|
|
|
|
func (q *Queries) CreateSavedFilter(ctx context.Context, arg CreateSavedFilterParams) (SavedFilters, error) {
|
|
row := q.db.QueryRow(ctx, CreateSavedFilter,
|
|
arg.UserID,
|
|
arg.Name,
|
|
arg.ResourceType,
|
|
arg.Filters,
|
|
)
|
|
var i SavedFilters
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Name,
|
|
&i.ResourceType,
|
|
&i.Filters,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
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 CreateSystemCollection = `-- name: CreateSystemCollection :one
|
|
INSERT INTO collections (user_id, name, description, icon, color, show_on_dashboard, query_type, priority, is_system_collection, auto_assign_rules)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, true, 'null'::jsonb)
|
|
RETURNING id, user_id, name, description, color, icon, auto_assign_rules, view_settings, show_on_dashboard, query_type, priority, is_system_collection, created_at
|
|
`
|
|
|
|
type CreateSystemCollectionParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Name string `db:"name" json:"name"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
Icon pgtype.Text `db:"icon" json:"icon"`
|
|
Color pgtype.Text `db:"color" json:"color"`
|
|
ShowOnDashboard pgtype.Bool `db:"show_on_dashboard" json:"show_on_dashboard"`
|
|
QueryType pgtype.Text `db:"query_type" json:"query_type"`
|
|
Priority pgtype.Int4 `db:"priority" json:"priority"`
|
|
}
|
|
|
|
func (q *Queries) CreateSystemCollection(ctx context.Context, arg CreateSystemCollectionParams) (Collections, error) {
|
|
row := q.db.QueryRow(ctx, CreateSystemCollection,
|
|
arg.UserID,
|
|
arg.Name,
|
|
arg.Description,
|
|
arg.Icon,
|
|
arg.Color,
|
|
arg.ShowOnDashboard,
|
|
arg.QueryType,
|
|
arg.Priority,
|
|
)
|
|
var i Collections
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.Color,
|
|
&i.Icon,
|
|
&i.AutoAssignRules,
|
|
&i.ViewSettings,
|
|
&i.ShowOnDashboard,
|
|
&i.QueryType,
|
|
&i.Priority,
|
|
&i.IsSystemCollection,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const CreateUnlinkedBook = `-- name: CreateUnlinkedBook :one
|
|
|
|
INSERT INTO unlinked_books (device_id, content_id, file_path, title, author, confidence_score)
|
|
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"`
|
|
}
|
|
|
|
// ============================================
|
|
// ENHANCED KOBO SYNC
|
|
// ============================================
|
|
// 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 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 DeleteMediaBookmark = `-- name: DeleteMediaBookmark :exec
|
|
DELETE FROM media_bookmarks
|
|
WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) DeleteMediaBookmark(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, DeleteMediaBookmark, id)
|
|
return 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 DeleteProcessingIssue = `-- name: DeleteProcessingIssue :one
|
|
DELETE FROM processing_issues
|
|
WHERE id = $1
|
|
RETURNING id, media_item_id, library_id, issue_type, issue_description, severity, resolved, resolved_at, created_at
|
|
`
|
|
|
|
func (q *Queries) DeleteProcessingIssue(ctx context.Context, id pgtype.UUID) (ProcessingIssues, error) {
|
|
row := q.db.QueryRow(ctx, DeleteProcessingIssue, id)
|
|
var i ProcessingIssues
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.LibraryID,
|
|
&i.IssueType,
|
|
&i.IssueDescription,
|
|
&i.Severity,
|
|
&i.Resolved,
|
|
&i.ResolvedAt,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, 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 DeleteSavedFilter = `-- name: DeleteSavedFilter :one
|
|
DELETE FROM saved_filters
|
|
WHERE id = $1 AND user_id = $2
|
|
RETURNING id, user_id, name, resource_type, filters, created_at, updated_at
|
|
`
|
|
|
|
type DeleteSavedFilterParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
func (q *Queries) DeleteSavedFilter(ctx context.Context, arg DeleteSavedFilterParams) (SavedFilters, error) {
|
|
row := q.db.QueryRow(ctx, DeleteSavedFilter, arg.ID, arg.UserID)
|
|
var i SavedFilters
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Name,
|
|
&i.ResourceType,
|
|
&i.Filters,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, 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 DeleteUserSystemCollection = `-- name: DeleteUserSystemCollection :exec
|
|
DELETE FROM collections
|
|
WHERE user_id = $1
|
|
AND name = $2
|
|
AND is_system_collection = true
|
|
`
|
|
|
|
type DeleteUserSystemCollectionParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Name string `db:"name" json:"name"`
|
|
}
|
|
|
|
func (q *Queries) DeleteUserSystemCollection(ctx context.Context, arg DeleteUserSystemCollectionParams) error {
|
|
_, err := q.db.Exec(ctx, DeleteUserSystemCollection, arg.UserID, arg.Name)
|
|
return err
|
|
}
|
|
|
|
const FindHashConflictGroups = `-- name: FindHashConflictGroups :many
|
|
SELECT library_id, file_sha256, COUNT(*) AS dup_count
|
|
FROM media_items
|
|
WHERE file_sha256 IS NOT NULL
|
|
GROUP BY library_id, file_sha256
|
|
HAVING COUNT(*) > 1
|
|
`
|
|
|
|
type FindHashConflictGroupsRow struct {
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
DupCount int64 `db:"dup_count" json:"dup_count"`
|
|
}
|
|
|
|
// Find content-duplicate groups (same library + SHA-256, more than one row)
|
|
func (q *Queries) FindHashConflictGroups(ctx context.Context) ([]FindHashConflictGroupsRow, error) {
|
|
rows, err := q.db.Query(ctx, FindHashConflictGroups)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []FindHashConflictGroupsRow{}
|
|
for rows.Next() {
|
|
var i FindHashConflictGroupsRow
|
|
if err := rows.Scan(&i.LibraryID, &i.FileSha256, &i.DupCount); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
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 GetActiveAnnotationsForBook = `-- name: GetActiveAnnotationsForBook :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,
|
|
mh.note_text,
|
|
mh.dedup_key,
|
|
mh.last_modified_at,
|
|
mh.last_modified_source
|
|
FROM media_highlights mh
|
|
WHERE mh.media_item_id = $1 AND mh.user_id = $2 AND mh.deleted = FALSE
|
|
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,
|
|
NULL as note_text,
|
|
mn.dedup_key,
|
|
mn.last_modified_at,
|
|
mn.last_modified_source
|
|
FROM media_notes mn
|
|
WHERE mn.media_item_id = $1 AND mn.user_id = $2 AND mn.deleted = FALSE
|
|
ORDER BY created_at DESC
|
|
`
|
|
|
|
type GetActiveAnnotationsForBookParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
type GetActiveAnnotationsForBookRow 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"`
|
|
NoteText pgtype.Text `db:"note_text" json:"note_text"`
|
|
DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"`
|
|
LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"`
|
|
LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"`
|
|
}
|
|
|
|
// ============================================
|
|
// ANNOTATION SERVE QUERIES
|
|
// ============================================
|
|
func (q *Queries) GetActiveAnnotationsForBook(ctx context.Context, arg GetActiveAnnotationsForBookParams) ([]GetActiveAnnotationsForBookRow, error) {
|
|
rows, err := q.db.Query(ctx, GetActiveAnnotationsForBook, arg.MediaItemID, arg.UserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetActiveAnnotationsForBookRow{}
|
|
for rows.Next() {
|
|
var i GetActiveAnnotationsForBookRow
|
|
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,
|
|
&i.NoteText,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
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 GetAllSystemSettings = `-- name: GetAllSystemSettings :many
|
|
SELECT setting_key, setting_value, description FROM system_settings ORDER BY setting_key
|
|
`
|
|
|
|
type GetAllSystemSettingsRow struct {
|
|
SettingKey string `db:"setting_key" json:"setting_key"`
|
|
SettingValue string `db:"setting_value" json:"setting_value"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
}
|
|
|
|
func (q *Queries) GetAllSystemSettings(ctx context.Context) ([]GetAllSystemSettingsRow, error) {
|
|
rows, err := q.db.Query(ctx, GetAllSystemSettings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetAllSystemSettingsRow{}
|
|
for rows.Next() {
|
|
var i GetAllSystemSettingsRow
|
|
if err := rows.Scan(&i.SettingKey, &i.SettingValue, &i.Description); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetAllSystemSettingsFull = `-- name: GetAllSystemSettingsFull :many
|
|
SELECT id, setting_key, setting_value, description, updated_at, setting_type, min_value, max_value, requires_restart, category FROM system_settings ORDER BY category, setting_key
|
|
`
|
|
|
|
func (q *Queries) GetAllSystemSettingsFull(ctx context.Context) ([]SystemSettings, error) {
|
|
rows, err := q.db.Query(ctx, GetAllSystemSettingsFull)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SystemSettings{}
|
|
for rows.Next() {
|
|
var i SystemSettings
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.SettingKey,
|
|
&i.SettingValue,
|
|
&i.Description,
|
|
&i.UpdatedAt,
|
|
&i.SettingType,
|
|
&i.MinValue,
|
|
&i.MaxValue,
|
|
&i.RequiresRestart,
|
|
&i.Category,
|
|
); 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 AND COALESCE(mh.deleted, FALSE) = FALSE
|
|
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 AND COALESCE(mn.deleted, FALSE) = FALSE
|
|
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 GetBooksByTag = `-- name: GetBooksByTag :many
|
|
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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items
|
|
WHERE library_id = $1 AND tags @> ARRAY[$2::text]
|
|
ORDER BY title ASC
|
|
`
|
|
|
|
type GetBooksByTagParams struct {
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Column2 string `db:"column_2" json:"column_2"`
|
|
}
|
|
|
|
func (q *Queries) GetBooksByTag(ctx context.Context, arg GetBooksByTagParams) ([]MediaItems, error) {
|
|
rows, err := q.db.Query(ctx, GetBooksByTag, arg.LibraryID, arg.Column2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []MediaItems{}
|
|
for rows.Next() {
|
|
var i MediaItems
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
); 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, show_on_dashboard, query_type, priority, is_system_collection, 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.ShowOnDashboard,
|
|
&i.QueryType,
|
|
&i.Priority,
|
|
&i.IsSystemCollection,
|
|
&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, ci.excluded, mi.title, mi.author, mi.cover_image_path, mi.library_id
|
|
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"`
|
|
Excluded pgtype.Bool `db:"excluded" json:"excluded"`
|
|
Title string `db:"title" json:"title"`
|
|
Author pgtype.Text `db:"author" json:"author"`
|
|
CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
}
|
|
|
|
// 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.Excluded,
|
|
&i.Title,
|
|
&i.Author,
|
|
&i.CoverImagePath,
|
|
&i.LibraryID,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetCollectionItemsForDashboard = `-- name: GetCollectionItemsForDashboard :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.imported_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.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, ci.excluded FROM media_items mi
|
|
INNER JOIN collection_items ci ON ci.media_item_id = mi.id
|
|
WHERE ci.collection_id = $1
|
|
AND ($2::uuid IS NULL OR mi.library_id = $2::uuid)
|
|
ORDER BY ci.added_at DESC
|
|
LIMIT $3
|
|
`
|
|
|
|
type GetCollectionItemsForDashboardParams struct {
|
|
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
type GetCollectionItemsForDashboardRow 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 []string `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 []string `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"`
|
|
ImportedAt pgtype.Timestamptz `db:"imported_at" json:"imported_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"`
|
|
MangaType pgtype.Text `db:"manga_type" json:"manga_type"`
|
|
ReadingDirection pgtype.Text `db:"reading_direction" json:"reading_direction"`
|
|
SeriesCount pgtype.Int4 `db:"series_count" json:"series_count"`
|
|
Volume pgtype.Int4 `db:"volume" json:"volume"`
|
|
Imprint pgtype.Text `db:"imprint" json:"imprint"`
|
|
AgeRating pgtype.Text `db:"age_rating" json:"age_rating"`
|
|
WebUrl pgtype.Text `db:"web_url" json:"web_url"`
|
|
StoryArc pgtype.Text `db:"story_arc" json:"story_arc"`
|
|
IsBlackAndWhite pgtype.Bool `db:"is_black_and_white" json:"is_black_and_white"`
|
|
MetadataNotes pgtype.Text `db:"metadata_notes" json:"metadata_notes"`
|
|
CommunityRating pgtype.Float8 `db:"community_rating" json:"community_rating"`
|
|
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
|
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
|
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
|
ContributorsSearch []string `db:"contributors_search" json:"contributors_search"`
|
|
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"`
|
|
Excluded pgtype.Bool `db:"excluded" json:"excluded"`
|
|
}
|
|
|
|
func (q *Queries) GetCollectionItemsForDashboard(ctx context.Context, arg GetCollectionItemsForDashboardParams) ([]GetCollectionItemsForDashboardRow, error) {
|
|
rows, err := q.db.Query(ctx, GetCollectionItemsForDashboard, arg.CollectionID, arg.LibraryID, arg.Limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetCollectionItemsForDashboardRow{}
|
|
for rows.Next() {
|
|
var i GetCollectionItemsForDashboardRow
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
&i.Excluded,
|
|
); 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.show_on_dashboard, c.query_type, c.priority, c.is_system_collection, 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"`
|
|
ShowOnDashboard pgtype.Bool `db:"show_on_dashboard" json:"show_on_dashboard"`
|
|
QueryType pgtype.Text `db:"query_type" json:"query_type"`
|
|
Priority pgtype.Int4 `db:"priority" json:"priority"`
|
|
IsSystemCollection pgtype.Bool `db:"is_system_collection" json:"is_system_collection"`
|
|
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.ShowOnDashboard,
|
|
&i.QueryType,
|
|
&i.Priority,
|
|
&i.IsSystemCollection,
|
|
&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, show_on_dashboard, query_type, priority, is_system_collection, 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.ShowOnDashboard,
|
|
&i.QueryType,
|
|
&i.Priority,
|
|
&i.IsSystemCollection,
|
|
&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.show_on_dashboard, c.query_type, c.priority, c.is_system_collection, 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.ShowOnDashboard,
|
|
&i.QueryType,
|
|
&i.Priority,
|
|
&i.IsSystemCollection,
|
|
&i.CreatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetContinueReadingItems = `-- name: GetContinueReadingItems :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.imported_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.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi
|
|
INNER JOIN (
|
|
SELECT DISTINCT ON (media_item_id) media_item_id, last_read_at
|
|
FROM reading_progress
|
|
WHERE user_id = $1
|
|
AND percentage > 0
|
|
AND percentage < 1
|
|
ORDER BY media_item_id, last_read_at DESC
|
|
) rp ON rp.media_item_id = mi.id
|
|
WHERE ($2::uuid IS NULL OR mi.library_id = $2::uuid)
|
|
ORDER BY rp.last_read_at DESC
|
|
LIMIT $3
|
|
`
|
|
|
|
type GetContinueReadingItemsParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
// Smart section queries (for system collections)
|
|
func (q *Queries) GetContinueReadingItems(ctx context.Context, arg GetContinueReadingItemsParams) ([]MediaItems, error) {
|
|
rows, err := q.db.Query(ctx, GetContinueReadingItems, arg.UserID, arg.LibraryID, arg.Limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []MediaItems{}
|
|
for rows.Next() {
|
|
var i MediaItems
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetContinueSeriesItems = `-- name: GetContinueSeriesItems :many
|
|
WITH user_series_progress AS (
|
|
SELECT mi.series,
|
|
MAX(mi.series_number) as max_read_number,
|
|
MAX(rp.last_read_at) as last_read_at
|
|
FROM reading_progress rp
|
|
JOIN media_items mi ON mi.id = rp.media_item_id
|
|
WHERE rp.user_id = $2
|
|
AND rp.percentage > 0
|
|
AND mi.series IS NOT NULL AND mi.series != ''
|
|
AND ($3::uuid IS NULL OR mi.library_id = $3::uuid)
|
|
GROUP BY mi.series
|
|
),
|
|
next_books AS (
|
|
SELECT DISTINCT ON (mi.series) 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.imported_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.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence,
|
|
usp.last_read_at
|
|
FROM media_items mi
|
|
JOIN user_series_progress usp ON mi.series = usp.series
|
|
WHERE ($3::uuid IS NULL OR mi.library_id = $3::uuid)
|
|
AND (mi.series_number > usp.max_read_number OR usp.max_read_number IS NULL)
|
|
ORDER BY mi.series, mi.series_number ASC NULLS LAST
|
|
)
|
|
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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence, last_read_at FROM next_books
|
|
ORDER BY last_read_at DESC NULLS LAST
|
|
LIMIT $1
|
|
`
|
|
|
|
type GetContinueSeriesItemsParams struct {
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
}
|
|
|
|
type GetContinueSeriesItemsRow 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 []string `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 []string `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"`
|
|
ImportedAt pgtype.Timestamptz `db:"imported_at" json:"imported_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"`
|
|
MangaType pgtype.Text `db:"manga_type" json:"manga_type"`
|
|
ReadingDirection pgtype.Text `db:"reading_direction" json:"reading_direction"`
|
|
SeriesCount pgtype.Int4 `db:"series_count" json:"series_count"`
|
|
Volume pgtype.Int4 `db:"volume" json:"volume"`
|
|
Imprint pgtype.Text `db:"imprint" json:"imprint"`
|
|
AgeRating pgtype.Text `db:"age_rating" json:"age_rating"`
|
|
WebUrl pgtype.Text `db:"web_url" json:"web_url"`
|
|
StoryArc pgtype.Text `db:"story_arc" json:"story_arc"`
|
|
IsBlackAndWhite pgtype.Bool `db:"is_black_and_white" json:"is_black_and_white"`
|
|
MetadataNotes pgtype.Text `db:"metadata_notes" json:"metadata_notes"`
|
|
CommunityRating pgtype.Float8 `db:"community_rating" json:"community_rating"`
|
|
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
|
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
|
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
|
ContributorsSearch []string `db:"contributors_search" json:"contributors_search"`
|
|
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"`
|
|
LastReadAt interface{} `db:"last_read_at" json:"last_read_at"`
|
|
}
|
|
|
|
func (q *Queries) GetContinueSeriesItems(ctx context.Context, arg GetContinueSeriesItemsParams) ([]GetContinueSeriesItemsRow, error) {
|
|
rows, err := q.db.Query(ctx, GetContinueSeriesItems, arg.Limit, arg.UserID, arg.LibraryID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetContinueSeriesItemsRow{}
|
|
for rows.Next() {
|
|
var i GetContinueSeriesItemsRow
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
&i.LastReadAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetDashboardPreferences = `-- name: GetDashboardPreferences :one
|
|
|
|
SELECT id, user_id, library_id, hidden_collections, collection_order, items_per_section, created_at, updated_at FROM user_dashboard_preferences
|
|
WHERE user_id = $1 AND ($2::uuid IS NULL OR library_id = $2::uuid)
|
|
`
|
|
|
|
type GetDashboardPreferencesParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
}
|
|
|
|
// ============================================
|
|
// CAROUSEL-STYLE DASHBOARD
|
|
// ============================================
|
|
// Dashboard preferences queries
|
|
func (q *Queries) GetDashboardPreferences(ctx context.Context, arg GetDashboardPreferencesParams) (UserDashboardPreferences, error) {
|
|
row := q.db.QueryRow(ctx, GetDashboardPreferences, arg.UserID, arg.LibraryID)
|
|
var i UserDashboardPreferences
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.LibraryID,
|
|
&i.HiddenCollections,
|
|
&i.CollectionOrder,
|
|
&i.ItemsPerSection,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
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"`
|
|
BookhoardUuid 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.BookhoardUuid)
|
|
var i DeviceCatalogs
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.DeviceID,
|
|
&i.MediaItemID,
|
|
&i.BookhoardUuid,
|
|
&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.BookhoardUuid,
|
|
&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"`
|
|
BookhoardUuid 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.BookhoardUuid,
|
|
&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 GetDictionaryEntry = `-- name: GetDictionaryEntry :one
|
|
SELECT id, word, definition, part_of_speech, example, etymology, created_at, accessed_at FROM dictionary_cache
|
|
WHERE word = $1
|
|
`
|
|
|
|
func (q *Queries) GetDictionaryEntry(ctx context.Context, word string) (DictionaryCache, error) {
|
|
row := q.db.QueryRow(ctx, GetDictionaryEntry, word)
|
|
var i DictionaryCache
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Word,
|
|
&i.Definition,
|
|
&i.PartOfSpeech,
|
|
&i.Example,
|
|
&i.Etymology,
|
|
&i.CreatedAt,
|
|
&i.AccessedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetDistinctSeries = `-- name: GetDistinctSeries :many
|
|
SELECT series, COUNT(*) as book_count,
|
|
MAX(series_count) as total_in_series,
|
|
MAX(created_at) as last_entry_at
|
|
FROM media_items
|
|
WHERE ($1::uuid IS NULL OR library_id = $1::uuid) AND series IS NOT NULL AND series != ''
|
|
GROUP BY series
|
|
ORDER BY MAX(created_at) DESC
|
|
LIMIT $3 OFFSET $2
|
|
`
|
|
|
|
type GetDistinctSeriesParams struct {
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Offset pgtype.Int4 `db:"offset" json:"offset"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
type GetDistinctSeriesRow struct {
|
|
Series pgtype.Text `db:"series" json:"series"`
|
|
BookCount int64 `db:"book_count" json:"book_count"`
|
|
TotalInSeries interface{} `db:"total_in_series" json:"total_in_series"`
|
|
LastEntryAt interface{} `db:"last_entry_at" json:"last_entry_at"`
|
|
}
|
|
|
|
func (q *Queries) GetDistinctSeries(ctx context.Context, arg GetDistinctSeriesParams) ([]GetDistinctSeriesRow, error) {
|
|
rows, err := q.db.Query(ctx, GetDistinctSeries, arg.LibraryID, arg.Offset, arg.Limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetDistinctSeriesRow{}
|
|
for rows.Next() {
|
|
var i GetDistinctSeriesRow
|
|
if err := rows.Scan(
|
|
&i.Series,
|
|
&i.BookCount,
|
|
&i.TotalInSeries,
|
|
&i.LastEntryAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetDistinctSeriesCount = `-- name: GetDistinctSeriesCount :one
|
|
SELECT COUNT(DISTINCT series)::int
|
|
FROM media_items
|
|
WHERE ($1::uuid IS NULL OR library_id = $1::uuid) AND series IS NOT NULL AND series != ''
|
|
`
|
|
|
|
func (q *Queries) GetDistinctSeriesCount(ctx context.Context, libraryID pgtype.UUID) (int32, error) {
|
|
row := q.db.QueryRow(ctx, GetDistinctSeriesCount, libraryID)
|
|
var column_1 int32
|
|
err := row.Scan(&column_1)
|
|
return column_1, err
|
|
}
|
|
|
|
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 GetFirstAdmin = `-- name: GetFirstAdmin :one
|
|
SELECT id FROM users
|
|
WHERE role = 'admin'
|
|
ORDER BY created_at ASC
|
|
LIMIT 1
|
|
`
|
|
|
|
func (q *Queries) GetFirstAdmin(ctx context.Context) (pgtype.UUID, error) {
|
|
row := q.db.QueryRow(ctx, GetFirstAdmin)
|
|
var id pgtype.UUID
|
|
err := row.Scan(&id)
|
|
return id, err
|
|
}
|
|
|
|
const GetFirstAdminExclude = `-- name: GetFirstAdminExclude :one
|
|
SELECT id, email, username, theme, first_name, last_name, role, max_devices, created_at, updated_at FROM users
|
|
WHERE role = 'admin' AND id != $1
|
|
ORDER BY created_at ASC
|
|
LIMIT 1
|
|
`
|
|
|
|
type GetFirstAdminExcludeRow 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"`
|
|
MaxDevices pgtype.Int4 `db:"max_devices" json:"max_devices"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
}
|
|
|
|
func (q *Queries) GetFirstAdminExclude(ctx context.Context, id pgtype.UUID) (GetFirstAdminExcludeRow, error) {
|
|
row := q.db.QueryRow(ctx, GetFirstAdminExclude, id)
|
|
var i GetFirstAdminExcludeRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Email,
|
|
&i.Username,
|
|
&i.Theme,
|
|
&i.FirstName,
|
|
&i.LastName,
|
|
&i.Role,
|
|
&i.MaxDevices,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetHashConflict = `-- name: GetHashConflict :one
|
|
SELECT id, library_id, file_sha256, status, resolution, resolved_by, created_at, resolved_at FROM hash_conflicts WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetHashConflict(ctx context.Context, id pgtype.UUID) (HashConflicts, error) {
|
|
row := q.db.QueryRow(ctx, GetHashConflict, id)
|
|
var i HashConflicts
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.FileSha256,
|
|
&i.Status,
|
|
&i.Resolution,
|
|
&i.ResolvedBy,
|
|
&i.CreatedAt,
|
|
&i.ResolvedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
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 GetLibraryByFolderPathPrefix = `-- name: GetLibraryByFolderPathPrefix :one
|
|
SELECT lf.library_id, lf.folder_path, 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 $1 LIKE lf.folder_path || '%'
|
|
ORDER BY LENGTH(lf.folder_path) DESC
|
|
LIMIT 1
|
|
`
|
|
|
|
type GetLibraryByFolderPathPrefixRow struct {
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
FolderPath string `db:"folder_path" json:"folder_path"`
|
|
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) GetLibraryByFolderPathPrefix(ctx context.Context, folderPath string) (GetLibraryByFolderPathPrefixRow, error) {
|
|
row := q.db.QueryRow(ctx, GetLibraryByFolderPathPrefix, folderPath)
|
|
var i GetLibraryByFolderPathPrefixRow
|
|
err := row.Scan(
|
|
&i.LibraryID,
|
|
&i.FolderPath,
|
|
&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 GetLibraryItems = `-- name: GetLibraryItems :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.imported_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.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi
|
|
WHERE ($1::uuid IS NULL OR mi.library_id = $1::uuid)
|
|
ORDER BY mi.created_at DESC
|
|
`
|
|
|
|
func (q *Queries) GetLibraryItems(ctx context.Context, libraryID pgtype.UUID) ([]MediaItems, error) {
|
|
rows, err := q.db.Query(ctx, GetLibraryItems, libraryID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []MediaItems{}
|
|
for rows.Next() {
|
|
var i MediaItems
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
); 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 GetLibraryWithType = `-- name: GetLibraryWithType :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,
|
|
lt.allowed_extensions
|
|
FROM libraries l
|
|
JOIN library_types lt ON l.library_type_id = lt.id
|
|
WHERE l.id = $1
|
|
`
|
|
|
|
type GetLibraryWithTypeRow 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"`
|
|
AllowedExtensions []string `db:"allowed_extensions" json:"allowed_extensions"`
|
|
}
|
|
|
|
// ============================================================================
|
|
// LIBRARY WITH TYPE INFO QUERIES
|
|
// ============================================================================
|
|
func (q *Queries) GetLibraryWithType(ctx context.Context, id pgtype.UUID) (GetLibraryWithTypeRow, error) {
|
|
row := q.db.QueryRow(ctx, GetLibraryWithType, id)
|
|
var i GetLibraryWithTypeRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.LibraryTypeID,
|
|
&i.CreatedByAdminID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.TypeName,
|
|
&i.TypeDescription,
|
|
&i.AllowedExtensions,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaBookmark = `-- name: GetMediaBookmark :one
|
|
SELECT id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at, dedup_key, last_modified_at, last_modified_source, device_sync_data, percentage_location, epubcfi_location, chapter_reference, deleted, deleted_at FROM media_bookmarks WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetMediaBookmark(ctx context.Context, id pgtype.UUID) (MediaBookmarks, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaBookmark, id)
|
|
var i MediaBookmarks
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.PageNumber,
|
|
&i.ChapterNumber,
|
|
&i.CfiPosition,
|
|
&i.Title,
|
|
&i.Position,
|
|
&i.Notes,
|
|
&i.CreatedAt,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.DeviceSyncData,
|
|
&i.PercentageLocation,
|
|
&i.EpubcfiLocation,
|
|
&i.ChapterReference,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaBookmarkByDedupKey = `-- name: GetMediaBookmarkByDedupKey :one
|
|
|
|
SELECT id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at, dedup_key, last_modified_at, last_modified_source, device_sync_data, percentage_location, epubcfi_location, chapter_reference, deleted, deleted_at FROM media_bookmarks
|
|
WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3
|
|
ORDER BY deleted ASC, deleted_at DESC NULLS LAST
|
|
LIMIT 1
|
|
`
|
|
|
|
type GetMediaBookmarkByDedupKeyParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"`
|
|
}
|
|
|
|
// ============================================
|
|
// ANNOTATION SYNC QUERIES (bookmarks)
|
|
// ============================================
|
|
func (q *Queries) GetMediaBookmarkByDedupKey(ctx context.Context, arg GetMediaBookmarkByDedupKeyParams) (MediaBookmarks, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaBookmarkByDedupKey, arg.UserID, arg.MediaItemID, arg.DedupKey)
|
|
var i MediaBookmarks
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.PageNumber,
|
|
&i.ChapterNumber,
|
|
&i.CfiPosition,
|
|
&i.Title,
|
|
&i.Position,
|
|
&i.Notes,
|
|
&i.CreatedAt,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.DeviceSyncData,
|
|
&i.PercentageLocation,
|
|
&i.EpubcfiLocation,
|
|
&i.ChapterReference,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaBookmarks = `-- name: GetMediaBookmarks :many
|
|
SELECT id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at, dedup_key, last_modified_at, last_modified_source, device_sync_data, percentage_location, epubcfi_location, chapter_reference, deleted, deleted_at FROM media_bookmarks
|
|
WHERE media_item_id = $1 AND user_id = $2 AND COALESCE(deleted, FALSE) = FALSE
|
|
ORDER BY created_at DESC
|
|
`
|
|
|
|
type GetMediaBookmarksParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
func (q *Queries) GetMediaBookmarks(ctx context.Context, arg GetMediaBookmarksParams) ([]MediaBookmarks, error) {
|
|
rows, err := q.db.Query(ctx, GetMediaBookmarks, arg.MediaItemID, arg.UserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []MediaBookmarks{}
|
|
for rows.Next() {
|
|
var i MediaBookmarks
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.PageNumber,
|
|
&i.ChapterNumber,
|
|
&i.CfiPosition,
|
|
&i.Title,
|
|
&i.Position,
|
|
&i.Notes,
|
|
&i.CreatedAt,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.DeviceSyncData,
|
|
&i.PercentageLocation,
|
|
&i.EpubcfiLocation,
|
|
&i.ChapterReference,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
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, dedup_key, last_modified_at, last_modified_source, note_text, deleted, deleted_at 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,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.NoteText,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaHighlightByDedupKey = `-- name: GetMediaHighlightByDedupKey :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, dedup_key, last_modified_at, last_modified_source, note_text, deleted, deleted_at FROM media_highlights
|
|
WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3
|
|
ORDER BY deleted ASC, deleted_at DESC NULLS LAST
|
|
LIMIT 1
|
|
`
|
|
|
|
type GetMediaHighlightByDedupKeyParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"`
|
|
}
|
|
|
|
// ============================================
|
|
// ANNOTATION SYNC QUERIES (highlights)
|
|
// ============================================
|
|
func (q *Queries) GetMediaHighlightByDedupKey(ctx context.Context, arg GetMediaHighlightByDedupKeyParams) (MediaHighlights, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaHighlightByDedupKey, arg.UserID, arg.MediaItemID, arg.DedupKey)
|
|
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,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.NoteText,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
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, dedup_key, last_modified_at, last_modified_source, note_text, deleted, deleted_at FROM media_highlights WHERE media_item_id = $1 AND user_id = $2 AND COALESCE(deleted, FALSE) = FALSE 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,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.NoteText,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
); 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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, 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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1 AND library_id = $2
|
|
`
|
|
|
|
type GetMediaItemByFilePathParams struct {
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
}
|
|
|
|
func (q *Queries) GetMediaItemByFilePath(ctx context.Context, arg GetMediaItemByFilePathParams) (MediaItems, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaItemByFilePath, arg.FilePath, arg.LibraryID)
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaItemByFilePathAnyLibrary = `-- name: GetMediaItemByFilePathAnyLibrary :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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1 LIMIT 1
|
|
`
|
|
|
|
func (q *Queries) GetMediaItemByFilePathAnyLibrary(ctx context.Context, filePath string) (MediaItems, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaItemByFilePathAnyLibrary, 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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1
|
|
`
|
|
|
|
// ============================================
|
|
// KOREADER SYNC PROTOCOL
|
|
// ============================================
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, 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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, 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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, 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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, 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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaItemBySHA256AndLibrary = `-- name: GetMediaItemBySHA256AndLibrary :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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_sha256 = $1 AND library_id = $2
|
|
`
|
|
|
|
type GetMediaItemBySHA256AndLibraryParams struct {
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
}
|
|
|
|
// Get media item by SHA-256 hash within a specific library (content dedup)
|
|
func (q *Queries) GetMediaItemBySHA256AndLibrary(ctx context.Context, arg GetMediaItemBySHA256AndLibraryParams) (MediaItems, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaItemBySHA256AndLibrary, arg.FileSha256, arg.LibraryID)
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&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 GetMediaItemUsageCounts = `-- name: GetMediaItemUsageCounts :one
|
|
SELECT
|
|
(SELECT COUNT(*) FROM reading_progress rp WHERE rp.media_item_id = $1) AS progress_count,
|
|
(SELECT COUNT(*) FROM media_highlights mh WHERE mh.media_item_id = $1) AS highlights_count,
|
|
(SELECT COUNT(*) FROM media_bookmarks mb WHERE mb.media_item_id = $1) AS bookmarks_count,
|
|
(SELECT COUNT(*) FROM media_notes mn WHERE mn.media_item_id = $1) AS notes_count,
|
|
(SELECT COUNT(*) FROM collection_items ci WHERE ci.media_item_id = $1) AS collections_count
|
|
`
|
|
|
|
type GetMediaItemUsageCountsRow struct {
|
|
ProgressCount int64 `db:"progress_count" json:"progress_count"`
|
|
HighlightsCount int64 `db:"highlights_count" json:"highlights_count"`
|
|
BookmarksCount int64 `db:"bookmarks_count" json:"bookmarks_count"`
|
|
NotesCount int64 `db:"notes_count" json:"notes_count"`
|
|
CollectionsCount int64 `db:"collections_count" json:"collections_count"`
|
|
}
|
|
|
|
// Per-item user-data counts, used when choosing which duplicate copy to keep
|
|
func (q *Queries) GetMediaItemUsageCounts(ctx context.Context, mediaItemID pgtype.UUID) (GetMediaItemUsageCountsRow, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaItemUsageCounts, mediaItemID)
|
|
var i GetMediaItemUsageCountsRow
|
|
err := row.Scan(
|
|
&i.ProgressCount,
|
|
&i.HighlightsCount,
|
|
&i.BookmarksCount,
|
|
&i.NotesCount,
|
|
&i.CollectionsCount,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
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, dedup_key, last_modified_at, last_modified_source, deleted, deleted_at 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,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetMediaNoteByDedupKey = `-- name: GetMediaNoteByDedupKey :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, dedup_key, last_modified_at, last_modified_source, deleted, deleted_at FROM media_notes
|
|
WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3
|
|
ORDER BY deleted ASC, deleted_at DESC NULLS LAST
|
|
LIMIT 1
|
|
`
|
|
|
|
type GetMediaNoteByDedupKeyParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"`
|
|
}
|
|
|
|
// ============================================
|
|
// ANNOTATION SYNC QUERIES (notes)
|
|
// ============================================
|
|
func (q *Queries) GetMediaNoteByDedupKey(ctx context.Context, arg GetMediaNoteByDedupKeyParams) (MediaNotes, error) {
|
|
row := q.db.QueryRow(ctx, GetMediaNoteByDedupKey, arg.UserID, arg.MediaItemID, arg.DedupKey)
|
|
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,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
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, dedup_key, last_modified_at, last_modified_source, deleted, deleted_at FROM media_notes WHERE media_item_id = $1 AND user_id = $2 AND COALESCE(deleted, FALSE) = FALSE 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,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
); 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 GetNotStartedItems = `-- name: GetNotStartedItems :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.imported_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.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi
|
|
WHERE ($1::uuid IS NULL OR mi.library_id = $1::uuid)
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM reading_progress rp
|
|
WHERE rp.media_item_id = mi.id
|
|
AND rp.user_id = $2
|
|
AND rp.percentage > 0
|
|
)
|
|
ORDER BY mi.created_at DESC
|
|
LIMIT $3
|
|
`
|
|
|
|
type GetNotStartedItemsParams struct {
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
func (q *Queries) GetNotStartedItems(ctx context.Context, arg GetNotStartedItemsParams) ([]MediaItems, error) {
|
|
rows, err := q.db.Query(ctx, GetNotStartedItems, arg.LibraryID, arg.UserID, arg.Limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []MediaItems{}
|
|
for rows.Next() {
|
|
var i MediaItems
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
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 GetPanelData = `-- name: GetPanelData :one
|
|
SELECT id, media_item_id, page_number, detection_method, panels, created_at, updated_at FROM panel_data
|
|
WHERE media_item_id = $1 AND page_number = $2
|
|
`
|
|
|
|
type GetPanelDataParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
PageNumber int32 `db:"page_number" json:"page_number"`
|
|
}
|
|
|
|
func (q *Queries) GetPanelData(ctx context.Context, arg GetPanelDataParams) (PanelData, error) {
|
|
row := q.db.QueryRow(ctx, GetPanelData, arg.MediaItemID, arg.PageNumber)
|
|
var i PanelData
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.PageNumber,
|
|
&i.DetectionMethod,
|
|
&i.Panels,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
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 GetProcessingIssueStats = `-- name: GetProcessingIssueStats :one
|
|
SELECT
|
|
COUNT(*) FILTER (WHERE severity = 'error' AND resolved = false) as error_count,
|
|
COUNT(*) FILTER (WHERE severity = 'warning' AND resolved = false) as warning_count,
|
|
COUNT(*) FILTER (WHERE severity = 'info' AND resolved = false) as info_count
|
|
FROM processing_issues
|
|
WHERE library_id = $1
|
|
`
|
|
|
|
type GetProcessingIssueStatsRow struct {
|
|
ErrorCount int64 `db:"error_count" json:"error_count"`
|
|
WarningCount int64 `db:"warning_count" json:"warning_count"`
|
|
InfoCount int64 `db:"info_count" json:"info_count"`
|
|
}
|
|
|
|
func (q *Queries) GetProcessingIssueStats(ctx context.Context, libraryID pgtype.UUID) (GetProcessingIssueStatsRow, error) {
|
|
row := q.db.QueryRow(ctx, GetProcessingIssueStats, libraryID)
|
|
var i GetProcessingIssueStatsRow
|
|
err := row.Scan(&i.ErrorCount, &i.WarningCount, &i.InfoCount)
|
|
return i, err
|
|
}
|
|
|
|
const GetReaderSettings = `-- name: GetReaderSettings :one
|
|
SELECT setting_value FROM reader_settings
|
|
WHERE user_id = $1 AND setting_key = 'reader_settings'
|
|
`
|
|
|
|
func (q *Queries) GetReaderSettings(ctx context.Context, userID pgtype.UUID) ([]byte, error) {
|
|
row := q.db.QueryRow(ctx, GetReaderSettings, userID)
|
|
var setting_value []byte
|
|
err := row.Scan(&setting_value)
|
|
return setting_value, err
|
|
}
|
|
|
|
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, context_text, 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.ContextText,
|
|
&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 GetReadingSpeed = `-- name: GetReadingSpeed :one
|
|
SELECT id, user_id, media_item_id, words_per_minute, pages_per_minute, pages_read, total_reading_minutes, last_read_at, updated_at FROM reading_speed
|
|
WHERE user_id = $1 AND media_item_id = $2
|
|
`
|
|
|
|
type GetReadingSpeedParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
}
|
|
|
|
func (q *Queries) GetReadingSpeed(ctx context.Context, arg GetReadingSpeedParams) (ReadingSpeed, error) {
|
|
row := q.db.QueryRow(ctx, GetReadingSpeed, arg.UserID, arg.MediaItemID)
|
|
var i ReadingSpeed
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.MediaItemID,
|
|
&i.WordsPerMinute,
|
|
&i.PagesPerMinute,
|
|
&i.PagesRead,
|
|
&i.TotalReadingMinutes,
|
|
&i.LastReadAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetRecentlyAddedItems = `-- name: GetRecentlyAddedItems :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.imported_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.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi
|
|
WHERE ($1::uuid IS NULL OR mi.library_id = $1::uuid)
|
|
ORDER BY mi.imported_at DESC NULLS LAST, mi.created_at DESC
|
|
LIMIT $2
|
|
`
|
|
|
|
type GetRecentlyAddedItemsParams struct {
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
func (q *Queries) GetRecentlyAddedItems(ctx context.Context, arg GetRecentlyAddedItemsParams) ([]MediaItems, error) {
|
|
rows, err := q.db.Query(ctx, GetRecentlyAddedItems, arg.LibraryID, arg.Limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []MediaItems{}
|
|
for rows.Next() {
|
|
var i MediaItems
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetRecentlyReadItems = `-- name: GetRecentlyReadItems :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.imported_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.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi
|
|
INNER JOIN (
|
|
SELECT DISTINCT ON (media_item_id) media_item_id, last_read_at
|
|
FROM reading_progress
|
|
WHERE user_id = $1
|
|
AND percentage >= 1
|
|
ORDER BY media_item_id, last_read_at DESC
|
|
) rp ON rp.media_item_id = mi.id
|
|
WHERE ($2::uuid IS NULL OR mi.library_id = $2::uuid)
|
|
ORDER BY rp.last_read_at DESC
|
|
LIMIT $3
|
|
`
|
|
|
|
type GetRecentlyReadItemsParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
func (q *Queries) GetRecentlyReadItems(ctx context.Context, arg GetRecentlyReadItemsParams) ([]MediaItems, error) {
|
|
rows, err := q.db.Query(ctx, GetRecentlyReadItems, arg.UserID, arg.LibraryID, arg.Limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []MediaItems{}
|
|
for rows.Next() {
|
|
var i MediaItems
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
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 GetSavedFilterByID = `-- name: GetSavedFilterByID :one
|
|
SELECT id, user_id, name, resource_type, filters, created_at, updated_at FROM saved_filters
|
|
WHERE id = $1 AND user_id = $2
|
|
`
|
|
|
|
type GetSavedFilterByIDParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
func (q *Queries) GetSavedFilterByID(ctx context.Context, arg GetSavedFilterByIDParams) (SavedFilters, error) {
|
|
row := q.db.QueryRow(ctx, GetSavedFilterByID, arg.ID, arg.UserID)
|
|
var i SavedFilters
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Name,
|
|
&i.ResourceType,
|
|
&i.Filters,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetSavedFilters = `-- name: GetSavedFilters :many
|
|
SELECT id, user_id, name, resource_type, filters, created_at, updated_at FROM saved_filters
|
|
WHERE user_id = $1 AND resource_type = $2
|
|
ORDER BY created_at DESC
|
|
`
|
|
|
|
type GetSavedFiltersParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
ResourceType string `db:"resource_type" json:"resource_type"`
|
|
}
|
|
|
|
func (q *Queries) GetSavedFilters(ctx context.Context, arg GetSavedFiltersParams) ([]SavedFilters, error) {
|
|
rows, err := q.db.Query(ctx, GetSavedFilters, arg.UserID, arg.ResourceType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SavedFilters{}
|
|
for rows.Next() {
|
|
var i SavedFilters
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Name,
|
|
&i.ResourceType,
|
|
&i.Filters,
|
|
&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 GetSeriesBooks = `-- name: GetSeriesBooks :many
|
|
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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items
|
|
WHERE series = $1
|
|
ORDER BY series_number ASC NULLS LAST
|
|
`
|
|
|
|
func (q *Queries) GetSeriesBooks(ctx context.Context, series pgtype.Text) ([]MediaItems, error) {
|
|
rows, err := q.db.Query(ctx, GetSeriesBooks, series)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []MediaItems{}
|
|
for rows.Next() {
|
|
var i MediaItems
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const GetSeriesCovers = `-- name: GetSeriesCovers :many
|
|
SELECT cover_image_path, library_id
|
|
FROM media_items
|
|
WHERE ($1::uuid IS NULL OR library_id = $1::uuid) AND series = $2 AND cover_image_path IS NOT NULL AND cover_image_path != ''
|
|
ORDER BY series_number ASC NULLS LAST
|
|
LIMIT $3
|
|
`
|
|
|
|
type GetSeriesCoversParams struct {
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Series pgtype.Text `db:"series" json:"series"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
type GetSeriesCoversRow struct {
|
|
CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
}
|
|
|
|
func (q *Queries) GetSeriesCovers(ctx context.Context, arg GetSeriesCoversParams) ([]GetSeriesCoversRow, error) {
|
|
rows, err := q.db.Query(ctx, GetSeriesCovers, arg.LibraryID, arg.Series, arg.Limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetSeriesCoversRow{}
|
|
for rows.Next() {
|
|
var i GetSeriesCoversRow
|
|
if err := rows.Scan(&i.CoverImagePath, &i.LibraryID); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
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 GetSystemCollectionsForDashboard = `-- name: GetSystemCollectionsForDashboard :many
|
|
SELECT id, user_id, name, description, color, icon, auto_assign_rules, view_settings, show_on_dashboard, query_type, priority, is_system_collection, created_at FROM collections
|
|
WHERE user_id = $1
|
|
AND is_system_collection = true
|
|
AND show_on_dashboard = true
|
|
ORDER BY priority ASC
|
|
`
|
|
|
|
// Dashboard collections queries
|
|
func (q *Queries) GetSystemCollectionsForDashboard(ctx context.Context, userID pgtype.UUID) ([]Collections, error) {
|
|
rows, err := q.db.Query(ctx, GetSystemCollectionsForDashboard, 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.ShowOnDashboard,
|
|
&i.QueryType,
|
|
&i.Priority,
|
|
&i.IsSystemCollection,
|
|
&i.CreatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
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 GetSystemSetting = `-- name: GetSystemSetting :one
|
|
SELECT setting_value FROM system_settings WHERE setting_key = $1
|
|
`
|
|
|
|
// System Settings queries
|
|
func (q *Queries) GetSystemSetting(ctx context.Context, settingKey string) (string, error) {
|
|
row := q.db.QueryRow(ctx, GetSystemSetting, settingKey)
|
|
var setting_value string
|
|
err := row.Scan(&setting_value)
|
|
return setting_value, err
|
|
}
|
|
|
|
const GetSystemSettingFull = `-- name: GetSystemSettingFull :one
|
|
SELECT id, setting_key, setting_value, description, updated_at, setting_type, min_value, max_value, requires_restart, category FROM system_settings WHERE setting_key = $1
|
|
`
|
|
|
|
func (q *Queries) GetSystemSettingFull(ctx context.Context, settingKey string) (SystemSettings, error) {
|
|
row := q.db.QueryRow(ctx, GetSystemSettingFull, settingKey)
|
|
var i SystemSettings
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.SettingKey,
|
|
&i.SettingValue,
|
|
&i.Description,
|
|
&i.UpdatedAt,
|
|
&i.SettingType,
|
|
&i.MinValue,
|
|
&i.MaxValue,
|
|
&i.RequiresRestart,
|
|
&i.Category,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const GetSystemTimezone = `-- name: GetSystemTimezone :one
|
|
SELECT setting_value FROM system_settings WHERE setting_key = 'default_timezone'
|
|
`
|
|
|
|
func (q *Queries) GetSystemTimezone(ctx context.Context) (string, error) {
|
|
row := q.db.QueryRow(ctx, GetSystemTimezone)
|
|
var setting_value string
|
|
err := row.Scan(&setting_value)
|
|
return setting_value, err
|
|
}
|
|
|
|
const GetTombstonedAnnotationsForBook = `-- name: GetTombstonedAnnotationsForBook :many
|
|
SELECT
|
|
mh.id,
|
|
mh.dedup_key,
|
|
'highlight' as annotation_type,
|
|
mh.device_sync_data,
|
|
mh.deleted_at
|
|
FROM media_highlights mh
|
|
WHERE mh.media_item_id = $1 AND mh.user_id = $2 AND mh.deleted = TRUE AND mh.deleted_at > $3
|
|
UNION ALL
|
|
SELECT
|
|
mn.id,
|
|
mn.dedup_key,
|
|
'note' as annotation_type,
|
|
mn.device_sync_data,
|
|
mn.deleted_at
|
|
FROM media_notes mn
|
|
WHERE mn.media_item_id = $1 AND mn.user_id = $2 AND mn.deleted = TRUE AND mn.deleted_at > $3
|
|
UNION ALL
|
|
SELECT
|
|
mb.id,
|
|
mb.dedup_key,
|
|
'bookmark' as annotation_type,
|
|
mb.device_sync_data,
|
|
mb.deleted_at
|
|
FROM media_bookmarks mb
|
|
WHERE mb.media_item_id = $1 AND mb.user_id = $2 AND mb.deleted = TRUE AND mb.deleted_at > $3
|
|
ORDER BY deleted_at DESC
|
|
`
|
|
|
|
type GetTombstonedAnnotationsForBookParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
DeletedAt pgtype.Timestamptz `db:"deleted_at" json:"deleted_at"`
|
|
}
|
|
|
|
type GetTombstonedAnnotationsForBookRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"`
|
|
AnnotationType string `db:"annotation_type" json:"annotation_type"`
|
|
DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"`
|
|
DeletedAt pgtype.Timestamptz `db:"deleted_at" json:"deleted_at"`
|
|
}
|
|
|
|
func (q *Queries) GetTombstonedAnnotationsForBook(ctx context.Context, arg GetTombstonedAnnotationsForBookParams) ([]GetTombstonedAnnotationsForBookRow, error) {
|
|
rows, err := q.db.Query(ctx, GetTombstonedAnnotationsForBook, arg.MediaItemID, arg.UserID, arg.DeletedAt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetTombstonedAnnotationsForBookRow{}
|
|
for rows.Next() {
|
|
var i GetTombstonedAnnotationsForBookRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.DedupKey,
|
|
&i.AnnotationType,
|
|
&i.DeviceSyncData,
|
|
&i.DeletedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
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.context_text,
|
|
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"`
|
|
ContextText pgtype.Text `db:"context_text" json:"context_text"`
|
|
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.ContextText,
|
|
&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
|
|
u.id,
|
|
u.email,
|
|
u.username,
|
|
u.theme,
|
|
u.first_name,
|
|
u.last_name,
|
|
u.role,
|
|
u.max_devices,
|
|
u.created_at,
|
|
u.updated_at,
|
|
u.timezone,
|
|
(SELECT COUNT(*) FROM devices WHERE user_id = u.id) as device_count
|
|
FROM users u
|
|
WHERE u.id = $1
|
|
`
|
|
|
|
type GetUserRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Email string `db:"email" json:"email"`
|
|
Username string `db:"username" json:"username"`
|
|
Theme pgtype.Text `db:"theme" json:"theme"`
|
|
FirstName pgtype.Text `db:"first_name" json:"first_name"`
|
|
LastName pgtype.Text `db:"last_name" json:"last_name"`
|
|
Role string `db:"role" json:"role"`
|
|
MaxDevices pgtype.Int4 `db:"max_devices" json:"max_devices"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
Timezone pgtype.Text `db:"timezone" json:"timezone"`
|
|
DeviceCount int64 `db:"device_count" json:"device_count"`
|
|
}
|
|
|
|
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.MaxDevices,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Timezone,
|
|
&i.DeviceCount,
|
|
)
|
|
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 GetUserCollectionsForDashboard = `-- name: GetUserCollectionsForDashboard :many
|
|
SELECT c.id, c.user_id, c.name, c.description, c.color, c.icon, c.auto_assign_rules, c.view_settings, c.show_on_dashboard, c.query_type, c.priority, c.is_system_collection, c.created_at FROM collections c
|
|
WHERE c.user_id = $1
|
|
AND c.show_on_dashboard = true
|
|
AND c.is_system_collection = false
|
|
ORDER BY priority ASC
|
|
`
|
|
|
|
func (q *Queries) GetUserCollectionsForDashboard(ctx context.Context, userID pgtype.UUID) ([]Collections, error) {
|
|
rows, err := q.db.Query(ctx, GetUserCollectionsForDashboard, 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.ShowOnDashboard,
|
|
&i.QueryType,
|
|
&i.Priority,
|
|
&i.IsSystemCollection,
|
|
&i.CreatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
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 ASC
|
|
`
|
|
|
|
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 GetVisibleLibraryMediaCounts = `-- name: GetVisibleLibraryMediaCounts :many
|
|
SELECT l.id, COUNT(mi.id) as media_count
|
|
FROM libraries l
|
|
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $1
|
|
LEFT JOIN media_items mi ON mi.library_id = l.id
|
|
WHERE COALESCE(lv.is_visible, true) = true
|
|
GROUP BY l.id
|
|
`
|
|
|
|
type GetVisibleLibraryMediaCountsRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
MediaCount int64 `db:"media_count" json:"media_count"`
|
|
}
|
|
|
|
func (q *Queries) GetVisibleLibraryMediaCounts(ctx context.Context, userID pgtype.UUID) ([]GetVisibleLibraryMediaCountsRow, error) {
|
|
rows, err := q.db.Query(ctx, GetVisibleLibraryMediaCounts, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetVisibleLibraryMediaCountsRow{}
|
|
for rows.Next() {
|
|
var i GetVisibleLibraryMediaCountsRow
|
|
if err := rows.Scan(&i.ID, &i.MediaCount); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const HasRecentConflictResolution = `-- name: HasRecentConflictResolution :one
|
|
SELECT EXISTS(
|
|
SELECT 1 FROM sync_conflicts
|
|
WHERE media_item_id = $1
|
|
AND user_id = $2
|
|
AND resolution_status != 'unresolved'
|
|
AND resolved_at > NOW() - INTERVAL '10 minutes'
|
|
)
|
|
`
|
|
|
|
type HasRecentConflictResolutionParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
func (q *Queries) HasRecentConflictResolution(ctx context.Context, arg HasRecentConflictResolutionParams) (bool, error) {
|
|
row := q.db.QueryRow(ctx, HasRecentConflictResolution, arg.MediaItemID, arg.UserID)
|
|
var exists bool
|
|
err := row.Scan(&exists)
|
|
return exists, err
|
|
}
|
|
|
|
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 ListAllConflictsByUserAndStatus = `-- name: ListAllConflictsByUserAndStatus :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 = $2
|
|
ORDER BY sc.created_at DESC
|
|
`
|
|
|
|
type ListAllConflictsByUserAndStatusParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
ResolutionStatus pgtype.Text `db:"resolution_status" json:"resolution_status"`
|
|
}
|
|
|
|
type ListAllConflictsByUserAndStatusRow 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) ListAllConflictsByUserAndStatus(ctx context.Context, arg ListAllConflictsByUserAndStatusParams) ([]ListAllConflictsByUserAndStatusRow, error) {
|
|
rows, err := q.db.Query(ctx, ListAllConflictsByUserAndStatus, arg.UserID, arg.ResolutionStatus)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListAllConflictsByUserAndStatusRow{}
|
|
for rows.Next() {
|
|
var i ListAllConflictsByUserAndStatusRow
|
|
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 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 ListConflictsByUser = `-- name: ListConflictsByUser :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
|
|
ORDER BY sc.created_at DESC
|
|
`
|
|
|
|
type ListConflictsByUserRow 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) ListConflictsByUser(ctx context.Context, userID pgtype.UUID) ([]ListConflictsByUserRow, error) {
|
|
rows, err := q.db.Query(ctx, ListConflictsByUser, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListConflictsByUserRow{}
|
|
for rows.Next() {
|
|
var i ListConflictsByUserRow
|
|
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 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.imported_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.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name
|
|
FROM media_items mi
|
|
JOIN libraries l ON mi.library_id = l.id
|
|
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 []string `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 []string `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"`
|
|
ImportedAt pgtype.Timestamptz `db:"imported_at" json:"imported_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"`
|
|
MangaType pgtype.Text `db:"manga_type" json:"manga_type"`
|
|
ReadingDirection pgtype.Text `db:"reading_direction" json:"reading_direction"`
|
|
SeriesCount pgtype.Int4 `db:"series_count" json:"series_count"`
|
|
Volume pgtype.Int4 `db:"volume" json:"volume"`
|
|
Imprint pgtype.Text `db:"imprint" json:"imprint"`
|
|
AgeRating pgtype.Text `db:"age_rating" json:"age_rating"`
|
|
WebUrl pgtype.Text `db:"web_url" json:"web_url"`
|
|
StoryArc pgtype.Text `db:"story_arc" json:"story_arc"`
|
|
IsBlackAndWhite pgtype.Bool `db:"is_black_and_white" json:"is_black_and_white"`
|
|
MetadataNotes pgtype.Text `db:"metadata_notes" json:"metadata_notes"`
|
|
CommunityRating pgtype.Float8 `db:"community_rating" json:"community_rating"`
|
|
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
|
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
|
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
|
ContributorsSearch []string `db:"contributors_search" json:"contributors_search"`
|
|
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_2 string `db:"library_type_name_2" json:"library_type_name_2"`
|
|
}
|
|
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
&i.LibraryName,
|
|
&i.LibraryTypeName_2,
|
|
); 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.imported_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.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name
|
|
FROM media_items mi
|
|
JOIN libraries l ON mi.library_id = l.id
|
|
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 []string `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 []string `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"`
|
|
ImportedAt pgtype.Timestamptz `db:"imported_at" json:"imported_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"`
|
|
MangaType pgtype.Text `db:"manga_type" json:"manga_type"`
|
|
ReadingDirection pgtype.Text `db:"reading_direction" json:"reading_direction"`
|
|
SeriesCount pgtype.Int4 `db:"series_count" json:"series_count"`
|
|
Volume pgtype.Int4 `db:"volume" json:"volume"`
|
|
Imprint pgtype.Text `db:"imprint" json:"imprint"`
|
|
AgeRating pgtype.Text `db:"age_rating" json:"age_rating"`
|
|
WebUrl pgtype.Text `db:"web_url" json:"web_url"`
|
|
StoryArc pgtype.Text `db:"story_arc" json:"story_arc"`
|
|
IsBlackAndWhite pgtype.Bool `db:"is_black_and_white" json:"is_black_and_white"`
|
|
MetadataNotes pgtype.Text `db:"metadata_notes" json:"metadata_notes"`
|
|
CommunityRating pgtype.Float8 `db:"community_rating" json:"community_rating"`
|
|
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
|
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
|
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
|
ContributorsSearch []string `db:"contributors_search" json:"contributors_search"`
|
|
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_2 string `db:"library_type_name_2" json:"library_type_name_2"`
|
|
}
|
|
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
&i.LibraryName,
|
|
&i.LibraryTypeName_2,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const ListMediaItemsBySHA256AndLibrary = `-- name: ListMediaItemsBySHA256AndLibrary :many
|
|
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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_sha256 = $1 AND library_id = $2 ORDER BY file_path
|
|
`
|
|
|
|
type ListMediaItemsBySHA256AndLibraryParams struct {
|
|
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
}
|
|
|
|
// List all media items sharing a SHA-256 hash within a library (hash conflict group)
|
|
func (q *Queries) ListMediaItemsBySHA256AndLibrary(ctx context.Context, arg ListMediaItemsBySHA256AndLibraryParams) ([]MediaItems, error) {
|
|
rows, err := q.db.Query(ctx, ListMediaItemsBySHA256AndLibrary, arg.FileSha256, arg.LibraryID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []MediaItems{}
|
|
for rows.Next() {
|
|
var i MediaItems
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const ListMediaItemsMissingHash = `-- name: ListMediaItemsMissingHash :many
|
|
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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_sha256 IS NULL ORDER BY created_at
|
|
`
|
|
|
|
// List media items that have no stored SHA-256 (imported before hashing existed)
|
|
func (q *Queries) ListMediaItemsMissingHash(ctx context.Context) ([]MediaItems, error) {
|
|
rows, err := q.db.Query(ctx, ListMediaItemsMissingHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []MediaItems{}
|
|
for rows.Next() {
|
|
var i MediaItems
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
); 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.imported_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.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name
|
|
FROM media_items mi
|
|
JOIN libraries l ON mi.library_id = l.id
|
|
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 []string `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 []string `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"`
|
|
ImportedAt pgtype.Timestamptz `db:"imported_at" json:"imported_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"`
|
|
MangaType pgtype.Text `db:"manga_type" json:"manga_type"`
|
|
ReadingDirection pgtype.Text `db:"reading_direction" json:"reading_direction"`
|
|
SeriesCount pgtype.Int4 `db:"series_count" json:"series_count"`
|
|
Volume pgtype.Int4 `db:"volume" json:"volume"`
|
|
Imprint pgtype.Text `db:"imprint" json:"imprint"`
|
|
AgeRating pgtype.Text `db:"age_rating" json:"age_rating"`
|
|
WebUrl pgtype.Text `db:"web_url" json:"web_url"`
|
|
StoryArc pgtype.Text `db:"story_arc" json:"story_arc"`
|
|
IsBlackAndWhite pgtype.Bool `db:"is_black_and_white" json:"is_black_and_white"`
|
|
MetadataNotes pgtype.Text `db:"metadata_notes" json:"metadata_notes"`
|
|
CommunityRating pgtype.Float8 `db:"community_rating" json:"community_rating"`
|
|
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
|
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
|
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
|
ContributorsSearch []string `db:"contributors_search" json:"contributors_search"`
|
|
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_2 string `db:"library_type_name_2" json:"library_type_name_2"`
|
|
}
|
|
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
&i.LibraryName,
|
|
&i.LibraryTypeName_2,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const ListPendingHashConflicts = `-- name: ListPendingHashConflicts :many
|
|
SELECT hc.id, hc.library_id, hc.file_sha256, hc.created_at,
|
|
l.name AS library_name,
|
|
COUNT(mi.id) AS item_count
|
|
FROM hash_conflicts hc
|
|
JOIN libraries l ON l.id = hc.library_id
|
|
LEFT JOIN media_items mi ON mi.library_id = hc.library_id AND mi.file_sha256 = hc.file_sha256
|
|
WHERE hc.status = 'pending'
|
|
GROUP BY hc.id, hc.library_id, hc.file_sha256, hc.created_at, l.name
|
|
ORDER BY hc.created_at
|
|
`
|
|
|
|
type ListPendingHashConflictsRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
FileSha256 string `db:"file_sha256" json:"file_sha256"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
LibraryName string `db:"library_name" json:"library_name"`
|
|
ItemCount int64 `db:"item_count" json:"item_count"`
|
|
}
|
|
|
|
func (q *Queries) ListPendingHashConflicts(ctx context.Context) ([]ListPendingHashConflictsRow, error) {
|
|
rows, err := q.db.Query(ctx, ListPendingHashConflicts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListPendingHashConflictsRow{}
|
|
for rows.Next() {
|
|
var i ListPendingHashConflictsRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.LibraryID,
|
|
&i.FileSha256,
|
|
&i.CreatedAt,
|
|
&i.LibraryName,
|
|
&i.ItemCount,
|
|
); 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 ListProcessingIssuesByLibrary = `-- name: ListProcessingIssuesByLibrary :many
|
|
SELECT
|
|
pi.id,
|
|
pi.media_item_id,
|
|
pi.issue_type,
|
|
pi.issue_description,
|
|
pi.severity,
|
|
pi.resolved,
|
|
pi.resolved_at,
|
|
pi.created_at,
|
|
mi.title,
|
|
mi.file_path,
|
|
mi.format_group,
|
|
lt.name as library_type_name
|
|
FROM processing_issues pi
|
|
JOIN media_items mi ON pi.media_item_id = mi.id
|
|
JOIN libraries l ON pi.library_id = l.id
|
|
JOIN library_types lt ON l.library_type_id = lt.id
|
|
WHERE pi.library_id = $1
|
|
AND pi.resolved = false
|
|
ORDER BY
|
|
CASE pi.severity
|
|
WHEN 'error' THEN 1
|
|
WHEN 'warning' THEN 2
|
|
WHEN 'info' THEN 3
|
|
END,
|
|
pi.created_at DESC
|
|
`
|
|
|
|
type ListProcessingIssuesByLibraryRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
IssueType string `db:"issue_type" json:"issue_type"`
|
|
IssueDescription string `db:"issue_description" json:"issue_description"`
|
|
Severity string `db:"severity" json:"severity"`
|
|
Resolved pgtype.Bool `db:"resolved" json:"resolved"`
|
|
ResolvedAt pgtype.Timestamptz `db:"resolved_at" json:"resolved_at"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
Title string `db:"title" json:"title"`
|
|
FilePath string `db:"file_path" json:"file_path"`
|
|
FormatGroup string `db:"format_group" json:"format_group"`
|
|
LibraryTypeName string `db:"library_type_name" json:"library_type_name"`
|
|
}
|
|
|
|
func (q *Queries) ListProcessingIssuesByLibrary(ctx context.Context, libraryID pgtype.UUID) ([]ListProcessingIssuesByLibraryRow, error) {
|
|
rows, err := q.db.Query(ctx, ListProcessingIssuesByLibrary, libraryID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListProcessingIssuesByLibraryRow{}
|
|
for rows.Next() {
|
|
var i ListProcessingIssuesByLibraryRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.IssueType,
|
|
&i.IssueDescription,
|
|
&i.Severity,
|
|
&i.Resolved,
|
|
&i.ResolvedAt,
|
|
&i.CreatedAt,
|
|
&i.Title,
|
|
&i.FilePath,
|
|
&i.FormatGroup,
|
|
&i.LibraryTypeName,
|
|
); 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
|
|
u.id,
|
|
u.email,
|
|
u.username,
|
|
u.theme,
|
|
u.first_name,
|
|
u.last_name,
|
|
u.role,
|
|
u.max_devices,
|
|
u.created_at,
|
|
u.updated_at,
|
|
(SELECT COUNT(*) FROM devices WHERE user_id = u.id) as device_count
|
|
FROM users u
|
|
ORDER BY u.created_at DESC
|
|
`
|
|
|
|
type ListUsersRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Email string `db:"email" json:"email"`
|
|
Username string `db:"username" json:"username"`
|
|
Theme pgtype.Text `db:"theme" json:"theme"`
|
|
FirstName pgtype.Text `db:"first_name" json:"first_name"`
|
|
LastName pgtype.Text `db:"last_name" json:"last_name"`
|
|
Role string `db:"role" json:"role"`
|
|
MaxDevices pgtype.Int4 `db:"max_devices" json:"max_devices"`
|
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
|
DeviceCount int64 `db:"device_count" json:"device_count"`
|
|
}
|
|
|
|
func (q *Queries) ListUsers(ctx context.Context) ([]ListUsersRow, error) {
|
|
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.MaxDevices,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.DeviceCount,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const PurgeExpiredBookmarkTombstones = `-- name: PurgeExpiredBookmarkTombstones :exec
|
|
DELETE FROM media_bookmarks WHERE deleted = TRUE AND deleted_at < $1
|
|
`
|
|
|
|
func (q *Queries) PurgeExpiredBookmarkTombstones(ctx context.Context, deletedAt pgtype.Timestamptz) error {
|
|
_, err := q.db.Exec(ctx, PurgeExpiredBookmarkTombstones, deletedAt)
|
|
return err
|
|
}
|
|
|
|
const PurgeExpiredHighlightTombstones = `-- name: PurgeExpiredHighlightTombstones :exec
|
|
DELETE FROM media_highlights WHERE deleted = TRUE AND deleted_at < $1
|
|
`
|
|
|
|
func (q *Queries) PurgeExpiredHighlightTombstones(ctx context.Context, deletedAt pgtype.Timestamptz) error {
|
|
_, err := q.db.Exec(ctx, PurgeExpiredHighlightTombstones, deletedAt)
|
|
return err
|
|
}
|
|
|
|
const PurgeExpiredNoteTombstones = `-- name: PurgeExpiredNoteTombstones :exec
|
|
DELETE FROM media_notes WHERE deleted = TRUE AND deleted_at < $1
|
|
`
|
|
|
|
func (q *Queries) PurgeExpiredNoteTombstones(ctx context.Context, deletedAt pgtype.Timestamptz) error {
|
|
_, err := q.db.Exec(ctx, PurgeExpiredNoteTombstones, deletedAt)
|
|
return err
|
|
}
|
|
|
|
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 ReassignLibraries = `-- name: ReassignLibraries :exec
|
|
UPDATE libraries SET created_by_admin_id = $2, updated_at = NOW() WHERE created_by_admin_id = $1
|
|
`
|
|
|
|
type ReassignLibrariesParams struct {
|
|
CreatedByAdminID pgtype.UUID `db:"created_by_admin_id" json:"created_by_admin_id"`
|
|
CreatedByAdminID_2 pgtype.UUID `db:"created_by_admin_id_2" json:"created_by_admin_id_2"`
|
|
}
|
|
|
|
func (q *Queries) ReassignLibraries(ctx context.Context, arg ReassignLibrariesParams) error {
|
|
_, err := q.db.Exec(ctx, ReassignLibraries, arg.CreatedByAdminID, arg.CreatedByAdminID_2)
|
|
return err
|
|
}
|
|
|
|
const ReassignMediaItems = `-- name: ReassignMediaItems :exec
|
|
UPDATE media_items SET added_by_admin_id = $2 WHERE added_by_admin_id = $1
|
|
`
|
|
|
|
type ReassignMediaItemsParams struct {
|
|
AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"`
|
|
AddedByAdminID_2 pgtype.UUID `db:"added_by_admin_id_2" json:"added_by_admin_id_2"`
|
|
}
|
|
|
|
func (q *Queries) ReassignMediaItems(ctx context.Context, arg ReassignMediaItemsParams) error {
|
|
_, err := q.db.Exec(ctx, ReassignMediaItems, arg.AddedByAdminID, arg.AddedByAdminID_2)
|
|
return err
|
|
}
|
|
|
|
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 ReparentMediaItemChildren = `-- name: ReparentMediaItemChildren :exec
|
|
SELECT reparent_media_item_children($1::uuid, $2::uuid)
|
|
`
|
|
|
|
type ReparentMediaItemChildrenParams struct {
|
|
Column1 pgtype.UUID `db:"column_1" json:"column_1"`
|
|
Column2 pgtype.UUID `db:"column_2" json:"column_2"`
|
|
}
|
|
|
|
// Re-parent all child rows of p_source onto p_target (defined in schema.sql)
|
|
func (q *Queries) ReparentMediaItemChildren(ctx context.Context, arg ReparentMediaItemChildrenParams) error {
|
|
_, err := q.db.Exec(ctx, ReparentMediaItemChildren, arg.Column1, arg.Column2)
|
|
return err
|
|
}
|
|
|
|
const ResetSystemCollectionMetadata = `-- name: ResetSystemCollectionMetadata :exec
|
|
UPDATE collections
|
|
SET description = $3,
|
|
icon = $4,
|
|
color = $5,
|
|
priority = $6
|
|
WHERE user_id = $1
|
|
AND name = $2
|
|
AND is_system_collection = true
|
|
`
|
|
|
|
type ResetSystemCollectionMetadataParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
Name string `db:"name" json:"name"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
Icon pgtype.Text `db:"icon" json:"icon"`
|
|
Color pgtype.Text `db:"color" json:"color"`
|
|
Priority pgtype.Int4 `db:"priority" json:"priority"`
|
|
}
|
|
|
|
func (q *Queries) ResetSystemCollectionMetadata(ctx context.Context, arg ResetSystemCollectionMetadataParams) error {
|
|
_, err := q.db.Exec(ctx, ResetSystemCollectionMetadata,
|
|
arg.UserID,
|
|
arg.Name,
|
|
arg.Description,
|
|
arg.Icon,
|
|
arg.Color,
|
|
arg.Priority,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const ResolveHashConflict = `-- name: ResolveHashConflict :exec
|
|
UPDATE hash_conflicts
|
|
SET status = 'resolved',
|
|
resolution = $2,
|
|
resolved_by = $3,
|
|
resolved_at = NOW()
|
|
WHERE id = $1
|
|
`
|
|
|
|
type ResolveHashConflictParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Resolution pgtype.Text `db:"resolution" json:"resolution"`
|
|
ResolvedBy pgtype.UUID `db:"resolved_by" json:"resolved_by"`
|
|
}
|
|
|
|
func (q *Queries) ResolveHashConflict(ctx context.Context, arg ResolveHashConflictParams) error {
|
|
_, err := q.db.Exec(ctx, ResolveHashConflict, arg.ID, arg.Resolution, arg.ResolvedBy)
|
|
return err
|
|
}
|
|
|
|
const ResolveProcessingIssue = `-- name: ResolveProcessingIssue :one
|
|
UPDATE processing_issues
|
|
SET resolved = true,
|
|
resolved_at = NOW()
|
|
WHERE id = $1
|
|
AND media_item_id = $2
|
|
RETURNING id, media_item_id, library_id, issue_type, issue_description, severity, resolved, resolved_at, created_at
|
|
`
|
|
|
|
type ResolveProcessingIssueParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
}
|
|
|
|
func (q *Queries) ResolveProcessingIssue(ctx context.Context, arg ResolveProcessingIssueParams) (ProcessingIssues, error) {
|
|
row := q.db.QueryRow(ctx, ResolveProcessingIssue, arg.ID, arg.MediaItemID)
|
|
var i ProcessingIssues
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.LibraryID,
|
|
&i.IssueType,
|
|
&i.IssueDescription,
|
|
&i.Severity,
|
|
&i.Resolved,
|
|
&i.ResolvedAt,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, 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 SearchAuthorValues = `-- name: SearchAuthorValues :many
|
|
SELECT
|
|
mi.author as value,
|
|
COUNT(*) as count,
|
|
word_similarity($1, COALESCE(mi.author, ''))::float8 as score
|
|
FROM media_items mi
|
|
JOIN libraries l ON mi.library_id = l.id
|
|
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $2
|
|
WHERE COALESCE(lv.is_visible, true) = true
|
|
AND mi.library_id = $3
|
|
AND word_similarity($1, COALESCE(mi.author, '')) > 0.3
|
|
AND mi.author IS NOT NULL
|
|
AND mi.author != ''
|
|
GROUP BY mi.author, word_similarity($1, COALESCE(mi.author, ''))::float8
|
|
ORDER BY score DESC, count DESC
|
|
LIMIT $5 OFFSET $4
|
|
`
|
|
|
|
type SearchAuthorValuesParams struct {
|
|
SearchQuery pgtype.Text `db:"search_query" json:"search_query"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Offset pgtype.Int4 `db:"offset" json:"offset"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
type SearchAuthorValuesRow struct {
|
|
Value pgtype.Text `db:"value" json:"value"`
|
|
Count int64 `db:"count" json:"count"`
|
|
Score float64 `db:"score" json:"score"`
|
|
}
|
|
|
|
func (q *Queries) SearchAuthorValues(ctx context.Context, arg SearchAuthorValuesParams) ([]SearchAuthorValuesRow, error) {
|
|
rows, err := q.db.Query(ctx, SearchAuthorValues,
|
|
arg.SearchQuery,
|
|
arg.UserID,
|
|
arg.LibraryID,
|
|
arg.Offset,
|
|
arg.Limit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SearchAuthorValuesRow{}
|
|
for rows.Next() {
|
|
var i SearchAuthorValuesRow
|
|
if err := rows.Scan(&i.Value, &i.Count, &i.Score); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const SearchGenreValues = `-- name: SearchGenreValues :many
|
|
SELECT
|
|
mi.genre as value,
|
|
COUNT(*) as count,
|
|
word_similarity($1, COALESCE(mi.genre, ''))::float8 as score
|
|
FROM media_items mi
|
|
JOIN libraries l ON mi.library_id = l.id
|
|
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $2
|
|
WHERE COALESCE(lv.is_visible, true) = true
|
|
AND mi.library_id = $3
|
|
AND word_similarity($1, COALESCE(mi.genre, '')) > 0.3
|
|
AND mi.genre IS NOT NULL
|
|
AND mi.genre != ''
|
|
GROUP BY mi.genre, word_similarity($1, COALESCE(mi.genre, ''))::float8
|
|
ORDER BY score DESC, count DESC
|
|
LIMIT $5 OFFSET $4
|
|
`
|
|
|
|
type SearchGenreValuesParams struct {
|
|
SearchQuery pgtype.Text `db:"search_query" json:"search_query"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Offset pgtype.Int4 `db:"offset" json:"offset"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
type SearchGenreValuesRow struct {
|
|
Value pgtype.Text `db:"value" json:"value"`
|
|
Count int64 `db:"count" json:"count"`
|
|
Score float64 `db:"score" json:"score"`
|
|
}
|
|
|
|
func (q *Queries) SearchGenreValues(ctx context.Context, arg SearchGenreValuesParams) ([]SearchGenreValuesRow, error) {
|
|
rows, err := q.db.Query(ctx, SearchGenreValues,
|
|
arg.SearchQuery,
|
|
arg.UserID,
|
|
arg.LibraryID,
|
|
arg.Offset,
|
|
arg.Limit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SearchGenreValuesRow{}
|
|
for rows.Next() {
|
|
var i SearchGenreValuesRow
|
|
if err := rows.Scan(&i.Value, &i.Count, &i.Score); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const SearchLanguageValues = `-- name: SearchLanguageValues :many
|
|
SELECT
|
|
mi.language as value,
|
|
COUNT(*) as count,
|
|
word_similarity($1, COALESCE(mi.language, ''))::float8 as score
|
|
FROM media_items mi
|
|
JOIN libraries l ON mi.library_id = l.id
|
|
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $2
|
|
WHERE COALESCE(lv.is_visible, true) = true
|
|
AND mi.library_id = $3
|
|
AND word_similarity($1, COALESCE(mi.language, '')) > 0.3
|
|
AND mi.language IS NOT NULL
|
|
AND mi.language != ''
|
|
GROUP BY mi.language, word_similarity($1, COALESCE(mi.language, ''))::float8
|
|
ORDER BY score DESC, count DESC
|
|
LIMIT $5 OFFSET $4
|
|
`
|
|
|
|
type SearchLanguageValuesParams struct {
|
|
SearchQuery pgtype.Text `db:"search_query" json:"search_query"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Offset pgtype.Int4 `db:"offset" json:"offset"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
type SearchLanguageValuesRow struct {
|
|
Value pgtype.Text `db:"value" json:"value"`
|
|
Count int64 `db:"count" json:"count"`
|
|
Score float64 `db:"score" json:"score"`
|
|
}
|
|
|
|
func (q *Queries) SearchLanguageValues(ctx context.Context, arg SearchLanguageValuesParams) ([]SearchLanguageValuesRow, error) {
|
|
rows, err := q.db.Query(ctx, SearchLanguageValues,
|
|
arg.SearchQuery,
|
|
arg.UserID,
|
|
arg.LibraryID,
|
|
arg.Offset,
|
|
arg.Limit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SearchLanguageValuesRow{}
|
|
for rows.Next() {
|
|
var i SearchLanguageValuesRow
|
|
if err := rows.Scan(&i.Value, &i.Count, &i.Score); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
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.imported_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.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name
|
|
FROM media_items mi
|
|
JOIN libraries l ON mi.library_id = l.id
|
|
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 ($2::uuid IS NULL OR mi.library_id = $2::uuid)
|
|
AND (
|
|
mi.title ILIKE $3 OR
|
|
mi.author ILIKE $3 OR
|
|
mi.series ILIKE $3 OR
|
|
$3 = ANY(mi.tags_search) OR
|
|
$3 = ANY(mi.contributors_search)
|
|
)
|
|
ORDER BY
|
|
CASE
|
|
WHEN mi.title ILIKE $3 THEN 1
|
|
WHEN mi.author ILIKE $3 THEN 2
|
|
WHEN mi.series ILIKE $3 THEN 3
|
|
WHEN $3 = ANY(mi.tags_search) THEN 4
|
|
ELSE 5
|
|
END,
|
|
mi.title ASC
|
|
LIMIT $5 OFFSET $4
|
|
`
|
|
|
|
type SearchMediaItemsParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_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 []string `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 []string `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"`
|
|
ImportedAt pgtype.Timestamptz `db:"imported_at" json:"imported_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"`
|
|
MangaType pgtype.Text `db:"manga_type" json:"manga_type"`
|
|
ReadingDirection pgtype.Text `db:"reading_direction" json:"reading_direction"`
|
|
SeriesCount pgtype.Int4 `db:"series_count" json:"series_count"`
|
|
Volume pgtype.Int4 `db:"volume" json:"volume"`
|
|
Imprint pgtype.Text `db:"imprint" json:"imprint"`
|
|
AgeRating pgtype.Text `db:"age_rating" json:"age_rating"`
|
|
WebUrl pgtype.Text `db:"web_url" json:"web_url"`
|
|
StoryArc pgtype.Text `db:"story_arc" json:"story_arc"`
|
|
IsBlackAndWhite pgtype.Bool `db:"is_black_and_white" json:"is_black_and_white"`
|
|
MetadataNotes pgtype.Text `db:"metadata_notes" json:"metadata_notes"`
|
|
CommunityRating pgtype.Float8 `db:"community_rating" json:"community_rating"`
|
|
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
|
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
|
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
|
ContributorsSearch []string `db:"contributors_search" json:"contributors_search"`
|
|
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_2 string `db:"library_type_name_2" json:"library_type_name_2"`
|
|
}
|
|
|
|
// 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.LibraryID,
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
&i.LibraryName,
|
|
&i.LibraryTypeName_2,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const SearchMediaItemsUnified = `-- name: SearchMediaItemsUnified :many
|
|
SELECT
|
|
mi.id,
|
|
mi.library_id,
|
|
mi.title,
|
|
mi.author,
|
|
mi.isbn,
|
|
mi.description,
|
|
mi.file_path,
|
|
mi.file_size,
|
|
mi.mime_type,
|
|
mi.cover_image_path,
|
|
mi.series,
|
|
mi.series_number,
|
|
mi.tags,
|
|
mi.asin,
|
|
mi.date_published,
|
|
mi.publisher,
|
|
mi.contributors,
|
|
mi.language,
|
|
mi.edition,
|
|
mi.page_count,
|
|
mi.genre,
|
|
mi.copyright_year,
|
|
mi.goodreads_id,
|
|
mi.openlibrary_id,
|
|
mi.google_books_id,
|
|
mi.added_by_admin_id,
|
|
mi.created_at,
|
|
mi.updated_at,
|
|
mi.format_group,
|
|
mi.format_mimetype,
|
|
mi.is_reflowable,
|
|
mi.has_fixed_layout,
|
|
mi.total_characters,
|
|
mi.chapter_count,
|
|
mi.entitlement_id,
|
|
mi.revision_number,
|
|
mi.kobo_content_id,
|
|
mi.kobo_metadata,
|
|
mi.tags_search,
|
|
mi.contributors_search,
|
|
mi.file_sha256,
|
|
mi.opf_identifier,
|
|
mi.opf_uuid,
|
|
mi.hash_confidence,
|
|
l.name as library_name,
|
|
lt.name as library_type_name
|
|
FROM media_items mi
|
|
JOIN libraries l ON mi.library_id = l.id
|
|
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 ($2::uuid IS NULL
|
|
OR mi.library_id = $2::uuid)
|
|
-- Fuzzy author filter
|
|
AND ($3 = '' OR word_similarity($3, COALESCE(mi.author, '')) > 0.3)
|
|
-- Fuzzy series filter
|
|
AND ($4 = '' OR word_similarity($4, COALESCE(mi.series, '')) > 0.3)
|
|
-- Fuzzy genre filter
|
|
AND ($5 = '' OR word_similarity($5, COALESCE(mi.genre, '')) > 0.3)
|
|
-- Fuzzy language filter
|
|
AND ($6 = '' OR word_similarity($6, COALESCE(mi.language, '')) > 0.3)
|
|
-- Tags filter (NEW - fuzzy match against tags array)
|
|
AND ($7 = '' OR EXISTS (
|
|
SELECT 1 FROM unnest(mi.tags_search) AS tag
|
|
WHERE word_similarity($7, tag) > 0.3
|
|
))
|
|
-- Year range (exact) - prioritize date_published, fallback to copyright_year
|
|
AND (
|
|
$8 = 0 OR
|
|
EXTRACT(YEAR FROM mi.date_published) >= $8 OR
|
|
(mi.date_published IS NULL AND mi.copyright_year >= $8)
|
|
)
|
|
AND (
|
|
$9 = 0 OR
|
|
EXTRACT(YEAR FROM mi.date_published) <= $9 OR
|
|
(mi.date_published IS NULL AND mi.copyright_year <= $9)
|
|
)
|
|
-- Boolean (exact)
|
|
AND (
|
|
$10::bool IS NULL OR -- Not specified = show all
|
|
($10::bool IS TRUE AND mi.cover_image_path IS NOT NULL) OR
|
|
($10::bool IS FALSE AND mi.cover_image_path IS NULL)
|
|
)
|
|
-- Search query (fuzzy or exact based on quotes)
|
|
AND (
|
|
$11 = '' OR
|
|
-- Fuzzy search (default)
|
|
$12 = false AND (
|
|
word_similarity($11, mi.title) > 0.3 OR
|
|
word_similarity($11, COALESCE(mi.author, '')) > 0.3 OR
|
|
word_similarity($11, COALESCE(mi.series, '')) > 0.3 OR
|
|
EXISTS (
|
|
SELECT 1 FROM unnest(mi.tags_search) AS tag
|
|
WHERE word_similarity($11, tag) > 0.3
|
|
LIMIT 1
|
|
) OR
|
|
EXISTS (
|
|
SELECT 1 FROM unnest(mi.contributors_search) AS contributor
|
|
WHERE word_similarity($11, contributor) > 0.3
|
|
LIMIT 1
|
|
)
|
|
) OR
|
|
-- Exact search (with quotes)
|
|
-- Keeping old pattern commented out in case we want wildcard exact back. See search.go line 62
|
|
-- sqlc.narg('is_exact_search') = true AND (
|
|
-- mi.title ILIKE sqlc.narg('search_pattern') OR
|
|
-- mi.author ILIKE sqlc.narg('search_pattern') OR
|
|
-- mi.series ILIKE sqlc.narg('search_pattern') OR
|
|
-- sqlc.narg('search_pattern') = ANY(mi.tags_search) OR
|
|
-- sqlc.narg('search_pattern') = ANY(mi.contributors_search)
|
|
-- )
|
|
-- Exact search (with quotes) - true exact match, not substring
|
|
$12 = true AND (
|
|
mi.title = $11 OR
|
|
COALESCE(mi.author, '') = $11 OR
|
|
COALESCE(mi.series, '') = $11 OR
|
|
$11 = ANY(mi.tags_search) OR
|
|
$11 = ANY(mi.contributors_search)
|
|
)
|
|
)
|
|
ORDER BY
|
|
-- Primary sort: relevance score when searching
|
|
CASE
|
|
WHEN $11 != '' THEN
|
|
GREATEST(
|
|
CASE WHEN $12 = false THEN
|
|
word_similarity($11, mi.title)
|
|
ELSE 0 END,
|
|
CASE WHEN $12 = false THEN
|
|
word_similarity($11, COALESCE(mi.author, ''))
|
|
ELSE 0 END,
|
|
word_similarity($3, COALESCE(mi.author, '')),
|
|
word_similarity($5, COALESCE(mi.genre, '')),
|
|
(SELECT MAX(word_similarity($7, tag))
|
|
FROM unnest(mi.tags_search) AS tag)
|
|
)
|
|
ELSE 0
|
|
END DESC,
|
|
-- Secondary sort: user-specified sort parameter
|
|
CASE
|
|
WHEN $13 = 'title ASC' THEN mi.title
|
|
ELSE ''
|
|
END ASC,
|
|
CASE
|
|
WHEN $13 = 'title DESC' THEN mi.title
|
|
ELSE ''
|
|
END DESC,
|
|
CASE
|
|
WHEN $13 = 'author ASC' THEN COALESCE(mi.author, '')
|
|
ELSE ''
|
|
END ASC,
|
|
CASE
|
|
WHEN $13 = 'author DESC' THEN COALESCE(mi.author, '')
|
|
ELSE ''
|
|
END DESC,
|
|
CASE
|
|
WHEN $13 = 'created_at ASC' THEN mi.created_at
|
|
ELSE '1970-01-01'::timestamp
|
|
END ASC,
|
|
CASE
|
|
WHEN $13 = 'created_at DESC' THEN mi.created_at
|
|
ELSE '1970-01-01'::timestamp
|
|
END DESC,
|
|
CASE
|
|
WHEN $13 = 'page_count ASC' THEN COALESCE(mi.page_count::text, '0')
|
|
ELSE ''
|
|
END ASC,
|
|
CASE
|
|
WHEN $13 = 'page_count DESC' THEN COALESCE(mi.page_count::text, '0')
|
|
ELSE ''
|
|
END DESC,
|
|
-- Tertiary sort: title (default fallback)
|
|
mi.title ASC
|
|
LIMIT $15 OFFSET $14
|
|
`
|
|
|
|
type SearchMediaItemsUnifiedParams 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"`
|
|
TagsFilter interface{} `db:"tags_filter" json:"tags_filter"`
|
|
YearMin interface{} `db:"year_min" json:"year_min"`
|
|
YearMax interface{} `db:"year_max" json:"year_max"`
|
|
HasCover pgtype.Bool `db:"has_cover" json:"has_cover"`
|
|
SearchQuery interface{} `db:"search_query" json:"search_query"`
|
|
IsExactSearch interface{} `db:"is_exact_search" json:"is_exact_search"`
|
|
Sort interface{} `db:"sort" json:"sort"`
|
|
Offset pgtype.Int4 `db:"offset" json:"offset"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
type SearchMediaItemsUnifiedRow 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 []string `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 []string `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"`
|
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
|
ContributorsSearch []string `db:"contributors_search" json:"contributors_search"`
|
|
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) SearchMediaItemsUnified(ctx context.Context, arg SearchMediaItemsUnifiedParams) ([]SearchMediaItemsUnifiedRow, error) {
|
|
rows, err := q.db.Query(ctx, SearchMediaItemsUnified,
|
|
arg.UserID,
|
|
arg.LibraryID,
|
|
arg.AuthorFilter,
|
|
arg.SeriesFilter,
|
|
arg.GenreFilter,
|
|
arg.LanguageFilter,
|
|
arg.TagsFilter,
|
|
arg.YearMin,
|
|
arg.YearMax,
|
|
arg.HasCover,
|
|
arg.SearchQuery,
|
|
arg.IsExactSearch,
|
|
arg.Sort,
|
|
arg.Offset,
|
|
arg.Limit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SearchMediaItemsUnifiedRow{}
|
|
for rows.Next() {
|
|
var i SearchMediaItemsUnifiedRow
|
|
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.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&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 SearchSeriesValues = `-- name: SearchSeriesValues :many
|
|
SELECT
|
|
mi.series as value,
|
|
COUNT(*) as count,
|
|
word_similarity($1, COALESCE(mi.series, ''))::float8 as score
|
|
FROM media_items mi
|
|
JOIN libraries l ON mi.library_id = l.id
|
|
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $2
|
|
WHERE COALESCE(lv.is_visible, true) = true
|
|
AND mi.library_id = $3
|
|
AND word_similarity($1, COALESCE(mi.series, '')) > 0.3
|
|
AND mi.series IS NOT NULL
|
|
AND mi.series != ''
|
|
GROUP BY mi.series, word_similarity($1, COALESCE(mi.series, ''))::float8
|
|
ORDER BY score DESC, count DESC
|
|
LIMIT $5 OFFSET $4
|
|
`
|
|
|
|
type SearchSeriesValuesParams struct {
|
|
SearchQuery pgtype.Text `db:"search_query" json:"search_query"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Offset pgtype.Int4 `db:"offset" json:"offset"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
type SearchSeriesValuesRow struct {
|
|
Value pgtype.Text `db:"value" json:"value"`
|
|
Count int64 `db:"count" json:"count"`
|
|
Score float64 `db:"score" json:"score"`
|
|
}
|
|
|
|
func (q *Queries) SearchSeriesValues(ctx context.Context, arg SearchSeriesValuesParams) ([]SearchSeriesValuesRow, error) {
|
|
rows, err := q.db.Query(ctx, SearchSeriesValues,
|
|
arg.SearchQuery,
|
|
arg.UserID,
|
|
arg.LibraryID,
|
|
arg.Offset,
|
|
arg.Limit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SearchSeriesValuesRow{}
|
|
for rows.Next() {
|
|
var i SearchSeriesValuesRow
|
|
if err := rows.Scan(&i.Value, &i.Count, &i.Score); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const SearchTagsValues = `-- name: SearchTagsValues :many
|
|
SELECT
|
|
tag::TEXT as value,
|
|
COUNT(*) as count,
|
|
word_similarity($1, tag)::float8 as score
|
|
FROM media_items mi
|
|
JOIN libraries l ON mi.library_id = l.id
|
|
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $2
|
|
CROSS JOIN LATERAL unnest(mi.tags_search) AS tag
|
|
WHERE COALESCE(lv.is_visible, true) = true
|
|
AND ($3::uuid IS NULL OR mi.library_id = $3::uuid)
|
|
AND tag IS NOT NULL
|
|
AND word_similarity($1, tag) > 0.3
|
|
GROUP BY tag, word_similarity($1, tag)::float8
|
|
ORDER BY word_similarity($1, tag)::float8 DESC, COUNT DESC
|
|
LIMIT $5 OFFSET $4
|
|
`
|
|
|
|
type SearchTagsValuesParams struct {
|
|
SearchQuery pgtype.Text `db:"search_query" json:"search_query"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
Offset pgtype.Int4 `db:"offset" json:"offset"`
|
|
Limit pgtype.Int4 `db:"limit" json:"limit"`
|
|
}
|
|
|
|
type SearchTagsValuesRow struct {
|
|
Value string `db:"value" json:"value"`
|
|
Count int64 `db:"count" json:"count"`
|
|
Score float64 `db:"score" json:"score"`
|
|
}
|
|
|
|
func (q *Queries) SearchTagsValues(ctx context.Context, arg SearchTagsValuesParams) ([]SearchTagsValuesRow, error) {
|
|
rows, err := q.db.Query(ctx, SearchTagsValues,
|
|
arg.SearchQuery,
|
|
arg.UserID,
|
|
arg.LibraryID,
|
|
arg.Offset,
|
|
arg.Limit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SearchTagsValuesRow{}
|
|
for rows.Next() {
|
|
var i SearchTagsValuesRow
|
|
if err := rows.Scan(&i.Value, &i.Count, &i.Score); 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 SyncLibraryTypeExtensions = `-- name: SyncLibraryTypeExtensions :exec
|
|
UPDATE library_types SET allowed_extensions = $2 WHERE name = $1
|
|
`
|
|
|
|
type SyncLibraryTypeExtensionsParams struct {
|
|
Name string `db:"name" json:"name"`
|
|
AllowedExtensions []string `db:"allowed_extensions" json:"allowed_extensions"`
|
|
}
|
|
|
|
func (q *Queries) SyncLibraryTypeExtensions(ctx context.Context, arg SyncLibraryTypeExtensionsParams) error {
|
|
_, err := q.db.Exec(ctx, SyncLibraryTypeExtensions, arg.Name, arg.AllowedExtensions)
|
|
return err
|
|
}
|
|
|
|
const TombstoneMediaBookmarkByDedupKey = `-- name: TombstoneMediaBookmarkByDedupKey :exec
|
|
UPDATE media_bookmarks SET
|
|
deleted = TRUE,
|
|
deleted_at = NOW(),
|
|
last_modified_at = NOW()
|
|
WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3 AND deleted = FALSE
|
|
`
|
|
|
|
type TombstoneMediaBookmarkByDedupKeyParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"`
|
|
}
|
|
|
|
func (q *Queries) TombstoneMediaBookmarkByDedupKey(ctx context.Context, arg TombstoneMediaBookmarkByDedupKeyParams) error {
|
|
_, err := q.db.Exec(ctx, TombstoneMediaBookmarkByDedupKey, arg.UserID, arg.MediaItemID, arg.DedupKey)
|
|
return err
|
|
}
|
|
|
|
const TombstoneMediaBookmarkByID = `-- name: TombstoneMediaBookmarkByID :exec
|
|
UPDATE media_bookmarks SET
|
|
deleted = TRUE,
|
|
deleted_at = NOW(),
|
|
last_modified_at = NOW()
|
|
WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) TombstoneMediaBookmarkByID(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, TombstoneMediaBookmarkByID, id)
|
|
return err
|
|
}
|
|
|
|
const TombstoneMediaHighlightByDedupKey = `-- name: TombstoneMediaHighlightByDedupKey :exec
|
|
UPDATE media_highlights SET
|
|
deleted = TRUE,
|
|
deleted_at = NOW(),
|
|
last_modified_at = NOW()
|
|
WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3 AND deleted = FALSE
|
|
`
|
|
|
|
type TombstoneMediaHighlightByDedupKeyParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"`
|
|
}
|
|
|
|
func (q *Queries) TombstoneMediaHighlightByDedupKey(ctx context.Context, arg TombstoneMediaHighlightByDedupKeyParams) error {
|
|
_, err := q.db.Exec(ctx, TombstoneMediaHighlightByDedupKey, arg.UserID, arg.MediaItemID, arg.DedupKey)
|
|
return err
|
|
}
|
|
|
|
const TombstoneMediaHighlightByID = `-- name: TombstoneMediaHighlightByID :exec
|
|
UPDATE media_highlights SET
|
|
deleted = TRUE,
|
|
deleted_at = NOW(),
|
|
last_modified_at = NOW()
|
|
WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) TombstoneMediaHighlightByID(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, TombstoneMediaHighlightByID, id)
|
|
return err
|
|
}
|
|
|
|
const TombstoneMediaNoteByDedupKey = `-- name: TombstoneMediaNoteByDedupKey :exec
|
|
UPDATE media_notes SET
|
|
deleted = TRUE,
|
|
deleted_at = NOW(),
|
|
last_modified_at = NOW()
|
|
WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3 AND deleted = FALSE
|
|
`
|
|
|
|
type TombstoneMediaNoteByDedupKeyParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"`
|
|
}
|
|
|
|
func (q *Queries) TombstoneMediaNoteByDedupKey(ctx context.Context, arg TombstoneMediaNoteByDedupKeyParams) error {
|
|
_, err := q.db.Exec(ctx, TombstoneMediaNoteByDedupKey, arg.UserID, arg.MediaItemID, arg.DedupKey)
|
|
return err
|
|
}
|
|
|
|
const TombstoneMediaNoteByID = `-- name: TombstoneMediaNoteByID :exec
|
|
UPDATE media_notes SET
|
|
deleted = TRUE,
|
|
deleted_at = NOW(),
|
|
last_modified_at = NOW()
|
|
WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) TombstoneMediaNoteByID(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, TombstoneMediaNoteByID, id)
|
|
return 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, show_on_dashboard, query_type, priority, is_system_collection, 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.ShowOnDashboard,
|
|
&i.QueryType,
|
|
&i.Priority,
|
|
&i.IsSystemCollection,
|
|
&i.CreatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateDashboardPreferences = `-- name: UpdateDashboardPreferences :one
|
|
UPDATE user_dashboard_preferences
|
|
SET hidden_collections = $2,
|
|
collection_order = $3,
|
|
items_per_section = $4,
|
|
updated_at = NOW()
|
|
WHERE user_id = $1 AND library_id = $5
|
|
RETURNING id, user_id, library_id, hidden_collections, collection_order, items_per_section, created_at, updated_at
|
|
`
|
|
|
|
type UpdateDashboardPreferencesParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
HiddenCollections []string `db:"hidden_collections" json:"hidden_collections"`
|
|
CollectionOrder []string `db:"collection_order" json:"collection_order"`
|
|
ItemsPerSection pgtype.Int4 `db:"items_per_section" json:"items_per_section"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
}
|
|
|
|
func (q *Queries) UpdateDashboardPreferences(ctx context.Context, arg UpdateDashboardPreferencesParams) (UserDashboardPreferences, error) {
|
|
row := q.db.QueryRow(ctx, UpdateDashboardPreferences,
|
|
arg.UserID,
|
|
arg.HiddenCollections,
|
|
arg.CollectionOrder,
|
|
arg.ItemsPerSection,
|
|
arg.LibraryID,
|
|
)
|
|
var i UserDashboardPreferences
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.LibraryID,
|
|
&i.HiddenCollections,
|
|
&i.CollectionOrder,
|
|
&i.ItemsPerSection,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
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 UpdateDeviceAuthToken = `-- name: UpdateDeviceAuthToken :one
|
|
UPDATE devices
|
|
SET
|
|
auth_token = $2,
|
|
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 UpdateDeviceAuthTokenParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
AuthToken string `db:"auth_token" json:"auth_token"`
|
|
}
|
|
|
|
func (q *Queries) UpdateDeviceAuthToken(ctx context.Context, arg UpdateDeviceAuthTokenParams) (Devices, error) {
|
|
row := q.db.QueryRow(ctx, UpdateDeviceAuthToken, arg.ID, arg.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 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 UpdateDictionaryAccessed = `-- name: UpdateDictionaryAccessed :one
|
|
UPDATE dictionary_cache
|
|
SET accessed_at = NOW()
|
|
WHERE word = $1
|
|
RETURNING id, word, definition, part_of_speech, example, etymology, created_at, accessed_at
|
|
`
|
|
|
|
func (q *Queries) UpdateDictionaryAccessed(ctx context.Context, word string) (DictionaryCache, error) {
|
|
row := q.db.QueryRow(ctx, UpdateDictionaryAccessed, word)
|
|
var i DictionaryCache
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Word,
|
|
&i.Definition,
|
|
&i.PartOfSpeech,
|
|
&i.Example,
|
|
&i.Etymology,
|
|
&i.CreatedAt,
|
|
&i.AccessedAt,
|
|
)
|
|
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 UpdateMediaBookmark = `-- name: UpdateMediaBookmark :one
|
|
UPDATE media_bookmarks
|
|
SET
|
|
title = $2,
|
|
notes = $3,
|
|
position = $4,
|
|
updated_at = NOW()
|
|
WHERE id = $1 AND user_id = $5
|
|
RETURNING id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at, dedup_key, last_modified_at, last_modified_source, device_sync_data, percentage_location, epubcfi_location, chapter_reference, deleted, deleted_at
|
|
`
|
|
|
|
type UpdateMediaBookmarkParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Title string `db:"title" json:"title"`
|
|
Notes pgtype.Text `db:"notes" json:"notes"`
|
|
Position pgtype.Text `db:"position" json:"position"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
func (q *Queries) UpdateMediaBookmark(ctx context.Context, arg UpdateMediaBookmarkParams) (MediaBookmarks, error) {
|
|
row := q.db.QueryRow(ctx, UpdateMediaBookmark,
|
|
arg.ID,
|
|
arg.Title,
|
|
arg.Notes,
|
|
arg.Position,
|
|
arg.UserID,
|
|
)
|
|
var i MediaBookmarks
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.PageNumber,
|
|
&i.ChapterNumber,
|
|
&i.CfiPosition,
|
|
&i.Title,
|
|
&i.Position,
|
|
&i.Notes,
|
|
&i.CreatedAt,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.DeviceSyncData,
|
|
&i.PercentageLocation,
|
|
&i.EpubcfiLocation,
|
|
&i.ChapterReference,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateMediaBookmarkForSync = `-- name: UpdateMediaBookmarkForSync :one
|
|
UPDATE media_bookmarks SET
|
|
page_number = $2,
|
|
chapter_number = $3,
|
|
cfi_position = $4,
|
|
title = $5,
|
|
position = $6,
|
|
notes = $7,
|
|
percentage_location = $8,
|
|
epubcfi_location = $9,
|
|
chapter_reference = $10,
|
|
last_modified_at = $11,
|
|
last_modified_source = $12,
|
|
device_sync_data = $13,
|
|
created_at = created_at
|
|
WHERE id = $1
|
|
RETURNING id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at, dedup_key, last_modified_at, last_modified_source, device_sync_data, percentage_location, epubcfi_location, chapter_reference, deleted, deleted_at
|
|
`
|
|
|
|
type UpdateMediaBookmarkForSyncParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
PageNumber pgtype.Int4 `db:"page_number" json:"page_number"`
|
|
ChapterNumber pgtype.Int4 `db:"chapter_number" json:"chapter_number"`
|
|
CfiPosition pgtype.Text `db:"cfi_position" json:"cfi_position"`
|
|
Title string `db:"title" json:"title"`
|
|
Position pgtype.Text `db:"position" json:"position"`
|
|
Notes pgtype.Text `db:"notes" json:"notes"`
|
|
PercentageLocation pgtype.Float8 `db:"percentage_location" json:"percentage_location"`
|
|
EpubcfiLocation pgtype.Text `db:"epubcfi_location" json:"epubcfi_location"`
|
|
ChapterReference pgtype.Int4 `db:"chapter_reference" json:"chapter_reference"`
|
|
LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"`
|
|
LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"`
|
|
DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"`
|
|
}
|
|
|
|
func (q *Queries) UpdateMediaBookmarkForSync(ctx context.Context, arg UpdateMediaBookmarkForSyncParams) (MediaBookmarks, error) {
|
|
row := q.db.QueryRow(ctx, UpdateMediaBookmarkForSync,
|
|
arg.ID,
|
|
arg.PageNumber,
|
|
arg.ChapterNumber,
|
|
arg.CfiPosition,
|
|
arg.Title,
|
|
arg.Position,
|
|
arg.Notes,
|
|
arg.PercentageLocation,
|
|
arg.EpubcfiLocation,
|
|
arg.ChapterReference,
|
|
arg.LastModifiedAt,
|
|
arg.LastModifiedSource,
|
|
arg.DeviceSyncData,
|
|
)
|
|
var i MediaBookmarks
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.UserID,
|
|
&i.PageNumber,
|
|
&i.ChapterNumber,
|
|
&i.CfiPosition,
|
|
&i.Title,
|
|
&i.Position,
|
|
&i.Notes,
|
|
&i.CreatedAt,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.DeviceSyncData,
|
|
&i.PercentageLocation,
|
|
&i.EpubcfiLocation,
|
|
&i.ChapterReference,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
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, dedup_key, last_modified_at, last_modified_source, note_text, deleted, deleted_at
|
|
`
|
|
|
|
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,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.NoteText,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateMediaHighlightForSync = `-- name: UpdateMediaHighlightForSync :one
|
|
UPDATE media_highlights SET
|
|
selection_text = $2,
|
|
start_position = $3,
|
|
end_position = $4,
|
|
color = $5,
|
|
note_text = $6,
|
|
percentage_start = $7,
|
|
percentage_end = $8,
|
|
epubcfi_start = $9,
|
|
epubcfi_end = $10,
|
|
chapter_reference = $11,
|
|
last_modified_at = $12,
|
|
last_modified_source = $13,
|
|
device_sync_data = $14,
|
|
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, dedup_key, last_modified_at, last_modified_source, note_text, deleted, deleted_at
|
|
`
|
|
|
|
type UpdateMediaHighlightForSyncParams 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"`
|
|
NoteText pgtype.Text `db:"note_text" json:"note_text"`
|
|
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"`
|
|
ChapterReference pgtype.Int4 `db:"chapter_reference" json:"chapter_reference"`
|
|
LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"`
|
|
LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"`
|
|
DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"`
|
|
}
|
|
|
|
func (q *Queries) UpdateMediaHighlightForSync(ctx context.Context, arg UpdateMediaHighlightForSyncParams) (MediaHighlights, error) {
|
|
row := q.db.QueryRow(ctx, UpdateMediaHighlightForSync,
|
|
arg.ID,
|
|
arg.SelectionText,
|
|
arg.StartPosition,
|
|
arg.EndPosition,
|
|
arg.Color,
|
|
arg.NoteText,
|
|
arg.PercentageStart,
|
|
arg.PercentageEnd,
|
|
arg.EpubcfiStart,
|
|
arg.EpubcfiEnd,
|
|
arg.ChapterReference,
|
|
arg.LastModifiedAt,
|
|
arg.LastModifiedSource,
|
|
arg.DeviceSyncData,
|
|
)
|
|
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,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.NoteText,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
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,
|
|
tags_search = $10,
|
|
asin = $11,
|
|
date_published = $12,
|
|
publisher = $13,
|
|
contributors = $14,
|
|
contributors_search = $15,
|
|
language = $16,
|
|
edition = $17,
|
|
page_count = $18,
|
|
genre = $19,
|
|
copyright_year = $20,
|
|
goodreads_id = $21,
|
|
openlibrary_id = $22,
|
|
google_books_id = $23,
|
|
manga_type = $24,
|
|
reading_direction = $25,
|
|
series_count = $26,
|
|
volume = $27,
|
|
imprint = $28,
|
|
age_rating = $29,
|
|
web_url = $30,
|
|
metadata_notes = $31,
|
|
community_rating = $32,
|
|
story_arc = $33,
|
|
is_black_and_white = $34,
|
|
alternate_info = $35,
|
|
scan_information = $36,
|
|
summary = $37,
|
|
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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, 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 []string `db:"tags" json:"tags"`
|
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
|
Asin pgtype.Text `db:"asin" json:"asin"`
|
|
DatePublished pgtype.Date `db:"date_published" json:"date_published"`
|
|
Publisher pgtype.Text `db:"publisher" json:"publisher"`
|
|
Contributors []string `db:"contributors" json:"contributors"`
|
|
ContributorsSearch []string `db:"contributors_search" json:"contributors_search"`
|
|
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"`
|
|
MangaType pgtype.Text `db:"manga_type" json:"manga_type"`
|
|
ReadingDirection pgtype.Text `db:"reading_direction" json:"reading_direction"`
|
|
SeriesCount pgtype.Int4 `db:"series_count" json:"series_count"`
|
|
Volume pgtype.Int4 `db:"volume" json:"volume"`
|
|
Imprint pgtype.Text `db:"imprint" json:"imprint"`
|
|
AgeRating pgtype.Text `db:"age_rating" json:"age_rating"`
|
|
WebUrl pgtype.Text `db:"web_url" json:"web_url"`
|
|
MetadataNotes pgtype.Text `db:"metadata_notes" json:"metadata_notes"`
|
|
CommunityRating pgtype.Float8 `db:"community_rating" json:"community_rating"`
|
|
StoryArc pgtype.Text `db:"story_arc" json:"story_arc"`
|
|
IsBlackAndWhite pgtype.Bool `db:"is_black_and_white" json:"is_black_and_white"`
|
|
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
|
}
|
|
|
|
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.TagsSearch,
|
|
arg.Asin,
|
|
arg.DatePublished,
|
|
arg.Publisher,
|
|
arg.Contributors,
|
|
arg.ContributorsSearch,
|
|
arg.Language,
|
|
arg.Edition,
|
|
arg.PageCount,
|
|
arg.Genre,
|
|
arg.CopyrightYear,
|
|
arg.GoodreadsID,
|
|
arg.OpenlibraryID,
|
|
arg.GoogleBooksID,
|
|
arg.MangaType,
|
|
arg.ReadingDirection,
|
|
arg.SeriesCount,
|
|
arg.Volume,
|
|
arg.Imprint,
|
|
arg.AgeRating,
|
|
arg.WebUrl,
|
|
arg.MetadataNotes,
|
|
arg.CommunityRating,
|
|
arg.StoryArc,
|
|
arg.IsBlackAndWhite,
|
|
arg.AlternateInfo,
|
|
arg.ScanInformation,
|
|
arg.Summary,
|
|
)
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&i.FileSha256,
|
|
&i.OpfIdentifier,
|
|
&i.OpfUuid,
|
|
&i.HashConfidence,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateMediaItemChapterMetadata = `-- name: UpdateMediaItemChapterMetadata :one
|
|
UPDATE media_items
|
|
SET chapter_metadata = $2, 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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence
|
|
`
|
|
|
|
type UpdateMediaItemChapterMetadataParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
|
}
|
|
|
|
func (q *Queries) UpdateMediaItemChapterMetadata(ctx context.Context, arg UpdateMediaItemChapterMetadataParams) (MediaItems, error) {
|
|
row := q.db.QueryRow(ctx, UpdateMediaItemChapterMetadata, arg.ID, arg.ChapterMetadata)
|
|
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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&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"`
|
|
}
|
|
|
|
// ============================================
|
|
// FORMAT DETECTION & PROGRESS
|
|
// ============================================
|
|
// 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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, 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
|
|
// ============================================
|
|
// UNIVERSAL BOOK IDENTIFIERS
|
|
// ============================================
|
|
// 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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&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, imported_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, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, 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.ImportedAt,
|
|
&i.UpdatedAt,
|
|
&i.FormatGroup,
|
|
&i.FormatMimetype,
|
|
&i.IsReflowable,
|
|
&i.HasFixedLayout,
|
|
&i.TotalCharacters,
|
|
&i.ChapterCount,
|
|
&i.EntitlementID,
|
|
&i.RevisionNumber,
|
|
&i.KoboContentID,
|
|
&i.KoboMetadata,
|
|
&i.MangaType,
|
|
&i.ReadingDirection,
|
|
&i.SeriesCount,
|
|
&i.Volume,
|
|
&i.Imprint,
|
|
&i.AgeRating,
|
|
&i.WebUrl,
|
|
&i.StoryArc,
|
|
&i.IsBlackAndWhite,
|
|
&i.MetadataNotes,
|
|
&i.CommunityRating,
|
|
&i.AlternateInfo,
|
|
&i.ScanInformation,
|
|
&i.Summary,
|
|
&i.ChapterMetadata,
|
|
&i.LibraryTypeName,
|
|
&i.TagsSearch,
|
|
&i.ContributorsSearch,
|
|
&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, dedup_key, last_modified_at, last_modified_source, deleted, deleted_at
|
|
`
|
|
|
|
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,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateMediaNoteForSync = `-- name: UpdateMediaNoteForSync :one
|
|
UPDATE media_notes SET
|
|
content = $2,
|
|
position = $3,
|
|
percentage_location = $4,
|
|
character_start = $5,
|
|
character_end = $6,
|
|
epubcfi_location = $7,
|
|
chapter_reference = $8,
|
|
paragraph_reference = $9,
|
|
last_modified_at = $10,
|
|
last_modified_source = $11,
|
|
device_sync_data = $12,
|
|
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, dedup_key, last_modified_at, last_modified_source, deleted, deleted_at
|
|
`
|
|
|
|
type UpdateMediaNoteForSyncParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Content string `db:"content" json:"content"`
|
|
Position pgtype.Text `db:"position" json:"position"`
|
|
PercentageLocation pgtype.Float8 `db:"percentage_location" json:"percentage_location"`
|
|
CharacterStart pgtype.Int4 `db:"character_start" json:"character_start"`
|
|
CharacterEnd pgtype.Int4 `db:"character_end" json:"character_end"`
|
|
EpubcfiLocation pgtype.Text `db:"epubcfi_location" json:"epubcfi_location"`
|
|
ChapterReference pgtype.Int4 `db:"chapter_reference" json:"chapter_reference"`
|
|
ParagraphReference pgtype.Int4 `db:"paragraph_reference" json:"paragraph_reference"`
|
|
LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"`
|
|
LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"`
|
|
DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"`
|
|
}
|
|
|
|
func (q *Queries) UpdateMediaNoteForSync(ctx context.Context, arg UpdateMediaNoteForSyncParams) (MediaNotes, error) {
|
|
row := q.db.QueryRow(ctx, UpdateMediaNoteForSync,
|
|
arg.ID,
|
|
arg.Content,
|
|
arg.Position,
|
|
arg.PercentageLocation,
|
|
arg.CharacterStart,
|
|
arg.CharacterEnd,
|
|
arg.EpubcfiLocation,
|
|
arg.ChapterReference,
|
|
arg.ParagraphReference,
|
|
arg.LastModifiedAt,
|
|
arg.LastModifiedSource,
|
|
arg.DeviceSyncData,
|
|
)
|
|
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,
|
|
&i.DedupKey,
|
|
&i.LastModifiedAt,
|
|
&i.LastModifiedSource,
|
|
&i.Deleted,
|
|
&i.DeletedAt,
|
|
)
|
|
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, context_text, 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.ContextText,
|
|
&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 UpdateReadingSpeed = `-- name: UpdateReadingSpeed :one
|
|
UPDATE reading_speed
|
|
SET
|
|
pages_per_minute = $3,
|
|
pages_read = pages_read + $4,
|
|
total_reading_minutes = total_reading_minutes + $5,
|
|
last_read_at = $6,
|
|
updated_at = NOW()
|
|
WHERE user_id = $1 AND media_item_id = $2
|
|
RETURNING id, user_id, media_item_id, words_per_minute, pages_per_minute, pages_read, total_reading_minutes, last_read_at, updated_at
|
|
`
|
|
|
|
type UpdateReadingSpeedParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
PagesPerMinute pgtype.Float4 `db:"pages_per_minute" json:"pages_per_minute"`
|
|
PagesRead pgtype.Int4 `db:"pages_read" json:"pages_read"`
|
|
TotalReadingMinutes pgtype.Float4 `db:"total_reading_minutes" json:"total_reading_minutes"`
|
|
LastReadAt pgtype.Timestamptz `db:"last_read_at" json:"last_read_at"`
|
|
}
|
|
|
|
func (q *Queries) UpdateReadingSpeed(ctx context.Context, arg UpdateReadingSpeedParams) (ReadingSpeed, error) {
|
|
row := q.db.QueryRow(ctx, UpdateReadingSpeed,
|
|
arg.UserID,
|
|
arg.MediaItemID,
|
|
arg.PagesPerMinute,
|
|
arg.PagesRead,
|
|
arg.TotalReadingMinutes,
|
|
arg.LastReadAt,
|
|
)
|
|
var i ReadingSpeed
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.MediaItemID,
|
|
&i.WordsPerMinute,
|
|
&i.PagesPerMinute,
|
|
&i.PagesRead,
|
|
&i.TotalReadingMinutes,
|
|
&i.LastReadAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateSavedFilter = `-- name: UpdateSavedFilter :one
|
|
UPDATE saved_filters
|
|
SET name = $1,
|
|
filters = $2,
|
|
updated_at = NOW()
|
|
WHERE id = $3 AND user_id = $4
|
|
RETURNING id, user_id, name, resource_type, filters, created_at, updated_at
|
|
`
|
|
|
|
type UpdateSavedFilterParams struct {
|
|
Name string `db:"name" json:"name"`
|
|
Filters []byte `db:"filters" json:"filters"`
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
}
|
|
|
|
func (q *Queries) UpdateSavedFilter(ctx context.Context, arg UpdateSavedFilterParams) (SavedFilters, error) {
|
|
row := q.db.QueryRow(ctx, UpdateSavedFilter,
|
|
arg.Name,
|
|
arg.Filters,
|
|
arg.ID,
|
|
arg.UserID,
|
|
)
|
|
var i SavedFilters
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Name,
|
|
&i.ResourceType,
|
|
&i.Filters,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateSyncQueueItemStatus = `-- name: UpdateSyncQueueItemStatus :one
|
|
UPDATE sync_queue
|
|
SET
|
|
status = $2,
|
|
attempts = attempts + 1,
|
|
error_message = $3,
|
|
processed_at = CASE WHEN $2::varchar = '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 UpdateSystemSetting = `-- name: UpdateSystemSetting :exec
|
|
UPDATE system_settings SET setting_value = $2, updated_at = NOW() WHERE setting_key = $1
|
|
`
|
|
|
|
type UpdateSystemSettingParams struct {
|
|
SettingKey string `db:"setting_key" json:"setting_key"`
|
|
SettingValue string `db:"setting_value" json:"setting_value"`
|
|
}
|
|
|
|
func (q *Queries) UpdateSystemSetting(ctx context.Context, arg UpdateSystemSettingParams) error {
|
|
_, err := q.db.Exec(ctx, UpdateSystemSetting, arg.SettingKey, arg.SettingValue)
|
|
return err
|
|
}
|
|
|
|
const UpdateUniversalProgress = `-- name: UpdateUniversalProgress :one
|
|
INSERT INTO reading_progress (
|
|
media_item_id,
|
|
user_id,
|
|
percentage,
|
|
character_offset,
|
|
epubcfi,
|
|
context_text,
|
|
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, $17, NOW(), $18, $19, NOW()
|
|
)
|
|
ON CONFLICT (media_item_id, user_id)
|
|
DO UPDATE SET
|
|
percentage = EXCLUDED.percentage,
|
|
character_offset = EXCLUDED.character_offset,
|
|
epubcfi = EXCLUDED.epubcfi,
|
|
context_text = EXCLUDED.context_text,
|
|
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, context_text, 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"`
|
|
ContextText pgtype.Text `db:"context_text" json:"context_text"`
|
|
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.ContextText,
|
|
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.ContextText,
|
|
&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 :one
|
|
UPDATE users SET max_devices = $2, updated_at = NOW() WHERE id = $1
|
|
RETURNING id, email, username, password_hash, first_name, last_name, role, theme, max_devices, created_at, timezone, updated_at
|
|
`
|
|
|
|
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) (Users, error) {
|
|
row := q.db.QueryRow(ctx, UpdateUserMaxDevices, arg.ID, arg.MaxDevices)
|
|
var i Users
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Email,
|
|
&i.Username,
|
|
&i.PasswordHash,
|
|
&i.FirstName,
|
|
&i.LastName,
|
|
&i.Role,
|
|
&i.Theme,
|
|
&i.MaxDevices,
|
|
&i.CreatedAt,
|
|
&i.Timezone,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, 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 UpdateUserRole = `-- name: UpdateUserRole :one
|
|
UPDATE users SET role = $2, updated_at = NOW() WHERE id = $1
|
|
RETURNING id, email, username, role
|
|
`
|
|
|
|
type UpdateUserRoleParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Role string `db:"role" json:"role"`
|
|
}
|
|
|
|
type UpdateUserRoleRow struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Email string `db:"email" json:"email"`
|
|
Username string `db:"username" json:"username"`
|
|
Role string `db:"role" json:"role"`
|
|
}
|
|
|
|
func (q *Queries) UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) (UpdateUserRoleRow, error) {
|
|
row := q.db.QueryRow(ctx, UpdateUserRole, arg.ID, arg.Role)
|
|
var i UpdateUserRoleRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Email,
|
|
&i.Username,
|
|
&i.Role,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpdateUserTheme = `-- name: UpdateUserTheme :exec
|
|
UPDATE users SET theme = $2, updated_at = NOW() WHERE id = $1
|
|
`
|
|
|
|
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 UpdateUserTimezone = `-- name: UpdateUserTimezone :exec
|
|
UPDATE users SET timezone = $2, updated_at = NOW() WHERE id = $1
|
|
`
|
|
|
|
type UpdateUserTimezoneParams struct {
|
|
ID pgtype.UUID `db:"id" json:"id"`
|
|
Timezone pgtype.Text `db:"timezone" json:"timezone"`
|
|
}
|
|
|
|
func (q *Queries) UpdateUserTimezone(ctx context.Context, arg UpdateUserTimezoneParams) error {
|
|
_, err := q.db.Exec(ctx, UpdateUserTimezone, arg.ID, arg.Timezone)
|
|
return err
|
|
}
|
|
|
|
const UpdateUsername = `-- name: UpdateUsername :exec
|
|
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
|
|
}
|
|
|
|
const UpsertDashboardPreferences = `-- name: UpsertDashboardPreferences :one
|
|
INSERT INTO user_dashboard_preferences (user_id, library_id, hidden_collections, collection_order, items_per_section)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
ON CONFLICT (user_id, library_id)
|
|
DO UPDATE SET
|
|
hidden_collections = EXCLUDED.hidden_collections,
|
|
collection_order = EXCLUDED.collection_order,
|
|
items_per_section = EXCLUDED.items_per_section,
|
|
updated_at = NOW()
|
|
RETURNING id, user_id, library_id, hidden_collections, collection_order, items_per_section, created_at, updated_at
|
|
`
|
|
|
|
type UpsertDashboardPreferencesParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
|
HiddenCollections []string `db:"hidden_collections" json:"hidden_collections"`
|
|
CollectionOrder []string `db:"collection_order" json:"collection_order"`
|
|
ItemsPerSection pgtype.Int4 `db:"items_per_section" json:"items_per_section"`
|
|
}
|
|
|
|
func (q *Queries) UpsertDashboardPreferences(ctx context.Context, arg UpsertDashboardPreferencesParams) (UserDashboardPreferences, error) {
|
|
row := q.db.QueryRow(ctx, UpsertDashboardPreferences,
|
|
arg.UserID,
|
|
arg.LibraryID,
|
|
arg.HiddenCollections,
|
|
arg.CollectionOrder,
|
|
arg.ItemsPerSection,
|
|
)
|
|
var i UserDashboardPreferences
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.LibraryID,
|
|
&i.HiddenCollections,
|
|
&i.CollectionOrder,
|
|
&i.ItemsPerSection,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpsertPanelData = `-- name: UpsertPanelData :one
|
|
INSERT INTO panel_data (media_item_id, page_number, detection_method, panels)
|
|
VALUES ($1, $2, $3, $4)
|
|
ON CONFLICT (media_item_id, page_number)
|
|
DO UPDATE SET
|
|
detection_method = EXCLUDED.detection_method,
|
|
panels = EXCLUDED.panels,
|
|
updated_at = NOW()
|
|
RETURNING id, media_item_id, page_number, detection_method, panels, created_at, updated_at
|
|
`
|
|
|
|
type UpsertPanelDataParams struct {
|
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
|
PageNumber int32 `db:"page_number" json:"page_number"`
|
|
DetectionMethod string `db:"detection_method" json:"detection_method"`
|
|
Panels []byte `db:"panels" json:"panels"`
|
|
}
|
|
|
|
func (q *Queries) UpsertPanelData(ctx context.Context, arg UpsertPanelDataParams) (PanelData, error) {
|
|
row := q.db.QueryRow(ctx, UpsertPanelData,
|
|
arg.MediaItemID,
|
|
arg.PageNumber,
|
|
arg.DetectionMethod,
|
|
arg.Panels,
|
|
)
|
|
var i PanelData
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MediaItemID,
|
|
&i.PageNumber,
|
|
&i.DetectionMethod,
|
|
&i.Panels,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpsertReaderSettings = `-- name: UpsertReaderSettings :one
|
|
INSERT INTO reader_settings (user_id, setting_key, setting_value)
|
|
VALUES ($1, 'reader_settings', $2)
|
|
ON CONFLICT (user_id, setting_key)
|
|
DO UPDATE SET
|
|
setting_value = EXCLUDED.setting_value,
|
|
updated_at = NOW()
|
|
RETURNING id, user_id, setting_key, setting_value, updated_at
|
|
`
|
|
|
|
type UpsertReaderSettingsParams struct {
|
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
SettingValue []byte `db:"setting_value" json:"setting_value"`
|
|
}
|
|
|
|
func (q *Queries) UpsertReaderSettings(ctx context.Context, arg UpsertReaderSettingsParams) (ReaderSettings, error) {
|
|
row := q.db.QueryRow(ctx, UpsertReaderSettings, arg.UserID, arg.SettingValue)
|
|
var i ReaderSettings
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.SettingKey,
|
|
&i.SettingValue,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const UpsertSystemSetting = `-- name: UpsertSystemSetting :one
|
|
INSERT INTO system_settings (setting_key, setting_value, description, setting_type, min_value, max_value, requires_restart, category)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
ON CONFLICT (setting_key) DO UPDATE
|
|
SET setting_value = EXCLUDED.setting_value,
|
|
description = EXCLUDED.description,
|
|
setting_type = EXCLUDED.setting_type,
|
|
min_value = EXCLUDED.min_value,
|
|
max_value = EXCLUDED.max_value,
|
|
requires_restart = EXCLUDED.requires_restart,
|
|
category = EXCLUDED.category,
|
|
updated_at = NOW()
|
|
RETURNING id, setting_key, setting_value, description, updated_at, setting_type, min_value, max_value, requires_restart, category
|
|
`
|
|
|
|
type UpsertSystemSettingParams struct {
|
|
SettingKey string `db:"setting_key" json:"setting_key"`
|
|
SettingValue string `db:"setting_value" json:"setting_value"`
|
|
Description pgtype.Text `db:"description" json:"description"`
|
|
SettingType pgtype.Text `db:"setting_type" json:"setting_type"`
|
|
MinValue pgtype.Text `db:"min_value" json:"min_value"`
|
|
MaxValue pgtype.Text `db:"max_value" json:"max_value"`
|
|
RequiresRestart pgtype.Bool `db:"requires_restart" json:"requires_restart"`
|
|
Category pgtype.Text `db:"category" json:"category"`
|
|
}
|
|
|
|
func (q *Queries) UpsertSystemSetting(ctx context.Context, arg UpsertSystemSettingParams) (SystemSettings, error) {
|
|
row := q.db.QueryRow(ctx, UpsertSystemSetting,
|
|
arg.SettingKey,
|
|
arg.SettingValue,
|
|
arg.Description,
|
|
arg.SettingType,
|
|
arg.MinValue,
|
|
arg.MaxValue,
|
|
arg.RequiresRestart,
|
|
arg.Category,
|
|
)
|
|
var i SystemSettings
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.SettingKey,
|
|
&i.SettingValue,
|
|
&i.Description,
|
|
&i.UpdatedAt,
|
|
&i.SettingType,
|
|
&i.MinValue,
|
|
&i.MaxValue,
|
|
&i.RequiresRestart,
|
|
&i.Category,
|
|
)
|
|
return i, err
|
|
}
|