| | import { Query } from "express-serve-static-core"; |
| | import sanitize from "sanitize-html"; |
| | import { z } from "zod"; |
| |
|
| | export function parseSort(sort: Query["sort"]) { |
| | if (!sort) return null; |
| | if (typeof sort === "string") return sort.split(","); |
| | if (Array.isArray(sort)) return sort.splice(3) as string[]; |
| | return null; |
| | } |
| |
|
| | export function sortBy(fields: string[], asc = true) { |
| | return (a: any, b: any) => { |
| | for (const field of fields) { |
| | if (a[field] !== b[field]) { |
| | |
| | if (a[field] == null) return 1; |
| | if (b[field] == null) return -1; |
| |
|
| | const valA = Array.isArray(a[field]) ? a[field].length : a[field]; |
| | const valB = Array.isArray(b[field]) ? b[field].length : b[field]; |
| |
|
| | const result = valA < valB ? -1 : 1; |
| | return asc ? result : -result; |
| | } |
| | } |
| | return 0; |
| | }; |
| | } |
| |
|
| | export function paginate(set: unknown[], page: number, pageSize: number = 20) { |
| | const p = Math.max(1, Math.min(page, Math.ceil(set.length / pageSize))); |
| | return { |
| | page: p, |
| | items: set.slice((p - 1) * pageSize, p * pageSize), |
| | pageSize, |
| | pageCount: Math.ceil(set.length / pageSize), |
| | totalCount: set.length, |
| | nextPage: p * pageSize < set.length ? p + 1 : null, |
| | prevPage: p > 1 ? p - 1 : null, |
| | }; |
| | } |
| |
|
| | export function sanitizeAndTrim( |
| | input?: string | null, |
| | options: sanitize.IOptions = { |
| | allowedTags: [], |
| | allowedAttributes: {}, |
| | } |
| | ) { |
| | return sanitize((input ?? "").trim(), options); |
| | } |
| |
|
| | |
| | export function makeOptionalPropsNullable<Schema extends z.AnyZodObject>( |
| | schema: Schema |
| | ) { |
| | const entries = Object.entries(schema.shape) as [ |
| | keyof Schema["shape"], |
| | z.ZodTypeAny, |
| | ][]; |
| | const newProps = entries.reduce( |
| | (acc, [key, value]) => { |
| | acc[key] = |
| | value instanceof z.ZodOptional ? value.unwrap().nullable() : value; |
| | return acc; |
| | }, |
| | {} as { |
| | [key in keyof Schema["shape"]]: Schema["shape"][key] extends z.ZodOptional< |
| | infer T |
| | > |
| | ? z.ZodNullable<T> |
| | : Schema["shape"][key]; |
| | } |
| | ); |
| | return z.object(newProps); |
| | } |
| |
|
| | export function redactIp(ip: string) { |
| | const ipv6 = ip.includes(":"); |
| | return ipv6 ? "redacted:ipv6" : ip.replace(/\.\d+\.\d+$/, ".xxx.xxx"); |
| | } |
| |
|
| | export function assertNever(x: never): never { |
| | throw new Error(`Called assertNever with argument ${x}.`); |
| | } |
| |
|
| | export function encodeCursor(v: string) { |
| | return Buffer.from(JSON.stringify(v)).toString("base64"); |
| | } |
| |
|
| | export function decodeCursor(cursor?: string) { |
| | if (!cursor) return null; |
| | return JSON.parse(Buffer.from(cursor, "base64").toString("utf-8")); |
| | } |
| |
|