widget news fetch 6
This commit is contained in:
parent
61e5ccadd2
commit
726c5d44ae
@ -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 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user