์จํ(On-Fit) ํ๋ก์ ํธ์์ ์ฑํ ๋ฐฉ ์ฝ์ ์ฒ๋ฆฌ ๊ธฐ๋ฅ์ ์ง์ ์ค๊ณํ๊ณ ๊ตฌํํ๋ค.
๋จ์ํ โ์ ์ฝ์ ๋ฉ์์ง ๊ฐ์๋ง ๋ณด์ฌ์ฃผ๋ฉด ๋๋ ๊ฑฐ ์๋?โ์ด๋ผ๊ณ ์๊ฐํ์ง๋ง,
๋ง์ ๊ตฌํํ๋ ค๋ ์ํคํ ์ฒ๋ถํฐ ์ ํด์ผ ํ ๊ฒ ๊ฝค ๋ง์๋ค.
์ด ๊ธ์์๋ ๋ด๊ฐ ์ด ๊ธฐ๋ฅ์ ์ด๋ป๊ฒ ๊ตฌ์กฐํํ๊ณ ,
์ ์ด๋ฐ ๋ฐฉ์์ผ๋ก ์ค๊ณํ๋์ง,
๊ทธ๋ฆฌ๊ณ ์ค์ ๊ตฌํ ์ฝ๋๋ฅผ ์ ๋ฆฌํด๋ณธ๋ค.
์จํ์ ์ฑํ ๊ธฐ๋ฅ์์ ๋๋ ๋ค์ ์๊ตฌ์ฌํญ์ ๋ง์กฑ์ํค๊ณ ์ถ์๋ค:
์ฆ, ์นด์นด์คํกยท์ธ์คํ DM์ฒ๋ผ ์์ฐ์ค๋ฌ์ด UX๊ฐ ํ์ํ๋ค.
messages.read_by๋จผ์ Supabase(Postgres)์ messages ํ
์ด๋ธ์
โ๋๊ฐ ์ด ๋ฉ์์ง๋ฅผ ์ฝ์๋์งโ ์ ์ฅํ ๊ตฌ์กฐ๊ฐ ํ์ํ๋ค.
๊ทธ๋์ read_by ๋ฐฐ์ด ์ปฌ๋ผ์ ํ๋ ์ถ๊ฐํ๋ค.
alter table messages
add column read_by text[] default '{}';
์์:
read_by = ['user1', 'user2']
์ด ๋ฉ์์ง๋ฅผ ์ฝ์ ์ ์ ๋ค์ ๋ชฉ๋ก์ ๋ด๋๋ค.
์ด ๊ตฌ์กฐ๋ฅผ ์ฑํํ ์ด์ :
์ด ๊ธฐ๋ฅ์ ์ค๊ณํ๋ฉด์ ๊ฐ์ฅ ์ค์ํ๊ฒ ํ ๊ฒฐ์ ์ API๋ฅผ ๋ ๊ฐ๋ก ๋ถ๋ฆฌํ๋ ๊ฒ์ด์๋ค.
GET /api/chat/roomsโ ์ฑํ ๋ฐฉ ๋ฆฌ์คํธ + unreadCount ๊ณ์ฐ
โ ์กฐํ ์ ์ฉ
POST /api/chat/readโ ์ ์ ๊ฐ ๋ฐฉ์ ๋ค์ด๊ฐ ์๊ฐ ๋ฉ์์ง๋ฅผ ์ฝ์ ์ฒ๋ฆฌ
โ ํ์(Action) ์ ์ฉ
์ด๋ ๊ฒ ๋ถ๋ฆฌํ ์ด์ ๋ ๊ฐ๋จํ๋ค.
๋์ ์์ด๋ฒ๋ฆฌ๋ฉด ์ ์ง๋ณด์๋ ํ์ฅ๋ ์ ๋งคํด์ง๋ค.
unread์ ์กฐ๊ฑด์ ์ด๋ ๋ค:
sender_id !== user.id (๋ด๊ฐ ๋ณด๋ธ ๋ฉ์์ง๋ ์ ์ธ)read_by ๋ฐฐ์ด์ ๋ด user.id๊ฐ ์์๋ค์ ์ฝ๋๋ก room๋ณ unreadCount๋ฅผ ๋ง๋ค์ด๋๋ค.
const unreadCountByRoom: Record<string, number> = {};
(messages ?? []).forEach((m) => {
const rid = m.room_id as string;
const senderId = m.sender_id as string | null;
const readBy = (m.read_by ?? []) as string[];
if (senderId !== user.id && !readBy.includes(user.id)) {
unreadCountByRoom[rid] = (unreadCountByRoom[rid] ?? 0) + 1;
}
});
๊ทธ๋ฆฌ๊ณ ์ต์ข ์๋ต์ ํฌํจ:
unreadCount: unreadCountByRoom[rid] ?? 0
์ด์ ํ๋ก ํธ์์๋ ๋จ์ํ unreadCount๋ง ๋ฐ์ ์ฐ๋ฉด ๋๋ค.
POST /api/chat/read์ ์ ๊ฐ ๋ฐฉ์ ์ง์ ํ์๋ง์ ํธ์ถ๋๋ API๋ค.
export async function POST(req: Request) {
const supabase = await createSupabaseServerClient();
const { ok: hasUser, user, response } = await requireUserOr401(supabase);
if (!hasUser || !user) return response;
const { roomId } = await req.json();
const { data: messages } = await supabase
.from("messages")
.select("id, read_by, sender_id")
.eq("room_id", roomId);
const targets = messages.filter((m) => {
return m.sender_id !== user.id && !m.read_by.includes(user.id);
});
await Promise.all(
targets.map((m) =>
supabase.from("messages").update({
read_by: [...m.read_by, user.id],
}).eq("id", m.id)
)
);
return ok({ updated: targets.length });
}
Promise.all๋ก ๋ณ๋ ฌ ์
๋ฐ์ดํธํ์ฌ ์ฑ๋ฅ๋ ํ๋ณดํ๋ค.
์ฑํ ๋ฐฉ ์นด๋๋ฅผ ๋๋ ์ ๋:
/api/chat/read ํธ์ถ (์ฝ์ ์ฒ๋ฆฌ)/chat/[roomId]๋ก ์ด๋const handleChatRoomCard = () => {
api.post("/api/chat/read", { roomId });
router.push(`/chat/${roomId}`);
};
์ด์ ์ฌ์ฉ์๊ฐ ๋ฐฉ์ ๋ค์ด๊ฐ๋ ์๊ฐ DB์ read_by๊ฐ ๊ฐฑ์ ๋๋ค.
๋ค์ ์ฑํ ๋ฆฌ์คํธ๋ก ๋์ค๋ฉด
GET /api/chat/rooms๊ฐ ์ต์ unreadCount=0์ ๊ฐ์ ธ์ค๋ฏ๋ก
๋ฑ์ง๊ฐ ์์ฐ์ค๋ฝ๊ฒ ์ฌ๋ผ์ง๋ค.
์ด์ ์จํ์์๋ ์ด๋ ๊ฒ ๋์ํ๋ค.
๋ฉ์ ์ ์ฑ์ฒ๋ผ ์์ฐ์ค๋ฌ์ด ์ฌ์ฉ ๊ฒฝํ์ด ๋ง๋ค์ด์ก๋ค.
์ ๋ฆฌํ๋ฉด ์ด๋ฒ ๊ธฐ๋ฅ์ ์ด๋ฐ ๊ตฌ์กฐ๋ก ์ค๊ณํ๋ค.
์ด ๊ตฌ์กฐ๋ ๋งค์ฐ ์ง๊ด์ ์ด๊ณ , Next.js Route Handler ํจํด๊ณผ๋ ์ ๋ง์๋จ์ด์ง๋ค.
์ด๋ฒ์ ์ฝ์ ์ฒ๋ฆฌ ๊ธฐ๋ฅ์ ์ง์ ์ค๊ณํ๋ฉด์ ๋ค์์ ๋ฐฐ์ ๋ค.