update

Changed files
+55 -39
internal
api
storage
+3
internal/api/handlers.go
··· 276 276 if pds.IPInfo.ASN > 0 { 277 277 response["asn"] = pds.IPInfo.ASN 278 278 } 279 + if pds.IPInfo.IsDatacenter { 280 + response["is_datacenter"] = pds.IPInfo.IsDatacenter 281 + } 279 282 } 280 283 281 284 return response
+52 -39
internal/storage/postgres.go
··· 93 93 CREATE INDEX IF NOT EXISTS idx_endpoints_type ON endpoints(endpoint_type); 94 94 CREATE INDEX IF NOT EXISTS idx_endpoints_ip ON endpoints(ip); 95 95 CREATE INDEX IF NOT EXISTS idx_endpoints_server_did ON endpoints(server_did); 96 + CREATE INDEX IF NOT EXISTS idx_endpoints_server_did_type_discovered ON endpoints(server_did, endpoint_type, discovered_at); 96 97 97 98 -- IP infos table (IP as PRIMARY KEY) 98 99 CREATE TABLE IF NOT EXISTS ip_infos ( ··· 670 671 671 672 func (p *PostgresDB) GetPDSDetail(ctx context.Context, endpoint string) (*PDSDetail, error) { 672 673 query := ` 674 + WITH target_endpoint AS ( 675 + SELECT 676 + e.id, 677 + e.endpoint, 678 + e.server_did, 679 + e.discovered_at, 680 + e.last_checked, 681 + e.status, 682 + e.ip 683 + FROM endpoints e 684 + WHERE e.endpoint = $1 AND e.endpoint_type = 'pds' 685 + ), 686 + aliases_agg AS ( 687 + SELECT 688 + te.server_did, 689 + array_agg(e.endpoint ORDER BY e.discovered_at) FILTER (WHERE e.endpoint != te.endpoint) as aliases, 690 + MIN(e.discovered_at) as first_discovered_at 691 + FROM target_endpoint te 692 + LEFT JOIN endpoints e ON te.server_did = e.server_did 693 + AND e.endpoint_type = 'pds' 694 + AND te.server_did IS NOT NULL 695 + GROUP BY te.server_did 696 + ) 673 697 SELECT 674 - e.id, e.endpoint, e.server_did, e.discovered_at, e.last_checked, e.status, e.ip, 698 + te.id, 699 + te.endpoint, 700 + te.server_did, 701 + te.discovered_at, 702 + te.last_checked, 703 + te.status, 704 + te.ip, 675 705 latest.user_count, 676 706 latest.response_time, 677 707 latest.version, ··· 679 709 latest.scanned_at, 680 710 i.city, i.country, i.country_code, i.asn, i.asn_org, 681 711 i.is_datacenter, i.is_vpn, i.latitude, i.longitude, 682 - i.raw_data 683 - FROM endpoints e 712 + i.raw_data, 713 + COALESCE(aa.aliases, ARRAY[]::text[]) as aliases, 714 + aa.first_discovered_at 715 + FROM target_endpoint te 716 + LEFT JOIN aliases_agg aa ON te.server_did = aa.server_did 684 717 LEFT JOIN LATERAL ( 685 718 SELECT scan_data, response_time, version, scanned_at, user_count 686 719 FROM endpoint_scans 687 - WHERE endpoint_id = e.id 720 + WHERE endpoint_id = te.id 688 721 ORDER BY scanned_at DESC 689 722 LIMIT 1 690 723 ) latest ON true 691 - LEFT JOIN ip_infos i ON e.ip = i.ip 692 - WHERE e.endpoint = $1 AND e.endpoint_type = 'pds' 724 + LEFT JOIN ip_infos i ON te.ip = i.ip 693 725 ` 694 726 695 727 detail := &PDSDetail{} ··· 703 735 var serverInfoJSON []byte 704 736 var scannedAt sql.NullTime 705 737 var rawDataJSON []byte 738 + var aliases []string 739 + var firstDiscoveredAt sql.NullTime 706 740 707 741 err := p.db.QueryRowContext(ctx, query, endpoint).Scan( 708 742 &detail.ID, &detail.Endpoint, &serverDID, &detail.DiscoveredAt, &detail.LastChecked, &detail.Status, &ip, ··· 710 744 &city, &country, &countryCode, &asn, &asnOrg, 711 745 &isDatacenter, &isVPN, &lat, &lon, 712 746 &rawDataJSON, 747 + pq.Array(&aliases), 748 + &firstDiscoveredAt, 713 749 ) 714 750 if err != nil { 715 751 return nil, err ··· 719 755 detail.IP = ip.String 720 756 } 721 757 722 - // NEW: Get aliases if this endpoint has a server_did 723 - if serverDID.Valid && serverDID.String != "" { 724 - aliasQuery := ` 725 - SELECT endpoint, discovered_at 726 - FROM endpoints 727 - WHERE server_did = $1 728 - AND endpoint_type = 'pds' 729 - AND endpoint != $2 730 - ORDER BY discovered_at ASC 731 - ` 758 + if serverDID.Valid { 759 + detail.ServerDID = serverDID.String 760 + } 732 761 733 - rows, err := p.db.QueryContext(ctx, aliasQuery, serverDID.String, endpoint) 734 - if err == nil { 735 - defer rows.Close() 736 - 737 - var aliases []string 738 - var primaryDiscoveredAt time.Time 739 - 740 - for rows.Next() { 741 - var alias string 742 - var discoveredAt time.Time 743 - if err := rows.Scan(&alias, &discoveredAt); err == nil { 744 - aliases = append(aliases, alias) 745 - if primaryDiscoveredAt.IsZero() || discoveredAt.Before(detail.DiscoveredAt) { 746 - primaryDiscoveredAt = discoveredAt 747 - } 748 - } 749 - } 750 - 751 - detail.Aliases = aliases 752 - detail.IsPrimary = detail.DiscoveredAt.Equal(primaryDiscoveredAt) || 753 - detail.DiscoveredAt.Before(primaryDiscoveredAt) 754 - } 762 + // Set aliases and is_primary 763 + detail.Aliases = aliases 764 + if serverDID.Valid && serverDID.String != "" && firstDiscoveredAt.Valid { 765 + // Has server_did - check if this is the first discovered 766 + detail.IsPrimary = detail.DiscoveredAt.Equal(firstDiscoveredAt.Time) || 767 + detail.DiscoveredAt.Before(firstDiscoveredAt.Time) 755 768 } else { 756 - // No server_did means it's a unique server 769 + // No server_did means unique server 757 770 detail.IsPrimary = true 758 771 } 759 772