Skip to content

Commit

Permalink
Log into github and create new contributor
Browse files Browse the repository at this point in the history
  • Loading branch information
SalmanAsh committed Jul 23, 2024
1 parent e393154 commit 463eb78
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 63 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,6 @@ cython_debug/

# Django
static/

# 0Auth app
.env
85 changes: 25 additions & 60 deletions api/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,94 +1,59 @@
# Generated by Django 3.2.25 on 2024-07-22 14:17
# Generated by Django 3.2.25 on 2024-07-23 13:03

import django.core.validators
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = []
dependencies = [
]

operations = [
migrations.CreateModel(
name="Contributor",
name='Contributor',
fields=[
(
"id",
models.IntegerField(
help_text="The contributor's GitHub user-ID.",
primary_key=True,
serialize=False,
),
),
(
"email",
models.EmailField(max_length=254, verbose_name="email"),
),
("name", models.TextField(verbose_name="name")),
("location", models.TextField(verbose_name="location")),
("html_url", models.TextField(verbose_name="html url")),
("avatar_url", models.TextField(verbose_name="avatar url")),
('id', models.IntegerField(help_text="The contributor's GitHub user-ID.", primary_key=True, serialize=False)),
('email', models.EmailField(max_length=254, verbose_name='email')),
('name', models.TextField(verbose_name='name')),
('location', models.TextField(null=True, verbose_name='location')),
('html_url', models.TextField(verbose_name='html url')),
('avatar_url', models.TextField(verbose_name='avatar url')),
],
options={
"verbose_name": "contributor",
"verbose_name_plural": "contributors",
'verbose_name': 'contributor',
'verbose_name_plural': 'contributors',
},
),
migrations.CreateModel(
name="Repository",
name='Repository',
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"gh_id",
models.IntegerField(
help_text="Github ID of the repo a contributor has contributed to.",
verbose_name="GitHub ID",
),
),
(
"points",
models.IntegerField(
default=0,
help_text="Story points the contributor closed for this repository.",
verbose_name="points",
),
),
(
"contributor",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="api.contributor",
),
),
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('gh_id', models.IntegerField(help_text='Github ID of the repo a contributor has contributed to.', verbose_name='GitHub ID')),
('points', models.IntegerField(default=0, help_text='Story points the contributor closed for this repository.', verbose_name='points')),
('contributor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.contributor')),
],
options={
"verbose_name": "repository",
"verbose_name_plural": "repositories",
"unique_together": {("contributor", "gh_id")},
'verbose_name': 'repository',
'verbose_name_plural': 'repositories',
'unique_together': {('contributor', 'gh_id')},
},
),
migrations.CreateModel(
name="AgreementSignature",
name='AgreementSignature',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('agreement_id', models.CharField(help_text='Commit ID of the contribution agreement in workspace.', max_length=40, validators=[django.core.validators.MinLengthValidator(40)], verbose_name='agreement id')),
('signed_at', models.DateTimeField(verbose_name='signed at')),
('contributor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.contributor')),
],
options={
"verbose_name": "agreement signature",
"verbose_name_plural": "agreement signatures",
"unique_together": {("contributor", "agreement_id")},
'verbose_name': 'agreement signature',
'verbose_name_plural': 'agreement signatures',
'unique_together': {('contributor', 'agreement_id')},
},
),
]
2 changes: 1 addition & 1 deletion api/models/contributor.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Contributor(models.Model):
)
email = models.EmailField(_("email"))
name = models.TextField(_("name"))
location = models.TextField(_("location"))
location = models.TextField(_("location"), null=True)
html_url = models.TextField(_("html url"))
avatar_url = models.TextField(_("avatar url"))

