feat: enhance comments (#1980)

* feat: non-inline comments support

* enhance comments

* fix types
This commit is contained in:
Philip Okugbe
2026-03-02 01:42:25 +00:00
committed by GitHub
parent d5e4b8bb59
commit 4f3577f009
12 changed files with 310 additions and 184 deletions
@@ -31,6 +31,7 @@ import {
AUDIT_SERVICE,
IAuditService,
} from '../../integrations/audit/audit.service';
import { WsService } from '../../ws/ws.service';
@UseGuards(JwtAuthGuard)
@Controller('comments')
@@ -41,6 +42,7 @@ export class CommentController {
private readonly pageRepo: PageRepo,
private readonly spaceAbility: SpaceAbilityFactory,
private readonly pageAccessService: PageAccessService,
private readonly wsService: WsService,
@Inject(AUDIT_SERVICE) private readonly auditService: IAuditService,
) {}
@@ -119,7 +121,10 @@ export class CommentController {
@HttpCode(HttpStatus.OK)
@Post('update')
async update(@Body() dto: UpdateCommentDto, @AuthUser() user: User) {
const comment = await this.commentRepo.findById(dto.commentId);
const comment = await this.commentRepo.findById(dto.commentId, {
includeCreator: true,
includeResolvedBy: true,
});
if (!comment) {
throw new NotFoundException('Comment not found');
}
@@ -170,6 +175,12 @@ export class CommentController {
await this.commentRepo.deleteComment(comment.id);
}
this.wsService.emitCommentEvent(comment.spaceId, comment.pageId, {
operation: 'commentDeleted',
pageId: comment.pageId,
commentId: comment.id,
});
this.auditService.log({
event: AuditEvent.COMMENT_DELETED,
resourceType: AuditResource.COMMENT,
@@ -17,6 +17,7 @@ import { CursorPaginationResult } from '@docmost/db/pagination/cursor-pagination
import { QueueJob, QueueName } from '../../integrations/queue/constants';
import { extractUserMentionIdsFromJson } from '../../common/helpers/prosemirror/utils';
import { ICommentNotificationJob } from '../../integrations/queue/constants/queue.interface';
import { WsService } from '../../ws/ws.service';
@Injectable()
export class CommentService {
@@ -25,6 +26,7 @@ export class CommentService {
constructor(
private commentRepo: CommentRepo,
private pageRepo: PageRepo,
private wsService: WsService,
@InjectQueue(QueueName.GENERAL_QUEUE)
private generalQueue: Queue,
@InjectQueue(QueueName.NOTIFICATION_QUEUE)
@@ -63,7 +65,7 @@ export class CommentService {
}
}
const comment = await this.commentRepo.insertComment({
const inserted = await this.commentRepo.insertComment({
pageId: page.id,
content: commentContent,
selection: createCommentDto?.selection?.substring(0, 250) ?? null,
@@ -74,6 +76,11 @@ export class CommentService {
spaceId: page.spaceId,
});
const comment = await this.commentRepo.findById(inserted.id, {
includeCreator: true,
includeResolvedBy: true,
});
this.generalQueue
.add(QueueJob.ADD_PAGE_WATCHERS, {
userIds: [userId],
@@ -99,6 +106,12 @@ export class CommentService {
createCommentDto.parentCommentId,
);
this.wsService.emitCommentEvent(page.spaceId, page.id, {
operation: 'commentCreated',
pageId: page.id,
comment,
});
return comment;
}
@@ -154,6 +167,12 @@ export class CommentService {
comment.editedAt = editedAt;
comment.updatedAt = editedAt;
this.wsService.emitCommentEvent(comment.spaceId, comment.pageId, {
operation: 'commentUpdated',
pageId: comment.pageId,
comment,
});
return comment;
}
+28 -3
View File
@@ -45,7 +45,7 @@ export class WsService {
return;
}
await this.broadcastToAuthorizedUsers(client, room, pageId, data);
await this.broadcastToAuthorizedUsers(room, client.data.userId, pageId, data);
}
async invalidateSpaceRestrictionCache(spaceId: string): Promise<void> {
@@ -54,6 +54,29 @@ export class WsService {
);
}
async emitCommentEvent(
spaceId: string,
pageId: string,
data: any,
): Promise<void> {
const room = getSpaceRoomName(spaceId);
const hasRestrictions = await this.spaceHasRestrictions(spaceId);
if (!hasRestrictions) {
this.server.to(room).emit('message', data);
return;
}
const isRestricted =
await this.pagePermissionRepo.hasRestrictedAncestor(pageId);
if (!isRestricted) {
this.server.to(room).emit('message', data);
return;
}
await this.broadcastToAuthorizedUsers(room, null, pageId, data);
}
async emitToUsers(userIds: string[], data: any): Promise<void> {
if (userIds.length === 0) return;
const rooms = userIds.map((id) => getUserRoomName(id));
@@ -82,14 +105,16 @@ export class WsService {
}
private async broadcastToAuthorizedUsers(
sender: Socket,
room: string,
excludeUserId: string | null,
pageId: string,
data: any,
): Promise<void> {
const sockets = await this.server.in(room).fetchSockets();
const otherSockets = sockets.filter((s) => s.id !== sender.id);
const otherSockets = excludeUserId
? sockets.filter((s) => s.data.userId !== excludeUserId)
: sockets;
if (otherSockets.length === 0) return;
const userSocketMap = new Map<string, typeof otherSockets>();