package store import ( "context" "errors" "github.com/jackc/pgx/v5" "git.unkin.net/unkin/tomswallapi/internal/model" ) // ---- Mangle ---------------------------------------------------------------- func (s *Store) ListMangle(ctx context.Context) ([]model.MangleRule, error) { rows, err := s.pool.Query(ctx, ` SELECT id, device, action, chain, mark_value, source, dest, proto, dport, sport, "user", mark, length, tos, helper, probability, comment FROM mangle ORDER BY id`) if err != nil { return nil, err } defer rows.Close() var out []model.MangleRule for rows.Next() { m, err := scanMangle(rows) if err != nil { return nil, err } out = append(out, m) } return out, rows.Err() } func (s *Store) GetMangle(ctx context.Context, id int64) (model.MangleRule, error) { rows, err := s.pool.Query(ctx, ` SELECT id, device, action, chain, mark_value, source, dest, proto, dport, sport, "user", mark, length, tos, helper, probability, comment FROM mangle WHERE id = $1`, id) if err != nil { return model.MangleRule{}, err } defer rows.Close() if !rows.Next() { return model.MangleRule{}, ErrNotFound } return scanMangle(rows) } func scanMangle(rows pgx.Rows) (model.MangleRule, error) { var m model.MangleRule var dport, sport []byte if err := rows.Scan(&m.ID, &m.Device, &m.Action, &m.Chain, &m.MarkValue, &m.Source, &m.Dest, &m.Proto, &dport, &sport, &m.User, &m.Mark, &m.Length, &m.TOS, &m.Helper, &m.Probability, &m.Comment); err != nil { return m, err } if err := unmarshalStrings(dport, &m.DPort); err != nil { return m, err } return m, unmarshalStrings(sport, &m.SPort) } func (s *Store) CreateMangle(ctx context.Context, m model.MangleRule) (int64, error) { dport, _ := jsonb(m.DPort) sport, _ := jsonb(m.SPort) var id int64 err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error { if err := tx.QueryRow(ctx, ` INSERT INTO mangle (device, action, chain, mark_value, source, dest, proto, dport, sport, "user", mark, length, tos, helper, probability, comment) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16) RETURNING id`, m.Device, m.Action, m.Chain, m.MarkValue, m.Source, m.Dest, m.Proto, dport, sport, m.User, m.Mark, m.Length, m.TOS, m.Helper, m.Probability, m.Comment).Scan(&id); err != nil { return err } return bump(ctx, tx) }) return id, err } func (s *Store) DeleteMangle(ctx context.Context, id int64) error { return s.deleteOne(ctx, `DELETE FROM mangle WHERE id = $1`, id) } // ---- Accounting ------------------------------------------------------------ func (s *Store) ListAccounting(ctx context.Context) ([]model.AccountingRule, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, action, section, chain, source, dest, proto, dport, sport, mark, comment FROM accounting ORDER BY id`) if err != nil { return nil, err } defer rows.Close() var out []model.AccountingRule for rows.Next() { a, err := scanAccounting(rows) if err != nil { return nil, err } out = append(out, a) } return out, rows.Err() } func (s *Store) GetAccounting(ctx context.Context, id int64) (model.AccountingRule, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, action, section, chain, source, dest, proto, dport, sport, mark, comment FROM accounting WHERE id = $1`, id) if err != nil { return model.AccountingRule{}, err } defer rows.Close() if !rows.Next() { return model.AccountingRule{}, ErrNotFound } return scanAccounting(rows) } func scanAccounting(rows pgx.Rows) (model.AccountingRule, error) { var a model.AccountingRule var dport, sport []byte if err := rows.Scan(&a.ID, &a.Device, &a.Action, &a.Section, &a.Chain, &a.Source, &a.Dest, &a.Proto, &dport, &sport, &a.Mark, &a.Comment); err != nil { return a, err } if err := unmarshalStrings(dport, &a.DPort); err != nil { return a, err } return a, unmarshalStrings(sport, &a.SPort) } func (s *Store) CreateAccounting(ctx context.Context, a model.AccountingRule) (int64, error) { dport, _ := jsonb(a.DPort) sport, _ := jsonb(a.SPort) var id int64 err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error { if err := tx.QueryRow(ctx, ` INSERT INTO accounting (device, action, section, chain, source, dest, proto, dport, sport, mark, comment) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING id`, a.Device, a.Action, a.Section, a.Chain, a.Source, a.Dest, a.Proto, dport, sport, a.Mark, a.Comment).Scan(&id); err != nil { return err } return bump(ctx, tx) }) return id, err } func (s *Store) DeleteAccounting(ctx context.Context, id int64) error { return s.deleteOne(ctx, `DELETE FROM accounting WHERE id = $1`, id) } // ---- TC devices ------------------------------------------------------------ func (s *Store) ListTCDevices(ctx context.Context) ([]model.TCDevice, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, interface, in_bandwidth, out_bandwidth, comment FROM tc_devices ORDER BY id`) if err != nil { return nil, err } defer rows.Close() var out []model.TCDevice for rows.Next() { var t model.TCDevice if err := rows.Scan(&t.ID, &t.Device, &t.Interface, &t.InBandwidth, &t.OutBandwidth, &t.Comment); err != nil { return nil, err } out = append(out, t) } return out, rows.Err() } func (s *Store) GetTCDevice(ctx context.Context, id int64) (model.TCDevice, error) { var t model.TCDevice err := s.pool.QueryRow(ctx, `SELECT id, device, interface, in_bandwidth, out_bandwidth, comment FROM tc_devices WHERE id = $1`, id, ).Scan(&t.ID, &t.Device, &t.Interface, &t.InBandwidth, &t.OutBandwidth, &t.Comment) if errors.Is(err, pgx.ErrNoRows) { return t, ErrNotFound } return t, err } func (s *Store) CreateTCDevice(ctx context.Context, t model.TCDevice) (int64, error) { var id int64 err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error { if err := tx.QueryRow(ctx, ` INSERT INTO tc_devices (device, interface, in_bandwidth, out_bandwidth, comment) VALUES ($1,$2,$3,$4,$5) RETURNING id`, t.Device, t.Interface, t.InBandwidth, t.OutBandwidth, t.Comment).Scan(&id); err != nil { return err } return bump(ctx, tx) }) return id, err } func (s *Store) DeleteTCDevice(ctx context.Context, id int64) error { return s.deleteOne(ctx, `DELETE FROM tc_devices WHERE id = $1`, id) } // ---- TC classes ------------------------------------------------------------ func (s *Store) ListTCClasses(ctx context.Context) ([]model.TCClass, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, interface, mark, rate, ceil, priority, comment FROM tc_classes ORDER BY id`) if err != nil { return nil, err } defer rows.Close() var out []model.TCClass for rows.Next() { var t model.TCClass if err := rows.Scan(&t.ID, &t.Device, &t.Interface, &t.Mark, &t.Rate, &t.Ceil, &t.Priority, &t.Comment); err != nil { return nil, err } out = append(out, t) } return out, rows.Err() } func (s *Store) GetTCClass(ctx context.Context, id int64) (model.TCClass, error) { var t model.TCClass err := s.pool.QueryRow(ctx, `SELECT id, device, interface, mark, rate, ceil, priority, comment FROM tc_classes WHERE id = $1`, id, ).Scan(&t.ID, &t.Device, &t.Interface, &t.Mark, &t.Rate, &t.Ceil, &t.Priority, &t.Comment) if errors.Is(err, pgx.ErrNoRows) { return t, ErrNotFound } return t, err } func (s *Store) CreateTCClass(ctx context.Context, t model.TCClass) (int64, error) { var id int64 err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error { if err := tx.QueryRow(ctx, ` INSERT INTO tc_classes (device, interface, mark, rate, ceil, priority, comment) VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING id`, t.Device, t.Interface, t.Mark, t.Rate, t.Ceil, t.Priority, t.Comment).Scan(&id); err != nil { return err } return bump(ctx, tx) }) return id, err } func (s *Store) DeleteTCClass(ctx context.Context, id int64) error { return s.deleteOne(ctx, `DELETE FROM tc_classes WHERE id = $1`, id) } // ---- TC filters ------------------------------------------------------------ func (s *Store) ListTCFilters(ctx context.Context) ([]model.TCFilter, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, class, source, dest, proto, dport, sport, tos, length, priority, comment FROM tc_filters ORDER BY id`) if err != nil { return nil, err } defer rows.Close() var out []model.TCFilter for rows.Next() { f, err := scanTCFilter(rows) if err != nil { return nil, err } out = append(out, f) } return out, rows.Err() } func (s *Store) GetTCFilter(ctx context.Context, id int64) (model.TCFilter, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, class, source, dest, proto, dport, sport, tos, length, priority, comment FROM tc_filters WHERE id = $1`, id) if err != nil { return model.TCFilter{}, err } defer rows.Close() if !rows.Next() { return model.TCFilter{}, ErrNotFound } return scanTCFilter(rows) } func scanTCFilter(rows pgx.Rows) (model.TCFilter, error) { var f model.TCFilter var dport, sport []byte if err := rows.Scan(&f.ID, &f.Device, &f.Class, &f.Source, &f.Dest, &f.Proto, &dport, &sport, &f.TOS, &f.Length, &f.Priority, &f.Comment); err != nil { return f, err } if err := unmarshalStrings(dport, &f.DPort); err != nil { return f, err } return f, unmarshalStrings(sport, &f.SPort) } func (s *Store) CreateTCFilter(ctx context.Context, f model.TCFilter) (int64, error) { dport, _ := jsonb(f.DPort) sport, _ := jsonb(f.SPort) var id int64 err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error { if err := tx.QueryRow(ctx, ` INSERT INTO tc_filters (device, class, source, dest, proto, dport, sport, tos, length, priority, comment) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING id`, f.Device, f.Class, f.Source, f.Dest, f.Proto, dport, sport, f.TOS, f.Length, f.Priority, f.Comment).Scan(&id); err != nil { return err } return bump(ctx, tx) }) return id, err } func (s *Store) DeleteTCFilter(ctx context.Context, id int64) error { return s.deleteOne(ctx, `DELETE FROM tc_filters WHERE id = $1`, id) } // ---- TC interfaces --------------------------------------------------------- func (s *Store) ListTCInterfaces(ctx context.Context) ([]model.TCInterface, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, interface, type, in_bandwidth, out_bandwidth, comment FROM tc_interfaces ORDER BY id`) if err != nil { return nil, err } defer rows.Close() var out []model.TCInterface for rows.Next() { var t model.TCInterface if err := rows.Scan(&t.ID, &t.Device, &t.Interface, &t.Type, &t.InBandwidth, &t.OutBandwidth, &t.Comment); err != nil { return nil, err } out = append(out, t) } return out, rows.Err() } func (s *Store) GetTCInterface(ctx context.Context, id int64) (model.TCInterface, error) { var t model.TCInterface err := s.pool.QueryRow(ctx, `SELECT id, device, interface, type, in_bandwidth, out_bandwidth, comment FROM tc_interfaces WHERE id = $1`, id, ).Scan(&t.ID, &t.Device, &t.Interface, &t.Type, &t.InBandwidth, &t.OutBandwidth, &t.Comment) if errors.Is(err, pgx.ErrNoRows) { return t, ErrNotFound } return t, err } func (s *Store) CreateTCInterface(ctx context.Context, t model.TCInterface) (int64, error) { var id int64 err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error { if err := tx.QueryRow(ctx, ` INSERT INTO tc_interfaces (device, interface, type, in_bandwidth, out_bandwidth, comment) VALUES ($1,$2,$3,$4,$5,$6) RETURNING id`, t.Device, t.Interface, t.Type, t.InBandwidth, t.OutBandwidth, t.Comment).Scan(&id); err != nil { return err } return bump(ctx, tx) }) return id, err } func (s *Store) DeleteTCInterface(ctx context.Context, id int64) error { return s.deleteOne(ctx, `DELETE FROM tc_interfaces WHERE id = $1`, id) } // ---- TC priorities --------------------------------------------------------- func (s *Store) ListTCPriorities(ctx context.Context) ([]model.TCPriority, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, band, proto, dport, sport, address, interface, helper, comment FROM tc_priorities ORDER BY id`) if err != nil { return nil, err } defer rows.Close() var out []model.TCPriority for rows.Next() { p, err := scanTCPriority(rows) if err != nil { return nil, err } out = append(out, p) } return out, rows.Err() } func (s *Store) GetTCPriority(ctx context.Context, id int64) (model.TCPriority, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, band, proto, dport, sport, address, interface, helper, comment FROM tc_priorities WHERE id = $1`, id) if err != nil { return model.TCPriority{}, err } defer rows.Close() if !rows.Next() { return model.TCPriority{}, ErrNotFound } return scanTCPriority(rows) } func scanTCPriority(rows pgx.Rows) (model.TCPriority, error) { var p model.TCPriority var dport, sport []byte if err := rows.Scan(&p.ID, &p.Device, &p.Band, &p.Proto, &dport, &sport, &p.Address, &p.Interface, &p.Helper, &p.Comment); err != nil { return p, err } if err := unmarshalStrings(dport, &p.DPort); err != nil { return p, err } return p, unmarshalStrings(sport, &p.SPort) } func (s *Store) CreateTCPriority(ctx context.Context, p model.TCPriority) (int64, error) { dport, _ := jsonb(p.DPort) sport, _ := jsonb(p.SPort) var id int64 err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error { if err := tx.QueryRow(ctx, ` INSERT INTO tc_priorities (device, band, proto, dport, sport, address, interface, helper, comment) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING id`, p.Device, p.Band, p.Proto, dport, sport, p.Address, p.Interface, p.Helper, p.Comment).Scan(&id); err != nil { return err } return bump(ctx, tx) }) return id, err } func (s *Store) DeleteTCPriority(ctx context.Context, id int64) error { return s.deleteOne(ctx, `DELETE FROM tc_priorities WHERE id = $1`, id) }