/** * 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` and `assets`, which the server answers * before the SPA, and `manage`, which no route uses any more. Keep it in step * with the route table. */ export const RESERVED_SLUGS: readonly string[] = [ "~", "dumps", "users", "playlists", "search", "login", "register", "notifications", "reset-password", "manage", "api", "assets", ]; /** * 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); } /** * Checks whether `slug` is well-formed and not reserved. The category service * runs the two checks apart, to give each failure its own message. */ export function isSlugAvailable(slug: string): boolean { return isValidSlugFormat(slug) && !isReservedSlug(slug); }