package repository import ( "context" "errors" "strings" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5" ) // FilterRuleRow is ContentSafety FilterRule persistence. type FilterRuleRow struct { ID uuid.UUID `json:"id"` Code string `json:"code"` Title string `json:"title"` Category string `json:"category"` Pattern string `json:"pattern"` Action string `json:"action"` Active bool `json:"active"` System bool `json:"system"` UpdatedAt time.Time `json:"updated_at"` } // FilterMatch is one evaluate hit. type FilterMatch struct { Code string `json:"code"` Title string `json:"title"` Category string `json:"category"` Action string `json:"action"` } // ListFilterRules returns active-first filter rules. func (r *AdminRepo) ListFilterRules(ctx context.Context) ([]FilterRuleRow, error) { rows, err := r.Pool.Query(ctx, ` SELECT id, code, title, category, pattern, action, active, system, updated_at FROM filter_rules ORDER BY active DESC, category ASC, code ASC LIMIT 500`) if err != nil { return nil, err } defer rows.Close() var out []FilterRuleRow for rows.Next() { var f FilterRuleRow if err := rows.Scan( &f.ID, &f.Code, &f.Title, &f.Category, &f.Pattern, &f.Action, &f.Active, &f.System, &f.UpdatedAt, ); err != nil { return nil, err } out = append(out, f) } return out, rows.Err() } // GetFilterRule loads one rule by id. func (r *AdminRepo) GetFilterRule(ctx context.Context, id uuid.UUID) (*FilterRuleRow, error) { var f FilterRuleRow err := r.Pool.QueryRow(ctx, ` SELECT id, code, title, category, pattern, action, active, system, updated_at FROM filter_rules WHERE id=$1`, id, ).Scan(&f.ID, &f.Code, &f.Title, &f.Category, &f.Pattern, &f.Action, &f.Active, &f.System, &f.UpdatedAt) if errors.Is(err, pgx.ErrNoRows) { return nil, err } if err != nil { return nil, err } return &f, nil } // EvaluateFilterRules runs simple substring match on active rules (ops preview). func (r *AdminRepo) EvaluateFilterRules(ctx context.Context, text string) ([]FilterMatch, error) { rules, err := r.ListFilterRules(ctx) if err != nil { return nil, err } lower := strings.ToLower(text) var out []FilterMatch for _, rule := range rules { if !rule.Active || rule.Pattern == "" { continue } if strings.Contains(lower, strings.ToLower(rule.Pattern)) { out = append(out, FilterMatch{ Code: rule.Code, Title: rule.Title, Category: rule.Category, Action: rule.Action, }) } } if out == nil { out = []FilterMatch{} } return out, nil }