package store import ( "errors" "fmt" "strings" "helix-proxy/internal/models" ) var ( ErrDuplicateProxySource = errors.New("duplicate proxy host source domain") ErrDuplicateRedirectionSource = errors.New("duplicate redirection host source domain") ErrDuplicateDeadSource = errors.New("duplicate dead host source domain") ErrDuplicateStreamPort = errors.New("duplicate stream incoming port") ) func IsConflictError(err error) bool { return errors.Is(err, ErrDuplicateProxySource) || errors.Is(err, ErrDuplicateRedirectionSource) || errors.Is(err, ErrDuplicateDeadSource) || errors.Is(err, ErrDuplicateStreamPort) } type domainRecord struct { ID int DomainNames []string } func normalizeSourceDomain(d string) (normalized, display string, ok bool) { display = strings.TrimSpace(d) if display == "" { return "", "", false } return strings.ToLower(display), display, true } func validateUniqueDomains(domainNames []string, selfID int, existing []domainRecord, errSentinel error) error { own := make(map[string]string) for _, d := range domainNames { norm, display, ok := normalizeSourceDomain(d) if !ok { continue } if prev, dup := own[norm]; dup { return fmt.Errorf("%w: duplicate source domain: %s", errSentinel, prev) } own[norm] = display } used := make(map[string]struct{}) for _, rec := range existing { if rec.ID == selfID { continue } for _, d := range rec.DomainNames { norm, _, ok := normalizeSourceDomain(d) if !ok { continue } used[norm] = struct{}{} } } for norm, display := range own { if _, conflict := used[norm]; conflict { return fmt.Errorf("%w: source domain already in use: %s", errSentinel, display) } } return nil } func validateProxyHostSources(hosts []models.ProxyHost, h models.ProxyHost) error { existing := make([]domainRecord, len(hosts)) for i, host := range hosts { existing[i] = domainRecord{ID: host.ID, DomainNames: host.DomainNames} } return validateUniqueDomains(h.DomainNames, h.ID, existing, ErrDuplicateProxySource) } func validateRedirectionHostSources(hosts []models.RedirectionHost, h models.RedirectionHost) error { existing := make([]domainRecord, len(hosts)) for i, host := range hosts { existing[i] = domainRecord{ID: host.ID, DomainNames: host.DomainNames} } return validateUniqueDomains(h.DomainNames, h.ID, existing, ErrDuplicateRedirectionSource) } func validateDeadHostSources(hosts []models.DeadHost, h models.DeadHost) error { existing := make([]domainRecord, len(hosts)) for i, host := range hosts { existing[i] = domainRecord{ID: host.ID, DomainNames: host.DomainNames} } return validateUniqueDomains(h.DomainNames, h.ID, existing, ErrDuplicateDeadSource) } func validateStreamIncomingPort(st models.Stream, streams []models.Stream) error { if st.IncomingPort <= 0 { return nil } for _, other := range streams { if other.ID == st.ID { continue } if other.IncomingPort == st.IncomingPort { return fmt.Errorf("%w: incoming port already in use: %d", ErrDuplicateStreamPort, st.IncomingPort) } } return nil }