HJW's IT Blog

조각집 프로젝트 기록 - 03 본문

카테고리 없음

조각집 프로젝트 기록 - 03

kiki1875 2024. 9. 24. 12:32

24일 기록

group 내용 수정 관련 API 를 만들었다. DTO를 사용하여 데이터를 다루었는데, DTO 의 이점을 확실하게 알고 쓰는게 좋을 것 같다.

  • Data Encapsulation
  • Type Safety
  • 데이터 구조 통일성

id 를 통해 group 정보를 받아와야 하는데, 향후 또 사용될 일이 있을것 같아 findGroupById 함수를 별도의 파일에 분리하여 작성하였다.

// router
router.put('/groups/:GID', async (req, res) => {
  const groupId = parseInt(req.params.GID, 10);
  const { name, password, imageUrl, isPublic, introduction} = req.body;

  try{
    const updateGroupDto = new UpdateGroupDto(name, isPublic, password, imageUrl, introduction);
    const updatedGroup = await updateGroup({groupId, updateDto: updateGroupDto, inputPassword : password});
    
    res.status(200).json({
      id : updatedGroup.GID,
      name : updatedGroup.GName,
      imageUrl : updatedGroup.GImage,
      isPublic : updatedGroup.IsPublic,
      likeCount : updatedGroup.GLikes,
      badges : updatedGroup.GBadgeCount,
      postCount : updatedGroup.PostCount,
      createdAt : updatedGroup.CreatedDate,
      introduction : updatedGroup.GIntro
    });
    
  }catch(error){
    const typedError = error as { status : number; message: string};
    res.status(typedError.status || 500).json({message : typedError.message});
  }

});
// updateGroup Controller
import { UpdateGroupDto } from "../DTO/createGroupDTO";
import { findGroupById, updateGroupInfo } from "../service/groupService";

interface ModifyGroupParams{
  groupId : number;
  updateDto : UpdateGroupDto;
  inputPassword : string;
}

export async function updateGroup({groupId, updateDto, inputPassword} : ModifyGroupParams){

  const group = await findGroupById(groupId);

  if(!group){
    throw { status: 404, message: '존재하지 않는 그룹입니다'};
  }

  if(group.GPassword !== inputPassword){
    throw {status: 403, message : '틀린 비밀번호 입니다.'};
  }

  if(updateDto.GName) group.GName = updateDto.GName;
  if(updateDto.GImage) group.GImage = updateDto.GImage;
  if(updateDto.GIntro) group.GIntro = updateDto.GIntro;
  if(updateDto.IsPublic !== undefined) group.IsPublic = updateDto.IsPublic;

  const updatedGroup = await updateGroupInfo(groupId, group);

  return updatedGroup;
}
// find by id, update group

import { PrismaClient } from ".prisma/client";

const prisma = new PrismaClient();

// id 로 그룹 조회
export const findGroupById = async (groupId : number) => {
  const group = await prisma.group.findUnique({
    where: {GID : groupId},
  });

  return group;
}

export const updateGroupInfo = async (groupId : number, group : any) => {
  
  const updatedGroup = await prisma.group.update({
    where: {GID : groupId},
    data : group,
  })

  return updatedGroup;
}

router 의 catch 문 내에서 res.status(error) 를 하니 에러가 발생했다. 이는 typescript 가 기본적으로 try-catch 문 내에서 발생한 오류를 unknown type 으로 취급하기 때문이다. 구글링을 통해 알아본 결과, 해당 오류를 해결하는 두가지 방법이 있었다.

  • Type-Assertion 을 통해 error 객체가 status 와 message 필드를 가진다고 개발자가 명시해주는 방법
  • Type-Guard 를 통해 error 객체가 특정 속성을 가지고 있는 지 확인 후 처리하는 방법

필자는 Type Assertion 을 통해 해당 에러 객체가 status 및 message 필드를 가진다고 명시하였다.

 

또한 처음에 다음과 같이 updateGroup() 함수에 값을 넘겨주려 하니 에러가 발생했다.

const updateGroupDto = new UpdateGroupDto(name, isPublic, password, imageUrl, introduction);
const updatedGroup = await updateGroup({groupId, updateGroupDto, password});

이는 JS 와 TS 에게 객체를 함수로 전달할 때, 객체 구조분해 할당 을 사용하기 때문이다. 이 방식은 함수의 매개변수로 전달된 객체에서 특정 필드 이름을 추출해 사용하기 때문에, 필드 명이 다르다면 해당 값을 추출할 수 없다.

  • DTO 이점
const updateGroupDto = new UpdateGroupDto(name, isPublic, password, imageUrl, introduction);
const updatedGroup = await updateGroup({groupId, updateDto: updateGroupDto, inputPassword : password});

 

 

group 삭제 api 를 작성했다.

router.delete('/groups/:GID', async (req,res) => {
  
  const groupID = parseInt(req.params.GID,10);
  const { password } = req.body;

  try{
    await deleteGroup(groupID, password);
    res.status(200).json({message : "성공적으로 삭제되었습니다."})
  }catch(error){
    const typedError = error as {status : number; message : string};
    res.status(typedError.status).json({message : typedError.message});
  }
});
  • groupID 와 password 을 파라미터로 받아 deleteGroup 함수를 실행시킨다
export async function deleteGroup(groupID: number, password : string){
  const group = await findGroupById(groupID);

  if(!group){
    throw { status : 404 , message : "존재하지 않는 그룹입니다."};
  }
  if(password !== group.GPassword){
    throw {status : 403, message : "틀린 비밀번호 입니다."};
  }

  deleteGroupById(groupID);
}
  • deleteGroup 은 간단한 유효성 검사 이후, deleteGroupById에 인자를 전달한다.
export const deleteGroupById = async (groupId: number) => {
  return await prisma.group.delete({
    where: {GID : groupId},
  });
};

지금 프로젝트 구조를 보면 그룹관련 컨트롤러들이 모두 다른 파일에 작성되어 있다.

하지만 향후 유지보수성을 위해 하나의 파일에 모두 묶어 관리하는 것이 나을것 같다는 생각이 든다. 프로젝트 규모가 크고, 만들어야 할 api 가 많다면 따로 구현하는 것도 좋겠지만 해당 프로젝트는 규모가 작기 때문에 리팩토링을 통해 모두 합쳐주었다.

 

 

그룹 상세 정보 라우터를 만들었다.

그룹 상세 정보는 해당 그룹이 얻은 배지 목록또한 있어야 하기 때문에, badgeService 를 만들어, 그룹 id 를 인자로 받아 해당 그룹이 가지고 있는 배지 목록을 반환하는 함수를 구현했다.

export const findGroupBadge = async (groupId : number): Promise<string[]> => {
  const badges = await prisma.groupBadge.findMany({
    where: {
      GID: groupId,
    },
    include:{
      badge: true,
    },
  });
  return badges.map((groupBadge) => groupBadge.badge.Name);
}