feat: add admin dashboard and core API routes with Docker configuration

This commit is contained in:
2026-10-29 19:46:37 +01:00
parent 2fdabe1dfd
commit 4f4856d9dc
89 changed files with 1984 additions and 1034 deletions

View File

@@ -1,3 +1,5 @@
import { isIP } from 'node:net';
export async function getGeoInfo(ip: string, headers: Headers) {
// 1. Try platform-specific headers first (Vercel, Cloudflare, etc.)
const headerCountry =
@@ -6,25 +8,33 @@ export async function getGeoInfo(ip: string, headers: Headers) {
headers.get('x-country-code');
if (headerCountry && headerCountry !== 'XX') {
// If we have a country code, we might still want to get the name
// For now, let's keep it simple or use a lookup map
return {
country: headerCountry,
city: headers.get('x-vercel-ip-city') || 'Unknown'
};
}
const cleanIp = ip.trim();
// 2. Handle localhost
if (ip === '127.0.0.1' || ip === '::1' || ip.includes('127.0.0.1')) {
if (cleanIp === '127.0.0.1' || cleanIp === '::1' || cleanIp.includes('127.0.0.1')) {
return { country: 'Local / Dev', city: 'Localhost' };
}
// Validate IPv4/IPv6 syntax to prevent SSRF / path traversal via spoofed X-Forwarded-For
if (isIP(cleanIp) === 0) {
return { country: 'Unknown', city: 'Unknown' };
}
// 3. Fallback to external API
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 2000); // 2s timeout
const response = await fetch(`http://ip-api.com/json/${ip}`, { signal: controller.signal });
const response = await fetch(`http://ip-api.com/json/${encodeURIComponent(cleanIp)}`, {
signal: controller.signal,
headers: { 'Accept-Encoding': 'gzip, deflate' },
});
clearTimeout(timeoutId);
if (response.ok) {