package comicanimator import ( "errors" "sort" "sync" "time" ) type Upload struct { ID, SessionID, Path, Name, MIMEType string Size int64 CreatedAt time.Time } type GenerationStatus string const ( Queued GenerationStatus = "queued" Submitting GenerationStatus = "submitting" Pending GenerationStatus = "pending" InProgress GenerationStatus = "in_progress" Downloading GenerationStatus = "downloading" Completed GenerationStatus = "completed" Failed GenerationStatus = "failed" ) type Generation struct { ID, SessionID, UploadID, OriginalName, ReviewedPrompt, ProviderJobID string Status GenerationStatus Duration int Resolution, OutputPath, OutputMIMEType string OutputSize int64 ErrorCode, ErrorMessage string CreatedAt, UpdatedAt time.Time CompletedAt *time.Time } type store struct { mu sync.RWMutex uploads map[string]Upload generations map[string]Generation } func newStore() *store { return &store{uploads: map[string]Upload{}, generations: map[string]Generation{}} } func (s *store) putUpload(u Upload) { s.mu.Lock(); defer s.mu.Unlock(); s.uploads[u.ID] = u } func (s *store) upload(id string) (Upload, bool) { s.mu.RLock() defer s.mu.RUnlock() u, ok := s.uploads[id] return u, ok } func (s *store) putGeneration(g Generation) { s.mu.Lock() defer s.mu.Unlock() s.generations[g.ID] = g } func (s *store) generation(id string) (Generation, bool) { s.mu.RLock() defer s.mu.RUnlock() g, ok := s.generations[id] return g, ok } func (s *store) update(id string, fn func(*Generation)) { s.mu.Lock() defer s.mu.Unlock() g, ok := s.generations[id] if !ok { return } fn(&g) g.UpdatedAt = time.Now().UTC() s.generations[id] = g } func (s *store) list(session string) []Generation { s.mu.RLock() defer s.mu.RUnlock() out := []Generation{} for _, g := range s.generations { if session == "" || g.SessionID == session { out = append(out, g) } } sort.Slice(out, func(i, j int) bool { return out[i].CreatedAt.After(out[j].CreatedAt) }) return out } var errForbidden = errors.New("access denied")