diff --git a/.gitignore b/.gitignore index 0abf7a1..1912139 100644 --- a/.gitignore +++ b/.gitignore @@ -135,6 +135,7 @@ celerybeat.pid # Environments .env .venv +dev.env env/ venv/ ENV/ @@ -182,6 +183,7 @@ node_modules bower_components *.sublime* +.vscode/ psd thumb @@ -189,3 +191,10 @@ sketch yarn.lock +#DB +data/ +db.env + +#jupyter +*.ipynb +.ipynb_checkpoints/ \ No newline at end of file diff --git a/backend/Untitled.ipynb b/backend/Untitled.ipynb deleted file mode 100644 index 3217246..0000000 --- a/backend/Untitled.ipynb +++ /dev/null @@ -1,101 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 2, - "id": "0d104680", - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "import os\n", - "os.environ['DJANGO_SETTINGS_MODULE'] = \"backend.settings\"\n", - "os.environ[\"DJANGO_ALLOW_ASYNC_UNSAFE\"] = \"true\"\n", - "\n", - "import django\n", - "django.setup()" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "955510d4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1\n", - "2\n", - "3\n", - "{'user': {'name': 'client1', 'alias': 'client1', 'email': 'client1@test.com'}, 'is_login': True}\n" - ] - } - ], - "source": [ - "from django.http import HttpResponse, JsonResponse\n", - "from django.core import serializers\n", - "\n", - "from rebikeuser.serializers import UserSerializer\n", - "from rebikeuser.userUtil import user_find_by_name, user_compPW, user_create_client, user_change_pw, user_change_alias\n", - "\n", - "def user_login():\n", - " input_id = 'client1'\n", - " input_pw = '1234'\n", - "\n", - " if input_pw != '' and input_id != '':\n", - " user = user_find_by_name(input_id).first()\n", - " print(1)\n", - " if user:\n", - " is_login = user_compPW(input_pw, user)\n", - " print(2)\n", - " if is_login:\n", - " user_data = UserSerializer(user)\n", - " print(3)\n", - "\n", - " result = {\n", - " 'user': user_data.data,\n", - " 'is_login': is_login,\n", - " }\n", - " return result\n", - "\n", - " return False\n", - "\n", - "print(user_login())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a0b06a11", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/backend/backend/settings.py b/backend/backend/settings.py index cff8ebe..dcb159b 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -1,5 +1,6 @@ from pathlib import Path + ####환경변수 설정 import os import environ @@ -18,7 +19,12 @@ env_file=os.path.join(BASE_DIR, '.env') ) -SECRET_KEY = env('SECRET_KEY') +if not env('IS_DEV'): + SECRET_KEY = env('SECRET_KEY') + DB_URL = 'localhost:8989' +else: + SECRET_KEY = env('SECRET_KEY') + DB_URL = env('DATABASE_URL') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = env('DEBUG') @@ -36,10 +42,11 @@ 'django.contrib.staticfiles', # add 'rest_framework', + 'corsheaders', + 'drf_yasg', # local apps 'rebikeuser', 'rebiketrash', - 'corsheaders', ] MIDDLEWARE = [ @@ -80,7 +87,10 @@ # https://docs.djangoproject.com/en/4.0/ref/settings/#databases DATABASES = { - 'default': env.db() + 'default': env.db(), + 'OPTIONS': { + 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'" + } } # Password validation @@ -110,7 +120,7 @@ USE_I18N = True -USE_TZ = True +USE_TZ = False # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.0/howto/static-files/ diff --git a/backend/backend/urls.py b/backend/backend/urls.py index 32eb384..0d419e8 100644 --- a/backend/backend/urls.py +++ b/backend/backend/urls.py @@ -1,18 +1,27 @@ - -from django.contrib import admin -from django.urls import path, include -from django.contrib import admin -from django.urls import path, include -from rest_framework.permissions import AllowAny -from django.contrib import admin -from django.urls import path, include from rest_framework import permissions -from django.urls import include, re_path +from django.contrib import admin +from django.urls import path, include, re_path from rest_framework.permissions import AllowAny +from drf_yasg.views import get_schema_view +from drf_yasg import openapi +schema_view_v1 = get_schema_view( + openapi.Info( + title="Open API", + default_version='v1', + description="간단한 설명", + terms_of_service="https://www.google.com/policies/terms/", + ), + public=True, + permission_classes=(AllowAny,), +) urlpatterns = [ path('admin/', admin.site.urls), path('user/', include('rebikeuser.urls')), path('trash/', include('rebiketrash.urls')), + + re_path(r'^swagger(?P\.json|\.yaml)$', schema_view_v1.without_ui(cache_timeout=0), name='schema-json'), + re_path(r'^swagger/$', schema_view_v1.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), + re_path(r'^redoc/$', schema_view_v1.with_ui('redoc', cache_timeout=0), name='schema-redoc'), ] diff --git a/backend/rebiketrash/admin.py b/backend/rebiketrash/admin.py index 8d6d0fd..a33d8a6 100644 --- a/backend/rebiketrash/admin.py +++ b/backend/rebiketrash/admin.py @@ -2,13 +2,15 @@ from .models import trash_kind, uploaded_trash_image + @admin.register(trash_kind) class TrashkindAdmin(admin.ModelAdmin): list_display = ['kind', 'way'] search_fields = ['kind'] + @admin.register(uploaded_trash_image) class UploadedtrashimageAdmin(admin.ModelAdmin): - list_display = ['uploaded_trash_image_id', 'img', 'created_at', 'updated_at','user_id', 'trash_kind'] + list_display = ['uploaded_trash_image_id', 'active', 'img', + 'created_at', 'updated_at', 'user_id', 'trash_kind'] search_fields = ['uploaded_trash_image_id'] - diff --git a/backend/rebiketrash/models.py b/backend/rebiketrash/models.py index 63530d8..0d60767 100644 --- a/backend/rebiketrash/models.py +++ b/backend/rebiketrash/models.py @@ -1,12 +1,12 @@ from django.db import models -from rebikeuser.models import user +from rebikeuser.models import user class trash_kind(models.Model): kind = models.CharField(primary_key=True, max_length=30) way = models.CharField(max_length=200) - created_at = models.DateTimeField() - updated_at = models.DateTimeField() + created_at = models.DateTimeField(auto_now_add=True, blank=True) + updated_at = models.DateTimeField(auto_now_add=True, blank=True) class Meta: managed = False @@ -17,11 +17,12 @@ class uploaded_trash_image(models.Model): uploaded_trash_image_id = models.AutoField(primary_key=True) active = models.IntegerField(default=1) img = models.CharField(max_length=200) - created_at = models.DateTimeField() - updated_at = models.DateTimeField() + created_at = models.DateTimeField(auto_now_add=True, blank=True) + updated_at = models.DateTimeField(auto_now_add=True, blank=True) user_id = models.ForeignKey(user, on_delete=models.CASCADE, db_column='user_id') trash_kind = models.ForeignKey(trash_kind, on_delete=models.CASCADE, db_column='trash_kind') class Meta: managed = False db_table = 'uploaded_trash_image' + diff --git a/backend/rebiketrash/serializers.py b/backend/rebiketrash/serializers.py index b414992..6496a66 100644 --- a/backend/rebiketrash/serializers.py +++ b/backend/rebiketrash/serializers.py @@ -1,13 +1,35 @@ +from pyexpat import model from rest_framework import serializers -from .models import trash_kind, uploaded_trash_image +from .models import trash_kind, uploaded_trash_image class TrashkindSerializer(serializers.ModelSerializer) : class Meta : - model = trash_kind # product 모델 사용 - fields = '__all__' # 모든 필드 포함 + model = trash_kind + fields = ('kind', 'way') class UploadedtrashimageSerializer(serializers.ModelSerializer) : class Meta : - model = uploaded_trash_image # product 모델 사용 - fields = '__all__' # 모든 필드 포함 \ No newline at end of file + model = uploaded_trash_image + fields = ("uploaded_trash_image_id","img") + +class UploadedtrashimageDetailSerializer(serializers.ModelSerializer) : + class Meta : + model = uploaded_trash_image + fields = '__all__' + +class UploadedtrashimageStatisticsSerializer(serializers.Serializer) : + cnt = serializers.SerializerMethodField() + + def get_cnt(self, model_instance): + return model_instance + + class Meta : + model = uploaded_trash_image + fields = ("kind","cnt") + + +class UploadedtrashimageCreateSerializer(serializers.ModelSerializer) : + class Meta : + model = uploaded_trash_image + fields = ('user_id', 'active', 'img', 'trash_kind') diff --git a/backend/rebiketrash/urls.py b/backend/rebiketrash/urls.py index 196cc12..fc967cd 100644 --- a/backend/rebiketrash/urls.py +++ b/backend/rebiketrash/urls.py @@ -7,15 +7,14 @@ urlpatterns =[ - path('mainpage/trashkind/',views.TrashkindListAPI.as_view()), - path('mypage//image/',views.histories, name='histories'), - path('mypage//image//',views.UploadedtrashimageListAPI.as_view()), - #path('mypage//statistics/',views.statistics, name='statistics'), -] - + #path('mainpage/users//',views.UploadImage.as_view()), + path('mainpage',views.UploadImage.as_view()), + path('mainpage/images//result',views.ImageResultPage), + path('mainpage/search-words//result',views.SearchResultPage), -### 유저 페이지 진입 시 액티브된 계정인지(있는지 없는지 확인) -### 통계페이지 (액티브 상관 없이 받아오기!) -### 자주 쓰이는 코드 유틸화 trashUtils.py + path('mypage/users//images',views.histories, name='histories'), + path('mypage/users//images/',views.UploadedtrashimageListAPI.as_view()), + path('mypage/users//statistics',views.statistics, name='statistics'), +] -### swagger, postman 확인 -> 성빈님 +### 자주 쓰이는 코드 유틸화 trashUtils.py \ No newline at end of file diff --git a/backend/rebiketrash/views.py b/backend/rebiketrash/views.py index f97d799..27a0330 100644 --- a/backend/rebiketrash/views.py +++ b/backend/rebiketrash/views.py @@ -1,43 +1,70 @@ -from audioop import reverse +from urllib import response from django.shortcuts import render, HttpResponse from django.db.models import Count from .models import trash_kind, uploaded_trash_image from rebikeuser.models import user -from rest_framework import status + + +from rest_framework import status, viewsets from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.decorators import api_view -from .serializers import TrashkindSerializer, UploadedtrashimageSerializer +from rest_framework.generics import CreateAPIView + + +from .serializers import TrashkindSerializer, UploadedtrashimageSerializer, UploadedtrashimageDetailSerializer, UploadedtrashimageStatisticsSerializer, UploadedtrashimageCreateSerializer # Create your views here. -class TrashkindListAPI(APIView): - def get(self, request): - queryset = trash_kind.objects.all() - print(queryset) - serializer = TrashkindSerializer(queryset, many=True) - return Response(serializer.data) @api_view(['GET']) -def histories(request,user_id): - uploadedTrashs = uploaded_trash_image.objects.filter(user_id=user_id, active=1) +def histories(request, user_id): + uploadedTrashs = uploaded_trash_image.objects.filter( + user_id=user_id, active=1) serializer = UploadedtrashimageSerializer(uploadedTrashs, many=True) return Response(serializer.data) + class UploadedtrashimageListAPI(APIView): def get(self, request, user_id, uploaded_trash_image_id): - uploadedTrashs = uploaded_trash_image.objects.filter(user_id=user_id, active=1, uploaded_trash_image_id = int(uploaded_trash_image_id)) - serializer = UploadedtrashimageSerializer(uploadedTrashs, many=True) + uploaded_trashs = uploaded_trash_image.objects.filter( + user_id=user_id, active=1, uploaded_trash_image_id=int(uploaded_trash_image_id)) + serializer = UploadedtrashimageDetailSerializer( + uploaded_trashs, many=True) return Response(serializer.data) def delete(self, request, user_id, uploaded_trash_image_id): - uploadedTrashs = uploaded_trash_image.objects.filter(user_id=user_id, active=1, uploaded_trash_image_id = int(uploaded_trash_image_id)) - uploadedTrashs.delete() - return Response(status=status.HTTP_204_NO_CONTENT) + uploaded_trashs = uploaded_trash_image.objects.filter( + user_id=user_id, active=1, uploaded_trash_image_id=int(uploaded_trash_image_id)) + uploaded_trashs.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + @api_view(['GET']) -def statistics(request,user_id): - uploadedTrashs = uploaded_trash_image.objects.filter(user_id=user_id).values('trash_kind') - serializer = UploadedtrashimageSerializer(uploadedTrashs, many=True) - return Response(serializer.data) \ No newline at end of file +def statistics(request, user_id): + uploaded_trashs = uploaded_trash_image.objects.filter( + user_id=user_id).values('trash_kind').annotate(cnt=Count('trash_kind')) + serializer = UploadedtrashimageStatisticsSerializer( + uploaded_trashs, many=True) + return Response(serializer.data) + +class UploadImage(CreateAPIView): + queryset = uploaded_trash_image.objects.all() + serializer_class = UploadedtrashimageCreateSerializer + +@api_view(['GET']) +def ImageResultPage(request, uploaded_trash_image_id): + ############## uploaded_trash_image_id 로 ai.. result + result = '유리' + queryset = trash_kind.objects.filter(kind=result) + serializer = TrashkindSerializer(queryset, many=True) + return Response(serializer.data) + +@api_view(['GET']) +def SearchResultPage(request, search_word): + ############## search_word + result = search_word + queryset = trash_kind.objects.filter(kind=result) + serializer = TrashkindSerializer(queryset, many=True) + return Response(serializer.data) diff --git a/backend/rebikeuser/admin.py b/backend/rebikeuser/admin.py index 946c252..2b02c3e 100644 --- a/backend/rebikeuser/admin.py +++ b/backend/rebikeuser/admin.py @@ -3,4 +3,5 @@ @admin.register(user) class UserAdmin(admin.ModelAdmin): - list_display = ['id', 'name', 'alias', 'pw', 'salt', 'email'] + + list_display = ['save_img','active', 'id', 'name', 'alias', 'pw', 'salt', 'email'] diff --git a/backend/rebikeuser/models.py b/backend/rebikeuser/models.py index fdcd4e5..86ecaa1 100644 --- a/backend/rebikeuser/models.py +++ b/backend/rebikeuser/models.py @@ -7,10 +7,12 @@ class user(models.Model): alias = models.CharField(unique=True, max_length=20) pw = models.BinaryField(max_length=60) salt = models.BinaryField(max_length=29) - email = models.CharField(unique=True, max_length=50) + email = models.CharField(max_length=50) active = models.IntegerField(default=1) + save_img = models.IntegerField(default=1) created_at = models.DateTimeField() updated_at = models.DateTimeField() + save_img = models.IntegerField(default=1) class Meta: managed = False diff --git a/backend/rebikeuser/serializers.py b/backend/rebikeuser/serializers.py index d4fd2eb..90a39aa 100644 --- a/backend/rebikeuser/serializers.py +++ b/backend/rebikeuser/serializers.py @@ -17,16 +17,19 @@ class Meta: class UserSignupResponse(serializers.ModelSerializer): class Meta: model = user - fields = ['id'] # 프론트에주는 값 + fields = ['name'] # 프론트에주는 값 class SignupInput(serializers.ModelSerializer): # 검증부 - email = serializers.EmailField() - pw = serializers.CharField(max_length=60) - alias = serializers.CharField(max_length=20) - name = serializers.CharField(max_length=20) + class Meta: + model = user + fields = ['email', 'pw', 'alias', 'name'] + + +class AutoUpload(serializers.ModelSerializer): + save_img = serializers.IntegerField(default=1) class Meta: model = user - fields = ['name', 'pw', 'alias', 'email'] # 실제 response할 필드 + fields = ['save_img'] diff --git a/backend/rebikeuser/urls.py b/backend/rebikeuser/urls.py index 59af8c7..35bd005 100644 --- a/backend/rebikeuser/urls.py +++ b/backend/rebikeuser/urls.py @@ -2,10 +2,12 @@ from . import views from .views import UserSignupAPI -#rebikeuser/urls.py +# rebikeuser/urls.py urlpatterns = [ path('login/', views.user_login), path('signup/', views.UserSignupAPI.as_view()), path('changepw/', views.user_pw_change), path('changealias/', views.user_alias_change), + path('deactivateuser/', views.deactivateUser), + path('autosave/', views.isAutoSave) ] diff --git a/backend/rebikeuser/userUtil.py b/backend/rebikeuser/userUtil.py index b8a3d61..a7975dc 100644 --- a/backend/rebikeuser/userUtil.py +++ b/backend/rebikeuser/userUtil.py @@ -2,6 +2,7 @@ import bcrypt from .models import user +from django.http import HttpResponse # @@ -37,9 +38,11 @@ def user_hash_pw(pw): # def user_create_client(name, email, pw, alias): if user_find_by_name(name): - return 'this id is duplicated' + return 1 if user_find_by_alias(alias): - return 'this alias is duplicated' + return 2 + if user_find_by_email(email): + return 3 hash_pw, salt = user_hash_pw(pw) return user.objects.create(name=name, alias=alias, pw=hash_pw, salt=salt, email=email) # return user.objects.all() @@ -50,19 +53,19 @@ def user_find_by_name(name): qs = user.objects.all() return qs.filter(name=name) +def user_find_by_email(email): + return user.objects.all().filter(email=email) # def user_find_by_alias(alias): qs = user.objects.all() - result = qs.filter(alias=alias) - return result + return qs.filter(alias=alias) # def user_user_search_by_name(name): qs = user.objects.all() - result = qs.filter(name__icontains=name) - return result + return qs.filter(name__icontains=name) # diff --git a/backend/rebikeuser/views.py b/backend/rebikeuser/views.py index 050e9b5..994f0db 100644 --- a/backend/rebikeuser/views.py +++ b/backend/rebikeuser/views.py @@ -1,15 +1,11 @@ from django.http import HttpResponse, JsonResponse - from django.shortcuts import redirect from rest_framework.decorators import api_view -from django.core import serializers - -from .serializers import UserSerializer, UserSignupResponse, SignupInput +from .serializers import UserSerializer, UserSignupResponse, SignupInput, AutoUpload from .userUtil import user_find_by_name, user_compPW, user_create_client, user_change_pw, user_change_alias from rest_framework.views import APIView from rest_framework.response import Response -from rest_framework.renderers import JSONRenderer from django.http import HttpResponse from .models import user @@ -20,21 +16,21 @@ def user_login(request): input_name = request.data['name'] input_pw = request.data['pw'] is_login = False - - data = None user_data = None - is_login = False + data = {"user": None, "is_login": is_login} if input_pw and input_name: user = user_find_by_name(input_name).first() if user: if user_compPW(input_pw, user): - user_data = UserSerializer(data={'name': user.name, 'alias': user.alias, 'email': user.email}) - if user_data.is_valid(): - data = { - "user": user_data.data, - "is_login": is_login - } + temp = UserSerializer(data={'name': user.name, 'alias': user.alias, 'email': user.email}) + if temp.is_valid(): + user_data = temp.data + is_login = True + data = { + "user": user_data, + "is_login": is_login + } return JsonResponse(data) @@ -45,12 +41,19 @@ def post(self, request): pw = request.data['pw'] # 바디 읽는 법 alias = request.data['alias'] email = request.data['email'] - serializer = SignupInput(data={'email': email, 'pw': pw, 'alias': alias, 'name': name}) - if serializer.is_valid(): - str = user_create_client(name, email, pw, alias) + + str = user_create_client(name, email, pw, alias) + if str == 1: + return HttpResponse('중복된 이름입니다.') + elif str == 2: + return HttpResponse('중복된 닉네임입니다.') + elif str == 3: + return HttpResponse('중복된 이메일입니다.') + else: serializer2 = UserSignupResponse(str, many=False) return Response(serializer2.data) # Only name - return redirect('/user/login/') + + # return redirect('/user/signup/') # get으로 회원가입 폼 화면 가져오기 @@ -66,10 +69,9 @@ def user_pw_change(request): if input_name and input_pw and input_past_pw: finduser = user_find_by_name(input_name).first() - if finduser.pw == input_past_pw: # 예전 pw와 name으로 찾은 user의 pw 일치여부 + if user_compPW(input_past_pw, finduser): # 예전 pw와 name으로 찾은 user의 pw 일치여부 user_change_pw(finduser, input_pw) return HttpResponse("성공") - # user_change_pw(finduser, input_pw) else: return HttpResponse('이전 비밀번호가 일치 하지 않습니다.') else: @@ -86,7 +88,20 @@ def user_alias_change(request): if finduser: user_change_alias(finduser, input_alias) # True : 변경됨, False : 변경실패 return HttpResponse('성공') - return False + return HttpResponse("실패") + + +@api_view(['POST']) +def deactivateUser(request): + name = request.data['name'] + pw = request.data['pw'] + d_user = user_find_by_name(name).first() + if d_user and user_compPW(pw, d_user): + d_user.active = 0 + d_user.save() + return HttpResponse("계정이 비활성화 되었습니다.") + else: + return HttpResponse("아이디 또는 비밀번호가 틀렸습니다."), redirect('/user/login/') @api_view(['GET']) @@ -96,6 +111,29 @@ def on_login(request): if username: qs = qs.filter(user_name=username) return HttpResponse(qs) + + +# +@api_view(['POST']) +def isAutoSave(request): + name = request.data['name'] + is_login = request.data['is_login'] + user = user_find_by_name(name).first() + if user.save_img == 1 and is_login: + user.save_img = 0 + user.save() + elif user.save_img == 0 and is_login: + user.save_img = 1 + user.save() + else: + return HttpResponse('로그인 하세요') + serializer = AutoUpload(data={"save_img": user.save_img}) + if serializer.is_valid(): + data = { + "save_img": serializer.data + } + return JsonResponse(data) + # # def user_pw_change(request): # input_id = request.GET.get('id', '') diff --git a/backend/requirements.txt b/backend/requirements.txt index 4851b26..928d92e 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -6,8 +6,9 @@ mysql-connector-python==8.0.23 setuptools==58.1.0 mysqlclient==2.1.1 djangorestframework==3.13.1 +django-filter==22.1 +django-rest-swagger==2.2.0 +drf-yasg==1.21.0 bcrypt==3.2.2 pygments django-cors-headers==3.13.0 -#django-filter==22.1 -#django-rest-swagger==2.2.0 diff --git a/db/dockerfile b/db/dockerfile new file mode 100644 index 0000000..763727f --- /dev/null +++ b/db/dockerfile @@ -0,0 +1 @@ +FROM mysql:8.0.29 diff --git a/docker-compose.yml b/docker-compose.yml index 6b8502a..210b499 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,14 @@ version: "3" services: - # backend: - # build: ./backend - # command: ["python","manage.py","runserver","0.0.0.0:8080"] - # ports: - # - 8080:8080 - # volumes: - # - ./backend:/app + + backend: + build: ./backend + command: [ "python","manage.py","runserver","0.0.0.0:8080" ] + ports: + - "8080:8080" + volumes: + - ./backend:/app frontend: build: ./frontend command: ["npm", "start"] @@ -15,3 +16,11 @@ services: - 3000:3000 volumes: - ./frontend:/app + mysqldb: + build: ./db + env_file: + - "./db/db.env" + ports: + - "8989:3306" + volumes: + - "./data/db/mysql:/var/lib/mysql" \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5fe7264..a5a5ecc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,7 +19,7 @@ "@types/react": "^18.0.15", "axios": "^0.27.2", "express": "^4.18.1", - "http-proxy-middleware": "^2.0.6", + "lottie-web": "^5.9.6", "mysql": "^2.18.1", "react": "^18.2.0", "react-cookie": "^4.1.1", @@ -11998,6 +11998,11 @@ "loose-envify": "cli.js" } }, + "node_modules/lottie-web": { + "version": "5.9.6", + "resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.9.6.tgz", + "integrity": "sha512-JFs7KsHwflugH5qIXBpB4905yC1Sub2MZWtl/elvO/QC6qj1ApqbUZJyjzJseJUtVpgiDaXQLjBlIJGS7UUUXA==" + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -25941,6 +25946,11 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "lottie-web": { + "version": "5.9.6", + "resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.9.6.tgz", + "integrity": "sha512-JFs7KsHwflugH5qIXBpB4905yC1Sub2MZWtl/elvO/QC6qj1ApqbUZJyjzJseJUtVpgiDaXQLjBlIJGS7UUUXA==" + }, "lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 71cb4f8..66231de 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,7 +15,7 @@ "@types/react": "^18.0.15", "axios": "^0.27.2", "express": "^4.18.1", - "http-proxy-middleware": "^2.0.6", + "lottie-web": "^5.9.6", "mysql": "^2.18.1", "react": "^18.2.0", "react-cookie": "^4.1.1", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 7ae570f..db0cf61 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import { useState, useEffect } from 'react'; import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; import Login from "./Page/Login"; @@ -14,6 +14,22 @@ import MyTrashChart from "./component/Mypage/MyTrashChart"; import ChangeInfo from "./component/Mypage/ChangeInfo"; import SearchResult from "./component/mainpage/SearchResult"; +function IsLogin(access_token){ + const [login, setLogin] = useState(false); + console.log("왜 안됨?") + + useEffect(()=>{ + if(localStorage.getItem({access_token}) !== null){ + console.log("로그인 on", setLogin) + }else{ + setLogin(false); + console.log("로그인 off", setLogin) + } + }, []) + + return login +} + function App() { return ( diff --git a/frontend/src/Page/Login.jsx b/frontend/src/Page/Login.jsx index f855a5f..5d1050c 100644 --- a/frontend/src/Page/Login.jsx +++ b/frontend/src/Page/Login.jsx @@ -1,24 +1,8 @@ import * as React from "react"; -import axios from "axios"; import { useState, useEffect } from "react"; -import { useCookies } from "react-cookie"; -import PriorityHighIcon from "@mui/icons-material/PriorityHigh"; +import axios from "axios"; import { createTheme, ThemeProvider } from "@mui/material/styles"; -import { - Divider, - FormControlLabel, - Checkbox, - Button, - CssBaseline, - TextField, - Box, - Typography, - Container, - Link, - styled, - Modal, - Backdrop, -} from "@mui/material"; +import { Divider, Button, CssBaseline, TextField, Box, Typography, Container, Link, styled,} from "@mui/material"; const style = { position: "absolute", @@ -73,255 +57,94 @@ const NaverLoginBtn = styled(Button)(({}) => ({ })); function Login() { - const [open, setOpen] = useState(false); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - - //쿠키 및 체크박스 기억 - const [cookies, setCookie, removeCookie] = useCookies(["rememberUserId"]); - const [isRemember, setIsRemember] = useState(false); - - // 최초 렌더링 시 - useEffect(() => { - /*저장된 쿠키값이 있으면, CheckBox TRUE 및 UserID에 값 셋팅*/ - if (cookies.rememberUserId !== undefined) { - setEmail(cookies.rememberUserId); - setIsRemember(true); - } - }, []); - - //쿠키 핸들 - const handleOnCookie = (e) => { - setIsRemember(e.target.checked); - if (!e.target.checked) { - removeCookie("rememberUserId"); - } else { - setCookie("rememberUserId", email, { maxAge: 180 }); //시간 설정 30 초 - } - }; - - const handleOpen = () => setOpen(true); - const handleClose = () => setOpen(false); - - const onEmailHandler = (event) => { - setEmail(event.currentTarget.value); - }; - - const onPasswordHandler = (event) => { - setPassword(event.currentTarget.value); - }; - - const handleSubmit = (event) => { - event.preventDefault(); - - const loginData = { - name: email, - pw: password, + const handleSubmit = (event) => { + event.preventDefault(); + + const data = new FormData(event.currentTarget); + console.log({ + event, + name: data.get("name"), + password: data.get("password"), + }); + + axios + .post("http://localhost:8080/user/login/", { + name: data.get("name"), + pw: data.get("password"), + }) + .then((response) => { + console.log("Well done!"); + console.log("User profile", response.data.user); + console.log("Is login?", response.data.is_login); + console.log("User ID", response.data.user.name); + + if (response.data.user) { + localStorage.clear() + localStorage.setItem("access_token", response.data.user.name) + console.log("아이디는?",localStorage.getItem("access_token")) + + alert("로그인 성공♻️") + + window.location.replace('/mainpage'); + }else { + console.log("아이디",localStorage.getItem("access_token")) + localStorage.clear() + } + + }) + .catch((error) => { + alert("아이디와 비밀번호를 다시 확인해주세요.") + // Handle error. + console.log("An error occurred:", error.response); + }); }; - console.log(loginData); - - axios - .post("http://localhost:8080/user/login/", loginData) - .then((response) => { - // Handle success. - localStorage.setItem("access_token", response.data.user.name); - console.log(localStorage.getItem("access_token")); - document.location.href = "/mainpage"; - }) - .catch((error) => { - handleOpen(); - setEmail(""); - setPassword(""); - console.log("An error occurred:", error.response); - }); - }; - - return ( - - - - - - - 로그인 - - - - - - - - } - label={ - - 아이디 저장 - - } - checked={isRemember} - onChange={(e) => { - handleOnCookie(e); - }} - /> - {/* { - handleOnCookie(e); - }} - checked={isRemember} - /> */} - - - - 가입하기 - - - - 또는 - - 카카오로 로그인하기 - - - 네이버로 로그인하기 - - - - - - 아이디 또는 비밀번호를 확인해주세요. - - - - ; - - + return ( + + + + + + + 로그인 + + + + + + + + + 가입하기 + + + 또는 + + + 카카오로 로그인하기 + + + 네이버로 로그인하기 + + + + + diff --git a/frontend/src/component/Header.jsx b/frontend/src/component/Header.jsx index e1495f9..69b62a5 100644 --- a/frontend/src/component/Header.jsx +++ b/frontend/src/component/Header.jsx @@ -1,6 +1,8 @@ -import * as React from "react"; -import { Button, Container, Box, Link, CssBaseline } from "@mui/material"; -import { createTheme, ThemeProvider } from "@mui/material/styles"; +import { useState, useRef, useEffect} from 'react'; +import {Button, Container, Box, Link,CssBaseline, Hidden, Typography} from '@mui/material'; +import { createTheme, ThemeProvider } from '@mui/material/styles'; +import lottie from 'lottie-web' + const theme = createTheme({ palette: { @@ -10,13 +12,64 @@ const theme = createTheme({ }, }); + const GetLogoLottie = ()=>{ + //lottie + const likecontainer = useRef(); + useEffect(()=>{ + lottie.loadAnimation({ + container: likecontainer.current, + renderer: 'svg', + loop: false, + autoplay:true, + animationData:require("../images/LottieLogo.json") + }) + + },[]) + return( + + + + + R e B I K E + + + + ) +} + function Header() { + const [mouseOn, setMouseOn] = useState(false); + + const handlePopoverOpen = () => { + setMouseOn(true); + }; + const handlePopoverClose = () => { + setMouseOn(false); + }; + + const token = localStorage.getItem("access_token"); console.log(token); function deleteToken() { localStorage.removeItem("access_token"); } + + + return ( + + + + + function deleteToken() { + localStorage.removeItem("access_token"); + } return ( @@ -84,20 +137,55 @@ function Header() { onClick={deleteToken} sx={{ textDecoration: "none", color: "white" }} > - {token} 님 - - ) : ( - - Login - - )} - - - - + + + {token ?( + // if IsLogin is true +
+ + + {mouseOn? + + + 클릭해서 로그아웃 + + + : + Hidden} + + + +
) + + : (// if IsLogin is false + + )} + + + + ); } diff --git a/frontend/src/component/Mypage/MultiActionAreaCard.jsx b/frontend/src/component/Mypage/MultiActionAreaCard.jsx index e88da09..ddfbba0 100644 --- a/frontend/src/component/Mypage/MultiActionAreaCard.jsx +++ b/frontend/src/component/Mypage/MultiActionAreaCard.jsx @@ -19,42 +19,41 @@ const MyTrashcanBtn = styled(Button)(({}) => ({ }, })); -export default function MultiActionAreaCard(props) { + +export default function MultiActionAreaCard({image}) { + // export default function MultiActionAreaCard() { + return ( - - - - - {props.imgID} - - - - - 더보기 - - + + + + + 물병 + {/* {kind} */} + + + + + 더보기 + + ); } diff --git a/frontend/src/component/Mypage/MyTrashcan.jsx b/frontend/src/component/Mypage/MyTrashcan.jsx index b05d1bc..046af59 100644 --- a/frontend/src/component/Mypage/MyTrashcan.jsx +++ b/frontend/src/component/Mypage/MyTrashcan.jsx @@ -1,9 +1,10 @@ -import * as React from "react"; +import { useState, useEffect, useRef} from 'react'; import { alpha, createTheme } from "@mui/material/styles"; -import { Box, Typography, Container, styled, Switch } from "@mui/material"; +import { Box,Typography,Container,styled,Switch,} from "@mui/material"; import MultiActionAreaCard from "./MultiActionAreaCard"; -import axios from "axios"; -import { useState, useEffect } from "react"; +import Api from "../../utils/customApi"; +import lottie from 'lottie-web' + const GreenSwitch = styled(Switch)(({ theme }) => ({ "& .MuiSwitch-switchBase.Mui-checked": { @@ -17,36 +18,166 @@ const GreenSwitch = styled(Switch)(({ theme }) => ({ }, })); + const GetNoTrashLottie = ()=>{ + //lottie + const nocontainer = useRef(); + useEffect(()=>{ + lottie.loadAnimation({ + container: nocontainer.current, + renderer: 'svg', + loop: false, + autoplay:true, + animationData:require("../../../src/images/noTrashLottie.json") + }) + + },[]) + return( + + + + + 쓰레기를 사진을 업로드 해보세요. + + + + + + ) +} + + + + function MyTrashcan() { - const [loading, setLoading] = useState(false); - const [trashList, setTrashList] = useState([]); - - const onLoadTrashList = async () => { - try { - // 요청이 시작 할 때에는 error 와 users 를 초기화하고 - // setError(null); - // setUsers(null); - // loading 상태를 true 로 바꿉니다. - setLoading(true); - const response = await axios.get( - "http://localhost:8080/trash/mypage/users/173dc2de-7076-40cf-a211-f3eca7aa9b4d/images" - ); - setTrashList(response.data); - // console.log(response.data); - // console.log(trashList); - } catch (e) { - console.log("An error occured : ", e); + + const token = localStorage.getItem("access_token"); + console.log(token); + + const [trash, setTrash] = useState([]) + + const fetchMyTrash = async () => { + const result = await Api.get('/trash/mypage/users/173dc2de-7076-40cf-a211-f3eca7aa9b4d/images').then( + res => res.data + ) + setTrash(result); + console.log("api요청 결과",result) + // console.log("api요청 결과",result.length) + + // console.log("api요청 결과 아이디?",result[0].img) + // console.log("정보 저장",trash) } - setLoading(false); - }; - useEffect(() => { - onLoadTrashList(); - }, []); - if (loading) return
로딩중..
; - // console.log("data : ", trashList); + useEffect(() => { + if (token !== '') { + fetchMyTrash(); + } + }, []); + + useEffect(() => { + console.log("정보 저장",trash) + }, [trash]); + + + + + return( + + + + 내 분리수거함 + + + + 사진 자동으로 추가 + + + + + + { + trash.length === 0 ? ( + + + + ): + ( + {trash?.map((content, index)=>( + + ))} + + {/* */} + + + ) + } + + + + + + return ( { backgroundColor : "white", borderColor:"#759F98", border : 1, - borderRadius : 10, margin:"auto", mt : 15,width: "58ch" }}> + borderRadius : 10, margin:"auto", mt : 30,width: "58ch" }}> { +// if(localStorage.getItem("access_token") !== null){ +// console.log("로그인 on", setLogin) +// }else{ +// setLogin(false); +// console.log("로그인 off", setLogin) +// } +// }, [login]) + +// return +// } + +// export default IsLogin \ No newline at end of file diff --git a/frontend/src/setupProxy.js b/frontend/src/setupProxy.js index 9d0dc73..755f572 100644 --- a/frontend/src/setupProxy.js +++ b/frontend/src/setupProxy.js @@ -1,7 +1,7 @@ const { createProxyMiddleware } = require("http-proxy-middleware"); module.exports = function (app) { app.use( - "/user", + "/user/signup", createProxyMiddleware({ target: "http://localhost:8080", }) diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js new file mode 100644 index 0000000..104c728 --- /dev/null +++ b/frontend/src/utils/constants.js @@ -0,0 +1,4 @@ + +export const DOMAIN = 'localhost'; + +export const API_BASE_URL = `http://${DOMAIN}:8080`; diff --git a/frontend/src/utils/customApi.jsx b/frontend/src/utils/customApi.jsx new file mode 100644 index 0000000..c6d830e --- /dev/null +++ b/frontend/src/utils/customApi.jsx @@ -0,0 +1,12 @@ +import axios from 'axios'; +import { API_BASE_URL } from './constants'; + +console.log(API_BASE_URL) +const Api = axios.create({ + baseURL: `${API_BASE_URL}`, + timeout: 10000, + params: {}, +}); + + +export default Api;