타입스크립트로 함께하는 웹 풀 사이클 개발(React, Node.js) 2기 - 8주차

이현철·2024년 4월 17일
post-thumbnail

💻 1일차 (4.15)

도서 정보 API 설계 및 구현 프로젝트/랜덤 데이터 생성 API설계 및 구현 프로젝트(8)

1. 좋아요 디비 설계


위와 같이 디비를 설계를 했을때 누가 좋아요를 눌렀는지 어떻게 알 수 있을까??
=> 로그인을 한 상태여야 조회가 가능하고 토큰에서 꺼내서 확인할 수 있다.

2. 좋아요 추가 구현

const addLike = (req, res) => {
    //카테고리 전체 목록 리스트
    console.log("1");
    const { id } = req.params;
    const { user_id } = req.body;

    let sql = `INSERT INTO likes (user_id, liked_book_id) VALUES (?, ?)`;
    let values = [user_id, id];
    conn.query(sql, values, (err, results) => {
        if (err) {
            //서버측 에러
            console.log(err);
            return res.status(StatusCodes.BAD_REQUEST).end();
        }
        res.status(StatusCodes.OK).json(results);
    });
};

3. 좋아요 삭제 구현

const removeLike = (req, res) => {
    //좋아요 취소
    const { id } = req.params; //book_id
    const { user_id } = req.body;

    let sql = `DELETE FROM likes WHERE user_id = ? AND liked_book_id = ?`;
    let values = [user_id, id];
    conn.query(sql, values, (err, results) => {
        if (err) {
            //서버측 에러
            console.log(err);
            return res.status(StatusCodes.BAD_REQUEST).end();
        }
        res.status(StatusCodes.OK).json(results);
    });
};

4. count(), AS, 서브쿼리의 등장

count는 데이터의 개수를 반환해주는 함수

SELECT count(*) FROM likes WHERE liked_book_id = 1

=> liked_book_id가 1인 데이터의 개수

books 테이블에 있는 likes라는 컬럼은 이 책에 좋아요를 누른 사람이 몇명인지를 나타내는 컬럼인데 이 컬럼을 추가하는 쿼리문은 다음과 같다.

  • 좋아요 개수 세서 books 테이블에 컬럼 추가해서 출력해주기
SELECT *, (SELECT count(*) FROM likes WHERE liked_book_id=books.id) AS likes FROM books;

5 . 나는 개발한다 고로 나는 EXISTS 한다.

개별 도서 조회 시, 사용자가 좋아요를 했는지에 대한 여부를 포함시켜보기 (EXIST 사용 => 조건에 맞는 것이 있는지 없는지)

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

const allBooks = (req, res) => {
    let { category_id, news, limit, currentPage } = req.query;
    console.log(limit, currentPage);
    let offset = limit * (currentPage - 1);
    let sql = `SELECT *, (SELECT count(*) FROM likes WHERE books.id=liked_book_id) AS likes FROM books`; //좋아요 개수가 포함된 books테이블 조회

    let values = [];

    if (category_id && news) {
        // news가 true이면서 category_id가 있는 경우
        sql += ` WHERE category_id=? AND pub_date BETWEEN DATE_SUB(NOW(), INTERVAL 1 MONTH) AND NOW()`; // 조건문 수정
        values = [parseInt(category_id)]; // 값 추가
    } else if (category_id) {
        // category_id만 있는 경우
        sql += ` WHERE category_id=?`;
        values = [parseInt(category_id)]; // 값 추가
    } else if (news) {
        // news만 있는 경우
        sql += ` WHERE pub_date BETWEEN DATE_SUB(NOW(), INTERVAL 1 MONTH) AND NOW()`;
    }

    sql += ` LIMIT ? OFFSET ?`; // LIMIT과 OFFSET 추가
    values.push(parseInt(limit), parseInt(offset)); // 값 추가

    conn.query(sql, values, (err, results) => {
        // 쿼리 실행
        if (err) {
            console.log(err);
            return res.status(StatusCodes.BAD_REQUEST).end();
        }

        if (results.length) {
            return res.status(StatusCodes.OK).json(results);
        } else {
            return res.status(StatusCodes.NOT_FOUND).end();
        }
    });
};

