diff --git a/pages/api/search/advanced.ts b/pages/api/search/advanced.ts new file mode 100644 index 0000000..853e95f --- /dev/null +++ b/pages/api/search/advanced.ts @@ -0,0 +1,94 @@ +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 { + q, + category, + risk_level_min, + risk_level_max, + status = 'verified', + page = '1', + limit = '20' + } = req.query + + const dbPath = path.join(process.cwd(), "database", "antihoax.db") + const db = new sqlite3.Database(dbPath) + + try { + let whereConditions = ["s.status = ?"] + let params: any[] = [status] + + if (q) { + whereConditions.push("(s.domain LIKE ? OR s.title LIKE ? OR s.description LIKE ?)") + params.push(`%${q}%`, `%${q}%`, `%${q}%`) + } + + if (category) { + whereConditions.push("EXISTS (SELECT 1 FROM source_categories sc JOIN categories c ON sc.category_id = c.id WHERE sc.source_id = s.id AND c.name = ?)") + params.push(category) + } + + if (risk_level_min) { + whereConditions.push("s.risk_level >= ?") + params.push(parseInt(risk_level_min as string)) + } + + if (risk_level_max) { + whereConditions.push("s.risk_level <= ?") + params.push(parseInt(risk_level_max as string)) + } + + const offset = (parseInt(page as string) - 1) * parseInt(limit as string) + + const query = ` + SELECT s.*, GROUP_CONCAT(c.name) as categories, + COUNT(*) OVER() as total_count + FROM sources s + LEFT JOIN source_categories sc ON s.id = sc.source_id + LEFT JOIN categories c ON sc.category_id = c.id + WHERE ${whereConditions.join(' AND ')} + GROUP BY s.id + ORDER BY s.risk_level DESC, s.created_at DESC + LIMIT ? OFFSET ? + ` + + params.push(parseInt(limit as string), offset) + + const results = await new Promise((resolve, reject) => { + db.all(query, params, (err, rows) => { + if (err) reject(err) + else resolve(rows) + }) + }) + + const total = results.length > 0 ? results[0].total_count : 0 + const totalPages = Math.ceil(total / parseInt(limit as string)) + + res.json({ + results: results.map(row => ({ + id: row.id, + domain: row.domain, + title: row.title, + risk_level: row.risk_level, + categories: row.categories ? row.categories.split(',') : [], + description: row.description, + created_at: row.created_at + })), + pagination: { + page: parseInt(page as string), + limit: parseInt(limit as string), + total, + totalPages + } + }) + + } catch (error) { + res.status(500).json({ error: "Search failed" }) + } finally { + db.close() + } +} \ No newline at end of file diff --git a/pages/search.tsx b/pages/search.tsx new file mode 100644 index 0000000..57c3d06 --- /dev/null +++ b/pages/search.tsx @@ -0,0 +1,142 @@ +import { useState } from "react" +import type { NextPage } from "next" +import Head from "next/head" +import Link from "next/link" + +const SearchPage: NextPage = () => { + const [query, setQuery] = useState("") + const [category, setCategory] = useState("") + const [riskLevel, setRiskLevel] = useState({ min: '', max: '' }) + const [results, setResults] = useState([]) + const [loading, setLoading] = useState(false) + const [pagination, setPagination] = useState(null) + + const handleSearch = async (page = 1) => { + if (!query.trim()) return + + setLoading(true) + const params = new URLSearchParams({ + q: query, + page: page.toString(), + limit: '10' + }) + + if (category) params.set('category', category) + if (riskLevel.min) params.set('risk_level_min', riskLevel.min) + if (riskLevel.max) params.set('risk_level_max', riskLevel.max) + + try { + const response = await fetch(`/api/search/advanced?${params}`) + const data = await response.json() + setResults(data.results || []) + setPagination(data.pagination) + } catch (error) { + console.error('Search error:', error) + } + setLoading(false) + } + + return ( +
+ + Advanced Search - Infohliadka + + +
+
+ ← Home +
+ +

Advanced Search

+ +
+
+ setQuery(e.target.value)} + style={{ width: '400px', padding: '8px', marginRight: '10px' }} + /> + +
+ +
+ + + setRiskLevel({...riskLevel, min: e.target.value})} + style={{ width: '80px', padding: '5px', marginRight: '5px' }} + min="1" max="5" + /> + setRiskLevel({...riskLevel, max: e.target.value})} + style={{ width: '80px', padding: '5px' }} + min="1" max="5" + /> +
+
+ + {results.length > 0 && ( +
+

Search Results ({pagination?.total || 0})

+ {results.map((result) => ( +
+

{result.domain}

+

Risk Level: {result.risk_level}/5

+

Categories: {result.categories.join(', ')}

+ {result.description &&

{result.description}

} + {new Date(result.created_at).toLocaleDateString()} +
+ ))} + + {pagination && pagination.totalPages > 1 && ( +
+ {Array.from({length: pagination.totalPages}, (_, i) => i + 1).map(page => ( + + ))} +
+ )} +
+ )} +
+
+ ) +} + +export default SearchPage \ No newline at end of file