프로그래머스 데브코스TIL북 스토어 프로젝트

[week8] 프로젝트 : Node.js 기반의 Rest API 구현 (14)

이규현2026-03-05
[week8] 프로젝트 : Node.js 기반의 Rest API 구현 (14)

북 스토어 프로젝트 마무리하기

내 장바구니 조회 수정

selected 했을 때와 안했을때의 차이를 구분지었습니다.

  • selected 했을 때 : 그 책만 조회
  • 안했을 떄 : 사용자가 담은 장바구니 전체 조회
export const getCartItems = (req, res) => {
  try {
    const { selected } = req.body || {};
    const auth = Authorization(req);

    let sql = `
      SELECT 
          cart.cart_id, 
          cart.book_id, 
          books.title, 
          books.summary, 
          cart.quantity, 
          books.price 
      FROM cart 
      LEFT JOIN books ON cart.book_id = books.book_id
      WHERE cart.user_id = ?`;

    let values = [auth.id];

    if (selected && selected.length > 0) {
      sql += ` AND cart.cart_id IN (?)`;
      values.push(selected);
    }

    conn.query(sql, values, (err, results) => {
      if (err) {
        console.error('장바구니 조회 DB 에러:', err);
        return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
          message: '장바구니 조회에 실패했습니다.',
          error: err.message,
        });
      }
      return res.status(StatusCodes.OK).json({
        message: '장바구니 조회 성공!',
        data: results,
        count: results.length,
      });
    });
  } catch (error) {
    console.error('장바구니 조회 에러:', error);
    const { status, message, code } = handleAuthError(error);
    return res.status(status).json({ message, code });
  }
};

주문 API 수정

order (주문하기)

