From 53db6ecbea2d24d1bf47c5a6b08f7e0b0fd16af1 Mon Sep 17 00:00:00 2001 From: bkw535 Date: Thu, 14 Aug 2025 22:19:33 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[REFACTOR]=20chat=20api=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migration.sql | 26 +++++++++++++++ .../migration.sql | 19 +++++++++++ prisma/schema.prisma | 14 ++++---- prisma/seed.js | 32 +++++++++++++++++++ src/chat/controller/chatroom.controller.js | 12 ++++--- src/chat/dto/chatroom.dto.js | 14 ++++---- src/chat/repository/chatroom.repository.js | 26 +++++++-------- src/chat/service/chat.service.js | 4 +-- src/chat/service/chatroom.service.js | 30 ++++++++--------- src/common/swagger/chat.json | 15 +++++---- 10 files changed, 134 insertions(+), 58 deletions(-) create mode 100644 prisma/migrations/20250814125609_rename_chatroom_fields/migration.sql create mode 100644 prisma/migrations/20250814131511_rename_request_to_commission/migration.sql diff --git a/prisma/migrations/20250814125609_rename_chatroom_fields/migration.sql b/prisma/migrations/20250814125609_rename_chatroom_fields/migration.sql new file mode 100644 index 0000000..4a43576 --- /dev/null +++ b/prisma/migrations/20250814125609_rename_chatroom_fields/migration.sql @@ -0,0 +1,26 @@ +-- 기존 FK 삭제 +ALTER TABLE `chatrooms` DROP FOREIGN KEY `chatrooms_consumer_id_fkey`; +ALTER TABLE `chatrooms` DROP FOREIGN KEY `chatrooms_request_id_fkey`; + +-- request_id → commission_id +ALTER TABLE `chatrooms` +CHANGE COLUMN `request_id` `commission_id` BIGINT NOT NULL; + +-- consumer_id → user_id +ALTER TABLE `chatrooms` +CHANGE COLUMN `consumer_id` `user_id` BIGINT NOT NULL; + +-- hidden_consumer → hidden_user +ALTER TABLE `chatrooms` +CHANGE COLUMN `hidden_consumer` `hidden_user` BOOLEAN NOT NULL DEFAULT false; + +-- FK 재생성 (이름 변경된 컬럼에 맞게) +ALTER TABLE `chatrooms` +ADD CONSTRAINT `chatrooms_user_id_fkey` +FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) +ON DELETE RESTRICT ON UPDATE CASCADE; + +ALTER TABLE `chatrooms` +ADD CONSTRAINT `chatrooms_commission_id_fkey` +FOREIGN KEY (`commission_id`) REFERENCES `requests`(`id`) +ON DELETE RESTRICT ON UPDATE CASCADE; \ No newline at end of file diff --git a/prisma/migrations/20250814131511_rename_request_to_commission/migration.sql b/prisma/migrations/20250814131511_rename_request_to_commission/migration.sql new file mode 100644 index 0000000..38a941d --- /dev/null +++ b/prisma/migrations/20250814131511_rename_request_to_commission/migration.sql @@ -0,0 +1,19 @@ +/* + Warnings: + + - You are about to drop the column `commission_id` on the `chatrooms` table. All the data in the column will be lost. + - Added the required column `commission_Id` to the `chatrooms` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropForeignKey +ALTER TABLE `chatrooms` DROP FOREIGN KEY `chatrooms_commission_id_fkey`; + +-- DropIndex +DROP INDEX `chatrooms_commission_id_fkey` ON `chatrooms`; + +-- AlterTable +ALTER TABLE `chatrooms` DROP COLUMN `commission_id`, + ADD COLUMN `commission_Id` BIGINT NOT NULL; + +-- AddForeignKey +ALTER TABLE `chatrooms` ADD CONSTRAINT `chatrooms_commission_Id_fkey` FOREIGN KEY (`commission_Id`) REFERENCES `commissions`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 11b30a1..01efdd4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -112,6 +112,7 @@ model Commission { category Category @relation(fields: [categoryId], references: [id]) requests Request[] bookmarks Bookmark[] + chatrooms Chatroom[] commissionTags CommissionTag[] @@map("commissions") @@ -145,7 +146,6 @@ model Request { user User @relation(fields: [userId], references: [id]) commission Commission @relation(fields: [commissionId], references: [id]) - chatrooms Chatroom[] reviews Review[] PointTransaction PointTransaction[] @@ -212,16 +212,16 @@ model PointTransaction { model Chatroom { id BigInt @id @default(autoincrement()) - requestId BigInt @map("request_id") - consumerId BigInt @map("consumer_id") + commissionId BigInt @map("commission_Id") + userId BigInt @map("user_id") artistId BigInt @map("artist_id") hiddenArtist Boolean @default(false) @map("hidden_artist") - hiddenConsumer Boolean @default(false) @map("hidden_consumer") + hiddenUser Boolean @default(false) @map("hidden_user") createdAt DateTime @default(now()) @map("created_at") - request Request @relation(fields: [requestId], references: [id]) - consumer User @relation("ConsumerChatrooms", fields: [consumerId], references: [id]) - artist Artist @relation("ArtistChatrooms", fields: [artistId], references: [id]) + commission Commission @relation(fields: [commissionId], references: [id]) + user User @relation("ConsumerChatrooms", fields: [userId], references: [id]) + artist Artist @relation("ArtistChatrooms", fields: [artistId], references: [id]) chatMessages ChatMessage[] @@map("chatrooms") diff --git a/prisma/seed.js b/prisma/seed.js index 43e1abf..882d826 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -54,6 +54,38 @@ async function main() { }, }); + // Artist 2 생성 + const artistAccount2 = await prisma.account.create({ + data: { + provider: "kakao", + oauthId: "artist2_oauth_id", + }, + }); + const artist2 = await prisma.artist.create({ + data: { + accountId: artistAccount2.id, + nickname: "artist_two", + description: "두 번째 테스트용 작가입니다.", + profileImage: "https://example.com/artist2.png", + }, + }); + + // Artist 3 생성 + const artistAccount3 = await prisma.account.create({ + data: { + provider: "kakao", + oauthId: "artist3_oauth_id", + }, + }); + const artist3 = await prisma.artist.create({ + data: { + accountId: artistAccount3.id, + nickname: "artist_three", + description: "세 번째 테스트용 작가입니다.", + profileImage: "https://example.com/artist3.png", + }, + }); + // Category 생성 const category1 = await prisma.category.create({ data: { diff --git a/src/chat/controller/chatroom.controller.js b/src/chat/controller/chatroom.controller.js index 8a9ecb2..e9dba1f 100644 --- a/src/chat/controller/chatroom.controller.js +++ b/src/chat/controller/chatroom.controller.js @@ -7,12 +7,12 @@ import { parseWithBigInt, stringifyWithBigInt } from "../../bigintJson.js"; export const createChatroom = async (req, res, next) => { try { - const consumerId = BigInt(req.user.userId); + const userId = BigInt(req.user.userId); const dto = new CreateChatroomDto({ - consumerId: consumerId, + userId, artistId: BigInt(req.body.artistId), - requestId: BigInt(req.body.requestId), + commissionId: BigInt(req.body.commissionId), }); const chatroom = await ChatroomService.createChatroom(dto); @@ -26,8 +26,10 @@ export const createChatroom = async (req, res, next) => { export const getChatroom = async (req, res, next) => { try { + const userId = BigInt(req.user.userId); + const dto = new GetChatroomDto({ - consumerId: BigInt(req.user.userId), + userId, accountId: BigInt(req.user.accountId), }); @@ -47,7 +49,7 @@ export const deleteChatrooms = async (req, res, next) => { const dto = new DeleteChatroomDto({ chatroomIds: req.body.chatroomIds, userType: req.body.userType, - userId: userId, + userId, }); const chatrooms = await ChatroomService.softDeleteChatroomsByUser(dto); diff --git a/src/chat/dto/chatroom.dto.js b/src/chat/dto/chatroom.dto.js index bb2a656..383a96f 100644 --- a/src/chat/dto/chatroom.dto.js +++ b/src/chat/dto/chatroom.dto.js @@ -1,14 +1,14 @@ export class CreateChatroomDto { - constructor({ consumerId, artistId, requestId }) { - this.consumerId = consumerId; + constructor({ userId, artistId, commissionId }) { + this.userId = userId; this.artistId = artistId; - this.requestId = requestId; + this.commissionId = commissionId; } } export class GetChatroomDto { - constructor({ consumerId, accountId }) { - this.consumerId = BigInt(consumerId); + constructor({ userId, accountId }) { + this.userId = BigInt(userId); this.accountId = BigInt(accountId); } } @@ -19,8 +19,8 @@ export class ChatroomListResponseDto { this.artist_id = room.artist.id; this.artist_nickname = room.artist.nickname; this.artist_profile_image = room.artist.profileImage; - this.request_id = room.request.id; - this.request_title = room.request.commission.title; + this.commission_id = room.commission.id; + this.commission_title = room.commission.title; // 이미지가 있으면 이미지 URL, 없으면 텍스트 content const lastMsg = room.chatMessages[0]; this.last_message = lastMsg?.imageUrl || lastMsg?.content || null; diff --git a/src/chat/repository/chatroom.repository.js b/src/chat/repository/chatroom.repository.js index b0a1e36..124ba9c 100644 --- a/src/chat/repository/chatroom.repository.js +++ b/src/chat/repository/chatroom.repository.js @@ -5,27 +5,27 @@ export const ChatroomRepository = { console.log(data) return await prisma.chatroom.create({ data: { - consumerId: data.consumerId, + userId: data.userId, artistId: data.artistId, - requestId: data.requestId, + commissionId: data.commissionId, }, }); }, - async findChatroomByUsersAndCommission(consumerId, artistId, requestId) { + async findChatroomByUsersAndCommission(userId, artistId, commissionId) { return await prisma.chatroom.findFirst({ where: { - consumerId, + userId, artistId, - requestId, + commissionId, }, }); }, - async findChatroomsByUser(consumerId) { + async findChatroomsByUser(userId) { // 1. 채팅방 기본 정보 + 마지막 메시지(내용, 생성시간, id) 조회 const chatrooms = await prisma.chatroom.findMany({ - where: { consumerId }, + where: { userId }, include: { artist: { select: { @@ -34,14 +34,10 @@ export const ChatroomRepository = { profileImage: true, } }, - request: { + commission: { select: { id: true, - commission: { - select: { - title: true, - } - } + title: true, } }, chatMessages: { @@ -91,8 +87,8 @@ export const ChatroomRepository = { }, async softDeleteChatrooms(chatroomIds, userType, userId) { - const hiddenField = userType === "consumer" ? "hiddenConsumer" : "hiddenArtist"; - const userField = userType === "consumer" ? "consumerId" : "artistId"; + const hiddenField = userType === "user" ? "hiddenUser" : "hiddenArtist"; + const userField = userType === "user" ? "userId" : "artistId"; await prisma.chatroom.updateMany({ where: { diff --git a/src/chat/service/chat.service.js b/src/chat/service/chat.service.js index e04aef7..7cb485e 100644 --- a/src/chat/service/chat.service.js +++ b/src/chat/service/chat.service.js @@ -10,8 +10,8 @@ export const ChatService = { throw new ChatroomNotFoundError({ chatroomId: dto.chatroomId }); } - if (dto.userId !== chatroom.consumerId && dto.userId !== chatroom.artistId) { - throw new ForbiddenError({ consumerId: dto.userId }); + if (dto.userId !== chatroom.userId && dto.userId !== chatroom.artistId) { + throw new ForbiddenError({ userId: dto.userId }); } const messages = await ChatRepository.findMessagesWithImages(dto); diff --git a/src/chat/service/chatroom.service.js b/src/chat/service/chatroom.service.js index c62feca..b4480bb 100644 --- a/src/chat/service/chatroom.service.js +++ b/src/chat/service/chatroom.service.js @@ -1,18 +1,18 @@ import { ChatroomRepository } from "../repository/chatroom.repository.js"; import { ChatRepository } from "../repository/chat.repository.js"; import { UserRepository } from "../../user/repository/user.repository.js"; -import { RequestRepository } from "../../request/repository/request.repository.js"; +import { CommissionRepository } from "../../commission/repository/commission.repository.js"; import { UserNotFoundError } from "../../common/errors/user.errors.js"; import { ArtistNotFoundError } from "../../common/errors/artist.errors.js"; -import { RequestNotFoundError } from "../../common/errors/request.errors.js"; +import { CommissionNotFoundError } from "../../common/errors/commission.errors.js"; import { ChatroomNotFoundError } from "../../common/errors/chat.errors.js"; import { ChatroomListResponseDto } from "../dto/chatroom.dto.js"; export const ChatroomService = { async createChatroom(dto) { - const user = await UserRepository.findUserById(dto.consumerId); + const user = await UserRepository.findUserById(dto.userId); if (!user) { - throw new UserNotFoundError({ consumerId: dto.consumerId }); + throw new UserNotFoundError({ userId: dto.userId }); } const artist = await UserRepository.findArtistById(dto.artistId); @@ -20,16 +20,16 @@ export const ChatroomService = { throw new ArtistNotFoundError({ artistId: dto.artistId }); } - const request = await RequestRepository.findRequestById(dto.requestId); - if (!request) { - throw new RequestNotFoundError({ requestId: dto.requestId }); + const commission = await CommissionRepository.findCommissionById(dto.commissionId); + if (!commission) { + throw new CommissionNotFoundError({ commissionId: dto.commissionId }); } // 채팅방 중복 확인 const existing = await ChatroomRepository.findChatroomByUsersAndCommission( - dto.consumerId, + dto.userId, dto.artistId, - dto.requestId + dto.commissionId ); // 기존 채팅방 반환 @@ -39,21 +39,21 @@ export const ChatroomService = { // 채팅방이 없을 시 생성 const chatroom = await ChatroomRepository.createChatroom({ - consumerId: dto.consumerId, + userId: dto.userId, artistId: dto.artistId, - requestId: dto.requestId, + commissionId: dto.commissionId, }); return chatroom; }, async getChatroomsByUserId(dto) { - const user = await UserRepository.findUserById(dto.consumerId); + const user = await UserRepository.findUserById(dto.userId); if (!user) { - throw new UserNotFoundError({ consumerId: dto.consumerId }); + throw new UserNotFoundError({ userId: dto.userId }); } - const chatrooms = await ChatroomRepository.findChatroomsByUser(dto.consumerId); + const chatrooms = await ChatroomRepository.findChatroomsByUser(dto.userId); console.log(dto.accountId) const result = []; @@ -76,7 +76,7 @@ export const ChatroomService = { const chatrooms = await ChatroomRepository.findChatroomsByIds(dto.chatroomIds); const chatroomIdsToDelete = chatrooms - .filter(cr => cr.hiddenConsumer && cr.hiddenArtist) + .filter(cr => cr.hiddenUser && cr.hiddenArtist) .map(cr => cr.id); if (chatroomIdsToDelete.length > 0) { diff --git a/src/common/swagger/chat.json b/src/common/swagger/chat.json index be9eb95..5f0db7e 100644 --- a/src/common/swagger/chat.json +++ b/src/common/swagger/chat.json @@ -3,7 +3,7 @@ "/api/chatrooms": { "post": { "summary": "채팅방 생성", - "description": "consumerId, artistId, requestId로 채팅방을 생성합니다.", + "description": "consumerId, artistId, commissionId로 채팅방을 생성합니다.", "security": [ { "bearerAuth": [] @@ -19,8 +19,9 @@ "properties": { "consumerId": { "type": "integer", "example": 1 }, "artistId": { "type": "integer", "example": 2 }, - "requestId": { "type": "integer", "example": 3 } - } + "commissionId": { "type": "integer", "example": 3 } + }, + "required": ["consumerId", "artistId", "commissionId"] } } } @@ -40,8 +41,8 @@ "properties": { "id": { "type": "string", "example": "1" }, "consumerId": { "type": "string", "example": "1" }, - "artistId": { "type": "string", "example": "1" }, - "requestId": { "type": "string", "example": "1" }, + "artistId": { "type": "string", "example": "2" }, + "commissionId": { "type": "string", "example": "3" }, "hiddenArtist": { "type": "boolean", "example": false }, "hiddenConsumer": { "type": "boolean", "example": false }, "createdAt": { "type": "string", "format": "date-time", "example": "2025-08-07T15:35:42.375Z" } @@ -114,8 +115,8 @@ "artist_id": { "type": "string", "example": "1" }, "artist_nickname": { "type": "string", "example": "artist_one" }, "artist_profile_image": { "type": "string", "example": "https://example.com/artist1.png" }, - "request_id": { "type": "string", "example": "1" }, - "request_title": { "type": "string", "example": "테스트 커미션 글" }, + "commission_id": { "type": "string", "example": "1" }, + "commission_title": { "type": "string", "example": "테스트 커미션 글" }, "last_message": { "type": ["string", "null"], "example": null }, "last_message_time": { "type": ["string", "null"], "format": "date-time", "example": null }, "has_unread": { "type": "integer", "example": 0 } From d20565cff3a49daebb0852ed2f53354afe9bf21f Mon Sep 17 00:00:00 2001 From: bkw535 Date: Thu, 14 Aug 2025 22:22:07 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[REFACTOR]=20swagger=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/swagger/chat.json | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/common/swagger/chat.json b/src/common/swagger/chat.json index 5f0db7e..0ecc87b 100644 --- a/src/common/swagger/chat.json +++ b/src/common/swagger/chat.json @@ -3,7 +3,7 @@ "/api/chatrooms": { "post": { "summary": "채팅방 생성", - "description": "consumerId, artistId, commissionId로 채팅방을 생성합니다.", + "description": "userId, artistId, commissionId로 채팅방을 생성합니다.", "security": [ { "bearerAuth": [] @@ -17,11 +17,10 @@ "schema": { "type": "object", "properties": { - "consumerId": { "type": "integer", "example": 1 }, "artistId": { "type": "integer", "example": 2 }, "commissionId": { "type": "integer", "example": 3 } }, - "required": ["consumerId", "artistId", "commissionId"] + "required": ["userId", "artistId", "commissionId"] } } } @@ -40,11 +39,10 @@ "type": "object", "properties": { "id": { "type": "string", "example": "1" }, - "consumerId": { "type": "string", "example": "1" }, "artistId": { "type": "string", "example": "2" }, "commissionId": { "type": "string", "example": "3" }, "hiddenArtist": { "type": "boolean", "example": false }, - "hiddenConsumer": { "type": "boolean", "example": false }, + "hiddenUser": { "type": "boolean", "example": false }, "createdAt": { "type": "string", "format": "date-time", "example": "2025-08-07T15:35:42.375Z" } } } @@ -179,8 +177,8 @@ }, "userType": { "type": "string", - "enum": ["consumer", "artist"], - "example": "consumer" + "enum": ["user", "artist"], + "example": "user" } }, "required": ["chatroomIds", "userType"] @@ -204,7 +202,7 @@ "type": "object", "properties": { "errorCode": { "type": "string", "example": "C001" }, - "reason": { "type": "string", "example": "userType은 'consumer' 또는 'artist'여야 합니다." }, + "reason": { "type": "string", "example": "userType은 'user' 또는 'artist'여야 합니다." }, "data": { "type": "object", "example": {} } } },