package db import ( "database/sql" _ "embed" "errors" "fmt" "log/slog" "time" "github.com/maximotejeda/us_dop_bot/models" _ "modernc.org/sqlite" ) type DB struct { *sql.DB log *slog.Logger } type change struct { Before models.Institucion `json:"before"` After models.Institucion `json:"after"` } type Message struct { Message string `json:"message"` Data change `json:"data"` Error error `json:"error"` } // Dial func Dial(path string, log *slog.Logger) *DB { db, err := sql.Open("sqlite", path) if err != nil { fmt.Printf("opening database: %s", err.Error()) panic("opening database") } if err := db.Ping(); err != nil { fmt.Printf("pinging database: %s", err.Error()) panic("pinging database") } return &DB{db, log} } // Inspect // Handle behavior of the changes // Will report errors to a nats consumer func (db *DB) Inspect(enter models.Institucion) error { if db == nil { return fmt.Errorf("nil or empty database") } // Get last row added inst, err := db.GetLatest(enter.Parser, enter.Name) // if no rows are found because of first enter a name - parser ? if errors.Is(sql.ErrNoRows, err) { db.log.Info("adding new item to table: ", "parse", enter.Parser, "name", enter.Name) if err != nil { db.log.Error("marshaling struct", "error", err) } return db.AddNew(enter) } // check prices compra venta if inst == nil { db.log.Error("row is nil", "name", enter.Name, "parser", enter.Parser) return fmt.Errorf("row is nil, not entering row") } if enter.Compra == inst.Compra && enter.Venta == inst.Venta { return nil } else { // if one of them changes create a new row db.log.Info("change registered, adding item", "parse", enter.Parser, "name", enter.Name, "compra enter", enter.Compra, "compra db", inst.Compra, "venta enter", enter.Venta, "venta db", inst.Venta) if err != nil { db.log.Error("marshaling struct", "error", err) } return db.AddNew(enter) } } // GetLatest // returns the latest row in a specific parser and name // we are using DateTime in DB and date.Datetime in go func (db *DB) GetLatest(parser string, name string) (inst *models.Institucion, err error) { var parsed string inst = &models.Institucion{} stmt, err := db.Prepare("SELECT name, parser, compra, venta, parsed FROM dolars WHERE parser = ? AND name = ? ORDER BY parsed DESC LIMIT 1;") if err != nil { db.log.Error("preparing", "error", err.Error()) return nil, err } defer stmt.Close() if err := stmt.QueryRow(parser, name).Scan(&inst.Name, &inst.Parser, &inst.Compra, &inst.Venta, &parsed); err != nil { db.log.Error("getting latest", "error", err.Error(), "parser", parser, "name", name) return nil, err } inst.Parsed, err = time.Parse(time.DateTime, parsed) if err != nil { //db.log.Error("parsed", "error", err.Error()) return nil, err } return inst, nil } // AddNew // Add a new row in the dolar table // Will send to nats changes on prices func (db *DB) AddNew(row models.Institucion) error { stmt, err := db.Prepare("INSERT INTO dolars (name, compra, venta, parser, parsed) VALUES(?,?,?,?,?);") if err != nil { return err } defer stmt.Close() parsed := row.Parsed.Format(time.DateTime) _, err = stmt.Exec(&row.Name, &row.Compra, &row.Venta, &row.Parser, &parsed) if err != nil { return err } return nil } func (db *DB) GetAll() ([]string, error) { stmt, err := db.Prepare("SELECT DISTINCT dolars.name FROM dolars WHERE name LIKE '%ban%' OR name LIKE '%scoti%' OR name LIKE '%asociacion%'") if err != nil { db.log.Error("[db-GetAll]", "error", err) return nil, err } rows, err := stmt.Query() if err != nil { db.log.Error("[db-GetAll-stmt]", "error", err) return nil, err } defer rows.Close() insts := []string{} for rows.Next() { inst := "" if err = rows.Scan(&inst); err != nil { return nil, err } if inst == "" { continue } insts = append(insts, inst) } if err := rows.Err(); err != nil { return insts, err } return insts, nil } func (db *DB) GetBancos() ([]string, error) { stmt, err := db.Prepare("SELECT DISTINCT dolars.name FROM dolars WHERE name LIKE '%ban%' OR name LIKE '%scoti%'") if err != nil { db.log.Error("[inst-GetAll]", "error", err) return nil, err } rows, err := stmt.Query() if err != nil { db.log.Error("[inst-GetAll-stmt]", "error", err) return nil, err } defer rows.Close() insts := []string{} for rows.Next() { inst := "" if err = rows.Scan(&inst); err != nil { return nil, err } if inst == "" { continue } insts = append(insts, inst) } if err := rows.Err(); err != nil { return insts, err } return insts, nil } func (db *DB) GetCajas() ([]string, error) { stmt, err := db.Prepare("SELECT DISTINCT dolars.name FROM dolars WHERE name LIKE '%asociacion%'") if err != nil { db.log.Error("[inst-GetAll]", "error", err) return nil, err } rows, err := stmt.Query() if err != nil { db.log.Error("[inst-GetAll-stmt]", "error", err) return nil, err } defer rows.Close() insts := []string{} for rows.Next() { inst := "" if err = rows.Scan(&inst); err != nil { return nil, err } if inst == "" { continue } insts = append(insts, inst) } if err := rows.Err(); err != nil { return insts, err } return insts, nil } func (db *DB) GetAgentes() ([]string, error) { stmt, err := db.Prepare("SELECT DISTINCT dolars.name FROM dolars WHERE name NOT LIKE '%ban%' AND name NOT LIKE '%scoti%' AND name NOT LIKE '%asociacion%'") if err != nil { db.log.Error("[inst-GetAll]", "error", err) return nil, err } rows, err := stmt.Query() if err != nil { db.log.Error("[inst-GetAll-stmt]", "error", err) return nil, err } defer rows.Close() insts := []string{} for rows.Next() { inst := "" if err = rows.Scan(&inst); err != nil { return nil, err } if inst == "" { continue } insts = append(insts, inst) } if err := rows.Err(); err != nil { return insts, err } return insts, nil } func (db *DB) GetLastPrice(name string) (inst *models.Institucion, err error) { var parsed string inst = &models.Institucion{} stmt, err := db.Prepare("SELECT name, parser, compra, venta, parsed FROM dolars WHERE name = ? ORDER BY parsed DESC LIMIT 1;") if err != nil { db.log.Error("preparing", "error", err.Error()) return nil, err } defer stmt.Close() if err := stmt.QueryRow(name).Scan(&inst.Name, &inst.Parser, &inst.Compra, &inst.Venta, &parsed); err != nil { db.log.Error("getting last price", "error", err.Error(), "name", name) return nil, err } inst.Parsed, err = time.Parse(time.DateTime, parsed) if err != nil { //db.log.Error("parsed", "error", err.Error()) return nil, err } return inst, nil } func (db *DB) GetChangeSince(name string, duration time.Duration) (insts []*models.Institucion, err error) { date := time.Now().Add(-duration).Format(time.DateTime) stmt, err := db.Prepare("SELECT name, parser, compra, venta, parsed FROM dolars WHERE name = ? AND parsed > ? ORDER BY parsed DESC;") if err != nil { db.log.Error("[GetChangeSince] preparing", "error", err.Error()) return nil, err } defer stmt.Close() rows, err := stmt.Query(name, date) if err != nil { db.log.Error("[GetChangeSince] preparing", "error", err.Error()) return nil, err } defer rows.Close() for rows.Next() { inst := models.Institucion{} parsed := "" if err := rows.Scan(&inst.Name, &inst.Parser, &inst.Compra, &inst.Venta, &parsed); err != nil { db.log.Error("[GetChangeSince] scanning", "error", err) return nil, err } inst.Parsed, err = time.Parse(time.DateTime, parsed) if err != nil { //db.log.Error("parsed", "error", err.Error()) continue } insts = append(insts, &inst) } return insts, nil }