some changes

This commit is contained in:
2025-12-21 00:00:03 +02:00
parent 904f446447
commit 85f8ac60e7
14 changed files with 678 additions and 1475 deletions

View File

@@ -10,158 +10,158 @@ import (
"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]
// @Summary Get all roles
// @Tags roles
// @Produce json
// @Success 200 {array} getRolesResponse
// @Failure 500 {object} ProblemDetails
// @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
writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "ACL service is not initialized", r)
default:
w.WriteHeader(http.StatusInternalServerError)
_ = json.NewEncoder(w).Encode(errorInternalServerError{
Error: ErrorInternalServerError,
Details: "Failed to get roles",
})
return
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(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
}())
type R struct {
ID uint `json:"id" example:"1"`
Name string `json:"name" example:"admin"`
}
resp := make([]R, 0, len(roles))
for _, role := range roles {
resp = append(resp, R{ID: role.ID, Name: role.Name})
}
_ = json.NewEncoder(w).Encode(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]
// @Summary Get role users
// @Tags roles
// @Produce json
// @Param roleId path int true "Role ID" example(1)
// @Success 200 {array} getRoleUsersResponse
// @Failure 400 {object} ProblemDetails
// @Failure 404 {object} ProblemDetails
// @Failure 500 {object} ProblemDetails
// @Router /api/acl/roles/{roleId}/users [get]
func (h *aclAdminHandler) getRoleUsers(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 {
writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-role-id", "Invalid role ID", "Role ID must be positive integer", r)
return
}
role, err := h.a.GetRoleByID(uint(roleID))
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.ErrRoleNotFound:
writeProblem(w, http.StatusNotFound, "/errors/acl/role-not-found", "Role not found", "No role with ID "+roleIDStr, 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
}
if len(role.Users) == 0 {
writeProblem(w, http.StatusNotFound, "/errors/acl/role-has-no-users", "Role has no users", "Role has no users", r)
return
}
var respUsers getRoleUsersResponse
for _, user := range role.Users {
respUsers = append(respUsers, getRoleUser{
ID: user.ID,
Name: user.Username,
Email: user.Email,
})
}
_ = json.NewEncoder(w).Encode(respUsers)
}
// @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} ProblemDetails
// @Failure 404 {object} ProblemDetails
// @Failure 500 {object} ProblemDetails
// @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",
})
writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-role-id", "Invalid role ID", "Role ID must be positive integer", r)
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
writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "ACL service is not initialized", r)
case acl.ErrRoleNotFound:
w.WriteHeader(http.StatusNotFound)
_ = json.NewEncoder(w).Encode(getRoleErrorRoleNotFound{
Error: ErrorRoleNotFound,
Details: "No role with ID " + roleIDStr,
})
return
writeProblem(w, http.StatusNotFound, "/errors/acl/role-not-found", "Role not found", "No role with ID "+roleIDStr, r)
default:
w.WriteHeader(http.StatusInternalServerError)
_ = json.NewEncoder(w).Encode(errorInternalServerError{
Error: ErrorInternalServerError,
Details: "Failed to get role with ID " + roleIDStr,
})
return
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(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]
// @Summary Create role
// @Tags roles
// @Accept json
// @Produce json
// @Param request body createRoleRequest true "Role"
// @Success 201 {object} createRoleResponse
// @Failure 400 {object} ProblemDetails
// @Failure 409 {object} ProblemDetails
// @Failure 500 {object} ProblemDetails
// @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",
})
writeProblem(w, http.StatusBadRequest, "/errors/invalid-request-body", "Invalid request body", "Body is not valid JSON", r)
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
writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "ACL service is not initialized", r)
case acl.ErrInvalidRoleName:
w.WriteHeader(http.StatusBadRequest)
_ = json.NewEncoder(w).Encode(createRoleErrorInvalidRoleName{
Error: ErrorFailedToCreateRole,
Details: "Role name must be non-empty string",
})
return
writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-role-name", "Invalid role name", "Role name must be non-empty", r)
case acl.ErrRoleAlreadyExists:
writeProblem(w, http.StatusConflict, "/errors/acl/role-already-exists", "Role already exists", "Role '"+req.Name+"' already exists", r)
default:
w.WriteHeader(http.StatusInternalServerError)
_ = json.NewEncoder(w).Encode(errorInternalServerError{
Error: ErrorInternalServerError,
Details: "Failed to create role with name '" + req.Name + "'",
})
return
writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "unexpected error", r)
}
return
}
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(createRoleResponse{
ID: roleID,
@@ -169,146 +169,92 @@ func (h *aclAdminHandler) createRole(w http.ResponseWriter, r *http.Request) {
})
}
// @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]
// @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} ProblemDetails
// @Failure 404 {object} ProblemDetails
// @Failure 409 {object} ProblemDetails
// @Failure 500 {object} ProblemDetails
// @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",
})
writeProblem(w, http.StatusBadRequest, "/errors/invalid-request-body", "Invalid request body", "Body is not valid JSON", r)
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",
})
writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-role-id", "Invalid role ID", "Role ID must be positive integer", r)
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
writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "ACL service is not initialized", r)
case acl.ErrInvalidRoleName:
w.WriteHeader(http.StatusBadRequest)
_ = json.NewEncoder(w).Encode(updateRoleErrorInvalidRoleName{
Error: ErrorFailedToUpdateRole,
Details: "Invalid role name",
})
return
writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-role-name", "Invalid role name", "Role name must be non-empty", r)
case acl.ErrRoleNotFound:
w.WriteHeader(http.StatusNotFound)
_ = json.NewEncoder(w).Encode(updateRoleErrorRoleNotFound{
Error: ErrorFailedToUpdateRole,
Details: "No role with ID " + roleIDStr,
})
return
writeProblem(w, http.StatusNotFound, "/errors/acl/role-not-found", "Role not found", "No role with ID "+roleIDStr, r)
case acl.ErrSameRoleName:
w.WriteHeader(http.StatusConflict)
_ = json.NewEncoder(w).Encode(updateRoleErrorRoleNameAlreadyExists{
Error: ErrorFailedToUpdateRole,
Details: "Role with name '" + req.Name + "' already exists",
})
return
writeProblem(w, http.StatusConflict, "/errors/acl/role-name-already-exists", "Role name already exists", "Role '"+req.Name+"' already exists", r)
default:
w.WriteHeader(http.StatusInternalServerError)
_ = json.NewEncoder(w).Encode(errorInternalServerError{
Error: ErrorInternalServerError,
Details: "Failed to update role with name '" + req.Name + "'",
})
return
writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "unexpected error", r)
}
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]
// @Summary Delete role
// @Tags roles
// @Produce json
// @Param roleId path int true "Role ID" example(1)
// @Success 200
// @Failure 400 {object} ProblemDetails
// @Failure 404 {object} ProblemDetails
// @Failure 409 {object} ProblemDetails
// @Failure 500 {object} ProblemDetails
// @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",
})
writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-role-id", "Invalid role ID", "Role ID must be positive integer", r)
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
writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "ACL service is not initialized", r)
case acl.ErrRoleNotFound:
w.WriteHeader(http.StatusNotFound)
_ = json.NewEncoder(w).Encode(deleteRoleErrorRoleNotFound{
Error: ErrorFailedToDeleteRole,
Details: "No role with ID " + roleIDStr,
})
return
writeProblem(w, http.StatusNotFound, "/errors/acl/role-not-found", "Role not found", "No role with ID "+roleIDStr, r)
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
writeProblem(w, http.StatusConflict, "/errors/acl/role-in-use", "Role in use", "Role "+roleIDStr+" is assigned to at least one user and cannot be deleted", r)
default:
w.WriteHeader(http.StatusInternalServerError)
_ = json.NewEncoder(w).Encode(errorInternalServerError{
Error: ErrorInternalServerError,
Details: "Failed to delete role with ID '" + roleIDStr + "'",
})
return
writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "unexpected error", r)
}
return
}
w.WriteHeader(http.StatusOK)
}