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:
2026-01-28 11:00:06 -05:00
parent 6f74216a02
commit dbc3590cad
6 changed files with 1599 additions and 197 deletions
+86 -15
View File
@@ -9,17 +9,28 @@ import (
)
type EbookRatings struct {
ID pgtype.UUID `db:"id" json:"id"`
EbookID pgtype.UUID `db:"ebook_id" json:"ebook_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"`
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"`
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 {
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"`
@@ -40,22 +51,82 @@ type Ebooks struct {
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 {
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"`
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"`
}
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 {
ID pgtype.UUID `db:"id" json:"id"`
Email string `db:"email" json:"email"`
+39 -7
View File
@@ -11,33 +11,65 @@ import (
)
type Querier interface {
AddUserEbookFolder(ctx context.Context, arg AddUserEbookFolderParams) (UserEbookFolders, error)
CreateEbook(ctx context.Context, arg CreateEbookParams) (Ebooks, error)
CreateEbookRating(ctx context.Context, arg CreateEbookRatingParams) (EbookRatings, error)
// Library Folders queries
AddLibraryFolder(ctx context.Context, arg AddLibraryFolderParams) (LibraryFolders, 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)
DeleteEbook(ctx context.Context, id pgtype.UUID) 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
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)
// 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)
GetEbookRating(ctx context.Context, arg GetEbookRatingParams) (EbookRatings, 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)
GetScanSettings(ctx context.Context, id pgtype.UUID) (GetScanSettingsRow, error)
GetUser(ctx context.Context, id pgtype.UUID) (GetUserRow, error)
GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error)
GetUserByEmailOrUsername(ctx context.Context, email string) (GetUserByEmailOrUsernameRow, 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)
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)
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)
UpdateEbook(ctx context.Context, arg UpdateEbookParams) (Ebooks, error)
UpdateEbookRating(ctx context.Context, arg UpdateEbookRatingParams) (EbookRatings, error)
// Library Visibility queries
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
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
UpdateReadingProgress(ctx context.Context, arg UpdateReadingProgressParams) (ReadingProgress, error)
UpdateScanSettings(ctx context.Context, arg UpdateScanSettingsParams) error
File diff suppressed because it is too large Load Diff
+167 -22
View File
@@ -24,6 +24,126 @@ SELECT password_hash FROM users WHERE id = $1;
-- name: ListUsers :many
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
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;
-- 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)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
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 ((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 *;
-- name: UpdateEbook :one
UPDATE ebooks SET
UPDATE media_items SET
title = $2,
author = $3,
isbn = $4,
@@ -54,15 +174,15 @@ WHERE id = $1
RETURNING *;
-- name: DeleteEbook :exec
DELETE FROM ebooks WHERE id = $1;
DELETE FROM media_items WHERE id = $1;
-- 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
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())
ON CONFLICT (ebook_id, user_id)
ON CONFLICT (media_item_id, user_id)
DO UPDATE SET
current_page = EXCLUDED.current_page,
total_pages = EXCLUDED.total_pages,
@@ -70,7 +190,7 @@ DO UPDATE SET
RETURNING *;
-- 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
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
SELECT scan_frequency_minutes, auto_scan_enabled FROM users WHERE id = $1;
-- name: CreateEbookRating :one
INSERT INTO ebook_ratings (ebook_id, user_id, rating)
-- name: CreateMediaRating :one
INSERT INTO media_ratings (media_item_id, user_id, rating)
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
rating = EXCLUDED.rating,
updated_at = NOW()
@@ -116,23 +267,17 @@ WHERE er.ebook_id = $1
ORDER BY er.created_at DESC;
-- name: UpdateEbookRating :one
UPDATE ebook_ratings SET
UPDATE media_ratings SET
rating = $3,
updated_at = NOW()
WHERE ebook_id = $1 AND user_id = $2
WHERE media_item_id = $1 AND user_id = $2
RETURNING *;
-- 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
INSERT INTO user_ebook_folders (user_id, folder_path) VALUES ($1, $2) RETURNING *;
-- 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 *;
-- Note: User ebook folders replaced by library folders system
-- Legacy folder management is now handled through libraries
-- name: GetEbookByFilePath :one
SELECT * FROM ebooks WHERE file_path = $1;
+200
View File
@@ -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
}