포스트

Kream 사이트 클론 프로젝트

의류 경매 서비스 KREAM을 모티브로 2주간 진행한 SHOEKREAM 클론 프로젝트. 선정 이유, 적용 기술, 데이터베이스 다이어그램, 백엔드 구현 기능을 정리한다.

Kream 사이트 클론 프로젝트

SHOEKREAM Project

shokreammain

🎇 팀명 : shoekream - 슈크림

의류 경매 서비스를 제공하는 KREAM을 모티브로 제작하게 된 SHOE-KREAM 팀의 프론트엔드 레포지토리 입니다. 짧은 프로젝트 기간동안 개발에 집중해야 하므로 디자인/기획 부분만 클론했습니다. 개발은 초기 세팅부터 전부 직접 구현했으며, 백앤드와 연결하여 실제 사용할 수 있는 서비스 수준으로 개발할 수 있도록 2주간 고군분투 하였습니다.

프로젝트 선정이유

  • 조사결과, 해당 사이트의 경매 입찰 기능과 결제 플로우, 차트 구현, 상품리스트 필터 구현 등 배울 점이 많다고 판단하여 선정하게 되었습니다.

📅 개발 기간 및 개발 인원

  • 개발 기간 : 2021/10/18 ~ 2021/10/29
  • 개발 인원
    👨‍👧‍👦 Front-End 4명 : 김현진, 박산성, 이선호, 하상영
    👨‍👧‍👦 Back-End 3명 : 박치훈, 양가현, 송영록
  • Back-end github 링크

🎬 프로젝트 구현 영상

  • 🔗 [구현영상] : https://youtu.be/N63MUdDmDFI

⛓️ 적용 기술

  • Front-End : HTML5, CSS3, React, SASS, JSX
  • Back-End : Python, Django, MySQL, jwt, bcypt, AWS RDS, AWS EC2
  • Common : Git, Github, Slack, Trello, Postman or Insomnia

⚙️ 데이터베이스 Diagram

kream

💻 구현 기능

Endpoint documentation - Postman API

BACKEND

로그인 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import jwt
import requests
from json.decoder import JSONDecodeError

from django.http  import JsonResponse
from django.views import View

from users.models import User
from my_settings  import SECRET_KEY, ALGORITHMS

class KakaoLogin(View):
    def get(self, request):
        try: 
            token = request.headers.get('Authorization')

            if token == None:
                return JsonResponse({'messsage': 'INVALID_TOKEN'}, status=401)

            kakao_account = requests.get('https://kapi.kakao.com/v2/user/me', headers = {'Authorization': f'Bearer {token}'}).json()
            print('::::kakao_account:', kakao_account)

            if not User.objects.filter(kakao_id=kakao_account['id']).exists():
                user = User.objects.create(
                    kakao_id = kakao_account['id'],
                    email    = kakao_account['kakao_account']['email'],
                    name     = kakao_account['kakao_account']['profile']['nickname']
                )
            user = User.objects.get(kakao_id=kakao_account['id'])

            access_token = jwt.encode({'user_id': user.id}, SECRET_KEY, algorithm=ALGORITHMS)

            return JsonResponse({'access_token': access_token}, status=201)

        except KeyError:
            return JsonResponse({'message': 'KEY_ERROR'}, status=400)
        
        except JSONDecodeError:
            return JsonResponse({'message': 'JSON_DECODE_ERROR'}, status=400)

        except jwt.DecodeError:
            return JsonResponse({'message': 'JWT_DECODE_ERROR'}, status=400)

        except ConnectionError:
            return JsonResponse({'message': 'CONNECTION_ERROR'}, status=400)
  • 카카오 연동 로그인 구현
  • Authorization 헤더에서 카카오 엑세스 토큰을 받아옴
  • request.get() 함수를 통해 카카오 API 엔드포인트로 사용자 정보 요청, json 형식으로 받아옴
  • 카카오 아이디로 사용자 조회, 없으면 새로운 유저 생성
  • 사용자 정보에서 user모델의 id를 이용하여 jwt 토큰 발급
  • jwt.encode()함수에 SECRET_KEY와 ALGORITHMS를 활용하여 JWT 토큰 생성 후 반환
  • 프론트엔드에 발상할 오류의 종류를 예외처리하여 명확히 명시

로그인 데코레이터 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import json
import jwt

from django.http  import JsonResponse

from my_settings  import SECRET_KEY, ALGORITHMS
from users.models import User

