package mail import ( "errors" "net/http" "github.com/go-chi/chi/v5" "github.com/ultisuite/ulti-backend/internal/api/apiresponse" "github.com/ultisuite/ulti-backend/internal/api/apivalidate" "github.com/ultisuite/ulti-backend/internal/api/middleware" "github.com/ultisuite/ulti-backend/internal/api/query" ) // FolderLabelRoutes registers folder and user-label endpoints without modifying Handler.Routes(). func (h *Handler) FolderLabelRoutes() chi.Router { r := chi.NewRouter() r.Get("/folders", h.ListFolders) r.Post("/folders", h.CreateFolder) r.Get("/folders/{folderID}", h.GetFolder) r.Put("/folders/{folderID}", h.UpdateFolder) r.Delete("/folders/{folderID}", h.DeleteFolder) r.Get("/labels", h.ListUserLabels) r.Post("/labels", h.CreateUserLabel) r.Put("/labels/{labelID}", h.UpdateUserLabel) r.Delete("/labels/{labelID}", h.DeleteUserLabel) return r } func (h *Handler) ListFolders(w http.ResponseWriter, r *http.Request) { claims := middleware.ClaimsFromContext(r.Context()) accountID := r.URL.Query().Get("account_id") if verr := validateListFoldersAccountID(accountID); verr != nil { apivalidate.WriteValidationError(w, r, verr) return } params, err := query.ParseListRequest(r) if err != nil { apivalidate.WriteQueryError(w, r, err) return } result, err := h.svc.ListFolders(r.Context(), claims.Sub, accountID, params) if err != nil { if errors.Is(err, ErrAccountNotFound) { apivalidate.WriteNotFound(w, r, "account not found") return } h.logger.Error("list folders", "error", err) apivalidate.WriteInternal(w, r) return } apiresponse.WriteJSON(w, http.StatusOK, result) } func (h *Handler) GetFolder(w http.ResponseWriter, r *http.Request) { claims := middleware.ClaimsFromContext(r.Context()) folder, err := h.svc.GetFolder(r.Context(), claims.Sub, chi.URLParam(r, "folderID")) if err != nil { if errors.Is(err, ErrNotFound) { apivalidate.WriteNotFound(w, r, "not found") return } h.logger.Error("get folder", "error", err) apivalidate.WriteInternal(w, r) return } apiresponse.WriteJSON(w, http.StatusOK, folder) } func (h *Handler) CreateFolder(w http.ResponseWriter, r *http.Request) { claims := middleware.ClaimsFromContext(r.Context()) userID, err := h.svc.ResolveUserID(r.Context(), claims.Sub) if err != nil { h.writeUserResolveError(w, r, err) return } var req createFolderRequest if err := apivalidate.DecodeJSON(w, r, maxFoldersRequestBody, &req); err != nil { return } if verr := validateCreateFolder(&req); verr != nil { apivalidate.WriteValidationError(w, r, verr) return } id, err := h.svc.CreateFolder(r.Context(), userID, &req) if err != nil { if errors.Is(err, ErrAccountNotFound) { apivalidate.WriteNotFound(w, r, "account not found") return } if errors.Is(err, ErrDuplicateFolder) { apiresponse.WriteError(w, r, http.StatusConflict, apiresponse.CodeInvalidRequest, "folder remote_name already exists", nil) return } h.logger.Error("create folder", "error", err) apivalidate.WriteInternal(w, r) return } apiresponse.WriteJSON(w, http.StatusCreated, map[string]string{"id": id}) } func (h *Handler) UpdateFolder(w http.ResponseWriter, r *http.Request) { claims := middleware.ClaimsFromContext(r.Context()) var req updateFolderRequest if err := apivalidate.DecodeJSON(w, r, maxFoldersRequestBody, &req); err != nil { return } if verr := validateUpdateFolder(&req); verr != nil { apivalidate.WriteValidationError(w, r, verr) return } if err := h.svc.UpdateFolder(r.Context(), claims.Sub, chi.URLParam(r, "folderID"), &req); err != nil { if errors.Is(err, ErrNotFound) { apivalidate.WriteNotFound(w, r, "not found") return } if errors.Is(err, ErrDuplicateFolder) { apiresponse.WriteError(w, r, http.StatusConflict, apiresponse.CodeInvalidRequest, "folder remote_name already exists", nil) return } h.logger.Error("update folder", "error", err) apivalidate.WriteInternal(w, r) return } w.WriteHeader(http.StatusNoContent) } func (h *Handler) DeleteFolder(w http.ResponseWriter, r *http.Request) { claims := middleware.ClaimsFromContext(r.Context()) if err := h.svc.DeleteFolder(r.Context(), claims.Sub, chi.URLParam(r, "folderID")); err != nil { if errors.Is(err, ErrNotFound) { apivalidate.WriteNotFound(w, r, "not found") return } if errors.Is(err, ErrFolderProtected) { apiresponse.WriteError(w, r, http.StatusBadRequest, apiresponse.CodeInvalidRequest, "system folder cannot be deleted", nil) return } h.logger.Error("delete folder", "error", err) apivalidate.WriteInternal(w, r) return } w.WriteHeader(http.StatusNoContent) } func (h *Handler) ListUserLabels(w http.ResponseWriter, r *http.Request) { claims := middleware.ClaimsFromContext(r.Context()) params, err := query.ParseListRequest(r) if err != nil { apivalidate.WriteQueryError(w, r, err) return } result, err := h.svc.ListUserLabels(r.Context(), claims.Sub, params) if err != nil { h.logger.Error("list user labels", "error", err) apivalidate.WriteInternal(w, r) return } apiresponse.WriteJSON(w, http.StatusOK, result) } func (h *Handler) CreateUserLabel(w http.ResponseWriter, r *http.Request) { claims := middleware.ClaimsFromContext(r.Context()) var req createUserLabelRequest if err := apivalidate.DecodeJSON(w, r, maxLabelsRequestBody, &req); err != nil { return } if verr := validateCreateUserLabel(&req); verr != nil { apivalidate.WriteValidationError(w, r, verr) return } id, err := h.svc.CreateUserLabel(r.Context(), claims.Sub, &req) if err != nil { if errors.Is(err, ErrDuplicateLabel) { apiresponse.WriteError(w, r, http.StatusConflict, apiresponse.CodeInvalidRequest, "label name already exists", nil) return } h.logger.Error("create user label", "error", err) apivalidate.WriteInternal(w, r) return } apiresponse.WriteJSON(w, http.StatusCreated, map[string]string{"id": id}) } func (h *Handler) UpdateUserLabel(w http.ResponseWriter, r *http.Request) { claims := middleware.ClaimsFromContext(r.Context()) var req updateUserLabelRequest if err := apivalidate.DecodeJSON(w, r, maxLabelsRequestBody, &req); err != nil { return } if verr := validateUpdateUserLabel(&req); verr != nil { apivalidate.WriteValidationError(w, r, verr) return } if err := h.svc.UpdateUserLabel(r.Context(), claims.Sub, chi.URLParam(r, "labelID"), &req); err != nil { if errors.Is(err, ErrNotFound) { apivalidate.WriteNotFound(w, r, "not found") return } if errors.Is(err, ErrDuplicateLabel) { apiresponse.WriteError(w, r, http.StatusConflict, apiresponse.CodeInvalidRequest, "label name already exists", nil) return } h.logger.Error("update user label", "error", err) apivalidate.WriteInternal(w, r) return } w.WriteHeader(http.StatusNoContent) } func (h *Handler) DeleteUserLabel(w http.ResponseWriter, r *http.Request) { claims := middleware.ClaimsFromContext(r.Context()) if err := h.svc.DeleteUserLabel(r.Context(), claims.Sub, chi.URLParam(r, "labelID")); err != nil { if errors.Is(err, ErrNotFound) { apivalidate.WriteNotFound(w, r, "not found") return } h.logger.Error("delete user label", "error", err) apivalidate.WriteInternal(w, r) return } w.WriteHeader(http.StatusNoContent) }