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.
46 lines
1.2 KiB
TypeScript
46 lines
1.2 KiB
TypeScript
/**
|
|
* Tiny helpers to parse + whitelist server-side sort parameters for the admin
|
|
* list/export endpoints. Anything not on the per-endpoint allow-list falls
|
|
* back to that endpoint's default column, so untrusted `sort`/`order` query
|
|
* params can never reach PostgREST verbatim.
|
|
*/
|
|
|
|
export type SortOrder = 'asc' | 'desc';
|
|
|
|
export function parseOrder(
|
|
v: string | null | undefined,
|
|
fallback: SortOrder = 'desc',
|
|
): SortOrder {
|
|
return v === 'asc' || v === 'desc' ? v : fallback;
|
|
}
|
|
|
|
export function parseSort<T extends string>(
|
|
v: string | null | undefined,
|
|
allowed: readonly T[],
|
|
fallback: T,
|
|
): T {
|
|
return v && (allowed as readonly string[]).includes(v) ? (v as T) : fallback;
|
|
}
|
|
|
|
export const USER_SORTS = [
|
|
'email',
|
|
'created_at',
|
|
'last_sign_in_at',
|
|
'role',
|
|
] as const;
|
|
export type UserSort = (typeof USER_SORTS)[number];
|
|
|
|
export const TUNNEL_SORTS = [
|
|
'subdomain',
|
|
'bytes_used',
|
|
'quota_bytes',
|
|
'is_active',
|
|
'created_at',
|
|
'last_seen_at',
|
|
'usage_pct',
|
|
] as const;
|
|
export type TunnelSort = (typeof TUNNEL_SORTS)[number];
|
|
|
|
export const AUDIT_SORTS = ['created_at', 'action', 'actor_email'] as const;
|
|
export type AuditSort = (typeof AUDIT_SORTS)[number];
|