All checks were successful
CI / Lint, typecheck, test (push) Successful in 3m19s
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 4m55s
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m5s
CD / Promote to Int (push) Successful in 10s
CI / Auth e2e pack (push) Successful in 5m20s
CI / Import/export fidelity gate (push) Successful in 45s
New comments table (thread via parent_id to the root, optional document anchor on roots, resolved_at/by; page purge cascades, trash hides) with a CommentsService enforcing the permission model: reading follows page read, writing follows the new pond setting commentPolicy (readers | editors) — 404 hides unreadable pages, 403 marks a failed write policy. Endpoints: threaded list per page with an open/resolved filter (resolved threads arrive collapsed by default), create (root or reply — replies attach to roots only and carry no anchor), edit own, delete own (roots with replies are admin-only, cascade), resolve/unresolve on roots for everyone who may comment. Bodies are Markdown rendered through the shared sanitizing pipeline; smuggled markup arrives as escaped text (fixture test). The UI lands with #92. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
86 lines
2.6 KiB
TypeScript
86 lines
2.6 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
Get,
|
|
HttpCode,
|
|
Param,
|
|
Patch,
|
|
Post,
|
|
Query,
|
|
Req,
|
|
} from '@nestjs/common';
|
|
import {
|
|
commentListQuerySchema,
|
|
createCommentInputSchema,
|
|
updateCommentInputSchema,
|
|
type CommentListQuery,
|
|
type CommentView,
|
|
type CreateCommentInput,
|
|
type PageCommentsView,
|
|
type UpdateCommentInput,
|
|
} from '@dorfteich/shared';
|
|
|
|
import { AuthedRequest } from '../auth/auth.guard';
|
|
import { ZodValidationPipe } from '../common/zod-validation.pipe';
|
|
import { AuthenticatedOnly, RequiresPagePermission } from '../permissions/permission.decorators';
|
|
import { CommentsService } from './comments.service';
|
|
|
|
/**
|
|
* Threaded page comments (issue #91). The page-scoped routes prove page
|
|
* read through the shared guard; the comment-scoped ones resolve their
|
|
* page (and the 404-vs-403 semantics) inside the service.
|
|
*/
|
|
@Controller()
|
|
export class CommentsController {
|
|
constructor(private readonly comments: CommentsService) {}
|
|
|
|
@Get('pages/:pageId/comments')
|
|
@RequiresPagePermission('read', { idParam: 'pageId' })
|
|
async list(
|
|
@Param('pageId') pageId: string,
|
|
@Query(new ZodValidationPipe(commentListQuerySchema)) query: CommentListQuery,
|
|
): Promise<PageCommentsView> {
|
|
return this.comments.list(pageId, query.filter);
|
|
}
|
|
|
|
@Post('pages/:pageId/comments')
|
|
@RequiresPagePermission('read', { idParam: 'pageId' }) // write policy: service
|
|
async create(
|
|
@Param('pageId') pageId: string,
|
|
@Body(new ZodValidationPipe(createCommentInputSchema)) input: CreateCommentInput,
|
|
@Req() request: AuthedRequest,
|
|
): Promise<CommentView> {
|
|
return this.comments.create(request.user!, pageId, input);
|
|
}
|
|
|
|
@Patch('comments/:id')
|
|
@AuthenticatedOnly()
|
|
async update(
|
|
@Param('id') id: string,
|
|
@Body(new ZodValidationPipe(updateCommentInputSchema)) input: UpdateCommentInput,
|
|
@Req() request: AuthedRequest,
|
|
): Promise<CommentView> {
|
|
return this.comments.update(request.user!, id, input.body);
|
|
}
|
|
|
|
@Delete('comments/:id')
|
|
@HttpCode(204)
|
|
@AuthenticatedOnly()
|
|
async remove(@Param('id') id: string, @Req() request: AuthedRequest): Promise<void> {
|
|
await this.comments.delete(request.user!, id);
|
|
}
|
|
|
|
@Post('comments/:id/resolve')
|
|
@AuthenticatedOnly()
|
|
async resolve(@Param('id') id: string, @Req() request: AuthedRequest): Promise<CommentView> {
|
|
return this.comments.setResolved(request.user!, id, true);
|
|
}
|
|
|
|
@Delete('comments/:id/resolve')
|
|
@AuthenticatedOnly()
|
|
async unresolve(@Param('id') id: string, @Req() request: AuthedRequest): Promise<CommentView> {
|
|
return this.comments.setResolved(request.user!, id, false);
|
|
}
|
|
}
|