observatory

This commit is contained in:
alma 2025-05-04 17:14:11 +02:00
parent 247b327f38
commit cc3ee4aaa3
5 changed files with 134 additions and 29 deletions

View File

@ -0,0 +1,15 @@
import { NextResponse } from 'next/server';
import { invalidateNewsCache } from '@/lib/redis';
export async function POST() {
try {
await invalidateNewsCache();
return NextResponse.json({ success: true });
} catch (error) {
console.error('Failed to invalidate news cache:', error);
return NextResponse.json(
{ error: 'Failed to invalidate cache', details: error instanceof Error ? error.message : 'Unknown error' },
{ status: 500 }
);
}
}

View File

@ -85,21 +85,29 @@ export async function GET(request: Request) {
const url = new URL(request.url); const url = new URL(request.url);
const forceRefresh = url.searchParams.get('refresh') === 'true'; const forceRefresh = url.searchParams.get('refresh') === 'true';
// Get limit from query params or default to 100
const limit = url.searchParams.get('limit') || '100';
// Also bypass cache if a non-default limit is explicitly requested
const bypassCache = forceRefresh || (url.searchParams.has('limit') && limit !== '100');
console.log(`News API request: limit=${limit}, forceRefresh=${forceRefresh}, bypassCache=${bypassCache}`);
// Try to get data from cache if not forcing refresh // Try to get data from cache if not forcing refresh
if (!forceRefresh) { if (!bypassCache) {
const cachedNews = await getCachedNewsData(); const cachedNews = await getCachedNewsData(limit);
if (cachedNews) { if (cachedNews) {
console.log('Using cached news data'); console.log(`Using cached news data (${cachedNews.length} articles)`);
return NextResponse.json(cachedNews); return NextResponse.json(cachedNews);
} }
} }
console.log('Fetching news from FastAPI server...'); console.log(`Fetching news from FastAPI server with limit=${limit}...`);
// Get limit from query params or default to 100 const apiUrl = `${env.NEWS_API_URL}/news?limit=${limit}`;
const limit = url.searchParams.get('limit') || '100'; console.log(`Full API URL: ${apiUrl}`);
const response = await fetch(`${env.NEWS_API_URL}/news?limit=${limit}`, { const response = await fetch(apiUrl, {
method: 'GET', method: 'GET',
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
@ -127,6 +135,7 @@ export async function GET(request: Request) {
let articles; let articles;
try { try {
articles = await response.json(); articles = await response.json();
console.log(`News API returned ${articles.length} articles with limit=${limit}`);
} catch (error) { } catch (error) {
console.error('Failed to parse news API response:', error); console.error('Failed to parse news API response:', error);
return NextResponse.json( return NextResponse.json(
@ -146,8 +155,10 @@ export async function GET(request: Request) {
url: article.url url: article.url
})); }));
console.log(`Formatted and returning ${formattedNews.length} news articles`);
// Cache the results // Cache the results
await cacheNewsData(formattedNews); await cacheNewsData(formattedNews, limit);
return NextResponse.json(formattedNews); return NextResponse.json(formattedNews);
} catch (error) { } catch (error) {

View File

@ -29,7 +29,7 @@ export function News() {
if (!isRefresh) setLoading(true); if (!isRefresh) setLoading(true);
try { try {
const response = await fetch(isRefresh ? '/api/news?refresh=true' : '/api/news'); const response = await fetch(isRefresh ? '/api/news?refresh=true&limit=100' : '/api/news?limit=100');
if (!response.ok) { if (!response.ok) {
throw new Error('Failed to fetch news'); throw new Error('Failed to fetch news');
} }
@ -37,11 +37,7 @@ export function News() {
const data = await response.json(); const data = await response.json();
// Debug log the date values // Debug log the date values
console.log('News data dates:', data.map((item: NewsItem) => ({ console.log(`News component received ${data.length} articles`);
id: item.id,
displayDate: item.displayDate,
timestamp: item.timestamp
})));
setNews(data); setNews(data);
setError(null); setError(null);
@ -84,6 +80,9 @@ export function News() {
<CardTitle className="text-lg font-semibold text-gray-800 flex items-center gap-2"> <CardTitle className="text-lg font-semibold text-gray-800 flex items-center gap-2">
<Telescope className="h-5 w-5 text-gray-600" /> <Telescope className="h-5 w-5 text-gray-600" />
Nouvelles Nouvelles
<span className="text-sm font-normal ml-2 text-gray-500">
({news.length})
</span>
</CardTitle> </CardTitle>
<Button <Button
variant="ghost" variant="ghost"

View File

@ -5,6 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { RefreshCw, Globe } from "lucide-react"; import { RefreshCw, Globe } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ObservatoryMap } from "./observatory-map"; import { ObservatoryMap } from "./observatory-map";
import { toast } from "@/components/ui/use-toast";
// News item interface matching the API response // News item interface matching the API response
interface NewsItem { interface NewsItem {
@ -31,16 +32,31 @@ export function ObservatoryView() {
}, []); }, []);
// Fetch news data // Fetch news data
const fetchNews = async () => { const fetchNews = async (forceRefresh = false) => {
setLoading(true); setLoading(true);
try { try {
const response = await fetch('/api/news?limit=100'); console.log('Requesting news with limit=100...');
const url = forceRefresh
? '/api/news?limit=100&refresh=true'
: '/api/news?limit=100';
console.log(`Fetching from: ${url}`);
const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
throw new Error('Failed to fetch news'); throw new Error('Failed to fetch news');
} }
const data = await response.json(); const data = await response.json();
console.log(`Observatory received ${data.length} news articles`);
// Log first 5 articles for debugging
if (data.length > 0) {
console.log('First 5 articles:', data.slice(0, 5));
} else {
console.log('No articles received from API');
}
setNews(data); setNews(data);
setError(null); setError(null);
} catch (err) { } catch (err) {
@ -112,7 +128,37 @@ export function ObservatoryView() {
if (!selectedCountry) return news; if (!selectedCountry) return news;
const countriesMap = extractCountries(news); const countriesMap = extractCountries(news);
return countriesMap[selectedCountry] || []; const filtered = countriesMap[selectedCountry] || [];
console.log(`Filtered news for ${selectedCountry}: ${filtered.length} of ${news.length} total articles`);
return filtered;
};
// Invalidate cache
const invalidateCache = async () => {
try {
const response = await fetch('/api/news/purge-cache', {
method: 'POST'
});
if (!response.ok) {
throw new Error('Failed to invalidate cache');
}
toast({
title: "Cache purged",
description: "News cache has been invalidated. Fetching fresh data...",
});
// Fetch fresh data
fetchNews(true);
} catch (err) {
console.error('Error invalidating cache:', err);
toast({
title: "Error",
description: "Failed to invalidate cache",
variant: "destructive"
});
}
}; };
// Loading state // Loading state
@ -129,7 +175,7 @@ export function ObservatoryView() {
return ( return (
<div className="w-full h-[calc(100vh-2rem)] flex flex-col items-center justify-center"> <div className="w-full h-[calc(100vh-2rem)] flex flex-col items-center justify-center">
<p className="text-red-500 mb-4">{error}</p> <p className="text-red-500 mb-4">{error}</p>
<Button onClick={fetchNews}>Retry</Button> <Button onClick={() => fetchNews(true)}>Retry</Button>
</div> </div>
); );
} }
@ -152,6 +198,26 @@ export function ObservatoryView() {
({filteredNews.length} articles) ({filteredNews.length} articles)
</span> </span>
</h2> </h2>
<div className="flex gap-1">
<Button
variant="ghost"
size="sm"
onClick={() => invalidateCache()}
className="h-8 px-2 text-xs"
aria-label="Purge cache"
>
Purge Cache
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => fetchNews(true)}
className="h-8 w-8 p-0"
aria-label="Refresh news"
>
<RefreshCw className="h-4 w-4" />
</Button>
</div>
</div> </div>
</div> </div>
<div className="overflow-y-auto flex-grow"> <div className="overflow-y-auto flex-grow">

View File

@ -130,7 +130,7 @@ export const KEYS = {
`email:content:${userId}:${accountId}:${emailId}`, `email:content:${userId}:${accountId}:${emailId}`,
// New widget cache keys // New widget cache keys
CALENDAR: (userId: string) => `widget:calendar:${userId}`, CALENDAR: (userId: string) => `widget:calendar:${userId}`,
NEWS: () => `widget:news`, // Global news cache, not user-specific NEWS: (limit = '100') => `widget:news:${limit}`, // Include limit in cache key
TASKS: (userId: string) => `widget:tasks:${userId}`, TASKS: (userId: string) => `widget:tasks:${userId}`,
MESSAGES: (userId: string) => `widget:messages:${userId}` MESSAGES: (userId: string) => `widget:messages:${userId}`
}; };
@ -567,14 +567,15 @@ export async function invalidateCalendarCache(
* Cache news data (global, not user-specific) * Cache news data (global, not user-specific)
*/ */
export async function cacheNewsData( export async function cacheNewsData(
data: any data: any,
limit = '100'
): Promise<void> { ): Promise<void> {
const redis = getRedisClient(); const redis = getRedisClient();
const key = KEYS.NEWS(); const key = KEYS.NEWS(limit);
try { try {
await redis.set(key, JSON.stringify(data), 'EX', TTL.NEWS); await redis.set(key, JSON.stringify(data), 'EX', TTL.NEWS);
console.log('News data cached successfully'); console.log(`News data cached successfully (${data.length} articles, limit=${limit})`);
} catch (error) { } catch (error) {
console.error('Error caching news data:', error); console.error('Error caching news data:', error);
} }
@ -583,9 +584,9 @@ export async function cacheNewsData(
/** /**
* Get cached news data * Get cached news data
*/ */
export async function getCachedNewsData(): Promise<any | null> { export async function getCachedNewsData(limit = '100'): Promise<any | null> {
const redis = getRedisClient(); const redis = getRedisClient();
const key = KEYS.NEWS(); const key = KEYS.NEWS(limit);
try { try {
const cachedData = await redis.get(key); const cachedData = await redis.get(key);
@ -593,7 +594,9 @@ export async function getCachedNewsData(): Promise<any | null> {
return null; return null;
} }
return JSON.parse(cachedData); const parsedData = JSON.parse(cachedData);
console.log(`Retrieved ${parsedData.length} articles from cache with limit=${limit}`);
return parsedData;
} catch (error) { } catch (error) {
console.error('Error getting cached news data:', error); console.error('Error getting cached news data:', error);
return null; return null;
@ -603,13 +606,24 @@ export async function getCachedNewsData(): Promise<any | null> {
/** /**
* Invalidate news cache * Invalidate news cache
*/ */
export async function invalidateNewsCache(): Promise<void> { export async function invalidateNewsCache(limit?: string): Promise<void> {
const redis = getRedisClient(); const redis = getRedisClient();
const key = KEYS.NEWS();
try { try {
if (limit) {
// Invalidate specific limit cache
const key = KEYS.NEWS(limit);
await redis.del(key); await redis.del(key);
console.log('News cache invalidated'); console.log(`News cache invalidated for limit=${limit}`);
} else {
// Try to invalidate for some common limits
const limits = ['5', '50', '100', '200'];
for (const lim of limits) {
const key = KEYS.NEWS(lim);
await redis.del(key);
}
console.log('All news caches invalidated');
}
} catch (error) { } catch (error) {
console.error('Error invalidating news cache:', error); console.error('Error invalidating news cache:', error);
} }