Files
silo-server/internal/api/handlers/library_collection_groups.go

252 lines
7.5 KiB
Go

package handlers
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/Silo-Server/silo-server/internal/catalog"
"github.com/Silo-Server/silo-server/internal/models"
)
type LibraryCollectionGroupHandler struct {
groupRepo *catalog.LibraryCollectionGroupRepository
collRepo *catalog.LibraryCollectionRepository
pool *pgxpool.Pool
}
func NewLibraryCollectionGroupHandler(
groupRepo *catalog.LibraryCollectionGroupRepository,
collRepo *catalog.LibraryCollectionRepository,
pool *pgxpool.Pool,
) *LibraryCollectionGroupHandler {
return &LibraryCollectionGroupHandler{groupRepo: groupRepo, collRepo: collRepo, pool: pool}
}
type createGroupRequest struct {
Name string `json:"name"`
Slug *string `json:"slug,omitempty"`
DefaultSortMode *string `json:"default_sort_mode,omitempty"`
}
type updateGroupRequest struct {
Name *string `json:"name,omitempty"`
Slug *string `json:"slug,omitempty"`
DefaultSortMode *string `json:"default_sort_mode,omitempty"`
}
type listGroupsResponse struct {
Groups []libraryCollectionGroupResponse `json:"groups"`
UngroupedSortOrder int `json:"ungrouped_sort_order"`
}
type collectionGroupReorderRequest struct {
IDs []string `json:"ids"`
}
func (h *LibraryCollectionGroupHandler) HandleListGroups(w http.ResponseWriter, r *http.Request) {
libraryID, ok := parseLibraryIDFromRouteParam(w, r, "libraryID")
if !ok {
return
}
groups, err := h.groupRepo.ListByLibrary(r.Context(), libraryID)
if err != nil {
writeError(w, http.StatusInternalServerError, "internal_error", "Failed to load groups")
return
}
if groups == nil {
groups = []models.LibraryCollectionGroup{}
}
ungroupedSortOrder := 9999
if h.groupRepo != nil {
var err error
if ungroupedSortOrder, err = h.groupRepo.GetUngroupedSortOrder(r.Context(), libraryID); err != nil {
ungroupedSortOrder = 9999
}
}
writeJSON(w, http.StatusOK, listGroupsResponse{
Groups: toLibraryCollectionGroupResponses(groups),
UngroupedSortOrder: ungroupedSortOrder,
})
}
func (h *LibraryCollectionGroupHandler) HandleCreateGroup(w http.ResponseWriter, r *http.Request) {
libraryID, ok := parseLibraryIDFromRouteParam(w, r, "libraryID")
if !ok {
return
}
var req createGroupRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "bad_request", "Invalid JSON body")
return
}
if req.Name == "" {
writeError(w, http.StatusBadRequest, "bad_request", "name required")
return
}
in := catalog.CreateLibraryCollectionGroupInput{
LibraryID: libraryID,
Name: req.Name,
Kind: models.GroupKindRegular,
}
if req.Slug != nil {
in.Slug = *req.Slug
}
if req.DefaultSortMode != nil {
in.DefaultSortMode = models.GroupSortMode(*req.DefaultSortMode)
}
g, err := h.groupRepo.Create(r.Context(), in)
if err != nil {
writeError(w, http.StatusInternalServerError, "internal_error", "Failed to create group")
return
}
writeJSON(w, http.StatusCreated, toLibraryCollectionGroupResponse(*g))
}
func (h *LibraryCollectionGroupHandler) HandleUpdateGroup(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
if id == "" {
writeError(w, http.StatusBadRequest, "bad_request", "id required")
return
}
var req updateGroupRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "bad_request", "Invalid JSON body")
return
}
in := catalog.UpdateLibraryCollectionGroupInput{Name: req.Name, Slug: req.Slug}
if req.DefaultSortMode != nil {
mode := models.GroupSortMode(*req.DefaultSortMode)
in.DefaultSortMode = &mode
}
g, err := h.groupRepo.Update(r.Context(), id, in)
if err != nil {
if errors.Is(err, catalog.ErrLibraryCollectionGroupNotFound) {
writeError(w, http.StatusNotFound, "not_found", "Group not found")
return
}
writeError(w, http.StatusInternalServerError, "internal_error", "Failed to update group")
return
}
writeJSON(w, http.StatusOK, toLibraryCollectionGroupResponse(*g))
}
func (h *LibraryCollectionGroupHandler) HandleDeleteGroup(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
if id == "" {
writeError(w, http.StatusBadRequest, "bad_request", "id required")
return
}
if err := h.groupRepo.Delete(r.Context(), id); err != nil {
if errors.Is(err, catalog.ErrLibraryCollectionGroupNotFound) {
writeError(w, http.StatusNotFound, "not_found", "Group not found")
return
}
writeError(w, http.StatusBadRequest, "bad_request", err.Error())
return
}
w.WriteHeader(http.StatusNoContent)
}
func (h *LibraryCollectionGroupHandler) HandleReorderGroups(w http.ResponseWriter, r *http.Request) {
libraryID, ok := parseLibraryIDFromRouteParam(w, r, "libraryID")
if !ok {
return
}
var req collectionGroupReorderRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "bad_request", "Invalid JSON body")
return
}
if err := h.groupRepo.Reorder(r.Context(), libraryID, req.IDs); err != nil {
writeError(w, http.StatusBadRequest, "bad_request", err.Error())
return
}
w.WriteHeader(http.StatusNoContent)
}
func (h *LibraryCollectionGroupHandler) HandleReorderCollectionsInGroup(w http.ResponseWriter, r *http.Request) {
groupID := chi.URLParam(r, "groupID")
if groupID == "" {
writeError(w, http.StatusBadRequest, "bad_request", "groupID required")
return
}
moveOmitted := r.URL.Query().Get("move_omitted")
strict := moveOmitted != "" && moveOmitted != "ungrouped"
var libraryID int
var targetGroupID *string
if groupID == "ungrouped" {
raw := r.URL.Query().Get("library_id")
if raw == "" {
writeError(w, http.StatusBadRequest, "bad_request", "library_id query param required for ungrouped reorder")
return
}
id, err := strconv.Atoi(raw)
if err != nil || id <= 0 {
writeError(w, http.StatusBadRequest, "bad_request", "invalid library_id")
return
}
libraryID = id
} else {
g, err := h.groupRepo.GetByID(r.Context(), groupID)
if err != nil {
if errors.Is(err, catalog.ErrLibraryCollectionGroupNotFound) {
writeError(w, http.StatusNotFound, "not_found", "Group not found")
return
}
writeError(w, http.StatusInternalServerError, "internal_error", "Failed to load group")
return
}
libraryID = g.LibraryID
targetGroupID = &g.ID
}
var req collectionGroupReorderRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "bad_request", "Invalid JSON body")
return
}
err := h.collRepo.MoveAndReorder(r.Context(), catalog.MoveAndReorderInput{
LibraryID: libraryID,
TargetGroupID: targetGroupID,
OrderedIDs: req.IDs,
Strict: strict,
})
var strictErr *catalog.StrictReorderError
if errors.As(err, &strictErr) {
writeJSON(w, http.StatusBadRequest, map[string]any{
"error": "strict_reorder_rejected",
"missing_ids": strictErr.MissingIDs,
})
return
}
if err != nil {
writeError(w, http.StatusBadRequest, "bad_request", err.Error())
return
}
w.WriteHeader(http.StatusNoContent)
}
func parseLibraryIDFromRouteParam(w http.ResponseWriter, r *http.Request, param string) (int, bool) {
raw := chi.URLParam(r, param)
if raw == "" {
writeError(w, http.StatusBadRequest, "bad_request", "library id required")
return 0, false
}
id, err := strconv.Atoi(raw)
if err != nil || id <= 0 {
writeError(w, http.StatusBadRequest, "bad_request", "invalid library id")
return 0, false
}
return id, true
}