widget news fetch 6

This commit is contained in:
Alma 2025-04-13 22:59:09 +02:00
parent 61e5ccadd2
commit 726c5d44ae
2 changed files with 134 additions and 71 deletions

View File

@ -6,35 +6,61 @@ function cleanDatabaseHost(host: string | undefined): string {
if (!host) { if (!host) {
throw new Error('Database host is not defined'); throw new Error('Database host is not defined');
} }
return host.replace(/^https?:\/\//, ''); // Remove any protocol and trailing slashes
return host.replace(/^https?:\/\//, '').replace(/\/$/, '');
} }
// Create a new pool using the environment variables // Create connection configuration
const pool = new Pool({ const dbConfig = {
user: process.env.DB_USER, user: process.env.DB_USER,
password: process.env.DB_PASSWORD, password: process.env.DB_PASSWORD,
host: cleanDatabaseHost(process.env.DB_HOST), host: cleanDatabaseHost(process.env.DB_HOST),
database: process.env.DB_NAME, database: process.env.DB_NAME,
port: 5432, // Default PostgreSQL port
ssl: { ssl: {
rejectUnauthorized: false // Required for some cloud databases rejectUnauthorized: false
} }
}); };
// Create a new pool using the configuration
const pool = new Pool(dbConfig);
export async function GET() { export async function GET() {
try { try {
// Log connection attempt // Log connection attempt with sanitized config
console.log('Attempting database connection with config:', { const sanitizedConfig = {
user: process.env.DB_USER, user: dbConfig.user,
host: process.env.DB_HOST, host: dbConfig.host,
database: process.env.DB_NAME, database: dbConfig.database,
hasPassword: !!process.env.DB_PASSWORD port: dbConfig.port,
}); hasPassword: !!dbConfig.password,
ssl: !!dbConfig.ssl
};
console.log('Attempting database connection with config:', sanitizedConfig);
// Connect to the database // First test the connection
const client = await pool.connect(); const client = await pool.connect();
console.log('Successfully connected to database'); console.log('Successfully connected to database');
try { try {
// First check if the news table exists
console.log('Checking if news table exists...');
const tableCheck = await client.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'news'
);
`);
if (!tableCheck.rows[0].exists) {
console.error('News table does not exist');
return NextResponse.json(
{ error: 'News table does not exist in database' },
{ status: 500 }
);
}
// Query the news table for the latest 10 news items // Query the news table for the latest 10 news items
console.log('Executing news query...'); console.log('Executing news query...');
const result = await client.query(` const result = await client.query(`
@ -46,16 +72,19 @@ export async function GET() {
source, source,
description, description,
category, category,
sentiment_score, sentiment_score as "sentimentScore",
sentiment, sentiment,
symbols, symbols,
symbol symbol
FROM news FROM news
ORDER BY date DESC ORDER BY date DESC
LIMIT 10 LIMIT 10;
`); `);
console.log(`Query completed. Found ${result.rows.length} news items.`); console.log(`Query completed. Found ${result.rows.length} news items.`);
if (result.rows.length > 0) {
console.log('Sample first row:', result.rows[0]);
}
// Format the response // Format the response
const news = result.rows.map(row => ({ const news = result.rows.map(row => ({
@ -67,10 +96,10 @@ export async function GET() {
description: row.description, description: row.description,
category: row.category, category: row.category,
sentiment: { sentiment: {
score: row.sentiment_score, score: row.sentimentScore,
label: row.sentiment label: row.sentiment
}, },
symbols: row.symbols, symbols: Array.isArray(row.symbols) ? row.symbols : null,
symbol: row.symbol symbol: row.symbol
})); }));
@ -82,27 +111,34 @@ export async function GET() {
{ status: 500 } { status: 500 }
); );
} finally { } finally {
// Release the client back to the pool
client.release(); client.release();
console.log('Database client released'); console.log('Database client released');
} }
} catch (error: any) { } catch (error: any) {
console.error('Database connection error:', error); console.error('Database connection error:', error);
// Check if error is due to missing configuration // Check if error is due to missing configuration
if (!process.env.DB_USER || !process.env.DB_PASSWORD || !process.env.DB_HOST || !process.env.DB_NAME) { if (!dbConfig.user || !dbConfig.password || !dbConfig.host || !dbConfig.database) {
console.error('Missing database configuration:', { const missingConfig = {
hasUser: !!process.env.DB_USER, user: !dbConfig.user,
hasPassword: !!process.env.DB_PASSWORD, password: !dbConfig.password,
hasHost: !!process.env.DB_HOST, host: !dbConfig.host,
hasDatabase: !!process.env.DB_NAME database: !dbConfig.database
}); };
console.error('Missing database configuration:', missingConfig);
return NextResponse.json( return NextResponse.json(
{ error: 'Database configuration is incomplete' }, { error: 'Database configuration is incomplete' },
{ status: 500 } { status: 500 }
); );
} }
// Return detailed error message
return NextResponse.json( return NextResponse.json(
{ error: 'Failed to connect to database: ' + (error?.message || 'Unknown error') }, {
error: 'Failed to connect to database',
details: error.message,
code: error.code
},
{ status: 500 } { status: 500 }
); );
} }

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useEffect, useState, useCallback } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import { fr } from 'date-fns/locale'; import { fr } from 'date-fns/locale';
@ -24,93 +24,120 @@ interface NewsItem {
symbol: string | null; symbol: string | null;
} }
export function News() { interface DebugState {
console.log('[News] Component mounting...'); newsCount: number;
loading: boolean;
error: string | null;
dbStatus: string;
lastUpdate: string;
lastError: string | null;
}
export function News() {
const [news, setNews] = useState<NewsItem[]>([]); const [news, setNews] = useState<NewsItem[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
const [dbStatus, setDbStatus] = useState<'connecting' | 'connected' | 'error'>('connecting'); const [dbStatus, setDbStatus] = useState<'connecting' | 'connected' | 'error'>('connecting');
const [debugState, setDebugState] = useState<DebugState>({
newsCount: 0,
loading: true,
error: null,
dbStatus: 'connecting',
lastUpdate: new Date().toISOString(),
lastError: null
});
// Debug info display component const updateDebugState = useCallback((updates: Partial<DebugState>) => {
const DebugInfo = () => { setDebugState(prev => {
console.log('[News] Current state:', { const newState = { ...prev, ...updates, lastUpdate: new Date().toISOString() };
newsCount: news.length, console.table(newState);
loading, return newState;
error,
dbStatus,
newsItems: news
}); });
}, []);
return ( const fetchNews = useCallback(async (isRefresh = false) => {
<div className="text-xs text-gray-500 mt-2 p-2 bg-gray-100 rounded"> updateDebugState({ loading: true, dbStatus: 'connecting' });
<p>Status: {dbStatus}</p>
<p>Loading: {loading ? 'true' : 'false'}</p>
<p>Error: {error || 'none'}</p>
<p>News items: {news.length}</p>
<details>
<summary>Debug Details</summary>
<pre className="mt-2 text-[10px] whitespace-pre-wrap">
{JSON.stringify(news, null, 2)}
</pre>
</details>
</div>
);
};
const fetchNews = async (isRefresh = false) => { if (isRefresh) {
console.log('[News] Fetching news, isRefresh:', isRefresh); setRefreshing(true);
updateDebugState({ lastError: null });
}
if (isRefresh) setRefreshing(true);
setLoading(true); setLoading(true);
setDbStatus('connecting'); setDbStatus('connecting');
try { try {
console.log('[News] Making API request to /api/news'); updateDebugState({ dbStatus: 'fetching' });
const response = await fetch('/api/news', { const response = await fetch('/api/news');
method: 'GET',
headers: {
'Accept': 'application/json',
},
});
console.log('[News] API response status:', response.status); updateDebugState({ dbStatus: response.ok ? 'received' : 'error' });
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`);
} }
const data = await response.json(); const data = await response.json();
console.log('[News] API response data:', data);
if (data.error) { if (data.error) {
throw new Error(data.error); throw new Error(data.error);
} }
console.log('[News] Setting news items:', data.news);
setNews(data.news || []); setNews(data.news || []);
setError(null); setError(null);
setDbStatus('connected'); setDbStatus('connected');
updateDebugState({
newsCount: (data.news || []).length,
error: null,
dbStatus: 'connected',
loading: false
});
} catch (err) { } catch (err) {
console.error('[News] Error fetching news:', err);
const errorMessage = err instanceof Error ? err.message : 'Failed to load news'; const errorMessage = err instanceof Error ? err.message : 'Failed to load news';
setError(errorMessage); setError(errorMessage);
setDbStatus('error'); setDbStatus('error');
setNews([]); setNews([]);
updateDebugState({
error: errorMessage,
dbStatus: 'error',
loading: false,
lastError: errorMessage
});
} finally { } finally {
setLoading(false); setLoading(false);
setRefreshing(false); setRefreshing(false);
} }
}; }, [updateDebugState]);
useEffect(() => { useEffect(() => {
console.log('[News] Running useEffect...'); updateDebugState({ dbStatus: 'initializing' });
fetchNews(); fetchNews();
return () => { return () => {
console.log('[News] Component unmounting...'); updateDebugState({ dbStatus: 'unmounting' });
}; };
}, []); }, [fetchNews, updateDebugState]);
const DebugInfo = () => (
<div className="text-xs text-gray-500 mt-2 p-2 bg-gray-100 rounded">
<p>Status: {debugState.dbStatus}</p>
<p>Loading: {debugState.loading ? 'true' : 'false'}</p>
<p>Error: {debugState.error || 'none'}</p>
<p>News items: {debugState.newsCount}</p>
<p>Last update: {new Date(debugState.lastUpdate).toLocaleTimeString()}</p>
{debugState.lastError && (
<p className="text-red-500">Last error: {debugState.lastError}</p>
)}
<details>
<summary>Debug Details</summary>
<pre className="mt-2 text-[10px] whitespace-pre-wrap">
{JSON.stringify(news, null, 2)}
</pre>
</details>
</div>
);
if (loading && !refreshing) { if (loading && !refreshing) {
return ( return (