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:
52
internal/db/sqlite.go
Normal file
52
internal/db/sqlite.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
Conn *sql.DB
|
||||
}
|
||||
|
||||
func New(dbPath string) (*DB, error) {
|
||||
dir := filepath.Dir(dbPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("create db dir: %w", err)
|
||||
}
|
||||
|
||||
conn, err := sql.Open("sqlite", dbPath+"?_journal_mode=WAL&_busy_timeout=5000")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open db: %w", err)
|
||||
}
|
||||
|
||||
conn.SetMaxOpenConns(1) // SQLite single-writer
|
||||
|
||||
if _, err := conn.Exec(schema); err != nil {
|
||||
return nil, fmt.Errorf("run schema: %w", err)
|
||||
}
|
||||
|
||||
slog.Info("database initialized", "path", dbPath)
|
||||
return &DB{Conn: conn}, nil
|
||||
}
|
||||
|
||||
func (d *DB) Close() error {
|
||||
return d.Conn.Close()
|
||||
}
|
||||
|
||||
func (d *DB) IsEmpty() bool {
|
||||
var count int
|
||||
d.Conn.QueryRow("SELECT COUNT(*) FROM companies").Scan(&count)
|
||||
return count == 0
|
||||
}
|
||||
|
||||
func (d *DB) IsMarketEmpty() bool {
|
||||
var count int
|
||||
d.Conn.QueryRow("SELECT COUNT(*) FROM selic_history").Scan(&count)
|
||||
return count == 0
|
||||
}
|
||||
Reference in New Issue
Block a user