Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 138 additions & 4 deletions src/chat/controllers/chat.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const createOrGetChatRoom = async (req: Request, res: Response): Promise<
} catch (err: any) {
console.error(err);
res.fail({
error: err.name || "InternalServerError",
error: err.error || "InternalServerError",
message: err.message || "์ฑ„ํŒ…๋ฐฉ ์ƒ์„ฑ/๋ฐ˜ํ™˜ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
statusCode: err.statusCode || 500,
});
Expand Down Expand Up @@ -61,7 +61,7 @@ export const getChatRoomDetail = async (req: Request, res: Response): Promise<vo
} catch (err: any) {
console.error(err);
res.fail({
error: err.name || "InternalServerError",
error: err.error || "InternalServerError",
message: err.message || "์ฑ„ํŒ…๋ฐฉ ์ƒ์„ธ ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
statusCode: err.statusCode || 500,
});
Expand Down Expand Up @@ -99,9 +99,143 @@ export const getChatRoomList = async(req: Request, res: Response) => {
} catch (err: any) {
console.error(err);
res.fail({
error: err.name || "InternalServerError",
error: err.error || "InternalServerError",
message: err.message || "์ฑ„ํŒ…๋ฐฉ ๋ชฉ๋ก ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
statusCode: err.statusCode || 500,
});
}
}
}

// == ์ƒ๋Œ€๋ฐฉ ์ฐจ๋‹จ
export const blockUser = async(req: Request, res: Response) => {
if (!req.user) {
res.fail({
statusCode: 401,
error: "no user",
message: "๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
});
return;
}
try {
const blockerId = (req.user as { user_id: number }).user_id;
const { blocked_user_id } = req.body as { blocked_user_id: number };

if (!blocked_user_id || isNaN(blocked_user_id)) {
res.fail({ statusCode: 400, error: "BadRequest", message: "์˜ฌ๋ฐ”๋ฅธ blocked_user_id๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค." });
return;
}

if (blockerId === blocked_user_id) {
res.fail({ statusCode: 400, error: "BadRequest", message: "์ž๊ธฐ ์ž์‹ ์„ ์ฐจ๋‹จํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." });
return;
}

await chatService.blockUserService(blockerId, blocked_user_id);

res.success(null, "์ƒ๋Œ€๋ฐฉ์„ ์„ฑ๊ณต์ ์œผ๋กœ ์ฐจ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.");
} catch (err: any) {
console.error(err);
res.fail({
error: err.error || "InternalServerError",
message: err.message || "์ƒ๋Œ€๋ฐฉ ์ฐจ๋‹จ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
statusCode: err.statusCode || 500,
});
}
};

// == ์ฑ„ํŒ…๋ฐฉ ๋‚˜๊ฐ€๊ธฐ
export const leaveChatRoom = async(req: Request, res: Response) => {
if (!req.user) {
res.fail({
statusCode: 401,
error: "no user",
message: "๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
});
return;
}
try {
const userId = (req.user as { user_id: number }).user_id;
const roomId = Number(req.params.roomId);
console.log("๐Ÿ€roomId:", roomId);

if (isNaN(roomId)) {
res.fail({ statusCode: 400, error: "BadRequest", message: "์˜ฌ๋ฐ”๋ฅธ roomId๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค." });
return;
}
await chatService.leaveChatRoomService(roomId, userId);

res.success(null, "์ฑ„ํŒ…๋ฐฉ์„ ์„ฑ๊ณต์ ์œผ๋กœ ๋‚˜๊ฐ”์Šต๋‹ˆ๋‹ค.");
} catch (err: any) {
console.error(err);
res.fail({
error: err.error || "InternalServerError",
message: err.message || "์ฑ„ํŒ…๋ฐฉ ๋‚˜๊ฐ€๊ธฐ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
statusCode: err.statusCode || 500,
});
}
};
// == S3 presigned URL ๋ฐœ๊ธ‰
export const getPresignedUrl = async(req: Request, res: Response) => {
if (!req.user) {
res.fail({
statusCode: 401,
error: "no user",
message: "๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
});
return;
}
try {
const rawFiles = req.body.files;

if (!rawFiles || !Array.isArray(rawFiles) || rawFiles.length === 0) {
res.fail({ statusCode: 400, error: "BadRequest", message: "์—…๋กœ๋“œํ•  ํŒŒ์ผ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค." });
return;
}

const files = rawFiles.map((file: any) => ({
fileName: file.name,
contentType: file.content_type
}));

const result = await chatService.getPresignedUrlService(files);

res.success(result, "presign์„ ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐœ๊ธ‰ํ–ˆ์Šต๋‹ˆ๋‹ค.");
} catch (err: any) {
console.error(err);
res.fail({
error: err.error || "InternalServerError",
message: err.message || "presigned URL ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
statusCode: err.statusCode || 500,
});
}
};

