diff --git a/apps/web/src/apis/chat/api.ts b/apps/web/src/apis/chat/api.ts index 1fa093d0..f9ddd7d0 100644 --- a/apps/web/src/apis/chat/api.ts +++ b/apps/web/src/apis/chat/api.ts @@ -1,6 +1,14 @@ import type { AxiosResponse } from "axios"; import type { ChatMessage, ChatPartner, ChatRoom } from "@/types/chat"; import { axiosInstance } from "@/utils/axiosInstance"; +import { + normalizeChatMessage, + normalizeChatPartner, + normalizeChatRoom, + type RawChatMessage, + type RawChatPartner, + type RawChatRoom, +} from "./normalize"; // QueryKeys for chat domain export const ChatQueryKeys = { @@ -27,20 +35,34 @@ interface GetChatHistoriesParams { page?: number; } +interface RawChatHistoriesResponse { + nextPageNumber: number; + content: RawChatMessage[]; +} + +interface RawChatRoomListResponse { + chatRooms: RawChatRoom[]; +} + export const chatApi = { getChatHistories: async ({ roomId, size = 20, page = 0 }: GetChatHistoriesParams): Promise => { - const res = await axiosInstance.get(`/chats/rooms/${roomId}`, { + const res = await axiosInstance.get(`/chats/rooms/${roomId}`, { params: { size, page, }, }); - return res.data; + return { + nextPageNumber: res.data.nextPageNumber, + content: (res.data.content ?? []).map(normalizeChatMessage), + }; }, getChatRooms: async (): Promise => { - const res = await axiosInstance.get("/chats/rooms"); - return res.data; + const res = await axiosInstance.get("/chats/rooms"); + return { + chatRooms: (res.data.chatRooms ?? []).map(normalizeChatRoom), + }; }, putReadChatRoom: async (roomId: number): Promise => { @@ -49,7 +71,7 @@ export const chatApi = { }, getChatPartner: async (roomId: number): Promise => { - const res = await axiosInstance.get(`/chats/rooms/${roomId}/partner`); - return res.data; + const res = await axiosInstance.get(`/chats/rooms/${roomId}/partner`); + return normalizeChatPartner(res.data); }, }; diff --git a/apps/web/src/apis/chat/normalize.ts b/apps/web/src/apis/chat/normalize.ts new file mode 100644 index 00000000..8850d708 --- /dev/null +++ b/apps/web/src/apis/chat/normalize.ts @@ -0,0 +1,80 @@ +import type { ChatAttachment, ChatMessage, ChatPartner, ChatRoom } from "@/types/chat"; + +type NumericLike = number | string | null | undefined; + +interface RawChatAttachment { + id?: NumericLike; + isImage: boolean; + url: string; + thumbnailUrl?: string | null; + createdAt: string; +} + +export interface RawChatMessage { + id?: NumericLike; + content: string; + senderId?: NumericLike; + siteUserId?: NumericLike; + createdAt: string; + attachments?: RawChatAttachment[]; +} + +export interface RawChatPartner { + partnerId?: NumericLike; + siteUserId?: NumericLike; + nickname: string; + profileUrl?: string | null; + university?: string | null; +} + +export interface RawChatRoom { + id: number; + lastChatMessage: string; + lastReceivedTime: string; + partner: RawChatPartner; + unReadCount: number; +} + +const toNumber = (value: NumericLike): number => { + if (typeof value === "number" && Number.isFinite(value)) { + return value; + } + + if (typeof value === "string" && value.trim() !== "") { + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : 0; + } + + return 0; +}; + +const normalizeAttachment = (attachment: RawChatAttachment): ChatAttachment => ({ + id: toNumber(attachment.id), + isImage: attachment.isImage, + url: attachment.url, + thumbnailUrl: attachment.thumbnailUrl ?? "", + createdAt: attachment.createdAt, +}); + +export const normalizeChatMessage = (message: RawChatMessage): ChatMessage => ({ + id: toNumber(message.id), + content: message.content, + senderId: toNumber(message.senderId ?? message.siteUserId), + createdAt: message.createdAt, + attachments: (message.attachments ?? []).map(normalizeAttachment), +}); + +export const normalizeChatPartner = (partner: RawChatPartner): ChatPartner => ({ + partnerId: toNumber(partner.partnerId ?? partner.siteUserId), + nickname: partner.nickname, + profileUrl: partner.profileUrl ?? null, + university: partner.university ?? null, +}); + +export const normalizeChatRoom = (room: RawChatRoom): ChatRoom => ({ + id: room.id, + lastChatMessage: room.lastChatMessage, + lastReceivedTime: room.lastReceivedTime, + partner: normalizeChatPartner(room.partner), + unReadCount: room.unReadCount, +}); diff --git a/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx b/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx index 8fb606ad..6a51670a 100644 --- a/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx +++ b/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx @@ -24,7 +24,7 @@ interface ChatContentProps { const ChatContent = ({ chatId }: ChatContentProps) => { const { accessToken } = useAuthStore(); const parsedData = tokenParse(accessToken); - const userId = parsedData?.sub ?? 0; + const userId = Number(parsedData?.sub ?? 0) || 0; const isMentor = parsedData?.role === UserRole.MENTOR || parsedData?.role === UserRole.ADMIN; diff --git a/apps/web/src/lib/web-socket/useConnectWebSocket.ts b/apps/web/src/lib/web-socket/useConnectWebSocket.ts index 11532820..cc831805 100644 --- a/apps/web/src/lib/web-socket/useConnectWebSocket.ts +++ b/apps/web/src/lib/web-socket/useConnectWebSocket.ts @@ -2,6 +2,7 @@ import { Client } from "@stomp/stompjs"; import type { MutableRefObject } from "react"; import { useEffect, useState } from "react"; import SockJS from "sockjs-client"; +import { normalizeChatMessage, type RawChatMessage } from "@/apis/chat/normalize"; import { type ChatMessage, ConnectionStatus } from "@/types/chat"; import useAuthStore from "../zustand/useAuthStore"; @@ -54,7 +55,7 @@ const useConnectWebSocket = ({ roomId, clientRef }: UseConnectWebSocketProps): U setConnectionStatus(ConnectionStatus.Connected); client.subscribe(`/topic/chat/${roomId}`, (message) => { try { - const receivedMessage = JSON.parse(message.body) as ChatMessage; + const receivedMessage = normalizeChatMessage(JSON.parse(message.body) as RawChatMessage); if (!receivedMessage.createdAt || Number.isNaN(new Date(receivedMessage.createdAt).getTime())) { receivedMessage.createdAt = new Date().toISOString(); diff --git a/apps/web/src/utils/jwtUtils.ts b/apps/web/src/utils/jwtUtils.ts index be8f9d8f..b7955405 100644 --- a/apps/web/src/utils/jwtUtils.ts +++ b/apps/web/src/utils/jwtUtils.ts @@ -1,5 +1,5 @@ interface JwtPayload { - sub: number; + sub: number | string; role: string; iat: number; exp: number;