|
| 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 |
0 commit comments