Files
sentinela-go/internal/db/companies.go
Rainbow f7c8b446bf feat: Sentinela v0.2.0 — Brazilian Financial Data API in Go
- 20 Go source files, single 16MB binary
- SQLite + FTS5 full-text search (pure Go, no CGO)
- BCB integration: Selic, CDI, IPCA, USD/BRL, EUR/BRL
- CVM integration: 2,524 companies from registry
- Fiber v2 REST API with 42 handlers
- Auto-seeds on first run (~5s for BCB + CVM)
- Token bucket rate limiter, optional API key auth
- Periodic sync scheduler (configurable)
- Graceful shutdown, structured logging (slog)
- All endpoints tested with real data
2026-02-10 11:15:54 -03:00

94 lines
2.9 KiB
Go

package db
import (
"database/sql"
"fmt"
"time"
)
type Company struct {
ID int64 `json:"id"`
Ticker string `json:"ticker,omitempty"`
Name string `json:"name"`
CNPJ string `json:"cnpj"`
CVMCode string `json:"cvm_code,omitempty"`
Sector string `json:"sector,omitempty"`
Status string `json:"status"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
func (d *DB) UpsertCompany(c *Company) error {
_, err := d.Conn.Exec(`
INSERT INTO companies (ticker, name, cnpj, cvm_code, sector, status, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(cnpj) DO UPDATE SET
ticker=excluded.ticker, name=excluded.name, cvm_code=excluded.cvm_code,
sector=excluded.sector, status=excluded.status, updated_at=excluded.updated_at`,
c.Ticker, c.Name, c.CNPJ, c.CVMCode, c.Sector, c.Status, time.Now().UTC().Format(time.RFC3339))
return err
}
func (d *DB) RebuildCompaniesFTS() error {
_, err := d.Conn.Exec(`
INSERT INTO companies_fts(companies_fts) VALUES('rebuild')`)
return err
}
func (d *DB) ListCompanies(limit, offset int, status, sector string) ([]Company, int, error) {
where := "WHERE 1=1"
args := []any{}
if status != "" {
where += " AND status = ?"
args = append(args, status)
}
if sector != "" {
where += " AND sector = ?"
args = append(args, sector)
}
var total int
err := d.Conn.QueryRow("SELECT COUNT(*) FROM companies "+where, args...).Scan(&total)
if err != nil {
return nil, 0, err
}
query := fmt.Sprintf("SELECT id, COALESCE(ticker,''), name, cnpj, COALESCE(cvm_code,''), COALESCE(sector,''), status, created_at, updated_at FROM companies %s ORDER BY name LIMIT ? OFFSET ?", where)
args = append(args, limit, offset)
rows, err := d.Conn.Query(query, args...)
if err != nil {
return nil, 0, err
}
defer rows.Close()
var companies []Company
for rows.Next() {
var c Company
if err := rows.Scan(&c.ID, &c.Ticker, &c.Name, &c.CNPJ, &c.CVMCode, &c.Sector, &c.Status, &c.CreatedAt, &c.UpdatedAt); err != nil {
return nil, 0, err
}
companies = append(companies, c)
}
return companies, total, nil
}
func (d *DB) GetCompany(id int64) (*Company, error) {
c := &Company{}
err := d.Conn.QueryRow("SELECT id, COALESCE(ticker,''), name, cnpj, COALESCE(cvm_code,''), COALESCE(sector,''), status, created_at, updated_at FROM companies WHERE id = ?", id).
Scan(&c.ID, &c.Ticker, &c.Name, &c.CNPJ, &c.CVMCode, &c.Sector, &c.Status, &c.CreatedAt, &c.UpdatedAt)
if err == sql.ErrNoRows {
return nil, nil
}
return c, err
}
func (d *DB) GetCompanyByCNPJ(cnpj string) (*Company, error) {
c := &Company{}
err := d.Conn.QueryRow("SELECT id, COALESCE(ticker,''), name, cnpj, COALESCE(cvm_code,''), COALESCE(sector,''), status, created_at, updated_at FROM companies WHERE cnpj = ?", cnpj).
Scan(&c.ID, &c.Ticker, &c.Name, &c.CNPJ, &c.CVMCode, &c.Sector, &c.Status, &c.CreatedAt, &c.UpdatedAt)
if err == sql.ErrNoRows {
return nil, nil
}
return c, err
}