Skip to content

Commit 7177aa9

Browse files
committed
add api/v2 routes for clm integration
1 parent e3755a8 commit 7177aa9

4 files changed

Lines changed: 132 additions & 0 deletions

File tree

task_api.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import bcrypt
2+
import datetime
3+
import jwt
4+
5+
from flask import Response, request
6+
from functools import wraps
7+
from http import HTTPStatus
8+
9+
from task_database import complete_task, generate_task, get_user, manual_complete_tasks, manual_revert_tasks
10+
from app_setup import app, db
11+
from tasklists import get_task_tier, list_for_tier
12+
from user_dao import UserDatabaseObject
13+
14+
15+
def token_required_v2(f):
16+
@wraps(f)
17+
def decorated(*args, **kwargs):
18+
token = None
19+
if 'Authorization' in request.headers:
20+
auth_header = request.headers['Authorization']
21+
token = auth_header.split(" ")[1] if " " in auth_header else auth_header
22+
23+
if not token:
24+
return { 'error': 'No token found' }, HTTPStatus.UNAUTHORIZED
25+
26+
try:
27+
data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
28+
user = get_user(data['sub'])
29+
except:
30+
return { 'error': 'Token is invalid' }, HTTPStatus.UNAUTHORIZED
31+
32+
return f(user, *args, **kwargs)
33+
return decorated
34+
35+
36+
@app.route('/api/v2/login', methods=['POST'])
37+
def apiv2_login():
38+
body = request.json
39+
if not body or not body['username'] or not body['password']:
40+
return { 'error': 'Missing username and/or password' }, HTTPStatus.UNAUTHORIZED
41+
42+
users = db['users']
43+
user = users.find_one({ 'username': body['username'] }, { '_id': 0 })
44+
if not user:
45+
return { 'error': 'Invalid credentials' }, HTTPStatus.UNAUTHORIZED
46+
47+
if not bcrypt.checkpw(body['password'].encode('utf-8'), user['hashed_password']):
48+
return { 'error': 'Invalid credentials' }, HTTPStatus.UNAUTHORIZED
49+
50+
now = datetime.datetime.now(datetime.timezone.utc)
51+
token = jwt.encode({
52+
'sub': user['username'],
53+
'iat': now,
54+
'exp': now + datetime.timedelta(hours=24)
55+
}, app.config['SECRET_KEY'])
56+
57+
return { 'token': token }
58+
59+
60+
@app.route('/api/v2/task-list', methods=['GET'])
61+
def apiv2_get_task_list():
62+
tiers = request.args.getlist('tier') or ['easy', 'medium', 'hard', 'elite', 'master']
63+
64+
return { tier: list_for_tier(tier) for tier in tiers }
65+
66+
67+
@app.route('/api/v2/user/profile', methods=['GET'])
68+
@token_required_v2
69+
def apiv2_get_user_profile(user: UserDatabaseObject):
70+
return {
71+
'username': user.username,
72+
'is_official': user.is_official,
73+
'is_lms_enabled': user.lms_enabled,
74+
'active_task_id': user.current_task_id(),
75+
'completed_tasks': [
76+
*user.easy.completed_tasks,
77+
*user.medium.completed_tasks,
78+
*user.hard.completed_tasks,
79+
*user.elite.completed_tasks,
80+
*user.master.completed_tasks,
81+
]
82+
}
83+
84+
85+
@app.route('/api/v2/user/tasks/<id>', methods=['PATCH'])
86+
@token_required_v2
87+
def apiv2_update_user_task(user: UserDatabaseObject, id: str) -> None:
88+
tier = get_task_tier(id)
89+
body = request.json
90+
91+
if body['completed'] == True:
92+
if user.current_task_id() == id:
93+
complete_task(user.username)
94+
else:
95+
manual_complete_tasks(user.username, tier, id)
96+
elif body['completed'] == False:
97+
manual_revert_tasks(user.username, tier, id)
98+
99+
return Response(status=HTTPStatus.NO_CONTENT)
100+
101+
102+
@app.route('/api/v2/user/generate-task', methods=['POST'])
103+
@token_required_v2
104+
def apiv2_generate_task(user: UserDatabaseObject):
105+
if user.current_task():
106+
return { 'error': 'User already has an active task' }, HTTPStatus.BAD_REQUEST
107+
108+
generated_task = generate_task(user.username)
109+
if generated_task:
110+
return { 'task_id': generated_task.id }
111+
112+
return { 'error': 'No available tasks to generate' }, HTTPStatus.BAD_REQUEST

taskapp.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import send_grid_email
1717
from templesync import check_logs, temple_player_data, import_logs
1818
from task_types import CollectionLogVerificationData
19+
import task_api
1920

2021

2122
'''

tasklists.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ def read_tasks(filename: str) -> list[TaskData]:
2828
json_list = json.load(f)
2929
return list(map(to_task_class, json_list.get('tasks')))
3030

31+
def get_task_tier(task_id: str) -> str:
32+
tiers = ['easy', 'medium', 'hard', 'elite', 'master', 'passive', 'extra', 'pets']
33+
for tier in tiers:
34+
tier_tasks = list_for_tier(tier)
35+
is_task_in_tier = next((True for task in tier_tasks if task.id == task_id), False)
36+
if (is_task_in_tier):
37+
return tier
38+
39+
return None
40+
3141

3242
easy = read_tasks('easy')
3343
medium = read_tasks('medium')

user_dao.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ def current_task(self) -> tuple | None:
8686
else:
8787
return None
8888

89+
def current_task_id(self) -> str | None:
90+
current_task = self.current_task()
91+
92+
if not current_task:
93+
return None
94+
95+
return current_task[3]
96+
97+
8998
def get_tier_progress(self, tier: str) -> TierProgress:
9099
# remove instance of duplicate uuid in completed tasks
91100
def clean_tasklists(tier: str):

0 commit comments

Comments
 (0)