export const order = async (req, res) => {
  try {
    const promiseConn = conn.promise();
    const auth = Authorization(req);

    const { items, delivery, totalQuantity, totalPrice, firstBookTitle } = req.body;

    let sql = `INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)`;
    let values = [delivery.address, delivery.receiver, delivery.contact];
    let [results] = await promiseConn.execute(sql, values);
    const delivery_id = results.insertId;

    sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id) 
           VALUES (?, ?, ?, ?, ?)`;
    values = [firstBookTitle, totalQuantity, totalPrice, auth.id, delivery_id];
    [results] = await promiseConn.execute(sql, values);
    const order_id = results.insertId;

    sql = `INSERT INTO orderedBook (order_id, book_id, quantity) VALUES ?`;
    const itemValues = items.map((item) => [order_id, item.book_id, item.quantity]);
    await promiseConn.query(sql, [itemValues]);

    const cartIds = items.map((item) => item.cart_id);
    await deleteCartItems(promiseConn, cartIds);

    return res
      .status(StatusCodes.CREATED)
      .json({ message: '주문 완료 및 장바구니가 비워졌습니다.', order_id });
  } catch (error) {
    console.error('주문 처리 중 에러:', error);

    if (error.name === 'TokenExpiredError' || error.name === 'JsonWebTokenError') {
      const { status, message, code } = handleAuthError(error);
      return res.status(status).json({ message, code });
    }

    return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
      message: '주문 처리 중 에러가 발생했습니다.',
      error: error.message,
    });
  }
};

export const deleteCartItems = async (promiseConn, items) => {
  const sql = `DELETE FROM cart WHERE cart_id IN (?)`;
  const [result] = await promiseConn.query(sql, [items]);
  return result;
};

getOrder (주문 내역 조회)

export const getOrders = async (req, res) => {
  try {
    const promiseConn = conn.promise();
    const auth = Authorization(req);

    const sql = `
      SELECT 
        orders.order_id, 
        orders.book_title, 
        orders.total_quantity, 
        orders.total_price, 
        orders.created_at, 
        delivery.address, 
        delivery.receiver, 
        delivery.contact 
      FROM orders 
      LEFT JOIN delivery ON orders.delivery_id = delivery.delivery_id
      WHERE orders.user_id = ?
    `;

    const [results] = await promiseConn.execute(sql, [auth.id]);

    return res.status(StatusCodes.OK).json(results);
  } catch (error) {
    console.error('주문 목록 조회 중 에러:', error);

    if (error.name === 'TokenExpiredError' || error.name === 'JsonWebTokenError') {
      const { status, message, code } = handleAuthError(error);
      return res.status(status).json({ message, code });
    }

    return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
      message: '주문 목록 조회 중 에러가 발생했습니다.',
      error: error.message,
    });
  }
};

getOrderDetail (주문 상세 조회)

export const getOrderDetail = async (req, res) => {
  try {
    const { id } = req.params;
    const auth = Authorization(req);
    const promiseConn = conn.promise();

    const sql = `
      SELECT 
        orderedBook.book_id, 
        books.title AS book_title, 
        books.author, 
        books.price, 
        orderedBook.quantity 
      FROM orderedBook
      LEFT JOIN books ON orderedBook.book_id = books.book_id
      WHERE orderedBook.order_id = ? AND orderedBook.order_id IN (
        SELECT order_id FROM orders WHERE user_id = ?
      )
    `;

    const [results] = await promiseConn.execute(sql, [id, auth.id]);

    if (results.length === 0) {
      return res.status(StatusCodes.NOT_FOUND).json({
        message: '해당 주문의 상세 내역을 찾을 수 없습니다.',
      });
    }

    return res.status(StatusCodes.OK).json(results);
  } catch (error) {
    console.error('주문 상세 조회 중 에러:', error);

    if (error.name === 'TokenExpiredError' || error.name === 'JsonWebTokenError') {
      const { status, message, code } = handleAuthError(error);
      return res.status(status).json({ message, code });
    }

    return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
      message: '주문 상세 조회 중 에러가 발생했습니다.',
      error: error.message,
    });
  }
};

전체 도서 조회 수정

export const getAllbooks = (req, res) => {
  let { category_id, news, limit, currentPage } = req.query;

  let auth = null;
  try {
    auth = Authorization(req);
  } catch (error) {
    auth = null;
  }

  let sql = `SELECT SQL_CALC_FOUND_ROWS *`;

  if (auth) {
    sql += `, (SELECT count(*) FROM likes WHERE liked_book_id = books.book_id) AS likes`;
  }

  sql += ` FROM books`;
  let values = [];

  if (category_id && news) {
    sql +=
      ' WHERE category_id = ? AND pub_date BETWEEN DATE_SUB(NOW(), INTERVAL 1 MONTH) AND NOW()';
    values.push(category_id);
  } else if (category_id) {
    sql += ' WHERE category_id = ?';
    values.push(category_id);
  } else if (news) {
    sql += ' WHERE pub_date BETWEEN DATE_SUB(NOW(), INTERVAL 1 MONTH) AND NOW()';
  }

  limit = limit || 10;
  currentPage = currentPage || 1;
  let parsedLimit = parseInt(limit);
  let parsedCurrentPage = parseInt(currentPage);
  let offset = (parsedCurrentPage - 1) * parsedLimit;

  sql += ' LIMIT ? OFFSET ?';
  values.push(parsedLimit, offset);

  conn.query(sql, values, (err, results) => {
    if (err) {
      console.error('도서 조회 DB 에러:', err);
      return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(err);
    }

    conn.query('SELECT FOUND_ROWS() AS total', (err, foundRows) => {
      if (err) {
        console.error('도서 총 개수 조회 DB 에러:', err);
        return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(err);
      }

      const totalCount = foundRows[0].total;
      const totalPages = Math.ceil(totalCount / parsedLimit);

      return res.status(StatusCodes.OK).json({
        data: results,
        pagination: {
          currentPage: parsedCurrentPage,
          limit: parsedLimit,
          totalCount,
          totalPages,
        },
      });
    });
  });
};