d317e8c758
WS1: pin all Docker stages to node:24.16.0-alpine; add engines node>=20. WS2: lib/redis.ts gains TTL-backed redisSet, redisDel, setTunnelActive (writes tunnel:active:<sub>=1/0 EX 30, TUNNEL_ACTIVE_TTL override, no-op without REDIS_URL); wired into tunnel active/delete/reassign routes. WS3: sortable columns, CSV export routes (token excluded), and bulk actions (self-account guard) across users/tunnels/audit admin tables.
37 lines
1.2 KiB
TypeScript
37 lines
1.2 KiB
TypeScript
import { type NextRequest } from 'next/server';
|
|
import { requireAdminApi } from '@/lib/auth/admin-guard';
|
|
import { parsePageParam, parsePerPageParam } from '@/lib/admin/validators';
|
|
import { getAuditList } from '@/lib/admin/list';
|
|
import { jsonNoStore } from '@/lib/admin/response';
|
|
|
|
export const runtime = 'nodejs';
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
export async function GET(req: NextRequest) {
|
|
const auth = await requireAdminApi();
|
|
if (!auth.ok) return auth.response;
|
|
|
|
const url = new URL(req.url);
|
|
const page = parsePageParam(url.searchParams.get('page'), 1);
|
|
const perPage = parsePerPageParam(url.searchParams.get('perPage'), 50, 100);
|
|
const action = url.searchParams.get('action') ?? '';
|
|
const targetType = url.searchParams.get('target_type') ?? '';
|
|
const sort = url.searchParams.get('sort');
|
|
const order = url.searchParams.get('order');
|
|
|
|
try {
|
|
const { entries, total } = await getAuditList({
|
|
page,
|
|
perPage,
|
|
action,
|
|
targetType,
|
|
sort,
|
|
order,
|
|
});
|
|
return jsonNoStore({ entries, total, page, perPage });
|
|
} catch (e) {
|
|
console.error('admin audit list failed', e);
|
|
return jsonNoStore({ error: 'internal error' }, { status: 500 });
|
|
}
|
|
}
|