From 2fae9cd1b9b127701c1479252e352aa146858d90 Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Thu, 1 Aug 2024 16:07:42 +0900 Subject: [PATCH 01/20] =?UTF-8?q?=20accounts=20=EC=97=94?= =?UTF-8?q?=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- potato_project/users/urls.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/potato_project/users/urls.py b/potato_project/users/urls.py index 70f2fa9..eaea092 100644 --- a/potato_project/users/urls.py +++ b/potato_project/users/urls.py @@ -2,25 +2,27 @@ from users import views urlpatterns = [ - path("github/login/", views.github_login, name="github_login"), - path("github/callback/", views.github_callback, name="github_callback"), + path("accounts/github/login/", views.github_login, name="github_login"), + path("accounts/github/callback/", views.github_callback, name="github_callback"), path( - "github/login/finish/", + "accounts/github/login/finish/", views.GithubLogin.as_view(), name="github_login_todjango", ), path( - "token/refresh/", views.CustomTokenRefreshView.as_view(), name="token_refresh" + "accounts/token/refresh/", + views.CustomTokenRefreshView.as_view(), + name="token_refresh", ), - path("logout/", views.logout_view, name="logout"), - path("profile/", views.UserDetail.as_view(), name="user_detail"), + path("api/accounts/logout/", views.logout_view, name="logout"), + path("api/accounts/profile/", views.UserDetail.as_view(), name="user_detail"), path( - "baekjoon_id/", + "api/accounts/baekjoon_id/", views.UpdateBaekjoonIDView.as_view(), name="update-baekjoon-id", ), path( - "nickname/", + "api/accounts/nickname/", views.UserNicknameUpdateView.as_view(), name="update-nickname", ), From 932b6905167261a621853ce9eed0c0776fd85eec Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Thu, 1 Aug 2024 16:08:11 +0900 Subject: [PATCH 02/20] =?UTF-8?q?=20app=20=EC=97=94=EB=93=9C?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- potato_project/app/urls.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/potato_project/app/urls.py b/potato_project/app/urls.py index 2d2fc0a..ee2c8a7 100644 --- a/potato_project/app/urls.py +++ b/potato_project/app/urls.py @@ -3,10 +3,10 @@ urlpatterns = [ path("api/admin/", admin.site.urls), - path("api/accounts/", include("dj_rest_auth.registration.urls")), - path("api/accounts/", include("dj_rest_auth.urls")), - path("api/accounts/", include("allauth.urls")), - path("api/accounts/", include("users.urls")), + path("accounts/", include("dj_rest_auth.registration.urls")), + path("accounts/", include("dj_rest_auth.urls")), + path("accounts/", include("allauth.urls")), + path("", include("users.urls")), path("api/baekjoons/", include("baekjoons.urls")), path("api/attendances/", include("attendances.urls")), path("api/potatoes/", include("potatoes.urls")), From 4c7fe3f986e322bf718a99dd6e18601889d685e4 Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Thu, 1 Aug 2024 16:09:16 +0900 Subject: [PATCH 03/20] =?UTF-8?q?=20github=20=EB=AA=A8=EB=8D=B8?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - date : datetime -> date --- potato_project/githubs/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/potato_project/githubs/models.py b/potato_project/githubs/models.py index ff27e5a..ba83439 100644 --- a/potato_project/githubs/models.py +++ b/potato_project/githubs/models.py @@ -8,7 +8,7 @@ class Github(TimeStampedModel): user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="User_id") commit_num = models.BigIntegerField(verbose_name="Commit Number") - date = models.DateTimeField(default=timezone.now) + date = models.DateField(default=timezone.now) def __str__(self): return f"{self.date}-{self.commit_num}" From 589f34a71200724338fab96c728bbb5fbff41572 Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Thu, 1 Aug 2024 16:09:46 +0900 Subject: [PATCH 04/20] =?UTF-8?q?=20todo=20=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - date : datetime -> date --- potato_project/todos/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/potato_project/todos/models.py b/potato_project/todos/models.py index e7f49a7..d2d4326 100644 --- a/potato_project/todos/models.py +++ b/potato_project/todos/models.py @@ -8,7 +8,7 @@ class Todo(TimeStampedModel): user = models.ForeignKey(User, on_delete=models.CASCADE) task = models.CharField(max_length=50) is_done = models.BooleanField(default=False) - date = models.DateTimeField() + date = models.DateField() def __str__(self): return self.task From eea39f00410d7eff712e6c0ffab111d88bf4fe48 Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Thu, 1 Aug 2024 16:17:30 +0900 Subject: [PATCH 05/20] =?UTF-8?q?=20todo=20views=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- potato_project/todos/views.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/potato_project/todos/views.py b/potato_project/todos/views.py index a5ef23f..91ade30 100644 --- a/potato_project/todos/views.py +++ b/potato_project/todos/views.py @@ -19,19 +19,15 @@ class TodoCreateView(generics.CreateAPIView): def perform_create(self, serializer): date_str = self.request.data.get("date") # 프론트엔드에서 전달된 날짜 문자열 try: - # datetime 객체 생성 (시간은 00:00:00으로 설정) - date_obj = datetime.strptime(date_str, "%Y-%m-%d").replace( - hour=0, minute=0, second=0, microsecond=0 - ) - # timezone-aware datetime 객체로 변환 - date = timezone.make_aware(date_obj) + # datetime 객체 생성 + date_obj = datetime.strptime(date_str, "%Y-%m-%d").date() except (ValueError, TypeError): return Response( {"error": "Invalid date format or missing date."}, status=status.HTTP_400_BAD_REQUEST, ) - serializer.save(user=self.request.user, date=date) + serializer.save(user=self.request.user, date=date_obj) # 2. 투두리스트 항목 수정 (UI에서 입력 받은 데이터 + 선택된 날짜로 수정) @@ -49,19 +45,15 @@ def get_queryset(self): def perform_update(self, serializer): date_str = self.request.data.get("date") try: - # datetime 객체 생성 (시간은 00:00:00으로 설정) - date_obj = datetime.strptime(date_str, "%Y-%m-%d").replace( - hour=0, minute=0, second=0, microsecond=0 - ) - # timezone-aware datetime 객체로 변환 - date = timezone.make_aware(date_obj) + # datetime 객체 생성 + date_obj = datetime.strptime(date_str, "%Y-%m-%d").date() except (ValueError, TypeError): return Response( {"error": "Invalid date format or missing date."}, status=status.HTTP_400_BAD_REQUEST, ) - serializer.save(date=date) + serializer.save(date=date_obj) # 3. 투두리스트 항목 삭제 From 3ba2692a0d5923e35c48d020f46aeb0f87882926 Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Thu, 1 Aug 2024 17:48:53 +0900 Subject: [PATCH 06/20] =?UTF-8?q?=20date=EB=A1=9C=20=EC=98=B5?= =?UTF-8?q?=EC=85=98=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?views=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- potato_project/todos/views.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/potato_project/todos/views.py b/potato_project/todos/views.py index 91ade30..1048ae8 100644 --- a/potato_project/todos/views.py +++ b/potato_project/todos/views.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import date, datetime, timedelta from django.db.models import Count, Q from django.shortcuts import get_object_or_404 @@ -110,7 +110,7 @@ class TodayTodoListView(generics.ListAPIView): def get_queryset(self): today = timezone.localtime(timezone.now()).date() - return Todo.objects.filter(user=self.request.user, date__date=today) + return Todo.objects.filter(user=self.request.user, date=today) # 6. 특정 날짜 투두리스트 조회 (캘린더에서 선택한 날짜 기준) @@ -125,7 +125,7 @@ def get_queryset(self): except ValueError: return Todo.objects.none() # 유효하지 않은 날짜면 빈 쿼리셋 반환 - return Todo.objects.filter(user=self.request.user, date__date=date) + return Todo.objects.filter(user=self.request.user, date=date) # 7. 월별 투두리스트 완료 개수 조회 @@ -135,25 +135,25 @@ class MonthlyCompletedTodosView(generics.ListAPIView): def list(self, request, *args, **kwargs): year = self.kwargs["year"] month = self.kwargs["month"] - start_date = datetime(year, month, 1) - end_date = start_date.replace(month=start_date.month + 1, day=1) - timedelta( - days=1 - ) + + # DateField를 사용하므로 start_date와 end_date를 date 객체로 생성 + start_date = date(year, month, 1) + # 다음 달 1일에서 하루를 빼서 해당 월의 마지막 날을 계산 + end_date = date(year, month + 1, 1) - timedelta(days=1) todos = Todo.objects.filter( user=request.user, date__range=(start_date, end_date), is_done=True ) + # 날짜별 완료 개수를 계산 daily_completed_counts = ( - todos.values("date__date") - .annotate(completed_count=Count("id")) - .order_by("date__date") + todos.values("date").annotate(completed_count=Count("id")).order_by("date") ) completed_counts = { "user_id": request.user.id, "completed_todos": { - str(item["date__date"]): item["completed_count"] + str(item["date"]): item["completed_count"] for item in daily_completed_counts }, } From e20069195dae42a6a9478b4c747b42209225cd97 Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Sun, 4 Aug 2024 22:09:16 +0900 Subject: [PATCH 07/20] docker-compose.yml --- docker-compose.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 372dcda..0b3e88d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,8 @@ services: - DB_USER=${RDS_USERNAME} - DB_PASSWORD=${RDS_PASSWORD} env_file: - - .env + - .env + # 개발용 @@ -53,7 +54,7 @@ services: # - .env # depends_on: # - db - + # db: # PostgreSQL Database # image: postgres:16-alpine @@ -66,4 +67,5 @@ services: # env_file: # - .env + From 95db2c6762edfe0841119ae675041b00583372a9 Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Sun, 4 Aug 2024 22:11:07 +0900 Subject: [PATCH 08/20] Dockerfile --- Dockerfile | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8fb6ca7..7a14c02 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,29 +19,26 @@ EXPOSE 8000 # ARG DEV=false -RUN python -m venv /py && \ - /py/bin/pip install --upgrade pip && \ - /py/bin/pip install -r /tmp/requirements.txt && \ - apk add --update --no-cache jpeg-dev && \ - apk add --update --no-cache --virtual .tmp-build-deps \ - build-base musl-dev zlib zlib-dev linux-headers && \ - # if [ $DEV = "true" ]; \ - # then /py/bin/pip install -r /tmp/requirements.dev.txt ; \ - # fi && \ - rm -rf /tmp && \ - apk del .tmp-build-deps && \ - adduser \ - --disabled-password \ - --no-create-home \ - django-user - -ENV PATH="/py/bin:$PATH" - +# 가상 환경 설정 및 패키지 설치 +RUN python -m venv /py +RUN /py/bin/pip install --upgrade pip +RUN /py/bin/pip install --no-cache-dir -r /tmp/requirements.txt + +# 시스템 패키지 설치 +RUN apk add --update --no-cache jpeg-dev +RUN apk add --update --no-cache --virtual .tmp-build-deps \ + build-base musl-dev zlib zlib-dev linux-headers \ + && apk del .tmp-build-deps + +# django-user 생성 및 권한 설정 +RUN if ! getent passwd django-user; then adduser -D django-user; fi +USER root +RUN chown -R django-user:django-user /py/lib/python3.11/site-packages USER django-user -# 이 명령어를 추가하여 pytest를 설치합니다. -RUN /py/bin/pip install pytest pytest-django - +# 추가 패키지 설치 +ENV PATH="/py/bin:$PATH" +RUN /py/bin/pip install --no-cache-dir pytest pytest-django django-cors-headers # 개발용 @@ -87,4 +84,5 @@ RUN /py/bin/pip install pytest pytest-django # USER django-user # # 이 명령어를 추가하여 pytest를 설치합니다. -# RUN /py/bin/pip install pytest pytest-django \ No newline at end of file +# RUN /py/bin/pip install pytest pytest-django + From d207fdbcd95bbdb3a90ec11e0e45e32e1e62b212 Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Sun, 4 Aug 2024 22:12:52 +0900 Subject: [PATCH 09/20] cors setting --- potato_project/app/settings.py | 61 ++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/potato_project/app/settings.py b/potato_project/app/settings.py index f0f2833..7eaac4d 100644 --- a/potato_project/app/settings.py +++ b/potato_project/app/settings.py @@ -53,8 +53,10 @@ "allauth.account", "allauth.socialaccount", "allauth.socialaccount.providers.github", + "corsheaders", ] + INSTALLED_APPS = DJANGO_SYSTEM_APPS + CUSTOM_USER_APPS # Custom user model @@ -71,6 +73,7 @@ # 미들웨어 설정 MIDDLEWARE = [ + "corsheaders.middleware.CorsMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", @@ -115,6 +118,9 @@ "USER": os.environ.get("RDS_USERNAME"), "PASSWORD": os.environ.get("RDS_PASSWORD"), "PORT": os.environ.get("RDS_PORT", 5432), + "OPTIONS": { + "client_encoding": "UTF8", # UTF-8 문자셋 설정 + }, } } @@ -205,6 +211,55 @@ } } -SOCIALACCOUNT_LOGIN_ON_GET = True -LOGIN_REDIRECT_URL = "/oauth-callback/" -ACCOUNT_LOGOUT_REDIRECT_URL = "/landing/" +SOCIALACCOUNT_LOGIN_ON_GET = False +# LOGIN_REDIRECT_URL = "/oauth-callback/" +# ACCOUNT_LOGOUT_REDIRECT_URL = "/landing/" + +DEFAULT_CHARSET = "utf-8" + +DEFAULT_CHARSET = "utf-8" + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "console": { + "class": "logging.StreamHandler", + }, + }, + "root": { + "handlers": ["console"], + "level": "DEBUG", + }, +} + +# CORS_ORIGIN_WHITELIST = ['http://localhost:5173', 'http://127.0.0.1:5173', 'https://www.gitpotatoes.com',] # 특정 Origin만 허용 +CORS_ALLOWED_ORIGINS = [ + "https://www.gitpotatoes.com", # 실제 배포 프론트엔드 URL + "http://localhost:5173", # 프론트엔드 로컬 서버 URL + "http://127.0.0.1:5173", # 프론트엔드 로컬 서버 URL +] +CORS_ALLOW_CREDENTIALS = True # 쿠키 등 credential 정보 허용 +CORS_ALLOW_METHODS = [ + "DELETE", + "GET", + "OPTIONS", + "PATCH", + "POST", + "PUT", +] +CORS_ALLOW_HEADERS = [ + "accept", + "accept-encoding", + "authorization", + "content-type", + "dnt", + "origin", + "user-agent", + "x-csrftoken", + "x-requested-with", +] +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") + +CSRF_COOKIE_SECURE = True +SESSION_COOKIE_SECURE = True From 605a1e1666246bbfe081016f51c9adc42d9cabfd Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Sun, 4 Aug 2024 22:13:59 +0900 Subject: [PATCH 10/20] isort, black --- potato_project/attendances/views.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/potato_project/attendances/views.py b/potato_project/attendances/views.py index 3014709..a85f113 100644 --- a/potato_project/attendances/views.py +++ b/potato_project/attendances/views.py @@ -40,7 +40,9 @@ def increment(self, request): # 출석날짜가 오늘이면 이미 출석함을 반환 attendance = self.get_user_attendance(user) if attendance and attendance.date == today: - return Response({"오늘은 출석을 이미 하셨어요!"}, status=status.HTTP_400_BAD_REQUEST) + return Response( + {"오늘은 출석을 이미 하셨어요!"}, status=status.HTTP_400_BAD_REQUEST + ) # 새로운 출석 기록 생성 new_attendance = Attendance.objects.create( @@ -71,4 +73,6 @@ def decrement(self, request): user.save() # 성공 응답 반환 - return Response({"message": "물건을 구매했습니다.", "total_coins": user.total_coins}) + return Response( + {"message": "물건을 구매했습니다.", "total_coins": user.total_coins} + ) From 6776c32db50023274039e5ec62fb35cd4e61bcaf Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Sun, 4 Aug 2024 22:15:50 +0900 Subject: [PATCH 11/20] =?UTF-8?q?=20=EA=B9=83=ED=97=88=EB=B8=8C=20?= =?UTF-8?q?=EC=BB=A4=EB=B0=8B=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EA=B0=90?= =?UTF-8?q?=EC=9E=90=20=ED=9A=8D=EB=93=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- potato_project/githubs/signals.py | 122 ++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 potato_project/githubs/signals.py diff --git a/potato_project/githubs/signals.py b/potato_project/githubs/signals.py new file mode 100644 index 0000000..78467e8 --- /dev/null +++ b/potato_project/githubs/signals.py @@ -0,0 +1,122 @@ +from datetime import date, timedelta + +from django.db.models.signals import post_save +from django.dispatch import receiver +from githubs.models import Github +from potatoes.models import Potato + + +@receiver(post_save, sender=Github) +def get_winter_potato(sender, instance, created, **kwargs): + # 새 Github 데이터가 생성 or 업데이트, 날짜가 크리스마스이며, commit_num이 1 이상인 경우에만 실행 + if ( + created + and instance.commit_num >= 1 + and instance.date.month == 12 + and instance.date.day == 25 + ): # 월과 일만 비교 + try: + # potato_type_id=6인 감자 조회 + potato = Potato.objects.get(user=instance.user, potato_type_id=6) + if not potato.is_acquired: # 이미 획득한 경우에는 변경하지 않음 + potato.is_acquired = True + potato.save() + except Potato.DoesNotExist: + # 해당 감자가 없는 경우 에러 처리 (필요에 따라 추가) + pass + + +@receiver(post_save, sender=Github) +def get_ghost_potato(sender, instance, created, **kwargs): + if ( + created + and instance.commit_num >= 1 + and instance.date.month == 10 + and instance.date.day == 31 + ): + try: + potato = Potato.objects.get(user=instance.user, potato_type_id=7) + if not potato.is_acquired: + potato.is_acquired = True + potato.save() + except Potato.DoesNotExist: + pass + + +@receiver(post_save, sender=Github) +def get_crystal_potato(sender, instance, created, **kwargs): + if created and instance.commit_num >= 1: + # 30일 전 날짜 계산 + thirty_days_ago = instance.date.date() - timedelta(days=30) + + # 30일 연속 커밋 여부 확인 + commits_in_30_days = ( + Github.objects.filter( + user=instance.user, + date__gte=thirty_days_ago, + date__lte=instance.date.date(), + commit_num__gte=1, + ) + .values("date") + .distinct() + .count() + ) + + if commits_in_30_days == 30: + try: + potato = Potato.objects.get(user=instance.user, potato_type_id=8) + if not potato.is_acquired: + potato.is_acquired = True + potato.save() + except Potato.DoesNotExist: + pass + + +@receiver(post_save, sender=Github) +def get_dirty_potato(sender, instance, created, **kwargs): + if created: + # 30일 전 날짜 계산 + thirty_days_ago = instance.date.date() - timedelta(days=30) + + # 30일 동안 커밋이 있었는지 확인 + any_commits_in_30_days = Github.objects.filter( + user=instance.user, + date__gte=thirty_days_ago, + date__lte=instance.date.date(), + commit_num__gte=1, + ).exists() + + if not any_commits_in_30_days: + # 30일 연속 커밋이 없는 경우 감자 아이디 9 획득 로직 실행 + try: + potato = Potato.objects.get(user=instance.user, potato_type_id=9) + if not potato.is_acquired: + potato.is_acquired = True + potato.save() + except Potato.DoesNotExist: + pass # 필요에 따라 에러 처리 추가 + + +@receiver(post_save, sender=Github) +def get_green_potato(sender, instance, created, **kwargs): + if created: + # 90일 전 날짜 계산 + ninety_days_ago = instance.date.date() - timedelta(days=90) + + # 90일 동안 커밋이 있었는지 확인 + any_commits_in_90_days = Github.objects.filter( + user=instance.user, + date__gte=ninety_days_ago, + date__lte=instance.date.date(), + commit_num__gte=1, + ).exists() + + if not any_commits_in_90_days: + # 90일 연속 커밋이 없는 경우 감자 아이디 10 획득 로직 실행 + try: + potato = Potato.objects.get(user=instance.user, potato_type_id=10) + if not potato.is_acquired: + potato.is_acquired = True + potato.save() + except Potato.DoesNotExist: + pass # 필요에 따라 에러 처리 추가 From b571aac7673d87bc0f92856de0bfc450320b200a Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Sun, 4 Aug 2024 22:16:08 +0900 Subject: [PATCH 12/20] =?UTF-8?q?=20=EB=A0=88=EB=B2=A8=EC=97=85?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EA=B0=90=EC=9E=90=20=ED=9A=8D?= =?UTF-8?q?=EB=93=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- potato_project/users/signals.py | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 potato_project/users/signals.py diff --git a/potato_project/users/signals.py b/potato_project/users/signals.py new file mode 100644 index 0000000..3fc6bb5 --- /dev/null +++ b/potato_project/users/signals.py @@ -0,0 +1,71 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver +from potatoes.models import Potato +from users.models import User + + +@receiver(post_save, sender=User) +def get_level_two_potato(sender, instance, **kwargs): + # 레벨이 2로 변경되었고, 이전 레벨이 2가 아닐 때만 실행 + if ( + instance.potato_level == 2 + and instance.tracker.previous("potato_level") != 2 + and not kwargs.get("created", False) + ): + try: + # potato_type_id=2인 감자 조회 + potato = Potato.objects.get(user=instance.user, potato_type_id=2) + if not potato.is_acquired: + potato.is_acquired = True + potato.save() + except Potato.DoesNotExist: + # 해당 감자가 없는 경우 에러 처리 (필요에 따라 추가) + pass + + +@receiver(post_save, sender=User) +def get_level_three_potato(sender, instance, **kwargs): + if ( + instance.potato_level == 3 + and instance.tracker.previous("potato_level") != 3 + and not kwargs.get("created", False) + ): + try: + potato = Potato.objects.get(user=instance.user, potato_type_id=3) + if not potato.is_acquired: + potato.is_acquired = True + potato.save() + except Potato.DoesNotExist: + pass + + +@receiver(post_save, sender=User) +def get_level_four_potato(sender, instance, **kwargs): + if ( + instance.potato_level == 4 + and instance.tracker.previous("potato_level") != 4 + and not kwargs.get("created", False) + ): + try: + potato = Potato.objects.get(user=instance.user, potato_type_id=4) + if not potato.is_acquired: + potato.is_acquired = True + potato.save() + except Potato.DoesNotExist: + pass + + +@receiver(post_save, sender=User) +def get_level_five_potato(sender, instance, **kwargs): + if ( + instance.potato_level == 5 + and instance.tracker.previous("potato_level") != 5 + and not kwargs.get("created", False) + ): + try: + potato = Potato.objects.get(user=instance.user, potato_type_id=5) + if not potato.is_acquired: + potato.is_acquired = True + potato.save() + except Potato.DoesNotExist: + pass From a704b244cd395d67311d089e69c454e524aee305 Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Sun, 4 Aug 2024 22:17:16 +0900 Subject: [PATCH 13/20] =?UTF-8?q?=20=EA=B9=83=ED=97=88=EB=B8=8C?= =?UTF-8?q?=20api=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 반환값 개수 매칭 에러 수정 - 필터에 author 추가해서 사용자가 커밋한 내용만 필터 --- potato_project/githubs/views.py | 97 ++++++++++++++++----------------- 1 file changed, 47 insertions(+), 50 deletions(-) diff --git a/potato_project/githubs/views.py b/potato_project/githubs/views.py index 60c6c4d..53a6589 100644 --- a/potato_project/githubs/views.py +++ b/potato_project/githubs/views.py @@ -1,11 +1,10 @@ from datetime import datetime, timedelta -# githubs api를 불러오는 함수 -# 깃허브 API 호출 서비스 import requests from django.db.models import Avg, Sum from django.http import JsonResponse from django.utils import timezone +from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView @@ -13,22 +12,25 @@ from .models import Github as GithubModel -# 깃허브 API 호출 서비스 class GitHubAPIService: def __init__(self, access_token): self.access_token = access_token - def get_commits(self, repo): + def get_commits(self, repo, author): github_api_url = f"https://api.github.com/repos/{repo}/commits" + params = { + "author": author, # author 파라미터 설정 + "per_page": 100, + } headers = { "Authorization": f"token {self.access_token}", "Accept": "application/vnd.github.v3+json", } - response = requests.get(github_api_url, headers=headers) + response = requests.get(github_api_url, headers=headers, params=params) if response.status_code == 200: - return response.json() + return response.json(), 200 else: return None, response.status_code @@ -42,22 +44,7 @@ def get_repos(self, username): response = requests.get(github_api_url, headers=headers) if response.status_code == 200: - return response.json() - else: - return None, response.status_code - - def get_total_commits(self, username): - github_api_url = f"https://api.github.com/search/commits?q=author:{username}" - headers = { - "Authorization": f"token {self.access_token}", - "Accept": "application/vnd.github.v3+json", - } - - response = requests.get(github_api_url, headers=headers) - - if response.status_code == 200: - data = response.json() - return data["total_count"] + return response.json(), 200 else: return None, response.status_code @@ -83,9 +70,12 @@ def get(self, request): return Response({"error": "깃허브 액세스 토큰이 없습니다."}, status=401) github_service = GitHubAPIService(user.github_access_token) + db_service = GitHubDatabaseService() # 오늘 날짜 - today = timezone.now().date() + today = timezone.localtime(timezone.now()).date() + + print("today: ", today) # 7일 전 날짜 week_ago = today - timedelta(days=7) @@ -94,57 +84,64 @@ def get(self, request): if repos is not None: # 오늘, 7일간 커밋 수 계산 - today_commit_count = 0 - week_commit_count = 0 + today_commits = 0 for repo in repos: - commits, _ = github_service.get_commits(repo["full_name"]) + commits, _ = github_service.get_commits( + repo["full_name"], user.username + ) if commits is not None: for commit in commits: commit_date = datetime.strptime( commit["commit"]["author"]["date"], "%Y-%m-%dT%H:%M:%SZ" ).date() if commit_date == today: - today_commit_count += 1 - if commit_date >= week_ago: - week_commit_count += 1 - - # 7일 평균 커밋 수 계산 - week_average_commit_count = round(week_commit_count / 7, 2) - - # 총 커밋 수 가져오기 (get_total_commits 사용) - total_commit_count = github_service.get_total_commits(user.username) + today_commits += 1 # 오늘 커밋 수 데이터베이스에 저장 db_service = GitHubDatabaseService() - db_service.update_or_create_commit_record(user, today_commit_count, today) + db_service.update_or_create_commit_record(user, today_commits, today) - # 경험치 및 레벨 업데이트 - user.exp = ( + # 최근 7일간 커밋 수 및 전체 커밋 수 계산 + week_commits = ( + GithubModel.objects.filter(user=user, date__gte=week_ago).aggregate( + Sum("commit_num") + )["commit_num__sum"] + or 0 + ) + total_commits = ( GithubModel.objects.filter(user=user).aggregate(Sum("commit_num"))[ "commit_num__sum" ] or 0 ) - level_up_threshold = 50 * 1.5**user.level - while user.exp >= level_up_threshold: - user.level += 1 - user.exp -= level_up_threshold - level_up_threshold = 50 * 1.5**user.level + + # 7일 평균 커밋 수 계산 + week_average_commit_count = round(week_commits / 7, 2) + + # 경험치 및 레벨 업데이트 + user.potato_exp = total_commits + level_up_threshold = int(50 * 1.5 ** (user.potato_level - 1)) + while user.potato_exp >= level_up_threshold: + user.potato_level += 1 + user.potato_exp -= level_up_threshold + level_up_threshold = int(50 * 1.5 ** (user.potato_level - 1)) user.save() # 다음 레벨까지 필요한 경험치 계산 - next_level_exp = 50 * 1.5**user.level - user.exp + next_level_exp = int(50 * 1.5 ** (user.potato_level - 1)) - user.potato_exp # 응답 데이터 생성 commit_statistics = { - "today_commit_count": today_commit_count, - "week_commit_count": week_commit_count, - "total_commit_count": total_commit_count, + "today_commit_count": today_commits, + "week_commit_count": week_commits, + "total_commit_count": total_commits, "week_average_commit_count": week_average_commit_count, - "level": user.level, - "exp": user.exp, - "next_level_exp": next_level_exp, # 다음 레벨까지 필요한 경험치 추가 + "level": user.potato_level, + "exp": user.potato_exp, + "next_level_exp": int( + next_level_exp + ), # 다음 레벨까지 필요한 경험치 추가 } return JsonResponse(commit_statistics, safe=False) From 25c8f4949bd1ef4b45a1845baeb2380c07f67406 Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Sun, 4 Aug 2024 22:18:20 +0900 Subject: [PATCH 14/20] =?UTF-8?q?=20permission=5Fclasses=20=3D?= =?UTF-8?q?=20[AllowAny]=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- potato_project/potato_types/views.py | 3 +++ potato_project/stacks/views.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/potato_project/potato_types/views.py b/potato_project/potato_types/views.py index 7836229..0cb5a08 100644 --- a/potato_project/potato_types/views.py +++ b/potato_project/potato_types/views.py @@ -2,6 +2,7 @@ from django.db import DatabaseError from rest_framework import status from rest_framework.exceptions import ValidationError +from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework.views import APIView @@ -11,6 +12,8 @@ # 전체 감자 목록 조회 class PotatoesList(APIView): + permission_classes = [AllowAny] + def get(self, request): try: potatoes = PotatoType.objects.all() diff --git a/potato_project/stacks/views.py b/potato_project/stacks/views.py index 2295b5b..0f8d617 100644 --- a/potato_project/stacks/views.py +++ b/potato_project/stacks/views.py @@ -2,6 +2,7 @@ from django.db import DatabaseError from rest_framework import status from rest_framework.exceptions import ValidationError +from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework.views import APIView @@ -11,6 +12,8 @@ # 전체 스택 조회 class StackList(APIView): + permission_classes = [AllowAny] + def get(self, request): try: stacks = Stack.objects.all() From 7ff0a43fe0815fa5536d84627d939a6de2a1d559 Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Sun, 4 Aug 2024 22:19:08 +0900 Subject: [PATCH 15/20] potato_id -> potato_type_id --- potato_project/potatoes/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/potato_project/potatoes/views.py b/potato_project/potatoes/views.py index 1c13239..4d33f58 100644 --- a/potato_project/potatoes/views.py +++ b/potato_project/potatoes/views.py @@ -57,8 +57,8 @@ class PotatoSelectPatch(APIView): def patch(self, request): try: - potato_id = request.data.get("id") - potato = Potato.objects.get(id=potato_id, user=request.user) + potato_type_id = request.data.get("id") + potato = Potato.objects.get(potato_type=potato_type_id, user=request.user) if not potato: return Response( From 21b5058fb7718aae449a3e6ffa62df6551c83afd Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Sun, 4 Aug 2024 22:20:38 +0900 Subject: [PATCH 16/20] =?UTF-8?q?=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- potato_project/todos/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/potato_project/todos/models.py b/potato_project/todos/models.py index d2d4326..e4ba831 100644 --- a/potato_project/todos/models.py +++ b/potato_project/todos/models.py @@ -4,7 +4,6 @@ class Todo(TimeStampedModel): - # field이름은 _id를 붙이지 않는게 좋다고하네? user = models.ForeignKey(User, on_delete=models.CASCADE) task = models.CharField(max_length=50) is_done = models.BooleanField(default=False) From 71ec3644c1aa1a942d7665bd0c8f268c8edf84dd Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Sun, 4 Aug 2024 22:20:59 +0900 Subject: [PATCH 17/20] =?UTF-8?q?=20=EB=B3=80=EC=88=98=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- potato_project/todos/views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/potato_project/todos/views.py b/potato_project/todos/views.py index 1048ae8..a6a3598 100644 --- a/potato_project/todos/views.py +++ b/potato_project/todos/views.py @@ -20,14 +20,14 @@ def perform_create(self, serializer): date_str = self.request.data.get("date") # 프론트엔드에서 전달된 날짜 문자열 try: # datetime 객체 생성 - date_obj = datetime.strptime(date_str, "%Y-%m-%d").date() + date = datetime.strptime(date_str, "%Y-%m-%d").date() except (ValueError, TypeError): return Response( {"error": "Invalid date format or missing date."}, status=status.HTTP_400_BAD_REQUEST, ) - serializer.save(user=self.request.user, date=date_obj) + serializer.save(user=self.request.user, date=date) # 2. 투두리스트 항목 수정 (UI에서 입력 받은 데이터 + 선택된 날짜로 수정) @@ -46,14 +46,14 @@ def perform_update(self, serializer): date_str = self.request.data.get("date") try: # datetime 객체 생성 - date_obj = datetime.strptime(date_str, "%Y-%m-%d").date() + date = datetime.strptime(date_str, "%Y-%m-%d").date() except (ValueError, TypeError): return Response( {"error": "Invalid date format or missing date."}, status=status.HTTP_400_BAD_REQUEST, ) - serializer.save(date=date_obj) + serializer.save(date=date) # 3. 투두리스트 항목 삭제 @@ -93,6 +93,7 @@ class TodoMarkUndoneView(generics.UpdateAPIView): def get_object(self): todo_id = self.kwargs.get("id") + return get_object_or_404(Todo, id=todo_id, user=self.request.user) def get_queryset(self): From 796d1aa247e5c3d9fafaa435bdc5d0fe8ebd8fe7 Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Sun, 4 Aug 2024 22:21:25 +0900 Subject: [PATCH 18/20] =?UTF-8?q?=20=EA=B0=90=EC=9E=90=EB=A0=88?= =?UTF-8?q?=EB=B2=A8=20=EB=94=94=ED=8F=B4=ED=8A=B8=200=20->=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- potato_project/users/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/potato_project/users/models.py b/potato_project/users/models.py index bb7bccb..50e773f 100644 --- a/potato_project/users/models.py +++ b/potato_project/users/models.py @@ -59,7 +59,7 @@ class User(AbstractBaseUser, PermissionsMixin, TimeStampedModel): nickname = models.CharField(max_length=255, null=False) # 감자 관련 필드 - potato_level = models.PositiveIntegerField(null=False, default=0) + potato_level = models.PositiveIntegerField(null=False, default=1) potato_exp = models.PositiveIntegerField(null=False, default=0) total_coins = models.PositiveIntegerField(default=0) From 652b837453c0bbe3674453671759c5e3a923c8b5 Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Sun, 4 Aug 2024 22:21:49 +0900 Subject: [PATCH 19/20] =?UTF-8?q?=20api=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- potato_project/users/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/potato_project/users/urls.py b/potato_project/users/urls.py index eaea092..d4ce7d3 100644 --- a/potato_project/users/urls.py +++ b/potato_project/users/urls.py @@ -14,7 +14,7 @@ views.CustomTokenRefreshView.as_view(), name="token_refresh", ), - path("api/accounts/logout/", views.logout_view, name="logout"), + path("api/logout/", views.logout_view, name="logout"), path("api/accounts/profile/", views.UserDetail.as_view(), name="user_detail"), path( "api/accounts/baekjoon_id/", From 4b59ebc0d3a219f8f560ec46690923650b526d40 Mon Sep 17 00:00:00 2001 From: dayeonkimm Date: Sun, 4 Aug 2024 22:22:53 +0900 Subject: [PATCH 20/20] =?UTF-8?q?=20=EB=A6=AC=EB=94=94=EB=A0=89?= =?UTF-8?q?=EC=85=98=20url=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- potato_project/users/views.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/potato_project/users/views.py b/potato_project/users/views.py index 27785c6..4deb7f9 100644 --- a/potato_project/users/views.py +++ b/potato_project/users/views.py @@ -30,10 +30,9 @@ load_dotenv() # .env 파일 로드 - state = os.environ.get("STATE") -# BASE_URL = "http://43.201.150.178:8000/" -BASE_URL = "http://localhost:8000/" # 프론트엔드 URL로 변경해야 함 +BASE_URL = "https://api.gitpotatoes.com/" +# BASE_URL = "http://localhost:8000" GITHUB_CALLBACK_URI = BASE_URL + "accounts/github/callback/" @@ -98,6 +97,7 @@ def github_callback(request): "https://api.github.com/user", headers={"Authorization": f"Bearer {access_token}"}, ) + user_response.encoding = "utf-8" user_json = user_response.json() # 에러 처리 @@ -181,7 +181,20 @@ def github_callback(request): secure=True, samesite="Lax", ) - return response + + jwt_access_token = login_data.get("access_token") + jwt_refresh_token = login_data.get("refresh_token") + user_data = ( + { + "pk": user.pk, + "username": user.username, + "profile_url": user.profile_url, + "nickname": user.nickname, + }, + ) + redirect_url = f"https://www.gitpotatoes.com/oauth-callback?access_token={jwt_access_token}&refresh_token={jwt_refresh_token}&user={json.dumps(user_data)}" + return redirect(redirect_url) + # return response class GithubLogin(SocialLoginView): @@ -211,7 +224,6 @@ def get_response(self): "nickname": user.nickname, }, } - # return redirect(settings.LOGIN_REDIRECT_URL) return JsonResponse(response_data) return response