315 lines
9.7 KiB
Go
315 lines
9.7 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 roles
|
|
// @Tags roles
|
|
// @Produce json
|
|
// @Success 200 {object} getRolesResponse
|
|
// @Failure 500 {object} errorInternalServerError
|
|
// @Router /api/acl/roles [get]
|
|
func (h *aclAdminHandler) getRoles(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
roles, err := h.a.GetRoles()
|
|
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 roles",
|
|
})
|
|
return
|
|
}
|
|
}
|
|
_ = json.NewEncoder(w).Encode(func() getRolesResponse {
|
|
// Transform acl.Role to getRolesResponse
|
|
resp := make(getRolesResponse, 0, len(roles))
|
|
for _, role := range roles {
|
|
resp = append(resp, struct {
|
|
ID uint `json:"id" example:"1"`
|
|
Name string `json:"name" example:"admin"`
|
|
}{
|
|
ID: role.ID,
|
|
Name: role.Name,
|
|
})
|
|
}
|
|
return resp
|
|
}())
|
|
}
|
|
|
|
// @Summary Get role by ID
|
|
// @Tags roles
|
|
// @Produce json
|
|
// @Param roleId path int true "Role ID" example(1)
|
|
// @Success 200 {object} getRoleResponse
|
|
// @Failure 400 {object} getRoleErrorInvalidRoleID
|
|
// @Failure 404 {object} getRoleErrorRoleNotFound
|
|
// @Failure 500 {object} errorInternalServerError
|
|
// @Router /api/acl/roles/{roleId} [get]
|
|
func (h *aclAdminHandler) getRole(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
roleIDStr := chi.URLParam(r, "roleId")
|
|
roleID, err := strconv.Atoi(roleIDStr)
|
|
if err != nil || roleID < 0 {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
_ = json.NewEncoder(w).Encode(getRoleErrorInvalidRoleID{
|
|
Error: ErrorInvalidRoleID,
|
|
Details: "Role ID must be positive integer",
|
|
})
|
|
return
|
|
}
|
|
role, err := h.a.GetRoleByID(uint(roleID))
|
|
if err != nil {
|
|
switch err {
|
|
case acl.ErrNotInitialized:
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(errorInternalServerError{
|
|
Error: ErrorInternalServerError,
|
|
Details: ErrorACLServiceNotInitialized,
|
|
})
|
|
return
|
|
case acl.ErrRoleNotFound:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
_ = json.NewEncoder(w).Encode(getRoleErrorRoleNotFound{
|
|
Error: ErrorRoleNotFound,
|
|
Details: "No role with ID " + roleIDStr,
|
|
})
|
|
return
|
|
default:
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(errorInternalServerError{
|
|
Error: ErrorInternalServerError,
|
|
Details: "Failed to get role with ID " + roleIDStr,
|
|
})
|
|
return
|
|
}
|
|
}
|
|
_ = json.NewEncoder(w).Encode(getRoleResponse{
|
|
ID: role.ID,
|
|
Name: role.Name,
|
|
})
|
|
}
|
|
|
|
// @Summary Create role
|
|
// @Tags roles
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body createRoleRequest true "Role"
|
|
// @Success 201 {object} createRoleResponse
|
|
// @Failure 400 {object} errorInvalidRequestBody
|
|
// @Failure 401 {object} createRoleErrorInvalidRoleName
|
|
// @Failure 409 {object} createRoleErrorRoleAlreadyExists
|
|
// @Failure 500 {object} errorInternalServerError
|
|
// @Router /api/acl/roles [post]
|
|
func (h *aclAdminHandler) createRole(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
var req createRoleRequest
|
|
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
|
|
}
|
|
roleID, err := h.a.CreateRole(req.Name)
|
|
if err != nil {
|
|
slog.Error("Failed to create role", "error", err.Error())
|
|
switch err {
|
|
case acl.ErrNotInitialized:
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(errorInternalServerError{
|
|
Error: ErrorInternalServerError,
|
|
Details: ErrorACLServiceNotInitialized,
|
|
})
|
|
return
|
|
case acl.ErrRoleAlreadyExists:
|
|
w.WriteHeader(http.StatusConflict)
|
|
_ = json.NewEncoder(w).Encode(createRoleErrorRoleAlreadyExists{
|
|
Error: ErrorFailedToCreateRole,
|
|
Details: "Role with name '" + req.Name + "' already exists",
|
|
})
|
|
return
|
|
case acl.ErrInvalidRoleName:
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
_ = json.NewEncoder(w).Encode(createRoleErrorInvalidRoleName{
|
|
Error: ErrorFailedToCreateRole,
|
|
Details: "Role name must be non-empty string",
|
|
})
|
|
return
|
|
default:
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(errorInternalServerError{
|
|
Error: ErrorInternalServerError,
|
|
Details: "Failed to create role with name '" + req.Name + "'",
|
|
})
|
|
return
|
|
}
|
|
}
|
|
w.WriteHeader(http.StatusCreated)
|
|
_ = json.NewEncoder(w).Encode(createRoleResponse{
|
|
ID: roleID,
|
|
Name: req.Name,
|
|
})
|
|
}
|
|
|
|
// @Summary Update role
|
|
// @Tags roles
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param roleId path int true "Role ID" example(1)
|
|
// @Param request body updateRoleRequest true "Role"
|
|
// @Success 200 {object} updateRoleResponse
|
|
// @Failure 400 {object} errorInvalidRequestBody
|
|
// @Failure 400 {object} updateRoleErrorInvalidRoleID
|
|
// @Failure 400 {object} updateRoleErrorInvalidRoleName
|
|
// @Failure 404 {object} updateRoleErrorRoleNotFound
|
|
// @Failure 409 {object} updateRoleErrorRoleNameAlreadyExists
|
|
// @Failure 500 {object} errorInternalServerError
|
|
// @Router /api/acl/roles/{roleId} [patch]
|
|
func (h *aclAdminHandler) updateRole(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
var req updateRoleRequest
|
|
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
|
|
}
|
|
roleIDStr := chi.URLParam(r, "roleId")
|
|
roleID, err := strconv.Atoi(roleIDStr)
|
|
if err != nil || roleID < 0 {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
_ = json.NewEncoder(w).Encode(updateRoleErrorInvalidRoleID{
|
|
Error: ErrorInvalidRoleID,
|
|
Details: "Role ID must be positive integer",
|
|
})
|
|
return
|
|
}
|
|
err = h.a.UpdateRole(uint(roleID), req.Name)
|
|
// TODO: make error handling more specific in acl service
|
|
if err != nil {
|
|
slog.Error("Failed to update role", "error", err.Error())
|
|
switch err {
|
|
case acl.ErrNotInitialized:
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(errorInternalServerError{
|
|
Error: ErrorInternalServerError,
|
|
Details: ErrorACLServiceNotInitialized,
|
|
})
|
|
return
|
|
case acl.ErrInvalidRoleName:
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
_ = json.NewEncoder(w).Encode(updateRoleErrorInvalidRoleName{
|
|
Error: ErrorFailedToUpdateRole,
|
|
Details: "Invalid role name",
|
|
})
|
|
return
|
|
case acl.ErrRoleNotFound:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
_ = json.NewEncoder(w).Encode(updateRoleErrorRoleNotFound{
|
|
Error: ErrorFailedToUpdateRole,
|
|
Details: "No role with ID " + roleIDStr,
|
|
})
|
|
return
|
|
case acl.ErrSameRoleName:
|
|
w.WriteHeader(http.StatusConflict)
|
|
_ = json.NewEncoder(w).Encode(updateRoleErrorRoleNameAlreadyExists{
|
|
Error: ErrorFailedToUpdateRole,
|
|
Details: "Role with name '" + req.Name + "' already exists",
|
|
})
|
|
return
|
|
default:
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(errorInternalServerError{
|
|
Error: ErrorInternalServerError,
|
|
Details: "Failed to update role with name '" + req.Name + "'",
|
|
})
|
|
return
|
|
}
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
_ = json.NewEncoder(w).Encode(updateRoleResponse{
|
|
ID: uint(roleID),
|
|
Name: req.Name,
|
|
})
|
|
}
|
|
|
|
// @Summary Delete role
|
|
// @Tags roles
|
|
// @Produce json
|
|
// @Param roleId path int true "Role ID" example(1)
|
|
// @Success 200
|
|
// @Failure 400 {object} deleteRoleErrorInvalidRoleID
|
|
// @Failure 404 {object} deleteRoleErrorRoleNotFound
|
|
// @Failure 409 {object} deleteRoleErrorRoleInUse
|
|
// @Failure 500 {object} errorInternalServerError
|
|
// @Router /api/acl/roles/{roleId} [delete]
|
|
func (h *aclAdminHandler) deleteRole(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
roleIDStr := chi.URLParam(r, "roleId")
|
|
roleID, err := strconv.Atoi(roleIDStr)
|
|
if err != nil || roleID < 0 {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
_ = json.NewEncoder(w).Encode(deleteRoleErrorInvalidRoleID{
|
|
Error: ErrorInvalidRoleID,
|
|
Details: "Role ID must be positive integer",
|
|
})
|
|
return
|
|
}
|
|
err = h.a.DeleteRole(uint(roleID))
|
|
// TODO: make error handling more specific in acl service
|
|
if err != nil {
|
|
slog.Error("Failed to delete role", "error", err.Error())
|
|
switch err {
|
|
case acl.ErrNotInitialized:
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(errorInternalServerError{
|
|
Error: ErrorInternalServerError,
|
|
Details: ErrorACLServiceNotInitialized,
|
|
})
|
|
return
|
|
case acl.ErrRoleNotFound:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
_ = json.NewEncoder(w).Encode(deleteRoleErrorRoleNotFound{
|
|
Error: ErrorFailedToDeleteRole,
|
|
Details: "No role with ID " + roleIDStr,
|
|
})
|
|
return
|
|
case acl.ErrRoleInUse:
|
|
w.WriteHeader(http.StatusConflict)
|
|
_ = json.NewEncoder(w).Encode(deleteRoleErrorRoleInUse{
|
|
Error: ErrorFailedToDeleteRole,
|
|
Details: "Role with ID " + roleIDStr + " is assigned to users and cannot be deleted",
|
|
})
|
|
return
|
|
default:
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(errorInternalServerError{
|
|
Error: ErrorInternalServerError,
|
|
Details: "Failed to delete role with ID '" + roleIDStr + "'",
|
|
})
|
|
return
|
|
}
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|