그룹채팅 - Part6 방장의 권한(1) - 채팅방 수정 / 삭제

Project Diary/Next.js + Prisma + MariaDB (KiloFlow)

방장이 채팅방을 수정하고 삭제하는 기능을 구현하는 방법에 대해 설명하겠습니다. 방장은 채팅방의 이름, 이미지, 태그 등을 수정할 수 있으며,  채팅방을 삭제할 수도 있습니다.


 

프론트엔드

 

1. 채팅방 정보 불러오기

채팅방 정보를 불러와서 수정 폼에 초기값으로 설정합니다.

useEffect(() => {
  const fetchChatroomInfo = async () => {
    // 채팅방 정보를 가져오는 API 호출
    const res = await fetch(`/api/community/current-chatroom-info?roomId=${roomId}&action=info`);
    const data = await res.json();
    // 가져온 데이터를 상태에 설정
    setChatroomInfo(data);
    setName(data.name);
    setHashtags(data.tags.split(" "));
    setMaxMembers(data.max_members);
    setImagePreview(data.image_url || communityThumb.src);
  };

  if (roomId) {
    fetchChatroomInfo(); // roomId가 존재할 때만 함수 호출
  }
}, [roomId]);

(설명 : 주석참고)

 

2. 이미지 변경 핸들러

const handleImageChange = (e: ChangeEvent<HTMLInputElement>) => {
  const file = e.target.files?.[0];
  if (file) {
    setImage(file); // 이미지 파일을 상태에 저장
    setImagePreview(URL.createObjectURL(file)); // 이미지 미리보기를 위한 URL 생성
  }
};

(설명 : 주석참고)

 

3. 해시태그 관리 핸들러

const handleHashtagChange = (index: number, value: string) => {
  const newHashtags = [...hashtags]; // 현재 해시태그 목록 복사
  newHashtags[index] = value; // 특정 인덱스의 해시태그 변경
  setHashtags(newHashtags); // 변경된 해시태그 목록을 상태에 저장
};

const handleAddHashtag = () => {
  setHashtags([...hashtags, ""]); // 해시태그 목록에 빈 문자열 추가
};

const handleRemoveHashtag = (index: number) => {
  const newHashtags = [...hashtags]; // 현재 해시태그 목록 복사
  newHashtags.splice(index, 1); // 특정 인덱스의 해시태그 제거
  setHashtags(newHashtags); // 변경된 해시태그 목록을 상태에 저장
};

const handleDefaultImage = () => {
  setImage(communityThumb.src); // 기본 이미지 설정
  setImagePreview(communityThumb.src); // 이미지 미리보기 설정
};

(설명 : 주석참고)

 

4. 채팅방 수정 요청 핸들러

const handleSubmit = async (event: FormEvent) => {
  event.preventDefault();

  const formData = new FormData();
  formData.append("name", name); // 채팅방 이름 추가
  formData.append("tags", hashtags.join(" ")); // 해시태그 추가
  formData.append("max_members", maxMembers.toString()); // 최대 인원 추가
  if (image !== communityThumb.src) {
    formData.append("image", image); // 이미지가 기본 이미지가 아닐 경우에만 추가
  }

  try {
    const res = await fetch(`/api/modify/chatroom-modify?roomId=${roomId}`, {
      method: "POST",
      body: formData,
    });

    if (res.ok) {
      const data = await res.json();
      setChatroomInfo(data); // 업데이트된 채팅방 정보 상태에 저장
      window.alert("채팅방 정보가 업데이트되었습니다.");
      router.back(); // 이전 페이지로 이동
    } else {
      const data = await res.json();
      console.log("채팅방 업데이트 데이터 오류", data);
    }
  } catch (err) {
    console.log("채팅방 업데이트 오류", err);
  }
};

(설명 : 주석참고)

5. 채팅방 삭제 요청 핸들러

const handleDelete = async () => {
  if (confirm("정말로 채팅방을 삭제하시겠습니까?")) {
    const res = await fetch(`/api/community/delete?roomId=${roomId}`, {
      method: "DELETE",
    });

    if (res.ok) {
      alert("채팅방이 삭제되었습니다.");
      router.push("/community/list"); // 채팅방 목록 페이지로 이동
    }
  }
};

(설명 : 주석참고)

 


 

백엔드

 

1. 채팅방 정보 가져오기 API엔드포인트

