Files
bookhoard/internal/handlers/library.go
T
john-okeefe ff44115be2 fix: correct user context type extraction in CreateLibrary handler
- Use c.Get("user").(database.Users) instead of c.Get("user_id").(string)
- Extract userUUID from user.ID.Bytes ([16]byte)
- Properly convert to pgtype.UUID for service layer
- Remove unnecessary uuid.Parse call

This fixes 500 Internal Server Error when creating libraries via API.
The JWT middleware sets user as database.Users struct, not string.

Related to Bruno Create Library request testing.
2026-01-29 21:16:26 -05:00

294 lines
8.9 KiB
Go

package handlers
import (
"bookmann/internal/database"
"bookmann/internal/services"
"net/http"
"os"
"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 {
userID := c.Get("user_id").(string)
userUUID, err := uuid.Parse(userID)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"})
}
libraries, err := h.libraryService.GetUserVisibleLibraries(c.Request().Context(), pgtype.UUID{Bytes: userUUID, Valid: true})
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()})
}
// Validate that folder path exists and is accessible
_, err = os.Stat(req.FolderPath)
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"})
}
folder, err := h.libraryService.AddLibraryFolder(
c.Request().Context(),
libraryID,
req.FolderPath,
)
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
}