package state import ( "encoding/json" "fmt" "os" "path/filepath" "time" ) // ─── Status constants ───────────────────────────────────────────────────────── const ( StatusWaitingEvaluator = "waiting_evaluator" StatusWaitingRedator = "waiting_redator" StatusWaitingEditor = "waiting_editor" StatusWaitingArt = "waiting_art" StatusWaitingDirector = "waiting_director" StatusWaitingPublisher = "waiting_publisher" StatusPublished = "published" StatusRejected = "rejected" EtapaDone = "done" EtapaPending = "pending" EtapaWaiting = "waiting" ) // ─── Schema ─────────────────────────────────────────────────────────────────── type Etapas struct { Evaluator string `json:"evaluator"` Redator string `json:"redator"` Editor string `json:"editor"` Art string `json:"art"` Director string `json:"director"` Publisher string `json:"publisher"` } type Aprovacao struct { Aprovado bool `json:"aprovado"` Timestamp time.Time `json:"timestamp,omitempty"` Ciclos int `json:"ciclos,omitempty"` } type Aprovacoes struct { Tema Aprovacao `json:"tema"` Texto Aprovacao `json:"texto"` Imagens Aprovacao `json:"imagens"` Final Aprovacao `json:"final,omitempty"` } type PostState struct { Slug string `json:"slug"` Categoria string `json:"categoria"` Status string `json:"status"` Formato string `json:"formato"` TemaEscolhido string `json:"tema_escolhido"` TrendReferencia string `json:"trend_referencia"` FunilTag string `json:"funil_tag,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` Etapas Etapas `json:"etapas"` Aprovacoes Aprovacoes `json:"aprovacoes"` PollingActive bool `json:"polling_active,omitempty"` DirectorMessageID int `json:"director_message_id,omitempty"` LinkedInPostID string `json:"linkedin_post_id,omitempty"` PublishedAt *time.Time `json:"published_at,omitempty"` } // ─── I/O ────────────────────────────────────────────────────────────────────── // LoadState reads work/state.json relative to postPath. // postPath is the post root: // func LoadState(postPath string) (*PostState, error) { path := stateFilePath(postPath) data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("ler state.json (%s): %w", path, err) } var s PostState if err := json.Unmarshal(data, &s); err != nil { return nil, fmt.Errorf("parsear state.json: %w", err) } return &s, nil } // SaveState writes work/state.json atomically (tmp + rename). // Updates UpdatedAt automatically. func SaveState(postPath string, s *PostState) error { s.UpdatedAt = time.Now() data, err := json.MarshalIndent(s, "", " ") if err != nil { return fmt.Errorf("serializar state: %w", err) } workDir := filepath.Join(postPath, "work") if err := os.MkdirAll(workDir, 0755); err != nil { return fmt.Errorf("criar work/: %w", err) } path := stateFilePath(postPath) tmp := path + ".tmp" if err := os.WriteFile(tmp, data, 0644); err != nil { return fmt.Errorf("escrever tmp: %w", err) } if err := os.Rename(tmp, path); err != nil { os.Remove(tmp) return fmt.Errorf("rename tmp→state.json: %w", err) } return nil } // ─── Methods ────────────────────────────────────────────────────────────────── // SetEtapa updates a specific agent's step status and bumps UpdatedAt. func (s *PostState) SetEtapa(agente, status string) { s.UpdatedAt = time.Now() switch agente { case "evaluator": s.Etapas.Evaluator = status case "redator": s.Etapas.Redator = status case "editor": s.Etapas.Editor = status case "art": s.Etapas.Art = status case "director": s.Etapas.Director = status case "publisher": s.Etapas.Publisher = status } } // IsStatus reports whether the current Status matches expected. func (s *PostState) IsStatus(expected string) bool { return s.Status == expected } // ─── Constructor ────────────────────────────────────────────────────────────── func NewPostState(slug, categoria, formato, tema, trend string) *PostState { now := time.Now() return &PostState{ Slug: slug, Categoria: categoria, Status: StatusWaitingEvaluator, Formato: formato, TemaEscolhido: tema, TrendReferencia: trend, CreatedAt: now, UpdatedAt: now, Etapas: Etapas{ Evaluator: EtapaPending, Redator: EtapaPending, Editor: EtapaPending, Art: EtapaPending, Director: EtapaPending, Publisher: EtapaPending, }, } } // ─── Internal ───────────────────────────────────────────────────────────────── func stateFilePath(postPath string) string { return filepath.Join(postPath, "work", "state.json") }