[Project] 아뤼따움 Aruitaum - 후기

Jane Yeonju Kim·2022년 5월 5일
2

Project

목록 보기
4/4
post-thumbnail

👆 디자인 천재 슬아님이 만들어주신 메인 배너 이미지(제 사진이 아닙니다!🤗), 로고 🥰



프로젝트 소개

웹 쇼핑몰의 기본 기능에 충실하게 만들기 위해 아리따움 사이트의 디자인 부분을 참고해서 직접 만들어본 팀 프로젝트 입니다!
✨Youtube 시현 영상✨

팀원 구성: 백엔드 2명 (김연주, 송정석) & 프론트 4명 (김영서, 이경원, 이슬아, 최정환)
               Github repo: Back-end           Github repo: Front-end

기간: 2022.04.18 ~ 2022.04.29 (12일)
기능: 회원가입 / 로그인 / 🛒장바구니 / 카테고리별 정렬 / 🔍검색 /
        후기(+ 후기에 대한 💝좋아요) / 📌최근 본 상품들 / 반응형 화면 구현



Aruitaum 프로젝트를 통해 배우고 얻은 것들

1. multer / path / fs 라이브러리 사용해보기

1차 프로젝트 때부터 욕심 났던 😎서버에 이미지 보내기 기능을 완성해보고 싶어서 이미지를 받아서 처리하는 multer 라이브러리, 파일 이름 및 경로를 관리할 수 있는 path 라이브러리, 파일을 관리할 수 있는 fs 라이브러리를 사용했습니다!

후기를 작성할 때 사진을 넣을 수 있게 만들었습니다.

사진을 담을 폴더가 없으면 생성 ➡
이미지 저장 경로 지정 및 이름 지정 ➡
다음 함수에 전달(multer를 이용하면 request.file(s)에 자동으로 이미지 관련 정보를 담아줍니다.)

// reviewRoute.js
const multer = require('multer');
const path = require('path');
const fs = require('fs');


const makeReviewFolder = async (req, res, next) => {
    // upload 폴더 지정 (없으면 생성)
    try {
        fs.readdirSync(`data/uploads/review`);
    } catch (error) {
        fs.mkdirSync(`data/uploads/review`, { recursive: true }, err => {
            console.log(err);
        });
    }
    next();
};

const upload = multer({
    storage: multer.diskStorage({
        // 업로드된 이미지 저장 경로 지정
        destination(req, file, cb) {
            cb(null, `data/uploads/review`);
        },
        // 업로드된 이미지 파일 이름 지정
        filename(req, file, cb) {
            const ext = path.extname(file.originalname);
            cb(null, path.basename(file.originalname, ext) + 
               Date.now() + ext);
        },
    }),
    // 파일 크기 제한 10MB
    limits: { fileSize: 10 * 1024 * 1024 },
});


// form태그 name속성을 "reviewImage"로 일치시켜야 파일을 받을 수 있습니다.
router.post(
    '/',
    validateToken.validateToken,
    makeReviewFolder,
    upload.array('reviewImage'),
    function (req, res, next) {
        next();
    },
    reviewController.makeReview
);

이미지 저장 경로를 userId별로 폴더를 생성해서 지정하려고 했지만..
저장 경로 설정 ➡ Controller단에서 request객체 접근
이 순서를 바꿀 수가 없어서 결국 review폴더에 몽땅 저장하도록 구현했습니다! 😇

2. ws (WebSocket) 라이브러리 기본적인 사용법

웹 소켓 프로토콜을 공부한다면 사실 Socket.io라는 훌륭한 Framework가 있다는 것도 금방 찾을 수 있습니다!
그런데 채팅 방이 하나만 존재하고 단순한 기능을 구현하는 입장에서 ws 라이브러리만으로도 충분히 만들어 볼 수 있었습니다.

const http = require('http');
const WebSocket = require('ws');
const express = require('express');
const chatService = require('./services/chatService');
const PORT = process.env.PORT;

const app = express();
const Server = http.createServer(app);

// HTTP프로토콜로 생성한 서버 위(?)에 WebSocket프로토콜로 서버를 생성합니다.
const wss = new WebSocket.Server({ server: Server });

const start = async () => {
    try {
        Server.listen(PORT, () =>
            console.log(`Server is listening on ${PORT}`)
        );

      	// WebSocket프로토콜로 연결이 될 때 실행할 기능들이 
      	// chatService.handleConnection에 담겨있습니다.
        wss.on('connection', chatService.handleConnection);
    } catch (err) {
        console.error(err);
        await prisma.$disconnect();
    }
};

start();

3. "좋아요"의 구현

프론트와 API Guide로 구체적으로 소통해야 한다고 느끼게 되었던, 미팅 때 합의한 추가 구현 기능이었음에도 정환님의 빠른 피드백으로 완성시킬 수 있었던 후기에 대한 좋아요 기능입니다!

