package postgres import ( "context" "errors" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgxpool" "botpontos/internal/domain" ) // Store reúne as consultas da aplicação. As queries são escritas à mão com pgx. // // Convenções: // - parâmetros de enum usam $n::text::, para o pgx enviar texto; // - datas (AAAA-MM-DD) trafegam como string, com $n::text::date e to_char na leitura; // - filtros opcionais usam ($n:: IS NULL OR ...) com ponteiros nil. type Store struct { pool *pgxpool.Pool } func NewStore(pool *pgxpool.Pool) *Store { return &Store{pool: pool} } // Ping é usado pelos healthchecks. func (s *Store) Ping(ctx context.Context) error { return s.pool.Ping(ctx) } type querier interface { Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) QueryRow(ctx context.Context, sql string, args ...any) pgx.Row } type scanner interface { Scan(dest ...any) error } // inTx executa fn numa transação; qualquer erro desfaz tudo. func (s *Store) inTx(ctx context.Context, fn func(q querier) error) error { tx, err := s.pool.Begin(ctx) if err != nil { return err } defer func() { _ = tx.Rollback(ctx) }() if err := fn(tx); err != nil { return err } return tx.Commit(ctx) } // mapErr traduz erros do pgx para os erros do domínio. func mapErr(err error) error { if err == nil { return nil } if errors.Is(err, pgx.ErrNoRows) { return domain.ErrNotFound } var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == "23505" { return domain.ErrConflict } return err } func expectOne(tag pgconn.CommandTag, err error) error { if err != nil { return mapErr(err) } if tag.RowsAffected() == 0 { return domain.ErrNotFound } return nil } // collect lê todas as linhas com a função de leitura informada. func collect[T any](rows pgx.Rows, err error, read func(scanner) (T, error)) ([]T, error) { if err != nil { return nil, err } defer rows.Close() var out []T for rows.Next() { item, err := read(rows) if err != nil { return nil, err } out = append(out, item) } if err := rows.Err(); err != nil { return nil, err } return out, nil } func cols(prefix string, names ...string) string { out := "" for i, n := range names { if i > 0 { out += ", " } out += prefix + n } return out } func strPtr(s string) *string { if s == "" { return nil } return &s } func pauseReasonPtr(r *domain.PauseReason) *string { if r == nil { return nil } v := string(*r) return &v }