feat: add library management API endpoints

- Add LibraryHandler with full CRUD operations
- Implement library creation with type validation
- Add library folder management endpoints
- Implement library visibility control system
- Add user library access management
- Include library statistics endpoint
- Support for admin and user-level operations

Provides modular foundation for multi-library system
This commit is contained in:
2026-01-28 11:00:40 -05:00
parent dbc3590cad
commit 87c0e309a3
+278
View File
@@ -0,0 +1,278 @@
package handlers
import (
"bookmann/internal/database"
"bookmann/internal/services"
"net/http"
"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)
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,
user.ID,
)
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, libraries)
}
// GetUserVisibleLibraries retrieves libraries visible to the current user
func (h *LibraryHandler) GetUserVisibleLibraries(c echo.Context) error {
user := c.Get("user").(*database.Users)
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()})
}
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
}