Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/profile #24

Merged
merged 43 commits into from
Nov 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c21fbc7
Merge branch 'feature/header' into feature/profile
seuha516 Oct 30, 2022
98ce237
[FEAT] footer
seuha516 Oct 30, 2022
9b532d9
[STYLE] App.tsx 공백
seuha516 Oct 30, 2022
235f4e6
[TEST] footer
seuha516 Oct 30, 2022
4fd7107
[FEAT] header, footer 드래그 방지
seuha516 Oct 30, 2022
ba8035d
[FEAT] request.user 설정
seuha516 Oct 30, 2022
c8c3beb
[FEAT] profile 백엔드
seuha516 Oct 30, 2022
4a096a9
[FEAT] 폰트 추가
seuha516 Oct 30, 2022
3abcc1a
[FIX] 헤더 개선
seuha516 Oct 30, 2022
38051c2
[FEAT] user 모델 created 추가
seuha516 Oct 30, 2022
cd7346e
[FEAT] created 추가
seuha516 Oct 30, 2022
01e965e
[FEAT] dateDiff 함수
seuha516 Oct 30, 2022
bd31fda
[FEAT] api
seuha516 Oct 30, 2022
8d7e7a1
[FEAT] slice
seuha516 Oct 30, 2022
7e672fb
[FIX] views.py
seuha516 Oct 30, 2022
4beee29
[FEAT] loading.tsx
seuha516 Oct 30, 2022
56072e2
[FEAT] mypage
seuha516 Oct 30, 2022
0f40696
[FEAT] loading
seuha516 Oct 30, 2022
952dad1
[FEAT] 폰트 추가
seuha516 Oct 30, 2022
04814b9
[FIX] 로딩 경로
seuha516 Oct 30, 2022
9ce09f5
[FIX] 코드 분리
seuha516 Oct 30, 2022
fe87ca7
[FEAT] 컴포넌트
seuha516 Oct 30, 2022
6c6c44a
[FEAT] button3 컴포넌트
seuha516 Oct 30, 2022
41e21a1
[TEST] 버튼
seuha516 Oct 30, 2022
1d78147
[TEST] input
seuha516 Oct 30, 2022
f42bc1a
[TEST] 로딩
seuha516 Oct 30, 2022
2ba8429
[REFACTOR] Login Page
seuha516 Oct 30, 2022
3d1a422
[REFACTOR] signup
seuha516 Oct 30, 2022
d5dd467
[FIX] debug
seuha516 Oct 30, 2022
63e4b38
[FIX] 시간 계산 오류 수정
seuha516 Oct 30, 2022
a4377c6
[STYLE] button3
seuha516 Oct 31, 2022
2ab529e
[STYLE] Signup
seuha516 Oct 31, 2022
dbee458
[STYLE] mypage
seuha516 Oct 31, 2022
56ab274
[FEAT] notfound
seuha516 Oct 31, 2022
be41abc
[FEAT] button4
seuha516 Oct 31, 2022
9a96377
[STYLE] button4
seuha516 Oct 31, 2022
5bda062
[FIX] userData
seuha516 Oct 31, 2022
b164b70
[FEAT] editProfile
seuha516 Oct 31, 2022
3fda773
Merge branch 'develop' into fix/profile
seuha516 Oct 31, 2022
7d51e9b
[TEST] 에러 수정
seuha516 Oct 31, 2022
471c87e
[FIX] profile 404
seuha516 Oct 31, 2022
be7a56f
[FIX] created 처리
seuha516 Oct 31, 2022
ee87cbe
[FIX] date
seuha516 Oct 31, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/FITogether/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@

MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'utils.jwt_middleware.JsonWebTokenMiddleWare',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'utils.jwt_middleware.JsonWebTokenMiddleWare',
]

ROOT_URLCONF = 'FITogether.urls'
Expand Down
2 changes: 2 additions & 0 deletions backend/images/tests.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import bcrypt
import datetime
from django.test import TestCase, Client
from users.models import User

Expand All @@ -16,6 +17,7 @@ def setUp(self):
image="profile_default.png",
exp=0,
level=1,
created=datetime.date.today()
)

def test_upload(self):
Expand Down
2 changes: 1 addition & 1 deletion backend/images/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
@require_http_methods(["POST"])
def upload(request):
"""
이미지를 백엔드 로컬 환경에 업로드합니다.
POST : 이미지를 백엔드 로컬 환경에 업로드
"""
try:
file_type = str(request.FILES['image'].content_type[:6])
Expand Down
17 changes: 17 additions & 0 deletions backend/posts/migrations/0002_alter_post_options.py
Original file line number Diff line number Diff line change
@@ -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',)},
),
]
19 changes: 19 additions & 0 deletions backend/users/migrations/0002_user_created.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
23 changes: 23 additions & 0 deletions backend/users/migrations/0003_user_updated_alter_user_created.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
10 changes: 4 additions & 6 deletions backend/users/models.py
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
2 changes: 2 additions & 0 deletions backend/users/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@
path('login/', views.login),
path('check/', views.check),
path('logout/', views.logout),

path('profile/<str:user_id>/', views.profile)
]
95 changes: 92 additions & 3 deletions backend/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand All @@ -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"),
Expand Down Expand Up @@ -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')):
Expand Down Expand Up @@ -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
3 changes: 2 additions & 1 deletion backend/utils/jwt_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Binary file modified frontend/env.tar.enc
Binary file not shown.
5 changes: 4 additions & 1 deletion frontend/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap" rel="stylesheet" />
<link
href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Red+Hat+Mono&family=Rubik:wght@700&family=Roboto+Condensed&family=Noto+Sans+KR&display=swap"
rel="stylesheet"
/>
<title>FITogether</title>
</head>
<body>
Expand Down
35 changes: 27 additions & 8 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -87,7 +94,6 @@ function App() {
</>
);
}

export default App;

const InsideComponent = () => {
Expand All @@ -100,14 +106,27 @@ const InsideComponent = () => {
element={
<Wrapper>
<Header />

<Routes>
<Route path="" element={<Main />} />
<Route path="post" element={<PostMain />} />
<Route path="post/create" element={<PostCreate />} />
<Route path="post/:id" element={<PostDetail />} />
<Route path="post/:id/edit" element={<PostEdit />} />

<Route path="post/*">
<Route path="" element={<PostMain />} />
<Route path="create" element={<PostCreate />} />
<Route path=":id" element={<PostDetail />} />
<Route path=":id/edit" element={<PostEdit />} />
</Route>

<Route path="profile/:username" element={<Mypage />} />
<Route path="edit_profile" element={<EditProfile />} />
<Route path="edit_password" element={<EditPassword />} />

<Route path="workout" element={<WorkoutLog />} />

<Route path="*" element={<NotFound />} />
</Routes>

<Footer />
</Wrapper>
}
/>
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/components/common/Loading.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { render, screen } from '@testing-library/react';
import Loading, { LoadingBox } from './Loading';

test('Loading', () => {
render(<Loading />);
expect(screen.getByText('FITogether')).toBeInTheDocument();
});

test('LoadingBox', () => {
render(<LoadingBox r="80px" />);
});
Loading