2018-05-01 17:51:38 +02:00
|
|
|
import json
|
2018-05-09 18:54:30 +02:00
|
|
|
import os
|
2018-05-29 19:06:33 +02:00
|
|
|
import shutil
|
2018-01-21 17:43:13 +01:00
|
|
|
|
2018-05-29 19:06:33 +02:00
|
|
|
from flask import Blueprint, current_app, jsonify, request
|
2018-05-01 17:51:38 +02:00
|
|
|
from mpwo_api import appLog, db
|
|
|
|
from sqlalchemy import exc
|
|
|
|
|
|
|
|
from ..users.utils import authenticate, verify_extension
|
2018-05-29 12:53:13 +02:00
|
|
|
from .models import Activity
|
2018-05-09 18:23:17 +02:00
|
|
|
from .utils import (
|
2018-05-29 12:53:13 +02:00
|
|
|
ActivityException, create_activity, edit_activity, get_chart_data,
|
2018-05-29 16:28:59 +02:00
|
|
|
process_files
|
2018-05-09 18:23:17 +02:00
|
|
|
)
|
2018-01-21 17:43:13 +01:00
|
|
|
|
|
|
|
activities_blueprint = Blueprint('activities', __name__)
|
|
|
|
|
|
|
|
|
|
|
|
@activities_blueprint.route('/activities', methods=['GET'])
|
|
|
|
@authenticate
|
2018-01-21 19:45:13 +01:00
|
|
|
def get_activities(auth_user_id):
|
2018-05-08 14:44:51 +02:00
|
|
|
"""Get all activities for authenticated user"""
|
2018-05-10 23:39:59 +02:00
|
|
|
try:
|
|
|
|
params = request.args.copy()
|
|
|
|
page = 1 if len(params) == 0 else int(params.pop('page'))
|
|
|
|
activities = Activity.query.filter_by(user_id=auth_user_id)\
|
|
|
|
.order_by(Activity.activity_date.desc()).paginate(
|
|
|
|
page, 5, False).items
|
|
|
|
response_object = {
|
|
|
|
'status': 'success',
|
|
|
|
'data': {
|
2018-05-20 13:12:35 +02:00
|
|
|
'activities': [activity.serialize() for activity in activities]
|
2018-05-10 23:39:59 +02:00
|
|
|
}
|
2018-01-21 17:43:13 +01:00
|
|
|
}
|
2018-05-10 23:39:59 +02:00
|
|
|
code = 200
|
|
|
|
except Exception as e:
|
|
|
|
appLog.error(e)
|
|
|
|
response_object = {
|
|
|
|
'status': 'error',
|
|
|
|
'message': 'Error. Please try again or contact the administrator.'
|
|
|
|
}
|
|
|
|
code = 500
|
|
|
|
return jsonify(response_object), code
|
2018-05-01 17:51:38 +02:00
|
|
|
|
|
|
|
|
2018-05-01 21:26:17 +02:00
|
|
|
@activities_blueprint.route('/activities/<int:activity_id>', methods=['GET'])
|
|
|
|
@authenticate
|
|
|
|
def get_activity(auth_user_id, activity_id):
|
|
|
|
"""Get an activity"""
|
|
|
|
activity = Activity.query.filter_by(id=activity_id).first()
|
|
|
|
activities_list = []
|
|
|
|
|
|
|
|
if activity:
|
|
|
|
activities_list.append(activity.serialize())
|
2018-05-02 17:01:31 +02:00
|
|
|
status = 'success'
|
2018-05-01 21:26:17 +02:00
|
|
|
code = 200
|
|
|
|
else:
|
2018-05-02 17:01:31 +02:00
|
|
|
status = 'not found'
|
2018-05-01 21:26:17 +02:00
|
|
|
code = 404
|
2018-05-02 17:01:31 +02:00
|
|
|
|
|
|
|
response_object = {
|
|
|
|
'status': status,
|
|
|
|
'data': {
|
|
|
|
'activities': activities_list
|
|
|
|
}
|
|
|
|
}
|
2018-05-01 21:26:17 +02:00
|
|
|
return jsonify(response_object), code
|
|
|
|
|
|
|
|
|
2018-05-28 14:57:41 +02:00
|
|
|
def get_activity_data(auth_user_id, activity_id, data_type):
|
|
|
|
"""Get chart data from an activity gpx file"""
|
2018-05-03 21:42:54 +02:00
|
|
|
activity = Activity.query.filter_by(id=activity_id).first()
|
2018-05-28 14:57:41 +02:00
|
|
|
content = ''
|
2018-05-03 21:42:54 +02:00
|
|
|
if activity:
|
|
|
|
if not activity.gpx or activity.gpx == '':
|
|
|
|
response_object = {
|
|
|
|
'status': 'fail',
|
2018-05-23 17:30:22 +02:00
|
|
|
'message': f'No gpx file for this activity (id: {activity_id})'
|
2018-05-03 21:42:54 +02:00
|
|
|
}
|
2018-05-09 16:32:06 +02:00
|
|
|
return jsonify(response_object), 400
|
2018-05-03 21:42:54 +02:00
|
|
|
|
|
|
|
try:
|
2018-05-28 14:57:41 +02:00
|
|
|
if data_type == 'chart':
|
|
|
|
content = get_chart_data(activity.gpx)
|
|
|
|
else: # data_type == 'gpx'
|
|
|
|
with open(activity.gpx, encoding='utf-8') as f:
|
|
|
|
content = f.read()
|
2018-05-03 21:42:54 +02:00
|
|
|
except Exception as e:
|
|
|
|
appLog.error(e)
|
2018-05-28 14:57:41 +02:00
|
|
|
response_object = {'status': 'error',
|
|
|
|
'message': 'internal error',
|
|
|
|
'data': ({'chart_data': content}
|
|
|
|
if data_type == 'chart'
|
|
|
|
else {'gpx': content})}
|
2018-05-09 16:32:06 +02:00
|
|
|
return jsonify(response_object), 500
|
2018-05-03 21:42:54 +02:00
|
|
|
|
|
|
|
status = 'success'
|
2018-05-09 16:32:06 +02:00
|
|
|
message = ''
|
2018-05-03 21:42:54 +02:00
|
|
|
code = 200
|
|
|
|
else:
|
|
|
|
status = 'not found'
|
2018-05-23 17:30:22 +02:00
|
|
|
message = f'Activity not found (id: {activity_id})'
|
2018-05-03 21:42:54 +02:00
|
|
|
code = 404
|
|
|
|
|
2018-05-28 14:57:41 +02:00
|
|
|
response_object = {'status': status,
|
|
|
|
'message': message,
|
|
|
|
'data': ({'chart_data': content}
|
|
|
|
if data_type == 'chart'
|
|
|
|
else {'gpx': content})}
|
2018-05-03 21:42:54 +02:00
|
|
|
return jsonify(response_object), code
|
2018-05-28 14:38:32 +02:00
|
|
|
|
|
|
|
|
2018-05-28 14:57:41 +02:00
|
|
|
@activities_blueprint.route(
|
|
|
|
'/activities/<int:activity_id>/gpx', methods=['GET']
|
|
|
|
)
|
|
|
|
@authenticate
|
|
|
|
def get_activity_gpx(auth_user_id, activity_id):
|
|
|
|
"""Get gpx file for an activity"""
|
|
|
|
return get_activity_data(auth_user_id, activity_id, 'gpx')
|
|
|
|
|
|
|
|
|
2018-05-28 14:38:32 +02:00
|
|
|
@activities_blueprint.route(
|
|
|
|
'/activities/<int:activity_id>/chart_data', methods=['GET']
|
|
|
|
)
|
|
|
|
@authenticate
|
|
|
|
def get_activity_chart_data(auth_user_id, activity_id):
|
|
|
|
"""Get chart data from an activity gpx file"""
|
2018-05-28 14:57:41 +02:00
|
|
|
return get_activity_data(auth_user_id, activity_id, 'chart')
|
2018-05-03 21:42:54 +02:00
|
|
|
|
|
|
|
|
2018-05-01 17:51:38 +02:00
|
|
|
@activities_blueprint.route('/activities', methods=['POST'])
|
|
|
|
@authenticate
|
|
|
|
def post_activity(auth_user_id):
|
2018-05-14 10:55:01 +02:00
|
|
|
"""Post an activity (with gpx file)"""
|
2018-05-01 17:51:38 +02:00
|
|
|
response_object = verify_extension('activity', request)
|
|
|
|
if response_object['status'] != 'success':
|
2018-05-09 16:32:06 +02:00
|
|
|
return jsonify(response_object), 400
|
2018-05-01 17:51:38 +02:00
|
|
|
|
|
|
|
activity_data = json.loads(request.form["data"])
|
|
|
|
if not activity_data or activity_data.get('sport_id') is None:
|
|
|
|
response_object = {
|
|
|
|
'status': 'error',
|
|
|
|
'message': 'Invalid payload.'
|
|
|
|
}
|
|
|
|
return jsonify(response_object), 400
|
|
|
|
|
|
|
|
activity_file = request.files['file']
|
2018-05-29 19:06:33 +02:00
|
|
|
upload_dir = os.path.join(
|
|
|
|
current_app.config['UPLOAD_FOLDER'],
|
|
|
|
'activities',
|
|
|
|
str(auth_user_id))
|
|
|
|
folders = {
|
|
|
|
'extract_dir': os.path.join(upload_dir, 'extract'),
|
|
|
|
'tmp_dir': os.path.join(upload_dir, 'tmp'),
|
|
|
|
}
|
2018-05-01 17:51:38 +02:00
|
|
|
|
|
|
|
try:
|
2018-05-29 16:28:59 +02:00
|
|
|
new_activities = process_files(
|
2018-05-29 19:06:33 +02:00
|
|
|
auth_user_id, activity_data, activity_file, folders
|
2018-05-11 13:45:54 +02:00
|
|
|
)
|
2018-05-29 16:28:59 +02:00
|
|
|
if len(new_activities) > 0:
|
|
|
|
response_object = {
|
|
|
|
'status': 'created',
|
|
|
|
'data': {
|
|
|
|
'activities': [new_activity.serialize()
|
|
|
|
for new_activity in new_activities]
|
|
|
|
}
|
2018-05-02 17:01:31 +02:00
|
|
|
}
|
2018-05-29 16:28:59 +02:00
|
|
|
code = 201
|
|
|
|
else:
|
|
|
|
response_object = {
|
|
|
|
'status': 'fail',
|
|
|
|
'data': {
|
|
|
|
'activities': []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
code = 400
|
2018-05-29 12:53:13 +02:00
|
|
|
except ActivityException as e:
|
2018-05-01 17:51:38 +02:00
|
|
|
db.session.rollback()
|
2018-05-29 12:53:13 +02:00
|
|
|
if e.e:
|
|
|
|
appLog.error(e.e)
|
2018-05-01 17:51:38 +02:00
|
|
|
response_object = {
|
2018-05-29 12:53:13 +02:00
|
|
|
'status': e.status,
|
|
|
|
'message': e.message,
|
2018-05-01 17:51:38 +02:00
|
|
|
}
|
2018-05-29 12:53:13 +02:00
|
|
|
code = 500 if e.status == 'error' else 400
|
2018-05-29 19:06:33 +02:00
|
|
|
|
|
|
|
shutil.rmtree(folders['extract_dir'], ignore_errors=True)
|
|
|
|
shutil.rmtree(folders['tmp_dir'], ignore_errors=True)
|
|
|
|
return jsonify(response_object), code
|
2018-05-08 15:46:54 +02:00
|
|
|
|
|
|
|
|
|
|
|
@activities_blueprint.route('/activities/no_gpx', methods=['POST'])
|
|
|
|
@authenticate
|
|
|
|
def post_activity_no_gpx(auth_user_id):
|
|
|
|
"""Post an activity without gpx file"""
|
|
|
|
activity_data = request.get_json()
|
|
|
|
if not activity_data or activity_data.get('sport_id') is None \
|
|
|
|
or activity_data.get('duration') is None \
|
|
|
|
or activity_data.get('distance') is None \
|
|
|
|
or activity_data.get('activity_date') is None:
|
|
|
|
response_object = {
|
|
|
|
'status': 'error',
|
|
|
|
'message': 'Invalid payload.'
|
|
|
|
}
|
|
|
|
return jsonify(response_object), 400
|
|
|
|
|
|
|
|
try:
|
2018-05-10 10:21:58 +02:00
|
|
|
new_activity = create_activity(auth_user_id, activity_data)
|
2018-05-08 15:46:54 +02:00
|
|
|
db.session.add(new_activity)
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
response_object = {
|
|
|
|
'status': 'created',
|
|
|
|
'data': {
|
|
|
|
'activities': [new_activity.serialize()]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return jsonify(response_object), 201
|
|
|
|
|
|
|
|
except (exc.IntegrityError, ValueError) as e:
|
|
|
|
db.session.rollback()
|
|
|
|
appLog.error(e)
|
|
|
|
response_object = {
|
|
|
|
'status': 'fail',
|
|
|
|
'message': 'Error during activity save.'
|
|
|
|
}
|
|
|
|
return jsonify(response_object), 500
|
2018-05-09 18:54:30 +02:00
|
|
|
|
|
|
|
|
2018-05-09 20:45:01 +02:00
|
|
|
@activities_blueprint.route('/activities/<int:activity_id>', methods=['PATCH'])
|
|
|
|
@authenticate
|
|
|
|
def update_activity(auth_user_id, activity_id):
|
|
|
|
"""Update an activity"""
|
|
|
|
activity_data = request.get_json()
|
2018-05-11 17:55:46 +02:00
|
|
|
if not activity_data:
|
2018-05-09 20:45:01 +02:00
|
|
|
response_object = {
|
|
|
|
'status': 'error',
|
|
|
|
'message': 'Invalid payload.'
|
|
|
|
}
|
|
|
|
return jsonify(response_object), 400
|
|
|
|
|
|
|
|
try:
|
|
|
|
activity = Activity.query.filter_by(id=activity_id).first()
|
|
|
|
if activity:
|
2018-05-11 17:55:46 +02:00
|
|
|
activity = edit_activity(activity, activity_data)
|
2018-05-09 20:45:01 +02:00
|
|
|
db.session.commit()
|
|
|
|
response_object = {
|
|
|
|
'status': 'success',
|
|
|
|
'data': {
|
|
|
|
'activities': [activity.serialize()]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
code = 200
|
|
|
|
else:
|
|
|
|
response_object = {
|
|
|
|
'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
|
|
|
|
|
|
|
|
|
2018-05-09 18:54:30 +02:00
|
|
|
@activities_blueprint.route(
|
|
|
|
'/activities/<int:activity_id>', methods=['DELETE']
|
|
|
|
)
|
|
|
|
@authenticate
|
|
|
|
def delete_activity(auth_user_id, activity_id):
|
|
|
|
"""Delete an activity"""
|
|
|
|
try:
|
|
|
|
activity = Activity.query.filter_by(id=activity_id).first()
|
|
|
|
if activity:
|
|
|
|
db.session.delete(activity)
|
|
|
|
db.session.commit()
|
|
|
|
response_object = {
|
|
|
|
'status': 'no content'
|
|
|
|
}
|
|
|
|
code = 204
|
|
|
|
else:
|
|
|
|
response_object = {
|
|
|
|
'status': 'not found',
|
|
|
|
'data': {
|
|
|
|
'activities': []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
code = 404
|
|
|
|
except (exc.IntegrityError, exc.OperationalError, ValueError, OSError) \
|
|
|
|
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
|