๐ŸŽฏ ์„œ๋ธŒ ์ฟผ๋ฆฌ๋ฅผ ํ†ตํ•ด ํ…Œ์ด๋ธ”์— ์ปฌ๋Ÿผ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ“™ Today I Learned

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค๊ณ„

categories TABLE ์ˆ˜์ •

books ํ…Œ์ด๋ธ” SELECT ์‹œ์— ์นผ๋Ÿผ์œผ๋กœ ๋„˜๊ฒจ์ค„ ๋•Œ id ์ด๋ฆ„์ด ์ค‘๋ณต๋˜์–ด category_id๋กœ ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

category_id integer [primary key]
category_name varchar

books TABLE ์ˆ˜์ •

likes๋Š” likes ํ…Œ์ด๋ธ”์—์„œ ๊ฐ€์ ธ์™€ ์นผ๋Ÿผ์œผ๋กœ ์ถ”๊ฐ€ํ•  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค.

Table books {
  id integer [primary key]
  title varchar
  img integer
  category_id integer
  form varchar
  isbn varchar
  summary text
  detail text
  author varchar
  pages integer
  contents text
  price integer
  pub_date date
}



SQL

์นผ๋Ÿผ ๊ฐœ์ˆ˜ ๋ฐ˜ํ™˜

์กฐ๊ฑด์— ํ•ด๋‹นํ•˜๋Š” ํ–‰์˜ ๊ฐœ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

SELECT COUNT(*) FROM ํ…Œ์ด๋ธ”๋ช… WHERE ์นผ๋Ÿผ1์ด๋ฆ„ = ์นผ๋Ÿผ2์ด๋ฆ„

ํŠน์ • ์กฐ๊ฑด ๋งŒ์กฑ ์—ฌ๋ถ€ ํ™•์ธ

์กฐ๊ฑด์— ๋งŒ์กฑํ•˜๋Š” ํ–‰์ด ์กด์žฌํ•˜๋ฉด 1 ์•„๋‹ˆ๋ฉด 0์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

SELECT EXISTS (SELECT 1 FROM ํ…Œ์ด๋ธ”๋ช… WHERE ์นผ๋Ÿผ1์ด๋ฆ„ = ? AND ์นผ๋Ÿผ2์ด๋ฆ„ = ?)

๐Ÿค” ์™œ SELECT 1์„ ์“ฐ๋Š”๊ฑธ๊นŒ?

EXISTS ์—์„œ๋Š” ์กด์žฌ ์—ฌ๋ถ€๋งŒ์„ ํŒ๋‹จํ•˜๊ธฐ ๋•Œ๋ฌธ์— *์„ ํ†ตํ•ด ๋ชจ๋“  ์นผ๋Ÿผ์„ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์€ ๋น„ํšจ์œจ์ ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.


์„œ๋ธŒ ์ฟผ๋ฆฌ ๋ณ„์นญ์œผ๋กœ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

์„œ๋ธŒ ์ฟผ๋ฆฌ๋Š” ๋‹ค๋ฅธ ์ฟผ๋ฆฌ ๋‚ด๋ถ€์—์„œ ์‹คํ–‰๋˜๋Š” SQL ์ฟผ๋ฆฌ๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

๋ณ„์นญ์ด ์ƒˆ๋กœ์šด ์นผ๋Ÿผ์œผ๋กœ ์ถ”๊ฐ€๋˜์–ด ํ…Œ์ด๋ธ”์„ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

SELECT *, (SELECT count(*) FROM ํ…Œ์ด๋ธ”๋ช… WHERE ์นผ๋Ÿผ1์ด๋ฆ„ = ์นผ๋Ÿผ2์ด๋ฆ„0) AS ๋ณ„์นญ FROM ํ…Œ์ด๋ธ”๋ช… 



๋„์„œ API ์„ค๊ณ„

๐Ÿ‘‰ ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„์€ ์ฃผํ™ฉ์ƒ‰ ๊ธ€์”จ๋กœ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

์ „์ฒด ๋„์„œ ์กฐํšŒ

  • Method : GET

  • URL :/books?limit=๊ฐœ์ˆ˜&currentPage=ํŽ˜์ด์ง€๋ฒˆํ˜ธ

  • HTTP Status Code : 200 Ok

  • Request Body : x

  • Response Body :

