feat: implement library system database schema
- Add library_types table with ebooks, comics, manga types - Add libraries table for multiple library support - Add library_folders table for multi-folder libraries - Add library_visibility table for user access control - Add media_items table replacing ebooks for broader media support - Create backward compatibility views for existing API - Implement library service with type validation and file extension handling - Support modular extension for future media types Manga type includes cbz/cbr archives as requested
This commit is contained in:
+118
-32
@@ -1,5 +1,51 @@
|
|||||||
-- Consolidated Bookmann Database Schema
|
-- Consolidated Bookmann Database Schema
|
||||||
-- This file contains the complete current schema for the Bookmann ebook management system
|
-- This file contains the complete current schema for the Bookmann media library management system
|
||||||
|
|
||||||
|
-- Create library types table
|
||||||
|
CREATE TABLE library_types (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
allowed_extensions TEXT[] NOT NULL, -- Array of allowed file extensions for this type
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Insert default library types
|
||||||
|
INSERT INTO library_types (name, description, allowed_extensions) VALUES
|
||||||
|
('ebooks', 'Ebook files including EPUB, PDF, MOBI, etc.', ARRAY['.epub', '.pdf', '.mobi', '.azw', '.azw3', '.txt', '.rtf', '.doc', '.docx', '.lit', '.fb2', '.pdb']),
|
||||||
|
('comics', 'Comic book archives and image formats', ARRAY['.cbz', '.cbr', '.cb7', '.cbt', '.pdf']),
|
||||||
|
('manga', 'Manga files including archives and image folders', ARRAY['.cbz', '.cbr', '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp']);
|
||||||
|
|
||||||
|
-- Create libraries table
|
||||||
|
CREATE TABLE libraries (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
library_type_id UUID NOT NULL REFERENCES library_types(id) ON DELETE RESTRICT,
|
||||||
|
created_by_admin_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create library_folders table for multiple folders per library
|
||||||
|
CREATE TABLE library_folders (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
library_id UUID NOT NULL REFERENCES libraries(id) ON DELETE CASCADE,
|
||||||
|
folder_path VARCHAR(500) NOT NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
UNIQUE(library_id, folder_path)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create library_visibility table for user-specific library visibility
|
||||||
|
CREATE TABLE library_visibility (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
library_id UUID NOT NULL REFERENCES libraries(id) ON DELETE CASCADE,
|
||||||
|
is_visible BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
UNIQUE(user_id, library_id)
|
||||||
|
);
|
||||||
|
|
||||||
-- Create users table
|
-- Create users table
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
@@ -17,12 +63,13 @@ CREATE TABLE users (
|
|||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Create ebooks table
|
-- Create media_items table (replaces ebooks table for broader media support)
|
||||||
CREATE TABLE ebooks (
|
CREATE TABLE media_items (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
library_id UUID NOT NULL REFERENCES libraries(id) ON DELETE CASCADE,
|
||||||
title VARCHAR(255) NOT NULL,
|
title VARCHAR(255) NOT NULL,
|
||||||
author VARCHAR(255),
|
author VARCHAR(255),
|
||||||
isbn VARCHAR(13),
|
isbn VARCHAR(13), -- Still relevant for ebooks
|
||||||
description TEXT,
|
description TEXT,
|
||||||
file_path VARCHAR(500) NOT NULL,
|
file_path VARCHAR(500) NOT NULL,
|
||||||
file_size BIGINT,
|
file_size BIGINT,
|
||||||
@@ -31,7 +78,7 @@ CREATE TABLE ebooks (
|
|||||||
series VARCHAR(255),
|
series VARCHAR(255),
|
||||||
series_number INTEGER,
|
series_number INTEGER,
|
||||||
tags TEXT,
|
tags TEXT,
|
||||||
asin VARCHAR(20),
|
asin VARCHAR(20), -- Still relevant for ebooks
|
||||||
date_published DATE,
|
date_published DATE,
|
||||||
publisher VARCHAR(255),
|
publisher VARCHAR(255),
|
||||||
contributors TEXT,
|
contributors TEXT,
|
||||||
@@ -40,57 +87,96 @@ CREATE TABLE ebooks (
|
|||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Create ebooks view for backward compatibility
|
||||||
|
CREATE VIEW ebooks AS
|
||||||
|
SELECT mi.*
|
||||||
|
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 lt.name = 'ebooks';
|
||||||
|
|
||||||
-- Create reading_progress table
|
-- Create reading_progress table
|
||||||
CREATE TABLE reading_progress (
|
CREATE TABLE reading_progress (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
ebook_id UUID NOT NULL REFERENCES ebooks(id) ON DELETE CASCADE,
|
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
|
||||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
current_page INTEGER DEFAULT 0,
|
current_page INTEGER DEFAULT 0,
|
||||||
total_pages INTEGER,
|
total_pages INTEGER,
|
||||||
last_read_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
last_read_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
UNIQUE(ebook_id, user_id)
|
UNIQUE(media_item_id, user_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Create ebook_ratings table
|
-- Create reading_progress view for backward compatibility
|
||||||
CREATE TABLE ebook_ratings (
|
CREATE VIEW ebook_reading_progress AS
|
||||||
|
SELECT rp.*,
|
||||||
|
mi.id as ebook_id -- Map media_item_id to ebook_id for compatibility
|
||||||
|
FROM reading_progress rp
|
||||||
|
JOIN media_items mi ON rp.media_item_id = mi.id
|
||||||
|
JOIN libraries l ON mi.library_id = l.id
|
||||||
|
JOIN library_types lt ON l.library_type_id = lt.id
|
||||||
|
WHERE lt.name = 'ebooks';
|
||||||
|
|
||||||
|
-- Create media_ratings table (replaces ebook_ratings)
|
||||||
|
CREATE TABLE media_ratings (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
ebook_id UUID NOT NULL REFERENCES ebooks(id) ON DELETE CASCADE,
|
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
|
||||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 10), -- 10-point scale for half-star precision
|
rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 10), -- 10-point scale for half-star precision
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
UNIQUE(ebook_id, user_id)
|
UNIQUE(media_item_id, user_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Create user_ebook_folders table for multiple folders per user
|
-- Create ebook_ratings view for backward compatibility
|
||||||
CREATE TABLE user_ebook_folders (
|
CREATE VIEW ebook_ratings AS
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
SELECT mr.*,
|
||||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
mi.id as ebook_id -- Map media_item_id to ebook_id for compatibility
|
||||||
folder_path VARCHAR(500) NOT NULL,
|
FROM media_ratings mr
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
JOIN media_items mi ON mr.media_item_id = mi.id
|
||||||
UNIQUE(user_id, folder_path)
|
JOIN libraries l ON mi.library_id = l.id
|
||||||
-- Note: Admin-only access is enforced at application level in handlers/auth.go
|
JOIN library_types lt ON l.library_type_id = lt.id
|
||||||
);
|
WHERE lt.name = 'ebooks';
|
||||||
|
|
||||||
|
-- Note: user_ebook_folders table is replaced by library_folders table
|
||||||
|
-- Libraries now handle folder management instead of individual users
|
||||||
|
|
||||||
-- Create indexes for better query performance
|
-- Create indexes for better query performance
|
||||||
CREATE INDEX idx_users_email ON users(email);
|
CREATE INDEX idx_users_email ON users(email);
|
||||||
CREATE INDEX idx_users_username ON users(username);
|
CREATE INDEX idx_users_username ON users(username);
|
||||||
CREATE INDEX idx_ebooks_title ON ebooks(title);
|
CREATE INDEX idx_library_types_name ON library_types(name);
|
||||||
CREATE INDEX idx_ebooks_author ON ebooks(author);
|
|
||||||
CREATE INDEX idx_ebooks_added_by_admin_id ON ebooks(added_by_admin_id);
|
-- Library indexes
|
||||||
CREATE INDEX idx_reading_progress_ebook_id ON reading_progress(ebook_id);
|
CREATE INDEX idx_libraries_name ON libraries(name);
|
||||||
|
CREATE INDEX idx_libraries_library_type_id ON libraries(library_type_id);
|
||||||
|
CREATE INDEX idx_libraries_created_by_admin_id ON libraries(created_by_admin_id);
|
||||||
|
CREATE INDEX idx_library_folders_library_id ON library_folders(library_id);
|
||||||
|
CREATE INDEX idx_library_visibility_user_id ON library_visibility(user_id);
|
||||||
|
CREATE INDEX idx_library_visibility_library_id ON library_visibility(library_id);
|
||||||
|
|
||||||
|
-- Media items indexes
|
||||||
|
CREATE INDEX idx_media_items_title ON media_items(title);
|
||||||
|
CREATE INDEX idx_media_items_author ON media_items(author);
|
||||||
|
CREATE INDEX idx_media_items_library_id ON media_items(library_id);
|
||||||
|
CREATE INDEX idx_media_items_added_by_admin_id ON media_items(added_by_admin_id);
|
||||||
|
|
||||||
|
-- Progress and ratings indexes
|
||||||
|
CREATE INDEX idx_reading_progress_media_item_id ON reading_progress(media_item_id);
|
||||||
CREATE INDEX idx_reading_progress_user_id ON reading_progress(user_id);
|
CREATE INDEX idx_reading_progress_user_id ON reading_progress(user_id);
|
||||||
CREATE INDEX idx_ebook_ratings_ebook_id ON ebook_ratings(ebook_id);
|
CREATE INDEX idx_media_ratings_media_item_id ON media_ratings(media_item_id);
|
||||||
CREATE INDEX idx_ebook_ratings_user_id ON ebook_ratings(user_id);
|
CREATE INDEX idx_media_ratings_user_id ON media_ratings(user_id);
|
||||||
CREATE INDEX idx_user_ebook_folders_user_id ON user_ebook_folders(user_id);
|
|
||||||
|
|
||||||
-- Add comment explaining the rating system
|
-- Add comment explaining the rating system
|
||||||
COMMENT ON COLUMN ebook_ratings.rating IS 'Rating scale 1-10 (odd numbers = half-stars: 1,3,5,7,9 = 0.5,1.5,2.5,3.5,4.5 stars)';
|
COMMENT ON COLUMN media_ratings.rating IS 'Rating scale 1-10 (odd numbers = half-stars: 1,3,5,7,9 = 0.5,1.5,2.5,3.5,4.5 stars)';
|
||||||
|
|
||||||
|
-- Library Type File Extensions Notes:
|
||||||
|
-- - Ebooks: .epub, .pdf, .mobi, .azw, .azw3, .txt, .rtf, .doc, .docx, .lit, .fb2, .pdb
|
||||||
|
-- - Comics: .cbz, .cbr, .cb7, .cbt, .pdf
|
||||||
|
-- - Manga: .cbz, .cbr, .png, .jpg, .jpeg, .gif, .bmp, .webp (note: manga includes image folders)
|
||||||
|
|
||||||
-- Role System Notes:
|
-- Role System Notes:
|
||||||
-- - All users default to 'user' role
|
-- - All users default to 'user' role
|
||||||
-- - Admin users can: add/edit/delete folders, scan ebooks, modify ebook metadata, delete ebooks
|
-- - Admin users can: create/manage libraries and folders, scan media, modify media metadata, delete media, control library visibility
|
||||||
-- - Regular users can: view all ebooks, rate ebooks, track reading progress, manage their profile
|
-- - Regular users can: view visible libraries, rate media, track reading progress, manage their profile
|
||||||
-- - To create first admin: UPDATE users SET role = 'admin' WHERE email = 'your-admin-email';
|
-- - To create first admin: UPDATE users SET role = 'admin' WHERE email = 'your-admin-email';
|
||||||
-- - Only admins can manage folders and ebook metadata (enforced at application level)
|
-- - Library visibility is controlled through library_visibility table - admins can hide/show libraries per user
|
||||||
-- - user_ebook_folders table admin-only access is enforced by AdminMiddleware in handlers
|
-- - Backward compatibility views (ebooks, ebook_ratings, ebook_reading_progress) maintain existing API contracts
|
||||||
+86
-15
@@ -9,17 +9,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type EbookRatings struct {
|
type EbookRatings struct {
|
||||||
ID pgtype.UUID `db:"id" json:"id"`
|
ID pgtype.UUID `db:"id" json:"id"`
|
||||||
EbookID pgtype.UUID `db:"ebook_id" json:"ebook_id"`
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
||||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||||
// Rating scale 1-10 (odd numbers = half-stars: 1,3,5,7,9 = 0.5,1.5,2.5,3.5,4.5 stars)
|
Rating int32 `db:"rating" json:"rating"`
|
||||||
Rating int32 `db:"rating" json:"rating"`
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||||
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
EbookID pgtype.UUID `db:"ebook_id" json:"ebook_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbookReadingProgress 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"`
|
||||||
|
EbookID pgtype.UUID `db:"ebook_id" json:"ebook_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ebooks struct {
|
type Ebooks struct {
|
||||||
ID pgtype.UUID `db:"id" json:"id"`
|
ID pgtype.UUID `db:"id" json:"id"`
|
||||||
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
||||||
Title string `db:"title" json:"title"`
|
Title string `db:"title" json:"title"`
|
||||||
Author pgtype.Text `db:"author" json:"author"`
|
Author pgtype.Text `db:"author" json:"author"`
|
||||||
Isbn pgtype.Text `db:"isbn" json:"isbn"`
|
Isbn pgtype.Text `db:"isbn" json:"isbn"`
|
||||||
@@ -40,22 +51,82 @@ type Ebooks struct {
|
|||||||
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Libraries 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LibraryFolders struct {
|
||||||
|
ID pgtype.UUID `db:"id" json:"id"`
|
||||||
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
||||||
|
FolderPath string `db:"folder_path" json:"folder_path"`
|
||||||
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LibraryTypes struct {
|
||||||
|
ID pgtype.UUID `db:"id" json:"id"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
AllowedExtensions []string `db:"allowed_extensions" json:"allowed_extensions"`
|
||||||
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LibraryVisibility struct {
|
||||||
|
ID pgtype.UUID `db:"id" json:"id"`
|
||||||
|
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"`
|
||||||
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MediaItems struct {
|
||||||
|
ID pgtype.UUID `db:"id" json:"id"`
|
||||||
|
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
||||||
|
Title string `db:"title" json:"title"`
|
||||||
|
Author pgtype.Text `db:"author" json:"author"`
|
||||||
|
Isbn pgtype.Text `db:"isbn" json:"isbn"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
FilePath string `db:"file_path" json:"file_path"`
|
||||||
|
FileSize pgtype.Int8 `db:"file_size" json:"file_size"`
|
||||||
|
MimeType pgtype.Text `db:"mime_type" json:"mime_type"`
|
||||||
|
CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"`
|
||||||
|
Series pgtype.Text `db:"series" json:"series"`
|
||||||
|
SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"`
|
||||||
|
Tags pgtype.Text `db:"tags" json:"tags"`
|
||||||
|
Asin pgtype.Text `db:"asin" json:"asin"`
|
||||||
|
DatePublished pgtype.Date `db:"date_published" json:"date_published"`
|
||||||
|
Publisher pgtype.Text `db:"publisher" json:"publisher"`
|
||||||
|
Contributors pgtype.Text `db:"contributors" json:"contributors"`
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MediaRatings 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 scale 1-10 (odd numbers = half-stars: 1,3,5,7,9 = 0.5,1.5,2.5,3.5,4.5 stars)
|
||||||
|
Rating int32 `db:"rating" json:"rating"`
|
||||||
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type ReadingProgress struct {
|
type ReadingProgress struct {
|
||||||
ID pgtype.UUID `db:"id" json:"id"`
|
ID pgtype.UUID `db:"id" json:"id"`
|
||||||
EbookID pgtype.UUID `db:"ebook_id" json:"ebook_id"`
|
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
||||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||||
CurrentPage pgtype.Int4 `db:"current_page" json:"current_page"`
|
CurrentPage pgtype.Int4 `db:"current_page" json:"current_page"`
|
||||||
TotalPages pgtype.Int4 `db:"total_pages" json:"total_pages"`
|
TotalPages pgtype.Int4 `db:"total_pages" json:"total_pages"`
|
||||||
LastReadAt pgtype.Timestamptz `db:"last_read_at" json:"last_read_at"`
|
LastReadAt pgtype.Timestamptz `db:"last_read_at" json:"last_read_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserEbookFolders struct {
|
|
||||||
ID pgtype.UUID `db:"id" json:"id"`
|
|
||||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
|
||||||
FolderPath string `db:"folder_path" json:"folder_path"`
|
|
||||||
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Users struct {
|
type Users struct {
|
||||||
ID pgtype.UUID `db:"id" json:"id"`
|
ID pgtype.UUID `db:"id" json:"id"`
|
||||||
Email string `db:"email" json:"email"`
|
Email string `db:"email" json:"email"`
|
||||||
|
|||||||
@@ -11,33 +11,65 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Querier interface {
|
type Querier interface {
|
||||||
AddUserEbookFolder(ctx context.Context, arg AddUserEbookFolderParams) (UserEbookFolders, error)
|
// Library Folders queries
|
||||||
CreateEbook(ctx context.Context, arg CreateEbookParams) (Ebooks, error)
|
AddLibraryFolder(ctx context.Context, arg AddLibraryFolderParams) (LibraryFolders, error)
|
||||||
CreateEbookRating(ctx context.Context, arg CreateEbookRatingParams) (EbookRatings, error)
|
CreateEbook(ctx context.Context, arg CreateEbookParams) (MediaItems, error)
|
||||||
|
// Backward compatibility - Ebooks ratings (using views)
|
||||||
|
CreateEbookRating(ctx context.Context, arg CreateEbookRatingParams) (MediaRatings, error)
|
||||||
|
// Libraries queries
|
||||||
|
CreateLibrary(ctx context.Context, arg CreateLibraryParams) (Libraries, error)
|
||||||
|
// Media Items queries
|
||||||
|
CreateMediaItem(ctx context.Context, arg CreateMediaItemParams) (MediaItems, error)
|
||||||
|
CreateMediaRating(ctx context.Context, arg CreateMediaRatingParams) (MediaRatings, error)
|
||||||
CreateUser(ctx context.Context, arg CreateUserParams) (CreateUserRow, error)
|
CreateUser(ctx context.Context, arg CreateUserParams) (CreateUserRow, error)
|
||||||
DeleteEbook(ctx context.Context, id pgtype.UUID) error
|
DeleteEbook(ctx context.Context, id pgtype.UUID) error
|
||||||
DeleteEbookRating(ctx context.Context, arg DeleteEbookRatingParams) error
|
DeleteEbookRating(ctx context.Context, arg DeleteEbookRatingParams) error
|
||||||
|
DeleteLibrary(ctx context.Context, id pgtype.UUID) error
|
||||||
|
DeleteLibraryFolder(ctx context.Context, arg DeleteLibraryFolderParams) (LibraryFolders, error)
|
||||||
|
DeleteMediaItem(ctx context.Context, id pgtype.UUID) error
|
||||||
|
DeleteMediaRating(ctx context.Context, arg DeleteMediaRatingParams) error
|
||||||
DeleteReadingProgress(ctx context.Context, arg DeleteReadingProgressParams) error
|
DeleteReadingProgress(ctx context.Context, arg DeleteReadingProgressParams) error
|
||||||
DeleteUser(ctx context.Context, id pgtype.UUID) error
|
DeleteUser(ctx context.Context, id pgtype.UUID) error
|
||||||
DeleteUserEbookFolder(ctx context.Context, arg DeleteUserEbookFolderParams) (UserEbookFolders, error)
|
// Backward compatibility - Ebooks queries (using views)
|
||||||
GetEbook(ctx context.Context, id pgtype.UUID) (Ebooks, error)
|
GetEbook(ctx context.Context, id pgtype.UUID) (Ebooks, error)
|
||||||
|
// Note: User ebook folders replaced by library folders system
|
||||||
|
// Legacy folder management is now handled through libraries
|
||||||
GetEbookByFilePath(ctx context.Context, filePath string) (Ebooks, error)
|
GetEbookByFilePath(ctx context.Context, filePath string) (Ebooks, error)
|
||||||
GetEbookRating(ctx context.Context, arg GetEbookRatingParams) (EbookRatings, error)
|
GetEbookRating(ctx context.Context, arg GetEbookRatingParams) (EbookRatings, error)
|
||||||
GetEbookRatings(ctx context.Context, ebookID pgtype.UUID) ([]GetEbookRatingsRow, error)
|
GetEbookRatings(ctx context.Context, ebookID pgtype.UUID) ([]GetEbookRatingsRow, error)
|
||||||
|
GetLibrary(ctx context.Context, id pgtype.UUID) (GetLibraryRow, error)
|
||||||
|
GetLibraryFolders(ctx context.Context, libraryID pgtype.UUID) ([]LibraryFolders, error)
|
||||||
|
GetLibraryType(ctx context.Context, id pgtype.UUID) (LibraryTypes, error)
|
||||||
|
GetLibraryTypeByName(ctx context.Context, name string) (LibraryTypes, error)
|
||||||
|
// Library Types queries
|
||||||
|
GetLibraryTypes(ctx context.Context) ([]LibraryTypes, error)
|
||||||
|
GetLibraryVisibility(ctx context.Context, arg GetLibraryVisibilityParams) (LibraryVisibility, error)
|
||||||
|
GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems, error)
|
||||||
|
GetMediaItemByFilePath(ctx context.Context, filePath string) (MediaItems, error)
|
||||||
|
GetMediaRating(ctx context.Context, arg GetMediaRatingParams) (MediaRatings, error)
|
||||||
|
GetMediaRatings(ctx context.Context, mediaItemID pgtype.UUID) ([]GetMediaRatingsRow, error)
|
||||||
GetReadingProgress(ctx context.Context, arg GetReadingProgressParams) (ReadingProgress, error)
|
GetReadingProgress(ctx context.Context, arg GetReadingProgressParams) (ReadingProgress, error)
|
||||||
GetScanSettings(ctx context.Context, id pgtype.UUID) (GetScanSettingsRow, error)
|
GetScanSettings(ctx context.Context, id pgtype.UUID) (GetScanSettingsRow, error)
|
||||||
GetUser(ctx context.Context, id pgtype.UUID) (GetUserRow, error)
|
GetUser(ctx context.Context, id pgtype.UUID) (GetUserRow, error)
|
||||||
GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error)
|
GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error)
|
||||||
GetUserByEmailOrUsername(ctx context.Context, email string) (GetUserByEmailOrUsernameRow, error)
|
GetUserByEmailOrUsername(ctx context.Context, email string) (GetUserByEmailOrUsernameRow, error)
|
||||||
GetUserByUsername(ctx context.Context, username string) (GetUserByUsernameRow, error)
|
GetUserByUsername(ctx context.Context, username string) (GetUserByUsernameRow, error)
|
||||||
GetUserEbookFolders(ctx context.Context, userID pgtype.UUID) ([]UserEbookFolders, error)
|
|
||||||
GetUserForLogin(ctx context.Context, email string) (GetUserForLoginRow, error)
|
GetUserForLogin(ctx context.Context, email string) (GetUserForLoginRow, error)
|
||||||
GetUserPasswordHash(ctx context.Context, id pgtype.UUID) (string, error)
|
GetUserPasswordHash(ctx context.Context, id pgtype.UUID) (string, error)
|
||||||
|
GetUserVisibleLibraries(ctx context.Context, userID pgtype.UUID) ([]GetUserVisibleLibrariesRow, error)
|
||||||
ListEbooks(ctx context.Context, arg ListEbooksParams) ([]Ebooks, error)
|
ListEbooks(ctx context.Context, arg ListEbooksParams) ([]Ebooks, error)
|
||||||
|
ListLibraries(ctx context.Context) ([]ListLibrariesRow, error)
|
||||||
|
ListMediaItems(ctx context.Context, arg ListMediaItemsParams) ([]ListMediaItemsRow, error)
|
||||||
|
ListMediaItemsByLibrary(ctx context.Context, libraryID pgtype.UUID) ([]ListMediaItemsByLibraryRow, error)
|
||||||
ListUsers(ctx context.Context) ([]ListUsersRow, error)
|
ListUsers(ctx context.Context) ([]ListUsersRow, error)
|
||||||
UpdateEbook(ctx context.Context, arg UpdateEbookParams) (Ebooks, error)
|
// Library Visibility queries
|
||||||
UpdateEbookRating(ctx context.Context, arg UpdateEbookRatingParams) (EbookRatings, error)
|
SetLibraryVisibility(ctx context.Context, arg SetLibraryVisibilityParams) (LibraryVisibility, error)
|
||||||
|
UpdateEbook(ctx context.Context, arg UpdateEbookParams) (MediaItems, error)
|
||||||
|
UpdateEbookRating(ctx context.Context, arg UpdateEbookRatingParams) (MediaRatings, error)
|
||||||
UpdateEmail(ctx context.Context, arg UpdateEmailParams) error
|
UpdateEmail(ctx context.Context, arg UpdateEmailParams) error
|
||||||
|
UpdateLibrary(ctx context.Context, arg UpdateLibraryParams) (Libraries, error)
|
||||||
|
UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams) (MediaItems, error)
|
||||||
|
UpdateMediaRating(ctx context.Context, arg UpdateMediaRatingParams) (MediaRatings, error)
|
||||||
UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error
|
UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error
|
||||||
UpdateReadingProgress(ctx context.Context, arg UpdateReadingProgressParams) (ReadingProgress, error)
|
UpdateReadingProgress(ctx context.Context, arg UpdateReadingProgressParams) (ReadingProgress, error)
|
||||||
UpdateScanSettings(ctx context.Context, arg UpdateScanSettingsParams) error
|
UpdateScanSettings(ctx context.Context, arg UpdateScanSettingsParams) error
|
||||||
|
|||||||
+989
-121
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,126 @@ SELECT password_hash FROM users WHERE id = $1;
|
|||||||
-- name: ListUsers :many
|
-- name: ListUsers :many
|
||||||
SELECT id, email, username, theme, first_name, last_name, role, created_at, updated_at FROM users ORDER BY created_at DESC;
|
SELECT id, email, username, theme, first_name, last_name, role, created_at, updated_at FROM users ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- Library Types queries
|
||||||
|
-- name: GetLibraryTypes :many
|
||||||
|
SELECT * FROM library_types ORDER BY name;
|
||||||
|
|
||||||
|
-- name: GetLibraryType :one
|
||||||
|
SELECT * FROM library_types WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: GetLibraryTypeByName :one
|
||||||
|
SELECT * FROM library_types WHERE name = $1;
|
||||||
|
|
||||||
|
-- Libraries queries
|
||||||
|
-- name: CreateLibrary :one
|
||||||
|
INSERT INTO libraries (name, description, library_type_id, created_by_admin_id)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetLibrary :one
|
||||||
|
SELECT l.*, 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;
|
||||||
|
|
||||||
|
-- name: ListLibraries :many
|
||||||
|
SELECT l.*, 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;
|
||||||
|
|
||||||
|
-- name: UpdateLibrary :one
|
||||||
|
UPDATE libraries SET
|
||||||
|
name = $2,
|
||||||
|
description = $3,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: DeleteLibrary :exec
|
||||||
|
DELETE FROM libraries WHERE id = $1;
|
||||||
|
|
||||||
|
-- Library Folders queries
|
||||||
|
-- name: AddLibraryFolder :one
|
||||||
|
INSERT INTO library_folders (library_id, folder_path) VALUES ($1, $2) RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetLibraryFolders :many
|
||||||
|
SELECT * FROM library_folders WHERE library_id = $1 ORDER BY created_at;
|
||||||
|
|
||||||
|
-- name: DeleteLibraryFolder :one
|
||||||
|
DELETE FROM library_folders WHERE library_id = $1 AND folder_path = $2 RETURNING *;
|
||||||
|
|
||||||
|
-- Library Visibility queries
|
||||||
|
-- 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 *;
|
||||||
|
|
||||||
|
-- name: GetLibraryVisibility :one
|
||||||
|
SELECT * FROM library_visibility WHERE user_id = $1 AND library_id = $2;
|
||||||
|
|
||||||
|
-- name: GetUserVisibleLibraries :many
|
||||||
|
SELECT l.*, lt.name as type_name, lt.description as type_description,
|
||||||
|
COALESCE(lv.is_visible, true) as is_visible
|
||||||
|
FROM libraries l
|
||||||
|
JOIN library_types lt ON l.library_type_id = lt.id
|
||||||
|
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $1
|
||||||
|
WHERE COALESCE(lv.is_visible, true) = true
|
||||||
|
ORDER BY l.created_at DESC;
|
||||||
|
|
||||||
|
-- Media Items queries
|
||||||
|
-- name: CreateMediaItem :one
|
||||||
|
INSERT INTO media_items (library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, added_by_admin_id)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetMediaItem :one
|
||||||
|
SELECT * FROM media_items WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: ListMediaItems :many
|
||||||
|
SELECT mi.*, 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;
|
||||||
|
|
||||||
|
-- name: ListMediaItemsByLibrary :many
|
||||||
|
SELECT mi.*, 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;
|
||||||
|
|
||||||
|
-- name: UpdateMediaItem :one
|
||||||
|
UPDATE media_items SET
|
||||||
|
title = $2,
|
||||||
|
author = $3,
|
||||||
|
isbn = $4,
|
||||||
|
description = $5,
|
||||||
|
cover_image_path = $6,
|
||||||
|
series = $7,
|
||||||
|
series_number = $8,
|
||||||
|
tags = $9,
|
||||||
|
asin = $10,
|
||||||
|
date_published = $11,
|
||||||
|
publisher = $12,
|
||||||
|
contributors = $13,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: DeleteMediaItem :exec
|
||||||
|
DELETE FROM media_items WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: GetMediaItemByFilePath :one
|
||||||
|
SELECT * FROM media_items WHERE file_path = $1;
|
||||||
|
|
||||||
|
-- Backward compatibility - Ebooks queries (using views)
|
||||||
-- name: GetEbook :one
|
-- name: GetEbook :one
|
||||||
SELECT * FROM ebooks WHERE id = $1;
|
SELECT * FROM ebooks WHERE id = $1;
|
||||||
|
|
||||||
@@ -31,12 +151,12 @@ SELECT * FROM ebooks WHERE id = $1;
|
|||||||
SELECT * FROM ebooks ORDER BY created_at DESC LIMIT $1 OFFSET $2;
|
SELECT * FROM ebooks ORDER BY created_at DESC LIMIT $1 OFFSET $2;
|
||||||
|
|
||||||
-- name: CreateEbook :one
|
-- name: CreateEbook :one
|
||||||
INSERT INTO ebooks (title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, added_by_admin_id)
|
INSERT INTO media_items (library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, added_by_admin_id)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
|
VALUES ((SELECT id FROM libraries WHERE library_type_id = (SELECT id FROM library_types WHERE name = 'ebooks') LIMIT 1), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: UpdateEbook :one
|
-- name: UpdateEbook :one
|
||||||
UPDATE ebooks SET
|
UPDATE media_items SET
|
||||||
title = $2,
|
title = $2,
|
||||||
author = $3,
|
author = $3,
|
||||||
isbn = $4,
|
isbn = $4,
|
||||||
@@ -54,15 +174,15 @@ WHERE id = $1
|
|||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: DeleteEbook :exec
|
-- name: DeleteEbook :exec
|
||||||
DELETE FROM ebooks WHERE id = $1;
|
DELETE FROM media_items WHERE id = $1;
|
||||||
|
|
||||||
-- name: GetReadingProgress :one
|
-- name: GetReadingProgress :one
|
||||||
SELECT * FROM reading_progress WHERE ebook_id = $1 AND user_id = $2;
|
SELECT * FROM reading_progress WHERE media_item_id = $1 AND user_id = $2;
|
||||||
|
|
||||||
-- name: UpdateReadingProgress :one
|
-- name: UpdateReadingProgress :one
|
||||||
INSERT INTO reading_progress (ebook_id, user_id, current_page, total_pages, last_read_at)
|
INSERT INTO reading_progress (media_item_id, user_id, current_page, total_pages, last_read_at)
|
||||||
VALUES ($1, $2, $3, $4, NOW())
|
VALUES ($1, $2, $3, $4, NOW())
|
||||||
ON CONFLICT (ebook_id, user_id)
|
ON CONFLICT (media_item_id, user_id)
|
||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
current_page = EXCLUDED.current_page,
|
current_page = EXCLUDED.current_page,
|
||||||
total_pages = EXCLUDED.total_pages,
|
total_pages = EXCLUDED.total_pages,
|
||||||
@@ -70,7 +190,7 @@ DO UPDATE SET
|
|||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: DeleteReadingProgress :exec
|
-- name: DeleteReadingProgress :exec
|
||||||
DELETE FROM reading_progress WHERE ebook_id = $1 AND user_id = $2;
|
DELETE FROM reading_progress WHERE media_item_id = $1 AND user_id = $2;
|
||||||
|
|
||||||
-- name: UpdateUserTheme :exec
|
-- name: UpdateUserTheme :exec
|
||||||
UPDATE users SET theme = $2, updated_at = NOW() WHERE id = $1;
|
UPDATE users SET theme = $2, updated_at = NOW() WHERE id = $1;
|
||||||
@@ -96,10 +216,41 @@ UPDATE users SET scan_frequency_minutes = $2, auto_scan_enabled = $3, updated_at
|
|||||||
-- name: GetScanSettings :one
|
-- name: GetScanSettings :one
|
||||||
SELECT scan_frequency_minutes, auto_scan_enabled FROM users WHERE id = $1;
|
SELECT scan_frequency_minutes, auto_scan_enabled FROM users WHERE id = $1;
|
||||||
|
|
||||||
-- name: CreateEbookRating :one
|
-- name: CreateMediaRating :one
|
||||||
INSERT INTO ebook_ratings (ebook_id, user_id, rating)
|
INSERT INTO media_ratings (media_item_id, user_id, rating)
|
||||||
VALUES ($1, $2, $3)
|
VALUES ($1, $2, $3)
|
||||||
ON CONFLICT (ebook_id, user_id)
|
ON CONFLICT (media_item_id, user_id)
|
||||||
|
DO UPDATE SET
|
||||||
|
rating = EXCLUDED.rating,
|
||||||
|
updated_at = NOW()
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetMediaRating :one
|
||||||
|
SELECT * FROM media_ratings WHERE media_item_id = $1 AND user_id = $2;
|
||||||
|
|
||||||
|
-- name: GetMediaRatings :many
|
||||||
|
SELECT mr.*, 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;
|
||||||
|
|
||||||
|
-- name: UpdateMediaRating :one
|
||||||
|
UPDATE media_ratings SET
|
||||||
|
rating = $3,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE media_item_id = $1 AND user_id = $2
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: DeleteMediaRating :exec
|
||||||
|
DELETE FROM media_ratings WHERE media_item_id = $1 AND user_id = $2;
|
||||||
|
|
||||||
|
-- Backward compatibility - Ebooks ratings (using views)
|
||||||
|
-- name: CreateEbookRating :one
|
||||||
|
INSERT INTO media_ratings (media_item_id, user_id, rating)
|
||||||
|
SELECT $1, $2, $3
|
||||||
|
WHERE EXISTS (SELECT 1 FROM media_items WHERE id = $1)
|
||||||
|
ON CONFLICT (media_item_id, user_id)
|
||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
rating = EXCLUDED.rating,
|
rating = EXCLUDED.rating,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
@@ -116,23 +267,17 @@ WHERE er.ebook_id = $1
|
|||||||
ORDER BY er.created_at DESC;
|
ORDER BY er.created_at DESC;
|
||||||
|
|
||||||
-- name: UpdateEbookRating :one
|
-- name: UpdateEbookRating :one
|
||||||
UPDATE ebook_ratings SET
|
UPDATE media_ratings SET
|
||||||
rating = $3,
|
rating = $3,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE ebook_id = $1 AND user_id = $2
|
WHERE media_item_id = $1 AND user_id = $2
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: DeleteEbookRating :exec
|
-- name: DeleteEbookRating :exec
|
||||||
DELETE FROM ebook_ratings WHERE ebook_id = $1 AND user_id = $2;
|
DELETE FROM media_ratings WHERE media_item_id = $1 AND user_id = $2;
|
||||||
|
|
||||||
-- name: AddUserEbookFolder :one
|
-- Note: User ebook folders replaced by library folders system
|
||||||
INSERT INTO user_ebook_folders (user_id, folder_path) VALUES ($1, $2) RETURNING *;
|
-- Legacy folder management is now handled through libraries
|
||||||
|
|
||||||
-- name: GetUserEbookFolders :many
|
|
||||||
SELECT * FROM user_ebook_folders WHERE user_id = $1 ORDER BY created_at;
|
|
||||||
|
|
||||||
-- name: DeleteUserEbookFolder :one
|
|
||||||
DELETE FROM user_ebook_folders WHERE user_id = $1 AND folder_path = $2 RETURNING *;
|
|
||||||
|
|
||||||
-- name: GetEbookByFilePath :one
|
-- name: GetEbookByFilePath :one
|
||||||
SELECT * FROM ebooks WHERE file_path = $1;
|
SELECT * FROM ebooks WHERE file_path = $1;
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bookmann/internal/database"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LibraryService struct {
|
||||||
|
db *database.Queries
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLibraryService(db *database.Queries) *LibraryService {
|
||||||
|
return &LibraryService{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Library type definitions and file extensions
|
||||||
|
const (
|
||||||
|
LibraryTypeEbooks = "ebooks"
|
||||||
|
LibraryTypeComics = "comics"
|
||||||
|
LibraryTypeManga = "manga"
|
||||||
|
)
|
||||||
|
|
||||||
|
var AllowedExtensions = map[string][]string{
|
||||||
|
LibraryTypeEbooks: {".epub", ".pdf", ".mobi", ".azw", ".azw3", ".txt", ".rtf", ".doc", ".docx", ".lit", ".fb2", ".pdb"},
|
||||||
|
LibraryTypeComics: {".cbz", ".cbr", ".cb7", ".cbt", ".pdf"},
|
||||||
|
LibraryTypeManga: {".cbz", ".cbr", ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLibraryTypes retrieves all available library types
|
||||||
|
func (s *LibraryService) GetLibraryTypes(ctx context.Context) ([]database.LibraryTypes, error) {
|
||||||
|
return s.db.GetLibraryTypes(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLibrary creates a new library with the given parameters
|
||||||
|
func (s *LibraryService) CreateLibrary(ctx context.Context, name, description, libraryType string, adminID pgtype.UUID) (*database.Libraries, error) {
|
||||||
|
// Get library type ID
|
||||||
|
libType, err := s.db.GetLibraryTypeByName(ctx, libraryType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid library type: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create library
|
||||||
|
library, err := s.db.CreateLibrary(ctx, database.CreateLibraryParams{
|
||||||
|
Name: name,
|
||||||
|
Description: pgtype.Text{String: description, Valid: true},
|
||||||
|
LibraryTypeID: libType.ID,
|
||||||
|
CreatedByAdminID: adminID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create library: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &library, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLibrary retrieves a library by ID with type information
|
||||||
|
func (s *LibraryService) GetLibrary(ctx context.Context, libraryID pgtype.UUID) (*database.GetLibraryRow, error) {
|
||||||
|
library, err := s.db.GetLibrary(ctx, libraryID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &library, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLibraries retrieves all libraries
|
||||||
|
func (s *LibraryService) ListLibraries(ctx context.Context) ([]database.ListLibrariesRow, error) {
|
||||||
|
return s.db.ListLibraries(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLibrary updates an existing library
|
||||||
|
func (s *LibraryService) UpdateLibrary(ctx context.Context, libraryID pgtype.UUID, name, description string) (*database.Libraries, error) {
|
||||||
|
library, err := s.db.UpdateLibrary(ctx, database.UpdateLibraryParams{
|
||||||
|
ID: libraryID,
|
||||||
|
Name: name,
|
||||||
|
Description: pgtype.Text{String: description, Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &library, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLibrary deletes a library and all its associated data
|
||||||
|
func (s *LibraryService) DeleteLibrary(ctx context.Context, libraryID pgtype.UUID) error {
|
||||||
|
return s.db.DeleteLibrary(ctx, libraryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLibraryFolder adds a folder to a library
|
||||||
|
func (s *LibraryService) AddLibraryFolder(ctx context.Context, libraryID pgtype.UUID, folderPath string) (*database.LibraryFolders, error) {
|
||||||
|
folder, err := s.db.AddLibraryFolder(ctx, database.AddLibraryFolderParams{
|
||||||
|
LibraryID: libraryID,
|
||||||
|
FolderPath: folderPath,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &folder, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLibraryFolders retrieves all folders for a library
|
||||||
|
func (s *LibraryService) GetLibraryFolders(ctx context.Context, libraryID pgtype.UUID) ([]database.LibraryFolders, error) {
|
||||||
|
return s.db.GetLibraryFolders(ctx, libraryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLibraryFolder removes a folder from a library
|
||||||
|
func (s *LibraryService) DeleteLibraryFolder(ctx context.Context, libraryID pgtype.UUID, folderPath string) error {
|
||||||
|
_, err := s.db.DeleteLibraryFolder(ctx, database.DeleteLibraryFolderParams{
|
||||||
|
LibraryID: libraryID,
|
||||||
|
FolderPath: folderPath,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLibraryVisibility sets library visibility for a user
|
||||||
|
func (s *LibraryService) SetLibraryVisibility(ctx context.Context, userID, libraryID pgtype.UUID, isVisible bool) (*database.LibraryVisibility, error) {
|
||||||
|
visibility, err := s.db.SetLibraryVisibility(ctx, database.SetLibraryVisibilityParams{
|
||||||
|
UserID: userID,
|
||||||
|
LibraryID: libraryID,
|
||||||
|
IsVisible: isVisible,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &visibility, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserVisibleLibraries retrieves all libraries visible to a user
|
||||||
|
func (s *LibraryService) GetUserVisibleLibraries(ctx context.Context, userID pgtype.UUID) ([]database.GetUserVisibleLibrariesRow, error) {
|
||||||
|
return s.db.GetUserVisibleLibraries(ctx, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFileExtensionAllowed checks if a file extension is allowed for a library type
|
||||||
|
func (s *LibraryService) IsFileExtensionAllowed(libraryType, extension string) bool {
|
||||||
|
extensions, exists := AllowedExtensions[libraryType]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ext := range extensions {
|
||||||
|
if strings.EqualFold(ext, extension) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLibraryFileExtensions returns all allowed file extensions for a library type
|
||||||
|
func (s *LibraryService) GetLibraryFileExtensions(libraryType string) []string {
|
||||||
|
extensions, exists := AllowedExtensions[libraryType]
|
||||||
|
if !exists {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
return extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLibraryTypeFromFileExtension determines the library type based on file extension
|
||||||
|
func (s *LibraryService) GetLibraryTypeFromFileExtension(filename string) string {
|
||||||
|
ext := strings.ToLower(filepath.Ext(filename))
|
||||||
|
|
||||||
|
for libType, extensions := range AllowedExtensions {
|
||||||
|
for _, allowedExt := range extensions {
|
||||||
|
if ext == allowedExt {
|
||||||
|
return libType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateLibraryPath checks if a path is valid for the given library type
|
||||||
|
func (s *LibraryService) ValidateLibraryPath(libraryType, folderPath string) error {
|
||||||
|
// You could add more validation here like:
|
||||||
|
// - Check if path exists
|
||||||
|
// - Check if path is readable
|
||||||
|
// - Validate path format for specific library types
|
||||||
|
// - Check for appropriate file structures (e.g., for manga with image folders)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLibraryStats returns statistics for a library (media count, etc.)
|
||||||
|
func (s *LibraryService) GetLibraryStats(ctx context.Context, libraryID pgtype.UUID) (map[string]interface{}, error) {
|
||||||
|
// For now, return basic info. This can be expanded with more detailed stats
|
||||||
|
mediaItems, err := s.db.ListMediaItemsByLibrary(ctx, libraryID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"media_count": len(mediaItems),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user