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 {object} getResourcesResponse // @Failure 500 {object} errorInternalServerError // @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: w.WriteHeader(http.StatusInternalServerError) _ = json.NewEncoder(w).Encode(errorInternalServerError{ Error: ErrorInternalServerError, Details: ErrorACLServiceNotInitialized, }) return default: w.WriteHeader(http.StatusInternalServerError) _ = json.NewEncoder(w).Encode(errorInternalServerError{ Error: ErrorInternalServerError, Details: "Failed to get resources", }) return } } _ = json.NewEncoder(w).Encode(func() getResourcesResponse { resp := make(getResourcesResponse, 0, len(resources)) for _, res := range resources { resp = append(resp, struct { ID uint `json:"id" example:"1"` Key string `json:"key" example:"html.view"` }{ ID: res.ID, Key: res.Key, }) } return 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} getResourceErrorInvalidResourceID // @Failure 404 {object} getResourceErrorResourceNotFound // @Failure 500 {object} errorInternalServerError // @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 { w.WriteHeader(http.StatusBadRequest) _ = json.NewEncoder(w).Encode(getResourceErrorInvalidResourceID{ Error: ErrorInvalidResourceID, Details: "Resource ID must be positive integer", }) return } resource, err := h.a.GetResourceByID(uint(resourceID)) if err != nil { switch err { case acl.ErrNotInitialized: w.WriteHeader(http.StatusInternalServerError) _ = json.NewEncoder(w).Encode(errorInternalServerError{ Error: ErrorInternalServerError, Details: ErrorACLServiceNotInitialized, }) return case acl.ErrResourceNotFound: w.WriteHeader(http.StatusNotFound) _ = json.NewEncoder(w).Encode(getResourceErrorResourceNotFound{ Error: ErrorResourceNotFound, Details: "No resource with ID " + resourceIDStr, }) return default: w.WriteHeader(http.StatusInternalServerError) _ = json.NewEncoder(w).Encode(errorInternalServerError{ Error: ErrorInternalServerError, Details: "Failed to get resource with ID " + resourceIDStr, }) 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} errorInvalidRequestBody // @Failure 400 {object} createResourceErrorInvalidResourceKey // @Failure 409 {object} createResourceErrorResourceAlreadyExists // @Failure 500 {object} errorInternalServerError // @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 { w.WriteHeader(http.StatusBadRequest) _ = json.NewEncoder(w).Encode(errorInvalidRequestBody{ Error: ErrorInvalidRequestBody, Details: "Request body is not valid JSON", }) return } resourceID, err := h.a.CreateResource(req.Key) if err != nil { slog.Error("Failed to create resource", "error", err.Error()) switch err { case acl.ErrNotInitialized: w.WriteHeader(http.StatusInternalServerError) _ = json.NewEncoder(w).Encode(errorInternalServerError{ Error: ErrorInternalServerError, Details: ErrorACLServiceNotInitialized, }) return case acl.ErrInvalidResourceKey: w.WriteHeader(http.StatusBadRequest) _ = json.NewEncoder(w).Encode(createResourceErrorInvalidResourceKey{ Error: ErrorFailedToCreateResource, Details: "Resource key must be non-empty", }) return case acl.ErrResourceAlreadyExists: w.WriteHeader(http.StatusConflict) _ = json.NewEncoder(w).Encode(createResourceErrorResourceAlreadyExists{ Error: ErrorFailedToCreateResource, Details: "Resource with key '" + req.Key + "' already exists", }) return default: w.WriteHeader(http.StatusInternalServerError) _ = json.NewEncoder(w).Encode(errorInternalServerError{ Error: ErrorInternalServerError, Details: "Failed to create resource with key '" + req.Key + "'", }) 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} errorInvalidRequestBody // @Failure 400 {object} updateResourceErrorInvalidResourceID // @Failure 400 {object} updateResourceErrorInvalidResourceKey // @Failure 404 {object} updateResourceErrorResourceNotFound // @Failure 409 {object} updateResourceErrorResourceKeyAlreadyExists // @Failure 500 {object} errorInternalServerError // @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 { w.WriteHeader(http.StatusBadRequest) _ = json.NewEncoder(w).Encode(errorInvalidRequestBody{ Error: ErrorInvalidRequestBody, Details: "Request body is not valid JSON", }) return } resourceIDStr := chi.URLParam(r, "resourceId") resourceID, err := strconv.Atoi(resourceIDStr) if err != nil || resourceID < 0 { w.WriteHeader(http.StatusBadRequest) _ = json.NewEncoder(w).Encode(updateResourceErrorInvalidResourceID{ Error: ErrorInvalidResourceID, Details: "Resource ID must be positive integer", }) return } err = h.a.UpdateResource(uint(resourceID), req.Key) if err != nil { slog.Error("Failed to update resource", "error", err.Error()) switch err { case acl.ErrNotInitialized: w.WriteHeader(http.StatusInternalServerError) _ = json.NewEncoder(w).Encode(errorInternalServerError{ Error: ErrorInternalServerError, Details: ErrorACLServiceNotInitialized, }) return case acl.ErrInvalidResourceKey: w.WriteHeader(http.StatusBadRequest) _ = json.NewEncoder(w).Encode(updateResourceErrorInvalidResourceKey{ Error: ErrorFailedToUpdateResource, Details: "Invalid resource key", }) return case acl.ErrResourceNotFound: w.WriteHeader(http.StatusNotFound) _ = json.NewEncoder(w).Encode(updateResourceErrorResourceNotFound{ Error: ErrorFailedToUpdateResource, Details: "No resource with ID " + resourceIDStr, }) return case acl.ErrSameResourceKey: w.WriteHeader(http.StatusConflict) _ = json.NewEncoder(w).Encode(updateResourceErrorResourceKeyAlreadyExists{ Error: ErrorFailedToUpdateResource, Details: "Resource with key '" + req.Key + "' already exists", }) return default: w.WriteHeader(http.StatusInternalServerError) _ = json.NewEncoder(w).Encode(errorInternalServerError{ Error: ErrorInternalServerError, Details: "Failed to update resource with key '" + req.Key + "'", }) return } } w.WriteHeader(http.StatusOK) _ = 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} deleteResourceErrorInvalidResourceID // @Failure 404 {object} deleteResourceErrorResourceNotFound // @Failure 409 {object} deleteResourceErrorResourceInUse // @Failure 500 {object} errorInternalServerError // @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 { w.WriteHeader(http.StatusBadRequest) _ = json.NewEncoder(w).Encode(deleteResourceErrorInvalidResourceID{ Error: ErrorInvalidResourceID, Details: "Resource ID must be positive integer", }) return } err = h.a.DeleteResource(uint(resourceID)) if err != nil { slog.Error("Failed to delete resource", "error", err.Error()) switch err { case acl.ErrNotInitialized: w.WriteHeader(http.StatusInternalServerError) _ = json.NewEncoder(w).Encode(errorInternalServerError{ Error: ErrorInternalServerError, Details: ErrorACLServiceNotInitialized, }) return case acl.ErrResourceNotFound: w.WriteHeader(http.StatusNotFound) _ = json.NewEncoder(w).Encode(deleteResourceErrorResourceNotFound{ Error: ErrorFailedToDeleteResource, Details: "No resource with ID " + resourceIDStr, }) return case acl.ErrResourceInUse: w.WriteHeader(http.StatusConflict) _ = json.NewEncoder(w).Encode(deleteResourceErrorResourceInUse{ Error: ErrorFailedToDeleteResource, Details: "Resource with ID " + resourceIDStr + " is used and cannot be deleted", }) return default: w.WriteHeader(http.StatusInternalServerError) _ = json.NewEncoder(w).Encode(errorInternalServerError{ Error: ErrorInternalServerError, Details: "Failed to delete resource with ID '" + resourceIDStr + "'", }) return } } w.WriteHeader(http.StatusOK) }