package finnhub import ( "log" "time" "git.rouggy.com/rouggy/stockradar/internal/db" ) type Poller struct { db *db.DB getKey func() (string, error) ticker *time.Ticker done chan struct{} lastRun time.Time } func NewPoller(database *db.DB, getKey func() (string, error)) *Poller { return &Poller{ db: database, getKey: getKey, done: make(chan struct{}), } } func (p *Poller) Start() { p.ticker = time.NewTicker(15 * time.Minute) go func() { // Run immediately on start if err := p.Sync(); err != nil { log.Printf("finnhub poller: initial sync: %v", err) } for { select { case <-p.ticker.C: if err := p.Sync(); err != nil { log.Printf("finnhub poller: sync: %v", err) } case <-p.done: return } } }() } func (p *Poller) Stop() { if p.ticker != nil { p.ticker.Stop() } close(p.done) } func (p *Poller) Sync() error { apiKey, err := p.getKey() if err != nil || apiKey == "" { return nil // pas de clé configurée, on skip silencieusement } client := New(apiKey) tickers, err := p.watchlistTickers() if err != nil { return err } now := time.Now() from := now.AddDate(0, 0, -7).Format("2006-01-02") to := now.Format("2006-01-02") total := 0 for _, sym := range tickers { items, err := client.CompanyNews(sym, from, to) if err != nil { log.Printf("finnhub: news %s: %v", sym, err) continue } for _, item := range items { if p.insertNews(sym, item) { total++ } } time.Sleep(250 * time.Millisecond) // Finnhub free tier: 60 req/min } // News marché général (sans ticker spécifique) market, err := client.MarketNews() if err == nil { for _, item := range market { p.insertNews("", item) } } p.lastRun = now if total > 0 { log.Printf("finnhub: sync done — %d nouvelles news", total) } return nil } func (p *Poller) LastRun() time.Time { return p.lastRun } func (p *Poller) watchlistTickers() ([]string, error) { rows, err := p.db.Query(`SELECT ticker FROM watchlist WHERE active=1`) if err != nil { return nil, err } defer rows.Close() var tickers []string for rows.Next() { var t string if err := rows.Scan(&t); err != nil { return nil, err } tickers = append(tickers, t) } return tickers, nil } func (p *Poller) insertNews(ticker string, item NewsItem) bool { published := time.Unix(item.Datetime, 0).UTC().Format(time.RFC3339) res, err := p.db.Exec(` INSERT OR IGNORE INTO news (finnhub_id, ticker, headline, source, url, published_at) VALUES (?, ?, ?, ?, ?, ?) `, item.ID, ticker, item.Headline, item.Source, item.URL, published) if err != nil { return false } n, _ := res.RowsAffected() return n > 0 }