Repository navigation
Expand file tree
/
Copy pathgdelt-geo.js
More file actions
105 lines (92 loc) · 3.09 KB
/
Copy pathgdelt-geo.js
File metadata and controls
105 lines (92 loc) · 3.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// GDELT Geo API proxy with security hardening
export const config = { runtime: 'edge' };
const ALLOWED_FORMATS = ['geojson', 'json', 'csv'];
const MAX_RECORDS = 500;
const MIN_RECORDS = 1;
const ALLOWED_TIMESPANS = ['1d', '7d', '14d', '30d', '60d', '90d'];
function getCorsOrigin(req) {
const origin = req.headers.get('origin') || '';
// Allow *.worldmonitor.app and localhost
if (
origin.endsWith('.worldmonitor.app') ||
origin === 'https://worldmonitor.app' ||
origin.startsWith('http://localhost:')
) {
return origin;
}
return 'https://worldmonitor.app';
}
function validateMaxRecords(val) {
const num = parseInt(val, 10);
if (isNaN(num)) return 250;
return Math.max(MIN_RECORDS, Math.min(MAX_RECORDS, num));
}
function validateFormat(val) {
return ALLOWED_FORMATS.includes(val) ? val : 'geojson';
}
function validateTimespan(val) {
return ALLOWED_TIMESPANS.includes(val) ? val : '7d';
}
function sanitizeQuery(val) {
if (!val || typeof val !== 'string') return 'protest';
return val.slice(0, 200).replace(/[<>\"']/g, '');
}
export default async function handler(req) {
const corsOrigin = getCorsOrigin(req);
if (req.method !== 'GET' && req.method !== 'OPTIONS') {
return new Response(JSON.stringify({ error: 'Method not allowed' }), {
status: 405,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': corsOrigin,
},
});
}
if (req.method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': corsOrigin,
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
const url = new URL(req.url);
const query = sanitizeQuery(url.searchParams.get('query'));
const format = validateFormat(url.searchParams.get('format') || 'geojson');
const maxrecords = validateMaxRecords(url.searchParams.get('maxrecords') || '250');
const timespan = validateTimespan(url.searchParams.get('timespan') || '7d');
try {
const response = await fetch(
`https://api.gdeltproject.org/api/v2/geo/geo?query=${encodeURIComponent(query)}&format=${format}&maxrecords=${maxrecords}×pan=${timespan}`
);
if (!response.ok) {
return new Response(JSON.stringify({ error: 'Upstream service unavailable' }), {
status: 502,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': corsOrigin,
},
});
}
const data = await response.text();
return new Response(data, {
status: 200,
headers: {
'Content-Type': format === 'csv' ? 'text/csv' : 'application/json',
'Access-Control-Allow-Origin': corsOrigin,
'Cache-Control': 'public, max-age=300',
},
});
} catch (error) {
console.error('[GDELT] Fetch error:', error.message);
return new Response(JSON.stringify({ error: 'Failed to fetch GDELT data' }), {
status: 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': corsOrigin,
},
});
}
}