API - refactor api responses

This commit is contained in:
Sam 2021-01-01 16:39:25 +01:00
parent 21e79c8883
commit 4705393a08
15 changed files with 482 additions and 564 deletions

View File

@ -240,7 +240,8 @@
<dt class="field-even">Status Codes</dt> <dt class="field-even">Status Codes</dt>
<dd class="field-even"><ul class="simple"> <dd class="field-even"><ul class="simple">
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a> Successfully logged in.</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a> Successfully logged in.</p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> Invalid credentials.</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> Invalid payload.</p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> Invalid credentials.</p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a> Error. Please try again or contact the administrator.</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a> Error. Please try again or contact the administrator.</p></li>
</ul> </ul>
</dd> </dd>

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,15 @@ from datetime import datetime, timedelta
import requests import requests
from fittrackee import appLog, db from fittrackee import appLog, db
from flask import Blueprint, Response, current_app, jsonify, request, send_file from fittrackee.responses import (
DataInvalidPayloadErrorResponse,
DataNotFoundErrorResponse,
InternalServerErrorResponse,
InvalidPayloadErrorResponse,
NotFoundErrorResponse,
handle_error_and_return_response,
)
from flask import Blueprint, Response, current_app, request, send_file
from sqlalchemy import exc from sqlalchemy import exc
from ..users.utils import ( from ..users.utils import (
@ -251,7 +259,7 @@ def get_activities(auth_user_id):
.paginate(page, per_page, False) .paginate(page, per_page, False)
.items .items
) )
response_object = { return {
'status': 'success', 'status': 'success',
'data': { 'data': {
'activities': [ 'activities': [
@ -259,15 +267,8 @@ def get_activities(auth_user_id):
] ]
}, },
} }
code = 200
except Exception as e: except Exception as e:
appLog.error(e) return handle_error_and_return_response(e)
response_object = {
'status': 'error',
'message': 'Error. Please try again or contact the administrator.',
}
code = 500
return jsonify(response_object), code
@activities_blueprint.route( @activities_blueprint.route(
@ -360,27 +361,17 @@ def get_activity(auth_user_id, activity_short_id):
""" """
activity_uuid = decode_short_id(activity_short_id) activity_uuid = decode_short_id(activity_short_id)
activity = Activity.query.filter_by(uuid=activity_uuid).first() activity = Activity.query.filter_by(uuid=activity_uuid).first()
activities_list = [] if not activity:
return DataNotFoundErrorResponse('activities')
if activity: error_response = can_view_activity(auth_user_id, activity.user_id)
response_object, code = can_view_activity( if error_response:
auth_user_id, activity.user_id return error_response
)
if response_object:
return jsonify(response_object), code
activities_list.append(activity.serialize()) return {
status = 'success' 'status': 'success',
code = 200 'data': {'activities': [activity.serialize()]},
else:
status = 'not found'
code = 404
response_object = {
'status': status,
'data': {'activities': activities_list},
} }
return jsonify(response_object), code
def get_activity_data( def get_activity_data(
@ -389,23 +380,23 @@ def get_activity_data(
"""Get data from an activity gpx file""" """Get data from an activity gpx file"""
activity_uuid = decode_short_id(activity_short_id) activity_uuid = decode_short_id(activity_short_id)
activity = Activity.query.filter_by(uuid=activity_uuid).first() activity = Activity.query.filter_by(uuid=activity_uuid).first()
content = '' if not activity:
if activity: return DataNotFoundErrorResponse(
response_object, code = can_view_activity( data_type=data_type,
auth_user_id, activity.user_id message=f'Activity not found (id: {activity_short_id})',
) )
if response_object:
return jsonify(response_object), code error_response = can_view_activity(auth_user_id, activity.user_id)
if error_response:
return error_response
if not activity.gpx or activity.gpx == '': if not activity.gpx or activity.gpx == '':
message = ( return NotFoundErrorResponse(
f'No gpx file for this activity (id: {activity_short_id})' f'No gpx file for this activity (id: {activity_short_id})'
) )
response_object = {'status': 'error', 'message': message}
return jsonify(response_object), 404
try: try:
absolute_gpx_filepath = get_absolute_file_path(activity.gpx) absolute_gpx_filepath = get_absolute_file_path(activity.gpx)
if data_type == 'chart': if data_type == 'chart_data':
content = get_chart_data(absolute_gpx_filepath, segment_id) content = get_chart_data(absolute_gpx_filepath, segment_id)
else: # data_type == 'gpx' else: # data_type == 'gpx'
with open(absolute_gpx_filepath, encoding='utf-8') as f: with open(absolute_gpx_filepath, encoding='utf-8') as f:
@ -416,32 +407,17 @@ def get_activity_data(
) )
except ActivityGPXException as e: except ActivityGPXException as e:
appLog.error(e.message) appLog.error(e.message)
response_object = {'status': e.status, 'message': e.message} if e.status == 'not found':
code = 404 if e.status == 'not found' else 500 return NotFoundErrorResponse(e.message)
return jsonify(response_object), code return InternalServerErrorResponse(e.message)
except Exception as e: except Exception as e:
appLog.error(e) return handle_error_and_return_response(e)
response_object = {'status': 'error', 'message': 'internal error'}
return jsonify(response_object), 500
status = 'success' return {
message = '' 'status': 'success',
code = 200 'message': '',
else: 'data': ({data_type: content}),
status = 'not found'
message = f'Activity not found (id: {activity_short_id})'
code = 404
response_object = {
'status': status,
'message': message,
'data': (
{'chart_data': content}
if data_type == 'chart'
else {'gpx': content}
),
} }
return jsonify(response_object), code
@activities_blueprint.route( @activities_blueprint.route(
@ -558,7 +534,7 @@ def get_activity_chart_data(auth_user_id, activity_short_id):
:statuscode 500: :statuscode 500:
""" """
return get_activity_data(auth_user_id, activity_short_id, 'chart') return get_activity_data(auth_user_id, activity_short_id, 'chart_data')
@activities_blueprint.route( @activities_blueprint.route(
@ -681,7 +657,7 @@ def get_segment_chart_data(auth_user_id, activity_short_id, segment_id):
""" """
return get_activity_data( return get_activity_data(
auth_user_id, activity_short_id, 'chart', segment_id auth_user_id, activity_short_id, 'chart_data', segment_id
) )
@ -718,18 +694,11 @@ def get_map(map_id):
try: try:
activity = Activity.query.filter_by(map_id=map_id).first() activity = Activity.query.filter_by(map_id=map_id).first()
if not activity: if not activity:
response_object = { return NotFoundErrorResponse('Map does not exist.')
'status': 'error',
'message': 'Map does not exist',
}
return jsonify(response_object), 404
else:
absolute_map_filepath = get_absolute_file_path(activity.map) absolute_map_filepath = get_absolute_file_path(activity.map)
return send_file(absolute_map_filepath) return send_file(absolute_map_filepath)
except Exception as e: except Exception as e:
appLog.error(e) return handle_error_and_return_response(e)
response_object = {'status': 'error', 'message': 'internal error.'}
return jsonify(response_object), 500
@activities_blueprint.route( @activities_blueprint.route(
@ -887,16 +856,13 @@ def post_activity(auth_user_id):
:statuscode 500: :statuscode 500:
""" """
response_object, response_code = verify_extension_and_size( error_response = verify_extension_and_size('activity', request)
'activity', request if error_response:
) return error_response
if response_object['status'] != 'success':
return jsonify(response_object), response_code
activity_data = json.loads(request.form['data']) activity_data = json.loads(request.form['data'])
if not activity_data or activity_data.get('sport_id') is None: if not activity_data or activity_data.get('sport_id') is None:
response_object = {'status': 'error', 'message': 'Invalid payload.'} return InvalidPayloadErrorResponse()
return jsonify(response_object), 400
activity_file = request.files['file'] activity_file = request.files['file']
upload_dir = os.path.join( upload_dir = os.path.join(
@ -921,20 +887,19 @@ def post_activity(auth_user_id):
] ]
}, },
} }
code = 201
else: else:
response_object = {'status': 'fail', 'data': {'activities': []}} return DataInvalidPayloadErrorResponse('activities', 'fail')
code = 400
except ActivityException as e: except ActivityException as e:
db.session.rollback() db.session.rollback()
if e.e: if e.e:
appLog.error(e.e) appLog.error(e.e)
response_object = {'status': e.status, 'message': e.message} if e.status == 'error':
code = 500 if e.status == 'error' else 400 return InternalServerErrorResponse(e.message)
return InvalidPayloadErrorResponse(e.message)
shutil.rmtree(folders['extract_dir'], ignore_errors=True) shutil.rmtree(folders['extract_dir'], ignore_errors=True)
shutil.rmtree(folders['tmp_dir'], ignore_errors=True) shutil.rmtree(folders['tmp_dir'], ignore_errors=True)
return jsonify(response_object), code return response_object, 201
@activities_blueprint.route('/activities/no_gpx', methods=['POST']) @activities_blueprint.route('/activities/no_gpx', methods=['POST'])
@ -1059,8 +1024,7 @@ def post_activity_no_gpx(auth_user_id):
or activity_data.get('distance') is None or activity_data.get('distance') is None
or activity_data.get('activity_date') is None or activity_data.get('activity_date') is None
): ):
response_object = {'status': 'error', 'message': 'Invalid payload.'} return InvalidPayloadErrorResponse()
return jsonify(response_object), 400
try: try:
user = User.query.filter_by(id=auth_user_id).first() user = User.query.filter_by(id=auth_user_id).first()
@ -1068,20 +1032,21 @@ def post_activity_no_gpx(auth_user_id):
db.session.add(new_activity) db.session.add(new_activity)
db.session.commit() db.session.commit()
response_object = { return (
{
'status': 'created', 'status': 'created',
'data': {'activities': [new_activity.serialize()]}, 'data': {'activities': [new_activity.serialize()]},
} },
return jsonify(response_object), 201 201,
)
except (exc.IntegrityError, ValueError) as e: except (exc.IntegrityError, ValueError) as e:
db.session.rollback() return handle_error_and_return_response(
appLog.error(e) error=e,
response_object = { message='Error during activity save.',
'status': 'fail', status='fail',
'message': 'Error during activity save.', db=db,
} )
return jsonify(response_object), 500
@activities_blueprint.route( @activities_blueprint.route(
@ -1207,41 +1172,27 @@ def update_activity(auth_user_id, activity_short_id):
""" """
activity_data = request.get_json() activity_data = request.get_json()
if not activity_data: if not activity_data:
response_object = {'status': 'error', 'message': 'Invalid payload.'} return InvalidPayloadErrorResponse()
return jsonify(response_object), 400
try: try:
activity_uuid = decode_short_id(activity_short_id) activity_uuid = decode_short_id(activity_short_id)
activity = Activity.query.filter_by(uuid=activity_uuid).first() activity = Activity.query.filter_by(uuid=activity_uuid).first()
if activity: if not activity:
response_object, code = can_view_activity( return DataNotFoundErrorResponse('activities')
auth_user_id, activity.user_id
) response_object = can_view_activity(auth_user_id, activity.user_id)
if response_object: if response_object:
return jsonify(response_object), code return response_object
activity = edit_activity(activity, activity_data, auth_user_id) activity = edit_activity(activity, activity_data, auth_user_id)
db.session.commit() db.session.commit()
response_object = { return {
'status': 'success', 'status': 'success',
'data': {'activities': [activity.serialize()]}, 'data': {'activities': [activity.serialize()]},
} }
code = 200
else:
response_object = {
'status': 'not found',
'data': {'activities': []},
}
code = 404
except (exc.IntegrityError, exc.OperationalError, ValueError) as e: except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
db.session.rollback() return handle_error_and_return_response(e)
appLog.error(e)
response_object = {
'status': 'error',
'message': 'Error. Please try again or contact the administrator.',
}
code = 500
return jsonify(response_object), code
@activities_blueprint.route( @activities_blueprint.route(
@ -1284,34 +1235,19 @@ def delete_activity(auth_user_id, activity_short_id):
try: try:
activity_uuid = decode_short_id(activity_short_id) activity_uuid = decode_short_id(activity_short_id)
activity = Activity.query.filter_by(uuid=activity_uuid).first() activity = Activity.query.filter_by(uuid=activity_uuid).first()
if activity: if not activity:
response_object, code = can_view_activity( return DataNotFoundErrorResponse('activities')
auth_user_id, activity.user_id error_response = can_view_activity(auth_user_id, activity.user_id)
) if error_response:
if response_object: return error_response
return jsonify(response_object), code
db.session.delete(activity) db.session.delete(activity)
db.session.commit() db.session.commit()
response_object = {'status': 'no content'} return {'status': 'no content'}, 204
code = 204
else:
response_object = {
'status': 'not found',
'data': {'activities': []},
}
code = 404
except ( except (
exc.IntegrityError, exc.IntegrityError,
exc.OperationalError, exc.OperationalError,
ValueError, ValueError,
OSError, OSError,
) as e: ) as e:
db.session.rollback() return handle_error_and_return_response(e, db=db)
appLog.error(e)
response_object = {
'status': 'error',
'message': 'Error. Please try again or contact the administrator.',
}
code = 500
return jsonify(response_object), code

View File

@ -1,4 +1,4 @@
from flask import Blueprint, jsonify from flask import Blueprint
from ..users.utils import authenticate from ..users.utils import authenticate
from .models import Record from .models import Record
@ -103,14 +103,12 @@ def get_records(auth_user_id):
- Invalid token. Please log in again. - Invalid token. Please log in again.
""" """
records = ( records = (
Record.query.filter_by(user_id=auth_user_id) Record.query.filter_by(user_id=auth_user_id)
.order_by(Record.sport_id.asc(), Record.record_type.asc()) .order_by(Record.sport_id.asc(), Record.record_type.asc())
.all() .all()
) )
response_object = { return {
'status': 'success', 'status': 'success',
'data': {'records': [record.serialize() for record in records]}, 'data': {'records': [record.serialize() for record in records]},
} }
return jsonify(response_object), 200

View File

@ -1,5 +1,10 @@
from fittrackee import appLog, db from fittrackee import db
from flask import Blueprint, jsonify, request from fittrackee.responses import (
DataNotFoundErrorResponse,
InvalidPayloadErrorResponse,
handle_error_and_return_response,
)
from flask import Blueprint, request
from sqlalchemy import exc from sqlalchemy import exc
from ..users.models import User from ..users.models import User
@ -143,14 +148,12 @@ def get_sports(auth_user_id):
- Invalid token. Please log in again. - Invalid token. Please log in again.
""" """
user = User.query.filter_by(id=int(auth_user_id)).first() user = User.query.filter_by(id=int(auth_user_id)).first()
sports = Sport.query.order_by(Sport.id).all() sports = Sport.query.order_by(Sport.id).all()
response_object = { return {
'status': 'success', 'status': 'success',
'data': {'sports': [sport.serialize(user.admin) for sport in sports]}, 'data': {'sports': [sport.serialize(user.admin) for sport in sports]},
} }
return jsonify(response_object), 200
@sports_blueprint.route('/sports/<int:sport_id>', methods=['GET']) @sports_blueprint.route('/sports/<int:sport_id>', methods=['GET'])
@ -238,19 +241,14 @@ def get_sport(auth_user_id, sport_id):
:statuscode 404: sport not found :statuscode 404: sport not found
""" """
user = User.query.filter_by(id=int(auth_user_id)).first() user = User.query.filter_by(id=int(auth_user_id)).first()
sport = Sport.query.filter_by(id=sport_id).first() sport = Sport.query.filter_by(id=sport_id).first()
if sport: if sport:
response_object = { return {
'status': 'success', 'status': 'success',
'data': {'sports': [sport.serialize(user.admin)]}, 'data': {'sports': [sport.serialize(user.admin)]},
} }
code = 200 return DataNotFoundErrorResponse('sports')
else:
response_object = {'status': 'not found', 'data': {'sports': []}}
code = 404
return jsonify(response_object), code
@sports_blueprint.route('/sports/<int:sport_id>', methods=['PATCH']) @sports_blueprint.route('/sports/<int:sport_id>', methods=['PATCH'])
@ -325,28 +323,19 @@ def update_sport(auth_user_id, sport_id):
""" """
sport_data = request.get_json() sport_data = request.get_json()
if not sport_data or sport_data.get('is_active') is None: if not sport_data or sport_data.get('is_active') is None:
response_object = {'status': 'error', 'message': 'Invalid payload.'} return InvalidPayloadErrorResponse()
return jsonify(response_object), 400
try: try:
sport = Sport.query.filter_by(id=sport_id).first() sport = Sport.query.filter_by(id=sport_id).first()
if sport: if not sport:
return DataNotFoundErrorResponse('sports')
sport.is_active = sport_data.get('is_active') sport.is_active = sport_data.get('is_active')
db.session.commit() db.session.commit()
response_object = { return {
'status': 'success', 'status': 'success',
'data': {'sports': [sport.serialize(True)]}, 'data': {'sports': [sport.serialize(True)]},
} }
code = 200
else:
response_object = {'status': 'not found', 'data': {'sports': []}}
code = 404
except (exc.IntegrityError, exc.OperationalError, ValueError) as e: except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
db.session.rollback() return handle_error_and_return_response(e, db=db)
appLog.error(e)
response_object = {
'status': 'error',
'message': 'Error. Please try again or contact the administrator.',
}
code = 500
return jsonify(response_object), code

View File

@ -1,7 +1,13 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from fittrackee import appLog, db from fittrackee import db
from flask import Blueprint, jsonify, request from fittrackee.responses import (
InvalidPayloadErrorResponse,
NotFoundErrorResponse,
UserNotFoundErrorResponse,
handle_error_and_return_response,
)
from flask import Blueprint, request
from sqlalchemy import func from sqlalchemy import func
from ..users.models import User from ..users.models import User
@ -17,11 +23,7 @@ def get_activities(user_name, filter_type):
try: try:
user = User.query.filter_by(username=user_name).first() user = User.query.filter_by(username=user_name).first()
if not user: if not user:
response_object = { return UserNotFoundErrorResponse()
'status': 'not found',
'message': 'User does not exist.',
}
return jsonify(response_object), 404
params = request.args.copy() params = request.args.copy()
date_from = params.get('from') date_from = params.get('from')
@ -42,11 +44,7 @@ def get_activities(user_name, filter_type):
if sport_id: if sport_id:
sport = Sport.query.filter_by(id=sport_id).first() sport = Sport.query.filter_by(id=sport_id).first()
if not sport: if not sport:
response_object = { return NotFoundErrorResponse('Sport does not exist.')
'status': 'not found',
'message': 'Sport does not exist.',
}
return jsonify(response_object), 404
activities = ( activities = (
Activity.query.filter( Activity.query.filter(
@ -103,11 +101,9 @@ def get_activities(user_name, filter_type):
activity.activity_date, "%Y" activity.activity_date, "%Y"
) )
else: else:
response_object = { return InvalidPayloadErrorResponse(
'status': 'fail', 'Invalid time period.', 'fail'
'message': 'Invalid time period.', )
}
return jsonify(response_object), 400
sport_id = activity.sport_id sport_id = activity.sport_id
if time_period not in activities_list: if time_period not in activities_list:
activities_list[time_period] = {} activities_list[time_period] = {}
@ -125,19 +121,12 @@ def get_activities(user_name, filter_type):
'total_duration' 'total_duration'
] += convert_timedelta_to_integer(activity.moving) ] += convert_timedelta_to_integer(activity.moving)
response_object = { return {
'status': 'success', 'status': 'success',
'data': {'statistics': activities_list}, 'data': {'statistics': activities_list},
} }
code = 200
except Exception as e: except Exception as e:
appLog.error(e) return handle_error_and_return_response(e)
response_object = {
'status': 'error',
'message': 'Error. Please try again or contact the administrator.',
}
code = 500
return jsonify(response_object), code
@stats_blueprint.route('/stats/<user_name>/by_time', methods=['GET']) @stats_blueprint.route('/stats/<user_name>/by_time', methods=['GET'])
@ -371,7 +360,7 @@ def get_application_stats(auth_user_id):
.group_by(Activity.sport_id) .group_by(Activity.sport_id)
.count() .count()
) )
response_object = { return {
'status': 'success', 'status': 'success',
'data': { 'data': {
'activities': nb_activities, 'activities': nb_activities,
@ -380,4 +369,3 @@ def get_application_stats(auth_user_id):
'uploads_dir_size': get_upload_dir_size(), 'uploads_dir_size': get_upload_dir_size(),
}, },
} }
return jsonify(response_object), 200

View File

@ -1,5 +1,9 @@
from fittrackee import appLog, db from fittrackee import db
from flask import Blueprint, current_app, jsonify, request from fittrackee.responses import (
InvalidPayloadErrorResponse,
handle_error_and_return_response,
)
from flask import Blueprint, current_app, request
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
from ..users.utils import authenticate_as_admin from ..users.utils import authenticate_as_admin
@ -46,15 +50,11 @@ def get_application_config():
try: try:
config = AppConfig.query.one() config = AppConfig.query.one()
response_object = {'status': 'success', 'data': config.serialize()} return {'status': 'success', 'data': config.serialize()}
return jsonify(response_object), 200
except (MultipleResultsFound, NoResultFound) as e: except (MultipleResultsFound, NoResultFound) as e:
appLog.error(e) return handle_error_and_return_response(
response_object = { e, message='Error on getting configuration.'
'status': 'error', )
'message': 'Error on getting configuration.',
}
return jsonify(response_object), 500
@config_blueprint.route('/config', methods=['PATCH']) @config_blueprint.route('/config', methods=['PATCH'])
@ -111,8 +111,7 @@ def update_application_config(auth_user_id):
""" """
config_data = request.get_json() config_data = request.get_json()
if not config_data: if not config_data:
response_object = {'status': 'error', 'message': 'Invalid payload.'} return InvalidPayloadErrorResponse()
return jsonify(response_object), 400
try: try:
config = AppConfig.query.one() config = AppConfig.query.one()
@ -129,18 +128,12 @@ def update_application_config(auth_user_id):
db.session.commit() db.session.commit()
update_app_config_from_database(current_app, config) update_app_config_from_database(current_app, config)
return {'status': 'success', 'data': config.serialize()}
response_object = {'status': 'success', 'data': config.serialize()}
code = 200
except Exception as e: except Exception as e:
appLog.error(e) return handle_error_and_return_response(
response_object = { e, message='Error on updating configuration.'
'status': 'error', )
'message': 'Error on updating configuration.',
}
code = 500
return jsonify(response_object), code
@config_blueprint.route('/ping', methods=['GET']) @config_blueprint.route('/ping', methods=['GET'])
@ -169,4 +162,4 @@ def health_check():
:statuscode 200: success :statuscode 200: success
""" """
return jsonify({'status': 'success', 'message': 'pong!'}) return {'status': 'success', 'message': 'pong!'}

117
fittrackee/responses.py Normal file
View File

@ -0,0 +1,117 @@
from json import dumps
from fittrackee import appLog
from flask import Response
def get_empty_data_for_datatype(data_type):
return '' if data_type in ['gpx', 'chart_data'] else []
class HttpResponse(Response):
def __init__(
self,
response=None,
status_code=None,
content_type=None,
):
if isinstance(response, dict):
response = dumps(response)
content_type = (
'application/json' if content_type is None else content_type
)
super().__init__(
response=response,
status=status_code,
content_type=content_type,
)
class GenericErrorResponse(HttpResponse):
def __init__(self, status_code, message, status=None):
response = {
'status': 'error' if status is None else status,
'message': message,
}
super().__init__(
response=response,
status_code=status_code,
)
class InvalidPayloadErrorResponse(GenericErrorResponse):
def __init__(self, message=None, status=None):
message = 'Invalid payload.' if message is None else message
super().__init__(status_code=400, message=message, status=status)
class DataInvalidPayloadErrorResponse(HttpResponse):
def __init__(self, data_type, status=None):
response = {
'status': 'error' if status is None else status,
'data': {data_type: get_empty_data_for_datatype(data_type)},
}
super().__init__(response=response, status_code=400)
class UnauthorizedErrorResponse(GenericErrorResponse):
def __init__(self, message=None):
message = (
'Invalid token. Please request a new token.'
if message is None
else message
)
super().__init__(status_code=401, message=message)
class ForbiddenErrorResponse(GenericErrorResponse):
def __init__(self, message=None):
message = (
'You do not have permissions.' if message is None else message
)
super().__init__(status_code=403, message=message)
class NotFoundErrorResponse(GenericErrorResponse):
def __init__(self, message):
super().__init__(status_code=404, message=message, status='not found')
class UserNotFoundErrorResponse(NotFoundErrorResponse):
def __init__(self):
super().__init__(message='User does not exist.')
class DataNotFoundErrorResponse(HttpResponse):
def __init__(self, data_type, message=None):
response = {
'status': 'not found',
'data': {data_type: get_empty_data_for_datatype(data_type)},
}
if message:
response['message'] = message
super().__init__(response=response, status_code=404)
class PayloadTooLargeErrorResponse(GenericErrorResponse):
def __init__(self, message):
super().__init__(status_code=413, message=message, status='fail')
class InternalServerErrorResponse(GenericErrorResponse):
def __init__(self, message=None, status=None):
message = (
'Error. Please try again or contact the administrator.'
if message is None
else message
)
super().__init__(status_code=500, message=message, status=status)
def handle_error_and_return_response(
error, message=None, status=None, db=None
):
if db is not None:
db.session.rollback()
appLog.error(error)
return InternalServerErrorResponse(message=message, status=status)

View File

@ -833,7 +833,7 @@ class TestGetActivity:
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 404 assert response.status_code == 404
assert 'error' in data['status'] assert 'not found' in data['status']
assert ( assert (
f'No gpx file for this activity (id: {activity_short_id})' f'No gpx file for this activity (id: {activity_short_id})'
in data['message'] in data['message']
@ -860,7 +860,7 @@ class TestGetActivity:
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 404 assert response.status_code == 404
assert 'error' in data['status'] assert 'not found' in data['status']
assert ( assert (
f'No gpx file for this activity (id: {activity_short_id})' f'No gpx file for this activity (id: {activity_short_id})'
in data['message'] in data['message']
@ -888,7 +888,10 @@ class TestGetActivity:
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 500 assert response.status_code == 500
assert 'error' in data['status'] assert 'error' in data['status']
assert 'internal error' in data['message'] assert (
'Error. Please try again or contact the administrator.'
in data['message']
)
assert 'data' not in data assert 'data' not in data
def test_it_returns_500_on_getting_chart_data_if_an_activity_has_invalid_gpx_pathname( # noqa def test_it_returns_500_on_getting_chart_data_if_an_activity_has_invalid_gpx_pathname( # noqa
@ -913,7 +916,10 @@ class TestGetActivity:
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 500 assert response.status_code == 500
assert 'error' in data['status'] assert 'error' in data['status']
assert 'internal error' in data['message'] assert (
'Error. Please try again or contact the administrator.'
in data['message']
)
assert 'data' not in data assert 'data' not in data
def test_it_returns_404_if_activity_has_no_map(self, app, user_1): def test_it_returns_404_if_activity_has_no_map(self, app, user_1):
@ -933,5 +939,5 @@ class TestGetActivity:
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 404 assert response.status_code == 404
assert 'error' in data['status'] assert 'not found' in data['status']
assert 'Map does not exist' in data['message'] assert 'Map does not exist' in data['message']

View File

@ -843,7 +843,10 @@ class TestPostAndGetActivityWithGpx:
assert response.status_code == 500 assert response.status_code == 500
assert data['status'] == 'error' assert data['status'] == 'error'
assert data['message'] == 'internal error.' assert (
data['message']
== 'Error. Please try again or contact the administrator.'
)
def test_it_gets_an_activity_created_with_gpx( def test_it_gets_an_activity_created_with_gpx(
self, app, user_1, sport_1_cycling, gpx_file self, app, user_1, sport_1_cycling, gpx_file

View File

@ -162,7 +162,7 @@ class TestUserRegistration:
assert response.content_type == 'application/json' assert response.content_type == 'application/json'
assert response.status_code == 400 assert response.status_code == 400
def test_it_returns_error_if_paylaod_is_invalid(self, app): def test_it_returns_error_if_payload_is_invalid(self, app):
client = app.test_client() client = app.test_client()
response = client.post( response = client.post(
'/api/auth/register', '/api/auth/register',
@ -278,43 +278,49 @@ class TestUserRegistration:
class TestUserLogin: class TestUserLogin:
def test_user_can_register(self, app, user_1): def test_user_can_register(self, app, user_1):
client = app.test_client() client = app.test_client()
response = client.post( response = client.post(
'/api/auth/login', '/api/auth/login',
data=json.dumps(dict(email='test@test.com', password='12345678')), data=json.dumps(dict(email='test@test.com', password='12345678')),
content_type='application/json', content_type='application/json',
) )
assert response.content_type == 'application/json'
assert response.status_code == 200
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert data['status'] == 'success' assert data['status'] == 'success'
assert data['message'] == 'Successfully logged in.' assert data['message'] == 'Successfully logged in.'
assert data['auth_token'] assert data['auth_token']
assert response.content_type == 'application/json'
assert response.status_code == 200
def test_it_returns_error_if_user_does_not_exists(self, app): def test_it_returns_error_if_user_does_not_exists(self, app):
client = app.test_client() client = app.test_client()
response = client.post( response = client.post(
'/api/auth/login', '/api/auth/login',
data=json.dumps(dict(email='test@test.com', password='12345678')), data=json.dumps(dict(email='test@test.com', password='12345678')),
content_type='application/json', content_type='application/json',
) )
assert response.content_type == 'application/json'
assert response.status_code == 401
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert data['status'] == 'error' assert data['status'] == 'error'
assert data['message'] == 'Invalid credentials.' assert data['message'] == 'Invalid credentials.'
assert response.content_type == 'application/json'
assert response.status_code == 404
def test_it_returns_error_on_invalid_payload(self, app): def test_it_returns_error_on_invalid_payload(self, app):
client = app.test_client() client = app.test_client()
response = client.post( response = client.post(
'/api/auth/login', '/api/auth/login',
data=json.dumps(dict()), data=json.dumps(dict()),
content_type='application/json', content_type='application/json',
) )
assert response.content_type == 'application/json'
assert response.status_code == 400
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert data['status'] == 'error' assert data['status'] == 'error'
assert data['message'] == 'Invalid payload.' assert data['message'] == 'Invalid payload.'
assert response.content_type == 'application/json'
assert response.status_code == 400
def test_it_returns_error_if_password_is_invalid(self, app, user_1): def test_it_returns_error_if_password_is_invalid(self, app, user_1):
client = app.test_client() client = app.test_client()
@ -325,11 +331,11 @@ class TestUserLogin:
content_type='application/json', content_type='application/json',
) )
assert response.content_type == 'application/json'
assert response.status_code == 401
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert data['status'] == 'error' assert data['status'] == 'error'
assert data['message'] == 'Invalid credentials.' assert data['message'] == 'Invalid credentials.'
assert response.content_type == 'application/json'
assert response.status_code == 404
class TestUserLogout: class TestUserLogout:

View File

@ -111,7 +111,7 @@ class TestGetUser:
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 404 assert response.status_code == 404
assert 'fail' in data['status'] assert 'not found' in data['status']
assert 'User does not exist.' in data['message'] assert 'User does not exist.' in data['message']
@ -972,7 +972,7 @@ class TestGetUserPicture:
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 404 assert response.status_code == 404
assert 'fail' in data['status'] assert 'not found' in data['status']
assert 'User does not exist.' in data['message'] assert 'User does not exist.' in data['message']

View File

@ -3,8 +3,15 @@ import os
import jwt import jwt
from fittrackee import appLog, bcrypt, db from fittrackee import appLog, bcrypt, db
from fittrackee.responses import (
ForbiddenErrorResponse,
InvalidPayloadErrorResponse,
PayloadTooLargeErrorResponse,
UnauthorizedErrorResponse,
handle_error_and_return_response,
)
from fittrackee.tasks import reset_password_email from fittrackee.tasks import reset_password_email
from flask import Blueprint, current_app, jsonify, request from flask import Blueprint, current_app, request
from sqlalchemy import exc, or_ from sqlalchemy import exc, or_
from werkzeug.exceptions import RequestEntityTooLarge from werkzeug.exceptions import RequestEntityTooLarge
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
@ -84,11 +91,8 @@ def register_user():
""" """
if not current_app.config.get('is_registration_enabled'): if not current_app.config.get('is_registration_enabled'):
response_object = { return ForbiddenErrorResponse('Error. Registration is disabled.')
'status': 'error',
'message': 'Error. Registration is disabled.',
}
return jsonify(response_object), 403
# get post data # get post data
post_data = request.get_json() post_data = request.get_json()
if ( if (
@ -98,8 +102,7 @@ def register_user():
or post_data.get('password') is None or post_data.get('password') is None
or post_data.get('password_conf') is None or post_data.get('password_conf') is None
): ):
response_object = {'status': 'error', 'message': 'Invalid payload.'} return InvalidPayloadErrorResponse()
return jsonify(response_object), 400
username = post_data.get('username') username = post_data.get('username')
email = post_data.get('email') email = post_data.get('email')
password = post_data.get('password') password = post_data.get('password')
@ -108,24 +111,21 @@ def register_user():
try: try:
ret = register_controls(username, email, password, password_conf) ret = register_controls(username, email, password, password_conf)
except TypeError as e: except TypeError as e:
db.session.rollback() return handle_error_and_return_response(e, db=db)
appLog.error(e)
response_object = {
'status': 'error',
'message': 'Error. Please try again or contact the administrator.',
}
return jsonify(response_object), 500
if ret != '': if ret != '':
response_object = {'status': 'error', 'message': ret} return InvalidPayloadErrorResponse(ret)
return jsonify(response_object), 400
try: try:
# check for existing user # check for existing user
user = User.query.filter( user = User.query.filter(
or_(User.username == username, User.email == email) or_(User.username == username, User.email == email)
).first() ).first()
if not user: if user:
return InvalidPayloadErrorResponse(
'Sorry. That user already exists.'
)
# add new user to db # add new user to db
new_user = User(username=username, email=email, password=password) new_user = User(username=username, email=email, password=password)
new_user.timezone = 'Europe/Paris' new_user.timezone = 'Europe/Paris'
@ -133,28 +133,14 @@ def register_user():
db.session.commit() db.session.commit()
# generate auth token # generate auth token
auth_token = new_user.encode_auth_token(new_user.id) auth_token = new_user.encode_auth_token(new_user.id)
response_object = { return {
'status': 'success', 'status': 'success',
'message': 'Successfully registered.', 'message': 'Successfully registered.',
'auth_token': auth_token, 'auth_token': auth_token,
} }, 201
return jsonify(response_object), 201
else:
response_object = {
'status': 'error',
'message': 'Sorry. That user already exists.',
}
return jsonify(response_object), 400
# handler errors # handler errors
except (exc.IntegrityError, exc.OperationalError, ValueError) as e: except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
db.session.rollback() return handle_error_and_return_response(e, db=db)
appLog.error(e)
response_object = {
'status': 'error',
'message': 'Error. Please try again or contact the administrator.',
}
return jsonify(response_object), 500
@auth_blueprint.route('/auth/login', methods=['POST']) @auth_blueprint.route('/auth/login', methods=['POST'])
@ -200,15 +186,15 @@ def login_user():
:<json string password_conf: password confirmation :<json string password_conf: password confirmation
:statuscode 200: Successfully logged in. :statuscode 200: Successfully logged in.
:statuscode 404: Invalid credentials. :statuscode 400: Invalid payload.
:statuscode 401: Invalid credentials.
:statuscode 500: Error. Please try again or contact the administrator. :statuscode 500: Error. Please try again or contact the administrator.
""" """
# get post data # get post data
post_data = request.get_json() post_data = request.get_json()
if not post_data: if not post_data:
response_object = {'status': 'error', 'message': 'Invalid payload.'} return InvalidPayloadErrorResponse()
return jsonify(response_object), 400
email = post_data.get('email') email = post_data.get('email')
password = post_data.get('password') password = post_data.get('password')
try: try:
@ -217,27 +203,15 @@ def login_user():
if user and bcrypt.check_password_hash(user.password, password): if user and bcrypt.check_password_hash(user.password, password):
# generate auth token # generate auth token
auth_token = user.encode_auth_token(user.id) auth_token = user.encode_auth_token(user.id)
response_object = { return {
'status': 'success', 'status': 'success',
'message': 'Successfully logged in.', 'message': 'Successfully logged in.',
'auth_token': auth_token, 'auth_token': auth_token,
} }
return jsonify(response_object), 200 return UnauthorizedErrorResponse('Invalid credentials.')
else:
response_object = {
'status': 'error',
'message': 'Invalid credentials.',
}
return jsonify(response_object), 404
# handler errors # handler errors
except (exc.IntegrityError, exc.OperationalError, ValueError) as e: except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
db.session.rollback() return handle_error_and_return_response(e, db=db)
appLog.error(e)
response_object = {
'status': 'error',
'message': 'Error. Please try again or contact the administrator.',
}
return jsonify(response_object), 500
@auth_blueprint.route('/auth/logout', methods=['GET']) @auth_blueprint.route('/auth/logout', methods=['GET'])
@ -287,24 +261,18 @@ def logout_user(auth_user_id):
""" """
# get auth token # get auth token
auth_header = request.headers.get('Authorization') auth_header = request.headers.get('Authorization')
if auth_header: if not auth_header:
auth_token = auth_header.split(" ")[1] return UnauthorizedErrorResponse('Provide a valid auth token.')
auth_token = auth_header.split(' ')[1]
resp = User.decode_auth_token(auth_token) resp = User.decode_auth_token(auth_token)
if not isinstance(auth_user_id, str): if isinstance(auth_user_id, str):
response_object = { return UnauthorizedErrorResponse(resp)
return {
'status': 'success', 'status': 'success',
'message': 'Successfully logged out.', 'message': 'Successfully logged out.',
} }
return jsonify(response_object), 200
else:
response_object = {'status': 'error', 'message': resp}
return jsonify(response_object), 401
else:
response_object = {
'status': 'error',
'message': 'Provide a valid auth token.',
}
return jsonify(response_object), 401
@auth_blueprint.route('/auth/profile', methods=['GET']) @auth_blueprint.route('/auth/profile', methods=['GET'])
@ -365,8 +333,7 @@ def get_authenticated_user_profile(auth_user_id):
""" """
user = User.query.filter_by(id=auth_user_id).first() user = User.query.filter_by(id=auth_user_id).first()
response_object = {'status': 'success', 'data': user.serialize()} return {'status': 'success', 'data': user.serialize()}
return jsonify(response_object), 200
@auth_blueprint.route('/auth/profile/edit', methods=['POST']) @auth_blueprint.route('/auth/profile/edit', methods=['POST'])
@ -455,8 +422,8 @@ def edit_user(auth_user_id):
'weekm', 'weekm',
} }
if not post_data or not post_data.keys() >= user_mandatory_data: if not post_data or not post_data.keys() >= user_mandatory_data:
response_object = {'status': 'error', 'message': 'Invalid payload.'} return InvalidPayloadErrorResponse()
return jsonify(response_object), 400
first_name = post_data.get('first_name') first_name = post_data.get('first_name')
last_name = post_data.get('last_name') last_name = post_data.get('last_name')
bio = post_data.get('bio') bio = post_data.get('bio')
@ -471,8 +438,7 @@ def edit_user(auth_user_id):
if password is not None and password != '': if password is not None and password != '':
message = check_passwords(password, password_conf) message = check_passwords(password, password_conf)
if message != '': if message != '':
response_object = {'status': 'error', 'message': message} return InvalidPayloadErrorResponse(message)
return jsonify(response_object), 400
password = bcrypt.generate_password_hash( password = bcrypt.generate_password_hash(
password, current_app.config.get('BCRYPT_LOG_ROUNDS') password, current_app.config.get('BCRYPT_LOG_ROUNDS')
).decode() ).decode()
@ -495,22 +461,15 @@ def edit_user(auth_user_id):
user.weekm = weekm user.weekm = weekm
db.session.commit() db.session.commit()
response_object = { return {
'status': 'success', 'status': 'success',
'message': 'User profile updated.', 'message': 'User profile updated.',
'data': user.serialize(), 'data': user.serialize(),
} }
return jsonify(response_object), 200
# handler errors # handler errors
except (exc.IntegrityError, exc.OperationalError, ValueError) as e: except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
db.session.rollback() return handle_error_and_return_response(e, db=db)
appLog.error(e)
response_object = {
'status': 'error',
'message': 'Error. Please try again or contact the administrator.',
}
return jsonify(response_object), 500
@auth_blueprint.route('/auth/picture', methods=['POST']) @auth_blueprint.route('/auth/picture', methods=['POST'])
@ -557,20 +516,16 @@ def edit_picture(auth_user_id):
""" """
try: try:
response_object, response_code = verify_extension_and_size( response_object = verify_extension_and_size('picture', request)
'picture', request
)
except RequestEntityTooLarge as e: except RequestEntityTooLarge as e:
appLog.error(e) appLog.error(e)
max_file_size = current_app.config['MAX_CONTENT_LENGTH'] max_file_size = current_app.config['MAX_CONTENT_LENGTH']
response_object = { return PayloadTooLargeErrorResponse(
'status': 'fail', 'Error during picture update, file size exceeds '
'message': 'Error during picture update, file size exceeds ' f'{display_readable_file_size(max_file_size)}.'
f'{display_readable_file_size(max_file_size)}.', )
} if response_object:
return jsonify(response_object), 413 return response_object
if response_object['status'] != 'success':
return jsonify(response_object), response_code
file = request.files['file'] file = request.files['file']
filename = secure_filename(file.filename) filename = secure_filename(file.filename)
@ -593,21 +548,15 @@ def edit_picture(auth_user_id):
file.save(absolute_picture_path) file.save(absolute_picture_path)
user.picture = relative_picture_path user.picture = relative_picture_path
db.session.commit() db.session.commit()
return {
response_object = {
'status': 'success', 'status': 'success',
'message': 'User picture updated.', 'message': 'User picture updated.',
} }
return jsonify(response_object), 200
except (exc.IntegrityError, ValueError) as e: except (exc.IntegrityError, ValueError) as e:
db.session.rollback() return handle_error_and_return_response(
appLog.error(e) e, message='Error during picture update.', status='fail', db=db
response_object = { )
'status': 'fail',
'message': 'Error during picture update.',
}
return jsonify(response_object), 500
@auth_blueprint.route('/auth/picture', methods=['DELETE']) @auth_blueprint.route('/auth/picture', methods=['DELETE'])
@ -647,18 +596,11 @@ def del_picture(auth_user_id):
os.remove(picture_path) os.remove(picture_path)
user.picture = None user.picture = None
db.session.commit() db.session.commit()
return {'status': 'no content'}, 204
response_object = {'status': 'no content'}
return jsonify(response_object), 204
except (exc.IntegrityError, ValueError) as e: except (exc.IntegrityError, ValueError) as e:
db.session.rollback() return handle_error_and_return_response(
appLog.error(e) e, message='Error during picture deletion.', status='fail', db=db
response_object = { )
'status': 'fail',
'message': 'Error during picture deletion.',
}
return jsonify(response_object), 500
@auth_blueprint.route('/auth/password/reset-request', methods=['POST']) @auth_blueprint.route('/auth/password/reset-request', methods=['POST'])
@ -693,8 +635,7 @@ def request_password_reset():
""" """
post_data = request.get_json() post_data = request.get_json()
if not post_data or post_data.get('email') is None: if not post_data or post_data.get('email') is None:
response_object = {'status': 'error', 'message': 'Invalid payload.'} return InvalidPayloadErrorResponse()
return jsonify(response_object), 400
email = post_data.get('email') email = post_data.get('email')
user = User.query.filter(User.email == email).first() user = User.query.filter(User.email == email).first()
@ -718,11 +659,10 @@ def request_password_reset():
'email': user.email, 'email': user.email,
} }
reset_password_email.send(user_data, email_data) reset_password_email.send(user_data, email_data)
response_object = { return {
'status': 'success', 'status': 'success',
'message': 'Password reset request processed.', 'message': 'Password reset request processed.',
} }
return jsonify(response_object), 200
@auth_blueprint.route('/auth/password/update', methods=['POST']) @auth_blueprint.route('/auth/password/update', methods=['POST'])
@ -766,45 +706,31 @@ def update_password():
or post_data.get('password_conf') is None or post_data.get('password_conf') is None
or post_data.get('token') is None or post_data.get('token') is None
): ):
response_object = {'status': 'error', 'message': 'Invalid payload.'} return InvalidPayloadErrorResponse()
return jsonify(response_object), 400
password = post_data.get('password') password = post_data.get('password')
password_conf = post_data.get('password_conf') password_conf = post_data.get('password_conf')
token = post_data.get('token') token = post_data.get('token')
invalid_token_response_object = {
'status': 'error',
'message': 'Invalid token. Please request a new token.',
}
try: try:
user_id = decode_user_token(token) user_id = decode_user_token(token)
except (jwt.ExpiredSignatureError, jwt.InvalidTokenError): except (jwt.ExpiredSignatureError, jwt.InvalidTokenError):
return jsonify(invalid_token_response_object), 401 return UnauthorizedErrorResponse()
message = check_passwords(password, password_conf) message = check_passwords(password, password_conf)
if message != '': if message != '':
response_object = {'status': 'error', 'message': message} return InvalidPayloadErrorResponse(message)
return jsonify(response_object), 400
user = User.query.filter(User.id == user_id).first() user = User.query.filter(User.id == user_id).first()
if not user: if not user:
return jsonify(invalid_token_response_object), 401 return UnauthorizedErrorResponse()
try: try:
user.password = bcrypt.generate_password_hash( user.password = bcrypt.generate_password_hash(
password, current_app.config.get('BCRYPT_LOG_ROUNDS') password, current_app.config.get('BCRYPT_LOG_ROUNDS')
).decode() ).decode()
db.session.commit() db.session.commit()
response_object = { return {
'status': 'success', 'status': 'success',
'message': 'Password updated.', 'message': 'Password updated.',
} }
return jsonify(response_object), 200
except (exc.OperationalError, ValueError) as e: except (exc.OperationalError, ValueError) as e:
db.session.rollback() return handle_error_and_return_response(e, db=db)
appLog.error(e)
response_object = {
'status': 'error',
'message': 'Error. Please try again or contact the administrator.',
}
return jsonify(response_object), 500

View File

@ -1,8 +1,15 @@
import os import os
import shutil import shutil
from fittrackee import appLog, db from fittrackee import db
from flask import Blueprint, jsonify, request, send_file from fittrackee.responses import (
ForbiddenErrorResponse,
InvalidPayloadErrorResponse,
NotFoundErrorResponse,
UserNotFoundErrorResponse,
handle_error_and_return_response,
)
from flask import Blueprint, request, send_file
from sqlalchemy import exc from sqlalchemy import exc
from ..activities.utils_files import get_absolute_file_path from ..activities.utils_files import get_absolute_file_path
@ -156,7 +163,7 @@ def get_users(auth_user_id):
.paginate(page, per_page, False) .paginate(page, per_page, False)
) )
users = users_pagination.items users = users_pagination.items
response_object = { return {
'status': 'success', 'status': 'success',
'data': {'users': [user.serialize() for user in users]}, 'data': {'users': [user.serialize() for user in users]},
'pagination': { 'pagination': {
@ -167,7 +174,6 @@ def get_users(auth_user_id):
'total': users_pagination.total, 'total': users_pagination.total,
}, },
} }
return jsonify(response_object), 200
@users_blueprint.route('/users/<user_name>', methods=['GET']) @users_blueprint.route('/users/<user_name>', methods=['GET'])
@ -232,20 +238,16 @@ def get_single_user(auth_user_id, user_name):
:statuscode 404: :statuscode 404:
- User does not exist. - User does not exist.
""" """
response_object = {'status': 'fail', 'message': 'User does not exist.'}
try: try:
user = User.query.filter_by(username=user_name).first() user = User.query.filter_by(username=user_name).first()
if not user: if user:
return jsonify(response_object), 404 return {
else:
response_object = {
'status': 'success', 'status': 'success',
'data': {'users': [user.serialize()]}, 'data': {'users': [user.serialize()]},
} }
return jsonify(response_object), 200
except ValueError: except ValueError:
return jsonify(response_object), 404 pass
return UserNotFoundErrorResponse()
@users_blueprint.route('/users/<user_name>/picture', methods=['GET']) @users_blueprint.route('/users/<user_name>/picture', methods=['GET'])
@ -274,21 +276,16 @@ def get_picture(user_name):
- No picture. - No picture.
""" """
response_object = {'status': 'not found', 'message': 'No picture.'}
try: try:
user = User.query.filter_by(username=user_name).first() user = User.query.filter_by(username=user_name).first()
if not user: if not user:
response_object = { return UserNotFoundErrorResponse()
'status': 'fail',
'message': 'User does not exist.',
}
return jsonify(response_object), 404
if user.picture is not None: if user.picture is not None:
picture_path = get_absolute_file_path(user.picture) picture_path = get_absolute_file_path(user.picture)
return send_file(picture_path) return send_file(picture_path)
return jsonify(response_object), 404
except Exception: except Exception:
return jsonify(response_object), 404 pass
return NotFoundErrorResponse('No picture.')
@users_blueprint.route('/users/<user_name>', methods=['PATCH']) @users_blueprint.route('/users/<user_name>', methods=['PATCH'])
@ -359,34 +356,23 @@ def update_user(auth_user_id, user_name):
- User does not exist. - User does not exist.
:statuscode 500: :statuscode 500:
""" """
response_object = {'status': 'fail', 'message': 'User does not exist.'}
user_data = request.get_json() user_data = request.get_json()
if 'admin' not in user_data: if 'admin' not in user_data:
response_object = {'status': 'error', 'message': 'Invalid payload.'} return InvalidPayloadErrorResponse()
return jsonify(response_object), 400
try: try:
user = User.query.filter_by(username=user_name).first() user = User.query.filter_by(username=user_name).first()
if not user: if not user:
return jsonify(response_object), 404 return UserNotFoundErrorResponse()
else:
user.admin = user_data['admin'] user.admin = user_data['admin']
db.session.commit() db.session.commit()
response_object = { return {
'status': 'success', 'status': 'success',
'data': {'users': [user.serialize()]}, 'data': {'users': [user.serialize()]},
} }
return jsonify(response_object), 200
except exc.StatementError as e: except exc.StatementError as e:
db.session.rollback() return handle_error_and_return_response(e, db=db)
appLog.error(e)
response_object = {
'status': 'error',
'message': 'Error. Please try again or contact the administrator.',
}
code = 500
return jsonify(response_object), code
@users_blueprint.route('/users/<user_name>', methods=['DELETE']) @users_blueprint.route('/users/<user_name>', methods=['DELETE'])
@ -435,25 +421,20 @@ def delete_user(auth_user_id, user_name):
try: try:
auth_user = User.query.filter_by(id=auth_user_id).first() auth_user = User.query.filter_by(id=auth_user_id).first()
user = User.query.filter_by(username=user_name).first() user = User.query.filter_by(username=user_name).first()
if user: if not user:
return UserNotFoundErrorResponse()
if user.id != auth_user_id and not auth_user.admin: if user.id != auth_user_id and not auth_user.admin:
response_object = { return ForbiddenErrorResponse()
'status': 'error',
'message': 'You do not have permissions.',
}
return response_object, 403
if ( if (
user.admin is True user.admin is True
and User.query.filter_by(admin=True).count() == 1 and User.query.filter_by(admin=True).count() == 1
): ):
response_object = { return ForbiddenErrorResponse(
'status': 'error',
'message': (
'You can not delete your account, ' 'You can not delete your account, '
'no other user has admin rights.' 'no other user has admin rights.'
), )
}
return response_object, 403
for activity in Activity.query.filter_by(user_id=user.id).all(): for activity in Activity.query.filter_by(user_id=user.id).all():
db.session.delete(activity) db.session.delete(activity)
db.session.flush() db.session.flush()
@ -472,25 +453,11 @@ def delete_user(auth_user_id, user_name):
get_absolute_file_path(f'pictures/{user.id}'), get_absolute_file_path(f'pictures/{user.id}'),
ignore_errors=True, ignore_errors=True,
) )
response_object = {'status': 'no content'} return {'status': 'no content'}, 204
code = 204
else:
response_object = {
'status': 'not found',
'message': 'User does not exist.',
}
code = 404
except ( except (
exc.IntegrityError, exc.IntegrityError,
exc.OperationalError, exc.OperationalError,
ValueError, ValueError,
OSError, OSError,
) as e: ) as e:
db.session.rollback() return handle_error_and_return_response(e, db=db)
appLog.error(e)
response_object = {
'status': 'error',
'message': 'Error. Please try again or contact the administrator.',
}
code = 500
return jsonify(response_object), code

View File

@ -3,7 +3,13 @@ from datetime import timedelta
from functools import wraps from functools import wraps
import humanize import humanize
from flask import current_app, jsonify, request from fittrackee.responses import (
ForbiddenErrorResponse,
InvalidPayloadErrorResponse,
PayloadTooLargeErrorResponse,
UnauthorizedErrorResponse,
)
from flask import current_app, request
from .models import User from .models import User
@ -38,17 +44,12 @@ def register_controls(username, email, password, password_conf):
def verify_extension_and_size(file_type, req): def verify_extension_and_size(file_type, req):
response_object = {'status': 'success'}
code = 400
if 'file' not in req.files: if 'file' not in req.files:
response_object = {'status': 'fail', 'message': 'No file part.'} return InvalidPayloadErrorResponse('No file part.', 'fail')
return response_object, code
file = req.files['file'] file = req.files['file']
if file.filename == '': if file.filename == '':
response_object = {'status': 'fail', 'message': 'No selected file.'} return InvalidPayloadErrorResponse('No selected file.', 'fail')
return response_object, code
allowed_extensions = ( allowed_extensions = (
'ACTIVITY_ALLOWED_EXTENSIONS' 'ACTIVITY_ALLOWED_EXTENSIONS'
@ -67,52 +68,43 @@ def verify_extension_and_size(file_type, req):
file_extension file_extension
and file_extension in current_app.config.get(allowed_extensions) and file_extension in current_app.config.get(allowed_extensions)
): ):
response_object = { return InvalidPayloadErrorResponse(
'status': 'fail', 'File extension not allowed.', 'fail'
'message': 'File extension not allowed.', )
}
elif file_extension != 'zip' and req.content_length > max_file_size:
response_object = {
'status': 'fail',
'message': 'Error during picture update, file size exceeds '
f'{display_readable_file_size(max_file_size)}.',
}
code = 413
return response_object, code if file_extension != 'zip' and req.content_length > max_file_size:
return PayloadTooLargeErrorResponse(
'Error during picture update, file size exceeds '
f'{display_readable_file_size(max_file_size)}.'
)
return None
def verify_user(current_request, verify_admin): def verify_user(current_request, verify_admin):
response_object = { default_message = 'Provide a valid auth token.'
'status': 'error',
'message': 'Something went wrong. Please contact us.',
}
code = 401
auth_header = current_request.headers.get('Authorization') auth_header = current_request.headers.get('Authorization')
if not auth_header: if not auth_header:
response_object['message'] = 'Provide a valid auth token.' return UnauthorizedErrorResponse(default_message), None
return response_object, code, None auth_token = auth_header.split(' ')[1]
auth_token = auth_header.split(" ")[1]
resp = User.decode_auth_token(auth_token) resp = User.decode_auth_token(auth_token)
if isinstance(resp, str): if isinstance(resp, str):
response_object['message'] = resp return UnauthorizedErrorResponse(resp), None
return response_object, code, None
user = User.query.filter_by(id=resp).first() user = User.query.filter_by(id=resp).first()
if not user: if not user:
return response_object, code, None return UnauthorizedErrorResponse(default_message), None
if verify_admin and not is_admin(resp): if verify_admin and not is_admin(resp):
response_object['message'] = 'You do not have permissions.' return ForbiddenErrorResponse(), None
return response_object, 403, None return None, resp
return None, None, resp
def authenticate(f): def authenticate(f):
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
verify_admin = False verify_admin = False
response_object, code, resp = verify_user(request, verify_admin) response_object, resp = verify_user(request, verify_admin)
if response_object: if response_object:
return jsonify(response_object), code return response_object
return f(resp, *args, **kwargs) return f(resp, *args, **kwargs)
return decorated_function return decorated_function
@ -122,9 +114,9 @@ def authenticate_as_admin(f):
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
verify_admin = True verify_admin = True
response_object, code, resp = verify_user(request, verify_admin) response_object, resp = verify_user(request, verify_admin)
if response_object: if response_object:
return jsonify(response_object), code return response_object
return f(resp, *args, **kwargs) return f(resp, *args, **kwargs)
return decorated_function return decorated_function
@ -132,12 +124,8 @@ def authenticate_as_admin(f):
def can_view_activity(auth_user_id, activity_user_id): def can_view_activity(auth_user_id, activity_user_id):
if auth_user_id != activity_user_id: if auth_user_id != activity_user_id:
response_object = { return ForbiddenErrorResponse()
'status': 'error', return None
'message': 'You do not have permissions.',
}
return response_object, 403
return None, None
def display_readable_file_size(size_in_bytes): def display_readable_file_size(size_in_bytes):