Pipeline completo de publicação no LinkedIn: evaluator → redator → editor → art → director → publisher - Seed com 37 posts em _sugestoes.md - Sorteio de formato com N=3 bloqueados (format-history) - Reciclagem mensal de posts com rotação de formato - Revisão via Telegram com chat livre (Gemini 2.5 Flash) - Publicação via LinkedIn API (OAuth2) - Makefile com targets para Windows/Linux/ARM64 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
93 lines
2.2 KiB
Go
93 lines
2.2 KiB
Go
package history
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
histRelPath = "_history/format-history.json"
|
|
maxEntries = 20
|
|
)
|
|
|
|
type HistoryEntry struct {
|
|
Slug string `json:"slug"`
|
|
Formato string `json:"formato"`
|
|
Data string `json:"data"` // YYYY-MM-DD
|
|
}
|
|
|
|
type FormatHistory struct {
|
|
History []HistoryEntry `json:"history"`
|
|
}
|
|
|
|
// LoadHistory reads _history/format-history.json from workspacePath.
|
|
// Returns empty history if file doesn't exist.
|
|
func LoadHistory(workspacePath string) (*FormatHistory, error) {
|
|
path := filepath.Join(workspacePath, histRelPath)
|
|
data, err := os.ReadFile(path)
|
|
if os.IsNotExist(err) {
|
|
return &FormatHistory{}, nil
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ler format-history: %w", err)
|
|
}
|
|
var h FormatHistory
|
|
if err := json.Unmarshal(data, &h); err != nil {
|
|
return nil, fmt.Errorf("parsear format-history: %w", err)
|
|
}
|
|
return &h, nil
|
|
}
|
|
|
|
// SaveHistory writes _history/format-history.json atomically.
|
|
func SaveHistory(workspacePath string, h *FormatHistory) error {
|
|
dir := filepath.Join(workspacePath, "_history")
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return fmt.Errorf("criar _history/: %w", err)
|
|
}
|
|
|
|
data, err := json.MarshalIndent(h, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("serializar history: %w", err)
|
|
}
|
|
|
|
path := filepath.Join(workspacePath, histRelPath)
|
|
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: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// LastN returns the last n formats used (most recent first).
|
|
func (h *FormatHistory) LastN(n int) []string {
|
|
var out []string
|
|
for i, e := range h.History {
|
|
if i >= n {
|
|
break
|
|
}
|
|
out = append(out, e.Formato)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// AddEntry prepends a new entry and trims to maxEntries.
|
|
func (h *FormatHistory) AddEntry(slug, formato string) {
|
|
entry := HistoryEntry{
|
|
Slug: slug,
|
|
Formato: formato,
|
|
Data: time.Now().Format("2006-01-02"),
|
|
}
|
|
h.History = append([]HistoryEntry{entry}, h.History...)
|
|
if len(h.History) > maxEntries {
|
|
h.History = h.History[:maxEntries]
|
|
}
|
|
}
|