feat: stabilize social backend contracts

هذا الالتزام موجود في:
boutmoun123
2026-05-31 16:13:23 +03:00
الأصل ad6da6754d
التزام 49e132909e
40 ملفات معدلة مع 12037 إضافات و9562 حذوفات

عرض الملف

@@ -0,0 +1,136 @@
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Types } from 'mongoose';
import { PaginationQueryDto } from '../../common/dto/pagination-query.dto';
import { buildPaginatedResponse } from '../../common/utils/pagination.util';
import { BlocksRepository } from '../blocks/blocks.repository';
import { NotificationsService } from '../notifications/notifications.service';
import { PostsRepository } from '../posts/posts.repository';
import { UsersRepository } from '../users/users.repository';
import {
CollaborationRequest,
CollaborationRequestDocument,
} from './schemas/collaboration-request.schema';
@Injectable()
export class CollaborationRequestsService {
constructor(
@InjectModel(CollaborationRequest.name)
private readonly collaborationRequestModel: Model<CollaborationRequestDocument>,
private readonly postsRepository: PostsRepository,
private readonly usersRepository: UsersRepository,
private readonly blocksRepository: BlocksRepository,
private readonly notificationsService: NotificationsService,
) {}
async create(requesterId: string, postId: string, targetUserId: string) {
if (!Types.ObjectId.isValid(postId) || !Types.ObjectId.isValid(targetUserId)) {
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),
]);
if (!post) {
throw new NotFoundException('Post not found');
}
if (post.authorId.toString() !== requesterId) {
throw new ForbiddenException('Only the post owner can invite collaborators');
}
if (!targetUser || targetUser.isDisabled) {
throw new NotFoundException('Target user not found');
}
if (block) {
throw new BadRequestException('You cannot invite this user');
}
const filter = {
postId: new Types.ObjectId(postId),
requesterId: new Types.ObjectId(requesterId),
targetUserId: new Types.ObjectId(targetUserId),
status: 'pending',
};
const existing = await this.collaborationRequestModel.findOne(filter).exec();
const request = existing ?? await this.collaborationRequestModel
.findOneAndUpdate(
filter,
{
$setOnInsert: {
postId: new Types.ObjectId(postId),
requesterId: new Types.ObjectId(requesterId),
targetUserId: new Types.ObjectId(targetUserId),
status: 'pending',
},
},
{ new: true, upsert: true, setDefaultsOnInsert: true },
)
.exec();
if (!existing) {
await this.notificationsService.create({
actorId: requesterId,
recipientId: targetUserId,
type: 'collaboration_request',
referenceId: postId,
resourceType: 'post',
deepLink: `/posts/${postId}`,
});
}
return { message: 'Collaboration request sent', request };
}
async getMine(targetUserId: string, query: PaginationQueryDto) {
const page = query.page ?? 1;
const limit = query.limit ?? 20;
const skip = (page - 1) * limit;
const filter = { targetUserId: new Types.ObjectId(targetUserId), status: 'pending' };
const [items, total] = await Promise.all([
this.collaborationRequestModel
.find(filter)
.populate({ path: 'requesterId', select: 'name username stageName avatar isVerified isDisabled' })
.populate({ path: 'postId' })
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit)
.exec(),
this.collaborationRequestModel.countDocuments(filter).exec(),
]);
return buildPaginatedResponse(items, { page, limit, total, offset: skip });
}
async approve(targetUserId: string, requestId: string) {
const request = await this.updateStatus(targetUserId, requestId, 'approved');
await this.postsRepository.updateById(request.postId.toString(), {
$addToSet: { collaboratorIds: request.targetUserId },
});
return { approved: true, request };
}
async reject(targetUserId: string, requestId: string) {
const request = await this.updateStatus(targetUserId, requestId, 'rejected');
return { rejected: true, request };
}
private async updateStatus(targetUserId: string, requestId: string, status: 'approved' | 'rejected') {
if (!Types.ObjectId.isValid(requestId)) {
throw new BadRequestException('Invalid collaboration request id');
}
const request = await this.collaborationRequestModel
.findOneAndUpdate(
{ _id: new Types.ObjectId(requestId), targetUserId: new Types.ObjectId(targetUserId), status: 'pending' },
{ status },
{ new: true },
)
.exec();
if (!request) {
throw new NotFoundException('Collaboration request not found');
}
return request;
}
}