59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
import { Router, Request, Response } from "express";
|
|
import { pool } from "../db/client";
|
|
|
|
export const newsRouter = Router();
|
|
|
|
// GET /api/news?page=1&limit=10&category=&source=
|
|
newsRouter.get("/", async (req: Request, res: Response) => {
|
|
try {
|
|
const page = Math.max(1, parseInt(String(req.query.page || "1"), 10));
|
|
const limit = Math.min(50, Math.max(1, parseInt(String(req.query.limit || "10"), 10)));
|
|
const offset = (page - 1) * limit;
|
|
const category = req.query.category ? String(req.query.category) : null;
|
|
const source = req.query.source ? String(req.query.source) : null;
|
|
|
|
const conditions: string[] = [];
|
|
const values: unknown[] = [];
|
|
let idx = 1;
|
|
|
|
if (category) { conditions.push(`category = $${idx++}`); values.push(category); }
|
|
if (source) { conditions.push(`source ILIKE $${idx++}`); values.push(`%${source}%`); }
|
|
|
|
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
|
|
const countRes = await pool.query(
|
|
`SELECT COUNT(*) AS total FROM news_articles ${where}`,
|
|
values
|
|
);
|
|
const total = parseInt(countRes.rows[0].total, 10);
|
|
|
|
const rows = await pool.query(
|
|
`SELECT id, title, summary, source, source_url, published_at,
|
|
category, relevance_score, tags, content_hash
|
|
FROM news_articles
|
|
${where}
|
|
ORDER BY published_at DESC NULLS LAST
|
|
LIMIT $${idx} OFFSET $${idx + 1}`,
|
|
[...values, limit, offset]
|
|
);
|
|
|
|
// distinct categories for filter UI
|
|
const catRes = await pool.query(
|
|
"SELECT DISTINCT category FROM news_articles WHERE category IS NOT NULL ORDER BY category"
|
|
) as { rows: { category: string }[] };
|
|
|
|
res.json({
|
|
success: true,
|
|
articles: rows.rows,
|
|
total,
|
|
page,
|
|
limit,
|
|
pages: Math.ceil(total / limit),
|
|
categories: catRes.rows.map((r) => r.category),
|
|
});
|
|
} catch (err) {
|
|
console.error("News route error:", err);
|
|
res.status(500).json({ success: false, error: "Internal server error" });
|
|
}
|
|
});
|