package postgresql import ( "context" "fmt" "gitea.computernetthings.ru/yash/crypto_alert_bot/internal/entities" ) const instrumentListQuery = ` select i.id, c_base.symbol, c_quote.symbol, i.is_global from instrument i join currency c_base on c_base.id = i.base_currency_id join currency c_quote on c_quote.id = i.quoted_currency_id where i.is_global = true or exists ( select 1 from user_instrument ui where ui.instrument_id = i.id and ui.user_id = $1 ) order by i.is_global desc, i.id asc offset $2 limit $3` func (p *Postgresql) InstrumentList(ctx context.Context, userID entities.UserID, offset, limit int) ([]entities.Instrument, error) { rows, err := p.db.Query(ctx, instrumentListQuery, userID, offset, limit) if err != nil { return nil, fmt.Errorf("failed to exec instrumentListQuery: %w", err) } defer rows.Close() var instruments []entities.Instrument for rows.Next() { var inst entities.Instrument if err := rows.Scan(&inst.ID, &inst.BaseCurrency, &inst.QuoteCurrency, &inst.IsGlobal); err != nil { return nil, fmt.Errorf("failed to scan instrument row: %w", err) } instruments = append(instruments, inst) } return instruments, nil } // createInstrumentQuery upserts both currency symbols then the instrument itself. // It always returns an ID — the newly inserted row or the existing one on conflict. const createInstrumentQuery = ` with upsert_base as ( insert into currency(symbol) values($1) on conflict(symbol) do update set symbol = excluded.symbol returning id ), upsert_quote as ( insert into currency(symbol) values($2) on conflict(symbol) do update set symbol = excluded.symbol returning id ), ins as ( insert into instrument(base_currency_id, quoted_currency_id, is_global) select upsert_base.id, upsert_quote.id, false from upsert_base, upsert_quote on conflict (base_currency_id, quoted_currency_id) do nothing returning id ) select coalesce( (select id from ins), (select i.id from instrument i where i.base_currency_id = (select id from upsert_base) and i.quoted_currency_id = (select id from upsert_quote)) )` func (p *Postgresql) CreateInstrument(ctx context.Context, instrument *entities.Instrument) (entities.InstrumentID, error) { var id entities.InstrumentID err := p.db.QueryRow(ctx, createInstrumentQuery, instrument.BaseCurrency, instrument.QuoteCurrency).Scan(&id) if err != nil { return "", fmt.Errorf("failed to exec createInstrumentQuery: %w", err) } return id, nil } const addUserInstrumentQuery = ` insert into user_instrument(user_id, instrument_id) values ($1, $2) on conflict (user_id, instrument_id) do nothing` func (p *Postgresql) AddUserInstrument(ctx context.Context, userID entities.UserID, instrumentID entities.InstrumentID) error { _, err := p.db.Exec(ctx, addUserInstrumentQuery, userID, instrumentID) if err != nil { return fmt.Errorf("failed to exec addUserInstrumentQuery: %w", err) } return nil } const removeUserInstrumentQuery = ` delete from user_instrument where user_id = $1 and instrument_id = $2` func (p *Postgresql) RemoveUserInstrument(ctx context.Context, userID entities.UserID, instrumentID entities.InstrumentID) error { _, err := p.db.Exec(ctx, removeUserInstrumentQuery, userID, instrumentID) if err != nil { return fmt.Errorf("failed to exec removeUserInstrumentQuery: %w", err) } return nil }