package store import "strings" // R-415 / R-138 option B (2026-10-08; operator ruling 2026-09-14, `01` §7: every customer has their OWN domain, never a // name under felhom.eu). The hub enforced uniqueness on customer_id only, so two customers could be given the same // domain — or one inside the other, a shared zone in all but name — silently. DomainConflict answers, for a domain about // to be saved for customerID, which rule it breaks ("" = none): // // - "felhom.eu" — the domain is felhom.eu or a name under it; // - "" — another customer's domain equals it, contains it, or lies under it. // // Matching is case-insensitive, ignores a trailing dot, and is on LABEL boundaries („notexample.hu" is not under // „example.hu"). An empty domain conflicts with nothing. Pinned by TestR415_* (domain_conflict_test.go) and the // handler test TestR415_CreateAndEditRefuseConflictingDomain. func (s *Store) DomainConflict(customerID, domain string) (string, error) { d := normDomain(domain) if d == "" { return "", nil } if d == "felhom.eu" || strings.HasSuffix(d, ".felhom.eu") { return "felhom.eu", nil } rows, err := s.db.Query(`SELECT customer_id, domain FROM customer_configs WHERE customer_id != ? AND domain != ''`, customerID) if err != nil { return "", err } defer rows.Close() for rows.Next() { var id, other string if err := rows.Scan(&id, &other); err != nil { return "", err } o := normDomain(other) if o == "" { continue } if d == o || strings.HasSuffix(d, "."+o) || strings.HasSuffix(o, "."+d) { return id, nil } } return "", rows.Err() } func normDomain(d string) string { return strings.TrimSuffix(strings.ToLower(strings.TrimSpace(d)), ".") }