Allow viewers to request collaboration on posts
Deploy To Ghaymah / deploy (push) Has been cancelled

This commit is contained in:
boutmoun123
2026-06-10 20:30:03 +03:00
parent 48ea432669
commit a3628d19a2
5 changed files with 274 additions and 40 deletions
@@ -69,7 +69,7 @@ const createService = () => {
};
describe('CollaborationRequestsService', () => {
it('creates a pending collaboration request and notifies the target user', async () => {
it('allows a non-owner viewer to request collaboration on a post and notifies the post owner', async () => {
const requesterId = new Types.ObjectId().toString();
const targetUserId = new Types.ObjectId().toString();
const postId = new Types.ObjectId().toString();
@@ -81,7 +81,7 @@ describe('CollaborationRequestsService', () => {
const { service, model, postsRepository, usersRepository, blocksRepository, notificationsService } =
createService();
postsRepository.findById.mockResolvedValue({ authorId: new Types.ObjectId(requesterId) });
postsRepository.findById.mockResolvedValue({ authorId: new Types.ObjectId(targetUserId) });
usersRepository.findById.mockResolvedValue({ isDisabled: false });
blocksRepository.findAnyBetween.mockResolvedValue(null);
model.findOne
@@ -91,7 +91,6 @@ describe('CollaborationRequestsService', () => {
await expect(
service.create(requesterId, postId, {
targetUserId,
collaborationType: 'duet',
message: 'Let us work',
}),
@@ -100,6 +99,16 @@ describe('CollaborationRequestsService', () => {
request: expect.objectContaining({ populated: true }),
});
expect(model.findOneAndUpdate).toHaveBeenCalledWith(
expect.objectContaining({
postId: new Types.ObjectId(postId),
requesterId: new Types.ObjectId(requesterId),
targetUserId: new Types.ObjectId(targetUserId),
status: 'pending',
}),
expect.any(Object),
expect.any(Object),
);
expect(notificationsService.create).toHaveBeenCalledWith(
expect.objectContaining({
actorId: requesterId,
@@ -111,6 +120,18 @@ describe('CollaborationRequestsService', () => {
);
});
it('prevents the post owner from requesting collaboration on their own post', async () => {
const requesterId = new Types.ObjectId().toString();
const postId = new Types.ObjectId().toString();
const { service, postsRepository } = createService();
postsRepository.findById.mockResolvedValue({ authorId: new Types.ObjectId(requesterId) });
await expect(service.create(requesterId, postId, {})).rejects.toThrow(
'You cannot request collaboration on your own post',
);
});
it('creates a general collaboration request from a user profile', async () => {
const requesterId = new Types.ObjectId().toString();
const targetUserId = new Types.ObjectId().toString();
@@ -272,7 +293,7 @@ describe('CollaborationRequestsService', () => {
});
expect(postsRepository.updateById).toHaveBeenCalledWith(postId.toString(), {
$addToSet: { collaboratorIds: request.targetUserId },
$addToSet: { collaboratorIds: request.requesterId },
});
expect(notificationsService.create).toHaveBeenCalledWith(
expect.objectContaining({
@@ -52,30 +52,27 @@ export class CollaborationRequestsService {
dto: CreateCollaborationRequestDto,
file?: UploadedAudioFile,
) {
const targetUserId = dto.targetUserId;
if (!Types.ObjectId.isValid(postId) || !Types.ObjectId.isValid(targetUserId)) {
if (!Types.ObjectId.isValid(postId)) {
throw new BadRequestException('Invalid collaboration request');
}
if (requesterId === targetUserId) {
throw new BadRequestException('You cannot invite yourself');
}
const [post, targetUser, block] = await Promise.all([
this.postsRepository.findById(postId),
this.usersRepository.findById(targetUserId),
this.blocksRepository.findAnyBetween(requesterId, targetUserId),
]);
const post = await this.postsRepository.findById(postId);
if (!post) {
throw new NotFoundException('Post not found');
}
if (post.authorId.toString() !== requesterId) {
throw new ForbiddenException('Only the post owner can invite collaborators');
const targetUserId = post.authorId.toString();
if (requesterId === targetUserId) {
throw new BadRequestException('You cannot request collaboration on your own post');
}
const [targetUser, block] = await Promise.all([
this.usersRepository.findById(targetUserId),
this.blocksRepository.findAnyBetween(requesterId, targetUserId),
]);
if (!targetUser || targetUser.isDisabled) {
throw new NotFoundException('Target user not found');
}
@@ -302,7 +299,7 @@ export class CollaborationRequestsService {
if (request.postId) {
await this.postsRepository.updateById(request.postId.toString(), {
$addToSet: { collaboratorIds: request.targetUserId },
$addToSet: { collaboratorIds: request.requesterId },
});
}
@@ -568,4 +565,4 @@ export class CollaborationRequestsService {
deepLink: `/users/${actorId}`,
};
}
}
}
@@ -8,8 +8,9 @@ import {
} from '../schemas/collaboration-request.schema';
export class CreateCollaborationRequestDto {
@IsOptional()
@IsMongoId()
targetUserId!: string;
targetUserId?: string;
@IsOptional()
@Transform(({ value }) => (typeof value === 'string' ? value.trim().toLowerCase() : value))