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) }