Files
friendolls-server/src/friends/friends.service.ts

336 lines
8.3 KiB
TypeScript

import {
Injectable,
NotFoundException,
BadRequestException,
Logger,
ConflictException,
} from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { PrismaService } from '../database/prisma.service';
import { User, FriendRequest, FriendRequestStatus } from '@prisma/client';
import {
FriendEvents,
FriendRequestReceivedEvent,
FriendRequestAcceptedEvent,
FriendRequestDeniedEvent,
UnfriendedEvent,
} from './events/friend.events';
export type FriendRequestWithRelations = FriendRequest & {
sender: User;
receiver: User;
};
@Injectable()
export class FriendsService {
private readonly logger = new Logger(FriendsService.name);
constructor(
private readonly prisma: PrismaService,
private readonly eventEmitter: EventEmitter2,
) {}
async sendFriendRequest(
senderId: string,
receiverId: string,
): Promise<FriendRequestWithRelations> {
if (senderId === receiverId) {
throw new BadRequestException('Cannot send friend request to yourself');
}
const friendRequest = await this.prisma.$transaction(async (tx) => {
const receiver = await tx.user.findUnique({
where: { id: receiverId },
});
if (!receiver) {
throw new NotFoundException('User not found');
}
// Check for existing friendship using the transaction client
const existingFriendship = await tx.friendship.findFirst({
where: {
userId: senderId,
friendId: receiverId,
},
});
if (existingFriendship) {
throw new ConflictException('You are already friends with this user');
}
const existingRequest = await tx.friendRequest.findFirst({
where: {
OR: [
{ senderId, receiverId },
{
senderId: receiverId,
receiverId: senderId,
},
],
},
});
if (existingRequest) {
if (existingRequest.status === FriendRequestStatus.PENDING) {
if (existingRequest.senderId === senderId) {
throw new ConflictException(
'You already sent a friend request to this user',
);
} else {
throw new ConflictException(
'This user already sent you a friend request',
);
}
} else {
// If there's an existing request that is not pending (accepted or denied), delete it so a new one can be created
await tx.friendRequest.delete({
where: { id: existingRequest.id },
});
}
}
return await tx.friendRequest.create({
data: {
senderId,
receiverId,
status: FriendRequestStatus.PENDING,
},
include: {
sender: true,
receiver: true,
},
});
});
this.logger.log(
`Friend request sent from ${senderId} to ${receiverId} (ID: ${friendRequest.id})`,
);
// Emit event
const event: FriendRequestReceivedEvent = {
userId: receiverId,
friendRequest,
};
this.eventEmitter.emit(FriendEvents.REQUEST_RECEIVED, event);
return friendRequest;
}
async getPendingReceivedRequests(
userId: string,
): Promise<FriendRequestWithRelations[]> {
return this.prisma.friendRequest.findMany({
where: {
receiverId: userId,
status: FriendRequestStatus.PENDING,
},
include: {
sender: true,
receiver: true,
},
orderBy: {
createdAt: 'desc',
},
});
}
async getPendingSentRequests(
userId: string,
): Promise<FriendRequestWithRelations[]> {
return this.prisma.friendRequest.findMany({
where: {
senderId: userId,
status: FriendRequestStatus.PENDING,
},
include: {
sender: true,
receiver: true,
},
orderBy: {
createdAt: 'desc',
},
});
}
async acceptFriendRequest(
requestId: string,
userId: string,
): Promise<FriendRequestWithRelations> {
const friendRequest = await this.prisma.friendRequest.findUnique({
where: { id: requestId },
include: {
sender: true,
receiver: true,
},
});
if (!friendRequest) {
throw new NotFoundException('Friend request not found');
}
if (friendRequest.receiverId !== userId) {
throw new BadRequestException(
'You can only accept friend requests sent to you',
);
}
if (friendRequest.status !== FriendRequestStatus.PENDING) {
throw new BadRequestException(
`Friend request is already ${friendRequest.status.toLowerCase()}`,
);
}
await this.prisma.$transaction([
this.prisma.friendRequest.delete({
where: { id: requestId },
}),
this.prisma.friendship.create({
data: {
userId: friendRequest.senderId,
friendId: friendRequest.receiverId,
},
}),
this.prisma.friendship.create({
data: {
userId: friendRequest.receiverId,
friendId: friendRequest.senderId,
},
}),
]);
// Since we deleted the request, we return the original request object but with status accepted
const result = {
...friendRequest,
status: FriendRequestStatus.ACCEPTED,
updatedAt: new Date(),
};
this.logger.log(
`Friend request ${requestId} accepted. Users ${friendRequest.senderId} and ${friendRequest.receiverId} are now friends`,
);
// Emit event
const event: FriendRequestAcceptedEvent = {
userId: friendRequest.senderId,
friendRequest: result,
};
this.eventEmitter.emit(FriendEvents.REQUEST_ACCEPTED, event);
return result;
}
async denyFriendRequest(
requestId: string,
userId: string,
): Promise<FriendRequestWithRelations> {
const friendRequest = await this.prisma.friendRequest.findUnique({
where: { id: requestId },
include: {
sender: true,
receiver: true,
},
});
if (!friendRequest) {
throw new NotFoundException('Friend request not found');
}
if (friendRequest.receiverId !== userId) {
throw new BadRequestException(
'You can only deny friend requests sent to you',
);
}
if (friendRequest.status !== FriendRequestStatus.PENDING) {
throw new BadRequestException(
`Friend request is already ${friendRequest.status.toLowerCase()}`,
);
}
await this.prisma.friendRequest.delete({
where: { id: requestId },
});
// Since we deleted the request, we return the original request object but with status denied
const result = {
...friendRequest,
status: FriendRequestStatus.DENIED,
updatedAt: new Date(),
};
this.logger.log(`Friend request ${requestId} denied by user ${userId}`);
// Emit event
const event: FriendRequestDeniedEvent = {
userId: friendRequest.senderId,
friendRequest: result,
};
this.eventEmitter.emit(FriendEvents.REQUEST_DENIED, event);
return result;
}
async getFriends(userId: string) {
return this.prisma.friendship.findMany({
where: { userId },
include: {
friend: {
include: {
activeDoll: true,
},
},
},
orderBy: {
createdAt: 'desc',
},
});
}
async unfriend(userId: string, friendId: string): Promise<void> {
if (userId === friendId) {
throw new BadRequestException('Cannot unfriend yourself');
}
const friendship = await this.prisma.friendship.findFirst({
where: {
userId,
friendId,
},
});
if (!friendship) {
throw new NotFoundException('You are not friends with this user');
}
await this.prisma.friendship.deleteMany({
where: {
OR: [
{ userId, friendId },
{ userId: friendId, friendId: userId },
],
},
});
this.logger.log(`User ${userId} unfriended user ${friendId}`);
// Emit event
const event: UnfriendedEvent = {
userId: friendId,
friendId: userId,
};
this.eventEmitter.emit(FriendEvents.UNFRIENDED, event);
}
async areFriends(userId: string, friendId: string): Promise<boolean> {
const friendship = await this.prisma.friendship.findFirst({
where: {
userId,
friendId,
},
});
return !!friendship;
}
}