Backend ๐Ÿ“š/Node.js

[Node]Instagram Clone - 5. multer์™€ AWS S3๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒŒ์‹œ๋ฌผ ์ˆ˜์ •, ์‚ญ์ œ API

leejaejae 2024. 11. 2. 17:13

1. ๊ฒŒ์‹œ๋ฌผ ์ˆ˜์ •

  • ๊ฒŒ์‹œ๋ฌผ ๋ณธ๋ฌธ์„ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜, ์ฒจ๋ถ€๋œ ์ด๋ฏธ์ง€ ์‚ญ์ œ.

1) ์ฝ”๋“œ

const express = require("express");
const router = express.Router();
router.use(express.json());

const { Post } = require("../../models/Post");
const { auth } = require("../auth");
const cookieParser = require("cookie-parser");
router.use(cookieParser());

const multer = require("multer");
const storage = multer.memoryStorage();
const upload = multer({ storage }).array("images", 10); // ์ตœ๋Œ€ 10์žฅ์˜ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ
const s3 = require("../../config/s3"); // S3 ํด๋ผ์ด์–ธํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ
const { DeleteObjectCommand } = require("@aws-sdk/client-s3");

// ๊ฒŒ์‹œ๋ฌผ ์ˆ˜์ •
router.patch("/:id", auth, upload, async (req, res) => {
  const { id } = req.params;
  const { text, imagesToDelete } = req.body; // ์ˆ˜์ •ํ•  ํ…์ŠคํŠธ, ์‚ญ์ œํ•  ์ด๋ฏธ์ง€

  try {
    const post = await Post.findById(id);

    if (!post)
      return res.status(404).json({ message: "๊ฒŒ์‹œ๋ฌผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." });
    if (post.user.toString() !== req.user._id.toString())
      return res.status(403).json({ message: "๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค." });

    // ๋ณธ๋ฌธ ๋‚ด์šฉ ์ˆ˜์ •
    if (text) {
      post.text = text;
    }

    // ์ด๋ฏธ์ง€ ์‚ญ์ œ ๋กœ์ง
    if (imagesToDelete && post.images.length > 1) {
      for (const imageUrl of imagesToDelete) {
        // S3์—์„œ ์ด๋ฏธ์ง€ ์‚ญ์ œ
        const key = imageUrl.split("/").pop(); // URL์—์„œ ํŒŒ์ผ ์ด๋ฆ„ ์ถ”์ถœ
        const bucket = "post-jae";

        console.log("Deleting image:", imageUrl);
        console.log("Key for S3:", key);

        const deleteParams = {
          Bucket: bucket,
          Key: key,
        };

        await s3.send(new DeleteObjectCommand(deleteParams));

        // DB์—์„œ ํ•ด๋‹น ์ด๋ฏธ์ง€ URL ์‚ญ์ œ
        post.images = post.images.filter((img) => img !== imageUrl);
      }
    } else if (imagesToDelete && post.images.length <= 1) {
      return res
        .status(400)
        .json({ message: "์ด๋ฏธ์ง€๊ฐ€ 1์žฅ ์ด์ƒ ๋‚จ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค." });
    }

    await post.save(); // ์ˆ˜์ •๋œ ๊ฒŒ์‹œ๋ฌผ ์ €์žฅ

    return res.status(200).json({ message: "๊ฒŒ์‹œ๋ฌผ์ด ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", post });
  } catch (err) {
    return res.status(500).json({
      message: "๊ฒŒ์‹œ๋ฌผ ์ˆ˜์ • ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
      error: err.message,
    });
  }
});

module.exports = router;


