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 ErrServiceNotFound = errors.New("service not found") type ServicesRepo struct { Pool *pgxpool.Pool } func NewServicesRepo(pool *pgxpool.Pool) *ServicesRepo { return &ServicesRepo{Pool: pool} } const svcBaseSelect = ` SELECT id, name, proto, port_start, port_end, builtin, description, created_at, updated_at FROM firewall_services ` func (r *ServicesRepo) List(ctx context.Context) ([]models.FirewallService, error) { rows, err := r.Pool.Query(ctx, svcBaseSelect+" ORDER BY name ASC") if err != nil { return nil, err } defer rows.Close() out := make([]models.FirewallService, 0, 16) for rows.Next() { s, err := scanService(rows) if err != nil { return nil, err } out = append(out, *s) } return out, rows.Err() } func (r *ServicesRepo) Get(ctx context.Context, id int64) (*models.FirewallService, error) { row := r.Pool.QueryRow(ctx, svcBaseSelect+" WHERE id = $1", id) s, err := scanService(row) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, ErrServiceNotFound } return nil, err } return s, nil } func (r *ServicesRepo) Create(ctx context.Context, s models.FirewallService) (*models.FirewallService, error) { row := r.Pool.QueryRow(ctx, ` INSERT INTO firewall_services (name, proto, port_start, port_end, builtin, description) VALUES ($1, $2, $3, $4, FALSE, $5) RETURNING id, name, proto, port_start, port_end, builtin, description, created_at, updated_at`, s.Name, s.Proto, s.PortStart, s.PortEnd, s.Description) return scanService(row) } func (r *ServicesRepo) Update(ctx context.Context, id int64, s models.FirewallService) (*models.FirewallService, error) { // Forbid editing builtin services — they're guaranteed by the // migration set; users that want a tweak can clone with a new name. cur, err := r.Get(ctx, id) if err != nil { return nil, err } if cur.Builtin { return nil, errors.New("builtin service cannot be edited — clone it under a new name") } row := r.Pool.QueryRow(ctx, ` UPDATE firewall_services SET name = $1, proto = $2, port_start = $3, port_end = $4, description = $5, updated_at = NOW() WHERE id = $6 RETURNING id, name, proto, port_start, port_end, builtin, description, created_at, updated_at`, s.Name, s.Proto, s.PortStart, s.PortEnd, s.Description, id) return scanService(row) } func (r *ServicesRepo) 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 service cannot be deleted") } tag, err := r.Pool.Exec(ctx, `DELETE FROM firewall_services WHERE id = $1`, id) if err != nil { return err } if tag.RowsAffected() == 0 { return ErrServiceNotFound } return nil } func scanService(row interface{ Scan(...any) error }) (*models.FirewallService, error) { var s models.FirewallService if err := row.Scan( &s.ID, &s.Name, &s.Proto, &s.PortStart, &s.PortEnd, &s.Builtin, &s.Description, &s.CreatedAt, &s.UpdatedAt, ); err != nil { return nil, err } return &s, nil }