- 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
130 lines
4.7 KiB
Go
130 lines
4.7 KiB
Go
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
|
|
}
|