const bookDetail = (req, res) => {
    let book_id = req.params.id;
    let { user_id } = req.body;
    const 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 = [user_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.length) {
            return res.status(StatusCodes.OK).json(results[0]);
        } else {
            return res.status(StatusCodes.NOT_FOUND).end();
        }
    });
};

module.exports = {
    allBooks,
    bookDetail,
};

💻 2일차 (4.16)

도서 정보 API 설계 및 구현 프로젝트/랜덤 데이터 생성 API설계 및 구현 프로젝트(9)

1. 장바구니 테이블 생성

2. 제약조건 이름 정해주기

fk 제약조건을 걸 때, 다른 테이블에 이미 정해져 있는 제약조건과 이름이 같으면 안되기 때문에 모두 구분이 될 수 있게 이름을 지어야 한다.

fk 제약조건 이름짓기
=> fk기준 테이블명참조테이블명_참조키 ex) fk_cartItems_users_id

3. 장바구니 insert 해주기 (API 구현)

장바구니에 담으려면 지금 회원이 누군지를 알아야 한다. => jwt가 필요 (payload값에서 id가 필요) 또한 도서 id와 그 도서를 몇 권 담을건지 개수(quantity)도 필요하다.

CartController.js

const conn = require("../mariadb"); //db모듈
const { StatusCodes } = require("http-status-codes");

//장바구니 담기
const addToCart = (req, res) => {
   const { book_id, quantity, user_id } = req.body;

   let sql = `INSERT INTO cartItems (book_id, quantity, user_id) VALUES (?, ?, ?)`;
   let values = [book_id, quantity, user_id];
   conn.query(sql, values, (err, results) => {
       if (err) {
           //서버측 에러
           console.log(err);
           return res.status(StatusCodes.BAD_REQUEST).end();
       }
       res.status(StatusCodes.OK).json(results);
   });
};

4. 장바구니 목록조회 API 구현

장바구니에 담았을 때는 books 테이블에 있는 cartItems의 id, 책의 id, title, summary, quantity, price 순으로 나와야 하기 때문에, books와 JOIN 연산을 해주어야 한다.

//장바구니 아이템 목록 조회
const getCartItem = (req, res) => {
    const { user_id } = req.body;
    const sql = `SELECT cartItems.id, book_id, title, summary, quantity, price  FROM cartItems LEFT JOIN books ON books.id = cartItems.book_id  WHERE user_id = ? `;
    conn.query(sql, user_id, (err, results) => {
        if (err) {
            console.log(err);
            return res.status(StatusCodes.BAD_REQUEST).end();
        }
        return res.status(StatusCodes.OK).json(results);
    });
};

결과는 아래와 같은 형식으로 출력된다.

5. 장바구니 도서 삭제 API

장바구니 도서 삭제

const removeCartItem = (req, res) => {
    const { id } = req.params; //cartItemId
    let sql = `DELETE FROM cartItems WHERE id = ?`;
    conn.query(sql, id, (err, results) => {
        if (err) {
            //서버측 에러
            console.log(err);
            return res.status(StatusCodes.BAD_REQUEST).end();
        }
        res.status(StatusCodes.OK).json(results);
    });
};

5. 장바구니에서 선택한 상품 목록 조회

만약 장바구니에서 도서 수량이 변경되면 (DB UPDATE cartItems) 페이지를 나갔다가 들어와도 변경된 상태로 저장하는 방식을 사용할 것이다.

req.body에는 내가 주문 페이지에 나오는 장바구니 상품들 중에 체크한 상품의 cartItemsId를 selected라는 이름의 배열로 받을 것이다.

//장바구니 아이템 목록 조회
const getCartItem = (req, res) => {
    const { user_id, selected } = req.body; //selected는 장바구니에서 내가 선택한 도서들

    const sql = `SELECT cartItems.id, book_id, title, summary, quantity, price  
                    FROM cartItems LEFT JOIN books 
                    ON books.id = cartItems.book_id  
                    WHERE user_id = ? AND cartItems.id IN (?)`;
    let values = [user_id, 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);
    });
};

💻 3일차 (4.17)

도서 정보 API 설계 및 구현 프로젝트/랜덤 데이터 생성 API설계 및 구현 프로젝트(10)

1. 주문 API

2. 주문 DB ERD 설계

