This commit is contained in:
jpmvaz
2026-09-13 20:20:06 +01:00
commit 6994063c71
93 changed files with 7613 additions and 0 deletions
@@ -0,0 +1,65 @@
export const dynamic = 'force-dynamic';
import { NextResponse } from 'next/server';
import prisma from '@/lib/db';
import { invalidateSettingsCache } from '@/lib/settings';
export async function GET() {
try {
let settings = await prisma.settings.findUnique({
where: { id: 'main' },
});
if (!settings) {
settings = await prisma.settings.create({
data: {
id: 'main',
apiKey: process.env.API_FOOTBALL_API_KEY || null,
},
});
}
return NextResponse.json(settings);
} catch (error) {
console.error('Failed to get settings:', error);
return NextResponse.json(
{ error: 'Failed to get settings' },
{ status: 500 }
);
}
}
export async function PUT(request: Request) {
try {
const data = await request.json();
// Update league settings based on selection
let leagueId = data.leagueId;
let season = data.season;
if (data.selectedLeague === 'euro_2026') {
leagueId = 4; // EURO Championship
season = 2024;
} else if (data.selectedLeague === 'primeira_liga') {
leagueId = 94;
season = 2024;
}
const settings = await prisma.settings.upsert({
where: { id: 'main' },
update: { ...data, leagueId, season },
create: { id: 'main', ...data, leagueId, season },
});
// Bust the settings cache so API routes pick up the new values immediately
invalidateSettingsCache();
return NextResponse.json(settings);
} catch (error) {
console.error('Failed to update settings:', error);
return NextResponse.json(
{ error: 'Failed to update settings' },
{ status: 500 }
);
}
}
+69
View File
@@ -0,0 +1,69 @@
export const dynamic = 'force-dynamic';
import { NextResponse } from 'next/server';
import prisma from '@/lib/db';
export async function GET() {
try {
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
const startOfDay = new Date();
startOfDay.setHours(0, 0, 0, 0);
// Run all queries in parallel instead of sequentially
const [
totalRequests,
requestsByEndpoint,
recentRequests,
avgDuration,
successfulRequests,
requestsToday,
] = await Promise.all([
prisma.apiRequestLog.count(),
prisma.apiRequestLog.groupBy({
by: ['endpoint'],
_count: { id: true },
}),
prisma.apiRequestLog.findMany({
where: { createdAt: { gte: twentyFourHoursAgo } },
orderBy: { createdAt: 'asc' },
select: { createdAt: true }, // only fetch what we need
}),
prisma.apiRequestLog.aggregate({ _avg: { duration: true } }),
prisma.apiRequestLog.count({ where: { status: { gte: 200, lt: 300 } } }),
prisma.apiRequestLog.count({ where: { createdAt: { gte: startOfDay } } }),
]);
// Group by hour
const hourlyStats: Record<string, number> = {};
for (const req of recentRequests) {
const hour = new Date(req.createdAt).toISOString().slice(0, 13) + ':00';
hourlyStats[hour] = (hourlyStats[hour] ?? 0) + 1;
}
const chartData = Object.entries(hourlyStats).map(([time, count]) => ({
time,
count,
}));
const successRate =
totalRequests > 0 ? (successfulRequests / totalRequests) * 100 : 100;
return NextResponse.json({
totalRequests,
requestsToday,
requestsByEndpoint: requestsByEndpoint.map((r) => ({
endpoint: r.endpoint,
count: r._count.id,
})),
chartData,
avgResponseTime: Math.round(avgDuration._avg.duration ?? 0),
successRate: Math.round(successRate * 100) / 100,
});
} catch (error) {
console.error('Failed to get stats:', error);
return NextResponse.json(
{ error: 'Failed to get stats' },
{ status: 500 }
);
}
}
+76
View File
@@ -0,0 +1,76 @@
export const dynamic = 'force-dynamic';
import { NextResponse } from 'next/server';
import { getSettings } from '@/lib/settings';
import { logApiRequest } from '@/lib/api-logger';
// Revalidation times per fixture type
const REVALIDATE: Record<string, number> = {
live: 30, // live scores — refresh every 30s
last: 300, // recent results — refresh every 5 min
next: 300, // upcoming — refresh every 5 min
};
export async function GET(request: Request) {
const startTime = Date.now();
const { searchParams } = new URL(request.url);
const type = searchParams.get('type') || 'live';
try {
const settings = await getSettings();
const apiKey = settings.apiKey || process.env.API_FOOTBALL_API_KEY;
if (!apiKey) {
return NextResponse.json(
{ error: 'API key not configured' },
{ status: 500 }
);
}
const params = new URLSearchParams({
league: settings.leagueId.toString(),
season: settings.season.toString(),
});
if (type === 'live') {
params.set('live', 'all');
} else if (type === 'last') {
params.set('last', '10');
} else if (type === 'next') {
params.set('next', '10');
}
const revalidate = REVALIDATE[type] ?? 60;
const response = await fetch(
`https://v3.football.api-sports.io/fixtures?${params.toString()}`,
{
headers: {
'x-rapidapi-key': apiKey,
'x-rapidapi-host': 'v3.football.api-sports.io',
},
next: { revalidate },
}
);
const data = await response.json();
const duration = Date.now() - startTime;
// Fire-and-forget log (non-blocking)
logApiRequest(`/fixtures?type=${type}`, response.status, duration);
return NextResponse.json(data, {
headers: {
'Cache-Control': `public, s-maxage=${revalidate}, stale-while-revalidate=${revalidate * 2}`,
},
});
} catch (error) {
const duration = Date.now() - startTime;
logApiRequest(`/fixtures?type=${type}`, 500, duration);
console.error('Fixtures API error:', error);
return NextResponse.json(
{ error: 'Failed to fetch fixtures' },
{ status: 500 }
);
}
}
+30
View File
@@ -0,0 +1,30 @@
export const dynamic = 'force-dynamic';
import { NextResponse } from 'next/server';
import { getSettings } from '@/lib/settings';
export async function GET() {
try {
const settings = await getSettings();
// Return only public settings (no API key)
return NextResponse.json({
siteName: settings.siteName,
selectedLeague: settings.selectedLeague,
primaryColor: settings.primaryColor,
secondaryColor: settings.secondaryColor,
showLiveMatches: settings.showLiveMatches,
showStandings: settings.showStandings,
showTopScorers: settings.showTopScorers,
showRecentResults: settings.showRecentResults,
showUpcoming: settings.showUpcoming,
refreshInterval: settings.refreshInterval,
});
} catch (error) {
console.error('Settings API error:', error);
return NextResponse.json(
{ error: 'Failed to fetch settings' },
{ status: 500 }
);
}
}
+53
View File
@@ -0,0 +1,53 @@
export const dynamic = 'force-dynamic';
import { NextResponse } from 'next/server';
import { getSettings } from '@/lib/settings';
import { logApiRequest } from '@/lib/api-logger';
const REVALIDATE = 300; // 5 minutes
export async function GET() {
const startTime = Date.now();
try {
const settings = await getSettings();
const apiKey = settings.apiKey || process.env.API_FOOTBALL_API_KEY;
if (!apiKey) {
return NextResponse.json(
{ error: 'API key not configured' },
{ status: 500 }
);
}
const response = await fetch(
`https://v3.football.api-sports.io/standings?league=${settings.leagueId}&season=${settings.season}`,
{
headers: {
'x-rapidapi-key': apiKey,
'x-rapidapi-host': 'v3.football.api-sports.io',
},
next: { revalidate: REVALIDATE },
}
);
const data = await response.json();
const duration = Date.now() - startTime;
logApiRequest('/standings', response.status, duration);
return NextResponse.json(data, {
headers: {
'Cache-Control': `public, s-maxage=${REVALIDATE}, stale-while-revalidate=${REVALIDATE * 2}`,
},
});
} catch (error) {
const duration = Date.now() - startTime;
logApiRequest('/standings', 500, duration);
console.error('Standings API error:', error);
return NextResponse.json(
{ error: 'Failed to fetch standings' },
{ status: 500 }
);
}
}
+53
View File
@@ -0,0 +1,53 @@
export const dynamic = 'force-dynamic';
import { NextResponse } from 'next/server';
import { getSettings } from '@/lib/settings';
import { logApiRequest } from '@/lib/api-logger';
const REVALIDATE = 300; // 5 minutes
export async function GET() {
const startTime = Date.now();
try {
const settings = await getSettings();
const apiKey = settings.apiKey || process.env.API_FOOTBALL_API_KEY;
if (!apiKey) {
return NextResponse.json(
{ error: 'API key not configured' },
{ status: 500 }
);
}
const response = await fetch(
`https://v3.football.api-sports.io/players/topscorers?league=${settings.leagueId}&season=${settings.season}`,
{
headers: {
'x-rapidapi-key': apiKey,
'x-rapidapi-host': 'v3.football.api-sports.io',
},
next: { revalidate: REVALIDATE },
}
);
const data = await response.json();
const duration = Date.now() - startTime;
logApiRequest('/topscorers', response.status, duration);
return NextResponse.json(data, {
headers: {
'Cache-Control': `public, s-maxage=${REVALIDATE}, stale-while-revalidate=${REVALIDATE * 2}`,
},
});
} catch (error) {
const duration = Date.now() - startTime;
logApiRequest('/topscorers', 500, duration);
console.error('Top Scorers API error:', error);
return NextResponse.json(
{ error: 'Failed to fetch top scorers' },
{ status: 500 }
);
}
}