diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx new file mode 100644 index 0000000..2ea1de2 --- /dev/null +++ b/pages/admin/index.tsx @@ -0,0 +1,118 @@ +import { useState, useEffect } from 'react' +import type { NextPage } from 'next' +import Head from 'next/head' + +interface DashboardStats { + total_sources: number + pending_sources: number + pending_reports: number + high_risk_sources: number + sources_added_week: number + reports_today: number +} + +const AdminDashboard: NextPage = () => { + const [stats, setStats] = useState(null) + const [loading, setLoading] = useState(true) + + useEffect(() => { + fetchStats() + }, []) + + const fetchStats = async () => { + try { + const response = await fetch('/api/admin/dashboard') + if (response.ok) { + const data = await response.json() + setStats(data) + } + } catch (error) { + console.error('Failed to fetch stats:', error) + } finally { + setLoading(false) + } + } + + if (loading) { + return ( +
+ + Admin Panel - Infohliadka + +
Loading...
+
+ ) + } + + return ( +
+ + Admin Panel - Infohliadka + + +
+

Admin Dashboard

+ + {stats && ( +
+
+

Celkové zdroje

+
{stats.total_sources}
+
+ +
+

Čakajúce schválenie

+
{stats.pending_sources}
+
+ +
+

Vysoké riziko

+
{stats.high_risk_sources}
+
+ +
+

Nové hlásenia

+
{stats.pending_reports}
+
+ +
+

Pridané tento týždeň

+
{stats.sources_added_week}
+
+ +
+

Hlásenia dnes

+
{stats.reports_today}
+
+
+ )} + +
+

Rýchle akcie

+
+ + Správa zdrojov + + + Hlásenia + +
+
+
+
+ ) +} + +export default AdminDashboard \ No newline at end of file diff --git a/pages/admin/sources.tsx b/pages/admin/sources.tsx new file mode 100644 index 0000000..71f115f --- /dev/null +++ b/pages/admin/sources.tsx @@ -0,0 +1,176 @@ +import { useState, useEffect } from 'react' +import type { NextPage } from 'next' +import Head from 'next/head' + +interface Source { + id: number + url: string + domain: string + title?: string + type: string + status: string + risk_level: number + created_at: string +} + +const SourcesManagement: NextPage = () => { + const [sources, setSources] = useState([]) + const [loading, setLoading] = useState(true) + const [filter, setFilter] = useState('pending') + + useEffect(() => { + fetchSources() + }, [filter]) + + const fetchSources = async () => { + try { + const response = await fetch(`/api/admin/sources?status=${filter}`) + if (response.ok) { + const data = await response.json() + setSources(data) + } + } catch (error) { + console.error('Failed to fetch sources:', error) + } finally { + setLoading(false) + } + } + + const updateSource = async (id: number, status: string, riskLevel: number) => { + try { + const response = await fetch(`/api/admin/sources/${id}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + status, + risk_level: riskLevel, + }), + }) + + if (response.ok) { + fetchSources() // Refresh the list + } + } catch (error) { + console.error('Failed to update source:', error) + } + } + + const getRiskColor = (level: number) => { + if (level >= 4) return '#ef4444' + if (level >= 3) return '#f59e0b' + return '#6b7280' + } + + return ( +
+ + Správa zdrojov - Infohliadka + + +
+

Správa zdrojov

+ +
+ + +
+ + {loading ? ( +
Loading...
+ ) : ( +
+ + + + + + + + + + + + + {sources.map((source) => ( + + + + + + + + + ))} + +
DoménaTypRizikoStatusDátumAkcie
+ + {source.domain} + + {source.type} + + {source.risk_level} + + {source.status} + {new Date(source.created_at).toLocaleDateString('sk-SK')} + + {source.status === 'pending' && ( +
+ + +
+ )} +
+
+ )} + +
+ + ← Späť na dashboard + +
+
+
+ ) +} + +export default SourcesManagement \ No newline at end of file diff --git a/pages/api/admin/dashboard.ts b/pages/api/admin/dashboard.ts new file mode 100644 index 0000000..c5ecf5d --- /dev/null +++ b/pages/api/admin/dashboard.ts @@ -0,0 +1,72 @@ +import type { NextApiRequest, NextApiResponse } from 'next' +import sqlite3 from 'sqlite3' +import path from 'path' + +interface DashboardStats { + total_sources: number + pending_sources: number + pending_reports: number + high_risk_sources: number + sources_added_week: number + reports_today: number +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }) + } + + const dbPath = path.join(process.cwd(), 'database', 'antihoax.db') + const db = new sqlite3.Database(dbPath) + + try { + const stats = await new Promise((resolve, reject) => { + const queries = [ + "SELECT COUNT(*) as total_sources FROM sources WHERE status = 'verified'", + "SELECT COUNT(*) as pending_sources FROM sources WHERE status = 'pending'", + "SELECT COUNT(*) as pending_reports FROM reports WHERE status = 'pending'", + "SELECT COUNT(*) as high_risk_sources FROM sources WHERE status = 'verified' AND risk_level >= 4", + "SELECT COUNT(*) as sources_added_week FROM sources WHERE created_at > datetime('now', '-7 days')", + "SELECT COUNT(*) as reports_today FROM reports WHERE created_at > datetime('now', '-1 day')" + ] + + const results: any = {} + let completed = 0 + + queries.forEach((query, index) => { + db.get(query, (err, row: any) => { + if (err) { + reject(err) + return + } + + const key = Object.keys(row)[0] + results[key] = row[key] + completed++ + + if (completed === queries.length) { + resolve({ + total_sources: results.total_sources || 0, + pending_sources: results.pending_sources || 0, + pending_reports: results.pending_reports || 0, + high_risk_sources: results.high_risk_sources || 0, + sources_added_week: results.sources_added_week || 0, + reports_today: results.reports_today || 0 + }) + } + }) + }) + }) + + return res.status(200).json(stats) + + } catch (error) { + console.error('Database error:', error) + return res.status(500).json({ error: 'Internal server error' }) + } finally { + db.close() + } +} \ No newline at end of file diff --git a/pages/api/admin/sources/[id].ts b/pages/api/admin/sources/[id].ts new file mode 100644 index 0000000..a32c70e --- /dev/null +++ b/pages/api/admin/sources/[id].ts @@ -0,0 +1,49 @@ +import type { NextApiRequest, NextApiResponse } from 'next' +import sqlite3 from 'sqlite3' +import path from 'path' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'PATCH') { + return res.status(405).json({ error: 'Method not allowed' }) + } + + const { id } = req.query + const { status, risk_level, rejection_reason } = req.body + + if (!id || !status) { + return res.status(400).json({ error: 'ID and status are required' }) + } + + const dbPath = path.join(process.cwd(), 'database', 'antihoax.db') + const db = new sqlite3.Database(dbPath) + + try { + await new Promise((resolve, reject) => { + const query = ` + UPDATE sources + SET status = ?, risk_level = ?, rejection_reason = ?, updated_at = CURRENT_TIMESTAMP + WHERE id = ? + ` + + db.run( + query, + [status, risk_level || 0, rejection_reason || null, id], + function(err) { + if (err) reject(err) + else resolve() + } + ) + }) + + return res.status(200).json({ success: true }) + + } catch (error) { + console.error('Database error:', error) + return res.status(500).json({ error: 'Internal server error' }) + } finally { + db.close() + } +} \ No newline at end of file diff --git a/pages/api/admin/sources/index.ts b/pages/api/admin/sources/index.ts new file mode 100644 index 0000000..644f9e4 --- /dev/null +++ b/pages/api/admin/sources/index.ts @@ -0,0 +1,43 @@ +import type { NextApiRequest, NextApiResponse } from 'next' +import sqlite3 from 'sqlite3' +import path from 'path' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }) + } + + const { status = 'pending', page = '1', limit = '20' } = req.query + + const dbPath = path.join(process.cwd(), 'database', 'antihoax.db') + const db = new sqlite3.Database(dbPath) + + try { + const sources = await new Promise((resolve, reject) => { + const offset = (parseInt(page as string) - 1) * parseInt(limit as string) + + db.all( + `SELECT * FROM sources + WHERE status = ? + ORDER BY created_at DESC + LIMIT ? OFFSET ?`, + [status, parseInt(limit as string), offset], + (err, rows) => { + if (err) reject(err) + else resolve(rows) + } + ) + }) + + return res.status(200).json(sources) + + } catch (error) { + console.error('Database error:', error) + return res.status(500).json({ error: 'Internal server error' }) + } finally { + db.close() + } +} \ No newline at end of file