package useCases import ( "RenewCMS/internal/domain/audit" "RenewCMS/internal/domain/update" "context" "sync" "time" ) type RequestRestartUseCase struct { jobs update.JobStore helper update.HelperDetector results update.ResultStore requests update.RestartRequestStore status updateStatusBuilder now func() time.Time recorder audit.Recorder mu sync.Mutex } func NewRequestRestartUseCase(store update.StatusStore, jobs update.JobStore, helper update.HelperDetector, results update.ResultStore, requests update.RestartRequestStore, currentVersion string, now func() time.Time, recorder audit.Recorder) *RequestRestartUseCase { return &RequestRestartUseCase{ jobs: jobs, helper: helper, results: results, requests: requests, status: updateStatusBuilder{store: store, jobs: jobs, results: results, requests: requests, currentVersion: currentVersion, now: now}, now: now, recorder: recorder, } } func (r *RequestRestartUseCase) Request(ctx context.Context, userId uint32, kind string) (update.UpdateStatus, error) { event := audit.NewEvent(audit.ActionRestartRequested, audit.CategorySystem) status, request, err := r.request(userId, kind) if err != nil { recordFailure(ctx, r.recorder, event, err, restartFailureReasons...) return update.UpdateStatus{}, err } event.Params = restartParams(request) r.recorder.Record(ctx, event) return status, nil } func restartParams(request update.RestartRequest) map[string]any { params := map[string]any{audit.ParamKind: request.Kind} if request.Kind == update.RestartKindUpdate { params[audit.ParamTag] = request.Tag } return params } func (r *RequestRestartUseCase) request(userId uint32, kind string) (update.UpdateStatus, update.RestartRequest, error) { if !r.helper.Installed() { return update.UpdateStatus{}, update.RestartRequest{}, update.ErrNotPrepared } r.mu.Lock() defer r.mu.Unlock() current := r.status.build(true) if err := refuseWhileBusy(current); err != nil { return update.UpdateStatus{}, update.RestartRequest{}, err } request, err := r.requestFor(kind, userId) if err != nil { return update.UpdateStatus{}, update.RestartRequest{}, err } if err := r.results.Delete(); err != nil { return update.UpdateStatus{}, update.RestartRequest{}, err } if err := r.requests.Write(request); err != nil { return update.UpdateStatus{}, update.RestartRequest{}, err } return r.status.build(true), request, nil } func refuseWhileBusy(status update.UpdateStatus) error { switch status.State { case update.StateDownloading, update.StateVerifying: return update.ErrJobInProgress case update.StateRestarting: return update.ErrRestartPending } return nil } func (r *RequestRestartUseCase) requestFor(kind string, userId uint32) (update.RestartRequest, error) { request := update.RestartRequest{ Kind: update.RestartKindRestart, RequestedAt: r.now().UTC(), RequestedBy: userId, } if job, found := r.jobs.Load(); found && job.State == update.StateReady { request.Kind = update.RestartKindUpdate request.Tag = job.Tag } if kind != "" && kind != request.Kind { return update.RestartRequest{}, update.ErrRestartKindMismatch } return request, nil }