package store import ( "context" "errors" "fmt" "github.com/jackc/pgx/v5" "git.unkin.net/unkin/tomswallapi/internal/model" ) // ---- Tunnels --------------------------------------------------------------- func (s *Store) ListTunnels(ctx context.Context) ([]model.Tunnel, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, type, zone, gateways, gateway_zones, port, comment FROM tunnels ORDER BY id`) if err != nil { return nil, err } defer rows.Close() var out []model.Tunnel for rows.Next() { t, err := scanTunnel(rows) if err != nil { return nil, err } out = append(out, t) } return out, rows.Err() } func (s *Store) GetTunnel(ctx context.Context, id int64) (model.Tunnel, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, type, zone, gateways, gateway_zones, port, comment FROM tunnels WHERE id = $1`, id) if err != nil { return model.Tunnel{}, err } defer rows.Close() if !rows.Next() { return model.Tunnel{}, ErrNotFound } return scanTunnel(rows) } func scanTunnel(rows pgx.Rows) (model.Tunnel, error) { var t model.Tunnel var gw, gz []byte if err := rows.Scan(&t.ID, &t.Device, &t.Type, &t.Zone, &gw, &gz, &t.Port, &t.Comment); err != nil { return t, err } if err := unmarshalStrings(gw, &t.Gateways); err != nil { return t, err } return t, unmarshalStrings(gz, &t.GatewayZones) } func (s *Store) CreateTunnel(ctx context.Context, t model.Tunnel) (int64, error) { gw, _ := jsonb(t.Gateways) gz, _ := jsonb(t.GatewayZones) var id int64 err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error { if err := tx.QueryRow(ctx, ` INSERT INTO tunnels (device, type, zone, gateways, gateway_zones, port, comment) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`, t.Device, t.Type, t.Zone, gw, gz, t.Port, t.Comment).Scan(&id); err != nil { return err } return bump(ctx, tx) }) return id, err } func (s *Store) DeleteTunnel(ctx context.Context, id int64) error { return s.deleteOne(ctx, `DELETE FROM tunnels WHERE id = $1`, id) } // ---- Stopped rules --------------------------------------------------------- func (s *Store) ListStoppedRules(ctx context.Context) ([]model.StoppedRule, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, action, source, dest, proto, dport, sport, comment FROM stopped_rules ORDER BY id`) if err != nil { return nil, err } defer rows.Close() var out []model.StoppedRule for rows.Next() { r, err := scanStopped(rows) if err != nil { return nil, err } out = append(out, r) } return out, rows.Err() } func (s *Store) GetStoppedRule(ctx context.Context, id int64) (model.StoppedRule, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, action, source, dest, proto, dport, sport, comment FROM stopped_rules WHERE id = $1`, id) if err != nil { return model.StoppedRule{}, err } defer rows.Close() if !rows.Next() { return model.StoppedRule{}, ErrNotFound } return scanStopped(rows) } func scanStopped(rows pgx.Rows) (model.StoppedRule, error) { var r model.StoppedRule var dport, sport []byte if err := rows.Scan(&r.ID, &r.Device, &r.Action, &r.Source, &r.Dest, &r.Proto, &dport, &sport, &r.Comment); err != nil { return r, err } if err := unmarshalStrings(dport, &r.DPort); err != nil { return r, err } return r, unmarshalStrings(sport, &r.SPort) } func (s *Store) CreateStoppedRule(ctx context.Context, r model.StoppedRule) (int64, error) { dport, _ := jsonb(r.DPort) sport, _ := jsonb(r.SPort) var id int64 err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error { if err := tx.QueryRow(ctx, ` INSERT INTO stopped_rules (device, action, source, dest, proto, dport, sport, comment) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id`, r.Device, r.Action, r.Source, r.Dest, r.Proto, dport, sport, r.Comment).Scan(&id); err != nil { return err } return bump(ctx, tx) }) return id, err } func (s *Store) DeleteStoppedRule(ctx context.Context, id int64) error { return s.deleteOne(ctx, `DELETE FROM stopped_rules WHERE id = $1`, id) } // ---- Proxy ARP / NDP (identical shape, table-parameterized) ---------------- func (s *Store) listProxy(ctx context.Context, table string) ([]model.ProxyEntry, error) { q := fmt.Sprintf(`SELECT id, device, address, interface, external, haveroute, persistent, comment FROM %s ORDER BY id`, table) rows, err := s.pool.Query(ctx, q) if err != nil { return nil, err } defer rows.Close() var out []model.ProxyEntry for rows.Next() { var p model.ProxyEntry if err := rows.Scan(&p.ID, &p.Device, &p.Address, &p.Interface, &p.External, &p.HaveRoute, &p.Persistent, &p.Comment); err != nil { return nil, err } out = append(out, p) } return out, rows.Err() } func (s *Store) getProxy(ctx context.Context, table string, id int64) (model.ProxyEntry, error) { q := fmt.Sprintf(`SELECT id, device, address, interface, external, haveroute, persistent, comment FROM %s WHERE id = $1`, table) var p model.ProxyEntry err := s.pool.QueryRow(ctx, q, id).Scan(&p.ID, &p.Device, &p.Address, &p.Interface, &p.External, &p.HaveRoute, &p.Persistent, &p.Comment) if errors.Is(err, pgx.ErrNoRows) { return p, ErrNotFound } return p, err } func (s *Store) createProxy(ctx context.Context, table string, p model.ProxyEntry) (int64, error) { q := fmt.Sprintf(`INSERT INTO %s (device, address, interface, external, haveroute, persistent, comment) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`, table) var id int64 err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error { if err := tx.QueryRow(ctx, q, p.Device, p.Address, p.Interface, p.External, p.HaveRoute, p.Persistent, p.Comment).Scan(&id); err != nil { return err } return bump(ctx, tx) }) return id, err } func (s *Store) ListProxyARP(ctx context.Context) ([]model.ProxyEntry, error) { return s.listProxy(ctx, "proxy_arp") } func (s *Store) GetProxyARP(ctx context.Context, id int64) (model.ProxyEntry, error) { return s.getProxy(ctx, "proxy_arp", id) } func (s *Store) CreateProxyARP(ctx context.Context, p model.ProxyEntry) (int64, error) { return s.createProxy(ctx, "proxy_arp", p) } func (s *Store) DeleteProxyARP(ctx context.Context, id int64) error { return s.deleteOne(ctx, `DELETE FROM proxy_arp WHERE id = $1`, id) } func (s *Store) ListProxyNDP(ctx context.Context) ([]model.ProxyEntry, error) { return s.listProxy(ctx, "proxy_ndp") } func (s *Store) GetProxyNDP(ctx context.Context, id int64) (model.ProxyEntry, error) { return s.getProxy(ctx, "proxy_ndp", id) } func (s *Store) CreateProxyNDP(ctx context.Context, p model.ProxyEntry) (int64, error) { return s.createProxy(ctx, "proxy_ndp", p) } func (s *Store) DeleteProxyNDP(ctx context.Context, id int64) error { return s.deleteOne(ctx, `DELETE FROM proxy_ndp WHERE id = $1`, id) } // ---- ARP rules ------------------------------------------------------------- func (s *Store) ListArpRules(ctx context.Context) ([]model.ArpRule, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, action, action_address, action_mac, source, dest, opcode, comment FROM arp_rules ORDER BY id`) if err != nil { return nil, err } defer rows.Close() var out []model.ArpRule for rows.Next() { var r model.ArpRule if err := rows.Scan(&r.ID, &r.Device, &r.Action, &r.ActionAddress, &r.ActionMAC, &r.Source, &r.Dest, &r.Opcode, &r.Comment); err != nil { return nil, err } out = append(out, r) } return out, rows.Err() } func (s *Store) GetArpRule(ctx context.Context, id int64) (model.ArpRule, error) { var r model.ArpRule err := s.pool.QueryRow(ctx, `SELECT id, device, action, action_address, action_mac, source, dest, opcode, comment FROM arp_rules WHERE id = $1`, id, ).Scan(&r.ID, &r.Device, &r.Action, &r.ActionAddress, &r.ActionMAC, &r.Source, &r.Dest, &r.Opcode, &r.Comment) if errors.Is(err, pgx.ErrNoRows) { return r, ErrNotFound } return r, err } func (s *Store) CreateArpRule(ctx context.Context, r model.ArpRule) (int64, error) { var id int64 err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error { if err := tx.QueryRow(ctx, ` INSERT INTO arp_rules (device, action, action_address, action_mac, source, dest, opcode, comment) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id`, r.Device, r.Action, r.ActionAddress, r.ActionMAC, r.Source, r.Dest, r.Opcode, r.Comment).Scan(&id); err != nil { return err } return bump(ctx, tx) }) return id, err } func (s *Store) DeleteArpRule(ctx context.Context, id int64) error { return s.deleteOne(ctx, `DELETE FROM arp_rules WHERE id = $1`, id) } // ---- Maclist --------------------------------------------------------------- func (s *Store) ListMaclist(ctx context.Context) ([]model.MaclistEntry, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, action, interface, mac, addresses, log, comment FROM maclist ORDER BY id`) if err != nil { return nil, err } defer rows.Close() var out []model.MaclistEntry for rows.Next() { m, err := scanMaclist(rows) if err != nil { return nil, err } out = append(out, m) } return out, rows.Err() } func (s *Store) GetMaclist(ctx context.Context, id int64) (model.MaclistEntry, error) { rows, err := s.pool.Query(ctx, `SELECT id, device, action, interface, mac, addresses, log, comment FROM maclist WHERE id = $1`, id) if err != nil { return model.MaclistEntry{}, err } defer rows.Close() if !rows.Next() { return model.MaclistEntry{}, ErrNotFound } return scanMaclist(rows) } func scanMaclist(rows pgx.Rows) (model.MaclistEntry, error) { var m model.MaclistEntry var addrs []byte if err := rows.Scan(&m.ID, &m.Device, &m.Action, &m.Interface, &m.MAC, &addrs, &m.Log, &m.Comment); err != nil { return m, err } return m, unmarshalStrings(addrs, &m.Addresses) } func (s *Store) CreateMaclist(ctx context.Context, m model.MaclistEntry) (int64, error) { addrs, _ := jsonb(m.Addresses) var id int64 err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error { if err := tx.QueryRow(ctx, ` INSERT INTO maclist (device, action, interface, mac, addresses, log, comment) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`, m.Device, m.Action, m.Interface, m.MAC, addrs, m.Log, m.Comment).Scan(&id); err != nil { return err } return bump(ctx, tx) }) return id, err } func (s *Store) DeleteMaclist(ctx context.Context, id int64) error { return s.deleteOne(ctx, `DELETE FROM maclist WHERE id = $1`, id) }