[23.12.11] TIL

yy·2023년 12월 12일

개발일지

목록 보기
58/122

지난주부터의 구현하려고 했던 게 있었는데 바로 별점 등록 및 조회 부분이다. 구현하기 전에 잠깐 생각해봤을때는 쉽게 구현될 줄 알았으나 생각보다 복잡하다는걸 시작하고서야 알았다. 게시글 작성하면서 별점을 등록하게 되는데 그 때 같이 locations에 있는 starAvg를 업데이트 해주면 되는 일이었다.
처음 생각은 조회할 때 평균을 조회해서 보여줄수는 없을까? 라는 생각을 했다. 근데 문제는 각 게시글을 조회하면서 집계함수를 사용할 수 없다는 점이었다.
뭔말이냐면 posts를 findMany로 조회를 할 때 select를 사용해서 필요한 컬럼을 조회(star: true 형식)를 하는데 그 안에 _avg를 사용해서 뭘 나타낼 수가 없었다.
그래서 결국 게시글 작성할 때 넣는걸로 해서 코드를 만들었다.

/* 게시물 작성 */
//auth.middleware 추가로 넣기
router.post(
  "/posts",
  // authMiddleware,
  upload.array("imgUrl", 5),
  async (req, res, next) => {
    try {
      const validation = await createPosts.validateAsync(req.body);
      const {
        content,
        likeCount,
        categoryName,
        storeName,
        address,
        latitude,
        longitude,
        star,
      } = validation;
      // const { userId } = req.user; //auth.middleware 넣으면 주석 해제하기
      const userId = 6;

      const user = await prisma.users.findFirst({
        where: { userId },
      });

      if (!user) {
        return res.status(400).json({ message: "유저가 존재하지 않습니다." });
      }

      const category = await prisma.categories.findFirst({
        where: { categoryName },
      });

      if (!category) {
        return res
          .status(400)
          .json({ message: "카테고리가 존재하지 않습니다." });
      }

      const districtName = address.split(" ")[1];

      const district = await prisma.districts.findFirst({
        where: { districtName },
      });

      if (!district) {
        return res.status(400).json({ message: "지역이 존재하지 않습니다." });
      }

      // 같은 장소에 한 사람이 여러 개의 포스팅 올리지 않도록 하기
      const findPosts = await prisma.posts.findFirst({
        where: {
          UserId: userId,
          Location: {
            is: {
              address,
            },
          },
        },
      });

      if (findPosts) {
        return res.status(400).json({
          message: "이미 같은 장소에 대한 유저의 포스팅이 존재합니다.",
        });
      }

      //이미지 이름 나눠서 저장
      const imgPromises = req.files.map(async (file) => {
        const imgName = randomImgName();

        const params = {
          Bucket: bucketName,
          Key: imgName,
          Body: file.buffer,
          ContentType: file.mimetype,
        };
        const command = new PutObjectCommand(params);
        await s3.send(command);

        return imgName;
      });

      const imgNames = await Promise.all(imgPromises);

      const location = await prisma.locations.findFirst({
        where: { address }
      });

      //location 정보가 기존 X => location랑 posts 생성.
      if (!location) {
        const createLocation = await prisma.locations.create({
          data: {
            storeName,
            address,
            latitude,
            longitude,
            starAvg: 0,
            Category: { connect: { categoryId: +category.categoryId } },
            District: { connect: { districtId: +district.districtId } },
            User: { connect: { userId: +user.userId } },
          },
        });

        await prisma.posts.create({
          data: {
            content,
            likeCount: +likeCount,
            star,
            User: { connect: { userId: +user.userId } },
            Category: { connect: { categoryId: +category.categoryId } },
            Location: { connect: { locationId: +createLocation.locationId } },
            imgUrl: imgNames.join(","),
          },
        });

      }
      //location 정보가 기존 O => location 업데이트, posts 생성
      await prisma.$transaction(async (prisma) => {
        await prisma.posts.create({
          data: {
            content,
            likeCount: +likeCount,
            star,
            User: { connect: { userId: +user.userId } },
            Category: { connect: { categoryId: +category.categoryId } },
            Location: { connect: { locationId: +location.locationId } },
            imgUrl: imgNames.join(","),
          },
        });

        const starsAvg = await prisma.posts.aggregate({
          where: { LocationId: location.locationId },
          _avg: {
            star: true
          }
        });

        await prisma.locations.update({
          where: {
            locationId: location.locationId,
          },
          data: {
            starAvg: starsAvg._avg.star
          }
        })
      })

      return res.status(200).json({ message: "게시글 등록이 완료되었습니다." });
    } catch (error) {
      throw new Error("트랜잭션 실패");
      next(error);
    }
  },
);

