Match Figma profile, blocks, auth, and archive backend

هذا الالتزام موجود في:
boutmoun123
2026-06-28 18:46:01 +03:00
الأصل 3298338e70
التزام 36ac36e333
44 ملفات معدلة مع 1245 إضافات و39 حذوفات

عرض الملف

@@ -6,6 +6,7 @@ import { PostsModule } from '../posts/posts.module';
import { UsersModule } from '../users/users.module';
import { Like, LikeSchema } from '../likes/schemas/like.schema';
import { FollowsModule } from '../follows/follows.module';
import { BlocksModule } from '../blocks/blocks.module';
import { Comment, CommentSchema } from './schemas/comment.schema';
import { CommentsController } from './comments.controller';
import { CommentsService } from './comments.service';
@@ -22,6 +23,7 @@ import { CommentsRepository } from './comments.repository';
NotificationsModule,
UsersModule,
FollowsModule,
BlocksModule,
],
controllers: [CommentsController],
providers: [CommentsService, CommentsRepository],

عرض الملف

@@ -44,6 +44,9 @@ describe('CommentsService', () => {
const followsRepository = {
findOne: jest.fn(),
};
const blocksService = {
hasBlockBetween: jest.fn().mockResolvedValue(false),
};
const service = new CommentsService(
commentsRepository as any,
@@ -53,6 +56,7 @@ describe('CommentsService', () => {
notificationsService as any,
usersRepository as any,
followsRepository as any,
blocksService as any,
);
const result = await service.update(userId, commentId, {
@@ -82,4 +86,41 @@ describe('CommentsService', () => {
}),
);
});
it('blocks comments when the commenter and post author have a block relation', async () => {
const userId = new Types.ObjectId().toString();
const authorId = new Types.ObjectId().toString();
const postId = new Types.ObjectId().toString();
const commentsRepository = {
create: jest.fn(),
};
const postsRepository = {
findById: jest.fn().mockResolvedValue({
id: postId,
authorId: new Types.ObjectId(authorId),
commentsDisabled: false,
commentsFollowersOnly: false,
}),
setCommentsCount: jest.fn(),
};
const blocksService = {
hasBlockBetween: jest.fn().mockResolvedValue(true),
};
const service = new CommentsService(
commentsRepository as any,
postsRepository as any,
{ logSuperAdminAction: jest.fn() } as any,
{ bumpGlobalVersion: jest.fn() } as any,
{ createCommentNotification: jest.fn(), createMentionNotification: jest.fn() } as any,
{ findByUsernames: jest.fn() } as any,
{ findOne: jest.fn() } as any,
blocksService as any,
);
await expect(
service.create(userId, { postId, content: 'Blocked comment' }),
).rejects.toThrow('Post not found');
expect(commentsRepository.create).not.toHaveBeenCalled();
});
});

عرض الملف

@@ -6,6 +6,7 @@ import { resolveMongoSortDirection } from '../../common/utils/sort.util';
import { FeedVersionService } from '../../infrastructure/cache/feed-version.service';
import { AuditService } from '../audit/audit.service';
import { NotificationsService } from '../notifications/notifications.service';
import { BlocksService } from '../blocks/blocks.service';
import { FollowsRepository } from '../follows/follows.repository';
import { PostsRepository } from '../posts/posts.repository';
import { UsersRepository } from '../users/users.repository';
@@ -48,6 +49,7 @@ export class CommentsService {
private readonly notificationsService: NotificationsService,
private readonly usersRepository: UsersRepository,
private readonly followsRepository: FollowsRepository,
private readonly blocksService: BlocksService,
) {}
async create(userId: string, dto: CreateCommentDto) {
@@ -63,6 +65,7 @@ export class CommentsService {
if (!parent || parent.postId.toString() !== dto.postId) {
throw new NotFoundException('Parent comment not found');
}
await this.assertNoBlockBetween(userId, parent.authorId.toString());
parentRecipientId = parent.authorId.toString();
}
@@ -391,15 +394,27 @@ export class CommentsService {
const authorId = this.extractEntityId(post.authorId);
if (!post.commentsFollowersOnly || authorId === userId) {
await this.assertNoBlockBetween(userId, authorId);
return;
}
await this.assertNoBlockBetween(userId, authorId);
const followsAuthor = await this.followsRepository.findOne(userId, authorId);
if (!followsAuthor) {
throw new ForbiddenException('Only followers can comment on this post');
}
}
private async assertNoBlockBetween(actorId: string, targetUserId: string): Promise<void> {
if (!targetUserId || actorId === targetUserId) {
return;
}
const blocked = await this.blocksService.hasBlockBetween(actorId, targetUserId);
if (blocked) {
throw new NotFoundException('Post not found');
}
}
private matchesCommentFilter(content: string, keywords: string[] = []): boolean {
const normalized = content.toLowerCase();
return keywords
@@ -546,6 +561,9 @@ export class CommentsService {
}
for (const recipientId of recipients) {
if (await this.blocksService.hasBlockBetween(actorId, recipientId)) {
continue;
}
try {
await this.notificationsService.createCommentNotification(actorId, recipientId, postId, {
resourceType: 'post',
@@ -632,6 +650,9 @@ export class CommentsService {
if (excludedRecipientIds.has(mentionedUser.id)) {
continue;
}
if (await this.blocksService.hasBlockBetween(actorId, mentionedUser.id)) {
continue;
}
try {
await this.notificationsService.createMentionNotification(actorId, mentionedUser.id, postId, {