322 lines
10 KiB
Go
322 lines
10 KiB
Go
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)
|
|
}
|