diff --git a/internal/handlers/library.go b/internal/handlers/library.go new file mode 100644 index 0000000..b3cf96a --- /dev/null +++ b/internal/handlers/library.go @@ -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 +}