add filtering and sorting features; enhance events and articles pages with URL state persistence
This commit is contained in:
+54
-5
@@ -224,7 +224,7 @@ async function adminRoutes(fastify) {
|
||||
return rows.map(r => r.source);
|
||||
});
|
||||
|
||||
// events
|
||||
// events — supports keyword / date range / min-article-count / sort
|
||||
fastify.get('/admin/api/events', async (request, reply) => {
|
||||
if (!checkAuth(request, reply)) return;
|
||||
|
||||
@@ -232,17 +232,66 @@ async function adminRoutes(fastify) {
|
||||
const limit = Math.min(parseInt(q.limit, 10) || 50, 200);
|
||||
const offset = parseInt(q.offset, 10) || 0;
|
||||
|
||||
const total = db.prepare(`SELECT COUNT(*) as n FROM events`).get().n;
|
||||
const where = [];
|
||||
const whereParams = [];
|
||||
|
||||
if (q.keyword) {
|
||||
where.push('e.title LIKE ?');
|
||||
whereParams.push(`%${q.keyword}%`);
|
||||
}
|
||||
if (q.from) {
|
||||
where.push('e.created_at >= ?');
|
||||
whereParams.push(q.from);
|
||||
}
|
||||
if (q.to) {
|
||||
where.push('e.created_at <= ?');
|
||||
whereParams.push(q.to);
|
||||
}
|
||||
|
||||
// having filters operate on the aggregate count
|
||||
const having = [];
|
||||
const havingParams = [];
|
||||
if (q.min_articles) {
|
||||
const n = parseInt(q.min_articles, 10);
|
||||
if (!isNaN(n)) { having.push('article_count >= ?'); havingParams.push(n); }
|
||||
}
|
||||
|
||||
// whitelist sort columns + direction so user input cant break the query
|
||||
const sortMap = {
|
||||
created_desc: 'e.created_at DESC',
|
||||
created_asc: 'e.created_at ASC',
|
||||
articles_desc: 'article_count DESC',
|
||||
articles_asc: 'article_count ASC',
|
||||
};
|
||||
const orderBy = sortMap[q.sort] || sortMap.created_desc;
|
||||
|
||||
const whereClause = where.length ? `WHERE ${where.join(' AND ')}` : '';
|
||||
const havingClause = having.length ? `HAVING ${having.join(' AND ')}` : '';
|
||||
|
||||
// total count has to respect the HAVING clause too, so wrap the grouped query
|
||||
const totalRow = db.prepare(`
|
||||
SELECT COUNT(*) as n FROM (
|
||||
SELECT e.id, COUNT(a.id) as article_count
|
||||
FROM events e
|
||||
LEFT JOIN articles a ON a.event_id = e.id
|
||||
${whereClause}
|
||||
GROUP BY e.id
|
||||
${havingClause}
|
||||
)
|
||||
`).get(...whereParams, ...havingParams);
|
||||
|
||||
const rows = db.prepare(`
|
||||
SELECT e.id, e.title, e.created_at, COUNT(a.id) as article_count
|
||||
FROM events e
|
||||
LEFT JOIN articles a ON a.event_id = e.id
|
||||
${whereClause}
|
||||
GROUP BY e.id
|
||||
ORDER BY e.created_at DESC
|
||||
${havingClause}
|
||||
ORDER BY ${orderBy}
|
||||
LIMIT ? OFFSET ?
|
||||
`).all(limit, offset);
|
||||
`).all(...whereParams, ...havingParams, limit, offset);
|
||||
|
||||
return { total, rows };
|
||||
return { total: totalRow.n, rows };
|
||||
});
|
||||
|
||||
fastify.delete('/admin/api/events/:id', async (request, reply) => {
|
||||
|
||||
Reference in New Issue
Block a user