위의 그림을 보면 orders.delivery_id가 delivery.id를 참조한다.
또한 orderedBook.order_id가 orders.id를 참조한다.
또한 orderedBook.book_id는 books.id를 참조한다.
또한 orderedBook.user_id는 users.id를 참조한다.

  • id
    • 주문을 구분하게 만들어주는 primary key
  • delivery_id
    • 배송지 정보가 들어있는 delivery테이블의 id
  • total_price
    • 주문할 총 가격
  • created_at
    • 주문한 날짜
  • book_title
    • 주문한 여러 도서 중에 대표할 책 1가지의 제목
  • total_quantity
    • 주문할 도서 개수
  • user_id
    • 주문한 유저의 id

3. 주문 DB 생성

1. orders DB

2. delivery DB

3. orderedBook DB

4. 주문하기 SQL 생각해보기

4.1 데이터베이스 주문 insert

회원이 누구인지 알아야 하기 떄문에 jwt가 필요하나 일단 임시로 user_id를 req.body에 넣어서 진행한다.
또한 req.body에 담겨오는 items배열은 orderedBook 테이블에 들어가야하고, delivery로 넘어오는 배송 정보는 delivery 테이블에 삽입되어야 하며, totalQuantity, totalPrice, userId는 orders 테이블에 삽입되어야 한다.

5. delivery 테이블 insert, insertId 가져다쓰기

5.1 방금 INSERT한 데이터의 PK 가져오는 방법

주문하기 화면에서 배송정보를 먼저 입력하고 그 아래에서 주문 정보를 입력하기 떄문에, 위에서 작성한 배송정보에 대한 데이터의 PK를 바로 가져와야 하는데, 이것을 가져오는 방법은 크게 두 가지가 있다. 또, 주문을 하면 orderedBook 테이블에 데이터가 삽입되어야 하는데, 이것은 orders테이블의 가장 최신값을 가져와야 한다.

  • LAST_INSERT_ID()
    • 방금 들어간 데이터가 아닌 이전 값을 가지고 오는 오류가 간간히 발생
  • MAX()
    • id가 가장 큰 것을 고르게 되면 그게 가장 최신값이기 떄문.
    • ex) SELECT max(id) FROM orderedBook;
    • ex) SELECT last_insert_id();
  • 사실 위의 두 방법말고 데이터를 삽입했을 때 결과로 오는 result에 있는 insertId를 사용하면 된다.

6. orders insert

//[2기] 이현철
const conn = require("../mariadb");
const { StatusCodes } = require("http-status-codes");

const order = (req, res) => {
    const {
        items,
        delivery,
        totalQuantity,
        totalPrice,
        userId,
        firstBookTitle,
    } = req.body;
    let delivery_id = 3;
    let sql = `INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)`;

    let values = [delivery.address, delivery.receiver, delivery.contact];
    // conn.query(sql, values, (err, results) => {
    //     if (err) {
    //         //서버측 에러
    //         console.log(err);
    //         return res.status(StatusCodes.BAD_REQUEST).end();
    //     }

    //     delivery_id = 3; //results.insertId; //가장 최근에 들어간 데이터의 id
    //     return res.status(StatusCodes.OK).json(results);
    // });
    sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id) 
    VALUES (?,?,?,?,?)`;
    values = [firstBookTitle, totalQuantity, totalPrice, userId, delivery_id];
    // firstBookTitle : 대표책제목
    conn.query(sql, values, (err, results) => {
        if (err) {
            //서버측 에러
            console.log(err);
            return res.status(StatusCodes.BAD_REQUEST).end();
        }
        order_id = results.insertId;
        console.log(order_id);
        return res.status(StatusCodes.OK).json(results);
    });
};

const getOrders = (req, res) => {
    res.json({
        message: "주문 내역 조회",
    });
};

const getOrderDetail = (req, res) => {
    res.json({
        message: "주문 상세 조회",
    });
};

module.exports = {
    order,
    getOrderDetail,
    getOrders,
};

7. orderedBook insert

const order = (req, res) => {
    const {
        items,
        delivery,
        totalQuantity,
        totalPrice,
        userId,
        firstBookTitle,
    } = req.body;
    let delivery_id = 3;
    let order_id = 2;
    let sql = `INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)`;

    let values = [delivery.address, delivery.receiver, delivery.contact];
    // conn.query(sql, values, (err, results) => {
    //     if (err) {
    //         //서버측 에러
    //         console.log(err);
    //         return res.status(StatusCodes.BAD_REQUEST).end();
    //     }

    //     delivery_id = 3; //results.insertId; //가장 최근에 들어간 데이터의 id
    //     return res.status(StatusCodes.OK).json(results);
    // });
    sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id) 
    VALUES (?,?,?,?,?)`;
    values = [firstBookTitle, totalQuantity, totalPrice, userId, delivery_id];
    // firstBookTitle : 대표책제목
    // conn.query(sql, values, (err, results) => {
    //     if (err) {
    //         //서버측 에러
    //         console.log(err);
    //         return res.status(StatusCodes.BAD_REQUEST).end();
    //     }
    //     order_id = results.insertId;
    //     console.log(order_id);
    //     return res.status(StatusCodes.OK).json(results);
    // });

    sql = `INSERT INTO orderedBook (order_id, book_id, quantity) VALUES ?;`;
    //items.. 는 배열이고 이걸 하나씩 꺼내서 (forEach문을 돌려야 한다.)
    values = [];
    items.forEach((item) => {
        values.push([order_id, item.book_id, item.quantity]);
        console.log(values);
    });
    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);
    });
};

