2024년 10월 17일
// auth.js
const jwt = require("jsonwebtoken");
const dotenv = require("dotenv");
dotenv.config();
const ensureAuthorization = (req, res) => {
try {
let receivedJwt = req.headers["authorization"];
console.log("received jwt : ", 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;
// LikeController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken");
const dotenv = require("dotenv");
dotenv.config();
const ensureAuthorization = require("../auth"); // 인증 모듈
... 생략 ...
// CartController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken");
const dotenv = require("dotenv");
dotenv.config();
const authorization = require("../auth");
... 생략 ...
: 내 아이디로 들어갔을때 장바구니에 무엇이 담겨있는지 확인하도록 구현 & selected가 있을 땐 selected한 장바구니 목록 조회
// CartController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken");
const dotenv = require("dotenv");
dotenv.config();
const ensureAuthorization = require("../auth");
const getCartItems = (req, res) => {
let { selected } = req.body; // selected = [1, 3]
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 {
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];
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);
});
}
};
// OrderController.js
const mariadb = require("mysql2/promise");
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken");
const ensureAuthorization = require("../auth"); // 인증 모듈
const order = async (req, res) => {
const conn = await mariadb.createConnection({
host: "127.0.0.1",
user: "root",
password: "root",
database: "Bookshop",
dateStrings: true,
});
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 { items, delivery, totalQuantity, totalPrice, firstBookTitle } =
req.body;
//(1) delivery에 Insert
let sql =
"INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)";
let values = [delivery.address, delivery.receiver, delivery.contact];
let [results] = await conn.execute(sql, values);
let delivery_id = results.insertId;
// (2) orders에 Insert
sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id) VALUES (?, ?, ?, ?, ?)`;
values = [
firstBookTitle,
totalQuantity,
totalPrice,
authorization.id,
delivery_id,
];
[results] = await conn.execute(sql, values);
let order_id = results.insertId;
// (2-1) items를 가지고, 장바구니에서 book_id, quantity 조회
sql = `SELECT book_id, quantity FROM cartItems WHERE id IN (?)`;
let [orderItems, fields] = await conn.query(sql, [items]); // [rows, fields]
// (3) orderedBook에 Insert
sql = `INSERT INTO orderedBook (order_id, book_id, quantity) VALUES ?`;
values = [];
orderItems.forEach((item) => {
values.push([order_id, item.book_id, item.quantity]);
});
[results] = await conn.query(sql, [values]);
// 이중배열을 넘겨줄땐 execute가 아닌 query를 써야함
let result = await deleteCartItems(conn, items);
return res.status(StatusCodes.OK).json(result);
}
};
const deleteCartItems = async (conn, items) => {
let sql = `DELETE FROM cartItems WHERE id IN (?)`;
let result = await conn.query(sql, [items]);
return result;
};
POSTMAN) token + POST + localhost:9999/orders + 데이터
// BookController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken");
const ensureAuthorization = require("../auth"); // 인증 모듈
const bookDetail = (req, res) => {
// 로그인 상태가 아니면 => liked 빼고 보내주면 되고
// 로그인 상태이면 => liked 추가해서
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 {
let book_id = req.params.id;
let sql = `SELECT *,
(SELECT count(*) FROM likes WHERE liked_book_id=books.id) AS likes,
(SELECT EXISTS (SELECT * FROM likes WHERE user_id = ? 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) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
if (results[0]) {
return res.status(StatusCodes.OK).json(results[0]); // 책 한 권만 보여주기
} else {
return res.status(StatusCodes.NOT_FOUND).end();
}
});
}
};
POSTMAN) token + GET + localhost:9999/books/1
liked:0 인 것을 확인할 수 있다.
POSTMAN) token 입력하지 않음 + GET + localhost:9999/books/1
잘못된 토큰이라는 메시지를 볼 수 있다.
로그인 상태가 아닐때 도서 개별 조회를 하면 liked는 보이지 않도록 처리해주자
// auth.js
const jwt = require("jsonwebtoken");
const dotenv = require("dotenv");
dotenv.config();
const ensureAuthorization = (req, res) => {
try {
let receivedJwt = req.headers["authorization"];
console.log("received jwt : ", receivedJwt);
if(receivedJwt) {
let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
console.log(decodedJwt);
return decodedJwt;
} else {
throw new ReferenceError("jwt must be provided");
// or return receivedJwt
}
} catch (err) {
console.log(err.name);
console.log(err.message);
return err;
}
};
module.exports = ensureAuthorization;
const bookDetail = (req, res) => {
// 로그인 상태가 아니면 => liked 빼고 보내주면 되고
// 로그읜 상태이면 => liked 추가해서
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 {
let book_id = req.params.id;
authorization instanceof ReferenceError
? (sql = `SELECT *,
(SELECT count(*) FROM likes WHERE liked_book_id=books.id) AS likes
FROM books
LEFT JOIN category
ON books.category_id = category.category_id
WHERE books.id = ?;`)
: (sql = `SELECT *,
(SELECT count(*) FROM likes WHERE liked_book_id=books.id) AS likes,
(SELECT EXISTS (SELECT * FROM likes WHERE user_id = ? AND liked_book_id = ?)) AS liked
FROM books
LEFT JOIN category
ON books.category_id = category.category_id
WHERE books.id = ?;`);
authorization instanceof ReferenceError
? (values = [book_id])
: (values = [authorization.id, book_id, book_id]);
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
if (results[0]) {
return res.status(StatusCodes.OK).json(results[0]); // 책 한 권만 보여주기
} else {
return res.status(StatusCodes.NOT_FOUND).end();
}
});
}
};
POSTMAN) GET+localhost:9999/books/1
로그인 하지 않아서(토큰X) liked가 없는 것을 알 수 있음
POSTMAN) GET+localhost:9999/books/1+token
로그인하니깐(토큰O) liked 있는 것 확인할 수 있음
// CartController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken");
const ensureAuthorization = require("../auth");
const removeCartItem = (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 {
let 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);
});
}
};
// OrderController.js
... 생략 ...
const getOrders = async (req, res) => {
const conn = await mariadb.createConnection({
host: "127.0.0.1",
user: "root",
password: "root",
database: "Bookshop",
dateStrings: true,
});
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 {
let sql = `SELECT orders.id, created_at, address, receiver, contact, book_title, total_quantity, total_price FROM orders LEFT JOIN delivery ON orders.delivery_id = delivery.id;`;
let [rows, fields] = await conn.query(sql);
return res.status(StatusCodes.OK).json(rows);
}
};
const getOrderDetail = async (req, res) => {
const orderId = req.params.id;
const conn = await mariadb.createConnection({
host: "127.0.0.1",
user: "root",
password: "root",
database: "Bookshop",
dateStrings: true,
});
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 {
let sql = `SELECT book_id, title, author, price, quantity FROM orderedBook LEFT JOIN books ON orderedBook.book_id = books.id WHERE order_id=?`;
let [rows, field] = await conn.query(sql, orderId);
return res.status(StatusCodes.OK).json(rows);
}
};
// BookController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken");
const ensureAuthorization = require("../auth"); // 인증 모듈
// (카테고리별, 신간 여부) 전체 도서 목록 조회
const allBooks = (req, res) => {
let { category_id, news, limit, currentPage } = req.query;
let offset = limit * (currentPage - 1);
let sql =
`SELECT SQL_CALC_FOUND_ROWS *, (SELECT count(*) FROM likes
WHERE liked_book_id=books.id) AS likes 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 = [category_id];
} else if (category_id) {
sql += " WHERE category_id = ?";
values = [category_id];
} else if (news) {
sql +=
" WHERE pub_date BETWEEN DATE_SUB(NOW(), INTERVAL 1 MONTH) AND NOW()";
}
sql += " LIMIT ? OFFSET ?";
values = [...values, parseInt(limit), offset];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
// return res.status(StatusCodes.BAD_REQUEST).end();
}
console.log(results);
// if (results.length) {
// return res.status(StatusCodes.OK).json(results);
// } else {
// return res.status(StatusCodes.NOT_FOUND).end();
// }
});
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);
});
};
POSTMAN) GET + localhost:9999/books?limit=4 & currentPage=1
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken");
const ensureAuthorization = require("../auth"); // 인증 모듈
// (카테고리별, 신간 여부) 전체 도서 목록 조회
const allBooks = (req, res) => {
let allBooksRes = {}; // json 형태로 마련해둠
// API 설계에서 봤듯, json형태로 books(도서데이터),
// pagination(전체도서수, 현재 페이지) 받아올 것임
let { category_id, news, limit, currentPage } = req.query;
let offset = limit * (currentPage - 1);
let sql =
"SELECT SQL_CALC_FOUND_ROWS *, (SELECT count(*) FROM likes WHERE liked_book_id=books.id) AS likes 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 = [category_id];
} else if (category_id) {
sql += " WHERE category_id = ?";
values = [category_id];
} else if (news) {
sql +=
" WHERE pub_date BETWEEN DATE_SUB(NOW(), INTERVAL 1 MONTH) AND NOW()";
}
sql += " LIMIT ? OFFSET ?";
values = [...values, parseInt(limit), offset];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
// return res.status(StatusCodes.BAD_REQUEST).end();
}
if (results.length) {
console.log(results);
allBooksRes.books = results; // books(도서 데이터) 받음
} else {
return res.status(StatusCodes.NOT_FOUND).end();
}
});
sql = "SELECT found_rows()";
conn.query(sql, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
// results -> [{"found_rows()":11}]의 형태임
let pagination = {}; // pagination(총 도서 수, 현재 페이지) 받음
pagination.currentPage = parseInt(currentPage); // params로 받음
pagination.totalCount = results[0]["found_rows()"];
// 배열 안의 json 형태이므로
// 배열의 첫번째 요소를 꺼내서
// found_rows() 키 값을 사용
allBooksRes.pagination = pagination;
return res.status(StatusCodes.OK).json(allBooksRes);
});
};
POSTMAN) GET+ localhost:9999/books?limit=4¤tPage=1
// BookController.js
... 생략 ...
sql += " LIMIT ? OFFSET ?";
values = [...values, parseInt(limit), offset];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
// return res.status(StatusCodes.BAD_REQUEST).end();
}
if (results.length) {
results.map((result) => {
result.pubDate = result.pub_date;
delete result.pub_date;
});
allBooksRes.books = results;
} else {
return res.status(StatusCodes.NOT_FOUND).end();
}
});
... 생략 ...
: 실패시 필요한 error status code도 고려 해야 한다
if(results.affectedRows) {
return res.status(StatusCodes.CREATED).json(results);
} else {
return res.status(StatusCodes.BAD_REQUEST).end();
}
Router : 경로(URI, URL)와 HTTP method로 요청에 따른 경로를 찾아주는 역할
Controller : 길 매니저 - 요청을 환영! 직접 일을 하진 않음
Service : 직접 일을 하고 ex) 어떤 쿼리를 부를지
“비즈니스 로직”
Model : 데이터베이스와 소통 → query 집합
// radom-user.js
const { faker } = require('@faker-js/faker');
// localhost:2222/fake/users
// 한명의 사용자 정보 생성
// fullName, email, password, contact
console.log(faker.internet.userName());
console.log(faker.internet.email());
console.log(faker.internet.password());
// radom-user.js
const { faker } = require('@faker-js/faker');
// localhost:2222/fake/users
// 한명의 사용자 정보 생성
// fullName, email, password, contact
console.log(faker.internet.email());
console.log(faker.internet.password());
console.log(faker.person.fullName());
console.log(faker.phone.number());
// radom-user.js
const express = require("express");
const app = express();
const { faker } = require('@faker-js/faker');
// localhost:2222/fake/users
// req 숫자를 받아서, 그 수 만큼 사용자 정보를 생성해주는 api
app.get('/fake/users', function(req, res) {
res.status(200).json({
email : faker.internet.email(),
password : faker.internet.password(),
fullName : faker.person.fullName(),
contact : faker.phone.number()
})
});
console.log(faker.internet.email());
console.log(faker.internet.password());
console.log(faker.person.fullName());
console.log(faker.phone.number());
app.listen(2222);
localhost:2222/fake/users
// radom-user.js
const express = require("express");
const app = express();
const { faker } = require('@faker-js/faker');
// localhost:2222/fake/users
// req 숫자를 받아서, 그 수 만큼 사용자 정보를 생성해주는 api
app.get('/fake/users/', function(req, res) {
const {num} = req.query;
let index = 1;
let users = [];
while(index <= num) {
users.push({
email : faker.internet.email(),
password : faker.internet.password(),
fullName : faker.person.fullName(),
contact : faker.phone.number()
});
index++;
}
res.status(200).json(users);
});
app.listen(2222);
localhost:2222/faker/users?num=10
🍏🍎 오늘의 느낀점 : 드디어 오늘 백엔드코스가 끝났다.. 마지막인만큼 배운 내용도 많고 어려운 내용도 많았다. pagination을 백엔드에서 처리해줘서 프론트랑 연결해서 보여주는 것도 그동안 궁금했었는데 어떻게 처리하는지 알게 되었다. 초미니 프로젝트를 하면서 직접 API를 만드는 것도 해보면서 많은 것들을 만들어봐야 확실히 감이 올 것 같다.
그리고 코드 퀄리티를 높이기 위해 모듈화하는 것과 패키지 구조를 다시 한번 찾아보면서 코드를 처음부터 한번 다시 짜봐야겠다.