v2
This commit is contained in:
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user