Add search to the admin guest book endpoint
At scale (many submissions after a concert), paging through the guest book 20 entries at a time with no way to find a specific person is impractical. Add an optional ?search= query param that filters entries whose name or message contains the term (case-insensitive), with LIKE wildcards escaped so a literal % or _ in a search term can't be misinterpreted as a pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,11 @@ reportsAdminRouter.get('/:eventId/report', async (req: Request, res: Response) =
|
|||||||
* name: pageSize
|
* name: pageSize
|
||||||
* schema:
|
* schema:
|
||||||
* type: integer
|
* type: integer
|
||||||
|
* - in: query
|
||||||
|
* name: search
|
||||||
|
* description: Filters entries whose name or message contains this text (case-insensitive).
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
* responses:
|
* responses:
|
||||||
* 200:
|
* 200:
|
||||||
* description: Success
|
* description: Success
|
||||||
@@ -92,7 +97,8 @@ reportsAdminRouter.get('/:eventId/guestbook', async (req: Request, res: Response
|
|||||||
try {
|
try {
|
||||||
const page = Math.max(1, Number(req.query.page) || 1);
|
const page = Math.max(1, Number(req.query.page) || 1);
|
||||||
const pageSize = Math.min(200, Math.max(1, Number(req.query.pageSize) || 50));
|
const pageSize = Math.min(200, Math.max(1, Number(req.query.pageSize) || 50));
|
||||||
const result = await ReportsAdminService.getGuestBookEntries(Number(req.params.eventId), page, pageSize);
|
const search = typeof req.query.search === 'string' ? req.query.search : undefined;
|
||||||
|
const result = await ReportsAdminService.getGuestBookEntries(Number(req.params.eventId), page, pageSize, search);
|
||||||
res.status(200).send(result);
|
res.status(200).send(result);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
sendServerError(res, e);
|
sendServerError(res, e);
|
||||||
|
|||||||
@@ -166,13 +166,29 @@ export interface GuestBookEntry {
|
|||||||
message: string | null;
|
message: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getGuestBookEntries = async (eventId: number, page: number, pageSize: number): Promise<{entries: GuestBookEntry[]; total: number}> => {
|
// Escapes LIKE wildcards (% and _) so a search term is matched literally,
|
||||||
|
// not interpreted as a pattern - a search for "50%" must not match everything.
|
||||||
|
const escapeLikeTerm = (term: string) => term.replace(/[\\%_]/g, (c) => `\\${c}`);
|
||||||
|
|
||||||
|
export const getGuestBookEntries = async (
|
||||||
|
eventId: number,
|
||||||
|
page: number,
|
||||||
|
pageSize: number,
|
||||||
|
search?: string
|
||||||
|
): Promise<{entries: GuestBookEntry[]; total: number}> => {
|
||||||
let conn = await NachklangFeedbackDB.getConnection();
|
let conn = await NachklangFeedbackDB.getConnection();
|
||||||
try {
|
try {
|
||||||
const totalRows = await conn.query('SELECT COUNT(*) as cnt FROM guest_book_entries WHERE event_id = ?', [eventId]);
|
const trimmedSearch = search?.trim();
|
||||||
|
const whereClause = trimmedSearch
|
||||||
|
? 'WHERE event_id = ? AND (display_name LIKE ? ESCAPE \'\\\\\' OR message LIKE ? ESCAPE \'\\\\\')'
|
||||||
|
: 'WHERE event_id = ?';
|
||||||
|
const likeParam = trimmedSearch ? `%${escapeLikeTerm(trimmedSearch)}%` : undefined;
|
||||||
|
const whereParams = trimmedSearch ? [eventId, likeParam, likeParam] : [eventId];
|
||||||
|
|
||||||
|
const totalRows = await conn.query(`SELECT COUNT(*) as cnt FROM guest_book_entries ${whereClause}`, whereParams);
|
||||||
const rows = await conn.query(
|
const rows = await conn.query(
|
||||||
'SELECT entry_id, submission_id, created_at, display_name, message FROM guest_book_entries WHERE event_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?',
|
`SELECT entry_id, submission_id, created_at, display_name, message FROM guest_book_entries ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
|
||||||
[eventId, pageSize, (page - 1) * pageSize]
|
[...whereParams, pageSize, (page - 1) * pageSize]
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
total: Number(totalRows[0].cnt),
|
total: Number(totalRows[0].cnt),
|
||||||
|
|||||||
Reference in New Issue
Block a user