def login_decorator(func):
    def wrapper(self, request, *args, **kwargs):
        if 'Authorization' not in request.headers : 
            return JsonResponse ({'message' : 'UNAUTHORIZED'}, status=401)

        access_token = request.headers.get('Authorization')
        
        try:
            payload      = jwt.decode(access_token, SECRET_KEY, algorithms=ALGORITHMS)
            user         = User.objects.get(id=payload['user_id'])
            request.user = user

        except jwt.exceptions.DecodeError:
            return JsonResponse({'MESSAGE': 'INVALID_TOKEN'}, status=401)

        except User.DoesNotExist:
            return JsonResponse({'MESSAGE': 'INVALID_USER'}, status=401)

        return func(self, request,  *args, **kwargs)

    return wrapper
  • python decorator 활용
  • 로그인 성공 시 토큰 발급(jwt)
  • 로그인 실패 시 예외처리
  • jwt.decode() 함수를 통해 토큰 검증
  • 디코딩한 토큰에서 user_id 추출, user모델에서 해당 user_id 조회 후 가져옴
  • 추출한 사용자를 request.user에 저장, 이후 로그인 상태 유지

상세 페이지

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class DetailProductView(View) :
    def get(self, request, product_id) :
        try :
               
            buy_price_filter  = (Q(productsize__bidding__bidding_position_id = 2) & Q(productsize__bidding__bidding_status_id = 1))
            sell_price_filter = (Q(productsize__bidding__bidding_position_id = 1) & Q(productsize__bidding__bidding_status_id = 1))

            if not Product.objects.filter(id = product_id).exists():
                return JsonResponse({'MESSAGE':'product_id_not_exist'}, status = 404)
            
            product  = Product.objects\
                                    .annotate(buy_price = Min('productsize__bidding__price', filter = buy_price_filter), 
                                            sell_price = Max('productsize__bidding__price', filter = sell_price_filter))\
                                    .get(id = product_id)
                                                    
            orders   = Order.objects.\
                                    filter(bidding__product_size__product_id = product_id).\
                                    order_by('-created_at')

            wishlist = len(Wishlist.objects.filter(id=product_id).all())
            
            product_detail = [{
                                'product_id'      : product.id,
                                'name'            : product.name,
                                'brand_name'      : product.brand.name,
                                'release_price'   : product.release_price,
                                'model_number'    : product.model_number,
                                'image_list'      : [image.image_url for image in product.productimage_set.all()],
                                'recent_price'    : orders.first().bidding.price if orders.exists() else None,
                                'buy_price'       : product.buy_price,
                                'sell_price'      : product.sell_price,
                                'total_wishlist'  : wishlist,
                            }]

            return JsonResponse({'product_detail' : product_detail}, status=200)

        except AttributeError as e :
            return JsonResponse({'message' : f'{e}'}, status=400)        
        except TypeError as e :
            return JsonResponse({'message' : f'{e}'}, status=400)
  • 필터 조건 정의: 구매 입찰 정보중 입찰 상태가 유효한 조건(입찰 포지션 2, 상태가 1인 경우)과 판매 입찰 정보 중 입찰 상태가 유효한 조건(입찰 포지션 1, 상태가 1인 경우)
  • 효율적인 데이터 처리와 코드의 간결성을 위하여 annotate()함수 활용
  • 데이터베이스 단계서 집계 함수(Min, Max)를 활용하여 필요한 값을 미리 계산

상품 리스트 출력 API(ProductView)

1

브랜드 리스트 출력 API(BrandView) ```python class BrandView(View): def get(self, request): brands = Brand.objects.all()

1
2
3
4
5
        brand_list = [{
                    'brand_id'   : brand.id,
                    'brand_name' : brand.name,
                } for brand in brands]
        return JsonResponse({'brand_list' : brand_list}, status = 200) ``` - 메인 페이지, 제품 페이지 상단 브랜드 출력을 위하여 API 작성

❗ Reference

  • 이 프로젝트는 KREAM 사이트를 참조하여 학습목적으로 만들었습니다.
  • 실무수준의 프로젝트이지만 학습용으로 만들었기 때문에 이 코드를 활용하여 이득을 취하거나 무단 배포할 경우 법적으로 문제될 수 있습니다.
  • 이 프로젝트에서 사용하고 있는 사진 모두는 copyright free 사이트들의 이미지들을 취합 및 canva 에서 직접 제작한 이미지들로 제작되었습니다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.