💻 4일차 (4.18)

도서 정보 API 설계 및 구현 프로젝트/랜덤 데이터 생성 API설계 및 구현 프로젝트(11)

1. Node.js 비동기화

//[2기] 이현철
const conn = require("../mariadb");
const { StatusCodes } = require("http-status-codes");

const order = (req, res) => {
    const {
        items,
        delivery,
        totalQuantity,
        totalPrice,
        userId,
        firstBookTitle,
    } = req.body;
    let delivery_id;
    let order_id;
    let sql = `INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)`;

    let values = [delivery.address, delivery.receiver, delivery.contact];
    conn.query(sql, values, (err, results) => {
        if (err) {
            //서버측 에러
            console.log(err);
            return res.status(StatusCodes.BAD_REQUEST).end();
        }

        delivery_id = results.insertId; //가장 최근에 들어간 데이터의 id
        console.log(delivery_id);
        console.log(results.insertId);
    });
    console.log(delivery_id);

    sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id) 
    VALUES (?,?,?,?,?)`;
    values = [firstBookTitle, totalQuantity, totalPrice, userId, delivery_id];
    // firstBookTitle : 대표책제목
    conn.query(sql, values, (err, results) => {
        if (err) {
            //서버측 에러
            console.log(err);
            return res.status(StatusCodes.BAD_REQUEST).end();
        }
        order_id = results.insertId;
    });

    sql = `INSERT INTO orderedBook (order_id, book_id, quantity) VALUES ?;`;
    //items.. 는 배열이고 이걸 하나씩 꺼내서 (forEach문을 돌려야 한다.)
    values = [];
    items.forEach((item) => {
        values.push([order_id, item.book_id, item.quantity]);
    });
    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);
    });
};

const getOrders = (req, res) => {
    res.json({
        message: "주문 내역 조회",
    });
};

const getOrderDetail = (req, res) => {
    res.json({
        message: "주문 상세 조회",
    });
};

module.exports = {
    order,
    getOrderDetail,
    getOrders,
};

위와 같이 코드를 작성해보면 아래 사진에 대한 부분에서 콘솔이 밖에 있는 로그가 먼저 찍히고 나서 안에 있는 로그가 찍히는 일이 발생하여 코드에서 오류가 발생한다.


Node.js 비동기 처리 방식

  • 비동기 발생
    • 실행되는 코드가 기다려야 하는 시간이 생긴다는 의미이다.
    • 일단 이전 시간이 오래 걸리면, 기다리지 않고 다음 코드를 무작정 실행한다.
    • ex) setTimeout(), setInterval(), query()
  • 비동기 처리
    • 비동기가 필요 없을 때도 있다.
    • 이전 코드들의 시간을 모두 다 기다려준다. 즉 순서에 맞춰서 코드를 실행해준다.
  • 처리하는 방식 4가지
      1. 콜백 함수 : 할 일 다 하고, 이거 실행해줘(순서에 맞춰서 이걸 뒤에 실행해줘)
      1. promise (resolve, reject)
      1. then & catch
      1. ES2017 promise => async & await

위의 사진들을 보면 Node.js의 비동기 처리 방식에 대한 내용인데, 우리의 코드가 에러가 발생하는 이유를 잘 보여준다.
왜냐하면 우리의 코드를 잘 보면 delivery 테이블을 먼저 채우고 거기서 나온 delivery_id를 가지고 orders 테이블을 채우고 또 거기서 나온 데이터를 사용하여 orderedBooks 테이블을 채우기 때문에 총 쿼리문이 3개인데 Node에서는 쿼리문이 나오게 된다면 기다려주지 않고 바로 다음 코드를 실행하기 때문에 delivery 테이블에는 데이터가 잘 들어가지만 그 다음인 orders 테이블에 데이터를 삽입할 때 delivery_id가 아직 넘어오기 전에 쿼리문이 실행됐기 때문에 오류가 발생하는 것이다.

2. Promise 객체

Promise가 무엇인가?

프로미스는 자바스크립트 비동기 처리에 사용되는 객체이다. 여기서 자바스크립트의 비동기 처리란 "특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 자바스크립트의 특성"을 의마한다.

Promise가 왜 필요한가?

프로미스는 주로 서버에서 받아온 데이터를 화면에 표시할 때 사용한다. 일반적으로 웹 애플리케이션을 구현할 때 서버에서 데이터를 요청하고 받아오기 위해 API를 사용할 때 데이터를 받아오기도 전에 마치 데이터를 다 받아온 것 마냥 화면에 데이터를 표시하려고 하면 오류가 나거나 빈 화면이 뜨기 때문에 사용하는 것이 바로 Promise이다.

3. async

// async-await : Promise 객체를 좀 더 쉽고 편하게 사용하는 문법
//즉 비동기 처리가 쉽다.

//async 함수
// ____ function f() {} : 일반 함수
// async function f() {} : async 함수

async function f() {
  return 7;
  // async 함수는 문조건 프로미스 객체를 반환한다.
  // 만약 반환값이 프로미스가 아니면, 프로미스로 감싼다.
}

f().then(
  function(result){
	console.log("promiser resolve : ", result);
  }
}

4. await

//async의 두번째 기능
//Promise 객체가 일이 끝날 때까지 기다릴 수 있는
//공간을 제공한다.
//await은 async함수 안에서만 사용가능
async function f() {
  let promise = new Promise(function(resolve, reject){
	setTimeout(() => resolve("완료!", 3000);
  });
  let result = await promise; //promise객체가 일 다 할때까지 기다려
  console.log(result); //3초 뒤 완료! 출력
}

5. 3개의 promise 순서대로 실행하기

//async의 두번째 기능
//Promise 객체가 일이 끝날 때까지 기다릴 수 있는
//공간을 제공한다.
//await은 async함수 안에서만 사용가능
async function f() {
  //promise 객체 하나당 쿼리 하나라고 생각하면 된다.
  let promise1 = new Promise(function(resolve, reject){
	setTimeout(() => resolve("첫번째 쿼리", 3000);
  });
  let result = await promise1; //promise1객체가 일 다 할때까지 기다려
  
  let promise2 = new Promise(function(resolve, reject){
	setTimeout(() => resolve("두번째 쿼리 with" + result1, 3000);
  });
  let result2 = await promise2;
  
  
  let promise3 = new Promise(function(resolve, reject){
	setTimeout(() => resolve("세번째 쿼리 with" + result2 , 3000);
  });
  let result2 = await promise3;
  
}

//결과 (3초마다)
//첫번째 쿼리
//두번째 쿼리 with 첫번째 쿼리
//세번째 쿼리 with 두번째 쿼리 with 첫번째 쿼리

💻 5일차 (4.19)

도서 정보 API 설계 및 구현 프로젝트/랜덤 데이터 생성 API설계 및 구현 프로젝트(12)

1. 주문하기 API 완성

const order = async (req, res) => {
    const conn = await mariadb.createConnection({
        host: "localhost",
        user: "root",
        database: "Bookshop",
        password: "root1234",
        dateStrings: true,
    });
    const {
        items,
        delivery,
        totalQuantity,
        totalPrice,
        userId,
        firstBookTitle,
    } = req.body;

    // delievery 테이블 삽입
    let sql = `INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)`;
    let values = [delivery.address, delivery.receiver, delivery.contact];
    let [results] = await conn.execute(sql, values);
    console.log(results);
    let delivery_id = results.insertId;

    //orders 테이블에 삽입하는 코드
    sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id)
    VALUES (?,?,?,?,?)`;
    values = [firstBookTitle, totalQuantity, totalPrice, userId, delivery_id];
    // firstBookTitle : 대표책제목
    [results] = await conn.execute(sql, values); //이 코드가 끝날때까지 밑의 코드가 진행되면 안되기 때문에 await
    let order_id = results.insertId;

    //orderedBook 삽입하는 코드
    sql = `INSERT INTO orderedBook (order_id, book_id, quantity) VALUES ?;`;
    //items.. 는 배열이고 이걸 하나씩 꺼내서 (forEach문을 돌려야 한다.)
    values = [];
    items.forEach((item) => {
        values.push([order_id, item.book_id, item.quantity]);
    });
    [results] = await conn.query(sql, [values]);

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