// == ์ฑ„ํŒ…๋ฐฉ ๊ณ ์ • ํ† ๊ธ€
export const togglePinChatRoom = async(req: Request, res: Response) => {
if (!req.user) {
res.fail({
statusCode: 401,
error: "no user",
message: "๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
});
return;
}
try {
const userId = (req.user as { user_id: number }).user_id;
const roomId = Number(req.params.roomId);
if (isNaN(roomId)) {
res.fail({ statusCode: 400, error: "BadRequest", message: "์˜ฌ๋ฐ”๋ฅธ roomId๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค." });
return;
}
const isPinned = await chatService.togglePinChatRoomService(roomId, userId);
res.success(isPinned, "์ฑ„ํŒ…๋ฐฉ ๊ณ ์ •์„ ์„ฑ๊ณต์ ์œผ๋กœ ํ† ๊ธ€ํ–ˆ์Šต๋‹ˆ๋‹ค.");
}
catch (err: any) {
console.error(err);
res.fail({
error: err.error || "InternalServerError",
message: err.message || "์ฑ„ํŒ…๋ฐฉ ๊ณ ์ • ํ† ๊ธ€ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
statusCode: err.statusCode || 500,
});
}
}
5 changes: 5 additions & 0 deletions src/chat/dtos/chat.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,9 @@ export class ChatRoomListResponseDto {

return dto;
}
}

// == ์ฑ„ํŒ…๋ฐฉ ๊ณ ์ • ํ† ๊ธ€
export interface TogglePinResponseDto {
is_pinned: boolean;
}
60 changes: 59 additions & 1 deletion src/chat/repositories/chat.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class ChatRepository {
});
}

// == ์ฑ„ํŒ…๋ฐฉ ์ƒ์„ธ ์กฐํšŒ (์ฐธ์—ฌ์ž ์ •๋ณด ํฌํ•จ)
async findRoomDetailWithParticipant(roomId: number) {
return prisma.chatRoom.findUnique({
where: { room_id: roomId },
Expand All @@ -40,6 +41,23 @@ export class ChatRepository {
});
}

// == ์•ˆ์ฝ์€ ๋ฉ”์„ธ์ง€ ์ดˆ๊ธฐํ™”
async resetUnreadCount(roomId: number, userId: number, lastMessageId?: number | null) {
return prisma.chatParticipant.update({
where: {
room_id_user_id: {
room_id: roomId,
user_id: userId,
},
},
data: {
unread_count: 0,
last_read_message_id: lastMessageId,
},
});
}

// == ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ
async findMessagesByRoomId(roomId: number, cursor?: number, limit: number = 20, userId?: number) {
const leftInfo = await prisma.chatParticipant.findFirst({
where: {
Expand Down Expand Up @@ -194,6 +212,46 @@ export class ChatRepository {
]);
return {rooms, totalRoom}
};
}

// == ์ƒ๋Œ€๋ฐฉ ์ฐจ๋‹จ
async blockUser(blockerId: number, blockedId: number) {
return prisma.userBlock.create({
data: {
blocker_id: blockerId,
blocked_id: blockedId,
},
});
}

// == ์ฑ„ํŒ…๋ฐฉ ๋‚˜๊ฐ€๊ธฐ
async leaveChatRoom(roomId: number, userId: number) {
return prisma.chatParticipant.update({
where: {
room_id_user_id: {
room_id: roomId,
user_id: userId,
},
},
data: {
left_at: new Date(),
},
});
};


// == ์ฑ„ํŒ…๋ฐฉ ๊ณ ์ • ํ† ๊ธ€
async togglePinChatRoom(roomId: number, userId: number, isPinned: boolean) {
return prisma.chatParticipant.update({
where: {
room_id_user_id: {
room_id: roomId,
user_id: userId,
},
},
data: {
is_pinned: !isPinned, // ํ† ๊ธ€
},
});
}
}

Loading