Skip to content
This repository was archived by the owner on Apr 29, 2022. It is now read-only.

Commit 2f9f875

Browse files
committed
initial
1 parent 813e839 commit 2f9f875

File tree

1 file changed

+155
-0
lines changed

1 file changed

+155
-0
lines changed

conference/api.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
from enum import Enum
2+
import json
3+
from django.conf.urls import url as re_path
4+
from django.contrib.auth.hashers import check_password
5+
from django.db.models import Q, Case, When, Value, BooleanField
6+
from django.http import JsonResponse
7+
from django.views.decorators.csrf import csrf_exempt
8+
from conference.models import (
9+
AttendeeProfile,
10+
Conference,
11+
Speaker,
12+
TalkSpeaker,
13+
Ticket,
14+
)
15+
16+
DEBUG = True
17+
# Only these IPs can connect to the API
18+
ALLOWED_IPS = []
19+
20+
21+
# Error Codes
22+
class ApiError(Enum):
23+
WRONG_METHOD = 1
24+
AUTH_ERROR = 2
25+
INPUT_ERROR = 3
26+
UNAUTHORIZED = 4
27+
28+
29+
def _error(error: ApiError, msg: str) -> JsonResponse:
30+
return JsonResponse({
31+
'error': error.value,
32+
'message': f'{error.name}: {msg}'
33+
})
34+
35+
36+
def get_client_ip(request):
37+
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
38+
if x_forwarded_for:
39+
ip = x_forwarded_for.split(',')[0]
40+
else:
41+
ip = request.META.get('REMOTE_ADDR')
42+
return ip
43+
44+
45+
@csrf_exempt
46+
def isauth(request):
47+
"""
48+
Return whether or not the given email and password (sent via POST) are
49+
valid. If they are indeed valid, return the number and type of tickets
50+
assigned to the user.
51+
52+
Input via POST:
53+
{
54+
"email": email,
55+
"password": password (not encrypted)
56+
}
57+
Output (JSON)
58+
{
59+
"email": email,
60+
"first_name": first_name,
61+
"last_name": last_name,
62+
63+
"tickets": [{"fare_name": fare_name, "fare_code": fare_code}*]
64+
}
65+
66+
Tickets, if any, are returned only for the currently active conference and
67+
only if ASSIGNED to email.
68+
69+
If either email or password are incorrect/unknown, return
70+
{
71+
"message": "error message as string",
72+
"error": error_code
73+
}
74+
"""
75+
best_effort_ip = get_client_ip(request)
76+
if ALLOWED_IPS and best_effort_ip not in ALLOWED_IPS:
77+
return _error(ApiError.UNAUTHORIZED, 'you are not authorized here')
78+
79+
if request.method != 'POST':
80+
return _error(ApiError.WRONG_METHOD, 'please use POST')
81+
82+
required_fields = {'email', 'password'}
83+
84+
try:
85+
data = json.loads(request.body)
86+
except json.decoder.JSONDecodeError as ex:
87+
return _error(ApiError.INPUT_ERROR, ex.msg)
88+
89+
if not isinstance(data, dict) or not required_fields.issubset(data.keys()):
90+
return _error(ApiError.INPUT_ERROR,
91+
'please provide credentials in JSON format')
92+
93+
# First, let's find the user/account profile given the email address
94+
try:
95+
profile = AttendeeProfile.objects.get(user__email=data['email'])
96+
except AttendeeProfile.DoesNotExist:
97+
return _error(ApiError.AUTH_ERROR, 'unknown user')
98+
99+
# Is the password OK?
100+
if not check_password(data['password'], profile.user.password):
101+
return _error(ApiError.AUTH_ERROR, 'authentication error')
102+
103+
# Get the tickets
104+
conference = Conference.objects.current()
105+
tickets = Ticket.objects.filter(
106+
Q(fare__conference=conference.code)
107+
& Q(frozen=False)
108+
& Q(orderitem__order___complete=True)
109+
& Q(user=profile.user)
110+
).annotate(
111+
is_buyer=Case(
112+
When(orderitem__order__user__pk=profile.user.assopy_user.pk,
113+
then=Value(True)),
114+
default=Value(False),
115+
output_field=BooleanField(),
116+
)
117+
)
118+
119+
# A speaker is a user with at least one accepted talk in the current
120+
# conference.
121+
try:
122+
speaker = profile.user.speaker
123+
except Speaker.DoesNotExist:
124+
is_speaker = False
125+
else:
126+
talkspeakers = TalkSpeaker.objects.filter(
127+
speaker=speaker, talk__conference=conference.code
128+
)
129+
is_speaker = len([None for ts in talkspeakers
130+
if ts.talk.status == 'accepted']) != 0
131+
132+
payload = {
133+
"username": profile.user.username,
134+
"first_name": profile.user.first_name,
135+
"last_name": profile.user.last_name,
136+
"email": profile.user.email,
137+
"is_staff": profile.user.is_staff,
138+
"is_speaker": is_speaker,
139+
"is_active": profile.user.is_active,
140+
"is_minor": profile.is_minor,
141+
"tickets": [
142+
{"fare_name": t.fare.name, "fare_code": t.fare.code}
143+
for t in tickets
144+
]
145+
}
146+
147+
if DEBUG:
148+
data.pop('password')
149+
payload.update(data)
150+
return JsonResponse(payload)
151+
152+
153+
urlpatterns = [
154+
re_path(r"^v1/isauth/$", isauth, name="isauth"),
155+
]

0 commit comments

Comments
 (0)