Files
bookhoard/internal/services/library_service.go
T
john-okeefe dbc3590cad 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
2026-01-28 11:00:06 -05:00

201 lines
6.2 KiB
Go

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
}