위에서 conn.query대신 conn.execute를 사용한다.. 이유는 아직 모름..

2. 테이블 delete, truncate 차이

2.1 mysql 데이터 삭제하는 방법

    1. DELETE
    • DELETE 로 삭제하게 되면 auto-increment는 1로 초기화되지 않고 삭제 전 제일 큰 수부터 진행된다.
    • DELETE FROM 테이블명 (WHERE 조건);
    • 조건이 없으면 모든 행이 삭제된다 => 테이블은 남아있음
    1. DROP
    • DROM TABLE 테이블명;
    • 테이블을 째로 날리는거
    1. truncate
    • TRUNCATE 테이블명;
    • 조건이 있건 없건 모든 행이 삭제된다.
    • 삭제해도 테이블은 남아있다.
    • auto-increment 까지 초기화해준다.

2.2 주문하기 테스트 전 데이터베이스 정리하기

  1. truncate orderedBook
  2. truncate orders
    ..를 하면 1번은 잘 실행이 되는데 2번에서 truncate 할 수 없다고 오류가 난다.
    오류가 발생하는 이유는 orders.id가 orderedBook의 order_id에서 사용되고 있다고 인식하기 때문이다.. 하지만 우리는 앞서 orderedBook을 초기화했기 때문에 삭제해도 된다는 것을 알고있다..

