Dolls with friends
This commit is contained in:
@@ -55,19 +55,42 @@ export class DollsController {
|
||||
return this.dollsService.create(user.id, createDollDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@Get('me')
|
||||
@ApiOperation({
|
||||
summary: 'Get all dolls',
|
||||
summary: 'Get my dolls',
|
||||
description: 'Retrieves all dolls belonging to the authenticated user.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Return all dolls.',
|
||||
description: 'Return list of dolls owned by the user.',
|
||||
})
|
||||
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
|
||||
async findAll(@CurrentUser() authUser: AuthenticatedUser) {
|
||||
async listMyDolls(@CurrentUser() authUser: AuthenticatedUser) {
|
||||
const user = await this.authService.ensureUserExists(authUser);
|
||||
return this.dollsService.findAll(user.id);
|
||||
return this.dollsService.listByOwner(user.id, user.id);
|
||||
}
|
||||
|
||||
@Get('user/:userId')
|
||||
@ApiOperation({
|
||||
summary: "Get a user's dolls",
|
||||
description:
|
||||
'Retrieves dolls belonging to a specific user. Requires being friends with that user.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Return list of dolls owned by the specified user.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 403,
|
||||
description: 'Forbidden - Not friends with user',
|
||||
})
|
||||
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
|
||||
async listUserDolls(
|
||||
@CurrentUser() authUser: AuthenticatedUser,
|
||||
@Param('userId') userId: string,
|
||||
) {
|
||||
const user = await this.authService.ensureUserExists(authUser);
|
||||
return this.dollsService.listByOwner(userId, user.id);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { DollsService } from './dolls.service';
|
||||
import { PrismaService } from '../database/prisma.service';
|
||||
import { NotFoundException, ForbiddenException } from '@nestjs/common';
|
||||
@@ -30,6 +31,13 @@ describe('DollsService', () => {
|
||||
findFirst: jest.fn().mockResolvedValue(mockDoll),
|
||||
update: jest.fn().mockResolvedValue(mockDoll),
|
||||
},
|
||||
friendship: {
|
||||
findMany: jest.fn().mockResolvedValue([]),
|
||||
},
|
||||
};
|
||||
|
||||
const mockEventEmitter = {
|
||||
emit: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -40,6 +48,10 @@ describe('DollsService', () => {
|
||||
provide: PrismaService,
|
||||
useValue: mockPrismaService,
|
||||
},
|
||||
{
|
||||
provide: EventEmitter2,
|
||||
useValue: mockEventEmitter,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -73,14 +85,14 @@ describe('DollsService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return an array of dolls', async () => {
|
||||
describe('listByOwner', () => {
|
||||
it('should return own dolls without friendship check', async () => {
|
||||
const userId = 'user-1';
|
||||
await service.findAll(userId);
|
||||
await service.listByOwner(userId, userId);
|
||||
|
||||
expect(prismaService.doll.findMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
userId,
|
||||
userId: userId,
|
||||
deletedAt: null,
|
||||
},
|
||||
orderBy: {
|
||||
@@ -88,6 +100,42 @@ describe('DollsService', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should return friend's dolls if friends", async () => {
|
||||
const ownerId = 'friend-1';
|
||||
const requestingUserId = 'user-1';
|
||||
|
||||
// Mock friendship
|
||||
jest
|
||||
.spyOn(prismaService.friendship, 'findMany')
|
||||
.mockResolvedValueOnce([{ friendId: ownerId } as any]);
|
||||
|
||||
await service.listByOwner(ownerId, requestingUserId);
|
||||
|
||||
expect(prismaService.doll.findMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
userId: ownerId,
|
||||
deletedAt: null,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'asc',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw ForbiddenException if not friends', async () => {
|
||||
const ownerId = 'stranger-1';
|
||||
const requestingUserId = 'user-1';
|
||||
|
||||
// Mock empty friendship (default)
|
||||
jest
|
||||
.spyOn(prismaService.friendship, 'findMany')
|
||||
.mockResolvedValueOnce([]);
|
||||
|
||||
await expect(
|
||||
service.listByOwner(ownerId, requestingUserId),
|
||||
).rejects.toThrow(ForbiddenException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findOne', () => {
|
||||
@@ -107,13 +155,11 @@ describe('DollsService', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw ForbiddenException if doll belongs to another user', async () => {
|
||||
jest
|
||||
.spyOn(prismaService.doll, 'findFirst')
|
||||
.mockResolvedValueOnce({ ...mockDoll, userId: 'user-2' });
|
||||
it('should throw NotFoundException if doll not accessible', async () => {
|
||||
jest.spyOn(prismaService.doll, 'findFirst').mockResolvedValueOnce(null);
|
||||
|
||||
await expect(service.findOne('doll-1', 'user-1')).rejects.toThrow(
|
||||
ForbiddenException,
|
||||
NotFoundException,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -125,6 +171,17 @@ describe('DollsService', () => {
|
||||
|
||||
expect(prismaService.doll.update).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should throw ForbiddenException if not owner', async () => {
|
||||
jest
|
||||
.spyOn(prismaService.doll, 'findFirst')
|
||||
.mockResolvedValueOnce({ ...mockDoll, userId: 'user-2' });
|
||||
|
||||
const updateDto = { name: 'Updated Doll' };
|
||||
await expect(
|
||||
service.update('doll-1', 'user-1', updateDto),
|
||||
).rejects.toThrow(ForbiddenException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
@@ -138,5 +195,15 @@ describe('DollsService', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw ForbiddenException if not owner', async () => {
|
||||
jest
|
||||
.spyOn(prismaService.doll, 'findFirst')
|
||||
.mockResolvedValueOnce({ ...mockDoll, userId: 'user-2' });
|
||||
|
||||
await expect(service.remove('doll-1', 'user-1')).rejects.toThrow(
|
||||
ForbiddenException,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,18 +4,39 @@ import {
|
||||
ForbiddenException,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { PrismaService } from '../database/prisma.service';
|
||||
import { CreateDollDto, DollConfigurationDto } from './dto/create-doll.dto';
|
||||
import { UpdateDollDto } from './dto/update-doll.dto';
|
||||
import { Doll, Prisma } from '@prisma/client';
|
||||
import {
|
||||
DollEvents,
|
||||
DollCreatedEvent,
|
||||
DollUpdatedEvent,
|
||||
DollDeletedEvent,
|
||||
} from './events/doll.events';
|
||||
|
||||
@Injectable()
|
||||
export class DollsService {
|
||||
private readonly logger = new Logger(DollsService.name);
|
||||
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
async create(userId: string, createDollDto: CreateDollDto): Promise<Doll> {
|
||||
async getFriendIds(userId: string): Promise<string[]> {
|
||||
const friendships = await this.prisma.friendship.findMany({
|
||||
where: { userId },
|
||||
select: { friendId: true },
|
||||
});
|
||||
return friendships.map((f) => f.friendId);
|
||||
}
|
||||
|
||||
async create(
|
||||
requestingUserId: string,
|
||||
createDollDto: CreateDollDto,
|
||||
): Promise<Doll> {
|
||||
const defaultConfiguration: DollConfigurationDto = {
|
||||
colorScheme: {
|
||||
outline: '#000000',
|
||||
@@ -34,19 +55,50 @@ export class DollsService {
|
||||
},
|
||||
};
|
||||
|
||||
return this.prisma.doll.create({
|
||||
data: {
|
||||
name: createDollDto.name,
|
||||
configuration: configuration as unknown as Prisma.InputJsonValue,
|
||||
userId,
|
||||
},
|
||||
});
|
||||
return this.prisma.doll
|
||||
.create({
|
||||
data: {
|
||||
name: createDollDto.name,
|
||||
configuration: configuration as unknown as Prisma.InputJsonValue,
|
||||
userId: requestingUserId,
|
||||
},
|
||||
})
|
||||
.then((doll) => {
|
||||
const event: DollCreatedEvent = {
|
||||
userId: requestingUserId,
|
||||
doll,
|
||||
};
|
||||
this.eventEmitter.emit(DollEvents.DOLL_CREATED, event);
|
||||
return doll;
|
||||
});
|
||||
}
|
||||
|
||||
async findAll(userId: string): Promise<Doll[]> {
|
||||
async listByOwner(
|
||||
ownerId: string,
|
||||
requestingUserId: string,
|
||||
): Promise<Doll[]> {
|
||||
// If requesting own dolls, no need to check friendship
|
||||
if (ownerId === requestingUserId) {
|
||||
return this.prisma.doll.findMany({
|
||||
where: {
|
||||
userId: ownerId,
|
||||
deletedAt: null,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'asc',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// If requesting someone else's dolls, check friendship
|
||||
const friendIds = await this.getFriendIds(requestingUserId);
|
||||
if (!friendIds.includes(ownerId)) {
|
||||
throw new ForbiddenException('You are not friends with this user');
|
||||
}
|
||||
|
||||
return this.prisma.doll.findMany({
|
||||
where: {
|
||||
userId,
|
||||
userId: ownerId,
|
||||
deletedAt: null,
|
||||
},
|
||||
orderBy: {
|
||||
@@ -55,20 +107,22 @@ export class DollsService {
|
||||
});
|
||||
}
|
||||
|
||||
async findOne(id: string, userId: string): Promise<Doll> {
|
||||
async findOne(id: string, requestingUserId: string): Promise<Doll> {
|
||||
const friendIds = await this.getFriendIds(requestingUserId);
|
||||
const accessibleUserIds = [requestingUserId, ...friendIds];
|
||||
|
||||
const doll = await this.prisma.doll.findFirst({
|
||||
where: {
|
||||
id,
|
||||
userId: { in: accessibleUserIds },
|
||||
deletedAt: null,
|
||||
},
|
||||
});
|
||||
|
||||
if (!doll) {
|
||||
throw new NotFoundException(`Doll with ID ${id} not found`);
|
||||
}
|
||||
|
||||
if (doll.userId !== userId) {
|
||||
throw new ForbiddenException('You do not have access to this doll');
|
||||
throw new NotFoundException(
|
||||
`Doll with ID ${id} not found or access denied`,
|
||||
);
|
||||
}
|
||||
|
||||
return doll;
|
||||
@@ -76,10 +130,15 @@ export class DollsService {
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
userId: string,
|
||||
requestingUserId: string,
|
||||
updateDollDto: UpdateDollDto,
|
||||
): Promise<Doll> {
|
||||
const doll = await this.findOne(id, userId);
|
||||
const doll = await this.findOne(id, requestingUserId);
|
||||
|
||||
// Only owner can update
|
||||
if (doll.userId !== requestingUserId) {
|
||||
throw new ForbiddenException('You can only update your own dolls');
|
||||
}
|
||||
|
||||
let configuration = doll.configuration as unknown as DollConfigurationDto;
|
||||
|
||||
@@ -101,18 +160,31 @@ export class DollsService {
|
||||
};
|
||||
}
|
||||
|
||||
return this.prisma.doll.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: updateDollDto.name,
|
||||
configuration: configuration as unknown as Prisma.InputJsonValue,
|
||||
},
|
||||
});
|
||||
return this.prisma.doll
|
||||
.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: updateDollDto.name,
|
||||
configuration: configuration as unknown as Prisma.InputJsonValue,
|
||||
},
|
||||
})
|
||||
.then((doll) => {
|
||||
const event: DollUpdatedEvent = {
|
||||
userId: requestingUserId,
|
||||
doll,
|
||||
};
|
||||
this.eventEmitter.emit(DollEvents.DOLL_UPDATED, event);
|
||||
return doll;
|
||||
});
|
||||
}
|
||||
|
||||
async remove(id: string, userId: string): Promise<void> {
|
||||
// Check existence and ownership
|
||||
await this.findOne(id, userId);
|
||||
async remove(id: string, requestingUserId: string): Promise<void> {
|
||||
const doll = await this.findOne(id, requestingUserId);
|
||||
|
||||
// Only owner can delete
|
||||
if (doll.userId !== requestingUserId) {
|
||||
throw new ForbiddenException('You can only delete your own dolls');
|
||||
}
|
||||
|
||||
// Soft delete
|
||||
await this.prisma.doll.update({
|
||||
@@ -121,5 +193,11 @@ export class DollsService {
|
||||
deletedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
const event: DollDeletedEvent = {
|
||||
userId: requestingUserId,
|
||||
dollId: id,
|
||||
};
|
||||
this.eventEmitter.emit(DollEvents.DOLL_DELETED, event);
|
||||
}
|
||||
}
|
||||
|
||||
22
src/dolls/events/doll.events.ts
Normal file
22
src/dolls/events/doll.events.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Doll } from '@prisma/client';
|
||||
|
||||
export const DollEvents = {
|
||||
DOLL_CREATED: 'doll.created',
|
||||
DOLL_UPDATED: 'doll.updated',
|
||||
DOLL_DELETED: 'doll.deleted',
|
||||
} as const;
|
||||
|
||||
export interface DollCreatedEvent {
|
||||
userId: string;
|
||||
doll: Doll;
|
||||
}
|
||||
|
||||
export interface DollUpdatedEvent {
|
||||
userId: string;
|
||||
doll: Doll;
|
||||
}
|
||||
|
||||
export interface DollDeletedEvent {
|
||||
userId: string;
|
||||
dollId: string;
|
||||
}
|
||||
@@ -72,8 +72,10 @@ export class RedisIoAdapter extends IoAdapter {
|
||||
}
|
||||
|
||||
createIOServer(port: number, options?: ServerOptions): any {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const server = super.createIOServer(port, options);
|
||||
if (this.adapterConstructor) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||||
server.adapter(this.adapterConstructor);
|
||||
}
|
||||
return server;
|
||||
|
||||
@@ -190,7 +190,9 @@ describe('StateGateway', () => {
|
||||
data: { user: { keycloakSub: 'test-sub' } },
|
||||
};
|
||||
|
||||
await gateway.handleDisconnect(mockClient as unknown as AuthenticatedSocket);
|
||||
await gateway.handleDisconnect(
|
||||
mockClient as unknown as AuthenticatedSocket,
|
||||
);
|
||||
|
||||
expect(mockLoggerLog).toHaveBeenCalledWith(
|
||||
`Client id: ${mockClient.id} disconnected (user: test-sub)`,
|
||||
@@ -203,7 +205,9 @@ describe('StateGateway', () => {
|
||||
data: {},
|
||||
};
|
||||
|
||||
await gateway.handleDisconnect(mockClient as unknown as AuthenticatedSocket);
|
||||
await gateway.handleDisconnect(
|
||||
mockClient as unknown as AuthenticatedSocket,
|
||||
);
|
||||
|
||||
expect(mockLoggerLog).toHaveBeenCalledWith(
|
||||
`Client id: ${mockClient.id} disconnected (user: unknown)`,
|
||||
@@ -220,15 +224,21 @@ describe('StateGateway', () => {
|
||||
},
|
||||
};
|
||||
|
||||
(mockUserSocketService.getSocket as jest.Mock).mockResolvedValue('client1');
|
||||
(mockUserSocketService.getSocket as jest.Mock).mockResolvedValue(
|
||||
'client1',
|
||||
);
|
||||
(mockUserSocketService.getFriendsSockets as jest.Mock).mockResolvedValue([
|
||||
{ userId: 'friend-1', socketId: 'friend-socket-id' }
|
||||
{ userId: 'friend-1', socketId: 'friend-socket-id' },
|
||||
]);
|
||||
|
||||
await gateway.handleDisconnect(mockClient as unknown as AuthenticatedSocket);
|
||||
await gateway.handleDisconnect(
|
||||
mockClient as unknown as AuthenticatedSocket,
|
||||
);
|
||||
|
||||
expect(mockUserSocketService.getSocket).toHaveBeenCalledWith('user-id');
|
||||
expect(mockUserSocketService.removeSocket).toHaveBeenCalledWith('user-id');
|
||||
expect(mockUserSocketService.removeSocket).toHaveBeenCalledWith(
|
||||
'user-id',
|
||||
);
|
||||
expect(mockServer.to).toHaveBeenCalledWith('friend-socket-id');
|
||||
});
|
||||
});
|
||||
@@ -277,7 +287,9 @@ describe('StateGateway', () => {
|
||||
};
|
||||
|
||||
// Mock getFriendsSockets to return empty array
|
||||
(mockUserSocketService.getFriendsSockets as jest.Mock).mockResolvedValue([]);
|
||||
(mockUserSocketService.getFriendsSockets as jest.Mock).mockResolvedValue(
|
||||
[],
|
||||
);
|
||||
|
||||
const data: CursorPositionDto = { x: 100, y: 200 };
|
||||
|
||||
|
||||
@@ -26,6 +26,13 @@ import type {
|
||||
UnfriendedEvent,
|
||||
} from '../../friends/events/friend.events';
|
||||
|
||||
import { DollEvents } from '../../dolls/events/doll.events';
|
||||
import type {
|
||||
DollCreatedEvent,
|
||||
DollUpdatedEvent,
|
||||
DollDeletedEvent,
|
||||
} from '../../dolls/events/doll.events';
|
||||
|
||||
const WS_EVENT = {
|
||||
CURSOR_REPORT_POSITION: 'cursor-report-position',
|
||||
FRIEND_REQUEST_RECEIVED: 'friend-request-received',
|
||||
@@ -34,6 +41,9 @@ const WS_EVENT = {
|
||||
UNFRIENDED: 'unfriended',
|
||||
FRIEND_CURSOR_POSITION: 'friend-cursor-position',
|
||||
FRIEND_DISCONNECTED: 'friend-disconnected',
|
||||
FRIEND_DOLL_CREATED: 'friend-doll-created',
|
||||
FRIEND_DOLL_UPDATED: 'friend-doll-updated',
|
||||
FRIEND_DOLL_DELETED: 'friend-doll-deleted',
|
||||
} as const;
|
||||
|
||||
@WebSocketGateway({
|
||||
@@ -135,7 +145,8 @@ export class StateGateway
|
||||
const friends = client.data.friends;
|
||||
if (friends) {
|
||||
const friendIds = Array.from(friends);
|
||||
const friendSockets = await this.userSocketService.getFriendsSockets(friendIds);
|
||||
const friendSockets =
|
||||
await this.userSocketService.getFriendsSockets(friendIds);
|
||||
|
||||
for (const { socketId } of friendSockets) {
|
||||
this.io.to(socketId).emit(WS_EVENT.FRIEND_DISCONNECTED, {
|
||||
@@ -188,16 +199,15 @@ export class StateGateway
|
||||
const friends = client.data.friends;
|
||||
if (friends) {
|
||||
const friendIds = Array.from(friends);
|
||||
const friendSockets = await this.userSocketService.getFriendsSockets(friendIds);
|
||||
const friendSockets =
|
||||
await this.userSocketService.getFriendsSockets(friendIds);
|
||||
|
||||
for (const { socketId } of friendSockets) {
|
||||
const payload = {
|
||||
userId: currentUserId,
|
||||
position: data,
|
||||
};
|
||||
this.io
|
||||
.to(socketId)
|
||||
.emit(WS_EVENT.FRIEND_CURSOR_POSITION, payload);
|
||||
this.io.to(socketId).emit(WS_EVENT.FRIEND_CURSOR_POSITION, payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,7 +264,9 @@ export class StateGateway
|
||||
}
|
||||
|
||||
// 2. Update cache for the user who accepted the request (friendRequest.receiverId)
|
||||
const receiverSocketId = await this.userSocketService.getSocket(friendRequest.receiverId);
|
||||
const receiverSocketId = await this.userSocketService.getSocket(
|
||||
friendRequest.receiverId,
|
||||
);
|
||||
if (receiverSocketId) {
|
||||
const receiverSocket = this.io.sockets.sockets.get(
|
||||
receiverSocketId,
|
||||
@@ -318,4 +330,61 @@ export class StateGateway
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OnEvent(DollEvents.DOLL_CREATED)
|
||||
async handleDollCreated(payload: DollCreatedEvent) {
|
||||
const { userId, doll } = payload;
|
||||
const friendSockets = await this.userSocketService.getFriendsSockets([
|
||||
userId,
|
||||
]);
|
||||
|
||||
for (const { socketId } of friendSockets) {
|
||||
this.io.to(socketId).emit(WS_EVENT.FRIEND_DOLL_CREATED, {
|
||||
friendId: userId,
|
||||
doll: {
|
||||
id: doll.id,
|
||||
name: doll.name,
|
||||
configuration: doll.configuration,
|
||||
createdAt: doll.createdAt,
|
||||
updatedAt: doll.updatedAt,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@OnEvent(DollEvents.DOLL_UPDATED)
|
||||
async handleDollUpdated(payload: DollUpdatedEvent) {
|
||||
const { userId, doll } = payload;
|
||||
const friendSockets = await this.userSocketService.getFriendsSockets([
|
||||
userId,
|
||||
]);
|
||||
|
||||
for (const { socketId } of friendSockets) {
|
||||
this.io.to(socketId).emit(WS_EVENT.FRIEND_DOLL_UPDATED, {
|
||||
friendId: userId,
|
||||
doll: {
|
||||
id: doll.id,
|
||||
name: doll.name,
|
||||
configuration: doll.configuration,
|
||||
createdAt: doll.createdAt,
|
||||
updatedAt: doll.updatedAt,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@OnEvent(DollEvents.DOLL_DELETED)
|
||||
async handleDollDeleted(payload: DollDeletedEvent) {
|
||||
const { userId, dollId } = payload;
|
||||
const friendSockets = await this.userSocketService.getFriendsSockets([
|
||||
userId,
|
||||
]);
|
||||
|
||||
for (const { socketId } of friendSockets) {
|
||||
this.io.to(socketId).emit(WS_EVENT.FRIEND_DOLL_DELETED, {
|
||||
friendId: userId,
|
||||
dollId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,9 @@ export class UserSocketService {
|
||||
return !!socketId;
|
||||
}
|
||||
|
||||
async getFriendsSockets(friendIds: string[]): Promise<{ userId: string; socketId: string }[]> {
|
||||
async getFriendsSockets(
|
||||
friendIds: string[],
|
||||
): Promise<{ userId: string; socketId: string }[]> {
|
||||
if (friendIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -95,7 +97,10 @@ export class UserSocketService {
|
||||
}
|
||||
return sockets;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to batch get friend sockets from Redis', error);
|
||||
this.logger.error(
|
||||
'Failed to batch get friend sockets from Redis',
|
||||
error,
|
||||
);
|
||||
// Fallback to local implementation
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user