Expand Down
75 changes: 74 additions & 1 deletion api/views/contributor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@
Created on 16/07/2024 at 11:03:09(+01:00).
"""

import requests
from codeforlife.permissions import AllowAny
from codeforlife.request import Request
from codeforlife.response import Response
from codeforlife.user.models import User
from codeforlife.views import ModelViewSet
from codeforlife.views import ModelViewSet, action
from rest_framework import status

import settings

from ..models import Contributor
from ..serializers import ContributorSerializer
Expand All @@ -17,3 +23,70 @@ class ContributorViewSet(ModelViewSet[User, Contributor]):
permission_classes = [AllowAny]
serializer_class = ContributorSerializer
queryset = Contributor.objects.all()

@action(detail=False, methods=["get"])
def log_into_github(self, request: Request):
"""Users can login using their existing github account"""
# Get user access Token
access_token_request = requests.post(
url="https://github.com/login/oauth/access_token",
headers={"Accept": "application/json"},
params={
"client_id": settings.GITHUB_CLIENT_ID,
"client_secret": settings.GITHUB_CLIENT_SECRET,
"code": request.GET.get("code"),
},
timeout=5,
)
if not access_token_request.ok:
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
auth_data = access_token_request.json()

# Code expired
if "access_token" not in auth_data:
return Response(
status=status.HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS
)

access_token = auth_data["access_token"]
token_type = auth_data["token_type"]

# Get user's information
user_data_request = requests.get(
url="https://api.github.com/user",
headers={
"Accept": "application/json",
"Authorization": f"{token_type} {access_token}",
},
timeout=5,
)

user_data = user_data_request.json()
if not user_data["email"]:
return Response(
data="Email null",
status=status.HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS,
)

# Check if user is already a contributor
gh_id = user_data["id"]
if Contributor.objects.filter(pk=gh_id):
return Response(status=status.HTTP_409_CONFLICT)

# Create a new contributor
data = {
"id": gh_id,
"email": user_data["email"],
"name": user_data["name"],
"location": user_data["location"],
"html_url": user_data["html_url"],
"avatar_url": user_data["avatar_url"],
}

serializer = ContributorSerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response(status=status.HTTP_200_OK)
return Response(
status=status.HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS,
)
49 changes: 49 additions & 0 deletions api/views/contributor_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@
Created on 16/07/2024 at 14:54:09(+01:00).
"""

from unittest.mock import patch

import requests
from codeforlife.request import Request
from codeforlife.tests import ModelViewSetTestCase
from codeforlife.user.models import User
from rest_framework import status

import settings

from ..models import Contributor
from .contributor import ContributorViewSet
Expand Down Expand Up @@ -41,3 +48,45 @@ def test_create(self):
"avatar_url": "https://contributortest.github.io/",
}
)

# def test_log_into_github__no_code(self):
# """Login API call does not return a code."""
# code = "3e074f3e12656707cf7f"
# request = Request
# # request.GET= {"code": code}

# with patch.object(Request, "GET", return_value=request):
# self.client.get(
# self.reverse_action(
# "log_into_github",
# ),
# status_code_assertion=status.HTTP_500_INTERNAL_SERVER_ERROR,
# )

# def test_log_into_github__no_access_token(self):
# """POST API call did not return an access token"""
# code = "3e074f3e12656707cf7f"
# response = requests.Response()
# response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
# request = Request
# request.GET = {"code": code}
# with patch.object(
# requests, "post", return_value=response
# ) as requests_get:
# self.client.get(
# self.reverse_action(
# "log_into_github",
# ),
# status_code_assertion=status.HTTP_500_INTERNAL_SERVER_ERROR,
# )

# requests_get.assert_called_once_with(
# url="https://github.com/login/oauth/access_token",
# headers={"Accept": "application/json"},
# params={
# "client_id": settings.GITHUB_CLIENT_ID,
# "client_secret": settings.GITHUB_CLIENT_SECRET,
# "code": code,
# },
# timeout=5,
# )
1 change: 1 addition & 0 deletions api/views/login.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://github.com/login/oauth/authorize?client_id=Ov23liBErSabQFqROeMg
9 changes: 8 additions & 1 deletion settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,23 @@
https://docs.djangoproject.com/en/3.2/ref/settings/
"""

import os
from pathlib import Path

# pylint: disable-next=wildcard-import,unused-wildcard-import
from codeforlife.settings import *
from dotenv import load_dotenv

# Github
GH_ORG = "ocadotechnology"
GH_REPO = "codeforlife-workspace"
GH_REPO = "codeforlife-workspace"
GH_FILE = "CONTRIBUTING.md"

# 0Auth
load_dotenv()
GITHUB_CLIENT_ID = os.getenv("GITHUB_CLIENT_ID")
GITHUB_CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET")

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent

Expand Down

0 comments on commit 463eb78

Please sign in to comment.