Validation: the dump validators refuse the nulls their shared types don't allow (a null title or note on creation, a null URL on edit); a playlist title of spaces is refused and titles are stored trimmed; usernames and emails are checked by the same USERNAME_RE and EMAIL_RE in shared/validation.ts on sign-up, on PATCH /api/users/me (which checked other limits) and in the sign-up form. Mentions are read by shared/mention.ts's mentionedUsernames, which skips code and URLs as Markdown shows them: https://mastodon.social/@bob or a `@bob` in code notified bob; the editor's suggestions derive their trigger from the same pattern instead of a copy. Following an unknown user is a 404 rather than a foreign-key 500, and following a private playlist a 404 rather than a 403 that confirmed it exists. `ws` and `emojibase` are reserved category slugs, since the server answers them before the app. GET /api/users/me no longer turns every error into a 500. Dead code goes: adminOnlyMiddleware, verifyEmailTransport, listUsers, deleteUser with disconnectUser and the force_logout message only it sent, PATCH /api/notifications/:id/read (with markOneRead), isSlugAvailable, the unreachable 500 in fetchComment, and the `if (!ctx.state.user)` checks behind authMiddleware. 214 backend and 627 frontend tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
48 lines
1.3 KiB
TypeScript
48 lines
1.3 KiB
TypeScript
/**
|
|
* Checks which slugs a category may take. A category's feed lives at the URL
|
|
* root (`/music`), so its slug must not shadow another top-level route. The
|
|
* category service runs these checks when a category is created or renamed.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
/**
|
|
* The top-level path segments a category slug may not take. They are the SPA's
|
|
* own routes (`src/App.tsx`); `api`, `ws`, `assets` and `emojibase`, which the
|
|
* server answers before the SPA; and `manage`, a former route whose old links
|
|
* may still be about. Keep it in step with the route table and the build's
|
|
* top-level folders.
|
|
*/
|
|
export const RESERVED_SLUGS: readonly string[] = [
|
|
"~",
|
|
"dumps",
|
|
"users",
|
|
"playlists",
|
|
"search",
|
|
"login",
|
|
"register",
|
|
"notifications",
|
|
"reset-password",
|
|
"manage",
|
|
"api",
|
|
"ws",
|
|
"assets",
|
|
"emojibase",
|
|
];
|
|
|
|
/**
|
|
* Matches a well-formed category slug, made of lowercase letters and digits
|
|
* joined by single hyphens.
|
|
*/
|
|
export const CATEGORY_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
|
|
/** Checks whether `slug` matches {@link CATEGORY_SLUG_RE}. */
|
|
export function isValidSlugFormat(slug: string): boolean {
|
|
return CATEGORY_SLUG_RE.test(slug);
|
|
}
|
|
|
|
/** Checks whether `slug` is one of the {@link RESERVED_SLUGS}. */
|
|
export function isReservedSlug(slug: string): boolean {
|
|
return RESERVED_SLUGS.includes(slug);
|
|
}
|