Phase 2 Week 5: Device Registration & Management
Implement device registration and management system for universal sync. Database Changes: - Add device queries to queries.sql (CRUD operations, registration, auth) - Add sync queue management queries - Add conflict resolution queries - Regenerate sqlc models with new device-related types Device Handler (devices.go): - InitiateRegistration: Start device registration with auth URL and QR code - CheckRegistrationStatus: Poll for registration approval - ListDevices: Get all devices for current user - GetDevice: Get specific device details - UpdateDevice: Update device settings (name, sync settings, frequency) - DeleteDevice: Remove device from account - ApproveDevice: User approves device registration via web - RejectDevice: Reject pending device registration - ListPendingRegistrations: Show all pending registrations - generateDeviceToken: Generate secure Bearer token for devices Device Authentication Middleware (device_auth.go): - Authenticate: Validate device Bearer tokens - RequirePermission: Check device permissions by type - hasPermission: Define permissions per device type - UpdateLastSeen: Auto-update device last_seen timestamp Configuration: - Add BaseURL field to Config for device setup URLs API Endpoints: POST /api/devices/register - Initiate device registration POST /api/devices/register/status - Check registration status GET /api/devices/approve/:id - Approve device (web UI) POST /api/devices/reject/:id - Reject device GET /api/devices - List user's devices GET /api/devices/:id - Get device details PUT /api/devices/:id - Update device settings DELETE /api/devices/:id - Delete device GET /api/devices/pending - List pending registrations Bruno API Collection: - Initiate Device Registration - Check Registration Status - List Devices - Get Device - Update Device - Delete Device Dependencies: - github.com/skip2/go-qrcode for QR code generation Device Types Supported: - koreader: Calibre-compatible sync - kobo: Kobo sync protocol - web: Web interface - mobile: Mobile apps Device Permissions: - sync:progress - sync:annotations - sync:metadata - device:manage (web only)
This commit is contained in:
@@ -59,6 +59,70 @@ func (q *Queries) CleanupExpiredRefreshTokens(ctx context.Context) error {
|
||||
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 CreateDevice = `-- name: CreateDevice :one
|
||||
|
||||
INSERT INTO devices (user_id, device_name, device_type, device_identifier, auth_token, sync_enabled, auto_sync, sync_frequency_minutes, device_metadata)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
RETURNING id, user_id, device_name, device_type, device_identifier, auth_token, last_sync, last_seen, sync_enabled, auto_sync, sync_frequency_minutes, device_metadata, created_at, updated_at
|
||||
`
|
||||
|
||||
type CreateDeviceParams struct {
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
DeviceName string `db:"device_name" json:"device_name"`
|
||||
DeviceType string `db:"device_type" json:"device_type"`
|
||||
DeviceIdentifier string `db:"device_identifier" json:"device_identifier"`
|
||||
AuthToken string `db:"auth_token" json:"auth_token"`
|
||||
SyncEnabled pgtype.Bool `db:"sync_enabled" json:"sync_enabled"`
|
||||
AutoSync pgtype.Bool `db:"auto_sync" json:"auto_sync"`
|
||||
SyncFrequencyMinutes pgtype.Int4 `db:"sync_frequency_minutes" json:"sync_frequency_minutes"`
|
||||
DeviceMetadata []byte `db:"device_metadata" json:"device_metadata"`
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// PHASE 2: DEVICE MANAGEMENT & AUTH (Weeks 5-6)
|
||||
// ============================================
|
||||
// Device Registration & Management
|
||||
func (q *Queries) CreateDevice(ctx context.Context, arg CreateDeviceParams) (Devices, error) {
|
||||
row := q.db.QueryRow(ctx, CreateDevice,
|
||||
arg.UserID,
|
||||
arg.DeviceName,
|
||||
arg.DeviceType,
|
||||
arg.DeviceIdentifier,
|
||||
arg.AuthToken,
|
||||
arg.SyncEnabled,
|
||||
arg.AutoSync,
|
||||
arg.SyncFrequencyMinutes,
|
||||
arg.DeviceMetadata,
|
||||
)
|
||||
var i Devices
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.DeviceName,
|
||||
&i.DeviceType,
|
||||
&i.DeviceIdentifier,
|
||||
&i.AuthToken,
|
||||
&i.LastSync,
|
||||
&i.LastSeen,
|
||||
&i.SyncEnabled,
|
||||
&i.AutoSync,
|
||||
&i.SyncFrequencyMinutes,
|
||||
&i.DeviceMetadata,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const CreateEbookNote = `-- name: CreateEbookNote :one
|
||||
INSERT INTO media_notes (media_item_id, user_id, content, position)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
@@ -447,6 +511,88 @@ func (q *Queries) CreateRefreshToken(ctx context.Context, arg CreateRefreshToken
|
||||
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 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 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)
|
||||
@@ -500,6 +646,24 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateU
|
||||
return i, 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 DeleteEbookNote = `-- name: DeleteEbookNote :exec
|
||||
DELETE FROM media_notes WHERE id = $1
|
||||
`
|
||||
@@ -594,6 +758,24 @@ func (q *Queries) DeleteReadingProgress(ctx context.Context, arg DeleteReadingPr
|
||||
return err
|
||||
}
|
||||
|
||||
const DeleteSyncConflict = `-- name: DeleteSyncConflict :exec
|
||||
DELETE FROM sync_conflicts WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteSyncConflict(ctx context.Context, id pgtype.UUID) error {
|
||||
_, err := q.db.Exec(ctx, DeleteSyncConflict, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const DeleteSyncQueueItem = `-- name: DeleteSyncQueueItem :exec
|
||||
DELETE FROM sync_queue WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteSyncQueueItem(ctx context.Context, id pgtype.UUID) error {
|
||||
_, err := q.db.Exec(ctx, DeleteSyncQueueItem, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const DeleteUser = `-- name: DeleteUser :exec
|
||||
DELETE FROM users WHERE id = $1
|
||||
`
|
||||
@@ -603,6 +785,84 @@ func (q *Queries) DeleteUser(ctx context.Context, id pgtype.UUID) error {
|
||||
return 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 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
|
||||
@@ -1243,6 +1503,52 @@ func (q *Queries) GetScanSettings(ctx context.Context, id pgtype.UUID) (GetScanS
|
||||
return i, err
|
||||
}
|
||||
|
||||
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 GetUniversalProgress = `-- name: GetUniversalProgress :one
|
||||
SELECT
|
||||
rp.id,
|
||||
@@ -1586,6 +1892,84 @@ func (q *Queries) GetUserVisibleLibraries(ctx context.Context, userID pgtype.UUI
|
||||
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
|
||||
@@ -2200,6 +2584,149 @@ func (q *Queries) ListMediaItemsSorted(ctx context.Context, arg ListMediaItemsSo
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const ListPendingSyncQueueItems = `-- name: ListPendingSyncQueueItems :many
|
||||
SELECT id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at FROM sync_queue
|
||||
WHERE device_id = $1 AND status = 'pending'
|
||||
ORDER BY priority ASC, created_at ASC
|
||||
LIMIT $2
|
||||
`
|
||||
|
||||
type ListPendingSyncQueueItemsParams struct {
|
||||
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
||||
Limit int32 `db:"limit" json:"limit"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListPendingSyncQueueItems(ctx context.Context, arg ListPendingSyncQueueItemsParams) ([]SyncQueue, error) {
|
||||
rows, err := q.db.Query(ctx, ListPendingSyncQueueItems, arg.DeviceID, arg.Limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SyncQueue{}
|
||||
for rows.Next() {
|
||||
var i SyncQueue
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.DeviceID,
|
||||
&i.MediaItemID,
|
||||
&i.SyncType,
|
||||
&i.SyncData,
|
||||
&i.Priority,
|
||||
&i.Attempts,
|
||||
&i.MaxAttempts,
|
||||
&i.Status,
|
||||
&i.ErrorMessage,
|
||||
&i.CreatedAt,
|
||||
&i.ProcessedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const ListSyncConflictsByMediaItem = `-- name: ListSyncConflictsByMediaItem :many
|
||||
SELECT id, media_item_id, user_id, conflict_type, conflict_data, resolution_status, resolution_data, resolved_by, resolved_at, created_at FROM sync_conflicts
|
||||
WHERE media_item_id = $1 AND user_id = $2
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
type ListSyncConflictsByMediaItemParams struct {
|
||||
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListSyncConflictsByMediaItem(ctx context.Context, arg ListSyncConflictsByMediaItemParams) ([]SyncConflicts, error) {
|
||||
rows, err := q.db.Query(ctx, ListSyncConflictsByMediaItem, arg.MediaItemID, arg.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SyncConflicts{}
|
||||
for rows.Next() {
|
||||
var i SyncConflicts
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.MediaItemID,
|
||||
&i.UserID,
|
||||
&i.ConflictType,
|
||||
&i.ConflictData,
|
||||
&i.ResolutionStatus,
|
||||
&i.ResolutionData,
|
||||
&i.ResolvedBy,
|
||||
&i.ResolvedAt,
|
||||
&i.CreatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const ListSyncConflictsByUser = `-- name: ListSyncConflictsByUser :many
|
||||
SELECT sc.id, sc.media_item_id, sc.user_id, sc.conflict_type, sc.conflict_data, sc.resolution_status, sc.resolution_data, sc.resolved_by, sc.resolved_at, sc.created_at, mi.title, mi.author
|
||||
FROM sync_conflicts sc
|
||||
JOIN media_items mi ON sc.media_item_id = mi.id
|
||||
WHERE sc.user_id = $1 AND sc.resolution_status = 'unresolved'
|
||||
ORDER BY sc.created_at DESC
|
||||
`
|
||||
|
||||
type ListSyncConflictsByUserRow struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
ConflictType string `db:"conflict_type" json:"conflict_type"`
|
||||
ConflictData []byte `db:"conflict_data" json:"conflict_data"`
|
||||
ResolutionStatus pgtype.Text `db:"resolution_status" json:"resolution_status"`
|
||||
ResolutionData []byte `db:"resolution_data" json:"resolution_data"`
|
||||
ResolvedBy pgtype.UUID `db:"resolved_by" json:"resolved_by"`
|
||||
ResolvedAt pgtype.Timestamptz `db:"resolved_at" json:"resolved_at"`
|
||||
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||
Title string `db:"title" json:"title"`
|
||||
Author pgtype.Text `db:"author" json:"author"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListSyncConflictsByUser(ctx context.Context, userID pgtype.UUID) ([]ListSyncConflictsByUserRow, error) {
|
||||
rows, err := q.db.Query(ctx, ListSyncConflictsByUser, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []ListSyncConflictsByUserRow{}
|
||||
for rows.Next() {
|
||||
var i ListSyncConflictsByUserRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.MediaItemID,
|
||||
&i.UserID,
|
||||
&i.ConflictType,
|
||||
&i.ConflictData,
|
||||
&i.ResolutionStatus,
|
||||
&i.ResolutionData,
|
||||
&i.ResolvedBy,
|
||||
&i.ResolvedAt,
|
||||
&i.CreatedAt,
|
||||
&i.Title,
|
||||
&i.Author,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const ListUsers = `-- name: ListUsers :many
|
||||
SELECT id, email, username, theme, first_name, last_name, role, created_at, updated_at FROM users ORDER BY created_at DESC
|
||||
`
|
||||
@@ -2246,6 +2773,47 @@ func (q *Queries) ListUsers(ctx context.Context) ([]ListUsersRow, error) {
|
||||
return items, nil
|
||||
}
|
||||
|
||||
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 RevokeAllUserRefreshTokens = `-- name: RevokeAllUserRefreshTokens :exec
|
||||
UPDATE refresh_tokens SET revoked_at = NOW() WHERE user_id = $1 AND revoked_at IS NULL
|
||||
`
|
||||
@@ -2255,6 +2823,20 @@ func (q *Queries) RevokeAllUserRefreshTokens(ctx context.Context, userID pgtype.
|
||||
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 RevokeRefreshToken = `-- name: RevokeRefreshToken :exec
|
||||
UPDATE refresh_tokens SET revoked_at = NOW() WHERE token = $1
|
||||
`
|
||||
@@ -2567,6 +3149,120 @@ func (q *Queries) SetLibraryVisibility(ctx context.Context, arg SetLibraryVisibi
|
||||
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 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 UpdateEbookNote = `-- name: UpdateEbookNote :one
|
||||
UPDATE media_notes SET
|
||||
content = $2,
|
||||
@@ -3009,6 +3705,43 @@ func (q *Queries) UpdateScanSettings(ctx context.Context, arg UpdateScanSettings
|
||||
return err
|
||||
}
|
||||
|
||||
const UpdateSyncQueueItemStatus = `-- name: UpdateSyncQueueItemStatus :one
|
||||
UPDATE sync_queue
|
||||
SET
|
||||
status = $2,
|
||||
attempts = attempts + 1,
|
||||
error_message = $3,
|
||||
processed_at = CASE WHEN $2 = 'completed' THEN NOW() ELSE NULL END
|
||||
WHERE id = $1
|
||||
RETURNING id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at
|
||||
`
|
||||
|
||||
type UpdateSyncQueueItemStatusParams struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
Status pgtype.Text `db:"status" json:"status"`
|
||||
ErrorMessage pgtype.Text `db:"error_message" json:"error_message"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateSyncQueueItemStatus(ctx context.Context, arg UpdateSyncQueueItemStatusParams) (SyncQueue, error) {
|
||||
row := q.db.QueryRow(ctx, UpdateSyncQueueItemStatus, arg.ID, arg.Status, arg.ErrorMessage)
|
||||
var i SyncQueue
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.DeviceID,
|
||||
&i.MediaItemID,
|
||||
&i.SyncType,
|
||||
&i.SyncData,
|
||||
&i.Priority,
|
||||
&i.Attempts,
|
||||
&i.MaxAttempts,
|
||||
&i.Status,
|
||||
&i.ErrorMessage,
|
||||
&i.CreatedAt,
|
||||
&i.ProcessedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const UpdateUniversalProgress = `-- name: UpdateUniversalProgress :one
|
||||
INSERT INTO reading_progress (
|
||||
media_item_id,
|
||||
|
||||
Reference in New Issue
Block a user