├── .gitignore ├── README.md ├── backend ├── .gitignore ├── ai │ ├── __pycache__ │ │ └── ai.cpython-37.pyc │ ├── ai.py │ ├── gcs.py │ ├── image │ │ └── e42eabc3-e1f3-42e7-a36f-83c23cc326c9.png │ └── keras_model.h5 ├── api │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-37.pyc │ │ ├── admin.cpython-37.pyc │ │ ├── apps.cpython-37.pyc │ │ ├── forms.cpython-37.pyc │ │ ├── img_upload.cpython-37.pyc │ │ ├── models.cpython-37.pyc │ │ ├── serializers.cpython-37.pyc │ │ ├── tasks.cpython-37.pyc │ │ ├── urls.cpython-37.pyc │ │ ├── uuid.cpython-37.pyc │ │ └── views.cpython-37.pyc │ ├── admin.py │ ├── apps.py │ ├── bigquery.py │ ├── img_upload.py │ ├── models.py │ ├── serializers.py │ ├── tasks.py │ ├── tests.py │ ├── urls.py │ ├── uuids.py │ └── views.py ├── dockerfile ├── lego2me │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-37.pyc │ │ ├── celery.cpython-37.pyc │ │ ├── settings.cpython-37.pyc │ │ ├── urls.cpython-37.pyc │ │ ├── wsgi.cpython-37.pyc │ │ └── yasg.cpython-37.pyc │ ├── asgi.py │ ├── celery.py │ ├── settings.py │ ├── urls.py │ ├── wsgi.py │ └── yasg.py ├── manage.py └── requirements.txt ├── docker-compose.prod.yml ├── docker-compose.yml ├── frontend ├── .babelrc ├── .eslintrc.json ├── .gitignore ├── Dockerfile ├── README.md ├── components │ ├── Banner.tsx │ ├── Footer.tsx │ ├── HowtoCard.tsx │ ├── IntroBanner.tsx │ ├── ItemSelect.tsx │ ├── MediumCard.tsx │ ├── Nav.tsx │ ├── ResultLego.tsx │ └── UploadImgButton.tsx ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages │ ├── _app.tsx │ ├── api │ │ └── hello.ts │ ├── index.tsx │ └── result.tsx ├── postcss.config.js ├── public │ ├── favicon.ico │ ├── images │ │ ├── Saly-10.png │ │ ├── Saly-14.png │ │ ├── Saly-38.png │ │ ├── Saly-7.png │ │ ├── lego1.png │ │ ├── lego2.png │ │ ├── lego2me.png │ │ └── lego_result.png │ ├── items │ │ ├── hair │ │ │ ├── hair1_black.png │ │ │ ├── hair2_black.png │ │ │ └── hair3_black.png │ │ ├── item_default.png │ │ └── lego_default.png │ ├── legoItem │ │ ├── Bottom │ │ │ ├── Black_Pants.png │ │ │ ├── Blue_Pants.png │ │ │ ├── Brown_Pants.png │ │ │ ├── Green_Pants.png │ │ │ ├── Grey_Pants.png │ │ │ ├── Orange_Pants.png │ │ │ ├── Purple_Pants.png │ │ │ ├── Red_Pants.png │ │ │ ├── White_Pants.png │ │ │ └── Yellow_Pants.png │ │ └── Top │ │ │ ├── Black_Shirts.png │ │ │ ├── Blue_Shirts.png │ │ │ ├── Brown_Shirts.png │ │ │ ├── Green_Shirts.png │ │ │ ├── Grey_Shirts.png │ │ │ ├── Orange_Shirts.png │ │ │ ├── Purple_Shirts.png │ │ │ ├── Red_Shirts.png │ │ │ ├── White_Shirts.png │ │ │ └── Yellow_Shirts.png │ ├── placeholder.webp │ └── vercel.svg ├── recoil │ └── states.ts ├── styles │ ├── Home.module.css │ └── globals.css ├── tailwind.config.js └── tsconfig.json ├── nginx.conf └── nginx └── nginx.conf /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | lego2meproject-3eaf5d63b3b9.json 3 | bigquery-339204-ae0dfd4ee5d8.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lego2me 2 | ![Frame 1](https://user-images.githubusercontent.com/54930877/151214445-027c87e3-d8c5-4cbc-aac9-7cec41e1e20d.jpg) 3 | ![151363026-4966f45c-4758-496f-bc26-aac1af291a63](https://user-images.githubusercontent.com/54930877/177606805-bb6f6c1d-e127-4cff-a776-492ab6085c5c.gif) 4 | 5 | ## Index 6 | - [Lego2me](#lego2me) 7 | - [1. Prerequisites](#1-prerequisites) 8 | - [2. Installation Process](#2-installation-process) 9 | - [3. Getting Started](#3-getting-started) 10 | - [4. File Manifest](#4-file-manifest) 11 | - [5. Copyrights / End User Licesnse](#5-copyrights--end-user-licesnse) 12 | - [6. Contact Information](#6-contact-information) 13 | 14 | ## 1. Prerequisites 15 | - Our service was created through the AI Application Development by Silicon Valley Engineering program organized by Headstart Silicon Valley. 16 | - https://flagly.org/ 17 | ### **Architecture** 18 | ![image](https://user-images.githubusercontent.com/54930877/151213519-4f122273-bca5-47b4-99e2-2f0aeb5b05f5.png) 19 | ### **Dataware** 20 | ![image](https://user-images.githubusercontent.com/54930877/151213688-5ee75c1f-e916-482f-890d-5bdf5c91352a.png) 21 | 22 | ## 2. Installation Process 23 | - Requires a computer with an nvidia GPU. 24 | ``` 25 | $ git clone https://github.com/sung1san3/Lego2me 26 | $ docker-compose up --build 27 | ``` 28 | ## 3. Getting Started 29 | - If you are running in a local environment, type localhost in the Internet browser address. 30 | - Click the 'get started' button at the top and upload a picture of yourself you want to make into a LEGO character. After editing the top and bottom, save and check the result. 31 | ![image](https://user-images.githubusercontent.com/54930877/151208137-9c34e377-4610-4d8c-b839-d198c4f57447.png) 32 | - Check out the results and try customizing your character! 33 | - Please leave a review for the results 34 | ![image](https://user-images.githubusercontent.com/54930877/151210758-16a7822d-d600-4cdd-abd8-1bb283b2606b.png) 35 | ![image](https://user-images.githubusercontent.com/54930877/151211337-11d81eed-b686-44a6-96b9-16c7bb6d9935.png) 36 | 37 | 38 | ## 4. File Manifest && API 39 | ``` 40 | ├── README.md 41 | ├── backend 42 | │   ├── ai 43 | │   ├── api 44 | │   ├── db.sqlite3 45 | │   ├── dockerfile 46 | │   ├── lego2me 47 | │   ├── manage.py 48 | │   └── requirements.txt 49 | ├── docker-compose.prod.yml 50 | ├── docker-compose.yml 51 | ├── frontend 52 | │   ├── Dockerfile 53 | │   ├── README.md 54 | │   ├── components 55 | │   ├── next-env.d.ts 56 | │   ├── next.config.js 57 | │   ├── node_modules 58 | │   ├── package-lock.json 59 | │   ├── package.json 60 | │   ├── pages 61 | │   ├── postcss.config.js 62 | │   ├── public 63 | │   ├── recoil 64 | │   ├── styles 65 | │   ├── tailwind.config.js 66 | │   └── tsconfig.json 67 | ├── nginx 68 | │   └── nginx.conf 69 | └── nginx.conf 70 | ``` 71 | 72 | ## 5. Copyrights / End User Licesnse 73 | Our project is not affiliated with any services of the LEGO company. 74 | This project is not intended for commercial use, please do not use it for commercial purposes. 75 | ## 6. Contact Information 76 | 77 | | Name | 최세연 |정태원 | 권종석 | 허민 | 78 | | ------- | --------------------------------------------- | ------------------------------------ | --------------------------------------------- | --------------------------------------- | 79 | | Profile | || | | 80 | | role | Team ㅣLeader,
Backend | Backend & AI | Backend & AI | Frontend | 81 | | Github | [@Seyeon_Choi](https://github.com/barabobBOB) | [@teawon](https://github.com/teawon) | [@jongseok Kwon](https://github.com/himJJong) | [@Heo Min](https://github.com/hhhminme) | 82 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | .pyc -------------------------------------------------------------------------------- /backend/ai/__pycache__/ai.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/backend/ai/__pycache__/ai.cpython-37.pyc -------------------------------------------------------------------------------- /backend/ai/ai.py: -------------------------------------------------------------------------------- 1 | from keras.models import load_model 2 | from PIL import Image, ImageOps 3 | import numpy as np 4 | from . import gcs 5 | import os 6 | 7 | def ai_model(filename, index): 8 | # Load the model 9 | model = load_model('/backend/ai/keras_model.h5') #학습시킨 model 파일의 경로 10 | 11 | dic = ['Red_Shirts','Orange_Shirts','Yellow_Shirts','Green_Shirts','Blue_Shirts','Purple_Shirts','Brown_Shirts','Grey_Shirts','Black_Shirts','White_Shirts', 12 | 'Red_Pants','Orange_Pants','Yellow_Pants','Green_Pants','Blue_Pants','Purple_Pants','Brown_Pants','Grey_Pants','Black_Pants','White_Pants'] 13 | # Create the array of the right shape to feed into the keras model 14 | # The 'length' or number of images you can put into the array is 15 | # determined by the first position in the shape tuple, in this case 1. 16 | data = np.ndarray(shape=(1, 224, 224, 3), dtype=np.float32) 17 | # Replace this with the path to your image 18 | 19 | # GSC에서 해당 이미지 다운로드 20 | gcs.download_blob(filename) 21 | image_path = "/backend/ai/image/"+filename 22 | image = Image.open(image_path).convert('RGB') #이미지 경로 23 | #resize the image to a 224x224 with the same strategy as in TM2: 24 | #resizing the image to be at least 224x224 and then cropping from the center 25 | size = (224, 224) 26 | image = ImageOps.fit(image, size, Image.ANTIALIAS) 27 | 28 | #turn the image into a numpy array 29 | image_array = np.asarray(image) 30 | # Normalize the image 31 | normalized_image_array = (image_array.astype(np.float32) / 127.0) - 1 32 | # Load the image into the array 33 | data[0] = normalized_image_array 34 | 35 | # run the inference 36 | prediction = model.predict(data) 37 | print(prediction) 38 | strValue = str(prediction[0]) 39 | strValue = strValue.replace("[","") 40 | strValue = strValue.replace("]","") 41 | strValue = strValue.replace("\n","") 42 | #기존의 prdiction[0] = [[a , b , c , d , e , f..를 문자열로 다루기 위해 다음과 같이 표현]] 43 | strArray = str(strValue).split(" ") 44 | #하나의 문자열을 20개의 라벨에 대한 값으로 각각 나눔 45 | 46 | dataArr = [] 47 | for i in range(20): 48 | dataArr.append(float(strArray[i])) 49 | #문자열로 나누어진 값을 실수형태의 리스트로 저장 50 | 51 | result = 0 #결과값 인덱스 52 | 53 | for i in range(index, index+10): #라벨 인덱스 구하기 54 | 55 | if(dataArr[index]/', views.Get_View.as_view()), 14 | path('scores/', views.Post_Score_View.as_view()) 15 | ] -------------------------------------------------------------------------------- /backend/api/uuids.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | import os 3 | 4 | # 파일 이름 랜덤 5 | def get_file_path(instance, filename): 6 | ext = filename.split('.')[-1] 7 | filename = "%s.%s" % (uuid.uuid4(), ext) 8 | return os.path.join('', filename) -------------------------------------------------------------------------------- /backend/api/views.py: -------------------------------------------------------------------------------- 1 | from .bigquery import bigquery_score_save 2 | from rest_framework import viewsets 3 | from rest_framework.views import APIView 4 | from rest_framework.viewsets import ModelViewSet 5 | from .serializers import * 6 | from .models import Img_upload 7 | from rest_framework.response import Response 8 | from rest_framework import status 9 | from .models import * 10 | 11 | import os.path 12 | from .img_upload import * 13 | import uuid 14 | 15 | from .tasks import ai_model 16 | 17 | # 이미지 업로드 및 ai 실행 18 | class PostViewSet(viewsets.ModelViewSet): 19 | 20 | queryset = Img_upload.objects.all() 21 | serializer_class = Img_upload_serializers 22 | 23 | def create(self, request, *args, **kwargs): 24 | data = request.data 25 | filename = data.__getitem__('img_title') 26 | serializer = self.get_serializer(data=request.data) 27 | serializer.is_valid(raise_exception=True) 28 | self.perform_create(serializer) 29 | headers = self.get_success_headers(serializer.data) 30 | newFileName_top = str(Img_upload.objects.filter(img_title=filename).values('img_top')[0]['img_top']) 31 | newFileName_bottoms = str(Img_upload.objects.filter(img_title=filename).values('img_bottoms')[0]['img_bottoms']) 32 | print(newFileName_top+' // '+newFileName_bottoms) 33 | 34 | 35 | db_delete(newFileName_top) 36 | 37 | bucket = "lego2me__image" 38 | 39 | # 구글 스토리지 업로드 40 | upload_blob(newFileName_top, bucket) 41 | upload_blob(newFileName_bottoms, bucket) 42 | 43 | task_id = str(uuid.uuid4()) 44 | print(task_id + 'task_ id 생성') 45 | 46 | # db 저장 47 | task = Task() 48 | task.id = task_id 49 | task.status = 'false' 50 | task.save() 51 | 52 | ai_model.deley(newFileName_top, newFileName_bottoms, task_id) 53 | 54 | task_dic = {} 55 | task_dic['task'] = task_id 56 | return Response(task_dic, status=status.HTTP_201_CREATED, headers=headers) 57 | 58 | # ai 실행 결과 59 | class Get_View(APIView): 60 | def get(self, request, slug, format=None): 61 | status = str(Task.objects.filter(id=slug).values('status')[0]['status']) 62 | if status == 'true': 63 | result = Task.objects.get(id=slug) 64 | serializer = Task_serializers(result) 65 | return Response(serializer.data) 66 | else: 67 | return Response(serializer.errors, status=400) 68 | 69 | 70 | # bicquery에 점수 저장 71 | class Post_Score_View(APIView): 72 | def post(self, request): 73 | data = request.data 74 | id = data.__getitem__('id') 75 | print(id) 76 | score = data.__getitem__('score') 77 | print(score) 78 | serializer = Starscore_serializers(data=request.data) 79 | if serializer.is_valid(): 80 | # bigquery 저장 81 | bigquery_score_save(id, score) 82 | serializer.save() 83 | return Response(serializer.data, status=201) 84 | return Response(serializer.errors, status=400) 85 | -------------------------------------------------------------------------------- /backend/dockerfile: -------------------------------------------------------------------------------- 1 | # pull official base image 2 | #FROM python:3.7-alpine 3 | FROM --platform=linux/x86_64 python:3.7 4 | #-> 여기도 두 주석의 위치 5 | ENV PYTHONDONTWRITEBYTECODE 1 6 | ENV PYTHONUNBUFFERED 1 7 | 8 | RUN mkdir /backend 9 | 10 | WORKDIR /backend 11 | 12 | ADD requirements.txt /backend 13 | ADD . /backend/ 14 | RUN pip3 install --upgrade pip 15 | 16 | RUN pip install djangorestframework djangorestframework-jwt 17 | 18 | #ERROR: Could not find a version that satisfies the requirement ruamel.yaml.clib==0.2.6 (from versions: 0.1.0, 0.1.2, 0.2.0, 0.2.2, 0.2.3, 0.2.4, 0.2.6) 19 | #13 90.28 ERROR: No matching distribution found for ruamel.yaml.clib==0.2.6 20 | # executor failed running [/bin/sh -c pip3 install -r requirements.txt]: exit code: 1 21 | # ERROR: Service 'backend' failed to build : Build failed 22 | RUN pip3 install -r requirements.txt 23 | 24 | RUN pip3 install bigquery -------------------------------------------------------------------------------- /backend/lego2me/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/backend/lego2me/__init__.py -------------------------------------------------------------------------------- /backend/lego2me/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/backend/lego2me/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /backend/lego2me/__pycache__/celery.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/backend/lego2me/__pycache__/celery.cpython-37.pyc -------------------------------------------------------------------------------- /backend/lego2me/__pycache__/settings.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/backend/lego2me/__pycache__/settings.cpython-37.pyc -------------------------------------------------------------------------------- /backend/lego2me/__pycache__/urls.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/backend/lego2me/__pycache__/urls.cpython-37.pyc -------------------------------------------------------------------------------- /backend/lego2me/__pycache__/wsgi.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/backend/lego2me/__pycache__/wsgi.cpython-37.pyc -------------------------------------------------------------------------------- /backend/lego2me/__pycache__/yasg.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/backend/lego2me/__pycache__/yasg.cpython-37.pyc -------------------------------------------------------------------------------- /backend/lego2me/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for lego2me project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lego2me.settings') 15 | 16 | application = get_asgi_application() 17 | 18 | -------------------------------------------------------------------------------- /backend/lego2me/celery.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, unicode_literals 2 | import os 3 | from celery import Celery 4 | 5 | # set the default Django settings module for the 'celery' program. 6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lego2me.settings') 7 | 8 | app = Celery('lego2me', 9 | broker='amqp://guest:guest@rabbitmq:5672//') 10 | #rabbitmq는 docker-compose에서 정의한 host이름입니다. 11 | 12 | # Using a string here means the worker doesn't have to serialize 13 | # the configuration object to child processes. 14 | # - namespace='CELERY' means all celery-related configuration keys 15 | # should have a `CELERY_` prefix. 16 | app.config_from_object('django.conf:settings', namespace='CELERY') 17 | 18 | # Load task modules from all registered Django app configs. 19 | app.autodiscover_tasks() -------------------------------------------------------------------------------- /backend/lego2me/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for lego2me project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.10. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | import datetime #56에서 시작되는 JWT_AUTH 의 토큰 유효기간을 설정하기 위한 datetime import 15 | import os # 아래에 작성한 image가 추가될 때 경로를 설정해주기 위한 os import 16 | 17 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 18 | BASE_DIR = Path(__file__).resolve().parent.parent 19 | 20 | 21 | # Quick-start development settings - unsuitable for production 22 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 23 | 24 | # SECURITY WARNING: keep the secret key used in production secret! 25 | SECRET_KEY = 'django-insecure-fio=9zg%ci60tyb!fql1@n^4a#)+-!f+*in&!b^n-p=es^9__2' 26 | 27 | # SECURITY WARNING: don't run with debug turned on in production! 28 | DEBUG = True 29 | 30 | ALLOWED_HOSTS = [ 31 | '*' 32 | #'34.69.160.195' 33 | ] 34 | 35 | CORS_ORIGIN_WHITELIST = ['http://localhost:3000', 36 | # 인스턴스 IP 주소 37 | '_____________________:3000', 38 | '_____________________:8000', 39 | '_____________________:8080', 40 | ] 41 | 42 | CORS_ALLOW_CREDENTIALS = True 43 | 44 | INSTALLED_APPS = [ 45 | 'django.contrib.admin', 46 | 'django.contrib.auth', 47 | 'django.contrib.contenttypes', 48 | 'django.contrib.sessions', 49 | 'django.contrib.messages', 50 | 'django.contrib.staticfiles', 51 | 'api.apps.ApiConfig', 52 | 'rest_framework', 53 | 'rest_framework_jwt', 54 | 'corsheaders', 55 | 'drf_yasg', 56 | ] 57 | 58 | MIDDLEWARE = [ 59 | 'corsheaders.middleware.CorsMiddleware', # 추가 60 | 'django.middleware.common.CommonMiddleware', # 추가 61 | 'django.middleware.security.SecurityMiddleware', 62 | 'django.contrib.sessions.middleware.SessionMiddleware', 63 | 'django.middleware.common.CommonMiddleware', 64 | 'django.middleware.csrf.CsrfViewMiddleware', 65 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 66 | 'django.contrib.messages.middleware.MessageMiddleware', 67 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 68 | ] 69 | 70 | ROOT_URLCONF = 'lego2me.urls' 71 | 72 | TEMPLATES = [ 73 | { 74 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 75 | 'DIRS': [], 76 | 'APP_DIRS': True, 77 | 'OPTIONS': { 78 | 'context_processors': [ 79 | 'django.template.context_processors.debug', 80 | 'django.template.context_processors.request', 81 | 'django.contrib.auth.context_processors.auth', 82 | 'django.contrib.messages.context_processors.messages', 83 | ], 84 | }, 85 | }, 86 | ] 87 | 88 | #개발을 위해 로컬로 실행하면 주석처리 89 | #WSGI_APPLICATION = 'lego2me.wsgi.application' 90 | 91 | DATABASES = { 92 | 'default': 93 | { 'ENGINE': 'djongo', 94 | 'CLIENT': { 95 | 'name': 'lego2me', 96 | #로컬용 97 | #mongodb://localhost:27017 98 | #도커용 99 | #db 100 | 'host': 'db', 101 | 'username': 'root', 102 | 'password': 'legolego', 103 | 'authSource': 'admin', 104 | 'authMechanism': 'SCRAM-SHA-1' 105 | } 106 | } 107 | } 108 | 109 | RABBITMQ_HOSTS = (os.environ.get('RABBITMQ_HOST'), ) 110 | RABBITMQ_HOSTS = os.environ.get('RABBITMQ_HOST','rabbitmq') 111 | RABBITMQ_USER = os.environ.get('RABBITMQ_USER', 'guest') 112 | RABBITMQ_PASSWORD = os.environ.get('RABBITMQ_PASSWORD', 'guest') 113 | RABBITMQ_QUEUE_EXPIRES = 300.0 # seconds 114 | RABBITMQ_MESSAGE_EXPIRES = RABBITMQ_QUEUE_EXPIRES 115 | 116 | 117 | # Password validation 118 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 119 | 120 | AUTH_PASSWORD_VALIDATORS = [ 121 | { 122 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 123 | }, 124 | { 125 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 126 | }, 127 | { 128 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 129 | }, 130 | { 131 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 132 | }, 133 | ] 134 | 135 | # Internationalization 136 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 137 | 138 | LANGUAGE_CODE = 'en-us' 139 | 140 | TIME_ZONE = 'Asia/Seoul' 141 | 142 | USE_I18N = True 143 | 144 | USE_L10N = True 145 | 146 | USE_TZ = True 147 | 148 | #APPEND_SLASH = False 149 | 150 | # Static files (CSS, JavaScript, Images) 151 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 152 | 153 | STATIC_URL = '/static/' 154 | STATIC_ROOT = os.path.join(BASE_DIR, 'static') #개발자가 관리하는 파일들 155 | 156 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 157 | MEDIA_URL = '/media/' 158 | 159 | # Default primary key field type 160 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 161 | 162 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 163 | -------------------------------------------------------------------------------- /backend/lego2me/urls.py: -------------------------------------------------------------------------------- 1 | """lego2me URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.db import router 18 | from django.urls import path, include 19 | from django.conf import settings 20 | from django.conf.urls.static import static 21 | #from api import views 22 | from rest_framework import routers 23 | from rest_framework.permissions import AllowAny, IsAuthenticated, BasePermission 24 | from api import views 25 | 26 | from .yasg import * 27 | 28 | router = routers.DefaultRouter() 29 | 30 | urlpatterns = [ 31 | path('admin/', admin.site.urls), 32 | path('api/', include('api.urls')), 33 | path('api/tasks//', views.Get_View.as_view()), 34 | url(r'^',include(router.urls)), 35 | path('swagger', schema_view.without_ui(cache_timeout=0), name='schema-json'), 36 | path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), 37 | path('docs/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), 38 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 39 | -------------------------------------------------------------------------------- /backend/lego2me/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for lego2me project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lego2me.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /backend/lego2me/yasg.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from django.urls import path, include 3 | from drf_yasg.views import get_schema_view 4 | from rest_framework.permissions import AllowAny, IsAuthenticated, BasePermission 5 | from drf_yasg import openapi 6 | 7 | schema_url_patterns = [ 8 | path('api/', include('api.urls')), 9 | ] 10 | 11 | schema_view = get_schema_view( 12 | openapi.Info( 13 | title="lego2me API", 14 | default_version='v1', 15 | description = 16 | ''' 17 | lego2me api 문서 18 | ''', 19 | terms_of_service="https://www.google.com/policies/terms/", 20 | contact=openapi.Contact(email="test@gmail.com"), 21 | license=openapi.License(name="lego2me"), 22 | ), 23 | validators=['flex'], 24 | public=True, 25 | permission_classes=(AllowAny,), 26 | patterns=schema_url_patterns, 27 | ) -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lego2me.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.4.1 2 | beautifulsoup4==4.10.0 3 | certifi==2021.10.8 4 | charset-normalizer==2.0.10 5 | click==8.0.3 6 | coreapi==2.3.3 7 | coreschema==0.0.4 8 | dj-database-url==0.5.0 9 | Django==3.2.10 10 | django-cors-headers==3.10.1 11 | django-htmlmin==0.11.0 12 | djangorestframework==3.13.1 13 | djangorestframework-jwt==1.11.0 14 | djongo==1.3.6 15 | drf-yasg==1.20.0 16 | flex==6.14.1 17 | html5lib==1.1 18 | idna==3.3 19 | importlib-metadata==4.10.0 20 | inflection==0.5.1 21 | itypes==1.2.0 22 | Jinja2==3.0.3 23 | jsonpointer==2.2 24 | MarkupSafe==2.0.1 25 | packaging==21.3 26 | Pillow==9.0.0 27 | PyJWT==1.7.1 28 | pymongo==3.12.3 29 | pyparsing==3.0.6 30 | pytz==2021.3 31 | PyYAML==6.0 32 | requests==2.27.1 33 | rfc3987==1.3.8 34 | ruamel.yaml==0.17.20 35 | ruamel.yaml.clib==0.2.6 36 | six==1.16.0 37 | soupsieve==2.3.1 38 | sqlparse==0.2.4 39 | strict-rfc3339==0.7 40 | typing_extensions==4.0.1 41 | uritemplate==4.1.1 42 | urllib3==1.26.8 43 | validate-email==1.3 44 | webencodings==0.5.1 45 | Werkzeug==2.0.2 46 | whitenoise==5.3.0 47 | zipp==3.7.0 48 | celery 49 | numpy 50 | keras 51 | django_celery_results 52 | google-api-python-client 53 | google-cloud-storage 54 | #djangorestframework djangorestframework-jwt 55 | keras 56 | tensorflow 57 | -------------------------------------------------------------------------------- /docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | db: 5 | image: mongo:latest 6 | restart: unless-stopped #컨테이너 스탑하기전까지 항상 재시작 7 | volumes: 8 | - mongoVolume:/data/db 9 | environment: 10 | MONGO_INITDB_ROOT_USERNAME: root 11 | MONGO_INITDB_ROOT_PASSWORD: legolego 12 | MONGO_INITDB_DATABASE: django_mongodb_docker 13 | MONGODB_DATA_DIR: /var/lib/docker/volumes/mongo 14 | ports: 15 | - 27017:27017 16 | 17 | backend: 18 | container_name: lego2me_django 19 | image: django 20 | build: 21 | context: ./backend 22 | command: python manage.py runserver 0.0.0.0:8000 23 | ports: 24 | - 8000:8000 25 | volumes: 26 | - ./backend/:/backend/ 27 | expose: 28 | - 8001 29 | depends_on: 30 | - db 31 | - rabbitmq 32 | # - redis 33 | 34 | frontend: 35 | build: 36 | context: ./frontend 37 | dockerfile: Dockerfile 38 | ports: 39 | - 3000:3000 40 | volumes: 41 | - ./frontend:/usr/src/app 42 | - /usr/src/app/node_modules 43 | - /usr/src/app/.next 44 | 45 | rabbitmq: 46 | hostname: rabbitmq 47 | image: rabbitmq:3.7.14-management-alpine # 웹 UI management 툴 포함 48 | # 환경변수 설정 49 | environment: 50 | - RABBITMQ_USER=guest 51 | - RABBITMQ_PASSWORD=guest 52 | #- RABBITMQ_HOSTS=rabbitmq:5672 53 | 54 | ports: 55 | - "5672:5672" # rabbitMQ default port 56 | - "15672:15672" # UI를 위한 port 57 | 58 | #restart: on-failure #정상적으로 종료되지않은 경우에만 재시작 (오류코드 0 아니면 재시작) 59 | 60 | celery: 61 | image: django 62 | working_dir: /backend/ 63 | command: celery -A lego2me worker -l info 64 | volumes: 65 | - ./backend/:/backend/ 66 | depends_on: 67 | - backend 68 | 69 | 70 | nginx: 71 | image: nginx:latest 72 | container_name: nginx_service 73 | 74 | ports: 75 | - "80:80" 76 | 77 | depends_on: 78 | - backend 79 | - frontend 80 | volumes: 81 | - ./nginx/nginx.conf:/etc/nginx/nginx.conf 82 | 83 | 84 | volumes: 85 | mongoVolume: 86 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | db: 5 | image: mongo:latest 6 | restart: unless-stopped #컨테이너 스탑하기전까지 항상 재시작 7 | volumes: 8 | - mongoVolume:/data/db 9 | environment: 10 | MONGO_INITDB_ROOT_USERNAME: root 11 | MONGO_INITDB_ROOT_PASSWORD: legolego 12 | MONGO_INITDB_DATABASE: django_mongodb_docker 13 | MONGODB_DATA_DIR: /var/lib/docker/volumes/mongo 14 | ports: 15 | - 27017:27017 16 | 17 | backend: 18 | container_name: lego2me_django 19 | image: django 20 | build: 21 | context: ./backend 22 | command: python manage.py runserver 0.0.0.0:8000 23 | ports: 24 | - 8000:8000 25 | volumes: 26 | - ./backend/:/backend/ 27 | expose: 28 | - 8001 29 | depends_on: 30 | - db 31 | - rabbitmq 32 | # - redis 33 | 34 | frontend: 35 | build: 36 | context: ./frontend 37 | dockerfile: Dockerfile 38 | ports: 39 | - 3000:3000 40 | volumes: 41 | - ./frontend/:/usr/src/app 42 | - /usr/src/app/node_modules 43 | - /usr/src/app/.next 44 | 45 | rabbitmq: 46 | hostname: rabbitmq 47 | image: rabbitmq:3.7.14-management-alpine # 웹 UI management 툴 포함 48 | # 환경변수 설정 49 | environment: 50 | - RABBITMQ_USER=guest 51 | - RABBITMQ_PASSWORD=guest 52 | #- RABBITMQ_HOSTS=rabbitmq:5672 53 | 54 | ports: 55 | - "5672:5672" # rabbitMQ default port 56 | - "15672:15672" # UI를 위한 port 57 | 58 | #restart: on-failure #정상적으로 종료되지않은 경우에만 재시작 (오류코드 0 아니면 재시작) 59 | 60 | celery: 61 | image: django 62 | working_dir: /backend/ 63 | command: celery -A lego2me worker -l info 64 | volumes: 65 | - ./backend/:/backend/ 66 | depends_on: 67 | - backend 68 | #restart: on-failure 69 | 70 | # redis: 71 | # image: redis:alpine 72 | # ports: 73 | # - "6379:6379" 74 | 75 | nginx: 76 | image: nginx:latest 77 | container_name: nginx_service 78 | 79 | ports: 80 | - "8080:8080" 81 | depends_on: 82 | - backend 83 | - frontend 84 | volumes: 85 | - ./nginx/nginx.conf:/etc/nginx/nginx.conf 86 | 87 | volumes: 88 | mongoVolume: 89 | -------------------------------------------------------------------------------- /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"] 3 | } 4 | -------------------------------------------------------------------------------- /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12 2 | 3 | ENV PORT 3000 4 | 5 | # Create app directory 6 | RUN mkdir -p /usr/src/app 7 | WORKDIR /usr/src/app 8 | 9 | # Installing dependencies 10 | COPY package*.json /usr/src/app/ 11 | RUN npm install 12 | 13 | # Copying source files 14 | COPY . /usr/src/app 15 | 16 | # Building app 17 | RUN npm run build 18 | EXPOSE 3000 19 | 20 | # Running the app 21 | CMD "npm" "run" "dev" -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /frontend/components/Banner.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import Image from "next/image"; 3 | 4 | const Banner: React.FunctionComponent = () => { 5 | return ( 6 |
7 |
8 |

9 | Try transforming into Lego! 10 |

11 |

12 | Upload your picture! Try our AI technology to make Lego characters by 13 | analyzing the clothes and physical gender you are wearing. 14 |

15 |
16 |
17 |
18 | lego1 24 |
25 |
26 | lego2 32 |
33 |
34 | lego1 40 |
41 |
42 |
43 | ); 44 | }; 45 | 46 | export default Banner; 47 | -------------------------------------------------------------------------------- /frontend/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Footer: React.FunctionComponent = () => { 4 | return ( 5 |
6 |
7 |

8 | © LEGO2ME 2021. All rights reserved. 9 |

10 |
11 | ); 12 | }; 13 | 14 | export default Footer; 15 | -------------------------------------------------------------------------------- /frontend/components/HowtoCard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | const Howto: React.FunctionComponent = () => { 4 | return ( 5 |
6 |
7 |
8 |

9 | How to start? 10 |

11 |

12 | Our service is simple. Prepare a good full body picture. You 13 | can't hold something or take a picture of your body covered. 14 |

15 |
16 | 17 |
18 | 19 | 01 20 | 21 |

22 | Upload a Picture. 23 |

24 |
25 | Saly7 31 |
32 |
33 | 34 |
35 |
36 | Saly38 42 |
43 | 44 | 02 45 | 46 |

47 | customize your characters. 48 |

49 |
50 | 51 |
52 | 53 | 03 54 | 55 |

56 | Save your characters and share them. 57 |

58 |
59 | Saly1 65 |
66 |
67 |
68 |
69 | ); 70 | }; 71 | 72 | export default Howto; 73 | -------------------------------------------------------------------------------- /frontend/components/IntroBanner.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | import { useRouter } from "next/router"; 4 | const IntroBanner: React.FunctionComponent = () => { 5 | const router = useRouter(); 6 | 7 | return ( 8 |
9 |
10 |
11 | saly-10 17 |
18 |
19 |
20 |

21 | We thought about making 22 |

23 |

24 |

25 | more fun use of AI 26 |

27 |
28 | 29 |

30 | Our service was created through the AI Application Development by 31 | Silicon Valley Engineering program organized by Headstart Silicon 32 | Valley. For more information, visit GitHub! 33 |

34 | 41 |
42 |
43 |
44 | ); 45 | }; 46 | 47 | export default IntroBanner; 48 | -------------------------------------------------------------------------------- /frontend/components/ItemSelect.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { 3 | ArrowCircleLeftIcon, 4 | ArrowCircleRightIcon, 5 | } from "@heroicons/react/solid"; 6 | import MediumCard from "./MediumCard"; 7 | import { Square } from "@mui/icons-material"; 8 | 9 | interface ItemSelectProps { 10 | cardsData: { 11 | img: string; 12 | title: string; 13 | }[]; 14 | itemListTitle: string; 15 | //setState: Dispatch>; 16 | } 17 | 18 | const ItemSelect: React.FunctionComponent = ({ 19 | cardsData, 20 | itemListTitle, 21 | //setState, 22 | }) => { 23 | function handleClickLeft() { 24 | if (process.browser) { 25 | const horizontalScroll = document.getElementById(`${itemListTitle}`); 26 | if (horizontalScroll !== null) horizontalScroll.scrollLeft -= 200; 27 | } 28 | } 29 | 30 | function handleClickRight() { 31 | if (process.browser) { 32 | const horizontalScroll = document.getElementById(`${itemListTitle}`); 33 | if (horizontalScroll !== null) horizontalScroll.scrollLeft += 200; 34 | } 35 | } 36 | // Skeleton 37 | 38 | return ( 39 |
40 |
41 |

42 | {itemListTitle} 43 |

44 |
45 | 51 | 57 |
58 |
59 |
63 | {/* items */} 64 | {cardsData.map(({ img, title }) => ( 65 | // eslint-disable-next-line react/jsx-key 66 | 72 | ))} 73 |
74 |
75 | ); 76 | }; 77 | 78 | export default ItemSelect; 79 | -------------------------------------------------------------------------------- /frontend/components/MediumCard.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import Image from "next/image"; 3 | import { useRecoilState } from "recoil"; 4 | import { hairState, topState, bottomState } from "../recoil/states"; 5 | 6 | interface MediumCardProps { 7 | img: string; 8 | title: string; 9 | itemListTitle: string; 10 | } 11 | 12 | const MediumCard: React.FunctionComponent = ({ 13 | img, 14 | title, 15 | itemListTitle, 16 | }) => { 17 | const [hair, setHairState] = useRecoilState(hairState); 18 | const [top, setTopState] = useRecoilState(topState); 19 | const [bottom, setBottomState] = useRecoilState(bottomState); 20 | 21 | const handleClick = () => { 22 | if (itemListTitle === "HairStyle") { 23 | console.log(img); 24 | if (hair === img) { 25 | setHairState("/items/item_default.png"); 26 | } else { 27 | setHairState(img); 28 | } 29 | // Top select Event 30 | } else if (itemListTitle === "Top") { 31 | if (top === img) { 32 | setTopState("/items/item_default.png"); 33 | console.log(top); 34 | } else { 35 | setTopState(img); 36 | } 37 | // Bottom select Event 38 | } else if (itemListTitle === "Bottom") { 39 | if (bottom === img) { 40 | setBottomState("/items/item_default.png"); 41 | console.log(bottom); 42 | } else { 43 | setBottomState(img); 44 | } 45 | // error 46 | } else { 47 | console.log("item select err"); 48 | } 49 | }; 50 | 51 | return ( 52 |
53 |
54 | image 63 |
64 |
65 | ); 66 | }; 67 | 68 | export default MediumCard; 69 | -------------------------------------------------------------------------------- /frontend/components/Nav.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | import { useRouter } from "next/router"; 4 | import UploadImgButton from "./UploadImgButton"; 5 | 6 | const Nav: React.FunctionComponent = () => { 7 | const router = useRouter(); 8 | 9 | return ( 10 | 31 | ); 32 | }; 33 | 34 | export default Nav; 35 | -------------------------------------------------------------------------------- /frontend/components/ResultLego.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react"; 2 | import Image from "next/image"; 3 | import { Button } from "@mui/material"; 4 | import { useRecoilValue } from "recoil"; 5 | import { hairState, topState, bottomState } from "../recoil/states"; 6 | import DomToImage from "dom-to-image"; 7 | import { saveAs } from "file-saver"; 8 | 9 | const ResultLego: React.FunctionComponent = () => { 10 | // 구독하는 아톰의 값만 반환한다. 11 | const hairStateValue = useRecoilValue(hairState); 12 | const topStateValue = useRecoilValue(topState); 13 | const bottomStateValue = useRecoilValue(bottomState); 14 | 15 | return ( 16 |
17 |

18 | It's your character! 19 |

20 |
21 |
22 | result lego character 29 |
30 |
31 | result lego HairStyle 38 |
39 |
40 | result top 47 |
48 |
49 | result bottom 56 |
57 |
58 |
59 | ); 60 | }; 61 | 62 | export default ResultLego; 63 | -------------------------------------------------------------------------------- /frontend/components/UploadImgButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | useState, 3 | useCallback, 4 | useRef, 5 | useEffect, 6 | ChangeEvent, 7 | } from "react"; 8 | 9 | import { useRouter } from "next/router"; 10 | import Box from "@mui/material/Box"; 11 | import Button from "@mui/material/Button"; 12 | import Modal from "@mui/material/Modal"; 13 | import { styled, SxProps, Theme } from "@mui/material/styles"; 14 | import axios from "axios"; 15 | import FormData from "form-data"; 16 | import { useRecoilValue, useSetRecoilState, useResetRecoilState } from "recoil"; 17 | import { hairState, topState, bottomState } from "../recoil/states"; 18 | import ReactCrop from "react-image-crop"; 19 | import Skeleton from "@mui/material/Skeleton"; 20 | import { positions } from "@mui/system"; 21 | 22 | const UploadImgButton: React.FC = () => { 23 | const router = useRouter(); 24 | 25 | // 구독하는 아톰의 값만 반환한다. 26 | const hairStateValue = useRecoilValue(hairState); 27 | const topStateValue = useRecoilValue(topState); 28 | const bottomStateValue = useRecoilValue(bottomState); 29 | 30 | // 값을 변경하는 함수만 반환 31 | const setTopUseSetRecoilState = useSetRecoilState(topState); 32 | const setBottomUseSetRecoilState = useSetRecoilState(bottomState); 33 | 34 | // 설정된 기본값으로 리셋 35 | const resetHair = useResetRecoilState(hairState); 36 | const resetTop = useResetRecoilState(topState); 37 | const resetBottom = useResetRecoilState(bottomState); 38 | 39 | //모달 40 | const [open, setOpen] = useState(false); 41 | const handleOpen = () => setOpen(true); 42 | const handleClose = () => setOpen(false); 43 | const Input = styled("input")({ 44 | display: "none", 45 | }); 46 | 47 | //모달 스타일 48 | const sxBox: SxProps = (theme: Theme) => { 49 | return { 50 | position: "absolute", 51 | top: "50%", 52 | left: "50%", 53 | transform: "translate(-50%, -50%)", 54 | bgcolor: "background.paper", 55 | width: 700, 56 | border: "2px solid #000", 57 | boxShadow: 24, 58 | p: 2, 59 | }; 60 | }; 61 | 62 | //사진 저장 기능 63 | const [upImg, setUpImg] = useState(); 64 | const imgRef = useRef(null); 65 | const previewCanvasRef = useRef(null); 66 | const [crop, setCrop] = useState({ 67 | unit: "%", 68 | width: 40, 69 | aspect: 1 / 1, 70 | }); 71 | // 자른 이미지 상태 저장 72 | const [completedCrop, setCompletedCrop] = useState(null); 73 | const [topBlob, setTopBlob] = useState(null); 74 | const [bottomBlob, setBottomBlob] = useState(null); 75 | const [uploadImgName, setUploadImgName] = useState(null); 76 | 77 | function generateTopImage( 78 | canvas: { 79 | toBlob: (arg0: (blob: any) => void, arg1: string, arg2: number) => void; 80 | }, 81 | crop: any 82 | ) { 83 | if (!crop || !canvas) { 84 | return; 85 | } 86 | 87 | canvas.toBlob( 88 | (blob: Blob) => { 89 | const topFile = new File([blob], "image_top.png", { type: blob.type }); 90 | setTopBlob(topFile); 91 | console.log(topBlob); 92 | }, 93 | "image/png", 94 | 1 95 | ); 96 | } 97 | //하의 결과 저장 98 | function generateBottomImage( 99 | canvas: { 100 | toBlob: (arg0: (blob: any) => void, arg1: string, arg2: number) => void; 101 | }, 102 | crop: any 103 | ) { 104 | if (!crop || !canvas) { 105 | return; 106 | } 107 | canvas.toBlob( 108 | (blob: Blob) => { 109 | const bottomFile = new File([blob], "image_bottom.png", { 110 | type: blob.type, 111 | }); 112 | setBottomBlob(bottomFile); 113 | console.log(bottomBlob); 114 | }, 115 | "image/png", 116 | 1 117 | ); 118 | } 119 | 120 | const onSelectFile = (e: ChangeEvent) => { 121 | if (e.target.files && e.target.files.length > 0) { 122 | const file = e.target.files[0]; 123 | const reader = new FileReader(); 124 | reader.addEventListener("load", () => setUpImg(reader.result)); 125 | reader.readAsDataURL(file); 126 | 127 | const lastDot = file.name.lastIndexOf("."); 128 | const fileName = file.name.substring(-1, lastDot).concat(".png"); 129 | setUploadImgName(fileName); 130 | } 131 | }; 132 | 133 | const onLoad = useCallback((img) => { 134 | imgRef.current = img; 135 | }, []); 136 | 137 | useEffect(() => { 138 | if (!completedCrop || !previewCanvasRef.current || !imgRef.current) { 139 | return; 140 | } 141 | 142 | const image = imgRef.current; 143 | const canvas = previewCanvasRef.current; 144 | const crop = completedCrop; 145 | 146 | const scaleX = image.naturalWidth / image.width; 147 | const scaleY = image.naturalHeight / image.height; 148 | const ctx = canvas.getContext("2d"); 149 | const pixelRatio = window.devicePixelRatio; 150 | 151 | canvas.width = crop.width * pixelRatio * scaleX; 152 | canvas.height = crop.height * pixelRatio * scaleY; 153 | 154 | ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); 155 | ctx.imageSmoothingQuality = "high"; 156 | 157 | ctx.drawImage( 158 | image, 159 | crop.x * scaleX, 160 | crop.y * scaleY, 161 | crop.width * scaleX, 162 | crop.height * scaleY, 163 | 0, 164 | 0, 165 | crop.width * scaleX, 166 | crop.height * scaleY 167 | ); 168 | }, [completedCrop]); 169 | 170 | //로딩 상태 171 | const [loading, setLoading] = React.useState(false); 172 | 173 | const handleResult = () => { 174 | setLoading(true); 175 | const fd = new FormData(); 176 | if (topBlob !== null && bottomBlob !== null) { 177 | fd.append("img_top", topBlob); 178 | //fd.append("img_top_title", topBlob.name); 179 | fd.append("img_bottoms", bottomBlob); 180 | //fd.append("img_bottom_title", bottomBlob.name); 181 | fd.append("img_title", uploadImgName); 182 | axios 183 | .post("http://35.225.137.222:80/api/posts/", fd, { 184 | // <<여기서 포트 접근할 때는 ngix로 해야하는게?? 185 | headers: { 186 | "Content-Type": "multipart/form-data", 187 | }, 188 | }) 189 | 190 | .then((res) => { 191 | console.log("success"); 192 | // FIXME: 193 | const taskId = res.data.task; 194 | axios 195 | .get(`http://35.225.137.222:80/api/tasks/${taskId}`) 196 | .then((res) => { 197 | const imgPathTop = "/legoItem/Top/"; 198 | const imgPathBottom = "/legoItem/Bottom/"; 199 | 200 | const objTop = res.data.top_result; 201 | const objBottom = res.data.bottom_result; 202 | 203 | const resultTop = "".concat(imgPathTop, objTop, ".png"); 204 | const resultBottom = "".concat(imgPathBottom, objBottom, ".png"); 205 | 206 | console.log(resultTop); //White_shrirt 207 | console.log(resultBottom); //red_Bottos 208 | 209 | if ( 210 | hairStateValue !== "/items/default.png" || 211 | topStateValue !== "/items/default.png" || 212 | bottomStateValue !== "/items/default.png" 213 | ) { 214 | resetHair(); 215 | resetTop(); 216 | resetBottom(); 217 | } 218 | setTopUseSetRecoilState(`${resultTop}`); 219 | setBottomUseSetRecoilState(`${resultBottom}`); 220 | router.push({ 221 | pathname: "/result", 222 | query: { taskId: taskId }, 223 | }); 224 | }); 225 | }) 226 | .catch((err) => { 227 | console.log(err); 228 | handleClose(); 229 | window.alert("try again"); 230 | }); 231 | } 232 | }; 233 | 234 | return ( 235 |
236 | 245 | 251 | 252 |
253 |
254 | 267 |
268 |
269 | setCrop(c)} 275 | onComplete={(c) => setCompletedCrop(c)} 276 | /> 277 |
278 | 287 |
288 |
289 | 290 |
291 | 301 | 311 | {/* FIXME: */} 312 | 320 |
321 |
322 |
323 |
324 |
325 | ); 326 | }; 327 | 328 | export default UploadImgButton; 329 | -------------------------------------------------------------------------------- /frontend/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /frontend/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | reactStrictMode: true, 4 | purge: [], //remove this line 5 | purge: ["./components/**/*.tsx", "./pages/**/*.tsx", "./public/**/*.html"], //add this line 6 | darkMode: false, // or 'media' or 'class' 7 | theme: { 8 | extend: {}, 9 | }, 10 | variants: { 11 | extend: {}, 12 | }, 13 | swcMinify: false, 14 | plugins: [], 15 | }; 16 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start", 8 | "lint": "next lint" 9 | }, 10 | "dependencies": { 11 | "@emotion/react": "^11.7.1", 12 | "@emotion/styled": "^11.6.0", 13 | "@heroicons/react": "^1.0.5", 14 | "@material-ui/core": "^4.12.3", 15 | "@mui/icons-material": "^5.2.5", 16 | "@mui/material": "^5.2.7", 17 | "axios": "^0.24.0", 18 | "dom-to-image": "^2.6.0", 19 | "file-saver": "^2.0.5", 20 | "form-data": "^4.0.0", 21 | "next": "^12.0.9-canary.4", 22 | "next-images": "^1.8.4", 23 | "nextjs-progressbar": "0.0.13", 24 | "react": "17.0.2", 25 | "react-dom": "17.0.2", 26 | "react-image-crop": "^9.0.5", 27 | "recoil": "^0.5.2", 28 | "semantic-ui-css": "^2.4.1", 29 | "semantic-ui-react": "^2.0.4", 30 | "tailwind-scrollbar-hide": "^1.1.7" 31 | }, 32 | "devDependencies": { 33 | "@types/dom-to-image": "^2.6.4", 34 | "@types/file-saver": "^2.0.4", 35 | "@types/node": "17.0.5", 36 | "@types/react": "17.0.38", 37 | "autoprefixer": "^10.4.1", 38 | "eslint": "8.5.0", 39 | "eslint-config-next": "12.0.7", 40 | "postcss": "^8.4.5", 41 | "tailwindcss": "^3.0.8", 42 | "typescript": "4.5.4" 43 | }, 44 | "optionalDependencies": { 45 | "fsevemts": "*" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /frontend/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "../styles/globals.css"; 2 | import type { AppProps } from "next/app"; 3 | import NextNProgress from "nextjs-progressbar"; 4 | import { RecoilRoot } from "recoil"; 5 | 6 | function MyApp({ Component, pageProps }: AppProps) { 7 | return ( 8 | <> 9 | 10 | 11 | 15 | 16 | Lego2me 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | } 24 | 25 | export default MyApp; 26 | -------------------------------------------------------------------------------- /frontend/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from "next"; 3 | 4 | type Data = { 5 | name: string; 6 | }; 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: "John Doe" }); 13 | } 14 | -------------------------------------------------------------------------------- /frontend/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | 3 | import Banner from "../components/Banner"; 4 | import Nav from "../components/Nav"; 5 | import HowtoCard from "../components/HowtoCard"; 6 | import IntroBanner from "../components/IntroBanner"; 7 | import Footer from "../components/Footer"; 8 | 9 | const Home: NextPage = () => { 10 | return ( 11 | <> 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | ); 21 | }; 22 | 23 | export default Home; 24 | -------------------------------------------------------------------------------- /frontend/pages/result.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import React, { useEffect, useRef, useState } from "react"; 3 | import Footer from "../components/Footer"; 4 | import Nav from "../components/Nav"; 5 | import ItemSelect from "../components/ItemSelect"; 6 | import { useRecoilState } from "recoil"; 7 | import { hairState, topState, bottomState } from "../recoil/states"; 8 | import ResultLego from "../components/ResultLego"; 9 | import { useRouter } from "next/router"; 10 | import { styled, SxProps, Theme } from "@mui/material/styles"; 11 | import Box from "@mui/material/Box"; 12 | import Button from "@mui/material/Button"; 13 | import Typography from "@mui/material/Typography"; 14 | import Modal from "@mui/material/Modal"; 15 | import Rating from "@mui/material/Rating"; 16 | import FavoriteIcon from "@mui/icons-material/Favorite"; 17 | import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorder"; 18 | import axios from "axios"; 19 | 20 | const Result: NextPage = () => { 21 | const [hair, setHairState] = useRecoilState(hairState); 22 | const [top, setTopState] = useRecoilState(topState); 23 | const [bottom, setBottomState] = useRecoilState(bottomState); 24 | 25 | console.log(`result ${top}`); 26 | console.log(`result ${bottom}`); 27 | 28 | const hairStyle = [ 29 | { 30 | img: "/items/hair/hair1_black.png", 31 | title: "hair1", 32 | }, 33 | { 34 | img: "/items/hair/hair2_black.png", 35 | title: "hair2", 36 | }, 37 | { 38 | img: "/items/hair/hair3_black.png", 39 | title: "hair3", 40 | }, 41 | ]; 42 | const topStyle = [ 43 | { 44 | img: "/legoItem/Top/Black_Shirts.png", 45 | title: "Black_Shirts", 46 | }, 47 | { 48 | img: "/legoItem/Top/Blue_Shirts.png", 49 | title: "Blue_Shirts", 50 | }, 51 | { 52 | img: "/legoItem/Top/Brown_Shirts.png", 53 | title: "Brown_Shirts", 54 | }, 55 | { 56 | img: "/legoItem/Top/Green_Shirts.png", 57 | title: "Green_Shirts", 58 | }, 59 | { 60 | img: "/legoItem/Top/Grey_Shirts.png", 61 | title: "Grey_Shirts", 62 | }, 63 | { 64 | img: "/legoItem/Top/Orange_Shirts.png", 65 | title: "Orange_Shirts", 66 | }, 67 | { 68 | img: "/legoItem/Top/Purple_Shirts.png", 69 | title: "Purple_Shirts", 70 | }, 71 | { 72 | img: "/legoItem/Top/Red_Shirts.png", 73 | title: "Red_Shirts", 74 | }, 75 | { 76 | img: "/legoItem/Top/White_Shirts.png", 77 | title: "White_Shirts", 78 | }, 79 | { 80 | img: "/legoItem/Top/Yellow_Shirts.png", 81 | title: "Yellow_Shirts", 82 | }, 83 | ]; 84 | const bottomStyle = [ 85 | { 86 | img: "/legoItem/Bottom/Black_Pants.png", 87 | title: "Black_Pants", 88 | }, 89 | { 90 | img: "/legoItem/Bottom/Blue_Pants.png", 91 | title: "Blue_Pants", 92 | }, 93 | { 94 | img: "/legoItem/Bottom/Brown_Pants.png", 95 | title: "Brown_Pants", 96 | }, 97 | { 98 | img: "/legoItem/Bottom/Green_Pants.png", 99 | title: "Green_Pants", 100 | }, 101 | { 102 | img: "/legoItem/Bottom/Grey_Pants.png", 103 | title: "Grey_Pants", 104 | }, 105 | { 106 | img: "/legoItem/Bottom/Orange_Pants.png", 107 | title: "Orange_Pants", 108 | }, 109 | { 110 | img: "/legoItem/Bottom/Purple_Pants.png", 111 | title: "Purple_Pants", 112 | }, 113 | { 114 | img: "/legoItem/Bottom/Red_Pants.png", 115 | title: "Red_Pants", 116 | }, 117 | { 118 | img: "/legoItem/Bottom/White_Pants.png", 119 | title: "White_Pants", 120 | }, 121 | { 122 | img: "/legoItem/Bottom/Yellow_Pants.png", 123 | title: "Yellow_Pants", 124 | }, 125 | ]; 126 | 127 | //TODO: TaskId 받아오기 128 | const router = useRouter(); 129 | console.log(`taskId : ${router.query.taskId}`); 130 | 131 | //modal 132 | const [open, setOpen] = React.useState(false); 133 | const handleOpen = () => setOpen(true); 134 | const handleClose = () => setOpen(false); 135 | 136 | //modal style 137 | const sxBox: SxProps = (theme: Theme) => { 138 | return { 139 | position: "absolute", 140 | top: "50%", 141 | left: "50%", 142 | transform: "translate(-50%, -50%)", 143 | width: 400, 144 | bgcolor: "background.paper", 145 | border: "2px solid #000", 146 | boxShadow: 24, 147 | p: 4, 148 | }; 149 | }; 150 | 151 | // 별점 152 | const [value, setValue] = React.useState(2); 153 | 154 | // rating 서버 전송 함수 155 | const handleRatingSubmit = () => { 156 | const taskQuery = router.query.taskId; 157 | console.log(value); 158 | axios 159 | .post("http://35.225.137.222:80/api/scores/", { 160 | id: taskQuery, 161 | score: value, 162 | }) 163 | .then((res) => { 164 | console.log("success"); 165 | handleClose(); 166 | window.alert("Thank you!"); 167 | }) 168 | .catch((err) => { 169 | console.log(err); 170 | handleClose(); 171 | window.alert("Sorry Try Again!"); 172 | }); 173 | }; 174 | 175 | return ( 176 |
177 | 178 |
179 |
180 | {/* item select */} 181 | 186 | 191 | 196 |
197 |
198 | 199 |
200 |
201 | 210 | 216 | 217 | 223 | Rating & Review 224 | 225 | legend": { mt: 2 }, 228 | }} 229 | > 230 | { 235 | setValue(newValue); 236 | }} 237 | /> 238 | 239 | 244 | Please leave a rating and hit the submit button! 245 | 246 | 253 | 254 | 255 |
256 |
257 |
258 |
259 |
260 |
261 | ); 262 | }; 263 | 264 | export default Result; 265 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/images/Saly-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/images/Saly-10.png -------------------------------------------------------------------------------- /frontend/public/images/Saly-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/images/Saly-14.png -------------------------------------------------------------------------------- /frontend/public/images/Saly-38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/images/Saly-38.png -------------------------------------------------------------------------------- /frontend/public/images/Saly-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/images/Saly-7.png -------------------------------------------------------------------------------- /frontend/public/images/lego1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/images/lego1.png -------------------------------------------------------------------------------- /frontend/public/images/lego2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/images/lego2.png -------------------------------------------------------------------------------- /frontend/public/images/lego2me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/images/lego2me.png -------------------------------------------------------------------------------- /frontend/public/images/lego_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/images/lego_result.png -------------------------------------------------------------------------------- /frontend/public/items/hair/hair1_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/items/hair/hair1_black.png -------------------------------------------------------------------------------- /frontend/public/items/hair/hair2_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/items/hair/hair2_black.png -------------------------------------------------------------------------------- /frontend/public/items/hair/hair3_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/items/hair/hair3_black.png -------------------------------------------------------------------------------- /frontend/public/items/item_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/items/item_default.png -------------------------------------------------------------------------------- /frontend/public/items/lego_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/items/lego_default.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Bottom/Black_Pants.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Bottom/Black_Pants.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Bottom/Blue_Pants.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Bottom/Blue_Pants.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Bottom/Brown_Pants.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Bottom/Brown_Pants.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Bottom/Green_Pants.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Bottom/Green_Pants.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Bottom/Grey_Pants.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Bottom/Grey_Pants.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Bottom/Orange_Pants.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Bottom/Orange_Pants.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Bottom/Purple_Pants.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Bottom/Purple_Pants.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Bottom/Red_Pants.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Bottom/Red_Pants.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Bottom/White_Pants.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Bottom/White_Pants.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Bottom/Yellow_Pants.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Bottom/Yellow_Pants.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Top/Black_Shirts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Top/Black_Shirts.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Top/Blue_Shirts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Top/Blue_Shirts.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Top/Brown_Shirts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Top/Brown_Shirts.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Top/Green_Shirts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Top/Green_Shirts.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Top/Grey_Shirts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Top/Grey_Shirts.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Top/Orange_Shirts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Top/Orange_Shirts.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Top/Purple_Shirts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Top/Purple_Shirts.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Top/Red_Shirts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Top/Red_Shirts.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Top/White_Shirts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Top/White_Shirts.png -------------------------------------------------------------------------------- /frontend/public/legoItem/Top/Yellow_Shirts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/legoItem/Top/Yellow_Shirts.png -------------------------------------------------------------------------------- /frontend/public/placeholder.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sung1san3/Lego2me/93749daff3ca772bce75c7ae382fdc22db9a1754/frontend/public/placeholder.webp -------------------------------------------------------------------------------- /frontend/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/recoil/states.ts: -------------------------------------------------------------------------------- 1 | import { atom, useRecoilState } from "recoil"; 2 | 3 | const hairState = atom({ 4 | key: "hairState", 5 | default: "/items/item_default.png", 6 | }); 7 | 8 | const topState = atom({ 9 | key: "topState", 10 | default: "/items/item_default.png", 11 | }); 12 | 13 | const bottomState = atom({ 14 | key: "bottomState", 15 | default: "/items/item_default.png", 16 | }); 17 | 18 | export { hairState, topState, bottomState }; 19 | -------------------------------------------------------------------------------- /frontend/styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /frontend/styles/globals.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,400;0,600;0,700;0,900;1,800&display=swap"); 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | @import "~react-image-crop/dist/ReactCrop.css"; 7 | @layer components { 8 | .hoverAnimation { 9 | @apply hover:scale-105 transform transition duration-300; 10 | } 11 | } 12 | 13 | html, 14 | body { 15 | padding: 0; 16 | margin: 0; 17 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 18 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 19 | } 20 | 21 | a { 22 | color: inherit; 23 | text-decoration: none; 24 | } 25 | 26 | * { 27 | box-sizing: border-box; 28 | } 29 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | "./pages/**/*.{js,ts,jsx,tsx}", 4 | "./components/**/*.{js,ts,jsx,tsx}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | fontFamily: { 9 | Montserrat: ["Montserrat", "sans-serif"], 10 | }, 11 | }, 12 | plugins: [require("tailwind-scrollbar-hide")], 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "downlevelIteration": true 18 | }, 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "additional.d.ts"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 70; 3 | location / { 4 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 5 | proxy_set_header X-Forwarded-Proto $scheme; 6 | proxy_set_header Host $http_host; 7 | proxy_redirect off; 8 | proxy_pass -------------------:3000/ 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes 1; 3 | error_log /var/log/nginx/error.log warn; 4 | pid /var/run/nginx.pid; 5 | events { 6 | worker_connections 1024; 7 | } 8 | http { 9 | 10 | 11 | upstream frontend { #docker compose에서 정의한 service이름 12 | server frontend:3000; #해당 서비스의 포트번호 13 | } 14 | 15 | upstream backend { 16 | server backend:8000; 17 | } 18 | 19 | 20 | server { 21 | 22 | listen 80; 23 | 24 | location / { #99번 포트에 대해서 경로 "/"로 들어오면 frontend로 이동 25 | add_header 'Access-Control-Allow-Origin' '*'; 26 | proxy_http_version 1.1; 27 | proxy_set_header Upgrade $http_upgrade; 28 | proxy_set_header Connection "Upgrade"; 29 | proxy_set_header Origin ""; 30 | proxy_pass http://frontend; 31 | 32 | } 33 | 34 | location /api { #99번 포트에 대해서 /api ~~ 로 들어오면 backend로 이동(우선순위 높음) 35 | add_header 'Access-Control-Allow-Origin' '*'; 36 | proxy_http_version 1.1; 37 | proxy_set_header Upgrade $http_upgrade; 38 | proxy_set_header Connection "Upgrade"; 39 | proxy_set_header Origin ""; 40 | proxy_pass http://backend; 41 | } 42 | 43 | 44 | } 45 | } --------------------------------------------------------------------------------