Add a retargetable, human-readable vanity link namespace /l/<alias> that sits alongside the untouched /share/... routes. - New share_aliases table (workspace-scoped, UNIQUE(workspace_id, alias), page_id nullable ON DELETE SET NULL so the address outlives its target). - ShareAliasRepo + ShareAliasService (create / no-op / 409 reassign guard / availability / request-time readable-target resolution through the single existing share boundary). - Public ShareAliasRedirectController (GET /l/:alias) issues a 302 (never 301, the target is mutable) to the canonical /share/:key/p/:slug page; unknown / dangling / no-longer-readable aliases serve the SPA index with no leak. 'l/:alias' excluded from the global /api prefix. - Authenticated ShareAliasController (set/remove/availability/for-page). - Shared ASCII-only normalize/validate util (server + client copies). - Client: Custom address block in the share modal (live normalize + debounced availability + copy + reassign confirmation dialog). - Unit tests: util, repo SQL-shape, service semantics, migration/entity sanity (server jest) + client alias util (vitest). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
27 lines
781 B
TypeScript
27 lines
781 B
TypeScript
/**
|
|
* Client copy of the vanity share-alias helpers. Kept in sync with the server
|
|
* (`apps/server/src/core/share/share-alias.util.ts`) so live input feedback
|
|
* matches what the server will store/accept. ASCII-only, lowercase, hyphen
|
|
* separated, length 2..60.
|
|
*/
|
|
|
|
// Normalize a user-provided vanity alias into canonical ASCII storage form.
|
|
export function normalizeShareAlias(raw: string): string {
|
|
return (raw ?? "")
|
|
.trim()
|
|
.toLowerCase()
|
|
.replace(/[\s_]+/g, "-")
|
|
.replace(/-{2,}/g, "-")
|
|
.replace(/^-+|-+$/g, "");
|
|
}
|
|
|
|
const ALIAS_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
export function isValidShareAlias(alias: string): boolean {
|
|
return (
|
|
typeof alias === "string" &&
|
|
alias.length >= 2 &&
|
|
alias.length <= 60 &&
|
|
ALIAS_RE.test(alias)
|
|
);
|
|
}
|