✨ Feat: [#72] 깃허브 관련 기능 구현 완료
This commit is contained in:
124
users/views.py
124
users/views.py
@@ -8,16 +8,20 @@ from rest_framework_simplejwt.serializers import TokenObtainPairSerializer, Toke
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
from rest_framework_simplejwt.exceptions import TokenError, InvalidToken
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework import viewsets, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.decorators import action
|
||||
|
||||
from django.contrib.auth import authenticate
|
||||
from django.db.models import Case, When, Value, IntegerField, Q
|
||||
from django.utils.functional import cached_property
|
||||
from django.core.cache import cache
|
||||
|
||||
from .models import *
|
||||
from .serializers import *
|
||||
from .services import *
|
||||
from .permissions import *
|
||||
|
||||
from common.utils.fileManager import file_delete
|
||||
|
||||
@@ -29,6 +33,10 @@ from django.db import transaction
|
||||
from google.oauth2 import id_token
|
||||
from google.auth.transport import requests
|
||||
|
||||
from github import Github, GithubException
|
||||
|
||||
CACHE_TIMEOUT = 60 * 60
|
||||
COMMIT_PAGE_SIZE = 20
|
||||
|
||||
class RefreshAPIView(APIView):
|
||||
permission_classes = [AllowAny]
|
||||
@@ -107,7 +115,121 @@ class GoogleLoginAPIView(APIView):
|
||||
status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
class GithubAPIViewSet(viewsets.ViewSet):
|
||||
permission_classes = [IsAuthenticated, IsGithubLinked]
|
||||
# 깃허브 연동 및 api 요청
|
||||
|
||||
def _get_github(self):
|
||||
uid = self.request.user.id
|
||||
cache_key = f"gh-token:{uid}"
|
||||
|
||||
token = cache.get(cache_key)
|
||||
|
||||
if token is None:
|
||||
token = self.request.user.github_token.access_token
|
||||
cache.set(cache_key, token, timeout=CACHE_TIMEOUT)
|
||||
|
||||
return Github(token)
|
||||
|
||||
@cached_property
|
||||
def github(self):
|
||||
return self._get_github()
|
||||
|
||||
@transaction.atomic
|
||||
@action(detail=False, methods=["post"], url_path="connect", permission_classes=[IsAuthenticated])
|
||||
def connect(self, request):
|
||||
serializer = GithubCodeSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
try:
|
||||
data = GithubTokenService.exchange_code_to_access_token(serializer.validated_data['code'])
|
||||
except Exception as e:
|
||||
return Response({"connected": False, "detail": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
GithubTokenService.github_token_save(data, request.user)
|
||||
return Response({"connected": True}, status=status.HTTP_200_OK)
|
||||
|
||||
@action(detail=False, methods=["get"], url_path="personal-repos")
|
||||
def personal_repos(self, request):
|
||||
user = self.github.get_user()
|
||||
data = [
|
||||
{'full_name': r.full_name}
|
||||
for r in user.get_repos()
|
||||
]
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
@action(detail=False, methods=["get"], url_path="commits")
|
||||
def commits(self, request):
|
||||
repo_full_name = request.query_params.get("repo")
|
||||
if not repo_full_name:
|
||||
return Response({"is_query_param": False}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
page = int(request.query_params.get("page", 1))
|
||||
except ValueError:
|
||||
return Response({"is_page_int": False,"detail": "page and page_size must be integers"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if page < 1:
|
||||
return Response({"is_page_gte_1": False, "detail": "page and page_size must be >= 1"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
repo = self.github.get_repo(repo_full_name)
|
||||
commits = repo.get_commits(per_page=COMMIT_PAGE_SIZE).get_page(page - 1)
|
||||
except Exception as e:
|
||||
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
data = [
|
||||
{"sha": c.sha, "commit_message": c.commit.message}
|
||||
for c in commits[:COMMIT_PAGE_SIZE]
|
||||
]
|
||||
|
||||
return Response({
|
||||
"page": page,
|
||||
"page_size": COMMIT_PAGE_SIZE,
|
||||
"results": data
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
@action(detail=False, methods=["get"], url_path="commit-files")
|
||||
def commit_files(self, request):
|
||||
repo_full, sha = request.query_params.get("repo"), request.query_params.get("sha")
|
||||
if not (repo_full and sha):
|
||||
return Response({"is_query_param": False}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
commit = self.github.get_repo(repo_full).get_commit(sha)
|
||||
return Response([{"filename": f.filename, "status": f.status} for f in commit.files])
|
||||
|
||||
@action(detail=False, methods=["get"], url_path="file-content")
|
||||
def file_content(self, request):
|
||||
repo_full = request.query_params.get("repo")
|
||||
filename = request.query_params.get("filename")
|
||||
sha = request.query_params.get("sha")
|
||||
if not (repo_full and filename and sha):
|
||||
return Response({"is_query_param": False}, status=status.HTTP_400_BAD_REQUEST)
|
||||
try:
|
||||
file = self.github.get_repo(repo_full).get_contents(path=filename, ref=sha)
|
||||
if file.encoding == "base64":
|
||||
content = file.decoded_content.decode("utf-8", errors="replace")
|
||||
else:
|
||||
content = file.content
|
||||
return Response({"path": file.path, "ref": sha, "content": content})
|
||||
except GithubException as e:
|
||||
return Response({"detail": str(e)}, status=e.status)
|
||||
|
||||
@action(detail=False, methods=["get"], url_path="organizations")
|
||||
def organizations(self, request):
|
||||
user = self.github.get_user()
|
||||
orgs = user.get_orgs()
|
||||
data = {"org_name": [org.name for org in orgs]}
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
@action(detail=False, methods=["get"], url_path="organization-repos")
|
||||
def organization_repos(self, request):
|
||||
org_name = request.query_params.get("org")
|
||||
repos = self.github.get_organization(org_name).get_repos()
|
||||
|
||||
data = [
|
||||
{'full_name': r.full_name}
|
||||
for r in repos
|
||||
]
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class JoinAPIView(APIView):
|
||||
|
||||
Reference in New Issue
Block a user