package main import ( "database/sql" "encoding/json" "fmt" "log" "strings" _ "github.com/lib/pq" ) func connectDB(config Config) (*sql.DB, error) { connStr := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", config.DBHost, config.DBPort, config.DBUser, config.DBPassword, config.DBName) db, err := sql.Open("postgres", connStr) if err != nil { return nil, err } if err := db.Ping(); err != nil { return nil, err } return db, nil } func fetchClientsData(db *sql.DB) (map[string]ClientData, error) { // aliases это JSON массив в БД, конвертируем в текст через ::text // rps_limit берётся из client_info — NULL означает "алерт не нужен" // Данные всегда берутся из обеих таблиц: sp_info + manual_info (UNION) query := ` SELECT DISTINCT COALESCE(a.client_title, 'Unknown') as client_title, s.sid, s.domain_name, COALESCE(s.aliases::text, 'null') as aliases, COALESCE(MAX(ci.rps_limit) OVER (PARTITION BY a.client_title), 0) as rps_limit, COALESCE(MAX(ci.rps_commercial_limit) OVER (PARTITION BY a.client_title), 100) as rps_commercial_limit, COALESCE(a.four_hundred, 20) as limit_4xx, COALESCE(a.five_hundred, 10) as limit_5xx, COALESCE(a.ptaf_fallback_code_alert_count, 1) as ptaf_fallback_code_alert_count, COALESCE(MAX(ci.waf_provider) OVER (PARTITION BY a.client_title), '') as waf_provider, COALESCE(MAX(ci.ptaf_fallback_code) OVER (PARTITION BY a.client_title), '') as ptaf_fallback_code FROM ( SELECT sid, domain_name, aliases FROM sp_info WHERE sid IS NOT NULL UNION SELECT sid, domain_name, aliases FROM manual_info WHERE sid IS NOT NULL ) s LEFT JOIN apps_settings a ON s.sid = a.l7resourceid LEFT JOIN client_info ci ON a.client_title = ci.client_title WHERE ci.waf_provider IS NOT NULL ORDER BY COALESCE(a.client_title, 'Unknown'), s.sid ` rows, err := db.Query(query) if err != nil { return nil, fmt.Errorf("query failed: %w", err) } defer rows.Close() clients := make(map[string]ClientData) for rows.Next() { var clientTitle, sid, domainName, aliasesJSON, wafProvider, ptafFallbackCode string var rpsLimit, rpsCommercialLimit, limit4xx, limit5xx, ptafFallbackCodeAlertCount int if err := rows.Scan(&clientTitle, &sid, &domainName, &aliasesJSON, &rpsLimit, &rpsCommercialLimit, &limit4xx, &limit5xx, &ptafFallbackCodeAlertCount, &wafProvider, &ptafFallbackCode); err != nil { return nil, fmt.Errorf("scan failed: %w", err) } // Распарсить JSON массив aliases aliasesStr := parseAliasesJSON(aliasesJSON) if _, exists := clients[clientTitle]; !exists { clients[clientTitle] = ClientData{ ClientTitle: clientTitle, Domains: []DomainInfo{}, RPSLimit: rpsLimit, RPSCommercialLimit: rpsCommercialLimit, WafProvider: wafProvider, PtafFallbackCode: ptafFallbackCode, } } client := clients[clientTitle] client.Domains = append(client.Domains, DomainInfo{ SID: sid, DomainName: domainName, Aliases: aliasesStr, Limit4xx: limit4xx, Limit5xx: limit5xx, PtafFallbackCodeAlertCount: ptafFallbackCodeAlertCount, }) clients[clientTitle] = client } return clients, rows.Err() } // parseAliasesJSON конвертирует JSON массив aliases в строку // Input: ["domain1.com", "domain2.com"] или null // Output: "domain1.com, domain2.com" или "" func parseAliasesJSON(jsonStr string) string { // Если null или пустое if jsonStr == "" || jsonStr == "null" { return "" } // Попытаться распарсить как JSON массив var aliases []string if err := json.Unmarshal([]byte(jsonStr), &aliases); err != nil { // Если не удалось распарсить - вернуть как есть log.Printf("Warning: Failed to parse aliases JSON: %s (error: %v)", jsonStr, err) return jsonStr } // Соединить через запятую return strings.Join(aliases, ", ") }