package api_acladmin import ( "encoding/json" "log/slog" "net/http" "strconv" "git.oblat.lv/alex/triggerssmith/internal/acl" "github.com/go-chi/chi/v5" ) // @Summary Get all resources // @Tags resources // @Produce json // @Success 200 {array} getResourcesResponse // @Failure 500 {object} ProblemDetails // @Router /api/acl/resources [get] func (h *aclAdminHandler) getResources(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") resources, err := h.a.GetResources() if err != nil { switch err { case acl.ErrNotInitialized: writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "acl service is not initialized", r) default: slog.Error("unexpected server error", "error", err.Error()) writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "unexpected error", r) } return } type R struct { ID uint `json:"id" example:"1"` Key string `json:"key" example:"html.view"` } resp := make([]R, 0, len(resources)) for _, res := range resources { resp = append(resp, R{ID: res.ID, Key: res.Key}) } _ = json.NewEncoder(w).Encode(resp) } // @Summary Get resource by ID // @Tags resources // @Produce json // @Param resourceId path int true "Resource ID" example(1) // @Success 200 {object} getResourceResponse // @Failure 400 {object} ProblemDetails // @Failure 404 {object} ProblemDetails // @Failure 500 {object} ProblemDetails // @Router /api/acl/resources/{resourceId} [get] func (h *aclAdminHandler) getResource(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") resourceIDStr := chi.URLParam(r, "resourceId") resourceID, err := strconv.Atoi(resourceIDStr) if err != nil || resourceID < 0 { writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-resource-id", "Invalid resource ID", "Resource ID must be positive integer", r) return } resource, err := h.a.GetResourceByID(uint(resourceID)) if err != nil { switch err { case acl.ErrNotInitialized: writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "acl service is not initialized", r) case acl.ErrResourceNotFound: writeProblem(w, http.StatusNotFound, "/errors/acl/resource-not-found", "Resource not found", "No resource with ID "+resourceIDStr, r) default: slog.Error("unexpected server error", "error", err.Error()) writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "unexpected error", r) } return } _ = json.NewEncoder(w).Encode(getResourceResponse{ ID: resource.ID, Key: resource.Key, }) } // @Summary Create resource // @Tags resources // @Accept json // @Produce json // @Param request body createResourceRequest true "Resource" // @Success 201 {object} createResourceResponse // @Failure 400 {object} ProblemDetails // @Failure 409 {object} ProblemDetails // @Failure 500 {object} ProblemDetails // @Router /api/acl/resources [post] func (h *aclAdminHandler) createResource(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var req createResourceRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeProblem(w, http.StatusBadRequest, "/errors/invalid-request-body", "Invalid request body", "Body is not valid JSON", r) return } resourceID, err := h.a.CreateResource(req.Key) if err != nil { slog.Error("Failed to create resource", "error", err) switch err { case acl.ErrNotInitialized: writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "acl service is not initialized", r) case acl.ErrInvalidResourceKey: writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-resource-key", "Invalid resource key", "Resource key must be non-empty", r) case acl.ErrResourceAlreadyExists: writeProblem(w, http.StatusConflict, "/errors/acl/resource-already-exists", "Resource already exists", "Resource '"+req.Key+"' already exists", r) default: slog.Error("unexpected server error", "error", err.Error()) writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "unexpected error", r) } return } w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(createResourceResponse{ ID: resourceID, Key: req.Key, }) } // @Summary Update resource // @Tags resources // @Accept json // @Produce json // @Param resourceId path int true "Resource ID" example(1) // @Param request body updateResourceRequest true "Resource" // @Success 200 {object} updateResourceResponse // @Failure 400 {object} ProblemDetails // @Failure 404 {object} ProblemDetails // @Failure 409 {object} ProblemDetails // @Failure 500 {object} ProblemDetails // @Router /api/acl/resources/{resourceId} [patch] func (h *aclAdminHandler) updateResource(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var req updateResourceRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeProblem(w, http.StatusBadRequest, "/errors/invalid-request-body", "Invalid request body", "Body is not valid JSON", r) return } resourceIDStr := chi.URLParam(r, "resourceId") resourceID, err := strconv.Atoi(resourceIDStr) if err != nil || resourceID < 0 { writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-resource-id", "Invalid resource ID", "Resource ID must be positive integer", r) return } err = h.a.UpdateResource(uint(resourceID), req.Key) if err != nil { slog.Error("Failed to update resource", "error", err) switch err { case acl.ErrNotInitialized: writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "acl service is not initialized", r) case acl.ErrInvalidResourceKey: writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-resource-key", "Invalid resource key", "Resource key must be non-empty", r) case acl.ErrResourceNotFound: writeProblem(w, http.StatusNotFound, "/errors/acl/resource-not-found", "Resource not found", "No resource with ID "+resourceIDStr, r) case acl.ErrSameResourceKey: writeProblem(w, http.StatusConflict, "/errors/acl/resource-key-already-exists", "Resource key already exists", "Resource key '"+req.Key+"' already exists", r) default: slog.Error("unexpected server error", "error", err.Error()) writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "unexpected error", r) } return } _ = json.NewEncoder(w).Encode(updateResourceResponse{ ID: uint(resourceID), Key: req.Key, }) } // @Summary Delete resource // @Tags resources // @Produce json // @Param resourceId path int true "Resource ID" example(1) // @Success 200 // @Failure 400 {object} ProblemDetails // @Failure 404 {object} ProblemDetails // @Failure 409 {object} ProblemDetails // @Failure 500 {object} ProblemDetails // @Router /api/acl/resources/{resourceId} [delete] func (h *aclAdminHandler) deleteResource(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") resourceIDStr := chi.URLParam(r, "resourceId") resourceID, err := strconv.Atoi(resourceIDStr) if err != nil || resourceID < 0 { writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-resource-id", "Invalid resource ID", "Resource ID must be positive integer", r) return } err = h.a.DeleteResource(uint(resourceID)) if err != nil { slog.Error("Failed to delete resource", "error", err) switch err { case acl.ErrNotInitialized: writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "acl service is not initialized", r) case acl.ErrResourceNotFound: writeProblem(w, http.StatusNotFound, "/errors/acl/resource-not-found", "Resource not found", "No resource with ID "+resourceIDStr, r) case acl.ErrResourceInUse: writeProblem(w, http.StatusConflict, "/errors/acl/resource-in-use", "Resource in use", "Resource "+resourceIDStr+" is in use", r) default: slog.Error("unexpected server error", "error", err.Error()) writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "unexpected error", r) } return } w.WriteHeader(http.StatusOK) }