Skip to content

Commit c44d42e

Browse files
committed
feat: 소켓 완료
1 parent 44e9537 commit c44d42e

File tree

6 files changed

+305
-4
lines changed

6 files changed

+305
-4
lines changed

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
"@nestjs/jwt": "^10.2.0",
2626
"@nestjs/passport": "^10.0.3",
2727
"@nestjs/platform-express": "^10.3.2",
28+
"@nestjs/platform-socket.io": "^10.4.6",
2829
"@nestjs/swagger": "^7.4.2",
2930
"@nestjs/typeorm": "^10.0.2",
31+
"@nestjs/websockets": "^10.4.6",
3032
"@types/passport-kakao": "^1.0.3",
33+
"@types/socket.io": "^3.0.2",
3134
"class-transformer": "^0.5.1",
3235
"class-validator": "^0.14.1",
3336
"dayjs": "^1.11.13",

src/chat-message/chat-message.service.ts

+41
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,47 @@ export class ChatMessageService {
1212
private readonly chatMessageRepository: Repository<ChatMessage>,
1313
) {}
1414

15+
async saveMessage(
16+
chatRoomId: number,
17+
toUserId: number,
18+
message: string,
19+
fromUserId: number,
20+
createdAt: string,
21+
) {
22+
return await this.chatMessageRepository.save({
23+
chatRoomId,
24+
toUserId,
25+
message,
26+
fromUserId,
27+
createdAt,
28+
});
29+
}
30+
31+
async getMessagesByChatRoomId(chatRoomId: number) {
32+
const messages = await this.chatMessageRepository.find({
33+
where: { chatRoom: { id: chatRoomId }, status: 'activated' },
34+
order: { createdAt: 'ASC' }, // 오래된 메시지부터 최신 메시지 순으로 정렬
35+
relations: ['fromUser', 'toUser'], // 메시지 발신자, 수신자 정보도 함께 로드
36+
});
37+
38+
return messages.map((message) => ({
39+
id: message.id,
40+
content: message.content,
41+
fromUser: {
42+
id: message.fromUser.id,
43+
nickname: message.fromUser.nickname,
44+
profileUrl: message.fromUser.profilePictureUrl,
45+
},
46+
toUser: {
47+
id: message.toUser.id,
48+
nickname: message.toUser.nickname,
49+
profileUrl: message.toUser.profilePictureUrl,
50+
},
51+
createdAt: message.createdAt,
52+
toUserReadAt: message.toUserReadAt,
53+
}));
54+
}
55+
1556
async createChatMessage(
1657
queryRunner: QueryRunner,
1758
chatRoom: ChatRoom,

src/chat-room/chat-room.service.ts

+39
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,45 @@ export class ChatRoomService {
1212
private readonly chatRoomRepository: Repository<ChatRoom>,
1313
) {}
1414

15+
async getChatRoomsWithLatestMessage(userId: number) {
16+
const chatRooms = await this.chatRoomRepository
17+
.createQueryBuilder('chatRoom')
18+
.leftJoinAndSelect('chatRoom.fromUser', 'fromUser')
19+
.leftJoinAndSelect('chatRoom.toUser', 'toUser')
20+
.leftJoinAndSelect('chatRoom.chatMessages', 'chatMessages')
21+
.loadRelationIdAndMap('chatRoom.latestMessage', 'chatMessages')
22+
.where('chatRoom.fromUserId = :userId OR chatRoom.toUserId = :userId', {
23+
userId,
24+
})
25+
.andWhere('chatRoom.status = :status', { status: 'activated' })
26+
.orderBy('chatMessages.createdAt', 'DESC') // 최신 메시지 우선 정렬
27+
.getMany();
28+
29+
return chatRooms.map((chatRoom) => {
30+
// 상대방 정보 설정
31+
const otherUser =
32+
chatRoom.fromUser.id === userId ? chatRoom.toUser : chatRoom.fromUser;
33+
34+
// 최신 메시지 설정
35+
const latestMessage = chatRoom.chatMessages[0];
36+
37+
return {
38+
chatRoomId: chatRoom.id,
39+
otherUser: {
40+
id: otherUser.id,
41+
nickname: otherUser.nickname,
42+
profileUrl: otherUser.profilePictureUrl,
43+
},
44+
latestMessage: latestMessage
45+
? {
46+
content: latestMessage.content,
47+
createdAt: latestMessage.createdAt,
48+
}
49+
: null,
50+
};
51+
});
52+
}
53+
1554
async createChatRoom(
1655
queryRunner: QueryRunner,
1756
matching: Matching,

src/eventGateway.ts

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import {
2+
SubscribeMessage,
3+
WebSocketGateway,
4+
WebSocketServer,
5+
OnGatewayDisconnect,
6+
OnGatewayConnection,
7+
} from '@nestjs/websockets';
8+
import { Server, Socket } from 'socket.io';
9+
import { ChatRoomService } from './chat-room/chat-room.service';
10+
import { ChatMessageService } from './chat-message/chat-message.service';
11+
12+
//클라이언트의 패킷들이 게이트웨이를 통해서 들어오게 됩니다.
13+
@WebSocketGateway({ namespace: '/chatting', cors: { origin: '*' } })
14+
export class EventsGateway implements OnGatewayConnection, OnGatewayDisconnect {
15+
// 소켓 서버를 정의합니다.
16+
@WebSocketServer()
17+
server: Server;
18+
// 유저 정보를 레디스에 적재시키기 위해서 레디스 설정이 선행되야합니다.
19+
constructor(
20+
private readonly chatRoomService: ChatRoomService,
21+
private readonly chatMessageService: ChatMessageService,
22+
) {}
23+
24+
afterInit(server: Server) {
25+
console.log('Initialized', server.engine.clientsCount);
26+
}
27+
28+
handleConnection(client: Socket) {
29+
console.log(`Client connected: ${client.id}`);
30+
}
31+
32+
handleDisconnect(client: Socket) {
33+
console.log(`Client disconnected: ${client.id}`);
34+
}
35+
36+
@SubscribeMessage('getChatRooms')
37+
async handleGetChatRooms(client: Socket, userId: number) {
38+
const chatRooms =
39+
await this.chatRoomService.getChatRoomsWithLatestMessage(userId);
40+
client.emit('chatRoomList', chatRooms);
41+
}
42+
43+
@SubscribeMessage('sendMessage')
44+
async handleSendMessage(
45+
client: Socket,
46+
payload: {
47+
chatRoomId: number;
48+
toUserId: number;
49+
message: string;
50+
fromUserId: number;
51+
createdAt: string;
52+
},
53+
) {
54+
const { chatRoomId, toUserId, message, fromUserId, createdAt } = payload;
55+
56+
// 메시지 저장 로직
57+
const newMessage = await this.chatMessageService.saveMessage(
58+
chatRoomId,
59+
toUserId,
60+
message,
61+
fromUserId,
62+
createdAt,
63+
);
64+
65+
// 채팅방 리스트 업데이트
66+
const chatRooms =
67+
await this.chatRoomService.getChatRoomsWithLatestMessage(fromUserId);
68+
client.emit('chatRoomList', chatRooms);
69+
70+
// 해당 채팅방에 있는 모든 사용자에게 메시지 전송
71+
this.server.to(String(chatRoomId)).emit('newMessage', newMessage);
72+
}
73+
74+
@SubscribeMessage('getChatRoomMessages')
75+
async handleGetChatRoomMessages(client: Socket, chatRoomId: number) {
76+
const messages =
77+
await this.chatMessageService.getMessagesByChatRoomId(chatRoomId);
78+
client.emit('chatRoomMessages', messages); // 클라이언트에 메시지 리스트 전송
79+
}
80+
81+
@SubscribeMessage('joinChatRoom')
82+
handleJoinChatRoom(client: Socket, chatRoomId: number) {
83+
client.join(String(chatRoomId));
84+
}
85+
}

src/main.ts

+2
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { AppModule } from './app.module';
33
import { setupSwagger } from './utils/swagger';
44
import { ServiceExceptionToHttpExceptionFilter } from './common/exception-filter';
55
import { ValidationPipe } from '@nestjs/common';
6+
import { IoAdapter } from '@nestjs/platform-socket.io';
67

78
async function bootstrap() {
89
const app = await NestFactory.create(AppModule);
910
app.useGlobalFilters(new ServiceExceptionToHttpExceptionFilter());
1011
app.useGlobalPipes(new ValidationPipe());
12+
app.useWebSocketAdapter(new IoAdapter(app));
1113
setupSwagger(app);
1214
await app.listen(process.env.PORT);
1315
console.log(`Application is running on: ${await app.getUrl()}`);

0 commit comments

Comments
 (0)