feat: stabilize social backend contracts
هذا الالتزام موجود في:
@@ -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;
|
||||
}
|
||||
}
|
||||
المرجع في مشكلة جديدة
حظر مستخدم