observatory
This commit is contained in:
parent
247b327f38
commit
cc3ee4aaa3
15
app/api/news/purge-cache/route.ts
Normal file
15
app/api/news/purge-cache/route.ts
Normal 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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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">
|
||||
|
||||
34
lib/redis.ts
34
lib/redis.ts
@ -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);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user