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