From d37b7a69638532275855b32712482e4a55a44e33 Mon Sep 17 00:00:00 2001 From: seonjo1 Date: Mon, 26 Aug 2024 15:15:48 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20main=20=ED=99=94=EB=A9=B4=EC=97=90?= =?UTF-8?q?=20=EC=96=B8=EC=96=B4=EC=84=A0=ED=83=9D=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?3=EA=B0=9C=EB=A1=9C=20=EB=A7=8C=EB=93=A4=EA=B3=A0=20=EB=B0=B1?= =?UTF-8?q?=EC=97=94=EB=93=9C=20API=20=ED=98=B8=EC=B6=9C=ED=95=A0=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=ED=99=95=EB=B3=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app.js | 7 +++- frontend/src/components/2FA.js | 6 ++- frontend/src/components/Main-Menu.js | 33 +++++++++++++--- frontend/src/core/router.js | 5 +++ frontend/style.css | 57 ++++++++++++++++++++++++++-- 5 files changed, 96 insertions(+), 12 deletions(-) diff --git a/frontend/src/app.js b/frontend/src/app.js index 3166403..5069649 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -8,8 +8,11 @@ class App { constructor() { this.app = document.querySelector("#app"); this.lan = { value: 0 }; - console.log("start!!"); - console.log(this.lan); + // API!!! jwt가 있으면 해당 유저의 데이터베이스에서 언어 번호 (0 or 1 or 2) 얻어오기 + // fetch + // if (respose.ok) { + // this.lan.value = data.value; + // } } } diff --git a/frontend/src/components/2FA.js b/frontend/src/components/2FA.js index 44c1dcc..b186ba4 100644 --- a/frontend/src/components/2FA.js +++ b/frontend/src/components/2FA.js @@ -92,7 +92,11 @@ export class TwoFA extends Component { .then(data => { if (data) { if (data.success) { - console.log("code good!"); + // API!!! jwt가 있으면 해당 유저의 데이터베이스에서 언어 번호 (0 or 1 or 2) 얻어오기 + // fetch + // if (respose.ok) { + // this.lan.value = data.value; + // } changeUrl('/main'); // 메인 페이지로 이동 } else { messageElement.textContent = "Invalid code. Please try again."; diff --git a/frontend/src/components/Main-Menu.js b/frontend/src/components/Main-Menu.js index 1ec77ab..7d13ec6 100644 --- a/frontend/src/components/Main-Menu.js +++ b/frontend/src/components/Main-Menu.js @@ -9,17 +9,14 @@ export class Menu extends Component { 0: { gameMenuTexts: ["Local Game", "Multi Game", "AI", "Tournament"], userMenuTexts: ["Friends", "Profile", "Logout"], - lanText: "Change Language" }, 1: { gameMenuTexts: ["로컬 게임", "멀티 게임", "AI", "토너먼트"], userMenuTexts: ["친구", "프로필", "로그아웃"], - lanText: "언어 변경" }, 2: { gameMenuTexts: ["ローカルゲーム", "マルチゲーム", "AI", "トーナメント"], userMenuTexts: ["友達", "プロフィール", "ログアウト"], - lanText: "言語を変更" } }; @@ -36,7 +33,9 @@ export class Menu extends Component { return ` @@ -65,8 +64,30 @@ export class Menu extends Component { changeUrl("/main/tournament"); }); - this.addEvent('click', '#lanButton', () => { - this.props.lan.value = (this.props.lan.value + 1) % 3; + this.addEvent('click', '#enButton', () => { + this.props.lan.value = 0; + // API!!! this.props.lan.value를 db에 저장 + // fetch(){ + // this.props.lan.value 저장 + // } + changeUrl("/main") + }); + + this.addEvent('click', '#koButton', () => { + this.props.lan.value = 1; + // API!!! this.props.lan.value를 db에 저장 + // fetch(){ + // this.props.lan.value 저장 + // } + changeUrl("/main") + }); + + this.addEvent('click', '#jpButton', () => { + this.props.lan.value = 2; + // API!!! this.props.lan.value를 db에 저장 + // fetch(){ + // this.props.lan.value 저장 + // } changeUrl("/main") }); diff --git a/frontend/src/core/router.js b/frontend/src/core/router.js index 12be319..18c3964 100644 --- a/frontend/src/core/router.js +++ b/frontend/src/core/router.js @@ -95,6 +95,11 @@ export async function parsePath(path) { return changeUrl("/", false); }); } else { + // API!!! jwt가 있으면 해당 유저의 데이터베이스에서 언어 번호 (0 or 1 or 2) 얻어오기 + // fetch + // if (respose.ok) { + // this.lan.value = data.value; + // } return changeUrl("/main", false); } } diff --git a/frontend/style.css b/frontend/style.css index 0c8b947..0ff0d0b 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -174,14 +174,65 @@ div#menuBox { justify-content: center; } -div#lanButton { + +div#enButton { + position: absolute; + display: flex; + justify-content: center; + align-items: center; + font-weight: 400; + border-radius: 10px; + width: 100px; + height: 50px; + bottom: 30px; + right: 270px; + font-size: 17px; + border-radius: 50px; + color: white; + cursor: pointer; + background: #357ABD; + box-shadow: 0 0 0 rgba(0, 0, 0, 0); /* 기본 상태에서 그림자 없음 */ + transition: box-shadow 0.3s ease, transform 0.3s ease; +} + +div#enButton:hover { + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); /* 호버 시 더 깊은 그림자 */ + transform: translateY(-2px); /* 살짝 위로 이동 */ +} + +div#koButton { position: absolute; display: flex; justify-content: center; align-items: center; font-weight: 400; border-radius: 10px; - width: 160px; + width: 100px; + height: 50px; + bottom: 30px; + right: 150px; + font-size: 17px; + border-radius: 50px; + color: white; + cursor: pointer; + background: #357ABD; + box-shadow: 0 0 0 rgba(0, 0, 0, 0); /* 기본 상태에서 그림자 없음 */ + transition: box-shadow 0.3s ease, transform 0.3s ease; +} + +div#koButton:hover { + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); /* 호버 시 더 깊은 그림자 */ + transform: translateY(-2px); /* 살짝 위로 이동 */ +} + +div#jpButton { + position: absolute; + display: flex; + justify-content: center; + align-items: center; + font-weight: 400; + border-radius: 10px; + width: 100px; height: 50px; bottom: 30px; right: 30px; @@ -194,7 +245,7 @@ div#lanButton { transition: box-shadow 0.3s ease, transform 0.3s ease; } -div#lanButton:hover { +div#enButton:hover { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); /* 호버 시 더 깊은 그림자 */ transform: translateY(-2px); /* 살짝 위로 이동 */ } From 49bdbe2a40a9b54b301f6979c105302f425c8efa Mon Sep 17 00:00:00 2001 From: seungwonme Date: Thu, 29 Aug 2024 17:53:48 +0900 Subject: [PATCH 2/4] feat: Implement language api --- .gitignore | 6 ++++++ backend/users/models.py | 10 +++++++++- backend/users/serializers.py | 16 ++++------------ backend/users/urls.py | 2 ++ backend/users/views.py | 32 +++++++++++++++++++++++++++++++- nginx/default.conf | 26 -------------------------- 6 files changed, 52 insertions(+), 40 deletions(-) delete mode 100644 nginx/default.conf diff --git a/.gitignore b/.gitignore index fef8b12..398e72d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +# OS +.DS_Store + +# Nginx +default.conf + # vite node_modules/ diff --git a/backend/users/models.py b/backend/users/models.py index 63c6988..cc11987 100644 --- a/backend/users/models.py +++ b/backend/users/models.py @@ -2,6 +2,12 @@ class User(models.Model): + LANGUAGE_CHOICES = [ + (0, "EN"), + (1, "KR"), + (2, "JP"), + ] + user_id = models.IntegerField(primary_key=True) nickname = models.CharField(max_length=255) email = models.EmailField(max_length=255) @@ -10,9 +16,10 @@ class User(models.Model): is_online = models.BooleanField(default=False) win = models.IntegerField(default=0) lose = models.IntegerField(default=0) + language = models.IntegerField(choices=LANGUAGE_CHOICES, default=0) def __str__(self): - return self.nickname + '#' + str(self.user_id) + return self.nickname + "#" + str(self.user_id) class Friend(models.Model): @@ -39,6 +46,7 @@ def __str__(self): user2_name = self.user2.nickname if self.user2 else "Unknown" return f"Game {self.game_id}: {user1_name} vs {user2_name}" + class Tournament(models.Model): tournament_id = models.AutoField(primary_key=True) game_id1 = models.ForeignKey(Game, null=True, on_delete=models.SET_NULL, related_name="tournaments_as_game1") diff --git a/backend/users/serializers.py b/backend/users/serializers.py index e7778ef..ebb6ff9 100644 --- a/backend/users/serializers.py +++ b/backend/users/serializers.py @@ -7,6 +7,10 @@ class Meta: model = User fields = ["user_id", "nickname", "img_url", "is_2FA", "is_online", "win", "lose"] +class LanguageSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ["language"] class FriendSerializer(serializers.ModelSerializer): class Meta: @@ -16,15 +20,3 @@ class Meta: class FriendRequestSerializer(serializers.Serializer): user_id = serializers.IntegerField() - - -class GameSerializer(serializers.ModelSerializer): - class Meta: - model = Game - fields = ["game_id", "game_type", "user1", "user2", "score1", "score2", "start_timestamp", "end_timestamp"] - - -class TournamentSerializer(serializers.ModelSerializer): - class Meta: - model = Tournament - fields = ["name", "date", "winner"] diff --git a/backend/users/urls.py b/backend/users/urls.py index 7e0ae44..616458b 100644 --- a/backend/users/urls.py +++ b/backend/users/urls.py @@ -2,6 +2,7 @@ from .views import ( UserDetailView, FriendDetailView, + LanguageView, get_user, get_user_list, ) @@ -9,6 +10,7 @@ urlpatterns = [ path('me/', UserDetailView.as_view()), path('friends/', FriendDetailView.as_view()), + path('language/', LanguageView.as_view()), path('user/', get_user_list), path('user//', get_user), ] diff --git a/backend/users/views.py b/backend/users/views.py index 0d77edd..2d1243a 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -5,7 +5,12 @@ from rest_framework.views import APIView from rest_framework.response import Response from .models import User, Friend, Game, Tournament -from .serializers import UserSerializer, FriendSerializer, FriendRequestSerializer, GameSerializer, TournamentSerializer +from .serializers import ( + UserSerializer, + LanguageSerializer, + FriendSerializer, + FriendRequestSerializer, +) from login.views import decode_jwt from drf_yasg.utils import swagger_auto_schema @@ -56,6 +61,31 @@ def delete(self, request): return response +class LanguageView(APIView): + def get(self, request): + payload = decode_jwt(request) + if not payload: + return Response(status=status.HTTP_401_UNAUTHORIZED) + + user = get_object_or_404(User, pk=payload.get("id")) + return Response({"language": user.language}) + + @swagger_auto_schema(request_body=LanguageSerializer, responses={200: LanguageSerializer()}) + def put(self, request): + payload = decode_jwt(request) + if not payload: + return Response(status=status.HTTP_401_UNAUTHORIZED) + + user = get_object_or_404(User, pk=payload.get("id")) + serializer = LanguageSerializer(user, data=request.data, partial=True) + + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + class FriendDetailView(APIView): def get(self, request): payload = decode_jwt(request) diff --git a/nginx/default.conf b/nginx/default.conf deleted file mode 100644 index f0020a5..0000000 --- a/nginx/default.conf +++ /dev/null @@ -1,26 +0,0 @@ -server { - listen 443 ssl default_server; # 443번 포트를 사용하고, 기본 서버로 설정한다. IPv4 - listen [::]:443 ssl default_server; # 윗줄과 동일하나, IPv6 - server_name localhost; - - ssl_certificate /etc/ssl/nginx.crt; - ssl_certificate_key /etc/ssl/nginx.key; - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers HIGH:!aNULL:!MD5; # HIGH: 모든 암호화 알고리즘을 사용하되, aNULL과 MD5는 사용하지 않는다. - - location / { - proxy_pass http://frontend:5173; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /api/ { - proxy_pass http://backend:8000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} From 9f454af6cd20538db1a8b8f122a66ea7f4c0e58d Mon Sep 17 00:00:00 2001 From: seonjo1 Date: Thu, 29 Aug 2024 18:54:22 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20seunan=EA=B3=A0=E3=85=8F=20?= =?UTF-8?q?=EB=B0=B1=EC=97=94=EB=93=9C=20=EC=BD=9C=EB=9D=BC=EB=B3=B4=20(la?= =?UTF-8?q?nguage)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/transcendence/settings.py | 2 +- backend/users/views.py | 1 + frontend/src/app.js | 18 ++++++++--- frontend/src/components/2FA.js | 22 ++++++++++--- frontend/src/components/Main-Menu.js | 46 ++++++++++++++++------------ frontend/src/core/router.js | 23 +++++++++++--- 6 files changed, 76 insertions(+), 36 deletions(-) diff --git a/backend/transcendence/settings.py b/backend/transcendence/settings.py index a05feee..dcbbd29 100644 --- a/backend/transcendence/settings.py +++ b/backend/transcendence/settings.py @@ -14,7 +14,7 @@ # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent -setup_logging(level=logging.INFO) +setup_logging(level=logging.DEBUG) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ diff --git a/backend/users/views.py b/backend/users/views.py index 2d1243a..4dae52b 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -77,6 +77,7 @@ def put(self, request): return Response(status=status.HTTP_401_UNAUTHORIZED) user = get_object_or_404(User, pk=payload.get("id")) + print(request.data.get("language")) serializer = LanguageSerializer(user, data=request.data, partial=True) if serializer.is_valid(): diff --git a/frontend/src/app.js b/frontend/src/app.js index 5069649..9d0bdfe 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -8,11 +8,19 @@ class App { constructor() { this.app = document.querySelector("#app"); this.lan = { value: 0 }; - // API!!! jwt가 있으면 해당 유저의 데이터베이스에서 언어 번호 (0 or 1 or 2) 얻어오기 - // fetch - // if (respose.ok) { - // this.lan.value = data.value; - // } + fetch("https://localhost:443/api/language/", { + method: 'GET', + credentials: 'include', // 쿠키를 포함하여 요청 (사용자 인증 필요 시) + }) + .then(response => { + if (!response.ok){ + return null; + } + return response.json(); + }) + .then(data => { + if (data) this.lan.value = data.language; + }); } } diff --git a/frontend/src/components/2FA.js b/frontend/src/components/2FA.js index b186ba4..88ad8e0 100644 --- a/frontend/src/components/2FA.js +++ b/frontend/src/components/2FA.js @@ -93,11 +93,23 @@ export class TwoFA extends Component { if (data) { if (data.success) { // API!!! jwt가 있으면 해당 유저의 데이터베이스에서 언어 번호 (0 or 1 or 2) 얻어오기 - // fetch - // if (respose.ok) { - // this.lan.value = data.value; - // } - changeUrl('/main'); // 메인 페이지로 이동 + fetch("https://localhost:443/api/language/", { + method: 'GET', + credentials: 'include', // 쿠키를 포함하여 요청 (사용자 인증 필요 시) + }) + .then(response => { + if (!response.ok){ + changeUrl("/"); + return null; + } + return response.json(); + }) + .then(data => { + if (data){ + this.props.lan.value = data.language; + changeUrl('/main'); // 메인 페이지로 이동 + } + }); } else { messageElement.textContent = "Invalid code. Please try again."; messageElement.classList.remove('text-success'); diff --git a/frontend/src/components/Main-Menu.js b/frontend/src/components/Main-Menu.js index cf2bd8a..b8b74de 100644 --- a/frontend/src/components/Main-Menu.js +++ b/frontend/src/components/Main-Menu.js @@ -46,49 +46,55 @@ export class Menu extends Component { new List(document.querySelector("ul#gameMenu"), { className: "gameMode", ids: ["LocalGame", "MultiGame", "Tournament"], contents: this.translations.gameMenuTexts}); new List(document.querySelector("ul#userMenu"), { className: "showInfo", ids: ["Friends", "Profile", "Logout"], contents: this.translations.userMenuTexts}); } - + setEvent () { + this.addEvent('click', '#Friends', () => { changeUrl("/main/friends"); }); - + this.addEvent('click', '#LocalGame', () => { changeUrl(`/game/local/${this.uid}`); }); - + this.addEvent('click', "#MultiGame", () => { changeUrl("/main/matching"); }); - + this.addEvent('click', "#Tournament", () => { changeUrl("/main/tournament"); }); - + + function storeLang(value) { + fetch("https://localhost:443/api/language/", { + method: 'PUT', + credentials: 'include', // 쿠키를 포함하여 요청 (사용자 인증 필요 시) + headers: { + 'Content-Type': 'application/json' // JSON 데이터임을 명시 + }, + body: JSON.stringify({ + language: value + }) + }) + .then(response => { + if (!response.ok) changeUrl("/"); + }) + changeUrl("/main"); + } + this.addEvent('click', '#enButton', () => { this.props.lan.value = 0; - // API!!! this.props.lan.value를 db에 저장 - // fetch(){ - // this.props.lan.value 저장 - // } - changeUrl("/main") + storeLang(this.props.lan.value); }); this.addEvent('click', '#koButton', () => { this.props.lan.value = 1; - // API!!! this.props.lan.value를 db에 저장 - // fetch(){ - // this.props.lan.value 저장 - // } - changeUrl("/main") + storeLang(this.props.lan.value); }); this.addEvent('click', '#jpButton', () => { this.props.lan.value = 2; - // API!!! this.props.lan.value를 db에 저장 - // fetch(){ - // this.props.lan.value 저장 - // } - changeUrl("/main") + storeLang(this.props.lan.value); }); this.addEvent('click', '#Profile', () => { diff --git a/frontend/src/core/router.js b/frontend/src/core/router.js index 686fc87..9ef5191 100644 --- a/frontend/src/core/router.js +++ b/frontend/src/core/router.js @@ -96,11 +96,24 @@ export async function parsePath(path) { }); } else { // API!!! jwt가 있으면 해당 유저의 데이터베이스에서 언어 번호 (0 or 1 or 2) 얻어오기 - // fetch - // if (respose.ok) { - // this.lan.value = data.value; - // } - return changeUrl("/main", false); + fetch("https://localhost:443/api/language/", { + method: 'GET', + credentials: 'include', // 쿠키를 포함하여 요청 (사용자 인증 필요 시) + }) + .then(response => { + if (!response.ok){ + changeUrl("/"); + return null; + } + return response.json(); + }) + .then(data => { + if (data){ + console.log(data.language); + root.lan.value = data.language; + changeUrl('/main'); // 메인 페이지로 이동 + } + }); } } else return changeUrl("/", false); From 1e1fcf6fc6bba518f96ee0c13b924601e51243d2 Mon Sep 17 00:00:00 2001 From: seonjo1 Date: Thu, 29 Aug 2024 19:22:22 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=20?= =?UTF-8?q?=EC=BD=94=EB=94=A9=EC=87=BC=20=EC=84=B1=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app.js | 13 ------------- frontend/src/core/router.js | 18 +++++++++++++++++- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/frontend/src/app.js b/frontend/src/app.js index 9d0bdfe..cb991a4 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -8,19 +8,6 @@ class App { constructor() { this.app = document.querySelector("#app"); this.lan = { value: 0 }; - fetch("https://localhost:443/api/language/", { - method: 'GET', - credentials: 'include', // 쿠키를 포함하여 요청 (사용자 인증 필요 시) - }) - .then(response => { - if (!response.ok){ - return null; - } - return response.json(); - }) - .then(data => { - if (data) this.lan.value = data.language; - }); } } diff --git a/frontend/src/core/router.js b/frontend/src/core/router.js index 9ef5191..fc07670 100644 --- a/frontend/src/core/router.js +++ b/frontend/src/core/router.js @@ -154,7 +154,23 @@ export const initializeRouter = () => { window.addEventListener("popstate", async () => { await parsePath(window.location.pathname); }); - parsePath(window.location.pathname); + fetch("https://localhost:443/api/language/", { + method: 'GET', + credentials: 'include', // 쿠키를 포함하여 요청 (사용자 인증 필요 시) + }) + .then(response => { + if (!response.ok){ + console.log("so bad"); + return null; + } + return response.json(); + }) + .then(data => { + if (data){ + root.lan.value = data.language; + } + parsePath(window.location.pathname); + }); }; async function checkAuth() {