Files
bookhoard/internal/handlers/library.go
T
john-okeefe 00a083b60b Rename backend code references: Bookmann → Bookhoard
Backend changes:
- Update import paths: bookmann/internal → bookhoard/internal
- Rename struct fields: BookmannUUID → BookhoardUUID
- Update handler function names: mapContentIdToBookmannUUID → mapContentIdToBookhoardUUID
- Update HTTP response headers: X-Bookmann-* → X-Bookhoard-*
- Update service and middleware references
- Update main.go imports and references

This is part 2 of the project rename to Bookhoard.
2026-02-01 16:11:54 -05:00

305 lines
9.2 KiB
Go

package handlers
import (
"bookhoard/internal/database"
"bookhoard/internal/services"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/labstack/echo/v4"
)
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)
}
// 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
}