이 기능을 추가하게 되면 후기를 조회할 때도 후기에 대한 좋아요 정보를 추가해서 반환해야 한다는 것을 알게 되었습니다! 그리고 후기에 대한 좋아요의 테이블에서의 id를 전달해야 쉽게 삭제도 가능하다는 것을 배웠습니다!

네, 생각했던 것만큼 복잡한 기능이었습니다! 🤓

4. 느슨한 타입의 장바구니 API 구현

제품 상세 페이지에서 수량을 정하고 장바구니에 추가 버튼을 누르면 장바구니에 자동으로 집계가 되는 API를 구현하기 위해 고민을 해봤습니다. 그러다가 자바스크립트의 "느슨한 타입" 특성처럼 기존 장바구니에 있으면 추가하고 없으면 생성하는 구조를 만들어보기로 했습니다!

const updateCart = async (product_id, user_id, quantity, setQuantity) => {
    try {
      	// 현재 장바구니 정보 가져오기
        const currentCart = await cartDao.getCurrentCart(Number(user_id));
        const currenProducts = await Promise.all(
            currentCart.map(cart => cart.products.id)
        );
        const currenProductsIdx = currenProducts.indexOf(Number(product_id))

      	// 장바구니에 없다면 생성
        if (currenProductsIdx === -1) {
            await cartDao.addNewItemToCart(
                Number(product_id),
                Number(user_id),
                Number(quantity)
            );
        // 장바구니에 있다면 기존 수량에 추가하기(기존 수량 + quantity)
        // 10개 수량 제한이 있으므로 10개를 넘어가면 최대 수량인 10개로 수정
        } else if (currenProductsIdx !== -1 && !setQuantity) {
            if (
                currentCart[currenProductsIdx].quantity + Number(quantity) >
                10
            ) {
                quantity = 10 - currentCart[currenProductsIdx].quantity;
            }
            await cartDao.addMoreItemToCart(
                Number(product_id),
                Number(user_id),
                Number(quantity),
                Number(currentCart[currenProductsIdx].quantity)
            );
        // 장바구니에 있으면서 수량을 바꿔야할 때 
        //전달받은 수량으로(setQuantity) 수정하기
        } else if (currenProductsIdx !== -1 && setQuantity) {
            await cartDao.updateItemInCart(
                Number(product_id),
                Number(user_id),
                Number(setQuantity)
            );
        }

        // 마지막으로 현재 장바구니 정보 반환하기
        return await cartDao.getCurrentCart(user_id);
    } catch (error) {
        throw await error;
    }
};

🚧리팩토링🚧

그런데.. 사실은 DB구조를 하나만 바꿔서 구현할 수 있었습니다!
product_id, user_id 두 개를 묶어서 UNIQUE 키를 걸어두면 Create와 Update를 분리할 수 있었습니다. 이렇게 분리하는 편이 더 가독성이 좋아서 바꾸고 싶지만 DB구조를 바로 바꾸면 다른 팀원이 작성한 코드에도 영향을 줄 수가 있기 때문에..! 아래의 방식을 선택했습니다.

// 장바구니 생성 함수
const createCart = async (product_id, user_id, quantity) => {
		const cart = await cartDao.getCurrentCart(user_id, 
                                                  product_id);

  	// 현재 장바구니에 같은 상품이 있을 때
    if (cart[0]) {
    	return await cartDao.updateCart(cart[0].id, 
                                        cart[0].quantity+quantity)
    // 같은 상품이 없을 때
    } else {
		return await cartDao.createCart(user_id, 
                                        product_id, 
                                        quantity)
    }
}

// 장바구니 수정 함수
const updateCart = async (cart_id, quantity) => {
	return await cartDao.updateCate(cart_id, 
                                    quantity)
}



앞으로 프로젝트 하면서 더 신경 써야 겠다고 느낀 것들


일정 관리

🌞Daily standup meeting, 📅Weekly Sprint meeting이 정말 중요하다고 느꼈습니다! 매일 아침마다 서로의 진행상황을 체크하고 저녁에 진행했던 코드를 Pull Request로 남겨두는 방식으로 진행했는데 굉장히 효율적이었습니다.

각자 만들고 있는 기능을 유기적으로 생각할 수 있고, Sprint meeting시간을 더 건설적으로 보낼 수 있었습니다. 이 부분은 같은 일산에 사는 영서님이 주도적으로 진행해주셨는데 🙇‍♀️ 너무너무 감사했습니다!


API Guide

프론트에서 필요한 기능을 먼저 제시하느냐, 백에서 먼저 만들고 프론트에서 맞추느냐에 대한 고민을 하게 되었습니다. 2차 프로젝트를 진행하면서는 그래도 조금 여유가 생겨서😊 어떤 게 더 먼저일까🐣🐓하는 생각을 해봤습니다.

현업에서는 어떻게 진행이 되는지 실제로 일을 해봐야 알겠지만..
어쨌든 API Guide는 잘 만들어놔야 혼선이 없다는 생각이 들었습니다!🥰🥰

profile
안녕하세요! 김개발자입니다 👩‍💻 🐈

0개의 댓글