80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
import { type NextRequest, NextResponse } from 'next/server';
|
|
|
|
import { isAuthorized, unauthorized } from './auth';
|
|
import { getServerLocaleMap } from './localeServer';
|
|
import { matchKeys } from './localization';
|
|
|
|
type RouteContext = { params: Promise<Record<string, string>> };
|
|
type Handler = (req: NextRequest, ctx: RouteContext) => Promise<NextResponse>;
|
|
|
|
/**
|
|
* Wraps a route handler with auth checking and unified error handling.
|
|
*
|
|
* @example
|
|
* export const GET = withAuth(async (req) => {
|
|
* const rows = await pool.query('...');
|
|
* return NextResponse.json(rows);
|
|
* });
|
|
*/
|
|
export function withAuth(handler: Handler): Handler {
|
|
return async (req, ctx) => {
|
|
if (!isAuthorized(req)) return unauthorized();
|
|
try {
|
|
return await handler(req, ctx);
|
|
} catch (err) {
|
|
console.error(err);
|
|
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Builds a SQL WHERE clause fragment for item whitelist/blacklist filtering.
|
|
*
|
|
* - `isWhitelist=true`: `item_key ~* $N OR item_key = ANY($N+1)` (regex), `item_key = ANY($N)` (exact)
|
|
* - `isWhitelist=false`: `item_key !~* $N AND item_key != ALL($N+1)` (regex), `item_key != ALL($N)` (exact)
|
|
*
|
|
* Mutates `values` and `param.current` to track parameter bindings.
|
|
*/
|
|
export function buildItemFilter(
|
|
items: string[],
|
|
useRegex: boolean,
|
|
isWhitelist: boolean,
|
|
values: unknown[],
|
|
param: { current: number },
|
|
): string | null {
|
|
if (items.length === 0) return null;
|
|
|
|
if (useRegex) {
|
|
const localeMap = getServerLocaleMap();
|
|
const localeKeys = [...new Set(items.flatMap((p) => matchKeys(p, localeMap)))];
|
|
const sqlPattern = items.map((p) => `(${p})`).join('|');
|
|
|
|
if (isWhitelist) {
|
|
const orConds: string[] = [`item_key ~* $${param.current++}`];
|
|
values.push(sqlPattern);
|
|
if (localeKeys.length > 0) {
|
|
orConds.push(`item_key = ANY($${param.current++})`);
|
|
values.push(localeKeys);
|
|
}
|
|
return `(${orConds.join(' OR ')})`;
|
|
} else {
|
|
const andConds: string[] = [`item_key !~* $${param.current++}`];
|
|
values.push(sqlPattern);
|
|
if (localeKeys.length > 0) {
|
|
andConds.push(`item_key != ALL($${param.current++})`);
|
|
values.push(localeKeys);
|
|
}
|
|
return `(${andConds.join(' AND ')})`;
|
|
}
|
|
}
|
|
|
|
if (isWhitelist) {
|
|
values.push(items);
|
|
return `item_key = ANY($${param.current++})`;
|
|
} else {
|
|
values.push(items);
|
|
return `item_key != ALL($${param.current++})`;
|
|
}
|
|
}
|