Skip to content

Commit 1d50ae6

Browse files
authored
fix(web): normalize chat user ids across api and websocket (#484)
1 parent 4606204 commit 1d50ae6

File tree

5 files changed

+112
-9
lines changed

5 files changed

+112
-9
lines changed

apps/web/src/apis/chat/api.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import type { AxiosResponse } from "axios";
22
import type { ChatMessage, ChatPartner, ChatRoom } from "@/types/chat";
33
import { axiosInstance } from "@/utils/axiosInstance";
4+
import {
5+
normalizeChatMessage,
6+
normalizeChatPartner,
7+
normalizeChatRoom,
8+
type RawChatMessage,
9+
type RawChatPartner,
10+
type RawChatRoom,
11+
} from "./normalize";
412

513
// QueryKeys for chat domain
614
export const ChatQueryKeys = {
@@ -27,20 +35,34 @@ interface GetChatHistoriesParams {
2735
page?: number;
2836
}
2937

38+
interface RawChatHistoriesResponse {
39+
nextPageNumber: number;
40+
content: RawChatMessage[];
41+
}
42+
43+
interface RawChatRoomListResponse {
44+
chatRooms: RawChatRoom[];
45+
}
46+
3047
export const chatApi = {
3148
getChatHistories: async ({ roomId, size = 20, page = 0 }: GetChatHistoriesParams): Promise<ChatHistoriesResponse> => {
32-
const res = await axiosInstance.get<ChatHistoriesResponse>(`/chats/rooms/${roomId}`, {
49+
const res = await axiosInstance.get<RawChatHistoriesResponse>(`/chats/rooms/${roomId}`, {
3350
params: {
3451
size,
3552
page,
3653
},
3754
});
38-
return res.data;
55+
return {
56+
nextPageNumber: res.data.nextPageNumber,
57+
content: (res.data.content ?? []).map(normalizeChatMessage),
58+
};
3959
},
4060

4161
getChatRooms: async (): Promise<ChatRoomListResponse> => {
42-
const res = await axiosInstance.get<ChatRoomListResponse>("/chats/rooms");
43-
return res.data;
62+
const res = await axiosInstance.get<RawChatRoomListResponse>("/chats/rooms");
63+
return {
64+
chatRooms: (res.data.chatRooms ?? []).map(normalizeChatRoom),
65+
};
4466
},
4567

4668
putReadChatRoom: async (roomId: number): Promise<void> => {
@@ -49,7 +71,7 @@ export const chatApi = {
4971
},
5072

5173
getChatPartner: async (roomId: number): Promise<ChatPartner> => {
52-
const res = await axiosInstance.get<ChatPartner>(`/chats/rooms/${roomId}/partner`);
53-
return res.data;
74+
const res = await axiosInstance.get<RawChatPartner>(`/chats/rooms/${roomId}/partner`);
75+
return normalizeChatPartner(res.data);
5476
},
5577
};
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import type { ChatAttachment, ChatMessage, ChatPartner, ChatRoom } from "@/types/chat";
2+
3+
type NumericLike = number | string | null | undefined;
4+
5+
interface RawChatAttachment {
6+
id?: NumericLike;
7+
isImage: boolean;
8+
url: string;
9+
thumbnailUrl?: string | null;
10+
createdAt: string;
11+
}
12+
13+
export interface RawChatMessage {
14+
id?: NumericLike;
15+
content: string;
16+
senderId?: NumericLike;
17+
siteUserId?: NumericLike;
18+
createdAt: string;
19+
attachments?: RawChatAttachment[];
20+
}
21+
22+
export interface RawChatPartner {
23+
partnerId?: NumericLike;
24+
siteUserId?: NumericLike;
25+
nickname: string;
26+
profileUrl?: string | null;
27+
university?: string | null;
28+
}
29+
30+
export interface RawChatRoom {
31+
id: number;
32+
lastChatMessage: string;
33+
lastReceivedTime: string;
34+
partner: RawChatPartner;
35+
unReadCount: number;
36+
}
37+
38+
const toNumber = (value: NumericLike): number => {
39+
if (typeof value === "number" && Number.isFinite(value)) {
40+
return value;
41+
}
42+
43+
if (typeof value === "string" && value.trim() !== "") {
44+
const parsed = Number(value);
45+
return Number.isFinite(parsed) ? parsed : 0;
46+
}
47+
48+
return 0;
49+
};
50+
51+
const normalizeAttachment = (attachment: RawChatAttachment): ChatAttachment => ({
52+
id: toNumber(attachment.id),
53+
isImage: attachment.isImage,
54+
url: attachment.url,
55+
thumbnailUrl: attachment.thumbnailUrl ?? "",
56+
createdAt: attachment.createdAt,
57+
});
58+
59+
export const normalizeChatMessage = (message: RawChatMessage): ChatMessage => ({
60+
id: toNumber(message.id),
61+
content: message.content,
62+
senderId: toNumber(message.senderId ?? message.siteUserId),
63+
createdAt: message.createdAt,
64+
attachments: (message.attachments ?? []).map(normalizeAttachment),
65+
});
66+
67+
export const normalizeChatPartner = (partner: RawChatPartner): ChatPartner => ({
68+
partnerId: toNumber(partner.partnerId ?? partner.siteUserId),
69+
nickname: partner.nickname,
70+
profileUrl: partner.profileUrl ?? null,
71+
university: partner.university ?? null,
72+
});
73+
74+
export const normalizeChatRoom = (room: RawChatRoom): ChatRoom => ({
75+
id: room.id,
76+
lastChatMessage: room.lastChatMessage,
77+
lastReceivedTime: room.lastReceivedTime,
78+
partner: normalizeChatPartner(room.partner),
79+
unReadCount: room.unReadCount,
80+
});

apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ interface ChatContentProps {
2424
const ChatContent = ({ chatId }: ChatContentProps) => {
2525
const { accessToken } = useAuthStore();
2626
const parsedData = tokenParse(accessToken);
27-
const userId = parsedData?.sub ?? 0;
27+
const userId = Number(parsedData?.sub ?? 0) || 0;
2828

2929
const isMentor = parsedData?.role === UserRole.MENTOR || parsedData?.role === UserRole.ADMIN;
3030

apps/web/src/lib/web-socket/useConnectWebSocket.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Client } from "@stomp/stompjs";
22
import type { MutableRefObject } from "react";
33
import { useEffect, useState } from "react";
44
import SockJS from "sockjs-client";
5+
import { normalizeChatMessage, type RawChatMessage } from "@/apis/chat/normalize";
56

67
import { type ChatMessage, ConnectionStatus } from "@/types/chat";
78
import useAuthStore from "../zustand/useAuthStore";
@@ -54,7 +55,7 @@ const useConnectWebSocket = ({ roomId, clientRef }: UseConnectWebSocketProps): U
5455
setConnectionStatus(ConnectionStatus.Connected);
5556
client.subscribe(`/topic/chat/${roomId}`, (message) => {
5657
try {
57-
const receivedMessage = JSON.parse(message.body) as ChatMessage;
58+
const receivedMessage = normalizeChatMessage(JSON.parse(message.body) as RawChatMessage);
5859

5960
if (!receivedMessage.createdAt || Number.isNaN(new Date(receivedMessage.createdAt).getTime())) {
6061
receivedMessage.createdAt = new Date().toISOString();

apps/web/src/utils/jwtUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
interface JwtPayload {
2-
sub: number;
2+
sub: number | string;
33
role: string;
44
iat: number;
55
exp: number;

0 commit comments

Comments
 (0)