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
1.1 KiB
TypeScript
27 lines
1.1 KiB
TypeScript
import { Module } from '@nestjs/common';
|
|
import { ShareController } from './share.controller';
|
|
import { ShareService } from './share.service';
|
|
import { TokenModule } from '../auth/token.module';
|
|
import { ShareSeoController } from './share-seo.controller';
|
|
import { TransclusionModule } from '../page/transclusion/transclusion.module';
|
|
import { AiModule } from '../../integrations/ai/ai.module';
|
|
import { ShareAliasService } from './share-alias.service';
|
|
import { ShareAliasController } from './share-alias.controller';
|
|
import { ShareAliasRedirectController } from './share-alias-redirect.controller';
|
|
|
|
@Module({
|
|
// AiModule (AiSettingsService) is used by the page-info route to surface
|
|
// whether the anonymous public-share assistant is enabled for the workspace.
|
|
imports: [TokenModule, TransclusionModule, AiModule],
|
|
controllers: [
|
|
ShareController,
|
|
ShareSeoController,
|
|
// Vanity /l/:alias: authenticated management + public 302 resolver.
|
|
ShareAliasController,
|
|
ShareAliasRedirectController,
|
|
],
|
|
providers: [ShareService, ShareAliasService],
|
|
exports: [ShareService, ShareAliasService],
|
|
})
|
|
export class ShareModule {}
|