Update all handler functions to use *echo.Context (pointer) instead of echo.Context (value) as required by Echo v5. Changes across all handler files: - analytics.go: Update handler signatures - auth.go: Update authentication handler signatures - book_matching.go: Update matching handler signatures - collections.go: Update collection handler signatures - collections_preview_test.go: Update test signatures - commonhandlers.go: Update common handler signatures - conflicts.go: Update conflict handler signatures - context.go: Update context handler signatures - dashboard.go: Update dashboard handler signatures - devices.go: Update device handler signatures - jobs.go: Update job handler signatures - kobo.go: Update Kobo handler signatures - koreader.go: Update Koreader handler signatures - library.go: Update library handler signatures - matching.go: Update matching handler signatures - media.go: Update media handler signatures - opds.go: Update OPDS handler signatures - progress.go: Update progress handler signatures - queue.go: Update queue handler signatures - refresh_token.go: Update token handler signatures - scanner.go: Update scanner handler signatures - sidecar.go: Update sidecar handler signatures - sync.go: Update sync handler signatures - system_settings.go: Update settings handler signatures - websocket.go: Update WebSocket handler signatures All handlers now properly implement Echo v5's pointer-based context pattern. This change is necessary for type safety and compatibility with Echo v5's improved context handling and WebSocket support.
340 lines
10 KiB
Go
340 lines
10 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bookhoard/internal/database"
|
|
"bookhoard/internal/services"
|
|
"context"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"github.com/labstack/echo/v5"
|
|
)
|
|
|
|
type LibraryHandler struct {
|
|
db *database.Queries
|
|
libraryService *services.LibraryService
|
|
}
|
|
|
|
func NewLibraryHandler(db *database.Queries) *LibraryHandler {
|
|
return &LibraryHandler{
|
|
db: db,
|
|
libraryService: services.NewLibraryService(db),
|
|
}
|
|
}
|
|
|
|
// Request/Response types
|
|
type CreateLibraryRequest struct {
|
|
Name string `json:"name" validate:"required"`
|
|
Description string `json:"description"`
|
|
Type string `json:"type" validate:"required"`
|
|
}
|
|
|
|
type UpdateLibraryRequest struct {
|
|
Name string `json:"name" validate:"required"`
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
type AddLibraryFolderRequest struct {
|
|
FolderPath string `json:"folder_path" validate:"required"`
|
|
}
|
|
|
|
type SetLibraryVisibilityRequest struct {
|
|
LibraryID string `json:"library_id" validate:"required"`
|
|
IsVisible bool `json:"is_visible"`
|
|
}
|
|
|
|
// GetLibraryTypes retrieves all available library types
|
|
func (h *LibraryHandler) GetLibraryTypes(c *echo.Context) error {
|
|
types, err := h.libraryService.GetLibraryTypes(c.Request().Context())
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
}
|
|
return c.JSON(http.StatusOK, types)
|
|
}
|
|
|
|
// CreateLibrary creates a new library
|
|
func (h *LibraryHandler) CreateLibrary(c *echo.Context) error {
|
|
user := c.Get("user").(database.Users)
|
|
userUUID := user.ID.Bytes
|
|
|
|
var req CreateLibraryRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
|
}
|
|
if err := c.Validate(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
library, err := h.libraryService.CreateLibrary(
|
|
c.Request().Context(),
|
|
req.Name,
|
|
req.Description,
|
|
req.Type,
|
|
pgtype.UUID{Bytes: userUUID, Valid: true},
|
|
)
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
return c.JSON(http.StatusCreated, library)
|
|
}
|
|
|
|
// GetLibrary retrieves a specific library
|
|
func (h *LibraryHandler) GetLibrary(c *echo.Context) error {
|
|
libraryID, err := parseUUID(c.Param("id"))
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library id"})
|
|
}
|
|
|
|
library, err := h.libraryService.GetLibrary(c.Request().Context(), libraryID)
|
|
if err != nil {
|
|
return c.JSON(http.StatusNotFound, map[string]string{"error": "library not found"})
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, library)
|
|
}
|
|
|
|
// ListLibraries retrieves all libraries (admin only)
|
|
func (h *LibraryHandler) ListLibraries(c *echo.Context) error {
|
|
libraries, err := h.libraryService.ListLibraries(c.Request().Context())
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, map[string]interface{}{"data": libraries})
|
|
}
|
|
|
|
// GetUserVisibleLibraries retrieves libraries visible to the current user
|
|
func (h *LibraryHandler) GetUserVisibleLibraries(c *echo.Context) error {
|
|
user := MustGetAuthenticatedUser(c)
|
|
|
|
libraries, err := h.libraryService.GetUserVisibleLibraries(c.Request().Context(), user.ID)
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, libraries)
|
|
}
|
|
|
|
// UpdateLibrary updates an existing library
|
|
func (h *LibraryHandler) UpdateLibrary(c *echo.Context) error {
|
|
libraryID, err := parseUUID(c.Param("id"))
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library id"})
|
|
}
|
|
|
|
var req UpdateLibraryRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
|
}
|
|
if err := c.Validate(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
library, err := h.libraryService.UpdateLibrary(
|
|
c.Request().Context(),
|
|
libraryID,
|
|
req.Name,
|
|
req.Description,
|
|
)
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, library)
|
|
}
|
|
|
|
// DeleteLibrary deletes a library
|
|
func (h *LibraryHandler) DeleteLibrary(c *echo.Context) error {
|
|
libraryID, err := parseUUID(c.Param("id"))
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library id"})
|
|
}
|
|
|
|
err = h.libraryService.DeleteLibrary(c.Request().Context(), libraryID)
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
// AddLibraryFolder adds a folder to a library
|
|
func (h *LibraryHandler) AddLibraryFolder(c *echo.Context) error {
|
|
libraryID, err := parseUUID(c.Param("id"))
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library id"})
|
|
}
|
|
|
|
var req AddLibraryFolderRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
|
}
|
|
if err := c.Validate(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
// Path traversal protection - detect and block .. in path
|
|
if strings.Contains(req.FolderPath, "..") {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "path traversal not allowed"})
|
|
}
|
|
|
|
// Clean the path to remove any redundant separators or . references
|
|
cleanPath := filepath.Clean(req.FolderPath)
|
|
|
|
// Validate that folder path exists and is accessible
|
|
fileInfo, err := os.Stat(cleanPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "folder path does not exist"})
|
|
}
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "folder is not accessible"})
|
|
}
|
|
|
|
// Ensure it's actually a directory, not a file
|
|
if !fileInfo.IsDir() {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "path must be a directory"})
|
|
}
|
|
|
|
folder, err := h.libraryService.AddLibraryFolder(
|
|
c.Request().Context(),
|
|
libraryID,
|
|
cleanPath,
|
|
)
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
return c.JSON(http.StatusCreated, folder)
|
|
}
|
|
|
|
// GetLibraryFolders retrieves all folders for a library
|
|
func (h *LibraryHandler) GetLibraryFolders(c *echo.Context) error {
|
|
libraryID, err := parseUUID(c.Param("id"))
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library id"})
|
|
}
|
|
|
|
folders, err := h.libraryService.GetLibraryFolders(c.Request().Context(), libraryID)
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, folders)
|
|
}
|
|
|
|
// DeleteLibraryFolder removes a folder from a library
|
|
func (h *LibraryHandler) DeleteLibraryFolder(c *echo.Context) error {
|
|
libraryID, err := parseUUID(c.Param("id"))
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library id"})
|
|
}
|
|
|
|
var req AddLibraryFolderRequest // reuse same structure for folder_path
|
|
if err := c.Bind(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
|
}
|
|
if err := c.Validate(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
err = h.libraryService.DeleteLibraryFolder(c.Request().Context(), libraryID, req.FolderPath)
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
// BrowseDirectories returns directory listings for folder browser UI
|
|
func (h *LibraryHandler) BrowseDirectories(c *echo.Context) error {
|
|
path := c.QueryParam("path")
|
|
if path == "" {
|
|
path = "/" // Start from root
|
|
}
|
|
|
|
dirs, currentPath, parentPath, err := h.libraryService.BrowseDirectories(c.Request().Context(), path)
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, map[string]interface{}{
|
|
"current_path": currentPath,
|
|
"parent_path": parentPath,
|
|
"directories": dirs,
|
|
})
|
|
}
|
|
|
|
// SetLibraryVisibility sets library visibility for a user
|
|
func (h *LibraryHandler) SetLibraryVisibility(c *echo.Context) error {
|
|
user := c.Get("user").(database.Users)
|
|
|
|
var req SetLibraryVisibilityRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
|
}
|
|
if err := c.Validate(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
libraryID, err := parseUUID(req.LibraryID)
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library id"})
|
|
}
|
|
|
|
visibility, err := h.libraryService.SetLibraryVisibility(
|
|
c.Request().Context(),
|
|
user.ID,
|
|
libraryID,
|
|
req.IsVisible,
|
|
)
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, visibility)
|
|
}
|
|
|
|
// GetLibraryStats retrieves statistics for a library
|
|
func (h *LibraryHandler) GetLibraryStats(c *echo.Context) error {
|
|
libraryID, err := parseUUID(c.Param("id"))
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library id"})
|
|
}
|
|
|
|
stats, err := h.libraryService.GetLibraryStats(c.Request().Context(), libraryID)
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, stats)
|
|
}
|
|
|
|
// Helper function
|
|
func parseUUID(uuidStr string) (pgtype.UUID, error) {
|
|
parsedUUID, err := uuid.Parse(uuidStr)
|
|
if err != nil {
|
|
return pgtype.UUID{}, err
|
|
}
|
|
return pgtype.UUID{Bytes: [16]byte(parsedUUID), Valid: true}, nil
|
|
}
|
|
|
|
// GetUserVisibleLibrariesData returns libraries for SSR (not JSON response)
|
|
func (h *LibraryHandler) GetUserVisibleLibrariesData(ctx context.Context, userID pgtype.UUID) ([]database.GetUserVisibleLibrariesRow, error) {
|
|
return h.libraryService.GetUserVisibleLibraries(ctx, userID)
|
|
}
|
|
|
|
// GetLibraryTypeData returns all library types for SSR (not JSON response)
|
|
func (h *LibraryHandler) GetLibraryTypeData(ctx context.Context) ([]database.LibraryTypes, error) {
|
|
return h.libraryService.GetLibraryTypes(ctx)
|
|
}
|
|
|
|
// ListLibrariesData returns all libraries for SSR (admin only, not JSON response)
|
|
func (h *LibraryHandler) ListLibrariesData(ctx context.Context) ([]database.ListLibrariesRow, error) {
|
|
return h.libraryService.ListLibraries(ctx)
|
|
}
|