![[인프런 워밍업 스터디 클럽 3기 풀스택] 4주차 발자국](https://cdn.inflearn.com/public/files/blogs/b42533f7-f53d-4321-9a82-d3177f225319/스크린샷 2025-03-30 오후 9.50.37.png)
[인프런 워밍업 스터디 클럽 3기 풀스택] 4주차 발자국
강의수강
supabase Auth
다양한 인증방식을 지원하는 인증 시스템
이메일 인증, 소셜 로그인 등 다양한 방법을 지원
confirmation URL 방식
supabase가 사용자의 이메일로 확인 링크를 전송
사용자가 해당 링크를 클릭하면, supabase에서 해당 이메일을 확인하고 인증 완료
6 - Digit OTP 방식
이메일이나 SMS를 통해 6자리 숫자를 전송
사용자는 이 코드를 입력하여 본인 확인
supabase Realtime
데이터베이스의 변경 사항을 실시간으로 클라이언트에 전달하는 기능을 제공
broadcast
모든 사용자에게 동일한 데이터를 전송하는 방식
presence
현재 연결된 사용자를 실시간으로 추적하는 방식
postgres changes
데이터베이스에서 발생하는 변경 사항을 실시간으로 추적하는 방식
supabase RLS
Row Level Security
데이터베이스 테이블에 대해 구체적으로 접근 권한을 설정할 수 있게 해줌
미션
채팅 메시지 삭제
사용자가 특정 채팅 메시지를 삭제
다른 사용자가 작성한 메시지는 삭제 불가능
메시지 클릭 시 모달 화면을 띄어 삭제 여부 결정
삭제된 메시지 대신 "이 메시지는 삭제되었습니다" 같은 알림 표시
메시지 삭제 기능은 message 테이블의
is_deleted
컬럼 값을 이용하기로 했다.is_deleted
가 true 이면 삭제된 메시지고, false면 삭제되지 않은 메시지다.구현 전 생각했던 방식은
is_deleted
값을 변경하는 action 함수를 만들고, 특정 메시지를 클릭 시 모달 창을 띄어 해당 메시지를의 삭제 여부를 묻는다. 삭제한다고 하면 action 함수를 호출해is_deleted
값을 변경한다.
또한 상대방 채팅에서도 실시간으로 변경하기 위해 channel에서 update event도 구독한다.
/actions/chat-actions.ts
export async function deleteMessage({ message }) {
const supabase = await createServerSupabaseClient();
const {
data: { session },
error,
} = await supabase.auth.getSession();
if (error || !session.user) {
throw new Error("User is not authenticated");
}
const { error: updateError } = await supabase
.from("message")
.update({
...message,
is_deleted: true,
})
.eq("id", message.id);
if (updateError) {
throw new Error("Message error");
}
}
export async function getMessage(id: string) {
if (!id) {
return null;
}
const supabase = await createServerSupabaseClient();
const { data, error } = await supabase
.from("message")
.select("*")
.eq("id", Number(id))
.maybeSingle();
if (error) {
return null;
}
return data;
}
deleteMessage: 메시지 객체를 받아 is_deleted 컬럼의 값을 true 변경
getMessage: 모달 창에서 이용할 action message의 id를 받아 해당 id로 message 테이블을 쿼리해 메시지를 반환
/components/chat/chat-delete-button.tsx
"use client";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { deleteMessage } from "actions/chat-actions";
import { useRouter } from "next/navigation";
import { useRecoilValue } from "recoil";
import { selectedUserIdState } from "utils/recoil/atoms";
export default function ChatDeleteButton({ message }) {
const queryClient = useQueryClient();
const selectedUserId = useRecoilValue(selectedUserIdState);
const deleteMessageMutaition = useMutation({
mutationFn: () => deleteMessage({ message }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["messages", selectedUserId] });
},
});
const router = useRouter();
return (
<div className="flex flex-col gap-1 font-bold bg-white border-2 px-6 pt-8 py-4 rounded-md">
<p>
message : <span className="underline">{message.message}</span>
</p>
<p>Are you sure you want to delete this message?</p>
<div className="flex gap-2 justify-center py-4">
<button
onClick={() => {
deleteMessageMutaition.mutate();
router.back();
}}
className="w-14 py-1 px-2 bg-red-100 rounded-md border-2"
>
yes
</button>
<button
onClick={() => router.back()}
className="w-14 py-1 px-2 bg-blue-100 rounded-md border-2"
>
no
</button>
</div>
</div>
);
}
모달 창에서 렌더링할 컴포넌트 yes 버튼은 deleteMessageMutation.mutate()를 호출해 deleteMessage action을 호출한다. yes, no 버튼 마지막에 router.back()을 호출해 모달창을 종료한다.
/components/chat/chat-screen.tsx
useEffect(() => {
const channel = supabase
.channel("message_postgres_changes")
.on(
"postgres_changes",
{ event: "INSERT", schema: "public", table: "message" },
(payload) => {
if (
payload.eventType === "INSERT" &&
!payload.errors &&
!!payload.new
) {
getAllMessagesQuery.refetch();
}
}
)
.on(
"postgres_changes",
{ event: "UPDATE", schema: "public", table: "message" },
(payload) => {
if (payload.eventType === "UPDATE" && !payload.errors) {
getAllMessagesQuery.refetch();
}
}
)
.subscribe();
return () => {
channel.unsubscribe();
};
}, []);
chat-screen.tsx 컴포넌트에 supabase.channel 구독에서 { event: 'UPDATE' }를 추가함으로 message 테이블에 변경사항이 있으면 모든 메시지를 리패치하도록 함
댓글을 작성해보세요.