프로그래머스 데브코스, 국비지원교육, 코딩부트캠프
장바구니 기능을 구현했다. 마찬가지로 큰 어려움은 없긴 했는데 강의에서는 구현되지 않은 예외 처리를 생각하느라 시간이 조금 걸렸다.
CartController.js
const checkExist = `select (select count(*) from users where id = ?) as user_exists, (select count(*) from books where id = ?) as book_exists`;
const checkExistValues = async (connection, values) => {
const [rows] = await connection.query(checkExist, values);
return {
user_exists: rows[0].user_exists === 1,
book_exists: rows[0].book_exists === 1,
};
};
const addToCart = async (req, res) => {
const connection = await conn.getConnection();
const { bookId, quantity, userId } = camelcaseKeys(req.body);
const sqlInsertCart = 'insert into cartItems (book_id, quantity, user_id) values (?, ?, ?)';
const sqlSelectCart = 'select * from cartItems where user_id = ? and book_id = ?';
const sqlUpdateCart = 'update cartItems set quantity = quantity + ? where user_id = ? and book_id = ?';
const values = [bookId, quantity, userId];
const existValues = [userId, bookId];
try {
const { user_exists, book_exists } = await checkExistValues(connection, existValues);
if (!user_exists) {
return res.status(StatusCodes.NOT_FOUND).json({
message: '존재하지 않는 유저입니다.',
});
}
if (!book_exists) {
return res.status(StatusCodes.NOT_FOUND).json({
message: '존재하지 않는 도서입니다.',
});
}
const [rowsSelect] = await connection.query(sqlSelectCart, existValues);
if (rowsSelect.length > 0) {
const [rowsUpdate] = await connection.query(sqlUpdateCart, [quantity, userId, bookId]);
if (rowsUpdate.affectedRows > 0) {
return res.status(StatusCodes.OK).json({
message:
'이미 장바구니에 담긴 도서입니다. 원하시는 수량만큼 장바구니에 담긴 도서의 수량이 증가하였습니다.',
});
} else {
return res.status(StatusCodes.BAD_REQUEST).json({
message: '장바구니 추가에 실패하였습니다.',
});
}
}
const [rowsInsert] = await connection.query(sqlInsertCart, values);
if (rowsInsert.affectedRows > 0) {
return res.status(StatusCodes.CREATED).json({
message: '장바구니에 도서가 추가되었습니다.',
});
} else {
return res.status(StatusCodes.BAD_REQUEST).json({
message: '장바구니 추가에 실패하였습니다.',
});
}
} catch (err) {
console.log(err);
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
message: '장바구니 추가 중 문제가 발생하였습니다.',
});
} finally {
connection.release();
}
};
처음에는 장바구니에 도서를 다시 못 담게 막아뒀는데 생각해보니까 똑같은 도서를 담지 못하는 건 말도 안되지 않은가. 그래서 같은 도서를 다시 담으려고 할 때 해당 수량만큼 기존 도서 수량에 증가하는 방식을 택했다.
CartMiddleware.js
const { param, body, validationResult } = require('express-validator');
const validate = (req, res, next) => {
const err = validationResult(req);
if (!err.isEmpty()) {
return res.status(400).json(err.array());
} else {
return next();
}
};
const validateUserId = body('user_id')
.trim()
.notEmpty()
.withMessage('유저 아이디를 입력해주세요.')
.isInt()
.withMessage('숫자로 입력해주세요.');
const validateBookId = body('book_id')
.trim()
.notEmpty()
.withMessage('도서 아이디를 입력해주세요.')
.isInt()
.withMessage('숫자로 입력해주세요.');
const validateQuantity = body('quantity')
.trim()
.notEmpty()
.withMessage('수량을 입력해주세요.')
.isInt()
.withMessage('숫자로 입력해주세요.')
.custom((value) => value > 0)
.withMessage('수량은 1 이상이어야 합니다.');
const validatesAddToCart = [validateUserId, validateBookId, validateQuantity, validate];
const validatesGetCartItems = [validateUserId, validate];
module.exports = { validatesAddToCart, validatesGetCartItems };
다른 유효성 검사와 기본적으로 비슷하기는 한데 수량 유효성 검사는 기본적으로 1 이상만 올 수 있게 막아뒀다.
Carts.js
const express = require('express');
const router = express.Router();
const { addToCart, getCartItems, deleteCartItem } = require('../controller/CartController');
const { validatesAddToCart, validatesGetCartItems } = require('../middleware/CartMiddleware');
router.use(express.json());
router
.route('/')
// 장바구니 담기
.post(validatesAddToCart, addToCart)
// 장바구니 조회, 선택된 장바구니 조회
.get(validatesGetCartItems, getCartItems);
// 장바구니 도서 삭제
router.delete('/:id', deleteCartItem);
module.exports = router;
다른 라우터 파일과 비슷하다.
const getCartItems = async (req, res) => {
const connection = await conn.getConnection();
const { userId, selected } = camelcaseKeys(req.body);
const sqlSelectUser = 'select * from users where id = ?';
const sqlSelectAllCart = `select cartItems.id, book_id, title, summary, quantity, price
from cartItems left join books on cartItems.book_id = books.id where user_id = ?`;
const sqlSelectSelectedCart = `select cartItems.id, book_id, title, summary, quantity, price
from cartItems left join books on cartItems.book_id = books.id where user_id = ? and cartItems.id in (?)`;
let sqlSelectCart;
let values;
if (selected) {
sqlSelectCart = sqlSelectSelectedCart;
values = [userId, selected];
} else {
sqlSelectCart = sqlSelectAllCart;
values = [userId];
}
try {
const [rowsUser] = await connection.query(sqlSelectUser, userId);
if (rowsUser.length === 0) {
return res.status(StatusCodes.NOT_FOUND).json({
message: '존재하지 않는 유저입니다.',
});
}
const [rowsSelect] = await connection.query(sqlSelectCart, values);
if (rowsSelect.length > 0) {
return res.status(StatusCodes.OK).json(rowsSelect);
} else {
return res.status(StatusCodes.NOT_FOUND).json({
message: '장바구니에 담긴 도서가 없습니다.',
});
}
} catch (err) {
console.log(err);
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
message: '장바구니 조회 중 문제가 발생하였습니다.',
});
} finally {
connection.release();
}
};
기본적으로 selected가 들어오지 않고 userId만 들어온다면 해당 유저가 장바구니에 담은 모든 도서를 반환하고, selected가 배열로 함께 들어오면 선택한 장바구니만 반환한다.
강의에서는 selected가 들어오지 않을 때의 예외 처리를 하지 않으셔서 이 부분은 내가 따로 추가했다.
const deleteCartItem = async (req, res) => {
const connection = await conn.getConnection();
const { id } = req.params;
const sqlDeleteCart = 'delete from cartItems where id = ?';
try {
const [rowsDelete] = await connection.query(sqlDeleteCart, id);
if (rowsDelete.affectedRows > 0) {
return res.status(StatusCodes.OK).json({
message: '장바구니에서 도서가 삭제되었습니다.',
});
} else {
return res.status(StatusCodes.NOT_FOUND).json({
message: '존재하지 않는 장바구니 도서입니다.',
});
}
} catch (err) {
console.log(err);
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
message: '장바구니 도서 삭제 중 문제가 발생하였습니다.',
});
} finally {
connection.release();
}
};
딱히 특별한 건 없고 다른 delete 요청과 마찬가지로 그냥 삭제하는 로직이다.
마찬가지로 에러 처리 부분에서 생각이 조금 많아진다. 좀 더 나은 방법이 있을 것 같긴 한데 일단 지금 기능 구현하는 것에 바빠서 리팩토링을 잘 못하고 있다... 당장 수목금 여행을 가서 최대한 빨리 끝내놔야 하는데!!!!
남은 부분 열심히 해서 마무리 지어보자