인증 ensureAuthorization 모듈화
const jwt = require("jsonwebtoken");
const dotenv = require("dotenv");
dotenv.config();
const ensureAuthorization = (req, res) => {
try {
let receivedJwt = req.headers["authorization"];
console.log(receivedJwt);
let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
console.log(decodedJwt);
return decodedJwt;
} catch (err) {
console.log(err.name);
console.log(err.message);
return err;
}
};
module.exports = ensureAuthorization;
`const ensureAuthorization = require("../auth");` 으로 사용할 컨트롤러에 모듈을 import 해준다.
전체목록조회에서 선택된 아이템이 없을때?
주문서 작성 시 '선택한 장바구니 목록 조회' : selected 있음
없을때는? 그냥 장바구니 보기
조건을 분리해서 sql 문 작성하도록 한다
let sql = `SELECT cartItems.id, book_id, title, summary, quantity, price
FROM cartItems LEFT JOIN books
ON cartItems.book_id = books.id
WHERE user_id=?
`;
let values = [authorization.id, selected];
//주문서 작성 시 '선택한 장바구니 목록 조회'
if (selected) {
sql += ` AND cartItems.id IN (?)`;
values.push(selected);
}
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
}
주문 API 회원 인증 추가
delivery, orders, orderedBook 테이블 모두 토큰이 있을때(else 일때) + 토큰 만료 구분 과정 OrderContorller.js에 추가한다.
개별 상품 조회에서 로그인상태일경우 좋아요를 했는지 안했는지 보여줄때 토큰 사용한다.
토큰이 있으면 회원인증을 하기는 해야한다. 무조건 걸러주고! 로그인상태가 아니면> liked 빼고 보낸다.
로그인 상태가 아니라면 liked 제외
else if (authorization instanceof ReferenceError) {
//토큰이 없는 경우 > liked 제외
let book_id = req.params.id;
let sql = `SELECT *
, (SELECT count(*) FROM Bookshop.likes WHERE liked_book_id = books.id) as likes
FROM books
LEFT JOIN category ON books.category_id = category.category_id
WHERE books.id=?;
`;
let values = [book_id];
conn.query(sql, values, (err, results) => {
//...
} else {
let book_id = req.params.id;
let sql = `SELECT *
, (SELECT count(*) FROM Bookshop.likes WHERE liked_book_id = books.id) as likes
,(SELECT EXISTS (SELECT * FROM likes WHERE user_id = 1 AND liked_book_id=?)) AS liked
FROM books
LEFT JOIN category ON books.category_id = category.category_id
WHERE books.id=?;
`;
let values = [authorization.id, book_id, book_id];
conn.query(sql, values, (err, results) => {
//...
단순 liked 만 빠진건데 반복되는 sql을 통합하는 방법은?
좋아요 숫자의 경우 프론트에서 처리!
로그인 상태여야 하는 기능들 : 장바구니 도서 삭제, 주문(상세)내역 조회
Request Header | Authorization : 로그인할때 받은 Jwt Token(문자열) |
const delCartItems = (req, res) => {
let authorization = ensureAuthorization(req, res);
if (authorization instanceof jwt.TokenExpiredError) {
return res
.status(StatusCodes.UNAUTHORIZED)
.json({ message: "로그인세션이 만료되었습니다. 다시 로그인하세요." });
} else if (authorization instanceof jwt.JsonWebTokenError) {
return res
.status(StatusCodes.BAD_REQUEST)
.json({ message: "잘못된 토큰입니다." });
} else {
const cartItemId = req.params.id;
let sql = `DELETE FROM cartItems WHERE id = ?;`;
conn.query(sql, cartItemId, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
}
};
도서 전체 목록 조회 페이징 pagination 추가
현재 페이지 current page, 목록의 개수 total page 에 대한 정보가 필요하다.
무한스크롤일경우 페이지수보다는 전체 아이템의 개수가 더 필요할 수도..!!
Response Body | { books : [ { id : 도서 id, title : 도서제목, img : 이미지id (picsum img #id) summary : 요약 설명, author : 도서작가, price : 가격, likes : 좋아요 수, pubDate : 출간일 }, { title : 도서제목, img : 이미지id (picsum img #id) summary : 요약 설명, author : 도서작가, price : 가격, likes : 좋아요 수, pubDate : 출간일 }, ….], pagination : { currentPage : 현재 페이지, totalCount : 총 도서 수 } } |
totalBooks는 count(*)를 서브쿼리로 하면, 중복이 많기 때문에 SELECT를 한번 더 치거나
SELECT * FROM books LIMIT 4 OFFSET 0;
SELECT count(*) FROM books;
SQL 문법 `SQL_CALC_FOUND_ROWS`를 사용하거나!
SELECT sql_calc_found_rows * FROM books LIMIT 4 OFFSET 0;
SELECT found_rows();
처음에 SELECT할때 전체 ROW 수를 가져왔고, 어딘가에 저장된 found_rows()에 있는 개수를 가져온다.
sql = "SELECT found_rows();";
conn.query(sql, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
body 구성 맞추기
//결과데이터
{
"books": [
{
"id": 1,
"title": "어린왕자들",
"img": 7,
"category_id": 0,
"form": "종이책",
"isbn": "0",
"summary": "어리다..",
"detail": "많이 어리다..",
"author": "김어림",
"pages": 100,
"contents": "목차입니다.",
"price": 20000,
"pub_date": "2019-01-01",
"likes": 3
},
{
"id": 2,
"title": "신데렐라들",
"img": 10,
"category_id": 0,
"form": "종이책",
"isbn": "1",
"summary": "유리구두..",
"detail": "투명한 유리구두..",
"author": "김구두",
"pages": 100,
"contents": "목차입니다.",
"price": 20000,
"pub_date": "2023-12-01",
"likes": 2
},
{
"id": 3,
"title": "백설공주들",
"img": 60,
"category_id": 1,
"form": "종이책",
"isbn": "2",
"summary": "사과..",
"detail": "빨간 사과..",
"author": "김사과",
"pages": 100,
"contents": "목차입니다.",
"price": 20000,
"pub_date": "2023-11-01",
"likes": 3
},
{
"id": 4,
"title": "흥부와 놀부들",
"img": 90,
"category_id": 2,
"form": "종이책",
"isbn": "3",
"summary": "제비..",
"detail": "까만 제비..",
"author": "김제비",
"pages": 100,
"contents": "목차입니다.",
"price": 20000,
"pub_date": "2023-12-08",
"likes": 1
}
],
"pagination": {
"currentPage": "1",
"totalCount": 11
}
}
형식 정제 코드
let allBooksRes = {};
///...첫번째 select 데이터
if (results.length) allBooksRes.books = results;
///...두번째 select 행개수
let pagination = {};
pagination.currentPage = parseInt(currentPage);
pagination.totalCount = results[0]["found_rows()"];
allBooksRes.pagination = pagination;
return res.status(StatusCodes.OK).json(allBooksRes);
☑️ 배운 점
토큰 검증, 유무 적용
pagination 페이징 기법 > 전체상품개수 % LIMIT = 페이징 개수
SQL_CALC_FOUND_ROWS : select 문에 사용한다.
body 구성 형식 정제 > 사소하지만 중요한 부분