Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 6m25s
CI / Build container images (pull_request) Successful in 2m58s
CI / Auth e2e pack (pull_request) Successful in 8m35s
CI / Import/export fidelity gate (pull_request) Successful in 1m7s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CI / Lint, typecheck, test (push) Has been cancelled
CD / Build and push images (push) Has been cancelled
Instrument every full-content read channel for pages with classification = vs_nfd (ADR 0023, variant A): SPA state fetch and read rendering, public JSON content, no-JS shell, expanded embeds, public API GET (incl. the MCP read_page path and write echoes), attachment download under the #212 effective classification, all export shapes (markdown, pond ZIP, account data export, queued docx/odt/pdf at enqueue), and collab-token issuance as the api-side proxy for the WS join. Events land in the new read_events table (no FKs — evidence survives page purges and hard user deletions) with actor, session key (session:/token:/job:/anon), page, pond, channel and the classification at read time. Recording failures are NOT swallowed: a failed write aborts the read (hard failure, the deliberate contrast to AuditService — decision recorded in ADR 0023 and security.md §Logging, together with the recorded residuals: content fragments and feeds). One e2e test per channel proves both the event and its absence for unclassified pages, plus the hard-failure semantics. Refs #222. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AUtYMxwTCMHG9mVHnwbFg8
53 lines
2.1 KiB
TypeScript
53 lines
2.1 KiB
TypeScript
import { Controller, Get, Param, Req } from '@nestjs/common';
|
|
import type { TaskOverviewPage } from '@dorfteich/shared';
|
|
|
|
import { AuthedRequest } from '../auth/auth.guard';
|
|
import { TasksService } from '../pages/tasks.service';
|
|
import { AuthenticatedOnly } from '../permissions/permission.decorators';
|
|
import { readActorOf } from '../read-trail/read-actor';
|
|
import { PublicPageContent, PublicService } from './public.service';
|
|
|
|
/**
|
|
* Authenticated read-rendering (issue #135). Returns a page's rendered read
|
|
* HTML — plugin fallbacks, expanded page embeds, resolved media — for a
|
|
* signed-in viewer with read access. The transclusion node view fetches this to
|
|
* show an embedded page's content inline in the authenticated read view, which
|
|
* must also work for pages that are not public and so are out of reach of the
|
|
* `/public` endpoints. NOT `@Public`: the auth guard requires a session and the
|
|
* service enforces read permission (a non-readable page 404s, no leak).
|
|
*/
|
|
@Controller('read')
|
|
export class ReadContentController {
|
|
constructor(
|
|
private readonly publicPages: PublicService,
|
|
private readonly tasks: TasksService,
|
|
) {}
|
|
|
|
// The task collection (issue #154) — registered before the generic
|
|
// two-segment route so `tasks` is not read as a page slug.
|
|
@Get(':pondSlug/:pageSlug/tasks')
|
|
@AuthenticatedOnly()
|
|
async tasksOf(
|
|
@Param('pondSlug') pondSlug: string,
|
|
@Param('pageSlug') pageSlug: string,
|
|
@Req() request: AuthedRequest,
|
|
): Promise<TaskOverviewPage[]> {
|
|
return this.tasks.collect(request.user ?? null, pondSlug, pageSlug);
|
|
}
|
|
|
|
// Session required (explicit access rule, issue #52); per-page read
|
|
// permission is enforced in the service (resolve → canAccessPage → 404).
|
|
@Get(':pondSlug/:pageSlug')
|
|
@AuthenticatedOnly()
|
|
async content(
|
|
@Param('pondSlug') pondSlug: string,
|
|
@Param('pageSlug') pageSlug: string,
|
|
@Req() request: AuthedRequest,
|
|
): Promise<PublicPageContent> {
|
|
return this.publicPages.content(request.user ?? null, pondSlug, pageSlug, {
|
|
actor: readActorOf(request),
|
|
channel: 'page_view',
|
|
});
|
|
}
|
|
}
|