Files
notely/backend/internal/interfaces/handlers/category_handler.go
2026-03-26 16:27:14 +00:00

213 lines
5.8 KiB
Go

package handlers
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
"go.mongodb.org/mongo-driver/v2/bson"
"gitea.hostxtra.co.uk/mrhid6/notely/backend/internal/application/dto"
"gitea.hostxtra.co.uk/mrhid6/notely/backend/internal/application/services"
"gitea.hostxtra.co.uk/mrhid6/notely/backend/internal/interfaces/middleware"
)
// CategoryHandler handles category endpoints
type CategoryHandler struct {
categoryService *services.CategoryService
}
// NewCategoryHandler creates a new category handler
func NewCategoryHandler(categoryService *services.CategoryService) *CategoryHandler {
return &CategoryHandler{categoryService: categoryService}
}
// GetCategoryTree returns the full category tree for a space
func (h *CategoryHandler) GetCategoryTree(w http.ResponseWriter, r *http.Request) {
userID, err := getUserObjectID(r)
if err != nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
vars := mux.Vars(r)
spaceID, err := bson.ObjectIDFromHex(vars["spaceId"])
if err != nil {
http.Error(w, "invalid space id", http.StatusBadRequest)
return
}
tree, err := h.categoryService.GetCategoryTree(r.Context(), spaceID, userID)
if err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(tree)
}
// CreateCategory creates a new category in a space
func (h *CategoryHandler) CreateCategory(w http.ResponseWriter, r *http.Request) {
userID, err := getUserObjectID(r)
if err != nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
vars := mux.Vars(r)
spaceID, err := bson.ObjectIDFromHex(vars["spaceId"])
if err != nil {
http.Error(w, "invalid space id", http.StatusBadRequest)
return
}
var req dto.CreateCategoryRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid request body", http.StatusBadRequest)
return
}
category, err := h.categoryService.CreateCategory(r.Context(), spaceID, userID, &req)
if err != nil {
if err.Error() == "unauthorized" {
http.Error(w, err.Error(), http.StatusForbidden)
} else {
http.Error(w, err.Error(), http.StatusBadRequest)
}
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(category)
}
// UpdateCategory updates a category
func (h *CategoryHandler) UpdateCategory(w http.ResponseWriter, r *http.Request) {
userID, err := getUserObjectID(r)
if err != nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
vars := mux.Vars(r)
spaceID, err := bson.ObjectIDFromHex(vars["spaceId"])
if err != nil {
http.Error(w, "invalid space id", http.StatusBadRequest)
return
}
categoryID, err := bson.ObjectIDFromHex(vars["categoryId"])
if err != nil {
http.Error(w, "invalid category id", http.StatusBadRequest)
return
}
var req dto.UpdateCategoryRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid request body", http.StatusBadRequest)
return
}
category, err := h.categoryService.UpdateCategory(r.Context(), categoryID, spaceID, userID, &req)
if err != nil {
if err.Error() == "unauthorized" {
http.Error(w, err.Error(), http.StatusForbidden)
} else {
http.Error(w, err.Error(), http.StatusNotFound)
}
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(category)
}
// DeleteCategory deletes a category
func (h *CategoryHandler) DeleteCategory(w http.ResponseWriter, r *http.Request) {
userID, err := getUserObjectID(r)
if err != nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
vars := mux.Vars(r)
spaceID, err := bson.ObjectIDFromHex(vars["spaceId"])
if err != nil {
http.Error(w, "invalid space id", http.StatusBadRequest)
return
}
categoryID, err := bson.ObjectIDFromHex(vars["categoryId"])
if err != nil {
http.Error(w, "invalid category id", http.StatusBadRequest)
return
}
var moveNotesTo *string
if v := r.URL.Query().Get("moveNotesTo"); v != "" {
moveNotesTo = &v
}
if err := h.categoryService.DeleteCategory(r.Context(), categoryID, spaceID, userID, moveNotesTo); err != nil {
if err.Error() == "unauthorized" {
http.Error(w, err.Error(), http.StatusForbidden)
} else {
http.Error(w, err.Error(), http.StatusNotFound)
}
return
}
w.WriteHeader(http.StatusNoContent)
}
// MoveCategory moves a category to a new parent
func (h *CategoryHandler) MoveCategory(w http.ResponseWriter, r *http.Request) {
userID, err := getUserObjectID(r)
if err != nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
vars := mux.Vars(r)
spaceID, err := bson.ObjectIDFromHex(vars["spaceId"])
if err != nil {
http.Error(w, "invalid space id", http.StatusBadRequest)
return
}
categoryID, err := bson.ObjectIDFromHex(vars["categoryId"])
if err != nil {
http.Error(w, "invalid category id", http.StatusBadRequest)
return
}
var body struct {
ParentID *string `json:"parent_id"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "invalid request body", http.StatusBadRequest)
return
}
category, err := h.categoryService.MoveCategory(r.Context(), categoryID, spaceID, userID, body.ParentID)
if err != nil {
if err.Error() == "unauthorized" {
http.Error(w, err.Error(), http.StatusForbidden)
} else {
http.Error(w, err.Error(), http.StatusBadRequest)
}
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(category)
}
// getUserObjectID extracts the user ObjectID from the request context
func getUserObjectID(r *http.Request) (bson.ObjectID, error) {
userIDStr, ok := r.Context().Value(middleware.UserIDKey).(string)
if !ok || userIDStr == "" {
return bson.NilObjectID, http.ErrNoCookie
}
return bson.ObjectIDFromHex(userIDStr)
}