API - refactor api responses
This commit is contained in:
parent
21e79c8883
commit
4705393a08
@ -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
@ -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,59 +380,44 @@ 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
|
|
||||||
if not activity.gpx or activity.gpx == '':
|
|
||||||
message = (
|
|
||||||
f'No gpx file for this activity (id: {activity_short_id})'
|
|
||||||
)
|
|
||||||
response_object = {'status': 'error', 'message': message}
|
|
||||||
return jsonify(response_object), 404
|
|
||||||
|
|
||||||
try:
|
error_response = can_view_activity(auth_user_id, activity.user_id)
|
||||||
absolute_gpx_filepath = get_absolute_file_path(activity.gpx)
|
if error_response:
|
||||||
if data_type == 'chart':
|
return error_response
|
||||||
content = get_chart_data(absolute_gpx_filepath, segment_id)
|
if not activity.gpx or activity.gpx == '':
|
||||||
else: # data_type == 'gpx'
|
return NotFoundErrorResponse(
|
||||||
with open(absolute_gpx_filepath, encoding='utf-8') as f:
|
f'No gpx file for this activity (id: {activity_short_id})'
|
||||||
content = f.read()
|
)
|
||||||
if segment_id is not None:
|
|
||||||
content = extract_segment_from_gpx_file(
|
|
||||||
content, segment_id
|
|
||||||
)
|
|
||||||
except ActivityGPXException as e:
|
|
||||||
appLog.error(e.message)
|
|
||||||
response_object = {'status': e.status, 'message': e.message}
|
|
||||||
code = 404 if e.status == 'not found' else 500
|
|
||||||
return jsonify(response_object), code
|
|
||||||
except Exception as e:
|
|
||||||
appLog.error(e)
|
|
||||||
response_object = {'status': 'error', 'message': 'internal error'}
|
|
||||||
return jsonify(response_object), 500
|
|
||||||
|
|
||||||
status = 'success'
|
try:
|
||||||
message = ''
|
absolute_gpx_filepath = get_absolute_file_path(activity.gpx)
|
||||||
code = 200
|
if data_type == 'chart_data':
|
||||||
else:
|
content = get_chart_data(absolute_gpx_filepath, segment_id)
|
||||||
status = 'not found'
|
else: # data_type == 'gpx'
|
||||||
message = f'Activity not found (id: {activity_short_id})'
|
with open(absolute_gpx_filepath, encoding='utf-8') as f:
|
||||||
code = 404
|
content = f.read()
|
||||||
|
if segment_id is not None:
|
||||||
|
content = extract_segment_from_gpx_file(
|
||||||
|
content, segment_id
|
||||||
|
)
|
||||||
|
except ActivityGPXException as e:
|
||||||
|
appLog.error(e.message)
|
||||||
|
if e.status == 'not found':
|
||||||
|
return NotFoundErrorResponse(e.message)
|
||||||
|
return InternalServerErrorResponse(e.message)
|
||||||
|
except Exception as e:
|
||||||
|
return handle_error_and_return_response(e)
|
||||||
|
|
||||||
response_object = {
|
return {
|
||||||
'status': status,
|
'status': 'success',
|
||||||
'message': message,
|
'message': '',
|
||||||
'data': (
|
'data': ({data_type: content}),
|
||||||
{'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',
|
absolute_map_filepath = get_absolute_file_path(activity.map)
|
||||||
'message': 'Map does not exist',
|
return send_file(absolute_map_filepath)
|
||||||
}
|
|
||||||
return jsonify(response_object), 404
|
|
||||||
else:
|
|
||||||
absolute_map_filepath = get_absolute_file_path(activity.map)
|
|
||||||
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',
|
{
|
||||||
'data': {'activities': [new_activity.serialize()]},
|
'status': 'created',
|
||||||
}
|
'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
|
|
||||||
)
|
|
||||||
if response_object:
|
|
||||||
return jsonify(response_object), code
|
|
||||||
|
|
||||||
activity = edit_activity(activity, activity_data, auth_user_id)
|
response_object = can_view_activity(auth_user_id, activity.user_id)
|
||||||
db.session.commit()
|
if response_object:
|
||||||
response_object = {
|
return response_object
|
||||||
'status': 'success',
|
|
||||||
'data': {'activities': [activity.serialize()]},
|
activity = edit_activity(activity, activity_data, auth_user_id)
|
||||||
}
|
db.session.commit()
|
||||||
code = 200
|
return {
|
||||||
else:
|
'status': 'success',
|
||||||
response_object = {
|
'data': {'activities': [activity.serialize()]},
|
||||||
'status': 'not found',
|
|
||||||
'data': {'activities': []},
|
|
||||||
}
|
|
||||||
code = 404
|
|
||||||
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
|
||||||
db.session.rollback()
|
|
||||||
appLog.error(e)
|
|
||||||
response_object = {
|
|
||||||
'status': 'error',
|
|
||||||
'message': 'Error. Please try again or contact the administrator.',
|
|
||||||
}
|
}
|
||||||
code = 500
|
|
||||||
return jsonify(response_object), code
|
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
||||||
|
return handle_error_and_return_response(e)
|
||||||
|
|
||||||
|
|
||||||
@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
|
|
||||||
|
@ -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
|
|
||||||
|
@ -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:
|
||||||
sport.is_active = sport_data.get('is_active')
|
return DataNotFoundErrorResponse('sports')
|
||||||
db.session.commit()
|
|
||||||
response_object = {
|
sport.is_active = sport_data.get('is_active')
|
||||||
'status': 'success',
|
db.session.commit()
|
||||||
'data': {'sports': [sport.serialize(True)]},
|
return {
|
||||||
}
|
'status': 'success',
|
||||||
code = 200
|
'data': {'sports': [sport.serialize(True)]},
|
||||||
else:
|
|
||||||
response_object = {'status': 'not found', 'data': {'sports': []}}
|
|
||||||
code = 404
|
|
||||||
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
|
||||||
db.session.rollback()
|
|
||||||
appLog.error(e)
|
|
||||||
response_object = {
|
|
||||||
'status': 'error',
|
|
||||||
'message': 'Error. Please try again or contact the administrator.',
|
|
||||||
}
|
}
|
||||||
code = 500
|
|
||||||
return jsonify(response_object), code
|
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
||||||
|
return handle_error_and_return_response(e, db=db)
|
||||||
|
@ -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
|
|
||||||
|
@ -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
117
fittrackee/responses.py
Normal 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)
|
@ -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']
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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']
|
||||||
|
|
||||||
|
|
||||||
|
@ -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,53 +111,36 @@ 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:
|
||||||
# add new user to db
|
return InvalidPayloadErrorResponse(
|
||||||
new_user = User(username=username, email=email, password=password)
|
'Sorry. That user already exists.'
|
||||||
new_user.timezone = 'Europe/Paris'
|
)
|
||||||
db.session.add(new_user)
|
|
||||||
db.session.commit()
|
# add new user to db
|
||||||
# generate auth token
|
new_user = User(username=username, email=email, password=password)
|
||||||
auth_token = new_user.encode_auth_token(new_user.id)
|
new_user.timezone = 'Europe/Paris'
|
||||||
response_object = {
|
db.session.add(new_user)
|
||||||
'status': 'success',
|
db.session.commit()
|
||||||
'message': 'Successfully registered.',
|
# generate auth token
|
||||||
'auth_token': auth_token,
|
auth_token = new_user.encode_auth_token(new_user.id)
|
||||||
}
|
return {
|
||||||
return jsonify(response_object), 201
|
'status': 'success',
|
||||||
else:
|
'message': 'Successfully registered.',
|
||||||
response_object = {
|
'auth_token': auth_token,
|
||||||
'status': 'error',
|
}, 201
|
||||||
'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.')
|
||||||
resp = User.decode_auth_token(auth_token)
|
|
||||||
if not isinstance(auth_user_id, str):
|
auth_token = auth_header.split(' ')[1]
|
||||||
response_object = {
|
resp = User.decode_auth_token(auth_token)
|
||||||
'status': 'success',
|
if isinstance(auth_user_id, str):
|
||||||
'message': 'Successfully logged out.',
|
return UnauthorizedErrorResponse(resp)
|
||||||
}
|
|
||||||
return jsonify(response_object), 200
|
return {
|
||||||
else:
|
'status': 'success',
|
||||||
response_object = {'status': 'error', 'message': resp}
|
'message': 'Successfully logged out.',
|
||||||
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
|
|
||||||
|
@ -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']
|
|
||||||
db.session.commit()
|
|
||||||
response_object = {
|
|
||||||
'status': 'success',
|
|
||||||
'data': {'users': [user.serialize()]},
|
|
||||||
}
|
|
||||||
return jsonify(response_object), 200
|
|
||||||
|
|
||||||
except exc.StatementError as e:
|
user.admin = user_data['admin']
|
||||||
db.session.rollback()
|
db.session.commit()
|
||||||
appLog.error(e)
|
return {
|
||||||
response_object = {
|
'status': 'success',
|
||||||
'status': 'error',
|
'data': {'users': [user.serialize()]},
|
||||||
'message': 'Error. Please try again or contact the administrator.',
|
|
||||||
}
|
}
|
||||||
code = 500
|
except exc.StatementError as e:
|
||||||
return jsonify(response_object), code
|
return handle_error_and_return_response(e, db=db)
|
||||||
|
|
||||||
|
|
||||||
@users_blueprint.route('/users/<user_name>', methods=['DELETE'])
|
@users_blueprint.route('/users/<user_name>', methods=['DELETE'])
|
||||||
@ -435,62 +421,43 @@ 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:
|
||||||
if user.id != auth_user_id and not auth_user.admin:
|
return UserNotFoundErrorResponse()
|
||||||
response_object = {
|
|
||||||
'status': 'error',
|
if user.id != auth_user_id and not auth_user.admin:
|
||||||
'message': 'You do not have permissions.',
|
return ForbiddenErrorResponse()
|
||||||
}
|
if (
|
||||||
return response_object, 403
|
user.admin is True
|
||||||
if (
|
and User.query.filter_by(admin=True).count() == 1
|
||||||
user.admin is True
|
):
|
||||||
and User.query.filter_by(admin=True).count() == 1
|
return ForbiddenErrorResponse(
|
||||||
):
|
'You can not delete your account, '
|
||||||
response_object = {
|
'no other user has admin rights.'
|
||||||
'status': 'error',
|
|
||||||
'message': (
|
|
||||||
'You can not delete your account, '
|
|
||||||
'no other user has admin rights.'
|
|
||||||
),
|
|
||||||
}
|
|
||||||
return response_object, 403
|
|
||||||
for activity in Activity.query.filter_by(user_id=user.id).all():
|
|
||||||
db.session.delete(activity)
|
|
||||||
db.session.flush()
|
|
||||||
user_picture = user.picture
|
|
||||||
db.session.delete(user)
|
|
||||||
db.session.commit()
|
|
||||||
if user_picture:
|
|
||||||
picture_path = get_absolute_file_path(user.picture)
|
|
||||||
if os.path.isfile(picture_path):
|
|
||||||
os.remove(picture_path)
|
|
||||||
shutil.rmtree(
|
|
||||||
get_absolute_file_path(f'activities/{user.id}'),
|
|
||||||
ignore_errors=True,
|
|
||||||
)
|
)
|
||||||
shutil.rmtree(
|
|
||||||
get_absolute_file_path(f'pictures/{user.id}'),
|
for activity in Activity.query.filter_by(user_id=user.id).all():
|
||||||
ignore_errors=True,
|
db.session.delete(activity)
|
||||||
)
|
db.session.flush()
|
||||||
response_object = {'status': 'no content'}
|
user_picture = user.picture
|
||||||
code = 204
|
db.session.delete(user)
|
||||||
else:
|
db.session.commit()
|
||||||
response_object = {
|
if user_picture:
|
||||||
'status': 'not found',
|
picture_path = get_absolute_file_path(user.picture)
|
||||||
'message': 'User does not exist.',
|
if os.path.isfile(picture_path):
|
||||||
}
|
os.remove(picture_path)
|
||||||
code = 404
|
shutil.rmtree(
|
||||||
|
get_absolute_file_path(f'activities/{user.id}'),
|
||||||
|
ignore_errors=True,
|
||||||
|
)
|
||||||
|
shutil.rmtree(
|
||||||
|
get_absolute_file_path(f'pictures/{user.id}'),
|
||||||
|
ignore_errors=True,
|
||||||
|
)
|
||||||
|
return {'status': 'no content'}, 204
|
||||||
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
|
|
||||||
|
@ -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):
|
||||||
|
Loading…
Reference in New Issue
Block a user