코드를 완성하고 보니 생각보다 왜이렇게 쉬운걸 그렇게까지 고민했을까? 라는 생각을 했다.
우선 authmiddleware는 제쳐두고, userId 값을 임의로 넣어줬다.(미리 회원가입과 로그인을 통해 인증을 진행완료한 상태)

주소 기반으로 같은 사용자가 같은 장소에 여러 게시물을 올리지않도록 처리해놓는다.

      // 같은 장소에 한 사람이 여러 개의 포스팅 올리지 않도록 하기
      const findPosts = await prisma.posts.findFirst({
        where: {
          UserId: userId,
          Location: {
            is: {
              address,
            },
          },
        },
      });

if (findPosts) {
        return res.status(400).json({
          message: "이미 같은 장소에 대한 유저의 포스팅이 존재합니다.",
        });
      }

s3에 이미지를 등록한다.

//이미지 이름 나눠서 저장
      const imgPromises = req.files.map(async (file) => {
        const imgName = randomImgName();

        const params = {
          Bucket: bucketName,
          Key: imgName,
          Body: file.buffer,
          ContentType: file.mimetype,
        };
        const command = new PutObjectCommand(params);
        await s3.send(command);

        return imgName;
      });

      const imgNames = await Promise.all(imgPromises);

사용자한테 입력받은 주소를 기반으로 locations 테이블에서 data를 찾고, 만약 data가 없다면 location랑 posts 생성해준다.

const location = await prisma.locations.findFirst({
        where: { address }
      });

      //location 정보가 기존 X => location랑 posts 생성.
      if (!location) {
        const createLocation = await prisma.locations.create({
          data: {
            storeName,
            address,
            latitude,
            longitude,
            starAvg: 0,
            Category: { connect: { categoryId: +category.categoryId } },
            District: { connect: { districtId: +district.districtId } },
            User: { connect: { userId: +user.userId } },
          },
        });

        await prisma.posts.create({
          data: {
            content,
            likeCount: +likeCount,
            star,
            User: { connect: { userId: +user.userId } },
            Category: { connect: { categoryId: +category.categoryId } },
            Location: { connect: { locationId: +createLocation.locationId } },
            imgUrl: imgNames.join(","),
          },
        });

      }

만약 해당하는 장소가 있다면 posts만 넣어준다. 그리고 별점 평균을 aggregate를 사용하여 찾아주고, location테이블의 별점 평균을 업데이트 해준다.
둘의 작업이 동시에 일어나야하는 일이므로 transaction을 이용하여 일괄처리가 되도록한다.

//location 정보가 기존 O => location 업데이트, posts 생성
      await prisma.$transaction(async (prisma) => {
        await prisma.posts.create({
          data: {
            content,
            likeCount: +likeCount,
            star,
            User: { connect: { userId: +user.userId } },
            Category: { connect: { categoryId: +category.categoryId } },
            Location: { connect: { locationId: +location.locationId } },
            imgUrl: imgNames.join(","),
          },
        });

        const starsAvg = await prisma.posts.aggregate({
          where: { LocationId: location.locationId },
          _avg: {
            star: true
          }
        });

        await prisma.locations.update({
          where: {
            locationId: location.locationId,
          },
          data: {
            starAvg: starsAvg._avg.star
          }
        })
      })

그리고 에러가 떴을때 롤백되도록 에러처리를 해준다.

throw new Error("트랜잭션 실패");

왜이렇게 어렵게 생각했던거냐면 하나의 포스팅에 대한 starAvg를 갱신해야하는거였는데 나는 여러 starAvg를 갱신해야한다고 생각했다. 아마도 조회할 때랑 헷갈려서 그런 듯.

profile
시간이 걸릴 뿐 내가 못할 건 없다.

0개의 댓글