- Add GetUserVisibleLibrariesData() method for server-side rendering - Add GetLibraryTypeData() method for SSR type fetching - These helpers return data directly instead of JSON responses - Enables Hybrid SSR pattern while preserving API endpoints - Add context import for new methods
316 lines
9.7 KiB
Go
316 lines
9.7 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/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
|
|
}
|
|
|
|
// 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)
|
|
}
|