NeahNew/app/api/news/route.ts
2025-05-03 15:04:27 +02:00

157 lines
4.8 KiB
TypeScript

import { NextResponse } from 'next/server';
import { env } from '@/lib/env';
import { getCachedNewsData, cacheNewsData } from '@/lib/redis';
// Helper function to clean HTML content
function cleanHtmlContent(text: string): string {
if (!text) return '';
return text
.replace(/<[^>]*>/g, '') // Remove HTML tags
.replace(/&nbsp;/g, ' ') // Replace &nbsp; with space
.replace(/\s+/g, ' ') // Replace multiple spaces with single space
.trim();
}
// Helper function to format time
function formatDateTime(dateStr: string): { displayDate: string, timestamp: string } {
try {
const date = new Date(dateStr);
// Format like "17 avr." to match the Duties widget style
const day = date.getDate();
const month = date.toLocaleString('fr-FR', { month: 'short' })
.toLowerCase()
.replace('.', ''); // Remove the dot that comes with French locale
return {
displayDate: `${day} ${month}.`, // Add the dot back for consistent styling
timestamp: date.toLocaleString('fr-FR', {
day: '2-digit',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
}).replace(',', ' à')
};
} catch (error) {
return { displayDate: 'N/A', timestamp: 'N/A' };
}
}
// Helper function to truncate text
function truncateText(text: string, maxLength: number): string {
if (!text) return '';
const cleaned = cleanHtmlContent(text);
if (cleaned.length <= maxLength) return cleaned;
const lastSpace = cleaned.lastIndexOf(' ', maxLength);
const truncated = cleaned.substring(0, lastSpace > 0 ? lastSpace : maxLength).trim();
return truncated.replace(/[.,!?]$/, '') + '...';
}
// Helper function to format category
function formatCategory(category: string): string | null {
if (!category) return null;
// Return null for all categories to remove the labels completely
return null;
}
// Helper function to format source
function formatSource(source: string): string {
if (!source) return '';
const sourceName = source
.replace(/^(https?:\/\/)?(www\.)?/i, '')
.split('.')[0]
.toLowerCase()
.replace(/[^a-z0-9]/g, ' ')
.trim();
return sourceName.charAt(0).toUpperCase() + sourceName.slice(1);
}
interface NewsItem {
id: number;
title: string;
displayDate: string;
timestamp: string;
source: string;
description: string | null;
category: string | null;
url: string;
}
export async function GET(request: Request) {
try {
// Check if we should bypass cache
const url = new URL(request.url);
const forceRefresh = url.searchParams.get('refresh') === 'true';
// Try to get data from cache if not forcing refresh
if (!forceRefresh) {
const cachedNews = await getCachedNewsData();
if (cachedNews) {
console.log('Using cached news data');
return NextResponse.json(cachedNews);
}
}
console.log('Fetching news from FastAPI server...');
const response = await fetch(`${env.NEWS_API_URL}/news?limit=12`, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
// Add timeout to prevent hanging
signal: AbortSignal.timeout(5000)
});
if (!response.ok) {
console.error(`News API error: ${response.status} ${response.statusText}`);
const contentType = response.headers.get('content-type');
if (contentType && !contentType.includes('application/json')) {
console.error('News API returned non-JSON response');
return NextResponse.json(
{ error: 'News API returned invalid response format', status: response.status },
{ status: 502 }
);
}
return NextResponse.json(
{ error: 'Failed to fetch news', status: response.status },
{ status: 502 }
);
}
let articles;
try {
articles = await response.json();
} catch (error) {
console.error('Failed to parse news API response:', error);
return NextResponse.json(
{ error: 'Failed to parse news API response', details: error instanceof Error ? error.message : 'Unknown error' },
{ status: 502 }
);
}
const formattedNews: NewsItem[] = articles.map((article: any) => ({
id: article.id,
title: article.title,
displayDate: formatDateTime(article.date).displayDate,
timestamp: formatDateTime(article.date).timestamp,
source: formatSource(article.source),
description: truncateText(article.description || '', 200),
category: formatCategory(article.category),
url: article.url
}));
// Cache the results
await cacheNewsData(formattedNews);
return NextResponse.json(formattedNews);
} catch (error) {
console.error('News API error:', error);
return NextResponse.json(
{ error: 'Failed to fetch news', details: error instanceof Error ? error.message : 'Unknown error' },
{ status: 500 }
);
}
}