diff --git a/app.py b/app.py index bab1f93..c07bfa8 100644 --- a/app.py +++ b/app.py @@ -14,6 +14,7 @@ import sparklines app = Flask(__name__) # TODO CHANGE SECRET KEY TO ENVIRONMENT VARIABLE app.config['SECRET_KEY'] = 'secret!' +app.config["SQLALCHEMY_ECHO"] = False # True for debugging uri = os.getenv("DATABASE_URL") if uri and uri.startswith("postgres://"): uri = uri.replace("postgres://", "postgresql://", 1) @@ -50,8 +51,23 @@ class Workout(db.Model): created_at = db.Column(db.DateTime, nullable=False, default=db.func.now()) bike_id = db.Column(db.Integer, db.ForeignKey( 'bikes.id', ondelete='CASCADE'), nullable=False) + started_at = db.Column(db.DateTime, nullable=False) + duration = db.Column(db.Numeric, nullable=False) + average_rpm = db.Column(db.Numeric, nullable=False) + min_rpm = db.Column(db.Integer, nullable=False) + max_rpm = db.Column(db.Integer, nullable=False) + calories = db.Column(db.Numeric, nullable=False) + distance = db.Column(db.Numeric, nullable=False) + average_bpm = db.Column(db.Numeric, nullable=False) + min_bpm = db.Column(db.Integer, nullable=False) + max_bpm = db.Column(db.Integer, nullable=False) + is_heart_rate_available = db.Column( + db.Boolean, nullable=False, default=False) + is_cadence_available = db.Column(db.Boolean, nullable=False, default=False) cadence_readings = db.relationship( 'CadenceReading', backref='workout', lazy=True) + heart_rate_readings = db.relationship( + 'HeartRateReading', backref='workout', lazy=True) bike = db.relationship('Bike', backref='workouts', lazy=True) @@ -154,39 +170,39 @@ def workouts(user_id): @app.route('/user//workout//', methods=['GET', 'DELETE']) def workout(user_id, workout_id, graph_type): - workout = Workout.query.filter_by(user_id=user_id, id=workout_id).first() - if workout: - # Get the cadence readings for the workout - cadence_readings = CadenceReading.query.filter_by( - workout_id=workout_id).all() - if cadence_readings: - x_values = [reading.created_at for reading in cadence_readings] - if graph_type == 'cadence': - y_values = [reading.rpm for reading in cadence_readings] - return create_graph(x_values=x_values, y_values=y_values, y_label='Cadence (RPM)', filename='cadence'), 200 - elif graph_type == 'speed': - y_values = [reading.speed for reading in cadence_readings] - return create_graph(x_values=x_values, y_values=y_values, y_label='Speed (KPH)', filename='speed'), 200 - elif graph_type == 'distance': - y_values = [reading.distance for reading in cadence_readings] - return create_graph(x_values=x_values, y_values=y_values, y_label='Distance (KM)', filename='distance'), 200 - elif graph_type == 'calories': - y_values = [reading.calories for reading in cadence_readings] - return create_graph(x_values=x_values, y_values=y_values, y_label='Calories (KCAL)', filename='calories'), 200 - elif graph_type == 'power': - y_values = [reading.power for reading in cadence_readings] - return create_graph(x_values=x_values, y_values=y_values, y_label='Power (WATTS)', filename='power'), 200 - heart_rate_readings = HeartRateReading.query.filter_by( - workout_id=workout_id).all() - if heart_rate_readings: - x_values = [reading.created_at for reading in heart_rate_readings] - y_values = [reading.bpm for reading in heart_rate_readings] - return create_graph(x_values=x_values, y_values=y_values, y_label='Heart Rate (BPM)', filename='heart_rate'), 200 - else: - return jsonify({'message': 'No cadence readings for workout {}.'.format(workout_id)}), 404 - else: + workout = Workout.query.filter_by(user_id=user_id, id=workout_id).join( + Workout.cadence_readings).join(Workout.heart_rate_readings).first() + + if not workout: return jsonify({'message': 'Workout {} not found for user {}.'.format(workout_id, user_id)}), 404 + if workout.is_cadence_available: + x_values = [reading.created_at for reading in workout.cadence_readings] + if graph_type == 'cadence': + y_values = [reading.rpm for reading in workout.cadence_readings] + return create_graph(x_values=x_values, y_values=y_values, y_label='Cadence (RPM)', filename='cadence'), 200 + elif graph_type == 'speed': + y_values = [reading.speed for reading in workout.cadence_readings] + return create_graph(x_values=x_values, y_values=y_values, y_label='Speed (KPH)', filename='speed'), 200 + elif graph_type == 'distance': + y_values = [ + reading.distance for reading in workout.cadence_readings] + return create_graph(x_values=x_values, y_values=y_values, y_label='Distance (KM)', filename='distance'), 200 + elif graph_type == 'calories': + y_values = [ + reading.calories for reading in workout.cadence_readings] + return create_graph(x_values=x_values, y_values=y_values, y_label='Calories (KCAL)', filename='calories'), 200 + elif graph_type == 'power': + y_values = [reading.power for reading in workout.cadence_readings] + return create_graph(x_values=x_values, y_values=y_values, y_label='Power (WATTS)', filename='power'), 200 + if workout.is_heart_rate_available: + x_values = [ + reading.created_at for reading in workout.heart_rate_readings] + y_values = [reading.bpm for reading in workout.heart_rate_readings] + return create_graph(x_values=x_values, y_values=y_values, y_label='Heart Rate (BPM)', filename='heart_rate'), 200 + + return jsonify({'message': 'Unable to generate {} for workout {}.'.format(graph_type, workout_id)}), 409 + @app.route('/user//workout//view', methods=['GET']) def view_workout(user_id, workout_id): @@ -296,32 +312,28 @@ def get_workouts_for_user(user_id): workouts_data = [] workouts = Workout.query.filter_by(user_id=user_id).order_by( Workout.created_at.desc()).all() + for workout in workouts: - cadence_readings = CadenceReading.query.filter_by( - workout_id=workout.id).all() - if cadence_readings: - start_time = min( - reading.created_at for reading in cadence_readings) - end_time = max( - reading.created_at for reading in cadence_readings) - duration = end_time - start_time - average_rpm = sum( - reading.rpm for reading in cadence_readings) / len(cadence_readings) - calories = cadence_readings[-1].calories - distance = cadence_readings[-1].distance + duration = timedelta( + seconds=int(workout.duration)) if workout.duration else timedelta(seconds=0) + average_rpm = workout.average_rpm if workout.average_rpm else 0 + min_rpm = workout.min_rpm if workout.min_rpm else 0 + max_rpm = workout.max_rpm if workout.max_rpm else 0 + calories = workout.calories if workout.calories else 0 + distance = workout.distance if workout.distance else 0 + average_bpm = workout.average_bpm if workout.average_bpm else 0 + min_bpm = workout.min_bpm if workout.min_bpm else 0 + max_bpm = workout.max_bpm if workout.max_bpm else 0 + is_heart_rate_available = workout.is_heart_rate_available + is_cadence_available = workout.is_cadence_available + start_time = workout.started_at - selected_graph_types = ['speed'] - is_heart_rate_available = False - heart_rate_readings = HeartRateReading.query.filter_by( - workout_id=workout.id).all() - average_bpm = 0 - if heart_rate_readings: - selected_graph_types.append('heart_rate') - is_heart_rate_available = True - average_bpm = sum(heartrate.bpm for heartrate in heart_rate_readings) / \ - len(heart_rate_readings) + selected_graph_types = ['speed'] + if is_heart_rate_available: + selected_graph_types.append('heart_rate') + if is_cadence_available or is_heart_rate_available: workouts_data.append({ 'id': workout.id, 'user_id': user_id, @@ -331,13 +343,19 @@ def get_workouts_for_user(user_id): 'duration': format_duration(duration), 'duration_minutes': duration.total_seconds() / 60, 'average_rpm': int(average_rpm), + 'min_rpm': int(min_rpm), + 'max_rpm': int(max_rpm), 'calories': int(calories), 'distance': int(distance), 'bike_display_name': workout.bike.display_name, 'selected_graph_types': selected_graph_types, 'is_heart_rate_available': is_heart_rate_available, - 'average_bpm': int(average_bpm) + 'is_cadence_available': is_cadence_available, + 'average_bpm': int(average_bpm), + 'min_bpm': int(min_bpm), + 'max_bpm': int(max_bpm), }) + return workouts_data