[
  {
    "id" : ๋„์„œ id,
    "title": "๋„์„œ ์ œ๋ชฉ",
    "img" : ์ด๋ฏธ์ง€ id,
    "summary" : "์š”์•ฝ ์„ค๋ช…",
    "author" : "๋„์„œ ์ž‘๊ฐ€",
    "price" : ๊ฐ€๊ฒฉ,
    "likes" : ์ข‹์•„์š” ์ˆ˜,
    "pub_date" : "์ถœ๊ฐ„ ๋‚ ์งœ"
  },
  {
    "id" : ๋„์„œ id,
    "title": "๋„์„œ ์ œ๋ชฉ",
    "img" : ์ด๋ฏธ์ง€ id,
    "summary" : "์š”์•ฝ ์„ค๋ช…",
    "author" : "๋„์„œ ์ž‘๊ฐ€",
    "price" : ๊ฐ€๊ฒฉ,
    "likes" : ์ข‹์•„์š” ์ˆ˜,
    "pub_date" : "์ถœ๊ฐ„ ๋‚ ์งœ"
  },
  ...
]

๊ฐœ๋ณ„ ๋„์„œ ์กฐํšŒ

  • Method : GET

  • URL : /books/:id

  • HTTP Status Code : 200 Ok

  • Request Body

{
  "email": "์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์ด๋ฉ”์ผ",
  "password" : "์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ"
}
  • Response Body :
{
  "id" : ๋„์„œ id,
  "title": "๋„์„œ ์ œ๋ชฉ",
  "img" : ์ด๋ฏธ์ง€ id,
  "category_name" : "์นดํ…Œ๊ณ ๋ฆฌ",
  "form" : "ํฌ๋งท",
  "isbn" : "isbn",  
  "summary" : "์š”์•ฝ ์„ค๋ช…",
  "detail" : "์ž์„ธํ•œ ์„ค๋ช…",
  "author" : "๋„์„œ ์ž‘๊ฐ€",
  "pages" : ์ชฝ ์ˆ˜,
  "contents" : "๋ชฉ์ฐจ",
  "price" : ๊ฐ€๊ฒฉ,
  "likes" : ์ข‹์•„์š” ์ˆ˜,
  "liked" : boolean,
  "pub_date" : "์ถœ๊ฐ„ ๋‚ ์งœ"
}

์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์‹ ๊ฐ„ ๋„์„œ ์กฐํšŒ

  • Method : GET

  • URL : /books?categoryId=:categoryId&news={boolean}

  • HTTP Status Code : 200 Ok

  • Request Body : x

  • Response Body :

[
  {
    "id" : ๋„์„œ id,
    "title": "๋„์„œ ์ œ๋ชฉ",
    "img" : ์ด๋ฏธ์ง€ id,
    "summary" : "์š”์•ฝ ์„ค๋ช…",
    "author" : "๋„์„œ ์ž‘๊ฐ€",
    "price" : ๊ฐ€๊ฒฉ,
    "likes" : ์ข‹์•„์š” ์ˆ˜,
    "pub_date" : "์ถœ๊ฐ„ ๋‚ ์งœ"
  },
  {
    "id" : ๋„์„œ id,
    "title": "๋„์„œ ์ œ๋ชฉ",
    "img" : ์ด๋ฏธ์ง€ id,
    "summary" : "์š”์•ฝ ์„ค๋ช…",
    "author" : "๋„์„œ ์ž‘๊ฐ€",
    "price" : ๊ฐ€๊ฒฉ,
    "likes" : ์ข‹์•„์š” ์ˆ˜,
    "pub_date" : "์ถœ๊ฐ„ ๋‚ ์งœ"
  },
  ...
]
  • ์ฐธ๊ณ ์‚ฌํ•ญ : news๊ฐ€ true์ผ ๋•Œ ์‹ ๊ฐ„ ์กฐํšŒ ๊ธฐ์ค€์€ ์ถœ๊ฐ„์ผ๋กœ๋ถ€ํ„ฐ 1๋‹ฌ ์ด๋‚ด



์ข‹์•„์š” API ์„ค๊ณ„

์ข‹์•„์š” ์ถ”๊ฐ€

  • Method : POST

  • URL : /likes/:bookId

  • HTTP Status Code : 200 Ok

  • Request Body : token > header "authorization"

  • Response Body : x

  • TODO : ์ถ”ํ›„ api ์ˆ˜์ • , ํ˜„์žฌ๋Š” ํ† ํฐ์œผ๋กœ ํ•˜์ง€ ์•Š๊ณ ,userId๋ฅผ body๊ฐ’์œผ๋กœ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌ


์ข‹์•„์š” ์ทจ์†Œ

  • Method : DELETE

  • URL : /likes/:bookId

  • HTTP Status Code : 200 Ok

  • Request Body : token > header "authorization"

  • Response Body : x

  • TODO : ์ถ”ํ›„ api ์ˆ˜์ • , ํ˜„์žฌ๋Š” ํ† ํฐ์œผ๋กœ ํ•˜์ง€ ์•Š๊ณ ,userId๋ฅผ body๊ฐ’์œผ๋กœ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌ




LikeController.js

const conn = require('../mariadb');
const { StatusCodes } = require('http-status-codes');

const addLike = (req, res) => {
  const likedBookId = req.params.id;
  const { userId } = req.body;

  const sql = 'INSERT INTO likes (user_id, liked_book_id) VALUES (?, ?)';
  const values = [userId, likedBookId];
  conn.query(sql, values, (err, result) => {
    if (err) {
      console.log(err);
      return res.status(StatusCodes.BAD_REQUEST).end();
    }

    return res.status(StatusCodes.CREATED).json(result);
  });
};

const removeLike = (req, res) => {
  const likedBookId = req.params.id;
  const { userId } = req.body;

  const sql = 'DELETE FROM likes WHERE user_id =? AND liked_book_id = ?';
  const values = [userId, likedBookId];
  conn.query(sql, values, (err, result) => {
    if (err) {
      console.log(err);
      return res.status(StatusCodes.BAD_REQUEST).end();
    }

    return res.status(StatusCodes.OK).json(result);
  });
};

module.exports = {
  addLike,
  removeLike,
};
  • addLike : likes ํ…Œ์ด๋ธ”์— user_id ์™€ liked_book_id๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

  • removeLike : likes ํ…Œ์ด๋ธ”์—์„œ user_id ์™€ liked_book_id๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.




BookController.js

const conn = require('../mariadb');
const { StatusCodes } = require('http-status-codes');

const allBooks = (req, res) => {
  const { news, limit, currentPage, categoryId } = req.query;

  const offset = limit * (currentPage - 1);
  let sql =
    'SELECT *, (SELECT count(*) FROM likes WHERE liked_book_id = books.id) AS likes FROM books';
  let values = [];

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

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

  conn.query(sql, values, (err, result) => {
    if (err) {
      console.log(err);
      return res.status(StatusCodes.BAD_REQUEST).end();
    }
    if (result.length) {
      return res.status(StatusCodes.OK).json(result);
    } else {
      return res.status(StatusCodes.NOT_FOUND).end();
    }
  });
};

const bookDetail = (req, res) => {
  const id = parseInt(req.params.id);
  const { userId } = req.body;

  const sql = `SELECT *, 
  	(SELECT count(*) FROM likes WHERE liked_book_id = books.id) AS likes, 
  	(SELECT EXISTS (SELECT 1 FROM likes WHERE user_id = ? AND liked_book_id = ?)) AS liked 
  	FROM books 
  	LEFT JOIN categories 
  	ON books.category_id = categories.category_id 
  	WHERE books.id = ?`;
  const values = [userId, id, id];
  conn.query(sql, values, (err, result) => {
    if (err) {
      console.log(err);
      return res.status(StatusCodes.BAD_REQUEST).end();
    }

    if (result[0]) {
      return res.status(StatusCodes.OK).json(result);
    } else {
      return res.status(StatusCodes.NOT_FOUND).end();
    }
  });
};

module.exports = {
  allBooks,
  bookDetail,
};
  • allBooks : books ํ…Œ์ด๋ธ”์—์„œ ๋ชจ๋“  ์ปฌ๋Ÿผ์„ ๊ฐ€์ ธ์˜ค๋ฉด์„œ, likes ํ…Œ์ด๋ธ”์—์„œ ํ•ด๋‹น ์ฑ…์˜ ์ข‹์•„์š” ๊ฐœ์ˆ˜(likes)๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
  • bookDetail : books ํ…Œ์ด๋ธ”์—์„œ ๋ชจ๋“  ์นผ๋Ÿผ์„ ๊ฐ€์ ธ์˜ค๋ฉด์„œ, likes ํ…Œ์ด๋ธ”์—์„œ ํ•ด๋‹น ์ฑ…์˜ ์ข‹์•„์š” ๊ฐœ์ˆ˜์™€ ์œ ์ €๊ฐ€ ํ•ด๋‹น ์ฑ…์„ ์ข‹์•„์š”๋ฅผ ํ–ˆ๋Š”์ง€(liked) ์—ฌ๋ถ€๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.



โœ๏ธ ํ•œ ์ค„ ํšŒ๊ณ 

ํ…Œ์ด๋ธ”์„ ์กฐ์ธํ•˜์ง€ ์•Š์•„๋„ ์„œ๋ธŒ ์ฟผ๋ฆฌ์™€ ์นผ๋Ÿผ ๋ณ„์นญ์œผ๋กœ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์–ด ํŽธ๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

profile
๐ŸŒฑ๊ฐœ๋ฐœ ๊ธฐ๋ก์žฅ

0๊ฐœ์˜ ๋Œ“๊ธ€