import { NextApiRequest, NextApiResponse } from "next";
import prisma from "../../../lib/prisma";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { roomId, action } = req.query;

  if (!roomId || !action) {
    return res.status(400).json({ message: "Missing roomId or action parameter" }); // roomId 또는 action이 없을 경우 400 상태 코드 응답
  }

  if (req.method === "GET") {
    if (action === "info") {
      // 특정 채팅방 정보 가져오기
      const chatroom = await prisma.chatrooms.findUnique({
        where: { id: Number(roomId) },
      });
      if (!chatroom) {
        return res.status(404).json({ message: "Chatroom not found" }); // 채팅방이 존재하지 않을 경우 404 상태 코드 응답
      }
      return res.status(200).json(chatroom); // 채팅방 정보 응답
    } else if (action === "users") {
      // 특정 채팅방의 사용자 목록 가져오기
      const users = await prisma.chatroom_members.findMany({
        where: { chatroom_id: Number(roomId) },
        include: { user: true },
      });
      const userList = users.map((member) => member.user);
      return res.status(200).json(userList); // 사용자 목록 응답
    } else {
      return res.status(400).json({ message: "Invalid action parameter" }); // action 파라미터가 유효하지 않을 경우 400 상태 코드 응답
    }
  } else {
    res.setHeader("Allow", ["GET"]); // 허용되지 않는 메서드에 대한 응답 설정
    res.status(405).end(`Method ${req.method} Not Allowed`); // 405 상태 코드 응답
  }
}

(설명 : 주석참고)

 

2. 채팅방 정보 수정 API엔드포인트

import { NextApiRequest, NextApiResponse } from "next";
import prisma from "../../../lib/prisma";
import upload from "../../../lib/multer";

interface ExtendedRequest extends NextApiRequest {
  file: Express.Multer.File;
}

export const config = {
  api: {
    bodyParser: false, // Multer를 사용하기 위해 bodyParser 비활성화
  },
};

// 미들웨어 실행 함수
const runMiddleware = (req: NextApiRequest, res: NextApiResponse, fn: Function) => {
  return new Promise((resolve, reject) => {
    fn(req, res, (result: any) => {
      if (result instanceof Error) {
        return reject(result); // 오류 발생 시 reject 호출
      }
      return resolve(result); // 정상적으로 완료되면 resolve 호출
    });
  });
};

// 채팅방 수정 핸들러
export default async function handler(req: ExtendedRequest, res: NextApiResponse) {
  if (req.method === "POST") {
    try {
      await runMiddleware(req, res, upload.single("image")); // 이미지 업로드 미들웨어 실행

      const { roomId } = req.query;
      const { name, tags, max_members, image } = req.body;
      const imageUrl = req.file ? `/uploads/${req.file.filename}` : image; // 이미지 경로 설정

      const updatedData: any = {
        name, // 채팅방 이름
        tags, // 해시태그
        max_members: Number(max_members), // 최대 인원
      };

      if (imageUrl) {
        updatedData.image_url = imageUrl; // 이미지 URL 추가
      }

      // Prisma를 사용하여 채팅방 정보 업데이트
      const updatedChatroom = await prisma.chatrooms.update({
        where: { id: Number(roomId) },
        data: updatedData,
      });

      res.status(200).json(updatedChatroom); // 업데이트된 채팅방 정보 응답
    } catch (error) {
      console.error("Error updating chatroom:", error);
      res.status(500).json({ message: "Internal server error" }); // 오류 발생 시 응답
    }
  } else {
    res.setHeader("Allow", ["POST"]); // 허용되지 않는 메서드에 대한 응답 설정
    res.status(405).end(`Method ${req.method} Not Allowed`); // 405 상태 코드 응답
  }
}

(설명 : 주석참고)

 

3. 채팅방 삭제 API엔드포인트

import { NextApiRequest, NextApiResponse } from "next";
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { roomId } = req.query;

  if (req.method === "DELETE") {
    try {
      // Prisma 트랜잭션을 사용하여 연관된 모든 데이터를 삭제
      await prisma.$transaction([
        prisma.chatroom_members.deleteMany({
          where: { chatroom_id: Number(roomId) },
        }),
        prisma.chatMessages.deleteMany({
          where: { chatroom_id: Number(roomId) },
        }),
        prisma.chatrooms.delete({
          where: { id: Number(roomId) },
        }),
        prisma.notices.deleteMany({
          where: { chatroom_id: Number(roomId) },
        }),
      ]);
      res.status(204).end(); // 성공 시 204 상태 코드 응답
    } catch (error) {
      console.error("채팅방 삭제 에러", error);
      res.status(500).json({ error: "채팅방 삭제 중에 에러가 발생했습니다." }); // 오류 발생 시 응답
    }
  } else {
    res.setHeader("Allow", ["DELETE"]); // 허용되지 않는 메서드에 대한 응답 설정
    res.status(405).end(`Method ${req.method} Not Allowed`); // 405 상태 코드 응답
  }
}

(설명 : 주석참고)