package firewall import ( "context" "errors" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "git.netcell-it.de/projekte/edgeguard-native/internal/models" ) var ErrZoneNotFound = errors.New("zone not found") type ZonesRepo struct { Pool *pgxpool.Pool } func NewZonesRepo(pool *pgxpool.Pool) *ZonesRepo { return &ZonesRepo{Pool: pool} } const zoneBaseSelect = ` SELECT id, name, description, builtin, created_at, updated_at FROM firewall_zones ` func (r *ZonesRepo) List(ctx context.Context) ([]models.FirewallZone, error) { rows, err := r.Pool.Query(ctx, zoneBaseSelect+" ORDER BY name ASC") if err != nil { return nil, err } defer rows.Close() out := make([]models.FirewallZone, 0, 8) for rows.Next() { z, err := scanZone(rows) if err != nil { return nil, err } out = append(out, *z) } return out, rows.Err() } func (r *ZonesRepo) Get(ctx context.Context, id int64) (*models.FirewallZone, error) { row := r.Pool.QueryRow(ctx, zoneBaseSelect+" WHERE id = $1", id) z, err := scanZone(row) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, ErrZoneNotFound } return nil, err } return z, nil } // Exists is used by the rules / nat / iface handlers to validate // that the zone name they got from the operator references a real // zone (or the special 'any' which the rule layer handles itself). func (r *ZonesRepo) Exists(ctx context.Context, name string) (bool, error) { var n int err := r.Pool.QueryRow(ctx, `SELECT 1 FROM firewall_zones WHERE name = $1`, name).Scan(&n) if errors.Is(err, pgx.ErrNoRows) { return false, nil } if err != nil { return false, err } return true, nil } func (r *ZonesRepo) Create(ctx context.Context, z models.FirewallZone) (*models.FirewallZone, error) { row := r.Pool.QueryRow(ctx, ` INSERT INTO firewall_zones (name, description, builtin) VALUES ($1, $2, FALSE) RETURNING id, name, description, builtin, created_at, updated_at`, z.Name, z.Description) return scanZone(row) } // Update — builtin zones may have their description tweaked but // not their name (the renderer + iface rows reference zones by // name and a rename would silently dangle them). Custom zones can // be renamed; the handler is responsible for cascading the new // name into network_interfaces.role / firewall_rules.src_zone / // dst_zone / firewall_nat_rules.in_zone / out_zone if needed. func (r *ZonesRepo) Update(ctx context.Context, id int64, z models.FirewallZone) (*models.FirewallZone, error) { cur, err := r.Get(ctx, id) if err != nil { return nil, err } name := z.Name if cur.Builtin { name = cur.Name } row := r.Pool.QueryRow(ctx, ` UPDATE firewall_zones SET name = $1, description = $2, updated_at = NOW() WHERE id = $3 RETURNING id, name, description, builtin, created_at, updated_at`, name, z.Description, id) return scanZone(row) } // Delete — builtin zones are non-deletable; for custom zones the // caller must check for references in network_interfaces / // firewall_rules / firewall_nat_rules first (handler concern). func (r *ZonesRepo) Delete(ctx context.Context, id int64) error { cur, err := r.Get(ctx, id) if err != nil { return err } if cur.Builtin { return errors.New("builtin zone cannot be deleted") } tag, err := r.Pool.Exec(ctx, `DELETE FROM firewall_zones WHERE id = $1`, id) if err != nil { return err } if tag.RowsAffected() == 0 { return ErrZoneNotFound } return nil } // References returns the count of foreign uses of this zone (by // name) so the handler can surface a "zone is still in use" error // instead of letting a cascade go silent. func (r *ZonesRepo) References(ctx context.Context, name string) (int, error) { var n int err := r.Pool.QueryRow(ctx, ` SELECT (SELECT COUNT(*) FROM network_interfaces WHERE role = $1) + (SELECT COUNT(*) FROM firewall_rules WHERE src_zone = $1 OR dst_zone = $1) + (SELECT COUNT(*) FROM firewall_nat_rules WHERE in_zone = $1 OR out_zone = $1)`, name).Scan(&n) return n, err } func scanZone(row interface{ Scan(...any) error }) (*models.FirewallZone, error) { var z models.FirewallZone if err := row.Scan( &z.ID, &z.Name, &z.Description, &z.Builtin, &z.CreatedAt, &z.UpdatedAt, ); err != nil { return nil, err } return &z, nil }