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 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
if (!forceRefresh) {
const cachedNews = await getCachedNewsData();
if (!bypassCache) {
const cachedNews = await getCachedNewsData(limit);
if (cachedNews) {
console.log('Using cached news data');
console.log(`Using cached news data (${cachedNews.length} articles)`);
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 limit = url.searchParams.get('limit') || '100';
const apiUrl = `${env.NEWS_API_URL}/news?limit=${limit}`;
console.log(`Full API URL: ${apiUrl}`);
const response = await fetch(`${env.NEWS_API_URL}/news?limit=${limit}`, {
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
'Accept': 'application/json',
@ -127,6 +135,7 @@ export async function GET(request: Request) {
let articles;
try {
articles = await response.json();
console.log(`News API returned ${articles.length} articles with limit=${limit}`);
} catch (error) {
console.error('Failed to parse news API response:', error);
return NextResponse.json(
@ -146,8 +155,10 @@ export async function GET(request: Request) {
url: article.url
}));
console.log(`Formatted and returning ${formattedNews.length} news articles`);
// Cache the results
await cacheNewsData(formattedNews);
await cacheNewsData(formattedNews, limit);
return NextResponse.json(formattedNews);
} catch (error) {

View File

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

View File

@ -5,6 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { RefreshCw, Globe } from "lucide-react";
import { Button } from "@/components/ui/button";
import { ObservatoryMap } from "./observatory-map";
import { toast } from "@/components/ui/use-toast";
// News item interface matching the API response
interface NewsItem {
@ -31,16 +32,31 @@ export function ObservatoryView() {
}, []);
// Fetch news data
const fetchNews = async () => {
const fetchNews = async (forceRefresh = false) => {
setLoading(true);
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) {
throw new Error('Failed to fetch news');
}
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);
setError(null);
} catch (err) {
@ -112,7 +128,37 @@ export function ObservatoryView() {
if (!selectedCountry) return 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
@ -129,7 +175,7 @@ export function ObservatoryView() {
return (
<div className="w-full h-[calc(100vh-2rem)] flex flex-col items-center justify-center">
<p className="text-red-500 mb-4">{error}</p>
<Button onClick={fetchNews}>Retry</Button>
<Button onClick={() => fetchNews(true)}>Retry</Button>
</div>
);
}
@ -152,6 +198,26 @@ export function ObservatoryView() {
({filteredNews.length} articles)
</span>
</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 className="overflow-y-auto flex-grow">

View File

@ -130,7 +130,7 @@ export const KEYS = {
`email:content:${userId}:${accountId}:${emailId}`,
// New widget cache keys
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}`,
MESSAGES: (userId: string) => `widget:messages:${userId}`
};
@ -567,14 +567,15 @@ export async function invalidateCalendarCache(
* Cache news data (global, not user-specific)
*/
export async function cacheNewsData(
data: any
data: any,
limit = '100'
): Promise<void> {
const redis = getRedisClient();
const key = KEYS.NEWS();
const key = KEYS.NEWS(limit);
try {
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) {
console.error('Error caching news data:', error);
}
@ -583,9 +584,9 @@ export async function cacheNewsData(
/**
* Get cached news data
*/
export async function getCachedNewsData(): Promise<any | null> {
export async function getCachedNewsData(limit = '100'): Promise<any | null> {
const redis = getRedisClient();
const key = KEYS.NEWS();
const key = KEYS.NEWS(limit);
try {
const cachedData = await redis.get(key);
@ -593,7 +594,9 @@ export async function getCachedNewsData(): Promise<any | 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) {
console.error('Error getting cached news data:', error);
return null;
@ -603,13 +606,24 @@ export async function getCachedNewsData(): Promise<any | null> {
/**
* Invalidate news cache
*/
export async function invalidateNewsCache(): Promise<void> {
export async function invalidateNewsCache(limit?: string): Promise<void> {
const redis = getRedisClient();
const key = KEYS.NEWS();
try {
if (limit) {
// Invalidate specific limit cache
const key = KEYS.NEWS(limit);
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) {
console.error('Error invalidating news cache:', error);
}