+4
-1
internal/api/handlers.go
+4
-1
internal/api/handlers.go
···
358
358
scanMap["response_time"] = scan.ResponseTime
359
359
}
360
360
361
-
// NEW: Add version if available
362
361
if scan.Version != "" {
363
362
scanMap["version"] = scan.Version
363
+
}
364
+
365
+
if scan.UsedIP != "" {
366
+
scanMap["used_ip"] = scan.UsedIP
364
367
}
365
368
366
369
// Use the top-level UserCount field first
+38
-8
internal/pds/client.go
+38
-8
internal/pds/client.go
···
4
4
"context"
5
5
"encoding/json"
6
6
"fmt"
7
+
"net"
7
8
"net/http"
8
9
"time"
9
10
)
···
112
113
}
113
114
114
115
// CheckHealth performs a basic health check, ensuring the endpoint returns JSON with a "version"
115
-
func (c *Client) CheckHealth(ctx context.Context, endpoint string) (bool, time.Duration, string, error) {
116
+
// Returns: available, responseTime, version, usedIP, error
117
+
func (c *Client) CheckHealth(ctx context.Context, endpoint string) (bool, time.Duration, string, string, error) {
116
118
startTime := time.Now()
117
119
118
120
url := fmt.Sprintf("%s/xrpc/_health", endpoint)
119
121
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
120
122
if err != nil {
121
-
return false, 0, "", err
123
+
return false, 0, "", "", err
124
+
}
125
+
126
+
// Create a custom dialer to track which IP was actually used
127
+
var usedIP string
128
+
transport := &http.Transport{
129
+
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
130
+
conn, err := (&net.Dialer{
131
+
Timeout: 30 * time.Second,
132
+
KeepAlive: 30 * time.Second,
133
+
}).DialContext(ctx, network, addr)
134
+
135
+
if err == nil && conn != nil {
136
+
if remoteAddr := conn.RemoteAddr(); remoteAddr != nil {
137
+
// Extract IP from "ip:port" format
138
+
if tcpAddr, ok := remoteAddr.(*net.TCPAddr); ok {
139
+
usedIP = tcpAddr.IP.String()
140
+
}
141
+
}
142
+
}
143
+
144
+
return conn, err
145
+
},
146
+
}
147
+
148
+
// Create a client with our custom transport
149
+
client := &http.Client{
150
+
Timeout: c.httpClient.Timeout,
151
+
Transport: transport,
122
152
}
123
153
124
-
resp, err := c.httpClient.Do(req)
154
+
resp, err := client.Do(req)
125
155
duration := time.Since(startTime)
126
156
127
157
if err != nil {
128
-
return false, duration, "", err
158
+
return false, duration, "", usedIP, err
129
159
}
130
160
defer resp.Body.Close()
131
161
132
162
if resp.StatusCode != http.StatusOK {
133
-
return false, duration, "", fmt.Errorf("health check returned status %d", resp.StatusCode)
163
+
return false, duration, "", usedIP, fmt.Errorf("health check returned status %d", resp.StatusCode)
134
164
}
135
165
136
166
// Decode the JSON response and check for "version"
···
139
169
}
140
170
141
171
if err := json.NewDecoder(resp.Body).Decode(&healthResponse); err != nil {
142
-
return false, duration, "", fmt.Errorf("failed to decode health JSON: %w", err)
172
+
return false, duration, "", usedIP, fmt.Errorf("failed to decode health JSON: %w", err)
143
173
}
144
174
145
175
if healthResponse.Version == "" {
146
-
return false, duration, "", fmt.Errorf("health JSON response missing 'version' field")
176
+
return false, duration, "", usedIP, fmt.Errorf("health JSON response missing 'version' field")
147
177
}
148
178
149
179
// All checks passed
150
-
return true, duration, healthResponse.Version, nil
180
+
return true, duration, healthResponse.Version, usedIP, nil
151
181
}
+9
-6
internal/pds/scanner.go
+9
-6
internal/pds/scanner.go
···
146
146
go s.updateIPInfoIfNeeded(ctx, ips.IPv6)
147
147
}
148
148
149
-
// STEP 2: Health check (rest remains the same)
150
-
available, responseTime, version, err := s.client.CheckHealth(ctx, ep.Endpoint)
149
+
// STEP 2: Health check (now returns which IP was used)
150
+
available, responseTime, version, usedIP, err := s.client.CheckHealth(ctx, ep.Endpoint)
151
151
if err != nil || !available {
152
152
errMsg := "health check failed"
153
153
if err != nil {
···
157
157
Status: storage.EndpointStatusOffline,
158
158
ResponseTime: responseTime,
159
159
ErrorMessage: errMsg,
160
+
UsedIP: usedIP, // Save even if failed
160
161
})
161
162
return
162
163
}
···
189
190
Description: desc,
190
191
DIDs: dids,
191
192
Version: version,
193
+
UsedIP: usedIP, // NEW: Save which IP was used
192
194
})
193
195
194
196
// Save repos in batches (only tracks changes)
···
245
247
Metadata: make(map[string]interface{}),
246
248
}
247
249
248
-
var userCount int64 // NEW: Declare user count
250
+
var userCount int64
249
251
250
252
// Add PDS-specific metadata
251
253
if result.Status == storage.EndpointStatusOnline {
252
-
userCount = int64(len(result.DIDs)) // NEW: Get user count
253
-
scanData.Metadata["user_count"] = userCount // Keep in JSON for completeness
254
+
userCount = int64(len(result.DIDs))
255
+
scanData.Metadata["user_count"] = userCount
254
256
if result.Description != nil {
255
257
scanData.Metadata["server_info"] = result.Description
256
258
}
···
267
269
Status: result.Status,
268
270
ResponseTime: result.ResponseTime.Seconds() * 1000, // Convert to ms
269
271
UserCount: userCount,
270
-
Version: result.Version, // NEW: Set the version field
272
+
Version: result.Version,
273
+
UsedIP: result.UsedIP, // NEW
271
274
ScanData: scanData,
272
275
ScannedAt: time.Now().UTC(),
273
276
}
+2
-1
internal/pds/types.go
+2
-1
internal/pds/types.go
+15
-8
internal/storage/postgres.go
+15
-8
internal/storage/postgres.go
···
120
120
CREATE INDEX IF NOT EXISTS idx_ip_infos_country_code ON ip_infos(country_code);
121
121
CREATE INDEX IF NOT EXISTS idx_ip_infos_asn ON ip_infos(asn);
122
122
123
-
-- Endpoint scans (renamed from pds_scans)
123
+
-- Endpoint scans
124
124
CREATE TABLE IF NOT EXISTS endpoint_scans (
125
125
id BIGSERIAL PRIMARY KEY,
126
126
endpoint_id BIGINT NOT NULL,
···
128
128
response_time DOUBLE PRECISION,
129
129
user_count BIGINT,
130
130
version TEXT,
131
+
used_ip TEXT,
131
132
scan_data JSONB,
132
133
scanned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
133
134
FOREIGN KEY (endpoint_id) REFERENCES endpoints(id) ON DELETE CASCADE
···
136
137
CREATE INDEX IF NOT EXISTS idx_endpoint_scans_endpoint_status_scanned ON endpoint_scans(endpoint_id, status, scanned_at DESC);
137
138
CREATE INDEX IF NOT EXISTS idx_endpoint_scans_scanned_at ON endpoint_scans(scanned_at);
138
139
CREATE INDEX IF NOT EXISTS idx_endpoint_scans_user_count ON endpoint_scans(user_count DESC NULLS LAST);
140
+
CREATE INDEX IF NOT EXISTS idx_endpoint_scans_used_ip ON endpoint_scans(used_ip);
141
+
139
142
140
143
CREATE TABLE IF NOT EXISTS plc_metrics (
141
144
id BIGSERIAL PRIMARY KEY,
···
490
493
defer tx.Rollback()
491
494
492
495
query := `
493
-
INSERT INTO endpoint_scans (endpoint_id, status, response_time, user_count, version, scan_data, scanned_at)
494
-
VALUES ($1, $2, $3, $4, $5, $6, $7)
496
+
INSERT INTO endpoint_scans (endpoint_id, status, response_time, user_count, version, used_ip, scan_data, scanned_at)
497
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
495
498
`
496
-
_, err = tx.ExecContext(ctx, query, scan.EndpointID, scan.Status, scan.ResponseTime, scan.UserCount, scan.Version, scanDataJSON, scan.ScannedAt)
499
+
_, err = tx.ExecContext(ctx, query, scan.EndpointID, scan.Status, scan.ResponseTime, scan.UserCount, scan.Version, scan.UsedIP, scanDataJSON, scan.ScannedAt)
497
500
if err != nil {
498
501
return err
499
502
}
···
520
523
521
524
func (p *PostgresDB) GetEndpointScans(ctx context.Context, endpointID int64, limit int) ([]*EndpointScan, error) {
522
525
query := `
523
-
SELECT id, endpoint_id, status, response_time, user_count, version, scan_data, scanned_at
526
+
SELECT id, endpoint_id, status, response_time, user_count, version, used_ip, scan_data, scanned_at
524
527
FROM endpoint_scans
525
528
WHERE endpoint_id = $1
526
529
ORDER BY scanned_at DESC
···
538
541
var scan EndpointScan
539
542
var responseTime sql.NullFloat64
540
543
var userCount sql.NullInt64
541
-
var version sql.NullString // NEW
544
+
var version, usedIP sql.NullString
542
545
var scanDataJSON []byte
543
546
544
-
err := rows.Scan(&scan.ID, &scan.EndpointID, &scan.Status, &responseTime, &userCount, &version, &scanDataJSON, &scan.ScannedAt)
547
+
err := rows.Scan(&scan.ID, &scan.EndpointID, &scan.Status, &responseTime, &userCount, &version, &usedIP, &scanDataJSON, &scan.ScannedAt)
545
548
if err != nil {
546
549
return nil, err
547
550
}
···
554
557
scan.UserCount = userCount.Int64
555
558
}
556
559
557
-
if version.Valid { // NEW
560
+
if version.Valid {
558
561
scan.Version = version.String
562
+
}
563
+
564
+
if usedIP.Valid {
565
+
scan.UsedIP = usedIP.String
559
566
}
560
567
561
568
if len(scanDataJSON) > 0 {