diff --git a/backend/FITogether/settings.py b/backend/FITogether/settings.py index 50650b08..067262d2 100644 --- a/backend/FITogether/settings.py +++ b/backend/FITogether/settings.py @@ -49,7 +49,6 @@ MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', - 'utils.jwt_middleware.JsonWebTokenMiddleWare', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -57,6 +56,7 @@ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'utils.jwt_middleware.JsonWebTokenMiddleWare', ] ROOT_URLCONF = 'FITogether.urls' diff --git a/backend/images/tests.py b/backend/images/tests.py index 30391aba..09dfcaa3 100644 --- a/backend/images/tests.py +++ b/backend/images/tests.py @@ -1,4 +1,5 @@ import bcrypt +import datetime from django.test import TestCase, Client from users.models import User @@ -16,6 +17,7 @@ def setUp(self): image="profile_default.png", exp=0, level=1, + created=datetime.date.today() ) def test_upload(self): diff --git a/backend/images/views.py b/backend/images/views.py index de91d18f..24b68352 100644 --- a/backend/images/views.py +++ b/backend/images/views.py @@ -6,7 +6,7 @@ @require_http_methods(["POST"]) def upload(request): """ - 이미지를 백엔드 로컬 환경에 업로드합니다. + POST : 이미지를 백엔드 로컬 환경에 업로드 """ try: file_type = str(request.FILES['image'].content_type[:6]) diff --git a/backend/posts/migrations/0002_alter_post_options.py b/backend/posts/migrations/0002_alter_post_options.py new file mode 100644 index 00000000..9ab60df2 --- /dev/null +++ b/backend/posts/migrations/0002_alter_post_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.1.2 on 2022-10-31 21:22 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('posts', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='post', + options={'ordering': ('-created',)}, + ), + ] diff --git a/backend/users/migrations/0002_user_created.py b/backend/users/migrations/0002_user_created.py new file mode 100644 index 00000000..b78a1ecb --- /dev/null +++ b/backend/users/migrations/0002_user_created.py @@ -0,0 +1,19 @@ +# Generated by Django 4.1.2 on 2022-10-30 20:39 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='created', + field=models.DateField(default=datetime.date.today), + ), + ] diff --git a/backend/users/migrations/0003_user_updated_alter_user_created.py b/backend/users/migrations/0003_user_updated_alter_user_created.py new file mode 100644 index 00000000..fef33333 --- /dev/null +++ b/backend/users/migrations/0003_user_updated_alter_user_created.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.2 on 2022-10-31 21:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0002_user_created'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='updated', + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name='user', + name='created', + field=models.DateTimeField(auto_now_add=True), + ), + ] diff --git a/backend/users/models.py b/backend/users/models.py index 827f69dd..3f114ae6 100644 --- a/backend/users/models.py +++ b/backend/users/models.py @@ -1,6 +1,7 @@ from django.db import models +from utils.models import AbstractTimeStampedModel -class User(models.Model): +class User(AbstractTimeStampedModel): username = models.CharField(max_length=20, null=False) hashed_password = models.CharField(max_length=255, null=False) nickname = models.CharField(max_length=8, null=False) @@ -11,8 +12,5 @@ class User(models.Model): weight = models.FloatField(null=False) age = models.IntegerField(null=False) - exp = models.IntegerField(null = False) - level = models.IntegerField(null = False) - - # Related_name : posts <- posts.Post - # Related_name : comments <- comments.Comment + exp = models.IntegerField(null=False) + level = models.IntegerField(null=False) diff --git a/backend/users/urls.py b/backend/users/urls.py index 318f7e78..71e65010 100644 --- a/backend/users/urls.py +++ b/backend/users/urls.py @@ -8,4 +8,6 @@ path('login/', views.login), path('check/', views.check), path('logout/', views.logout), + + path('profile//', views.profile) ] diff --git a/backend/users/views.py b/backend/users/views.py index 63ef776e..601a219b 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -30,7 +30,7 @@ def signup(request): hashed_password = bcrypt.hashpw(data['password'].encode('utf-8'), bcrypt.gensalt()).decode('utf-8') - User( + User.objects.create( username=data['username'], hashed_password=hashed_password, nickname=data['nickname'], @@ -41,7 +41,7 @@ def signup(request): image="profile_default.png", exp=0, level=1, - ).save() + ) token = jwt.encode({'username': data['username']}, os.environ.get("JWT_SECRET"), @@ -74,7 +74,7 @@ def login(request): if data['password'] == "": return JsonResponse({"message": "비밀번호를 입력하세요."}, status=400) if not (User.objects.filter(username=data['username'])).exists(): - return JsonResponse({"message": "존재하지 않는 ID입니다."}, status=401) + return JsonResponse({"message": "존재하지 않는 유저네임입니다."}, status=401) user = User.objects.get(username=data['username']) if bcrypt.checkpw(data['password'].encode('utf-8'), user.hashed_password.encode('utf-8')): @@ -113,3 +113,92 @@ def logout(request): response = HttpResponse(status=204) response.set_cookie('access_token', None, max_age=60 * 60 * 24 * 7, samesite='None', secure=True, httponly=True) return response + +@require_http_methods(["GET", "PUT", "DELETE"]) +def profile(request, user_id): + """ + GET : 프로필 불러오기 + PUT : 프로필 수정 + DELETE : 회원 탈퇴 + """ + if not (User.objects.filter(username=user_id)).exists(): + return JsonResponse({"message": "존재하지 않는 유저입니다."}, status=404) + user = User.objects.get(username=user_id) + + if request.method == 'GET': + return JsonResponse({ + "username": user.username, + "nickname": user.nickname, + "image": user.image, + "gender": user.gender, + "height": user.height, + "weight": user.weight, + "age": user.age, + "exp": user.exp, + "level": user.level, + "created": user.created + }) + + elif request.method == 'PUT': + data = json.loads(request.body.decode()) + if request.user.username != user.username: + return HttpResponse(status=403) + + password = data.get("oldPassword", None) + new_password = data.get("newPassword", None) + if password and new_password: + if bcrypt.checkpw(password.encode('utf-8'), user.hashed_password.encode('utf-8')): + new_hashed_password = bcrypt.hashpw( + new_password.encode('utf-8'), + bcrypt.gensalt() + ).decode('utf-8') + user.hashed_password = new_hashed_password + user.save() + else: + return JsonResponse({"message": "비밀번호가 틀렸습니다."}, status=401) + else: + try: + if user.nickname != data['nickname'] \ + and (User.objects.filter(nickname=data['nickname'])).exists(): + return JsonResponse({"message": "이미 있는 닉네임입니다."}, status=409) + user.nickname = data['nickname'] + user.image = data['image'] + user.gender = data['gender'] + user.height = data['height'] + user.weight = data['weight'] + user.age = data['age'] + user.save() + except KeyError: + return HttpResponseBadRequest() + + token = jwt.encode( + {'username': user.username}, + os.environ.get("JWT_SECRET"), + os.environ.get("ALGORITHM") + ) + response = JsonResponse( + { + "username": user.username, + "nickname": user.nickname, + "image": user.image + }, + status=200 + ) + response.set_cookie( + 'access_token', + token, + max_age=60 * 60 * 24 * 7, + samesite='None', + secure=True, + httponly=True + ) + return response + + elif request.method == 'DELETE': + if request.user.username != user.username: + return HttpResponse(status=403) + + user.delete() + response = HttpResponse(status=204) + response.set_cookie('access_token', None, max_age=60 * 60 * 24 * 7, samesite='None', secure=True, httponly=True) + return response diff --git a/backend/utils/jwt_middleware.py b/backend/utils/jwt_middleware.py index 6e3e3bdd..0c73bf70 100644 --- a/backend/utils/jwt_middleware.py +++ b/backend/utils/jwt_middleware.py @@ -30,9 +30,10 @@ def __call__(self, request): username = payload.get("username", None) if not username: raise PermissionDenied() - User.objects.get(username=username) + request.user = User.objects.get(username=username) return self.get_response(request) except (PermissionDenied, jwt.exceptions.DecodeError, User.DoesNotExist): return JsonResponse({"message": "토큰이 올바르지 않습니다."}, status=401) + except ExpiredSignatureError: return JsonResponse({"message": "토큰이 만료되었습니다."}, status=403) diff --git a/frontend/env.tar.enc b/frontend/env.tar.enc index c69a4615..f986f6d4 100644 Binary files a/frontend/env.tar.enc and b/frontend/env.tar.enc differ diff --git a/frontend/public/index.html b/frontend/public/index.html index a31c0e9c..f2eb83e8 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -10,7 +10,10 @@ - + FITogether diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 61f34d23..da515f74 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,16 +6,23 @@ import FugazOne from 'assets/fonts/FugazOne.ttf'; import IBMPlexSansThaiLooped from 'assets/fonts/IBMPlexSansThaiLooped.ttf'; import NanumSquareR from 'assets/fonts/NanumSquareR.ttf'; +import Header from 'components/sections/Header'; +import Footer from 'components/sections/Footer'; +import NotFound from 'components/common/NotFound'; + import Main from 'containers/Main'; import Login from 'containers/user/Login'; import Signup from 'containers/user/Signup'; -import WorkoutLog from 'containers/workout/WorkoutLog'; -import Header from 'components/sections/Header'; +import Mypage from 'containers/user/Mypage'; +import EditProfile from 'containers/user/EditProfile'; +import EditPassword from 'containers/user/EditPassword'; import PostMain from 'containers/post/PostMain'; import PostCreate from 'containers/post/PostCreate'; -import PostDetail from 'containers/post/PostDetail'; import PostEdit from 'containers/post/PostEdit'; +import PostDetail from 'containers/post/PostDetail'; + +import WorkoutLog from 'containers/workout/WorkoutLog'; const GlobalStyles = createGlobalStyle` ${reset} @@ -87,7 +94,6 @@ function App() { ); } - export default App; const InsideComponent = () => { @@ -100,14 +106,27 @@ const InsideComponent = () => { element={
+ } /> - } /> - } /> - } /> - } /> + + + } /> + } /> + } /> + } /> + + + } /> + } /> + } /> + } /> + + } /> + +