- 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
94 lines
2.9 KiB
Go
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
|
|
}
|