해결법 )

SET FOREIGN_KEY_CHECKS = 0 

위의 코드는 우리 지금은 체크하지 말자! 라는 뜻
그 후 truncate를 해주고 다시 1로 세팅해주면 된다.

3. 주문하기 코드 완성 (주문한 상품 장바구니에서 제거하는 기능만 제외)

const order = async (req, res) => {
    const conn = await mariadb.createConnection({
        host: "localhost",
        user: "root",
        database: "Bookshop",
        password: "root1234",
        dateStrings: true,
    });
    const {
        items,
        delivery,
        totalQuantity,
        totalPrice,
        userId,
        firstBookTitle,
    } = req.body;

    // delievery 테이블 삽입
    let sql = `INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)`;
    let values = [delivery.address, delivery.receiver, delivery.contact];
    let [results] = await conn.execute(sql, values); //반환을 배열형태로 한다.
    console.log(results);
    let delivery_id = results.insertId;

    //orders 테이블에 삽입하는 코드
    sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id)
    VALUES (?,?,?,?,?)`;
    values = [firstBookTitle, totalQuantity, totalPrice, userId, delivery_id];
    // firstBookTitle : 대표책제목
    [results] = await conn.execute(sql, values); //이 코드가 끝날때까지 밑의 코드가 진행되면 안되기 때문에 await
    let order_id = results.insertId;

    //orderedBook 삽입하는 코드
    sql = `INSERT INTO orderedBook (order_id, book_id, quantity) VALUES ?;`;
    //items.. 는 배열이고 이걸 하나씩 꺼내서 (forEach문을 돌려야 한다.)
    values = [];
    items.forEach((item) => {
        values.push([order_id, item.book_id, item.quantity]);
    });
    results = await conn.query(sql, [values]);

    return res.status(StatusCodes.OK).json(results[0]);
};

response)

4. cartItemId Delete SQL 생각하기

장바구니에서 체크한 책들을 기반으로 넘어오기 때문에 cartItemId를 갖고있다.
우리가 이전에 구현하지 않은 장바구니에서 주문한 상품 삭제하는 기능은 이 cartItemId를 사용할 것이다.

cartItems)

orderedBook)

업로드중..

위의 두 사진을 보면 장바구니에 ascript
book_id가 2, 3, 4, 8번 책이 들어있는 것을 확인할 수 있다. 근데 orderedBook테이블을 보면 3, 4, 8번 책을 주문했다고 나와있다.
그러면 우리가 지금 해야할 것은 cartItems 테이블에 book_id가 2인 책만 두고 삭제하는 기능을 구현해야 한다.

완성된 주문하기 API

const order = async (req, res) => {
    const conn = await mariadb.createConnection({
        host: "localhost",
        user: "root",
        database: "Bookshop",
        password: "root1234",
        dateStrings: true,
    });
    const {
        items,
        delivery,
        totalQuantity,
        totalPrice,
        userId,
        firstBookTitle,
    } = req.body;

    // delievery 테이블 삽입
    let sql = `INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)`;
    let values = [delivery.address, delivery.receiver, delivery.contact];
    let [results] = await conn.execute(sql, values); //반환을 배열형태로 한다.
    console.log(results);
    let delivery_id = results.insertId;

    //orders 테이블에 삽입하는 코드
    sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id)
    VALUES (?,?,?,?,?)`;
    values = [firstBookTitle, totalQuantity, totalPrice, userId, delivery_id];
    // firstBookTitle : 대표책제목
    [results] = await conn.execute(sql, values); //이 코드가 끝날때까지 밑의 코드가 진행되면 안되기 때문에 await
    let order_id = results.insertId;

    //items를 가지고 장바구니에서 book_id와 quantity를 조회
    sql = `SELECT book_id, quantity FROM cartItems WHERE id IN (?)`;
    let [orderItems, fields] = await conn.query(sql, [items]);

    //orderedBook 삽입하는 코드
    sql = `INSERT INTO orderedBook (order_id, book_id, quantity) VALUES ?;`;
    //items.. 는 배열이고 이걸 하나씩 꺼내서 (forEach문을 돌려야 한다.)
    values = [];
    orderItems.forEach((item) => {
        values.push([order_id, item.book_id, item.quantity]);
    });
    results = await conn.query(sql, [values]);
    let result = await deleteCartItems(conn, items);
    return res.status(StatusCodes.OK).json(result);
};

