From 9d763be6c171abe49e63e80544219362641fd7ba Mon Sep 17 00:00:00 2001 From: aj Date: Mon, 16 Sep 2019 02:22:58 +0100 Subject: [PATCH] added request decorators and function annotations --- spotify/api/api.py | 970 ++++++++++++++++++++--------------------- spotify/db/database.py | 12 +- 2 files changed, 490 insertions(+), 492 deletions(-) diff --git a/spotify/api/api.py b/spotify/api/api.py index f226bff..26d2de0 100644 --- a/spotify/api/api.py +++ b/spotify/api/api.py @@ -4,6 +4,7 @@ import os import datetime import json import logging +import functools from google.cloud import firestore from google.cloud import tasks_v2 @@ -24,519 +25,514 @@ task_path = tasker.queue_path('sarsooxyz', 'europe-west2', 'spotify-executions') logger = logging.getLogger(__name__) +def login_required(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + if 'username' in session: + return func(*args, **kwargs) + else: + logger.warning('user not logged in') + return jsonify({'error': 'not logged in'}), 401 + return wrapper + + +def admin_required(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + user_dict = database.get_user_doc_ref(session['username']).get().to_dict() + + if user_dict: + if user_dict['type'] == 'admin': + return func(*args, **kwargs) + else: + logger.warning(f'{user_dict["username"]} not authorized') + return jsonify({'status': 'error', 'message': 'unauthorized'}), 401 + else: + logger.warning('user not logged in') + return jsonify({'error': 'not logged in'}), 401 + + return wrapper + + +def gae_cron(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + + if request.headers.get('X-Appengine-Cron', None): + return func(*args, **kwargs) + else: + logger.warning('user not logged in') + return jsonify({'status': 'error', 'message': 'unauthorised'}), 401 + + return wrapper + + +def cloud_task(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + + if request.headers.get('X-AppEngine-QueueName', None): + return func(*args, **kwargs) + else: + logger.warning('non tasks request') + return jsonify({'status': 'error', 'message': 'unauthorised'}), 401 + + return wrapper + + @blueprint.route('/playlists', methods=['GET']) +@login_required def get_playlists(): - if 'username' in session: + pulled_user = database.get_user_doc_ref(session['username']) - pulled_user = database.get_user_doc_ref(session['username']) + playlists = pulled_user.collection(u'playlists') - playlists = pulled_user.collection(u'playlists') + playlist_docs = [i.to_dict() for i in playlists.stream()] - playlist_docs = [i.to_dict() for i in playlists.stream()] + for j in playlist_docs: + j['playlist_references'] = [i.get().to_dict().get('name', 'n/a') + for i in j['playlist_references']] - for j in playlist_docs: - j['playlist_references'] = [i.get().to_dict().get('name', 'n/a') - for i in j['playlist_references']] + response = { + 'playlists': playlist_docs + } + + return jsonify(response), 200 + + +@blueprint.route('/playlist', methods=['GET', 'POST', 'PUT', 'DELETE']) +@login_required +def playlist(): + + user_ref = database.get_user_doc_ref(session['username']) + playlists = user_ref.collection(u'playlists') + + if request.method == 'GET' or request.method == 'DELETE': + playlist_name = request.args.get('name', None) + + if playlist_name: + + queried_playlist = [i for i in playlists.where(u'name', u'==', playlist_name).stream()] + + if len(queried_playlist) == 0: + return jsonify({'error': 'no playlist found'}), 404 + elif len(queried_playlist) > 1: + return jsonify({'error': 'multiple playlists found'}), 500 + + if request.method == "GET": + + playlist_doc = queried_playlist[0].to_dict() + + playlist_doc['playlist_references'] = [i.get().to_dict().get('name', 'n/a') + for i in playlist_doc['playlist_references']] + + return jsonify(playlist_doc), 200 + + elif request.method == 'DELETE': + + logger.info(f'deleted {session["username"]} / {queried_playlist[0].to_dict()["name"]}') + queried_playlist[0].reference.delete() + + return jsonify({"message": 'playlist deleted', "status": "success"}), 200 + + else: + return jsonify({"error": 'no name requested'}), 400 + + elif request.method == 'POST' or request.method == 'PUT': + + request_json = request.get_json() + + if 'name' not in request_json: + return jsonify({'error': "no name provided"}), 400 + + playlist_name = request_json['name'] + + playlist_parts = request_json.get('parts', None) + + playlist_references = [] + + if request_json.get('playlist_references', None): + if request_json['playlist_references'] != -1: + for i in request_json['playlist_references']: + retrieved_ref = database.get_user_playlist_ref_by_user_ref(user_ref, i) + if retrieved_ref: + playlist_references.append(retrieved_ref) + else: + return jsonify({"message": f'managed playlist {i} not found', "status": "error"}), 400 + + if len(playlist_references) == 0 and request_json.get('playlist_references', None) != -1: + playlist_references = None + + playlist_uri = request_json.get('uri', None) + playlist_shuffle = request_json.get('shuffle', None) + playlist_type = request_json.get('type', None) + + playlist_day_boundary = request_json.get('day_boundary', None) + playlist_add_this_month = request_json.get('add_this_month', None) + playlist_add_last_month = request_json.get('add_last_month', None) + + playlist_recommendation = request_json.get('include_recommendations', None) + playlist_recommendation_sample = request_json.get('recommendation_sample', None) + + queried_playlist = [i for i in playlists.where(u'name', u'==', playlist_name).stream()] + + if request.method == 'PUT': + + if len(queried_playlist) != 0: + return jsonify({'error': 'playlist already exists'}), 400 + + # if playlist_id is None or playlist_shuffle is None: + # return jsonify({'error': 'parts and id required'}), 400 + + from spotify.api.spotify import create_playlist as create_playlist + + to_add = { + 'name': playlist_name, + 'parts': playlist_parts if playlist_parts is not None else [], + 'playlist_references': playlist_references if playlist_references is not None else [], + 'include_recommendations': playlist_recommendation if playlist_recommendation is not None else False, + 'recommendation_sample': playlist_recommendation_sample if playlist_recommendation_sample is not None else 10, + 'uri': None, + 'shuffle': playlist_shuffle if playlist_shuffle is not None else False, + 'type': playlist_type if playlist_type is not None else 'default' + } + + if user_ref.get().to_dict()['spotify_linked']: + new_playlist = create_playlist(session['username'], playlist_name) + to_add['uri'] = str(new_playlist.uri) if new_playlist is not None else None + + if playlist_type == 'recents': + to_add['day_boundary'] = playlist_day_boundary if playlist_day_boundary is not None else 21 + to_add['add_this_month'] = playlist_add_this_month if playlist_add_this_month is not None else False + to_add['add_last_month'] = playlist_add_last_month if playlist_add_last_month is not None else False + + playlists.document().set(to_add) + logger.info(f'added {session["username"]} / {playlist_name}') + + return jsonify({"message": 'playlist added', "status": "success"}), 201 + + elif request.method == 'POST': + + if len(queried_playlist) == 0: + return jsonify({'error': "playlist doesn't exist"}), 400 + + if len(queried_playlist) > 1: + return jsonify({'error': "multiple playlists exist"}), 500 + + playlist_doc = playlists.document(queried_playlist[0].id) + + dic = {} + + if playlist_parts is not None: + if playlist_parts == -1: + dic['parts'] = [] + else: + dic['parts'] = playlist_parts + + if playlist_references is not None: + if playlist_references == -1: + dic['playlist_references'] = [] + else: + dic['playlist_references'] = playlist_references + + if playlist_uri is not None: + dic['uri'] = playlist_uri + + if playlist_shuffle is not None: + dic['shuffle'] = playlist_shuffle + + if playlist_day_boundary is not None: + dic['day_boundary'] = playlist_day_boundary + + if playlist_add_this_month is not None: + dic['add_this_month'] = playlist_add_this_month + + if playlist_add_last_month is not None: + dic['add_last_month'] = playlist_add_last_month + + if playlist_recommendation is not None: + dic['include_recommendations'] = playlist_recommendation + + if playlist_recommendation_sample is not None: + dic['recommendation_sample'] = playlist_recommendation_sample + + if playlist_type is not None: + dic['type'] = playlist_type + + if len(dic) == 0: + logger.warning(f'no changes to make for {session["username"]} / {playlist_name}') + return jsonify({"message": 'no changes to make', "status": "error"}), 400 + + playlist_doc.update(dic) + logger.info(f'updated {session["username"]} / {playlist_name}') + + return jsonify({"message": 'playlist updated', "status": "success"}), 200 + + +@blueprint.route('/user', methods=['GET', 'POST']) +@login_required +def user(): + + if request.method == 'GET': + + pulled_user = database.get_user_doc_ref(session['username']).get().to_dict() response = { - 'playlists': playlist_docs + 'username': pulled_user['username'], + 'type': pulled_user['type'], + 'spotify_linked': pulled_user['spotify_linked'], + 'validated': pulled_user['validated'] } return jsonify(response), 200 else: - logger.warning('user not logged in') - return jsonify({'error': 'not logged in'}), 401 - - -@blueprint.route('/playlist', methods=['GET', 'POST', 'PUT', 'DELETE']) -def playlist(): - - if 'username' in session: - - user_ref = database.get_user_doc_ref(session['username']) - playlists = user_ref.collection(u'playlists') - - if request.method == 'GET' or request.method == 'DELETE': - playlist_name = request.args.get('name', None) - - if playlist_name: - - queried_playlist = [i for i in playlists.where(u'name', u'==', playlist_name).stream()] - - if len(queried_playlist) == 0: - return jsonify({'error': 'no playlist found'}), 404 - elif len(queried_playlist) > 1: - return jsonify({'error': 'multiple playlists found'}), 500 - - if request.method == "GET": - - playlist_doc = queried_playlist[0].to_dict() - - playlist_doc['playlist_references'] = [i.get().to_dict().get('name', 'n/a') - for i in playlist_doc['playlist_references']] - - return jsonify(playlist_doc), 200 - - elif request.method == 'DELETE': - - logger.info(f'deleted {session["username"]} / {queried_playlist[0].to_dict()["name"]}') - queried_playlist[0].reference.delete() - - return jsonify({"message": 'playlist deleted', "status": "success"}), 200 - - else: - return jsonify({"error": 'no name requested'}), 400 - - elif request.method == 'POST' or request.method == 'PUT': - - request_json = request.get_json() - - if 'name' not in request_json: - return jsonify({'error': "no name provided"}), 400 - - playlist_name = request_json['name'] - - playlist_parts = request_json.get('parts', None) - - playlist_references = [] - - if request_json.get('playlist_references', None): - if request_json['playlist_references'] != -1: - for i in request_json['playlist_references']: - retrieved_ref = database.get_user_playlist_ref_by_user_ref(user_ref, i) - if retrieved_ref: - playlist_references.append(retrieved_ref) - else: - return jsonify({"message": f'managed playlist {i} not found', "status": "error"}), 400 - - if len(playlist_references) == 0 and request_json.get('playlist_references', None) != -1: - playlist_references = None - - playlist_uri = request_json.get('uri', None) - playlist_shuffle = request_json.get('shuffle', None) - playlist_type = request_json.get('type', None) - - playlist_day_boundary = request_json.get('day_boundary', None) - playlist_add_this_month = request_json.get('add_this_month', None) - playlist_add_last_month = request_json.get('add_last_month', None) - - playlist_recommendation = request_json.get('include_recommendations', None) - playlist_recommendation_sample = request_json.get('recommendation_sample', None) - - queried_playlist = [i for i in playlists.where(u'name', u'==', playlist_name).stream()] - - if request.method == 'PUT': - - if len(queried_playlist) != 0: - return jsonify({'error': 'playlist already exists'}), 400 - - # if playlist_id is None or playlist_shuffle is None: - # return jsonify({'error': 'parts and id required'}), 400 - - from spotify.api.spotify import create_playlist as create_playlist - - to_add = { - 'name': playlist_name, - 'parts': playlist_parts if playlist_parts is not None else [], - 'playlist_references': playlist_references if playlist_references is not None else [], - 'include_recommendations': playlist_recommendation if playlist_recommendation is not None else False, - 'recommendation_sample': playlist_recommendation_sample if playlist_recommendation_sample is not None else 10, - 'uri': None, - 'shuffle': playlist_shuffle if playlist_shuffle is not None else False, - 'type': playlist_type if playlist_type is not None else 'default' - } - - if user_ref.get().to_dict()['spotify_linked']: - new_playlist = create_playlist(session['username'], playlist_name) - to_add['uri'] = str(new_playlist.uri) if new_playlist is not None else None - - if playlist_type == 'recents': - to_add['day_boundary'] = playlist_day_boundary if playlist_day_boundary is not None else 21 - to_add['add_this_month'] = playlist_add_this_month if playlist_add_this_month is not None else False - to_add['add_last_month'] = playlist_add_last_month if playlist_add_last_month is not None else False - - playlists.document().set(to_add) - logger.info(f'added {session["username"]} / {playlist_name}') - - return jsonify({"message": 'playlist added', "status": "success"}), 201 - - elif request.method == 'POST': - - if len(queried_playlist) == 0: - return jsonify({'error': "playlist doesn't exist"}), 400 - - if len(queried_playlist) > 1: - return jsonify({'error': "multiple playlists exist"}), 500 - - playlist_doc = playlists.document(queried_playlist[0].id) - - dic = {} - - if playlist_parts is not None: - if playlist_parts == -1: - dic['parts'] = [] - else: - dic['parts'] = playlist_parts - - if playlist_references is not None: - if playlist_references == -1: - dic['playlist_references'] = [] - else: - dic['playlist_references'] = playlist_references - - if playlist_uri is not None: - dic['uri'] = playlist_uri - - if playlist_shuffle is not None: - dic['shuffle'] = playlist_shuffle - - if playlist_day_boundary is not None: - dic['day_boundary'] = playlist_day_boundary - - if playlist_add_this_month is not None: - dic['add_this_month'] = playlist_add_this_month - - if playlist_add_last_month is not None: - dic['add_last_month'] = playlist_add_last_month - - if playlist_recommendation is not None: - dic['include_recommendations'] = playlist_recommendation - - if playlist_recommendation_sample is not None: - dic['recommendation_sample'] = playlist_recommendation_sample - - if playlist_type is not None: - dic['type'] = playlist_type - - if len(dic) == 0: - logger.warning(f'no changes to make for {session["username"]} / {playlist_name}') - return jsonify({"message": 'no changes to make', "status": "error"}), 400 - - playlist_doc.update(dic) - logger.info(f'updated {session["username"]} / {playlist_name}') - - return jsonify({"message": 'playlist updated', "status": "success"}), 200 - - else: - logger.warning('user not logged in') - return jsonify({'error': 'not logged in'}), 401 - - -@blueprint.route('/user', methods=['GET', 'POST']) -def user(): - - if 'username' in session: - - if request.method == 'GET': - - pulled_user = database.get_user_doc_ref(session['username']).get().to_dict() - - response = { - 'username': pulled_user['username'], - 'type': pulled_user['type'], - 'spotify_linked': pulled_user['spotify_linked'], - 'validated': pulled_user['validated'] - } - - return jsonify(response), 200 - - else: - - if database.get_user_doc_ref(session['username']).get().to_dict()['type'] != 'admin': - return jsonify({'status': 'error', 'message': 'unauthorized'}), 401 - - request_json = request.get_json() - - if 'username' not in request_json: - return jsonify({'status': 'error', 'message': 'no username provided'}), 400 - - actionable_user = database.get_user_doc_ref(request_json['username']) - - if actionable_user.get().exists is False: - return jsonify({"message": 'non-existent user', "status": "error"}), 400 - - dic = {} - - if 'locked' in request_json: - logger.info(f'updating lock {request_json["username"]} / {request_json["locked"]}') - dic['locked'] = request_json['locked'] - - if 'spotify_linked' in request_json: - logger.info(f'deauthing {request_json["username"]}') - if request_json['spotify_linked'] is False: - dic.update({ - 'access_token': None, - 'refresh_token': None, - 'spotify_linked': False - }) - - if len(dic) == 0: - logger.warning(f'no updates for {request_json["username"]}') - return jsonify({"message": 'no changes to make', "status": "error"}), 400 - - actionable_user.update(dic) - logger.info(f'updated {request_json["username"]}') - - return jsonify({'message': 'account updated', 'status': 'succeeded'}), 200 - - else: - logger.warning('user not logged in') - return jsonify({'error': 'not logged in'}), 401 - - -@blueprint.route('/users', methods=['GET']) -def users(): - - if 'username' in session: - - if database.get_user_doc_ref(session['username']).get().to_dict()['type'] != 'admin': - return jsonify({'status': 'unauthorised'}), 401 - - dic = { - 'accounts': [] - } - - for account in [i.to_dict() for i in db.collection(u'spotify_users').stream()]: - - user_dic = { - 'username': account['username'], - 'type': account['type'], - 'spotify_linked': account['spotify_linked'], - 'locked': account['locked'], - 'last_login': account['last_login'] - } - - dic['accounts'].append(user_dic) - - return jsonify(dic), 200 - - else: - return jsonify({'error': 'not logged in'}), 401 - - -@blueprint.route('/user/password', methods=['POST']) -def change_password(): - - request_json = request.get_json() - - if 'username' in session: - - if 'new_password' in request_json and 'current_password' in request_json: - - if len(request_json['new_password']) == 0: - return jsonify({"error": 'zero length password'}), 400 - - if len(request_json['new_password']) > 30: - return jsonify({"error": 'password too long'}), 400 - - current_user = database.get_user_doc_ref(session['username']) - - if check_password_hash(current_user.get().to_dict()['password'], request_json['current_password']): - - current_user.update({'password': generate_password_hash(request_json['new_password'])}) - logger.info(f'password udpated {session["username"]}') - - return jsonify({"message": 'password changed', "status": "success"}), 200 - - else: - logger.warning(f"incorrect password {session['username']}") - return jsonify({'error': 'wrong password provided'}), 401 - - else: - return jsonify({'error': 'malformed request, no old_password/new_password'}), 400 - - else: - return jsonify({'error': 'not logged in'}), 401 - - -@blueprint.route('/playlist/play', methods=['POST']) -def play_playlist(): - - if 'username' in session: - - request_json = request.get_json() - - request_parts = request_json.get('parts', None) - request_playlist_type = request_json.get('playlist_type', 'default') - request_playlists = request_json.get('playlists', None) - request_shuffle = request_json.get('shuffle', False) - request_include_recommendations = request_json.get('include_recommendations', True) - request_recommendation_sample = request_json.get('recommendation_sample', 10) - request_day_boundary = request_json.get('day_boundary', 10) - request_add_this_month = request_json.get('add_this_month', False) - request_add_last_month = request_json.get('add_last_month', False) - - logger.info(f'playing {session["username"]}') - - if request_parts or request_playlists: - if len(request_parts) > 0 or len(request_playlists) > 0: - - if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD': - create_play_user_playlist_task(session['username'], - parts=request_parts, - playlist_type=request_playlist_type, - playlists=request_playlists, - shuffle=request_shuffle, - include_recommendations=request_include_recommendations, - recommendation_sample=request_recommendation_sample, - day_boundary=request_day_boundary, - add_this_month=request_add_this_month, - add_last_month=request_add_last_month) - else: - play_user_playlist(session['username'], - parts=request_parts, - playlist_type=request_playlist_type, - playlists=request_playlists, - shuffle=request_shuffle, - include_recommendations=request_include_recommendations, - recommendation_sample=request_recommendation_sample, - day_boundary=request_day_boundary, - add_this_month=request_add_this_month, - add_last_month=request_add_last_month) - - return jsonify({'message': 'execution requested', 'status': 'success'}), 200 - - else: - logger.error(f'insufficient playlist/part lengths {session["username"]}') - return jsonify({'error': 'insufficient playlist sources'}), 400 - - else: - logger.error(f'no playlists/parts {session["username"]}') - return jsonify({'error': 'insufficient playlist sources'}), 400 - - else: - logger.warning('user not logged in') - return jsonify({'error': 'not logged in'}), 401 - - -@blueprint.route('/playlist/play/task', methods=['POST']) -def play_playlist_task(): - if request.headers.get('X-AppEngine-QueueName', None): - payload = request.get_data(as_text=True) - if payload: - payload = json.loads(payload) - logger.info(f'playing {payload["username"]}') - - play_user_playlist(payload['username'], - parts=payload['parts'], - playlist_type=payload['playlist_type'], - playlists=payload['playlists'], - shuffle=payload['shuffle'], - include_recommendations=payload['include_recommendations'], - recommendation_sample=payload['recommendation_sample'], - day_boundary=payload['day_boundary'], - add_this_month=payload['add_this_month'], - add_last_month=payload['add_last_month']) - - return jsonify({'message': 'executed playlist', 'status': 'success'}), 200 - else: - logger.warning('non tasks request') - return jsonify({'error': 'unauthorized'}), 401 - - -@blueprint.route('/playlist/run', methods=['GET']) -def run_playlist(): - - if 'username' in session: - - playlist_name = request.args.get('name', None) - - if playlist_name: - - if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD': - create_run_user_playlist_task(session['username'], playlist_name) - else: - run_user_playlist(session['username'], playlist_name) - - return jsonify({'message': 'execution requested', 'status': 'success'}), 200 - - else: - logger.warning('no playlist requested') - return jsonify({"error": 'no name requested'}), 400 - - else: - logger.warning('user not logged in') - return jsonify({'error': 'not logged in'}), 401 - - -@blueprint.route('/playlist/run/task', methods=['POST']) -def run_playlist_task(): - - if request.headers.get('X-AppEngine-QueueName', None): - payload = request.get_data(as_text=True) - if payload: - payload = json.loads(payload) - - logger.info(f'running {payload["username"]} / {payload["name"]}') - - run_user_playlist(payload['username'], payload['name']) - - return jsonify({'message': 'executed playlist', 'status': 'success'}), 200 - else: - logger.warning('non tasks request') - return jsonify({'error': 'unauthorized'}), 401 - - -@blueprint.route('/playlist/run/user', methods=['GET']) -def run_user(): - - if 'username' in session: - - if database.get_user_doc_ref(session['username']).get().to_dict()['type'] == 'admin': - user_name = request.args.get('username', session['username']) - else: - user_name = session['username'] - - execute_user(user_name) - - return jsonify({'message': 'executed user', 'status': 'success'}), 200 - - else: - logger.warning('user not logged in') - return jsonify({'error': 'not logged in'}), 401 - - -@blueprint.route('/playlist/run/user/task', methods=['POST']) -def run_user_task(): - - if request.headers.get('X-AppEngine-QueueName', None): - payload = request.get_data(as_text=True) - if payload: - execute_user(payload) - return jsonify({'message': 'executed user', 'status': 'success'}), 200 - else: - logger.warning('non tasks request') - return jsonify({'error': 'unauthorized'}), 401 - - -@blueprint.route('/playlist/run/users', methods=['GET']) -def run_users(): - - if 'username' in session: if database.get_user_doc_ref(session['username']).get().to_dict()['type'] != 'admin': return jsonify({'status': 'error', 'message': 'unauthorized'}), 401 - execute_all_users() + request_json = request.get_json() - return jsonify({'message': 'executed all users', 'status': 'success'}), 200 + if 'username' not in request_json: + return jsonify({'status': 'error', 'message': 'no username provided'}), 400 + + actionable_user = database.get_user_doc_ref(request_json['username']) + + if actionable_user.get().exists is False: + return jsonify({"message": 'non-existent user', "status": "error"}), 400 + + dic = {} + + if 'locked' in request_json: + logger.info(f'updating lock {request_json["username"]} / {request_json["locked"]}') + dic['locked'] = request_json['locked'] + + if 'spotify_linked' in request_json: + logger.info(f'deauthing {request_json["username"]}') + if request_json['spotify_linked'] is False: + dic.update({ + 'access_token': None, + 'refresh_token': None, + 'spotify_linked': False + }) + + if len(dic) == 0: + logger.warning(f'no updates for {request_json["username"]}') + return jsonify({"message": 'no changes to make', "status": "error"}), 400 + + actionable_user.update(dic) + logger.info(f'updated {request_json["username"]}') + + return jsonify({'message': 'account updated', 'status': 'succeeded'}), 200 + + +@blueprint.route('/users', methods=['GET']) +@login_required +@admin_required +def users(): + + dic = { + 'accounts': [] + } + + for account in [i.to_dict() for i in db.collection(u'spotify_users').stream()]: + + user_dic = { + 'username': account['username'], + 'type': account['type'], + 'spotify_linked': account['spotify_linked'], + 'locked': account['locked'], + 'last_login': account['last_login'] + } + + dic['accounts'].append(user_dic) + + return jsonify(dic), 200 + + +@blueprint.route('/user/password', methods=['POST']) +@login_required +def change_password(): + + request_json = request.get_json() + + if 'new_password' in request_json and 'current_password' in request_json: + + if len(request_json['new_password']) == 0: + return jsonify({"error": 'zero length password'}), 400 + + if len(request_json['new_password']) > 30: + return jsonify({"error": 'password too long'}), 400 + + current_user = database.get_user_doc_ref(session['username']) + + if check_password_hash(current_user.get().to_dict()['password'], request_json['current_password']): + + current_user.update({'password': generate_password_hash(request_json['new_password'])}) + logger.info(f'password udpated {session["username"]}') + + return jsonify({"message": 'password changed', "status": "success"}), 200 + + else: + logger.warning(f"incorrect password {session['username']}") + return jsonify({'error': 'wrong password provided'}), 401 else: - logger.warning('user not logged in') - return jsonify({'error': 'not logged in'}), 401 + return jsonify({'error': 'malformed request, no old_password/new_password'}), 400 + + +@blueprint.route('/playlist/play', methods=['POST']) +@login_required +def play_playlist(): + + request_json = request.get_json() + + request_parts = request_json.get('parts', None) + request_playlist_type = request_json.get('playlist_type', 'default') + request_playlists = request_json.get('playlists', None) + request_shuffle = request_json.get('shuffle', False) + request_include_recommendations = request_json.get('include_recommendations', True) + request_recommendation_sample = request_json.get('recommendation_sample', 10) + request_day_boundary = request_json.get('day_boundary', 10) + request_add_this_month = request_json.get('add_this_month', False) + request_add_last_month = request_json.get('add_last_month', False) + + logger.info(f'playing {session["username"]}') + + if request_parts or request_playlists: + if len(request_parts) > 0 or len(request_playlists) > 0: + + if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD': + create_play_user_playlist_task(session['username'], + parts=request_parts, + playlist_type=request_playlist_type, + playlists=request_playlists, + shuffle=request_shuffle, + include_recommendations=request_include_recommendations, + recommendation_sample=request_recommendation_sample, + day_boundary=request_day_boundary, + add_this_month=request_add_this_month, + add_last_month=request_add_last_month) + else: + play_user_playlist(session['username'], + parts=request_parts, + playlist_type=request_playlist_type, + playlists=request_playlists, + shuffle=request_shuffle, + include_recommendations=request_include_recommendations, + recommendation_sample=request_recommendation_sample, + day_boundary=request_day_boundary, + add_this_month=request_add_this_month, + add_last_month=request_add_last_month) + + return jsonify({'message': 'execution requested', 'status': 'success'}), 200 + + else: + logger.error(f'insufficient playlist/part lengths {session["username"]}') + return jsonify({'error': 'insufficient playlist sources'}), 400 + + else: + logger.error(f'no playlists/parts {session["username"]}') + return jsonify({'error': 'insufficient playlist sources'}), 400 + + +@blueprint.route('/playlist/play/task', methods=['POST']) +@cloud_task +def play_playlist_task(): + payload = request.get_data(as_text=True) + if payload: + payload = json.loads(payload) + logger.info(f'playing {payload["username"]}') + + play_user_playlist(payload['username'], + parts=payload['parts'], + playlist_type=payload['playlist_type'], + playlists=payload['playlists'], + shuffle=payload['shuffle'], + include_recommendations=payload['include_recommendations'], + recommendation_sample=payload['recommendation_sample'], + day_boundary=payload['day_boundary'], + add_this_month=payload['add_this_month'], + add_last_month=payload['add_last_month']) + + return jsonify({'message': 'executed playlist', 'status': 'success'}), 200 + + +@blueprint.route('/playlist/run', methods=['GET']) +@login_required +def run_playlist(): + + playlist_name = request.args.get('name', None) + + if playlist_name: + + if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD': + create_run_user_playlist_task(session['username'], playlist_name) + else: + run_user_playlist(session['username'], playlist_name) + + return jsonify({'message': 'execution requested', 'status': 'success'}), 200 + + else: + logger.warning('no playlist requested') + return jsonify({"error": 'no name requested'}), 400 + + +@blueprint.route('/playlist/run/task', methods=['POST']) +@cloud_task +def run_playlist_task(): + + payload = request.get_data(as_text=True) + if payload: + payload = json.loads(payload) + + logger.info(f'running {payload["username"]} / {payload["name"]}') + + run_user_playlist(payload['username'], payload['name']) + + return jsonify({'message': 'executed playlist', 'status': 'success'}), 200 + + +@blueprint.route('/playlist/run/user', methods=['GET']) +@login_required +def run_user(): + + if database.get_user_doc_ref(session['username']).get().to_dict()['type'] == 'admin': + user_name = request.args.get('username', session['username']) + else: + user_name = session['username'] + + execute_user(user_name) + + return jsonify({'message': 'executed user', 'status': 'success'}), 200 + + +@blueprint.route('/playlist/run/user/task', methods=['POST']) +@cloud_task +def run_user_task(): + + payload = request.get_data(as_text=True) + if payload: + execute_user(payload) + return jsonify({'message': 'executed user', 'status': 'success'}), 200 + + +@blueprint.route('/playlist/run/users', methods=['GET']) +@login_required +@admin_required +def run_users(): + + execute_all_users() + return jsonify({'message': 'executed all users', 'status': 'success'}), 200 @blueprint.route('/playlist/run/users/cron', methods=['GET']) +@gae_cron def run_users_cron(): - if request.headers.get('X-Appengine-Cron', None): - execute_all_users() - return jsonify({'status': 'success'}), 200 - else: - logger.warning('user not logged in') - return jsonify({'status': 'error', 'message': 'unauthorised'}), 401 + execute_all_users() + return jsonify({'status': 'success'}), 200 def execute_all_users(): diff --git a/spotify/db/database.py b/spotify/db/database.py index 60b9515..dbc4952 100644 --- a/spotify/db/database.py +++ b/spotify/db/database.py @@ -1,12 +1,13 @@ from google.cloud import firestore import logging +from typing import List, Optional db = firestore.Client() logger = logging.getLogger(__name__) -def get_user_query_stream(user): +def get_user_query_stream(user: str) -> List[firestore.DocumentSnapshot]: users = [i for i in db.collection(u'spotify_users').where(u'username', u'==', user).stream()] @@ -17,7 +18,7 @@ def get_user_query_stream(user): return [] -def get_user_doc_ref(user): +def get_user_doc_ref(user: str) -> Optional[firestore.DocumentReference]: users = get_user_query_stream(user) @@ -34,14 +35,14 @@ def get_user_doc_ref(user): return None -def get_user_playlists_collection(user_id): +def get_user_playlists_collection(user_id: str) -> firestore.CollectionReference: playlists = db.document(u'spotify_users/{}'.format(user_id)).collection(u'playlists') return playlists -def get_user_playlist_ref_by_username(user, playlist): +def get_user_playlist_ref_by_username(user: str, playlist: str) -> Optional[firestore.CollectionReference]: user_ref = get_user_doc_ref(user) @@ -54,7 +55,8 @@ def get_user_playlist_ref_by_username(user, playlist): return None -def get_user_playlist_ref_by_user_ref(user_ref, playlist): +def get_user_playlist_ref_by_user_ref(user_ref: firestore.DocumentReference, + playlist: str) -> Optional[firestore.CollectionReference]: playlist_collection = get_user_playlists_collection(user_ref.id)