Skip to content

Commit 8e45b4b

Browse files
authored
Merge pull request #151 from umc-commit/refactor/150-chatroom-list
[REFACTOR] 채팅방 목록 조회 API 수정
2 parents 6fd8668 + bc0b7ca commit 8e45b4b

File tree

10 files changed

+201
-16
lines changed

10 files changed

+201
-16
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- CreateTable
2+
CREATE TABLE `chat_message_reads` (
3+
`id` BIGINT NOT NULL AUTO_INCREMENT,
4+
`messageId` BIGINT NOT NULL,
5+
`accountId` BIGINT NOT NULL,
6+
`read` BOOLEAN NOT NULL DEFAULT false,
7+
8+
PRIMARY KEY (`id`)
9+
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
10+
11+
-- AddForeignKey
12+
ALTER TABLE `chat_message_reads` ADD CONSTRAINT `chat_message_reads_messageId_fkey` FOREIGN KEY (`messageId`) REFERENCES `chat_messages`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
13+
14+
-- AddForeignKey
15+
ALTER TABLE `chat_message_reads` ADD CONSTRAINT `chat_message_reads_accountId_fkey` FOREIGN KEY (`accountId`) REFERENCES `accounts`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

prisma/schema.prisma

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ model Account {
2525
userCategories UserCategory[]
2626
follows Follow[]
2727
userBadges UserBadge[]
28+
chatMessageReads ChatMessageRead[]
2829
2930
@@unique([provider, oauthId])
3031
@@map("accounts")
@@ -235,10 +236,23 @@ model ChatMessage {
235236
236237
chatroom Chatroom @relation(fields: [chatroomId], references: [id])
237238
sender User @relation(fields: [senderId], references: [id])
239+
chatMessageReads ChatMessageRead[]
238240
239241
@@map("chat_messages")
240242
}
241243

244+
model ChatMessageRead {
245+
id BigInt @id @default(autoincrement())
246+
messageId BigInt
247+
accountId BigInt
248+
read Boolean @default(false)
249+
250+
message ChatMessage @relation(fields: [messageId], references: [id])
251+
account Account @relation(fields: [accountId], references: [id])
252+
253+
@@map("chat_message_reads")
254+
}
255+
242256
model Follow {
243257
id BigInt @id @default(autoincrement())
244258
artistId BigInt @map("artist_id")

src/chat/controller/chatroom.controller.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export const createChatroom = async (req, res, next) => {
2727
export const getChatroom = async (req, res, next) => {
2828
try {
2929
const dto = new GetChatroomDto({
30-
consumerId: BigInt(req.user.userId)
30+
consumerId: BigInt(req.user.userId),
31+
accountId: BigInt(req.user.accountId),
3132
});
3233

3334
const chatrooms = await ChatroomService.getChatroomsByUserId(dto);

src/chat/dto/chatroom.dto.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,26 @@ export class CreateChatroomDto {
77
}
88

99
export class GetChatroomDto {
10-
constructor({ consumerId }) {
10+
constructor({ consumerId, accountId }) {
1111
this.consumerId = BigInt(consumerId);
12+
this.accountId = BigInt(accountId);
1213
}
1314
}
1415

16+
export class ChatroomListResponseDto {
17+
constructor(room, unreadCount = 0) {
18+
this.chatroom_id = room.id;
19+
this.artist_id = room.artist.id;
20+
this.artist_nickname = room.artist.nickname;
21+
this.artist_profile_image = room.artist.profileImage;
22+
this.request_id = room.request.id;
23+
this.request_title = room.request.commission.title;
24+
this.last_message = room.chatMessages[0]?.content || null;
25+
this.last_message_time = room.chatMessages[0]?.createdAt || null;
26+
this.has_unread = unreadCount;
27+
}
28+
}
29+
1530
export class DeleteChatroomDto {
1631
constructor({ chatroomIds, userType, userId }) {
1732
this.chatroomIds = chatroomIds.map(id => BigInt(id));

src/chat/repository/chat.repository.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,50 @@ export const ChatRepository = {
4646
}
4747
});
4848
},
49+
50+
async markAsRead(accountId, messageId) {
51+
return await prisma.chatMessageRead.upsert({
52+
where: {
53+
messageId_accountId: {
54+
messageId: BigInt(messageId),
55+
accountId: BigInt(accountId),
56+
},
57+
},
58+
update: { read: true },
59+
create: {
60+
messageId: BigInt(messageId),
61+
accountId: BigInt(accountId),
62+
read: true,
63+
},
64+
});
65+
},
66+
67+
async isMessageRead(accountId, messageId) {
68+
const record = await prisma.chatMessageRead.findUnique({
69+
where: {
70+
messageId_accountId: {
71+
messageId: BigInt(messageId),
72+
accountId: BigInt(accountId),
73+
},
74+
},
75+
});
76+
return record?.read || false;
77+
},
78+
79+
async countUnreadMessages(chatroomId, accountId) {
80+
const count = await prisma.chatMessage.count({
81+
where: {
82+
chatroomId: BigInt(chatroomId),
83+
NOT: {
84+
chatMessageReads: {
85+
some: {
86+
accountId: BigInt(accountId),
87+
read: true,
88+
},
89+
},
90+
},
91+
},
92+
});
93+
return count;
94+
},
4995
};

src/chat/repository/chatroom.repository.js

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,35 @@ export const ChatroomRepository = {
2323
},
2424

2525
async findChatroomsByUser(consumerId) {
26-
return await prisma.chatroom.findMany({
27-
where: {
28-
consumerId: consumerId,
29-
hiddenConsumer: false,
26+
return prisma.chatroom.findMany({
27+
where: { consumerId },
28+
include: {
29+
artist: {
30+
select: {
31+
id: true,
32+
nickname: true,
33+
profileImage: true,
34+
}
35+
},
36+
request: {
37+
select: {
38+
id: true,
39+
commission: {
40+
select: {
41+
title: true
42+
}
43+
}
44+
}
45+
},
46+
chatMessages: {
47+
orderBy: { createdAt: "desc" },
48+
take: 1,
49+
select: {
50+
content: true,
51+
createdAt: true
52+
}
3053
}
54+
}
3155
});
3256
},
3357

src/chat/service/chat.service.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,12 @@ export const ChatService = {
2525
const messages = await ChatRepository.searchByKeyword(dto.keyword, chatroomIds);
2626
return messages;
2727
},
28+
29+
async markMessageAsRead(accountId, messageId) {
30+
return await ChatRepository.markAsRead(accountId, messageId);
31+
},
32+
33+
async getUnreadCount(chatroomId, accountId) {
34+
return await ChatRepository.countUnreadMessages(chatroomId, accountId);
35+
}
2836
};

src/chat/service/chatroom.service.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { ChatroomRepository } from "../repository/chatroom.repository.js";
2+
import { ChatRepository } from "../repository/chat.repository.js";
23
import { UserRepository } from "../../user/repository/user.repository.js";
34
import { RequestRepository } from "../../request/repository/request.repository.js";
45
import { UserNotFoundError } from "../../common/errors/user.errors.js";
56
import { ArtistNotFoundError } from "../../common/errors/artist.errors.js";
67
import { RequestNotFoundError } from "../../common/errors/request.errors.js";
78
import { ChatroomNotFoundError } from "../../common/errors/chat.errors.js";
9+
import { ChatroomListResponseDto } from "../dto/chatroom.dto.js";
810

911
export const ChatroomService = {
1012
async createChatroom(dto) {
@@ -52,8 +54,15 @@ export const ChatroomService = {
5254
}
5355

5456
const chatrooms = await ChatroomRepository.findChatroomsByUser(dto.consumerId);
55-
56-
return chatrooms;
57+
console.log(dto.accountId)
58+
59+
const result = [];
60+
for (const room of chatrooms) {
61+
const unreadCount = await ChatRepository.countUnreadMessages(room.id, dto.accountId);
62+
result.push(new ChatroomListResponseDto(room, unreadCount));
63+
}
64+
65+
return result;
5766
},
5867

5968
async softDeleteChatroomsByUser(dto) {

src/chat/socket/socket.js

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { uploadToS3 } from "../../s3.upload.js";
22
import { Server } from "socket.io";
33
import { stringifyWithBigInt } from "../../bigintJson.js";
44
import { PrismaClient } from '@prisma/client';
5+
import { verifyJwt } from '../../jwt.config.js';
6+
import { ChatRepository } from "../repository/chat.repository.js";
57

68
const prisma = new PrismaClient();
79

@@ -10,13 +12,61 @@ export default function setupSocket(server) {
1012
cors: { origin: "*", methods: ["GET", "POST"] },
1113
});
1214

15+
io.use((socket, next) => {
16+
try {
17+
const token = socket.handshake.auth?.token;
18+
if (!token) {
19+
return next(new Error("Authentication error: Token missing"));
20+
}
21+
const user = verifyJwt(token);
22+
socket.user = user; // userId, role 등 저장
23+
next();
24+
} catch (err) {
25+
next(new Error("Authentication error: Invalid token"));
26+
}
27+
});
28+
1329
io.on("connection", (socket) => {
1430
console.log("User connected:", socket.id);
1531

1632
// 채팅방 join
17-
socket.on("join", (chatroomId) => {
33+
socket.on("join", async (chatroomId) => {
1834
socket.join(chatroomId);
19-
console.log(`User ${socket.id} joined chatroom ${chatroomId}`);
35+
console.log(`User ${socket.user.userId} joined chatroom ${chatroomId}`);
36+
37+
try {
38+
console.log(socket.user)
39+
const accountId = BigInt(socket.user.accountId);
40+
41+
// 해당 채팅방의 모든 미확인 메시지에 대해 읽음 처리
42+
const unreadMessages = await prisma.chatMessage.findMany({
43+
where: {
44+
chatroomId: BigInt(chatroomId),
45+
NOT: {
46+
chatMessageReads: {
47+
some: {
48+
accountId: BigInt(accountId),
49+
read: true,
50+
},
51+
},
52+
},
53+
},
54+
});
55+
56+
// 읽음 상태 기록 업데이트
57+
await Promise.all(
58+
unreadMessages.map((msg) =>
59+
ChatRepository.markAsRead(accountId, msg.id)
60+
)
61+
);
62+
63+
// 필요하면 클라이언트에 읽음 처리 완료 알림 전송
64+
socket.emit("read messages success", { chatroomId });
65+
66+
} catch (err) {
67+
console.error("Error marking messages as read:", err);
68+
socket.emit("error", { message: "읽음 처리 중 오류가 발생했습니다." });
69+
}
2070
});
2171

2272
// 메시지 수신

src/common/swagger/chat.json

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,15 @@
107107
"items": {
108108
"type": "object",
109109
"properties": {
110-
"id": { "type": "integer", "example": 10 },
111-
"consumerId": { "type": "integer", "example": 1 },
112-
"artistId": { "type": "integer", "example": 2 },
113-
"requestId": { "type": "integer", "example": 3 },
114-
"createdAt": { "type": "string", "format": "date-time" },
115-
"updatedAt": { "type": "string", "format": "date-time" }
110+
"chatroom_id": { "type": "string", "example": "2" },
111+
"artist_id": { "type": "string", "example": "1" },
112+
"artist_nickname": { "type": "string", "example": "artist_one" },
113+
"artist_profile_image": { "type": "string", "example": "https://example.com/artist1.png" },
114+
"request_id": { "type": "string", "example": "1" },
115+
"request_title": { "type": "string", "example": "테스트 커미션 글" },
116+
"last_message": { "type": ["string", "null"], "example": null },
117+
"last_message_time": { "type": ["string", "null"], "format": "date-time", "example": null },
118+
"has_unread": { "type": "integer", "example": 0 }
116119
}
117120
}
118121
}

0 commit comments

Comments
 (0)