package waf import ( "context" "errors" "time" "github.com/jackc/pgx/v5" "git.netcell-it.de/projekte/edgeguard-native/internal/models" ) // ErrProfileNotFound wird von Get/Update/Delete zurückgegeben, wenn kein Profil // mit der ID existiert. var ErrProfileNotFound = errors.New("waf app profile not found") const profileSelect = ` SELECT id, name, description, rule_exclusions, builtin, created_at, updated_at FROM waf_app_profiles ` func scanProfile(row pgx.Row) (*models.WafAppProfile, error) { var p models.WafAppProfile if err := row.Scan(&p.ID, &p.Name, &p.Description, &p.RuleExclusions, &p.Builtin, &p.CreatedAt, &p.UpdatedAt); err != nil { return nil, err } return &p, nil } // ListProfiles gibt alle App-Profile zurück (Built-in zuerst, dann alphabetisch). func (r *Repo) ListProfiles(ctx context.Context) ([]models.WafAppProfile, error) { rows, err := r.Pool.Query(ctx, profileSelect+" ORDER BY builtin DESC, name ASC") if err != nil { return nil, err } defer rows.Close() out := make([]models.WafAppProfile, 0, 16) for rows.Next() { p, err := scanProfile(rows) if err != nil { return nil, err } out = append(out, *p) } return out, rows.Err() } // profilesByName lädt alle Profile in eine Name→Profil-Map (für die Auflösung // im Agent-Loader). func (r *Repo) profilesByName(ctx context.Context) (map[string]models.WafAppProfile, error) { list, err := r.ListProfiles(ctx) if err != nil { return nil, err } m := make(map[string]models.WafAppProfile, len(list)) for _, p := range list { m[p.Name] = p } return m, nil } // GetProfile gibt ein Profil per ID zurück, oder ErrProfileNotFound. func (r *Repo) GetProfile(ctx context.Context, id int64) (*models.WafAppProfile, error) { row := r.Pool.QueryRow(ctx, profileSelect+" WHERE id = $1", id) p, err := scanProfile(row) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, ErrProfileNotFound } return nil, err } return p, nil } // CreateProfile legt ein neues benutzerdefiniertes Profil an (builtin immer // false — Built-ins werden nicht über die API erzeugt). func (r *Repo) CreateProfile(ctx context.Context, name, description string, exclusions []string) (*models.WafAppProfile, error) { if exclusions == nil { exclusions = []string{} } now := time.Now() row := r.Pool.QueryRow(ctx, ` INSERT INTO waf_app_profiles (name, description, rule_exclusions, builtin, created_at, updated_at) VALUES ($1,$2,$3,false,$4,$4) RETURNING id, name, description, rule_exclusions, builtin, created_at, updated_at `, name, description, exclusions, now) return scanProfile(row) } // UpdateProfile ändert Name/Beschreibung/Ausnahmen eines Profils. Built-in- // Profile sind read-only (WHERE builtin = false) → ErrProfileNotFound, wenn // das Profil fehlt ODER built-in ist. func (r *Repo) UpdateProfile(ctx context.Context, id int64, name, description string, exclusions []string) (*models.WafAppProfile, error) { if exclusions == nil { exclusions = []string{} } row := r.Pool.QueryRow(ctx, ` UPDATE waf_app_profiles SET name = $2, description = $3, rule_exclusions = $4, updated_at = $5 WHERE id = $1 AND builtin = false RETURNING id, name, description, rule_exclusions, builtin, created_at, updated_at `, id, name, description, exclusions, time.Now()) p, err := scanProfile(row) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, ErrProfileNotFound } return nil, err } return p, nil } // DeleteProfile entfernt ein benutzerdefiniertes Profil. Built-in-Profile sind // geschützt. Gibt ErrProfileNotFound zurück, wenn nichts gelöscht wurde. func (r *Repo) DeleteProfile(ctx context.Context, id int64) error { tag, err := r.Pool.Exec(ctx, `DELETE FROM waf_app_profiles WHERE id = $1 AND builtin = false`, id) if err != nil { return err } if tag.RowsAffected() == 0 { return ErrProfileNotFound } return nil }