Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion api/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ def create_activity():
elevation_gain=data['elevation_gain'],
elevation_loss=data['elevation_loss'],
max_elevation=data['max_elevation'],
min_elevation=data['min_elevation']
min_elevation=data['min_elevation'],
steps=data.get('steps'),
floors=data.get('floors'),
zone_minutes=data.get('zone_minutes')
)

# Optional time series
Expand Down
5 changes: 5 additions & 0 deletions api/body_insight.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ def add_body_insight():
if not latest_activity:
return jsonify({"error": "No activities found for user"}), 404
activity_id = latest_activity.id

# Check for existing body_insight for activity
existing = BodyInsight.query.filter_by(activity_id=activity_id).first()
if existing:
return jsonify({"error": "BodyInsight already exists for this activity"}), 409

try:
insight = BodyInsight(
Expand Down
30 changes: 30 additions & 0 deletions api/sessions.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from flask import Blueprint, jsonify
from models.activity import Activity
from models.body_insight import BodyInsight
from models.sleep_data import SleepData
from models import db
from datetime import datetime

sessions_bp = Blueprint('sessions_api', __name__)

# Get all sessions summary for table.
@sessions_bp.route('', methods=['GET'])
def get_sessions():
# replace with authenticated user ID from session/token
Expand All @@ -23,3 +26,30 @@ def get_sessions():
})

return jsonify(sessions), 200



# Get activity, body insight and sleep data details for a single session
@sessions_bp.route('/<int:session_id>/details', methods=['GET'])
def get_session_details(session_id):
# Fetch the activity
activity = Activity.query.get(session_id)
if not activity:
return jsonify({"error": "Session not found"}), 404

# Fetch the linked BodyInsight, if it exists
body_insight = BodyInsight.query.filter_by(activity_id=activity.id).first()

# Fetch sleep data for the same date as the activity
sleep_entry = SleepData.query.filter_by(
user_id=activity.user_id,
date=activity.begin_time.date()
).first()

response = {
"activity": activity.as_dict(),
"body_insight": body_insight.as_dict() if body_insight else None,
"sleep_data": sleep_entry.as_dict() if sleep_entry else None
}

return jsonify(response), 200
56 changes: 56 additions & 0 deletions api/sleep_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# /api/sleep_data.py
from flask import Blueprint, request, jsonify
from models.sleep_data import SleepData
from models import db
from datetime import datetime

sleep_data_bp = Blueprint('sleep_data', __name__, url_prefix='/api/sleep_data')

# For now, mimic authenticated user
def get_current_user_id():
return 1 # Replace with token/session in future


# POST new sleep record
@sleep_data_bp.route('', methods=['POST'])
def add_sleep_data():
data = request.get_json()
user_id = get_current_user_id()

if not data.get('date') or not data.get('duration_minutes'):
return jsonify({"error": "Missing required fields: date, duration_minutes"}), 400

try:
sleep_date = datetime.strptime(data['date'], "%Y-%m-%d").date()
except ValueError:
return jsonify({"error": "Invalid date format. Use YYYY-MM-DD"}), 400

sleep_entry = SleepData(
user_id=user_id,
date=sleep_date,
duration_minutes=data['duration_minutes'],
sleep_score=data.get('sleep_score')
)

db.session.add(sleep_entry)
db.session.commit()

return jsonify(sleep_entry.as_dict()), 201


# GET all sleep records for the current user
@sleep_data_bp.route('', methods=['GET'])
def get_all_sleep():
user_id = get_current_user_id()
entries = SleepData.query.filter_by(user_id=user_id).order_by(SleepData.date.desc()).all()
return jsonify([e.as_dict() for e in entries]), 200


# GET sleep record by ID (scoped to current user)
@sleep_data_bp.route('/<int:id>', methods=['GET'])
def get_sleep_by_id(id):
user_id = get_current_user_id()
entry = SleepData.query.filter_by(id=id, user_id=user_id).first()
if not entry:
return jsonify({"error": "Sleep record not found for this user"}), 404
return jsonify(entry.as_dict()), 200
10 changes: 10 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
from api.goals import goals_bp
from api.profile import api as profile_api
from api.dashboard import dashboard_bp
from api.body_insight import body_insight_bp
from api.activity import activity_bp
from api.sessions import sessions_bp
from api.sleep_data import sleep_data_bp
from api.sync import sync_bp
from models import db
from dotenv import load_dotenv
from services.firebase_admin import init_firebase_admin
Expand Down Expand Up @@ -73,6 +78,11 @@
app.register_blueprint(goals_bp, url_prefix='/api/goals')
app.register_blueprint(dashboard_bp, url_prefix='/api/dashboard')
app.register_blueprint(profile_api, url_prefix='/api/profile')
app.register_blueprint(sync_bp, url_prefix='/api/synced')
app.register_blueprint(body_insight_bp, url_prefix='/api/body_insight')
app.register_blueprint(activity_bp, url_prefix='/api/activity')
app.register_blueprint(sessions_bp, url_prefix='/api/sessions')
app.register_blueprint(sleep_data_bp, url_prefix='/api/sleep_data')

# Main index route (login + welcome)
@app.route('/', methods=['GET', 'POST'])
Expand Down
1 change: 1 addition & 0 deletions models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
from .activity_summary import ActivitySummary
from .metadata import ActivityMetadata
from .body_insight import BodyInsight
from .sleep_data import SleepData
7 changes: 7 additions & 0 deletions models/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class Activity(db.Model):
elevation_loss = db.Column(db.Float, nullable=False)
max_elevation = db.Column(db.Float, nullable=False)
min_elevation = db.Column(db.Float, nullable=False)
steps = db.Column(db.Integer,nullable=True)
floors = db.Column(db.Integer,nullable=True)
zone_minutes = db.Column(db.Integer, nullable=True)




Expand All @@ -51,6 +55,9 @@ def as_dict(self):
"elevation_loss": self.elevation_loss,
"max_elevation": self.max_elevation,
"min_elevation": self.min_elevation,
"steps" : self.steps,
"floors" : self.floors,
"zone_minutes": self.zone_minutes,
"time_series": [ts.as_dict() for ts in self.time_series]
}

Expand Down
7 changes: 6 additions & 1 deletion models/body_insight.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ class BodyInsight(db.Model):
__tablename__ = 'body_insight'

id = db.Column(db.Integer, primary_key=True)
activity_id = db.Column(db.Integer, db.ForeignKey('activity.id'), nullable=False)
activity_id = db.Column(db.Integer, db.ForeignKey('activity.id'), nullable=False, unique=True)

activity = db.relationship(
'Activity',
backref=db.backref('body_insight', uselist=False) # Makes SQLAlchemy treat it as one-to-one
)

# Performance Metrics
vo2_max = db.Column(db.Float, nullable=True)
lactate_threshold = db.Column(db.Float, nullable=True)
Expand Down
22 changes: 22 additions & 0 deletions models/sleep_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from models import db
from datetime import date

class SleepData(db.Model):
__tablename__ = 'sleep_data'

id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user_profile.id'), nullable=False)
date = db.Column(db.Date, nullable=False) # The night the sleep occurred
duration_minutes = db.Column(db.Integer, nullable=False) # Total sleep in minutes
sleep_score = db.Column(db.Float, nullable=True) # Optional sleep quality score (0-100)

def as_dict(self):
return {
"id": self.id,
"user_id": self.user_id,
"date": self.date.isoformat(),
"duration_minutes": self.duration_minutes,
"hours": self.duration_minutes // 60,
"minutes": self.duration_minutes % 60,
"sleep_score": self.sleep_score
}