2) ๊ธฐ๋Šฅ ์„ค๋ช…

  • ๋ผ์šฐํŠธ ์„ค์ •router.patch("/:id", auth, upload, async (req, res) => {...})๋Š” ํŠน์ • ๊ฒŒ์‹œ๋ฌผ์„ ์ˆ˜์ •ํ•˜๊ธฐ ์œ„ํ•œ API ์—”๋“œํฌ์ธํŠธ. 
    • :id๋Š” ์ˆ˜์ •ํ•  ๊ฒŒ์‹œ๋ฌผ์˜ ID๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
  • ๊ฒŒ์‹œ๋ฌผ ๊ฒ€์ƒ‰const post = await Post.findById(id);๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ MongoDB์—์„œ ํ•ด๋‹น ID์˜ ๊ฒŒ์‹œ๋ฌผ์„ ๊ฒ€์ƒ‰. 
    • ๊ฒŒ์‹œ๋ฌผ์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ 404 ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜.
  • ์‚ฌ์šฉ์ž ๊ถŒํ•œ ํ™•์ธ: ์‚ฌ์šฉ์ž๊ฐ€ ํ•ด๋‹น ๊ฒŒ์‹œ๋ฌผ์˜ ์ž‘์„ฑ์ž์ธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด, if (post.user.toString() !== req.user._id.toString())๋ฅผ ์‚ฌ์šฉ. 
    • ๊ถŒํ•œ์ด ์—†์œผ๋ฉด 403 ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜.
  • ๋ณธ๋ฌธ ๋‚ด์šฉ ์ˆ˜์ •: ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์ƒˆ๋กœ์šด ํ…์ŠคํŠธ๊ฐ€ ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ post.text = text;๋ฅผ ํ†ตํ•ด ๊ฒŒ์‹œ๋ฌผ์˜ ๋ณธ๋ฌธ ๋‚ด์šฉ์„ ์ˆ˜์ •.
  • ์ด๋ฏธ์ง€ ์‚ญ์ œ ๋กœ์ง: if (imagesToDelete && post.images.length > 1) ์กฐ๊ฑด๋ฌธ์„ ํ†ตํ•ด ์‚ญ์ œํ•  ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๊ณ , ๊ฒŒ์‹œ๋ฌผ์— ๋‚จ์•„ ์žˆ๋Š” ์ด๋ฏธ์ง€๊ฐ€ 1์žฅ ์ด์ƒ์ผ ๊ฒฝ์šฐ์—๋งŒ ์‚ญ์ œ๋ฅผ ์ง„ํ–‰. 
    • ์‚ญ์ œํ•  ์ด๋ฏธ์ง€๋Š” S3์—์„œ ์‚ญ์ œ๋˜๊ณ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ๋„ ํ•ด๋‹น ์ด๋ฏธ์ง€ URL์ด ์ œ๊ฑฐ๋จ.
  • ์‘๋‹ต ์ฒ˜๋ฆฌ: ์ˆ˜์ •์ด ์™„๋ฃŒ๋˜๋ฉด 200 ์ƒํƒœ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์ˆ˜์ •๋œ ๊ฒŒ์‹œ๋ฌผ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜. 
    • ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ 500 ์ƒํƒœ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜.

 

2. ๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ

  • ์‚ฌ์šฉ์ž๊ฐ€ ๊ฒŒ์‹œ๋ฌผ์„ ์‚ญ์ œํ•  ๋•Œ, ํ•ด๋‹น ๊ฒŒ์‹œ๋ฌผ์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ์‚ฌ์šฉ์ž ๊ถŒํ•œ์„ ์ฒดํฌํ•œ ํ›„ S3์— ์ €์žฅ๋œ ์ด๋ฏธ์ง€๋„ ํ•จ๊ป˜ ์‚ญ์ œ.

1) ์ฝ”๋“œ

const express = require("express");
const router = express.Router();
router.use(express.json());

const { Post } = require("../../models/Post");
const { auth } = require("../auth");
const cookieParser = require("cookie-parser");
router.use(cookieParser());

const { DeleteObjectCommand } = require("@aws-sdk/client-s3");
const s3 = require("../../config/s3");

// ๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ
router.delete("/:id", auth, async (req, res) => {
  const { id } = req.params;

  try {
    const post = await Post.findById(id);

    if (!post)
      return res.status(404).json({ message: "๊ฒŒ์‹œ๋ฌผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." });
    if (post.user.toString() !== req.user._id.toString())
      return res.status(403).json({ message: "๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค." });

    // S3์—์„œ ์ด๋ฏธ์ง€ ์‚ญ์ œ
    for (let imageUrl of post.images) {
      const key = imageUrl.split(".com/")[1]; // URL์—์„œ ํ‚ค ์ถ”์ถœ
      const deleteParams = {
        Bucket: "post-jae",
        Key: key,
      };

      await s3.send(new DeleteObjectCommand(deleteParams)); // S3์—์„œ ์ด๋ฏธ์ง€ ์‚ญ์ œ
    }

    await Post.findByIdAndDelete(id); // DB์—์„œ ๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ

    return res.status(200).json({ message: "๊ฒŒ์‹œ๋ฌผ์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." });
  } catch (err) {
    return res.status(500).json({
      message: "๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
      error: err.message,
    });
  }
});

module.exports = router;


2) ๊ธฐ๋Šฅ ์„ค๋ช…

  • ๋ผ์šฐํŠธ ์„ค์ •router.delete("/:id", auth, async (req, res) => {...})๋Š” ํŠน์ • ๊ฒŒ์‹œ๋ฌผ์„ ์‚ญ์ œํ•˜๊ธฐ ์œ„ํ•œ API ์—”๋“œํฌ์ธํŠธ์ž„.
    • :id๋Š” ์‚ญ์ œํ•  ๊ฒŒ์‹œ๋ฌผ์˜ ID.
  • ๊ฒŒ์‹œ๋ฌผ ๊ฒ€์ƒ‰const post = await Post.findById(id);๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ MongoDB์—์„œ ํ•ด๋‹น ID์˜ ๊ฒŒ์‹œ๋ฌผ์„ ๊ฒ€์ƒ‰.
    • ๊ฒŒ์‹œ๋ฌผ์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ 404 ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜.
  • ์‚ฌ์šฉ์ž ๊ถŒํ•œ ํ™•์ธ: ์‚ฌ์šฉ์ž๊ฐ€ ํ•ด๋‹น ๊ฒŒ์‹œ๋ฌผ์˜ ์ž‘์„ฑ์ž์ธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด, if (post.user.toString() !== req.user._id.toString())๋ฅผ ์‚ฌ์šฉ.
    • ๊ถŒํ•œ์ด ์—†์œผ๋ฉด 403 ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜.
  • S3์—์„œ ์ด๋ฏธ์ง€ ์‚ญ์ œ: ๊ฒŒ์‹œ๋ฌผ์ด ์กด์žฌํ•˜๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ ๊ถŒํ•œ์ด ํ™•์ธ๋˜๋ฉด, for (let imageUrl of post.images) {...}๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒŒ์‹œ๋ฌผ์— ์ฒจ๋ถ€๋œ ๋ชจ๋“  ์ด๋ฏธ์ง€ URL์„ ๋ฐ˜๋ณตํ•˜๋ฉฐ S3์—์„œ ์‚ญ์ œ.
    • ์ด๋ฏธ์ง€ ํ‚ค๋Š” URL์—์„œ ์ถ”์ถœํ•˜์—ฌ DeleteObjectCommand๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ญ์ œ.
  • ๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ: ๋ชจ๋“  ์ด๋ฏธ์ง€๊ฐ€ ์‚ญ์ œ๋œ ํ›„ await Post.findByIdAndDelete(id);๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ๋„ ๊ฒŒ์‹œ๋ฌผ์„ ์‚ญ์ œ.
  • ์‘๋‹ต ์ฒ˜๋ฆฌ: ์‚ญ์ œ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด 200 ์ƒํƒœ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์„ฑ๊ณต ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜.
    • ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ 500 ์ƒํƒœ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜.

 

3. ๊ฒฐ๊ณผ ๐ŸŽ‰


1) ๊ฒŒ์‹œ๋ฌผ ์ˆ˜์ •

  • ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ •

  • ์‚ฌ์ง„ ์‚ญ์ œ

  • ๊ฒŒ์‹œ๋ฌผ์— ๋‚จ์•„ ์žˆ๋Š” ์ด๋ฏธ์ง€๊ฐ€ 1์žฅ ์ด์ƒ์ผ ๊ฒฝ์šฐ์—๋งŒ ์‚ญ์ œ๋ฅผ ์ง„ํ–‰ 


2) ๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