5. 주문내역조회

req에는 회원인증에 필요한 jwt가 들어오지만 우리는 아직 구현하지 않고 일단 user_id로 구현해보겠다.


const getOrders = async (req, res) => {
    const conn = await mariadb.createConnection({
        host: "localhost",
        user: "root",
        database: "Bookshop",
        password: "root1234",
        dateStrings: true,
    });
    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 { id } = req.params;
    const conn = await mariadb.createConnection({
        host: "localhost",
        user: "root",
        database: "Bookshop",
        password: "root1234",
        dateStrings: true,
    });
    sql = `SELECT book_id, title, author, price, quantity
            FROM orderedBook LEFT JOIN books ON orderedBook.book_id = books.id 
            WHERE order_id = ?`;
    let [rows, fields] = await conn.query(sql, [id]);
    return res.status(StatusCodes.OK).json(rows);
};

주간 회고

    1. 좋았던 것
    • 복잡한 DB에 대한 ERD 설계하는 방법에 대해 배울 수 있었던 점
    1. 배운 것
    • 쿼리문에 사용가능한 다양한 문법을 배운 것
    • ERD를 차근차근 설계하는 것
    1. 부족했던 것
    • 자기주도적으로 학습하는 자세(코드를 먼저 생각해보려고 하지 않았다.)
    • 생활패턴 (늦게자고 늦게 일어나는 문제)
    1. 바라는 것
    • 토이프로젝트를 시작하려고 하는데 막히는 것 없이 진행됐으면 좋겠다.
    • 일찍 좀 일어나보자...
profile
이현철

0개의 댓글