package manager import ( "context" "sync" "time" "eslogad-be/internal/contract" "github.com/google/uuid" ) type JobStatus string const ( JobStatusPending JobStatus = "pending" JobStatusProcessing JobStatus = "processing" JobStatusCompleted JobStatus = "completed" JobStatusFailed JobStatus = "failed" ) type BulkJobResult struct { JobID uuid.UUID `json:"job_id"` Status JobStatus `json:"status"` Message string `json:"message"` StartedAt time.Time `json:"started_at"` FinishedAt *time.Time `json:"finished_at,omitempty"` Summary contract.BulkCreationSummary `json:"summary"` Created []contract.UserResponse `json:"created"` Failed []contract.BulkUserErrorResult `json:"failed"` } type JobManager struct { jobs sync.Map } var jobManagerInstance *JobManager var once sync.Once func GetJobManager() *JobManager { once.Do(func() { jobManagerInstance = &JobManager{} }) return jobManagerInstance } func (jm *JobManager) CreateJob() uuid.UUID { jobID := uuid.New() job := &BulkJobResult{ JobID: jobID, Status: JobStatusPending, Message: "Job created, waiting to start", StartedAt: time.Now(), Summary: contract.BulkCreationSummary{ Total: 0, Succeeded: 0, Failed: 0, }, Created: []contract.UserResponse{}, Failed: []contract.BulkUserErrorResult{}, } jm.jobs.Store(jobID, job) return jobID } func (jm *JobManager) UpdateJob(jobID uuid.UUID, status JobStatus, message string) { if val, ok := jm.jobs.Load(jobID); ok { job := val.(*BulkJobResult) job.Status = status job.Message = message if status == JobStatusCompleted || status == JobStatusFailed { now := time.Now() job.FinishedAt = &now } jm.jobs.Store(jobID, job) } } func (jm *JobManager) UpdateJobResults(jobID uuid.UUID, created []contract.UserResponse, failed []contract.BulkUserErrorResult, summary contract.BulkCreationSummary) { if val, ok := jm.jobs.Load(jobID); ok { job := val.(*BulkJobResult) job.Created = append(job.Created, created...) job.Failed = append(job.Failed, failed...) job.Summary.Total = summary.Total job.Summary.Succeeded += summary.Succeeded job.Summary.Failed += summary.Failed jm.jobs.Store(jobID, job) } } func (jm *JobManager) GetJob(jobID uuid.UUID) (*BulkJobResult, bool) { if val, ok := jm.jobs.Load(jobID); ok { return val.(*BulkJobResult), true } return nil, false } func (jm *JobManager) CleanupOldJobs(ctx context.Context, maxAge time.Duration) { ticker := time.NewTicker(1 * time.Hour) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: cutoff := time.Now().Add(-maxAge) jm.jobs.Range(func(key, value interface{}) bool { job := value.(*BulkJobResult) if job.FinishedAt != nil && job.FinishedAt.Before(cutoff) { jm.jobs.Delete(key) } return true }) } } }