Files
gerbeur/shared/validation.ts
khannurien c3594e4c27 v4: what the server and the client both state is declared once — several rules were written out in two or more files, some already drifting apart. A link's site name (its host without www.) was computed in five places, now shared/url.ts's siteHost; dumpFileVersion, which the frontend and the server had to keep equal by hand, is one function in shared/dump.ts; the Markdown flattening of the chat's reply previews and the digest's quotes is shared/markdown.ts's markdownToPlainText, so the digest also keeps an image's alt text; an attachment's URL and how to find one in a text are shared/attachment.ts, which the editor, Markdown and attachment-service read, its id pattern the new UUID_RE that the file and thumbnail routes check ids with. On the server, the Bandcamp, YouTube playlist and link preview caches use one TtlCache (api/lib/ttl-cache.ts) instead of three copies of the same expiry and eviction; the link previews' theme-color and the emails' theme tokens read colours with one parser (api/lib/css-color.ts), and a transparent theme-color now gives no tint; the WebSocket route sends through ws-service's send, the welcome included, so it is type-checked; fetch.ts strips an IPv6 literal's brackets with public-address's bareHostname; and a category's slug is checked by the same validateSlug as a dump's, with its own length. The migrations keep their frozen copies, as they document. 272 backend tests and 677 frontend tests pass, and deno check, tsc, eslint and doc:lint are clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:16:52 +00:00

68 lines
2.1 KiB
TypeScript

/**
* Makes the forms check the same limits on what users submit as the server
* enforces, by holding them in one place.
*
* @module
*/
/**
* Read by the validators and services, which refuse what breaks a limit, and
* by the forms, which say so before sending. Lengths are JavaScript string
* lengths, so an emoji may count for two. The category limits are checked on
* the server only.
*/
export const VALIDATION = {
USERNAME_MIN: 1,
USERNAME_MAX: 32,
PASSWORD_MIN: 8,
PASSWORD_MAX: 128,
DUMP_TITLE_MAX: 200,
/** Applies to a dump's note, which the API calls its comment. */
DUMP_COMMENT_MAX: 5000,
PLAYLIST_TITLE_MAX: 100,
PLAYLIST_DESCRIPTION_MAX: 2000,
COMMENT_BODY_MAX: 5000,
USER_DESCRIPTION_MAX: 2000,
CATEGORY_NAME_MAX: 50,
CATEGORY_SLUG_MAX: 50,
CHAT_MESSAGE_MAX: 2000,
} as const;
/**
* Matches a valid username, made of letters, digits and underscores within the
* `VALIDATION` limits. Sign-up checks it on both sides. It uses the same `\w`
* that a mention's pattern reads a name with.
*/
export const USERNAME_RE: RegExp = new RegExp(
`^[a-zA-Z0-9_]{${VALIDATION.USERNAME_MIN},${VALIDATION.USERNAME_MAX}}$`,
);
/**
* How often a dump's or a playlist's slug may change, counted from the aliases
* the changes left: 3 times a day. Moderators are exempt. slug-service refuses
* a change past it with a 429, and the edit forms say what the limit is.
*/
export const RENAME_LIMITS = {
SLUG: { count: 3, windowMs: 24 * 3_600_000 },
} as const;
/**
* Matches an id the server makes with `crypto.randomUUID()`. A route checks
* one before it names a file on disk, and shared/attachment.ts finds the
* attachments a text links by it.
*/
export const UUID_RE: RegExp =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
/**
* Matches an email address well enough to catch a typo; only the reset email
* arriving proves it.
*/
export const EMAIL_RE: RegExp = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
/**
* The largest file a file dump may hold, in bytes. dump-service refuses a
* bigger one, and the create form before uploading it.
*/
export const DUMP_MAX_FILE_SIZE_BYTES: number = 50 * 1024 * 1024;