/** * 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 page. The * category service runs this check when a category is created or renamed, and * the migrations that reserve more words rename the categories that had one * with {@link renameReservedCategories}. * * Dump and playlist slugs reserve nothing: they live under their owner's * `dumps/` and `playlists/`, where nothing else is named by a user. A user * can't clash with a category either, since `@` is never in a slug. * * @module */ import type { DatabaseSync } from "node:sqlite"; import { FEED_TABS } from "../../shared/paths.ts"; /** * Lists the top-level path segments a category slug may not take: the feed * tabs (`/hot`), the SPA's own pages (`src/App.tsx`), and `api`, `ws`, * `assets` and `emojibase`, which the server answers before the SPA. Keep it * in step with the route table and the build's top-level folders. */ export const RESERVED_SLUGS: readonly string[] = [ ...FEED_TABS, "search", "login", "register", "notifications", "reset-password", "unsubscribe", "settings", "manage", "community", "authorize", "federation", "api", "ws", "assets", "emojibase", ]; /** Checks whether `slug` is one of the {@link RESERVED_SLUGS}. */ export function isReservedSlug(slug: string): boolean { return RESERVED_SLUGS.includes(slug); } /** * Renames each category whose slug is now reserved, such as `hot`, to the * first of `-2`, `-3`… that is free, and logs it. Migration 0021 * runs it for v4's words, 0022 for `community`, and 0024 for `authorize` and * `federation`. */ export function renameReservedCategories(db: DatabaseSync): void { const rows = db.prepare(`SELECT id, slug FROM categories;`).all() as { id: string; slug: string; }[]; const taken = new Set(rows.map((r) => r.slug)); for (const { id, slug } of rows) { if (!isReservedSlug(slug)) continue; let n = 2; while (taken.has(`${slug}-${n}`) || isReservedSlug(`${slug}-${n}`)) n++; const renamed = `${slug}-${n}`; taken.add(renamed); db.prepare(`UPDATE categories SET slug = ? WHERE id = ?;`).run(renamed, id); console.log( `[migrate] category "${slug}" is now "${renamed}": its slug is reserved`, ); } }