def login_user(): # get post data post_data = request.get_json() if not post_data: response_object = {'status': 'error', 'message': 'Invalid payload.'} return jsonify(response_object), 400 email = post_data.get('email') password = post_data.get('password') try: # check for existing user user = User.query.filter(User.email == email).first() if user and bcrypt.check_password_hash(user.password, password): # generate auth token auth_token = user.encode_auth_token(user.id) response_object = { 'status': 'success', 'message': 'Successfully logged in.', 'auth_token': auth_token.decode() } return jsonify(response_object), 200 else: response_object = { 'status': 'error', 'message': 'Invalid credentials.' } return jsonify(response_object), 404 # handler errors 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.' } return jsonify(response_object), 500
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: user = User.query.filter_by(id=auth_user_id).first() new_activity = create_activity(user, activity_data) 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
def delete_activity(auth_user_id, activity_id): """Delete an activity""" try: activity = Activity.query.filter_by(id=activity_id).first() if activity: response_object, code = can_view_activity( auth_user_id, activity.user_id) if response_object: return jsonify(response_object), code 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
def post_sport(auth_user_id): """Post a sport""" sport_data = request.get_json() if not sport_data or sport_data.get('label') is None: response_object = {'status': 'error', 'message': 'Invalid payload.'} return jsonify(response_object), 400 try: new_sport = Sport(label=sport_data.get('label')) db.session.add(new_sport) db.session.commit() response_object = { 'status': 'created', 'data': {'sports': [new_sport.serialize()]}, } code = 201 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
def update_sport(auth_user_id, sport_id): """Update a sport""" sport_data = request.get_json() if not sport_data or sport_data.get('label') is None: response_object = {'status': 'error', 'message': 'Invalid payload.'} return jsonify(response_object), 400 sports_list = [] try: sport = Sport.query.filter_by(id=sport_id).first() if sport: sport.label = sport_data.get('label') db.session.commit() sports_list.append({'id': sport.id, 'label': sport.label}) response_object = { 'status': 'success', 'data': {'sports': sports_list}, } code = 200 else: response_object = { 'status': 'not found', 'data': {'sports': sports_list}, } 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
def process_zip_archive(common_params, extract_dir): with zipfile.ZipFile(common_params['file_path'], "r") as zip_ref: zip_ref.extractall(extract_dir) new_activities = [] gpx_files_limit = os.getenv('REACT_APP_GPX_LIMIT_IMPORT', '10') if gpx_files_limit and gpx_files_limit.isdigit(): gpx_files_limit = int(gpx_files_limit) else: gpx_files_limit = 10 appLog.error('GPX limit not configured, set to 10.') gpx_files_ok = 0 for gpx_file in os.listdir(extract_dir): if '.' in gpx_file and gpx_file.rsplit('.', 1)[1].lower( ) in current_app.config.get('ACTIVITY_ALLOWED_EXTENSIONS'): gpx_files_ok += 1 if gpx_files_ok > gpx_files_limit: break file_path = os.path.join(extract_dir, gpx_file) params = common_params params['file_path'] = file_path new_activity = process_one_gpx_file(params, gpx_file) new_activities.append(new_activity) return new_activities
def delete_sport(auth_user_id, sport_id): """Delete a sport""" try: sport = Sport.query.filter_by(id=sport_id).first() if sport: db.session.delete(sport) db.session.commit() response_object = {'status': 'no content'} code = 204 else: response_object = {'status': 'not found', 'data': {'sports': []}} code = 404 except exc.IntegrityError as e: db.session.rollback() appLog.error(e) response_object = { 'status': 'error', 'message': 'Error. Associated activities exist.', } code = 500 except (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
def post_activity(auth_user_id): """Post an activity (with gpx file)""" response_object = verify_extension('activity', request) if response_object['status'] != 'success': return jsonify(response_object), 400 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'] 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'), } try: new_activities = process_files( auth_user_id, activity_data, activity_file, folders ) if len(new_activities) > 0: response_object = { 'status': 'created', 'data': { 'activities': [new_activity.serialize() for new_activity in new_activities] } } code = 201 else: response_object = { 'status': 'fail', 'data': { 'activities': [] } } code = 400 except ActivityException as e: db.session.rollback() if e.e: appLog.error(e.e) response_object = { 'status': e.status, 'message': e.message, } code = 500 if e.status == 'error' else 400 shutil.rmtree(folders['extract_dir'], ignore_errors=True) shutil.rmtree(folders['tmp_dir'], ignore_errors=True) return jsonify(response_object), code
def edit_user(user_id): # get post data post_data = request.get_json() if not post_data: response_object = {'status': 'error', 'message': 'Invalid payload.'} return jsonify(response_object), 400 first_name = post_data.get('first_name') last_name = post_data.get('last_name') bio = post_data.get('bio') birth_date = post_data.get('birth_date') location = post_data.get('location') password = post_data.get('password') password_conf = post_data.get('password_conf') timezone = post_data.get('timezone') if password is not None and password != '': if password_conf != password: response_object = { 'status': 'error', 'message': 'Password and password confirmation don\'t match.\n' } return jsonify(response_object), 400 else: password = bcrypt.generate_password_hash( password, current_app.config.get('BCRYPT_LOG_ROUNDS')).decode() try: user = User.query.filter_by(id=user_id).first() user.first_name = first_name user.last_name = last_name user.bio = bio user.location = location user.birth_date = (datetime.datetime.strptime(birth_date, '%Y-%m-%d') if birth_date else None) if password is not None and password != '': user.password = password user.timezone = timezone db.session.commit() response_object = { 'status': 'success', 'message': 'User profile updated.' } return jsonify(response_object), 200 # handler errors 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.' } return jsonify(response_object), 500
def get_activity_data(auth_user_id, activity_id, data_type, segment_id=None): """Get data from an activity gpx file""" activity = Activity.query.filter_by(id=activity_id).first() content = '' if activity: response_object, code = can_view_activity(auth_user_id, activity.user_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_id})' response_object = {'status': 'fail', 'message': message} return jsonify(response_object), 400 try: absolute_gpx_filepath = get_absolute_file_path(activity.gpx) if data_type == 'chart': content = get_chart_data(absolute_gpx_filepath, segment_id) else: # data_type == 'gpx' with open(absolute_gpx_filepath, encoding='utf-8') as f: 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' message = '' code = 200 else: status = 'not found' message = f'Activity not found (id: {activity_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
def del_picture(user_id): """ delete authenticated user picture **Example request**: .. sourcecode:: http DELETE /api/auth/picture HTTP/1.1 Content-Type: application/json **Example response**: .. sourcecode:: http HTTP/1.1 204 NO CONTENT Content-Type: application/json :reqheader Authorization: OAuth 2.0 Bearer Token :statuscode 204: picture deleted :statuscode 401: - Provide a valid auth token. - Signature expired. Please log in again. - Invalid token. Please log in again. :statuscode 500: Error during picture deletion. """ try: user = User.query.filter_by(id=user_id).first() picture_path = get_absolute_file_path(user.picture) if os.path.isfile(picture_path): os.remove(picture_path) user.picture = None db.session.commit() response_object = {'status': 'no content'} return jsonify(response_object), 204 except (exc.IntegrityError, ValueError) as e: db.session.rollback() appLog.error(e) response_object = { 'status': 'fail', 'message': 'Error during picture deletion.', } return jsonify(response_object), 500
def get_map(map_id): """ Get map image for activities with gpx **Example request**: .. sourcecode:: http GET /api/activities/map/fa33f4d996844a5c73ecd1ae24456ab8?1563529507772 HTTP/1.1 Content-Type: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: image/png :param string map_id: activity map id :statuscode 200: success :statuscode 401: - Provide a valid auth token. - Signature expired. Please log in again. - Invalid token. Please log in again. :statuscode 404: map does not exist :statuscode 500: """ try: activity = Activity.query.filter_by(map_id=map_id).first() if not activity: response_object = { 'status': 'fail', 'message': 'Map does not exist', } 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: appLog.error(e) response_object = {'status': 'error', 'message': 'internal error.'} return jsonify(response_object), 500
def update_activity(auth_user_id, activity_id): """Update an activity""" activity_data = request.get_json() if not activity_data: response_object = { 'status': 'error', 'message': 'Invalid payload.' } return jsonify(response_object), 400 try: activity = Activity.query.filter_by(id=activity_id).first() if activity: response_object, code = can_view_activity( auth_user_id, activity.user_id) if response_object: return jsonify(response_object), code activity = edit_activity(activity, activity_data, auth_user_id) 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
def get_map(map_id): try: activity = Activity.query.filter_by(map_id=map_id).first() if not activity: response_object = { 'status': 'fail', 'message': 'Map does not exist' } 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: appLog.error(e) response_object = { 'status': 'error', 'message': 'internal error.' } return jsonify(response_object), 500
def del_picture(user_id): try: user = User.query.filter_by(id=user_id).first() picture_path = get_absolute_file_path(user.picture) if os.path.isfile(picture_path): os.remove(picture_path) user.picture = None db.session.commit() response_object = {'status': 'no content'} return jsonify(response_object), 204 except (exc.IntegrityError, ValueError) as e: db.session.rollback() appLog.error(e) response_object = { 'status': 'fail', 'message': 'Error during picture deletion.' } return jsonify(response_object), 500
def get_weather(point): if not API_KEY or API_KEY == '': return None try: point_time = pytz.utc.localize(point.time) forecast = forecastio.load_forecast(API_KEY, point.latitude, point.longitude, time=point_time, units='si') weather = forecast.currently() return { 'summary': weather.summary, 'icon': weather.icon, 'temperature': weather.temperature, 'humidity': weather.humidity, 'wind': weather.windSpeed, } except Exception as e: appLog.error(e) return None
def edit_picture(user_id): code = 400 response_object = verify_extension('picture', request) if response_object['status'] != 'success': return jsonify(response_object), code file = request.files['file'] filename = secure_filename(file.filename) dirpath = os.path.join(current_app.config['UPLOAD_FOLDER'], 'pictures', str(user_id)) if not os.path.exists(dirpath): os.makedirs(dirpath) absolute_picture_path = os.path.join(dirpath, filename) relative_picture_path = os.path.join('pictures', str(user_id), filename) try: user = User.query.filter_by(id=user_id).first() if user.picture is not None: old_picture_path = get_absolute_file_path(user.picture) if os.path.isfile(get_absolute_file_path(old_picture_path)): os.remove(old_picture_path) file.save(absolute_picture_path) user.picture = relative_picture_path db.session.commit() response_object = { 'status': 'success', 'message': 'User picture updated.' } return jsonify(response_object), 200 except (exc.IntegrityError, ValueError) as e: db.session.rollback() appLog.error(e) response_object = { 'status': 'fail', 'message': 'Error during picture update.' } return jsonify(response_object), 500
def post_activity(auth_user_id): """ Post an activity with a gpx file **Example request**: .. sourcecode:: http POST /api/activities/ HTTP/1.1 Content-Type: multipart/form-data **Example response**: .. sourcecode:: http HTTP/1.1 201 CREATED Content-Type: application/json { "data": { "activities": [ { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "ascent": null, "ave_speed": 10.0, "bounds": [], "creation_date": "Sun, 14 Jul 2019 13:51:01 GMT", "descent": null, "distance": 10.0, "duration": "0:17:04", "id": 1, "map": null, "max_alt": null, "max_speed": 10.0, "min_alt": null, "modification_date": null, "moving": "0:17:04", "next_activity": 3, "notes": null, "pauses": null, "previous_activity": null, "records": [ { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 4, "record_type": "MS", "sport_id": 1, "user_id": 1, "value": 10.0 }, { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 3, "record_type": "LD", "sport_id": 1, "user_id": 1, "value": "0:17:04" }, { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 2, "record_type": "FD", "sport_id": 1, "user_id": 1, "value": 10.0 }, { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 1, "record_type": "AS", "sport_id": 1, "user_id": 1, "value": 10.0 } ], "segments": [], "sport_id": 1, "title": null, "user_id": 1, "weather_end": null, "weather_start": null, "with_gpx": false } ] }, "status": "success" } :param integer auth_user_id: authenticate user id (from JSON Web Token) :form file: gpx file (allowed extensions: .gpx, .zip) :form data: sport id and notes (example: ``{"sport_id": 1, "notes": ""}``) :reqheader Authorization: OAuth 2.0 Bearer Token :statuscode 201: activity created :statuscode 400: - Invalid payload. - No file part. - No selected file. - File extension not allowed. :statuscode 401: - Provide a valid auth token. - Signature expired. Please log in again. - Invalid token. Please log in again. :statuscode 413: Error during picture update: file size exceeds 1.0MB. :statuscode 500: """ response_object, response_code = verify_extension_and_size( 'activity', request) if response_object['status'] != 'success': return jsonify(response_object), response_code 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'] 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'), } try: new_activities = process_files(auth_user_id, activity_data, activity_file, folders) if len(new_activities) > 0: response_object = { 'status': 'created', 'data': { 'activities': [ new_activity.serialize() for new_activity in new_activities ] }, } code = 201 else: response_object = {'status': 'fail', 'data': {'activities': []}} code = 400 except ActivityException as e: db.session.rollback() if e.e: appLog.error(e.e) response_object = {'status': e.status, 'message': e.message} code = 500 if e.status == 'error' else 400 shutil.rmtree(folders['extract_dir'], ignore_errors=True) shutil.rmtree(folders['tmp_dir'], ignore_errors=True) return jsonify(response_object), code
def get_activities(auth_user_id): """Get all activities for authenticated user""" try: user = User.query.filter_by(id=auth_user_id).first() params = request.args.copy() page = 1 if 'page' not in params.keys() else int(params.get('page')) date_from = params.get('from') if date_from: date_from = datetime.strptime(date_from, '%Y-%m-%d') _, date_from = get_datetime_with_tz(user.timezone, date_from) date_to = params.get('to') if date_to: date_to = datetime.strptime(f'{date_to} 23:59:59', '%Y-%m-%d %H:%M:%S') _, date_to = get_datetime_with_tz(user.timezone, date_to) distance_from = params.get('distance_from') distance_to = params.get('distance_to') duration_from = params.get('duration_from') duration_to = params.get('duration_to') ave_speed_from = params.get('ave_speed_from') ave_speed_to = params.get('ave_speed_to') max_speed_from = params.get('max_speed_from') max_speed_to = params.get('max_speed_to') order = params.get('order') sport_id = params.get('sport_id') per_page = int(params.get('per_page')) if params.get('per_page') else 5 activities = Activity.query.filter( Activity.user_id == auth_user_id, Activity.sport_id == sport_id if sport_id else True, Activity.activity_date >= date_from if date_from else True, Activity.activity_date < date_to + timedelta(seconds=1) if date_to else True, Activity.distance >= int(distance_from) if distance_from else True, Activity.distance <= int(distance_to) if distance_to else True, Activity.moving >= convert_in_duration(duration_from) if duration_from else True, Activity.moving <= convert_in_duration(duration_to) if duration_to else True, Activity.ave_speed >= float(ave_speed_from) if ave_speed_from else True, Activity.ave_speed <= float(ave_speed_to) if ave_speed_to else True, Activity.max_speed >= float(max_speed_from) if max_speed_from else True, Activity.max_speed <= float(max_speed_to) if max_speed_to else True, ).order_by( Activity.activity_date.asc() if order == 'asc' else Activity.activity_date.desc() ).paginate( page, per_page, False ).items response_object = { 'status': 'success', 'data': { 'activities': [activity.serialize(params) for activity in activities] } } 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
def post_activity_no_gpx(auth_user_id): """ Post an activity without gpx file **Example request**: .. sourcecode:: http POST /api/activities/no_gpx HTTP/1.1 Content-Type: application/json **Example response**: .. sourcecode:: http HTTP/1.1 201 CREATED Content-Type: application/json { "data": { "activities": [ { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "ascent": null, "ave_speed": 10.0, "bounds": [], "creation_date": "Sun, 14 Jul 2019 13:51:01 GMT", "descent": null, "distance": 10.0, "duration": "0:17:04", "id": 1, "map": null, "max_alt": null, "max_speed": 10.0, "min_alt": null, "modification_date": null, "moving": "0:17:04", "next_activity": 3, "notes": null, "pauses": null, "previous_activity": null, "records": [ { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 4, "record_type": "MS", "sport_id": 1, "user_id": 1, "value": 10.0 }, { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 3, "record_type": "LD", "sport_id": 1, "user_id": 1, "value": "0:17:04" }, { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 2, "record_type": "FD", "sport_id": 1, "user_id": 1, "value": 10.0 }, { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 1, "record_type": "AS", "sport_id": 1, "user_id": 1, "value": 10.0 } ], "segments": [], "sport_id": 1, "title": null, "user_id": 1, "weather_end": null, "weather_start": null, "with_gpx": false } ] }, "status": "success" } :param integer auth_user_id: authenticate user id (from JSON Web Token) :<json string activity_date: activity date (format: ``%Y-%m-%d %H:%M``) :<json float distance: activity distance in km :<json integer duration: activity duration in seconds :<json string notes: notes (not mandatory) :<json integer sport_id: activity sport id :<json string title: activity title :reqheader Authorization: OAuth 2.0 Bearer Token :statuscode 201: activity created :statuscode 400: invalid payload :statuscode 401: - Provide a valid auth token. - Signature expired. Please log in again. - Invalid token. Please log in again. :statuscode 500: """ 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: user = User.query.filter_by(id=auth_user_id).first() new_activity = create_activity(user, activity_data) 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
def register_user(): """ register a user **Example request**: .. sourcecode:: http POST /api/auth/register HTTP/1.1 Content-Type: application/json **Example responses**: - successful registration .. sourcecode:: http HTTP/1.1 201 CREATED Content-Type: application/json { "auth_token": "JSON Web Token", "message": "Successfully registered.", "status": "success" } - error on registration .. sourcecode:: http HTTP/1.1 400 BAD REQUEST Content-Type: application/json { "message": "Errors: Valid email must be provided.\\n", "status": "error" } :<json string username: user name (3 to 12 characters required) :<json string email: user email :<json string password: password (8 characters required) :<json string password_conf: password confirmation :statuscode 201: Successfully registered. :statuscode 400: - Invalid payload. - Sorry. That user already exists. - Errors: - 3 to 12 characters required for usernanme. - Valid email must be provided. - Password and password confirmation don't match. - 8 characters required for password. :statuscode 403: Error. Registration is disabled. :statuscode 500: Error. Please try again or contact the administrator. """ if not current_app.config.get('REGISTRATION_ALLOWED'): response_object = { 'status': 'error', 'message': 'Error. Registration is disabled.', } return jsonify(response_object), 403 # get post data post_data = request.get_json() if (not post_data or post_data.get('username') is None or post_data.get('email') is None or post_data.get('password') is None or post_data.get('password_conf') is None): response_object = {'status': 'error', 'message': 'Invalid payload.'} return jsonify(response_object), 400 username = post_data.get('username') email = post_data.get('email') password = post_data.get('password') password_conf = post_data.get('password_conf') try: ret = register_controls(username, email, password, password_conf) except TypeError as e: db.session.rollback() appLog.error(e) response_object = { 'status': 'error', 'message': 'Error. Please try again or contact the administrator.', } return jsonify(response_object), 500 if ret != '': response_object = {'status': 'error', 'message': ret} return jsonify(response_object), 400 try: # check for existing user user = User.query.filter( or_(User.username == username, User.email == email)).first() if not user: # add new user to db new_user = User(username=username, email=email, password=password) new_user.timezone = 'Europe/Paris' db.session.add(new_user) db.session.commit() # generate auth token auth_token = new_user.encode_auth_token(new_user.id) response_object = { 'status': 'success', 'message': 'Successfully registered.', 'auth_token': auth_token.decode(), } return jsonify(response_object), 201 else: response_object = { 'status': 'error', 'message': 'Sorry. That user already exists.', } return jsonify(response_object), 400 # handler errors 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.', } return jsonify(response_object), 500
def register_user(): # get post data post_data = request.get_json() if not post_data or post_data.get('username') is None \ or post_data.get('email') is None \ or post_data.get('password') is None \ or post_data.get('password_conf') is None: response_object = {'status': 'error', 'message': 'Invalid payload.'} return jsonify(response_object), 400 username = post_data.get('username') email = post_data.get('email') password = post_data.get('password') password_conf = post_data.get('password_conf') try: ret = register_controls(username, email, password, password_conf) except TypeError as e: db.session.rollback() appLog.error(e) response_object = { 'status': 'error', 'message': 'Error. Please try again or contact the administrator.' } return jsonify(response_object), 500 if ret != '': response_object = {'status': 'error', 'message': 'Errors: ' + ret} return jsonify(response_object), 400 try: # check for existing user user = User.query.filter( or_(User.username == username, User.email == email)).first() if not user: # add new user to db new_user = User(username=username, email=email, password=password) new_user.timezone = 'Europe/Paris' db.session.add(new_user) db.session.commit() # generate auth token auth_token = new_user.encode_auth_token(new_user.id) response_object = { 'status': 'success', 'message': 'Successfully registered.', 'auth_token': auth_token.decode() } return jsonify(response_object), 201 else: response_object = { 'status': 'error', 'message': 'Sorry. That user already exists.' } return jsonify(response_object), 400 # handler errors 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.' } return jsonify(response_object), 500
def delete_activity(auth_user_id, activity_id): """ Delete an activity **Example request**: .. sourcecode:: http DELETE /api/activities/1 HTTP/1.1 Content-Type: application/json **Example response**: .. sourcecode:: http HTTP/1.1 204 NO CONTENT Content-Type: application/json :param integer auth_user_id: authenticate user id (from JSON Web Token) :param integer activity_id: activity id :reqheader Authorization: OAuth 2.0 Bearer Token :statuscode 204: activity deleted :statuscode 401: - Provide a valid auth token. - Signature expired. Please log in again. - Invalid token. Please log in again. :statuscode 404: activity not found :statuscode 500: Error. Please try again or contact the administrator. """ try: activity = Activity.query.filter_by(id=activity_id).first() if activity: response_object, code = can_view_activity(auth_user_id, activity.user_id) if response_object: return jsonify(response_object), code 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
def edit_picture(user_id): """ update authenticated user picture **Example request**: .. sourcecode:: http POST /api/auth/picture HTTP/1.1 Content-Type: multipart/form-data **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "message": "User picture updated.", "status": "success" } :form file: image file (allowed extensions: .jpg, .png, .gif) :reqheader Authorization: OAuth 2.0 Bearer Token :statuscode 200: User picture updated. :statuscode 400: - Invalid payload. - No file part. - No selected file. - File extension not allowed. :statuscode 401: - Provide a valid auth token. - Signature expired. Please log in again. - Invalid token. Please log in again. :statuscode 413: Error during picture update: file size exceeds 1.0MB. :statuscode 500: Error during picture update. """ try: response_object, response_code = verify_extension_and_size( 'picture', request) except RequestEntityTooLarge as e: appLog.error(e) max_file_size = current_app.config['MAX_CONTENT_LENGTH'] response_object = { 'status': 'fail', 'message': 'Error during picture update, file size exceeds ' f'{display_readable_file_size(max_file_size)}.', } return jsonify(response_object), 413 if response_object['status'] != 'success': return jsonify(response_object), response_code file = request.files['file'] filename = secure_filename(file.filename) dirpath = os.path.join(current_app.config['UPLOAD_FOLDER'], 'pictures', str(user_id)) if not os.path.exists(dirpath): os.makedirs(dirpath) absolute_picture_path = os.path.join(dirpath, filename) relative_picture_path = os.path.join('pictures', str(user_id), filename) try: user = User.query.filter_by(id=user_id).first() if user.picture is not None: old_picture_path = get_absolute_file_path(user.picture) if os.path.isfile(get_absolute_file_path(old_picture_path)): os.remove(old_picture_path) file.save(absolute_picture_path) user.picture = relative_picture_path db.session.commit() response_object = { 'status': 'success', 'message': 'User picture updated.', } return jsonify(response_object), 200 except (exc.IntegrityError, ValueError) as e: db.session.rollback() appLog.error(e) response_object = { 'status': 'fail', 'message': 'Error during picture update.', } return jsonify(response_object), 500
def edit_user(user_id): """ edit authenticated user **Example request**: .. sourcecode:: http POST /api/auth/profile/edit HTTP/1.1 Content-Type: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "data": { "admin": false, "bio": null, "birth_date": null, "created_at": "Sun, 14 Jul 2019 14:09:58 GMT", "email": "*****@*****.**", "first_name": null, "id": 2, "language": "en", "last_name": null, "location": null, "nb_activities": 6, "nb_sports": 3, "picture": false, "timezone": "Europe/Paris", "total_distance": 67.895, "total_duration": "6:50:27", "username": "******" "weekm": true, }, "message": "User profile updated.", "status": "success" } :<json string first_name: user first name :<json string last_name: user last name :<json string location: user location :<json string bio: user biography :<json string birth_date: user birth date (format: ``%Y-%m-%d``) :<json string password: user password :<json string password_conf: user password confirmation :<json string timezone: user time zone :<json string weekm: does week start on Monday? :<json string language: language preferences :reqheader Authorization: OAuth 2.0 Bearer Token :statuscode 200: User profile updated. :statuscode 400: - Invalid payload. - Password and password confirmation don't match. :statuscode 401: - Provide a valid auth token. - Signature expired. Please log in again. - Invalid token. Please log in again. :statuscode 500: Error. Please try again or contact the administrator. """ # get post data post_data = request.get_json() user_mandatory_data = { 'first_name', 'last_name', 'bio', 'birth_date', 'language', 'location', 'timezone', 'weekm', } if not post_data or not post_data.keys() >= user_mandatory_data: response_object = {'status': 'error', 'message': 'Invalid payload.'} return jsonify(response_object), 400 first_name = post_data.get('first_name') last_name = post_data.get('last_name') bio = post_data.get('bio') birth_date = post_data.get('birth_date') language = post_data.get('language') location = post_data.get('location') password = post_data.get('password') password_conf = post_data.get('password_conf') timezone = post_data.get('timezone') weekm = post_data.get('weekm') if password is not None and password != '': if password_conf != password: message = 'Password and password confirmation don\'t match.\n' response_object = {'status': 'error', 'message': message} return jsonify(response_object), 400 else: password = bcrypt.generate_password_hash( password, current_app.config.get('BCRYPT_LOG_ROUNDS')).decode() try: user = User.query.filter_by(id=user_id).first() user.first_name = first_name user.last_name = last_name user.bio = bio user.language = language user.location = location user.birth_date = (datetime.datetime.strptime(birth_date, '%Y-%m-%d') if birth_date else None) if password is not None and password != '': user.password = password user.timezone = timezone user.weekm = weekm db.session.commit() response_object = { 'status': 'success', 'message': 'User profile updated.', 'data': user.serialize(), } return jsonify(response_object), 200 # handler errors 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.', } return jsonify(response_object), 500
def get_activities(auth_user_id): """ Get activities for the authenticated user. **Example requests**: - without parameters .. sourcecode:: http GET /api/activities/ HTTP/1.1 - with some query parameters .. sourcecode:: http GET /api/activities?from=2019-07-02&to=2019-07-31&sport_id=1 HTTP/1.1 **Example responses**: - returning at least one activity .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "data": { "activities": [ { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "ascent": null, "ave_speed": 10.0, "bounds": [], "creation_date": "Sun, 14 Jul 2019 13:51:01 GMT", "descent": null, "distance": 10.0, "duration": "0:17:04", "id": 1, "map": null, "max_alt": null, "max_speed": 10.0, "min_alt": null, "modification_date": null, "moving": "0:17:04", "next_activity": 3, "notes": null, "pauses": null, "previous_activity": null, "records": [ { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 4, "record_type": "MS", "sport_id": 1, "user_id": 1, "value": 10.0 }, { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 3, "record_type": "LD", "sport_id": 1, "user_id": 1, "value": "0:17:04" }, { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 2, "record_type": "FD", "sport_id": 1, "user_id": 1, "value": 10.0 }, { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 1, "record_type": "AS", "sport_id": 1, "user_id": 1, "value": 10.0 } ], "segments": [], "sport_id": 1, "title": null, "user_id": 1, "weather_end": null, "weather_start": null, "with_gpx": false } ] }, "status": "success" } - returning no activities .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "data": { "activities": [] }, "status": "success" } :param integer auth_user_id: authenticate user id (from JSON Web Token) :query integer page: page if using pagination (default: 1) :query integer per_page: number of activities per page (default: 5) :query integer sport_id: sport id :query string from: start date (format: ``%Y-%m-%d``) :query string to: end date (format: ``%Y-%m-%d``) :query float distance_from: minimal distance :query float distance_to: maximal distance :query string duration_from: minimal duration (format: ``%H:%M``) :query string duration_to: maximal distance (format: ``%H:%M``) :query float ave_speed_from: minimal average speed :query float ave_speed_to: maximal average speed :query float max_speed_from: minimal max. speed :query float max_speed_to: maximal max. speed :query string order: sorting order (default: ``desc``) :reqheader Authorization: OAuth 2.0 Bearer Token :statuscode 200: success :statuscode 401: - Provide a valid auth token. - Signature expired. Please log in again. - Invalid token. Please log in again. :statuscode 500: """ try: user = User.query.filter_by(id=auth_user_id).first() params = request.args.copy() page = 1 if 'page' not in params.keys() else int(params.get('page')) date_from = params.get('from') if date_from: date_from = datetime.strptime(date_from, '%Y-%m-%d') _, date_from = get_datetime_with_tz(user.timezone, date_from) date_to = params.get('to') if date_to: date_to = datetime.strptime(f'{date_to} 23:59:59', '%Y-%m-%d %H:%M:%S') _, date_to = get_datetime_with_tz(user.timezone, date_to) distance_from = params.get('distance_from') distance_to = params.get('distance_to') duration_from = params.get('duration_from') duration_to = params.get('duration_to') ave_speed_from = params.get('ave_speed_from') ave_speed_to = params.get('ave_speed_to') max_speed_from = params.get('max_speed_from') max_speed_to = params.get('max_speed_to') order = params.get('order') sport_id = params.get('sport_id') per_page = int(params.get('per_page')) if params.get('per_page') else 5 activities = (Activity.query.filter( Activity.user_id == auth_user_id, Activity.sport_id == sport_id if sport_id else True, Activity.activity_date >= date_from if date_from else True, Activity.activity_date < date_to + timedelta(seconds=1) if date_to else True, Activity.distance >= int(distance_from) if distance_from else True, Activity.distance <= int(distance_to) if distance_to else True, Activity.moving >= convert_in_duration(duration_from) if duration_from else True, Activity.moving <= convert_in_duration(duration_to) if duration_to else True, Activity.ave_speed >= float(ave_speed_from) if ave_speed_from else True, Activity.ave_speed <= float(ave_speed_to) if ave_speed_to else True, Activity.max_speed >= float(max_speed_from) if max_speed_from else True, Activity.max_speed <= float(max_speed_to) if max_speed_to else True, ).order_by(Activity.activity_date.asc() if order == 'asc' else Activity.activity_date.desc()).paginate( page, per_page, False).items) response_object = { 'status': 'success', 'data': { 'activities': [activity.serialize(params) for activity in activities] }, } 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
def get_activities(user_id, filter_type): try: user = User.query.filter_by(id=user_id).first() if not user: response_object = { 'status': 'not found', 'message': 'User does not exist.', } return jsonify(response_object), 404 params = request.args.copy() date_from = params.get('from') if date_from: date_from = datetime.strptime(date_from, '%Y-%m-%d') _, date_from = get_datetime_with_tz(user.timezone, date_from) date_to = params.get('to') if date_to: date_to = datetime.strptime(f'{date_to} 23:59:59', '%Y-%m-%d %H:%M:%S') _, date_to = get_datetime_with_tz(user.timezone, date_to) sport_id = params.get('sport_id') time = params.get('time') if filter_type == 'by_sport': sport_id = params.get('sport_id') if sport_id: sport = Sport.query.filter_by(id=sport_id).first() if not sport: response_object = { 'status': 'not found', 'message': 'Sport does not exist.', } return jsonify(response_object), 404 activities = (Activity.query.filter( Activity.user_id == user_id, Activity.activity_date >= date_from if date_from else True, Activity.activity_date < date_to + timedelta(seconds=1) if date_to else True, Activity.sport_id == sport_id if sport_id else True, ).order_by(Activity.activity_date.asc()).all()) activities_list = {} for activity in activities: if filter_type == 'by_sport': sport_id = activity.sport_id if sport_id not in activities_list: activities_list[sport_id] = { 'nb_activities': 0, 'total_distance': 0.0, 'total_duration': 0, } activities_list[sport_id]['nb_activities'] += 1 activities_list[sport_id]['total_distance'] += float( activity.distance) activities_list[sport_id][ 'total_duration'] += convert_timedelta_to_integer( activity.moving) else: if time == 'week': activity_date = activity.activity_date - timedelta( days=(activity.activity_date.isoweekday( ) if activity.activity_date.isoweekday() < 7 else 0)) time_period = datetime.strftime(activity_date, "%Y-%m-%d") elif time == 'weekm': # week start Monday activity_date = activity.activity_date - timedelta( days=activity.activity_date.weekday()) time_period = datetime.strftime(activity_date, "%Y-%m-%d") elif time == 'month': time_period = datetime.strftime(activity.activity_date, "%Y-%m") # noqa elif time == 'year' or not time: time_period = datetime.strftime(activity.activity_date, "%Y") # noqa else: response_object = { 'status': 'fail', 'message': 'Invalid time period.', } return jsonify(response_object), 400 sport_id = activity.sport_id if time_period not in activities_list: activities_list[time_period] = {} if sport_id not in activities_list[time_period]: activities_list[time_period][sport_id] = { 'nb_activities': 0, 'total_distance': 0.0, 'total_duration': 0, } activities_list[time_period][sport_id]['nb_activities'] += 1 activities_list[time_period][sport_id][ 'total_distance'] += float(activity.distance) activities_list[time_period][sport_id][ 'total_duration'] += convert_timedelta_to_integer( activity.moving) response_object = { 'status': 'success', 'data': { 'statistics': activities_list }, } 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
def update_activity(auth_user_id, activity_id): """ Update an activity **Example request**: .. sourcecode:: http PATCH /api/activities/1 HTTP/1.1 Content-Type: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "data": { "activities": [ { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "ascent": null, "ave_speed": 10.0, "bounds": [], "creation_date": "Sun, 14 Jul 2019 13:51:01 GMT", "descent": null, "distance": 10.0, "duration": "0:17:04", "id": 1, "map": null, "max_alt": null, "max_speed": 10.0, "min_alt": null, "modification_date": null, "moving": "0:17:04", "next_activity": 3, "notes": null, "pauses": null, "previous_activity": null, "records": [ { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 4, "record_type": "MS", "sport_id": 1, "user_id": 1, "value": 10.0 }, { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 3, "record_type": "LD", "sport_id": 1, "user_id": 1, "value": "0:17:04" }, { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 2, "record_type": "FD", "sport_id": 1, "user_id": 1, "value": 10.0 }, { "activity_date": "Mon, 01 Jan 2018 00:00:00 GMT", "activity_id": 1, "id": 1, "record_type": "AS", "sport_id": 1, "user_id": 1, "value": 10.0 } ], "segments": [], "sport_id": 1, "title": null, "user_id": 1, "weather_end": null, "weather_start": null, "with_gpx": false } ] }, "status": "success" } :param integer auth_user_id: authenticate user id (from JSON Web Token) :param integer activity_id: activity id :<json string activity_date: activity date (format: ``%Y-%m-%d %H:%M``) (only for activity without gpx) :<json float distance: activity distance in km (only for activity without gpx) :<json integer duration: activity duration in seconds (only for activity without gpx) :<json string notes: notes :<json integer sport_id: activity sport id :<json string title: activity title :reqheader Authorization: OAuth 2.0 Bearer Token :statuscode 200: activity updated :statuscode 400: invalid payload :statuscode 401: - Provide a valid auth token. - Signature expired. Please log in again. - Invalid token. Please log in again. :statuscode 404: activity not found :statuscode 500: """ activity_data = request.get_json() if not activity_data: response_object = {'status': 'error', 'message': 'Invalid payload.'} return jsonify(response_object), 400 try: activity = Activity.query.filter_by(id=activity_id).first() if activity: response_object, code = can_view_activity(auth_user_id, activity.user_id) if response_object: return jsonify(response_object), code activity = edit_activity(activity, activity_data, auth_user_id) 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
def login_user(): """ user login **Example request**: .. sourcecode:: http POST /api/auth/login HTTP/1.1 Content-Type: application/json **Example responses**: - successful login .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "auth_token": "JSON Web Token", "message": "Successfully logged in.", "status": "success" } - error on login .. sourcecode:: http HTTP/1.1 404 NOT FOUND Content-Type: application/json { "message": "Invalid credentials.", "status": "error" } :<json string email: user email :<json string password_conf: password confirmation :statuscode 200: Successfully logged in. :statuscode 404: Invalid credentials. :statuscode 500: Error. Please try again or contact the administrator. """ # get post data post_data = request.get_json() if not post_data: response_object = {'status': 'error', 'message': 'Invalid payload.'} return jsonify(response_object), 400 email = post_data.get('email') password = post_data.get('password') try: # check for existing user user = User.query.filter(User.email == email).first() if user and bcrypt.check_password_hash(user.password, password): # generate auth token auth_token = user.encode_auth_token(user.id) response_object = { 'status': 'success', 'message': 'Successfully logged in.', 'auth_token': auth_token.decode(), } return jsonify(response_object), 200 else: response_object = { 'status': 'error', 'message': 'Invalid credentials.', } return jsonify(response_object), 404 # handler errors 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.', } return jsonify(response_object), 500