Files
RenewCMS/api/handlers/update/updateHandler.go
T

163 lines
5.1 KiB
Go

package update
import (
"RenewCMS/api/pkg/apperr"
"RenewCMS/api/pkg/claims"
validator "RenewCMS/api/pkg/validator"
domain "RenewCMS/internal/domain/update"
"RenewCMS/internal/domain/user"
"RenewCMS/internal/infrastructure/useCases"
"encoding/json"
"errors"
"io"
"net/http"
"strings"
)
const restartBodyLimitBytes = 1024
type Handler struct {
GetUpdateStatusUseCase *useCases.GetUpdateStatusUseCase
CheckForUpdateUseCase *useCases.CheckForUpdateUseCase
StartUpdateDownloadUseCase *useCases.StartUpdateDownloadUseCase
RequestRestartUseCase *useCases.RequestRestartUseCase
GetUserUseCase *useCases.GetUserUseCase
}
type restartRequest struct {
Kind *string `json:"kind" validate:"omitempty,oneof=update restart"`
}
func NewHandler(getStatus *useCases.GetUpdateStatusUseCase, checkForUpdate *useCases.CheckForUpdateUseCase, startDownload *useCases.StartUpdateDownloadUseCase, requestRestart *useCases.RequestRestartUseCase, getUser *useCases.GetUserUseCase) *Handler {
return &Handler{
GetUpdateStatusUseCase: getStatus,
CheckForUpdateUseCase: checkForUpdate,
StartUpdateDownloadUseCase: startDownload,
RequestRestartUseCase: requestRestart,
GetUserUseCase: getUser,
}
}
func (h *Handler) GetStatus(w http.ResponseWriter, r *http.Request) {
isOwner, ok := h.isOwner(w, r)
if !ok {
return
}
writeStatus(w, http.StatusOK, h.GetUpdateStatusUseCase.GetStatus(isOwner))
}
func (h *Handler) Check(w http.ResponseWriter, r *http.Request) {
if _, err := h.CheckForUpdateUseCase.Check(); err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
isOwner, ok := h.isOwner(w, r)
if !ok {
return
}
writeStatus(w, http.StatusOK, h.GetUpdateStatusUseCase.GetStatus(isOwner))
}
func (h *Handler) Download(w http.ResponseWriter, _ *http.Request) {
status, err := h.StartUpdateDownloadUseCase.Start()
if err != nil {
writeDownloadError(w, err)
return
}
writeStatus(w, http.StatusAccepted, status)
}
func (h *Handler) Restart(w http.ResponseWriter, r *http.Request) {
kind, ok := decodeRestartKind(w, r)
if !ok {
return
}
userId, ok := claims.UserId(r)
if !ok {
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
return
}
status, err := h.RequestRestartUseCase.Request(userId, kind)
if err != nil {
writeRestartError(w, err)
return
}
writeStatus(w, http.StatusAccepted, status)
}
func decodeRestartKind(w http.ResponseWriter, r *http.Request) (string, bool) {
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, restartBodyLimitBytes))
if err != nil {
http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest)
return "", false
}
if strings.TrimSpace(string(body)) == "" {
return "", true
}
var request restartRequest
if err := json.Unmarshal(body, &request); err != nil || validator.Validate.Struct(request) != nil {
http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest)
return "", false
}
if request.Kind == nil {
return "", true
}
return *request.Kind, true
}
func (h *Handler) isOwner(w http.ResponseWriter, r *http.Request) (bool, bool) {
userId, ok := claims.UserId(r)
if !ok {
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
return false, false
}
currentUser, err := h.GetUserUseCase.GetUser(userId)
if err != nil {
if errors.Is(err, user.ErrUserNotFound) {
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
return false, false
}
http.Error(w, "Internal server error", http.StatusInternalServerError)
return false, false
}
return currentUser.Role == user.RoleOwner, true
}
func writeDownloadError(w http.ResponseWriter, err error) {
switch {
case errors.Is(err, domain.ErrNotPrepared):
http.Error(w, apperr.UpdateNotPreparedErrorMessage, http.StatusConflict)
case errors.Is(err, domain.ErrJobInProgress), errors.Is(err, domain.ErrRestartPending):
http.Error(w, apperr.UpdateAlreadyRunningErrorMessage, http.StatusConflict)
case errors.Is(err, domain.ErrNothingToUpdate):
http.Error(w, apperr.NothingToUpdateErrorMessage, http.StatusBadRequest)
case errors.Is(err, domain.ErrPreflightRefused):
http.Error(w, apperr.UpdateRefusedErrorMessage, http.StatusUnprocessableEntity)
default:
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
}
func writeRestartError(w http.ResponseWriter, err error) {
switch {
case errors.Is(err, domain.ErrNotPrepared):
http.Error(w, apperr.UpdateNotPreparedErrorMessage, http.StatusConflict)
case errors.Is(err, domain.ErrJobInProgress), errors.Is(err, domain.ErrRestartPending):
http.Error(w, apperr.UpdateAlreadyRunningErrorMessage, http.StatusConflict)
case errors.Is(err, domain.ErrRestartKindMismatch):
http.Error(w, apperr.UpdateRestartMismatchErrorMessage, http.StatusConflict)
default:
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
}
func writeStatus(w http.ResponseWriter, code int, status domain.UpdateStatus) {
body, err := json.Marshal(status)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.WriteHeader(code)
_, _ = w.Write(body)
}