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
This commit is contained in:
129
internal/db/filings.go
Normal file
129
internal/db/filings.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Filing struct {
|
||||
ID int64 `json:"id"`
|
||||
ExternalID string `json:"external_id"`
|
||||
CompanyID *int64 `json:"company_id,omitempty"`
|
||||
CNPJ string `json:"cnpj"`
|
||||
Category string `json:"category"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Species string `json:"species,omitempty"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
ReferenceDate string `json:"reference_date,omitempty"`
|
||||
DeliveryDate string `json:"delivery_date"`
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
DownloadURL string `json:"download_url,omitempty"`
|
||||
Importance int `json:"importance"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
func (d *DB) UpsertFiling(f *Filing) error {
|
||||
_, err := d.Conn.Exec(`
|
||||
INSERT INTO filings (external_id, company_id, cnpj, category, type, species, subject, reference_date, delivery_date, protocol, version, download_url, importance)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(external_id) DO NOTHING`,
|
||||
f.ExternalID, f.CompanyID, f.CNPJ, f.Category, f.Type, f.Species, f.Subject, f.ReferenceDate, f.DeliveryDate, f.Protocol, f.Version, f.DownloadURL, f.Importance)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DB) RebuildFilingsFTS() error {
|
||||
_, err := d.Conn.Exec(`INSERT INTO filings_fts(filings_fts) VALUES('rebuild')`)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DB) ListFilings(limit, offset int, category, from, to string) ([]Filing, int, error) {
|
||||
where := "WHERE 1=1"
|
||||
args := []any{}
|
||||
if category != "" {
|
||||
where += " AND category = ?"
|
||||
args = append(args, category)
|
||||
}
|
||||
if from != "" {
|
||||
where += " AND delivery_date >= ?"
|
||||
args = append(args, from)
|
||||
}
|
||||
if to != "" {
|
||||
where += " AND delivery_date <= ?"
|
||||
args = append(args, to)
|
||||
}
|
||||
|
||||
var total int
|
||||
err := d.Conn.QueryRow("SELECT COUNT(*) FROM filings "+where, args...).Scan(&total)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(`SELECT id, external_id, company_id, cnpj, category, COALESCE(type,''), COALESCE(species,''),
|
||||
COALESCE(subject,''), COALESCE(reference_date,''), delivery_date, COALESCE(protocol,''),
|
||||
COALESCE(version,''), COALESCE(download_url,''), importance, created_at
|
||||
FROM filings %s ORDER BY delivery_date DESC 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()
|
||||
return scanFilings(rows)
|
||||
}
|
||||
|
||||
func (d *DB) GetFiling(id int64) (*Filing, error) {
|
||||
f := &Filing{}
|
||||
err := d.Conn.QueryRow(`SELECT id, external_id, company_id, cnpj, category, COALESCE(type,''), COALESCE(species,''),
|
||||
COALESCE(subject,''), COALESCE(reference_date,''), delivery_date, COALESCE(protocol,''),
|
||||
COALESCE(version,''), COALESCE(download_url,''), importance, created_at
|
||||
FROM filings WHERE id = ?`, id).
|
||||
Scan(&f.ID, &f.ExternalID, &f.CompanyID, &f.CNPJ, &f.Category, &f.Type, &f.Species, &f.Subject,
|
||||
&f.ReferenceDate, &f.DeliveryDate, &f.Protocol, &f.Version, &f.DownloadURL, &f.Importance, &f.CreatedAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return f, err
|
||||
}
|
||||
|
||||
func (d *DB) ListFilingsByCompany(companyID int64, limit, offset int) ([]Filing, int, error) {
|
||||
var total int
|
||||
d.Conn.QueryRow("SELECT COUNT(*) FROM filings WHERE company_id = ?", companyID).Scan(&total)
|
||||
|
||||
rows, err := d.Conn.Query(`SELECT id, external_id, company_id, cnpj, category, COALESCE(type,''), COALESCE(species,''),
|
||||
COALESCE(subject,''), COALESCE(reference_date,''), delivery_date, COALESCE(protocol,''),
|
||||
COALESCE(version,''), COALESCE(download_url,''), importance, created_at
|
||||
FROM filings WHERE company_id = ? ORDER BY delivery_date DESC LIMIT ? OFFSET ?`, companyID, limit, offset)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
filings, _, err := scanFilings(rows)
|
||||
return filings, total, err
|
||||
}
|
||||
|
||||
func (d *DB) RecentFilings(limit int) ([]Filing, error) {
|
||||
rows, err := d.Conn.Query(`SELECT id, external_id, company_id, cnpj, category, COALESCE(type,''), COALESCE(species,''),
|
||||
COALESCE(subject,''), COALESCE(reference_date,''), delivery_date, COALESCE(protocol,''),
|
||||
COALESCE(version,''), COALESCE(download_url,''), importance, created_at
|
||||
FROM filings ORDER BY delivery_date DESC LIMIT ?`, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
filings, _, err := scanFilings(rows)
|
||||
return filings, err
|
||||
}
|
||||
|
||||
func scanFilings(rows *sql.Rows) ([]Filing, int, error) {
|
||||
var filings []Filing
|
||||
for rows.Next() {
|
||||
var f Filing
|
||||
if err := rows.Scan(&f.ID, &f.ExternalID, &f.CompanyID, &f.CNPJ, &f.Category, &f.Type, &f.Species,
|
||||
&f.Subject, &f.ReferenceDate, &f.DeliveryDate, &f.Protocol, &f.Version, &f.DownloadURL, &f.Importance, &f.CreatedAt); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
filings = append(filings, f)
|
||||
}
|
||||
return filings, len(filings), nil
|
||||
}
|
||||
Reference in New Issue
Block a user