mirror of
https://github.com/joaovitoriasilva/endurain.git
synced 2026-01-08 23:38:01 -05:00
BMI logic + fixes
[aux] added auxiliary scripts [backend] added error logic to error printing [backend] rollbacked changes from Datetime to Timestamp [backend] added body composition sync from Garmin [backend] added additional validations for empty .gpx files [backend] add calculate BMI logic [backend] add BMI calculation in migration [backend] added Timezone logic to Strava activity import [frontend] removed block that prevent deleting Strava or GC activity [frontend] added new generic modal for single number input [frontend] replaced modal that retrieves Strava and GC activities for number of days with new generic modal [frontend] added shoes gear in add gear to walk and hike activities [frontend] minor bug fixes
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,6 +1,9 @@
|
||||
# General
|
||||
docker-compose.yml
|
||||
|
||||
# aux folder
|
||||
aux_scripts/.garminconnect
|
||||
|
||||
# Python
|
||||
backend/app/__pycache__/
|
||||
backend/app/*/__pycache__/
|
||||
|
||||
99
aux_scripts/aux_gc_getbcstats.py
Normal file
99
aux_scripts/aux_gc_getbcstats.py
Normal file
@@ -0,0 +1,99 @@
|
||||
from garminconnect import (
|
||||
Garmin,
|
||||
GarminConnectAuthenticationError,
|
||||
)
|
||||
import requests
|
||||
import datetime
|
||||
from garth.exc import GarthHTTPError
|
||||
import os
|
||||
from getpass import getpass
|
||||
|
||||
email = os.getenv("EMAIL")
|
||||
password = os.getenv("PASSWORD")
|
||||
tokenstore = os.getenv("GARMINTOKENS") or ".garminconnect"
|
||||
api = None
|
||||
today = datetime.date.today()
|
||||
|
||||
|
||||
def get_date():
|
||||
"""Get date from user input."""
|
||||
|
||||
return input("Enter date (dd-mm-yyyy): ")
|
||||
|
||||
|
||||
def get_credentials():
|
||||
"""Get user credentials."""
|
||||
|
||||
email = input("Login e-mail: ")
|
||||
password = getpass("Enter password: ")
|
||||
|
||||
return email, password
|
||||
|
||||
|
||||
def get_mfa():
|
||||
"""Get MFA."""
|
||||
|
||||
return input("MFA one-time code: ")
|
||||
|
||||
|
||||
def init_api(email, password):
|
||||
"""Initialize Garmin API with your credentials."""
|
||||
|
||||
try:
|
||||
# Using Oauth1 and OAuth2 token files from directory
|
||||
print(
|
||||
f"Trying to login to Garmin Connect using token data from directory '{tokenstore}'...\n"
|
||||
)
|
||||
|
||||
garmin = Garmin()
|
||||
garmin.login(tokenstore)
|
||||
|
||||
except (FileNotFoundError, GarthHTTPError, GarminConnectAuthenticationError):
|
||||
# Session is expired. You'll need to log in again
|
||||
print(
|
||||
"Login tokens not present, login with your Garmin Connect credentials to generate them.\n"
|
||||
f"They will be stored in '{tokenstore}' for future use.\n"
|
||||
)
|
||||
try:
|
||||
# Ask for credentials if not set as environment variables
|
||||
if not email or not password:
|
||||
email, password = get_credentials()
|
||||
|
||||
garmin = Garmin(
|
||||
email=email, password=password, is_cn=False, prompt_mfa=get_mfa
|
||||
)
|
||||
garmin.login()
|
||||
# Save Oauth1 and Oauth2 token files to directory for next login
|
||||
garmin.garth.dump(tokenstore)
|
||||
print(
|
||||
f"Oauth tokens stored in '{tokenstore}' directory for future use. (first method)\n"
|
||||
)
|
||||
except (
|
||||
FileNotFoundError,
|
||||
GarthHTTPError,
|
||||
GarminConnectAuthenticationError,
|
||||
requests.exceptions.HTTPError,
|
||||
) as err:
|
||||
print(err)
|
||||
return None
|
||||
|
||||
return garmin
|
||||
|
||||
|
||||
date = get_date()
|
||||
date_object = datetime.datetime.strptime(date, "%d-%m-%Y")
|
||||
|
||||
if not api:
|
||||
api = init_api(email, password)
|
||||
|
||||
if api:
|
||||
garmin_bc = api.get_body_composition(date_object, date_object)
|
||||
|
||||
if garmin_bc is None:
|
||||
# Log an informational event if no body composition were found
|
||||
print(
|
||||
f"User: No new Garmin Connect body composition found after {today}: garmin_bc is None"
|
||||
)
|
||||
|
||||
# Return the number of body compositions processed
|
||||
print(garmin_bc)
|
||||
@@ -30,7 +30,9 @@ def get_all_activities(db: Session):
|
||||
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_all_activities: {err}", "error")
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_all_activities: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -63,7 +65,9 @@ def get_user_activities(
|
||||
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_user_activities: {err}", "error")
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_user_activities: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -537,7 +541,9 @@ def get_activities_if_contains_name(name: str, user_id: int, db: Session):
|
||||
) from err
|
||||
|
||||
|
||||
def create_activity(activity: activities_schema.Activity, db: Session):
|
||||
def create_activity(
|
||||
activity: activities_schema.Activity, db: Session
|
||||
) -> activities_schema.Activity:
|
||||
try:
|
||||
# Create a new activity
|
||||
db_activity = activities_models.Activity(
|
||||
@@ -592,7 +598,7 @@ def create_activity(activity: activities_schema.Activity, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in create_activity: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in create_activity: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -638,7 +644,7 @@ def edit_activity(user_id: int, activity: activities_schema.Activity, db: Sessio
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in edit_activity: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in edit_activity: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -664,7 +670,9 @@ def add_gear_to_activity(activity_id: int, gear_id: int, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in add_gear_to_activity: {err}", "error")
|
||||
core_logger.print_to_log(
|
||||
f"Error in add_gear_to_activity: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -725,7 +733,7 @@ def delete_activity(activity_id: int, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in delete_activity: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in delete_activity: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
|
||||
@@ -2,7 +2,7 @@ from sqlalchemy import (
|
||||
Column,
|
||||
Integer,
|
||||
String,
|
||||
TIMESTAMP,
|
||||
DATETIME,
|
||||
ForeignKey,
|
||||
DECIMAL,
|
||||
BigInteger,
|
||||
@@ -38,11 +38,13 @@ class Activity(Base):
|
||||
comment="Gear type (1 - mountain bike, 2 - gravel bike, ...)",
|
||||
)
|
||||
start_time = Column(
|
||||
TIMESTAMP, nullable=False, comment="Activity start date (TIMESTAMP)"
|
||||
DATETIME, nullable=False, comment="Activity start date (DATETIME)"
|
||||
)
|
||||
end_time = Column(TIMESTAMP, nullable=False, comment="Activity end date (TIMESTAMP)")
|
||||
end_time = Column(DATETIME, nullable=False, comment="Activity end date (DATETIME)")
|
||||
timezone = Column(
|
||||
String(length=250), nullable=True, comment="Activity timezone (May include spaces)"
|
||||
String(length=250),
|
||||
nullable=True,
|
||||
comment="Activity timezone (May include spaces)",
|
||||
)
|
||||
total_elapsed_time = Column(
|
||||
DECIMAL(precision=20, scale=10),
|
||||
@@ -66,7 +68,7 @@ class Activity(Base):
|
||||
comment="Activity country (May include spaces)",
|
||||
)
|
||||
created_at = Column(
|
||||
TIMESTAMP, nullable=False, comment="Activity creation date (TIMESTAMP)"
|
||||
DATETIME, nullable=False, comment="Activity creation date (DATETIME)"
|
||||
)
|
||||
elevation_gain = Column(Integer, nullable=True, comment="Elevation gain in meters")
|
||||
elevation_loss = Column(Integer, nullable=True, comment="Elevation loss in meters")
|
||||
@@ -138,4 +140,4 @@ class Activity(Base):
|
||||
"ActivityStreams",
|
||||
back_populates="activity",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -646,10 +646,10 @@ async def delete_activity(
|
||||
os.remove(file)
|
||||
except FileNotFoundError as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"File not found {file}: {err}", "error")
|
||||
core_logger.print_to_log(f"File not found {file}: {err}", "error", exc=err)
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error deleting file {file}: {err}", "error")
|
||||
core_logger.print_to_log(f"Error deleting file {file}: {err}", "error", exc=err)
|
||||
|
||||
# Return success message
|
||||
return {"detail": f"Activity {activity_id} deleted successfully"}
|
||||
|
||||
@@ -234,7 +234,7 @@ def move_file(new_dir: str, new_filename: str, file_path: str):
|
||||
shutil.move(file_path, new_file_path)
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in move_file - {str(err)}", "error")
|
||||
core_logger.print_to_log(f"Error in move_file - {str(err)}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -267,7 +267,7 @@ def parse_file(token_user_id: int, file_extension: str, filename: str) -> dict:
|
||||
raise http_err
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in parse_file - {str(err)}", "error")
|
||||
core_logger.print_to_log(f"Error in parse_file - {str(err)}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
|
||||
@@ -28,7 +28,7 @@ def get_activity_streams(activity_id: int, db: Session):
|
||||
return activity_streams
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_activity_streams: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_activity_streams: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -56,7 +56,7 @@ def get_activity_stream_by_type(activity_id: int, stream_type: int, db: Session)
|
||||
return activity_stream
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_activity_stream_by_type: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_activity_stream_by_type: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -92,7 +92,7 @@ def create_activity_streams(
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger(f"Error in create_activity_streams: {err}", "error")
|
||||
core_logger(f"Error in create_activity_streams: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
"""v0.6.5 migration
|
||||
|
||||
Revision ID: 7ba9b71fe203
|
||||
Revision ID: 542605083c0c
|
||||
Revises: 65a0f1d72997
|
||||
Create Date: 2024-11-25 13:07:30.142799
|
||||
Create Date: 2024-12-09 16:02:43.332696
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '7ba9b71fe203'
|
||||
revision: str = '542605083c0c'
|
||||
down_revision: Union[str, None] = '65a0f1d72997'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
@@ -20,56 +20,52 @@ depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('gear', sa.Column('initial_kms', sa.DECIMAL(precision=11, scale=3), nullable=False, comment='Initial kilometers of the gear'))
|
||||
op.add_column('activities', sa.Column('timezone', sa.String(length=250), nullable=True, comment='Activity timezone (May include spaces)'))
|
||||
op.alter_column('activities', 'start_time',
|
||||
existing_type=sa.DATETIME(),
|
||||
type_=sa.TIMESTAMP(),
|
||||
comment='Activity start date (TIMESTAMP)',
|
||||
existing_type=mysql.DATETIME(),
|
||||
comment='Activity start date (DATETIME)',
|
||||
existing_comment='Activity start date (datetime)',
|
||||
existing_nullable=False)
|
||||
op.alter_column('activities', 'end_time',
|
||||
existing_type=sa.DATETIME(),
|
||||
type_=sa.TIMESTAMP(),
|
||||
comment='Activity end date (TIMESTAMP)',
|
||||
existing_type=mysql.DATETIME(),
|
||||
comment='Activity end date (DATETIME)',
|
||||
existing_comment='Activity end date (datetime)',
|
||||
existing_nullable=False)
|
||||
op.alter_column('activities', 'total_elapsed_time',
|
||||
existing_type=sa.DECIMAL(precision=20, scale=10),
|
||||
existing_type=mysql.DECIMAL(precision=20, scale=10),
|
||||
comment='Activity total elapsed time (s)',
|
||||
existing_comment='Activity total elapsed time (datetime)',
|
||||
existing_nullable=False)
|
||||
op.alter_column('activities', 'total_timer_time',
|
||||
existing_type=sa.DECIMAL(precision=20, scale=10),
|
||||
existing_type=mysql.DECIMAL(precision=20, scale=10),
|
||||
comment='Activity total timer time (s)',
|
||||
existing_comment='Activity total timer time (datetime)',
|
||||
existing_nullable=False)
|
||||
op.alter_column('activities', 'created_at',
|
||||
existing_type=sa.DATETIME(),
|
||||
type_=sa.TIMESTAMP(),
|
||||
comment='Activity creation date (TIMESTAMP)',
|
||||
existing_type=mysql.DATETIME(),
|
||||
comment='Activity creation date (DATETIME)',
|
||||
existing_comment='Activity creation date (datetime)',
|
||||
existing_nullable=False)
|
||||
op.add_column('gear', sa.Column('initial_kms', sa.DECIMAL(precision=11, scale=3), nullable=False, comment='Initial kilometers of the gear'))
|
||||
op.alter_column('gear', 'created_at',
|
||||
existing_type=sa.DATETIME(),
|
||||
type_=sa.TIMESTAMP(),
|
||||
comment='Gear creation date (TIMESTAMP)',
|
||||
existing_type=mysql.DATETIME(),
|
||||
comment='Gear creation date (DATETIME)',
|
||||
existing_comment='Gear creation date (date)',
|
||||
existing_nullable=False)
|
||||
op.add_column('health_data', sa.Column('date', sa.Date(), nullable=True, comment='Health data creation date (date)'))
|
||||
# Copy data from `created_at` to `date`
|
||||
op.execute("""
|
||||
UPDATE health_data
|
||||
SET date = DATE(created_at)
|
||||
""")
|
||||
# Make `date` column non-nullable
|
||||
op.alter_column('health_data', 'date', nullable=False, existing_type=sa.Date())
|
||||
op.add_column('health_data', sa.Column('bmi', sa.DECIMAL(precision=10, scale=2), nullable=True, comment='Body mass index (BMI)'))
|
||||
op.add_column('health_data', sa.Column('body_fat', sa.DECIMAL(precision=10, scale=2), nullable=True, comment='Body fat percentage'))
|
||||
op.add_column('health_data', sa.Column('body_water', sa.DECIMAL(precision=10, scale=2), nullable=True, comment='Body hydration percentage'))
|
||||
op.add_column('health_data', sa.Column('bone_mass', sa.DECIMAL(precision=10, scale=2), nullable=True, comment='Bone mass percentage'))
|
||||
op.add_column('health_data', sa.Column('muscle_mass', sa.DECIMAL(precision=10, scale=2), nullable=True, comment='Muscle mass percentage'))
|
||||
op.add_column('health_data', sa.Column('physique_rating', sa.DECIMAL(precision=10, scale=2), nullable=True, comment='Physique rating'))
|
||||
op.add_column('health_data', sa.Column('visceral_fat', sa.DECIMAL(precision=10, scale=2), nullable=True, comment='Visceral fat rating'))
|
||||
op.add_column('health_data', sa.Column('metabolic_age', sa.DECIMAL(precision=10, scale=2), nullable=True, comment='Metabolic age'))
|
||||
op.add_column('health_data', sa.Column('garminconnect_body_composition_id', sa.String(length=45), nullable=True, comment='Garmin Connect body composition ID'))
|
||||
op.alter_column('health_data', 'created_at',
|
||||
existing_type=sa.DATE(),
|
||||
comment='Health data creation date (date)',
|
||||
existing_comment='Health data creation date (datetime)',
|
||||
existing_nullable=False)
|
||||
op.add_column('activities', sa.Column('timezone', sa.String(length=250), nullable=True, comment='Activity timezone (May include spaces)'))
|
||||
op.drop_index('created_at', table_name='health_data')
|
||||
op.create_unique_constraint(None, 'health_data', ['date'])
|
||||
op.drop_column('health_data', 'created_at')
|
||||
# Add the new entry to the migrations table
|
||||
op.execute("""
|
||||
INSERT INTO migrations (id, name, description, executed) VALUES
|
||||
(2, 'v0.6.5', 'Process timezone for existing activities', false);
|
||||
@@ -79,56 +75,52 @@ def upgrade() -> None:
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('gear', 'initial_kms')
|
||||
op.alter_column('health_data', 'created_at',
|
||||
existing_type=sa.DATE(),
|
||||
comment='Health data creation date (datetime)',
|
||||
existing_comment='Health data creation date (date)',
|
||||
existing_nullable=False)
|
||||
op.add_column('health_data', sa.Column('created_at', sa.DATE(), nullable=True, comment='Health data creation date (datetime)'))
|
||||
# Copy data back from `date` to `created_at`
|
||||
op.execute("""
|
||||
UPDATE health_data
|
||||
SET created_at = date
|
||||
""")
|
||||
# Make `created_at` column non-nullable
|
||||
op.alter_column('health_data', 'created_at', nullable=False, existing_type=sa.Date())
|
||||
op.drop_constraint(None, 'health_data', type_='unique')
|
||||
op.create_index('created_at', 'health_data', ['created_at'], unique=True)
|
||||
op.drop_column('health_data', 'garminconnect_body_composition_id')
|
||||
op.drop_column('health_data', 'metabolic_age')
|
||||
op.drop_column('health_data', 'visceral_fat')
|
||||
op.drop_column('health_data', 'physique_rating')
|
||||
op.drop_column('health_data', 'muscle_mass')
|
||||
op.drop_column('health_data', 'bone_mass')
|
||||
op.drop_column('health_data', 'body_water')
|
||||
op.drop_column('health_data', 'body_fat')
|
||||
op.drop_column('health_data', 'bmi')
|
||||
op.drop_column('health_data', 'date')
|
||||
op.alter_column('gear', 'created_at',
|
||||
existing_type=sa.TIMESTAMP(),
|
||||
type_=sa.DATETIME(),
|
||||
existing_type=mysql.DATETIME(),
|
||||
comment='Gear creation date (date)',
|
||||
existing_comment='Gear creation date (TIMESTAMP)',
|
||||
existing_comment='Gear creation date (DATETIME)',
|
||||
existing_nullable=False)
|
||||
op.drop_column('gear', 'initial_kms')
|
||||
op.alter_column('activities', 'created_at',
|
||||
existing_type=sa.TIMESTAMP(),
|
||||
type_=sa.DATETIME(),
|
||||
existing_type=mysql.DATETIME(),
|
||||
comment='Activity creation date (datetime)',
|
||||
existing_comment='Activity creation date (TIMESTAMP)',
|
||||
existing_comment='Activity creation date (DATETIME)',
|
||||
existing_nullable=False)
|
||||
op.alter_column('activities', 'total_timer_time',
|
||||
existing_type=sa.DECIMAL(precision=20, scale=10),
|
||||
existing_type=mysql.DECIMAL(precision=20, scale=10),
|
||||
comment='Activity total timer time (datetime)',
|
||||
existing_comment='Activity total timer time (s)',
|
||||
existing_nullable=False)
|
||||
op.alter_column('activities', 'total_elapsed_time',
|
||||
existing_type=sa.DECIMAL(precision=20, scale=10),
|
||||
existing_type=mysql.DECIMAL(precision=20, scale=10),
|
||||
comment='Activity total elapsed time (datetime)',
|
||||
existing_comment='Activity total elapsed time (s)',
|
||||
existing_nullable=False)
|
||||
op.alter_column('activities', 'end_time',
|
||||
existing_type=sa.TIMESTAMP(),
|
||||
type_=sa.DATETIME(),
|
||||
existing_type=mysql.DATETIME(),
|
||||
comment='Activity end date (datetime)',
|
||||
existing_comment='Activity end date (TIMESTAMP)',
|
||||
existing_comment='Activity end date (DATETIME)',
|
||||
existing_nullable=False)
|
||||
op.alter_column('activities', 'start_time',
|
||||
existing_type=sa.TIMESTAMP(),
|
||||
type_=sa.DATETIME(),
|
||||
existing_type=mysql.DATETIME(),
|
||||
comment='Activity start date (datetime)',
|
||||
existing_comment='Activity start date (TIMESTAMP)',
|
||||
existing_comment='Activity start date (DATETIME)',
|
||||
existing_nullable=False)
|
||||
op.drop_column('activities', 'timezone')
|
||||
# Remove the entry from the migrations table
|
||||
op.execute("""
|
||||
DELETE FROM migrations
|
||||
WHERE id = 2;
|
||||
@@ -32,12 +32,12 @@ def get_main_logger():
|
||||
return logging.getLogger("main_logger")
|
||||
|
||||
|
||||
def print_to_log(message: str, type: str = "info"):
|
||||
def print_to_log(message: str, type: str = "info", exc: Exception = None):
|
||||
main_logger = get_main_logger()
|
||||
if type == "info":
|
||||
main_logger.info(message)
|
||||
elif type == "error":
|
||||
main_logger.error(message)
|
||||
main_logger.error(message, exc_info=exc is not None)
|
||||
elif type == "warning":
|
||||
main_logger.warning(message)
|
||||
elif type == "debug":
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
#from apscheduler.schedulers.background import BackgroundScheduler
|
||||
# from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
|
||||
import strava.utils as strava_utils
|
||||
import strava.activity_utils as strava_activity_utils
|
||||
|
||||
import garmin.activity_utils as garmin_activity_utils
|
||||
import garmin.health_utils as garmin_health_utils
|
||||
|
||||
import core.logger as core_logger
|
||||
|
||||
#scheduler = BackgroundScheduler()
|
||||
# scheduler = BackgroundScheduler()
|
||||
scheduler = AsyncIOScheduler()
|
||||
|
||||
|
||||
@@ -38,7 +39,7 @@ def start_scheduler():
|
||||
core_logger.print_to_log(
|
||||
"Added scheduler job to retrieve last day Garmin Connect users activities every 60 minutes"
|
||||
)
|
||||
# Add scheduler jobs to retrieve last day activities from Garmin Connect
|
||||
# Add scheduler job to retrieve last day activities from Garmin Connect
|
||||
scheduler.add_job(
|
||||
garmin_activity_utils.retrieve_garminconnect_users_activities_for_days,
|
||||
"interval",
|
||||
@@ -46,6 +47,18 @@ def start_scheduler():
|
||||
args=[1],
|
||||
)
|
||||
|
||||
# Log the addition of the job to retrieve last day Garmin Connect users body composition
|
||||
core_logger.print_to_log(
|
||||
"Added scheduler job to retrieve last day Garmin Connect users body composition every 4 hours (240 minutes)"
|
||||
)
|
||||
# Add scheduler job to retrieve last day body composition from Garmin Connect
|
||||
scheduler.add_job(
|
||||
garmin_health_utils.retrieve_garminconnect_users_bc_for_days,
|
||||
"interval",
|
||||
minutes=240,
|
||||
args=[1],
|
||||
)
|
||||
|
||||
|
||||
def stop_scheduler():
|
||||
scheduler.shutdown()
|
||||
|
||||
@@ -58,7 +58,7 @@ def create_activity_objects(
|
||||
pace = 0
|
||||
|
||||
if session_record["session"]["activity_type"]:
|
||||
activity_type=activities_utils.define_activity_type(
|
||||
activity_type = activities_utils.define_activity_type(
|
||||
session_record["session"]["activity_type"]
|
||||
)
|
||||
|
||||
@@ -82,7 +82,8 @@ def create_activity_objects(
|
||||
else:
|
||||
if session_record["time_offset"]:
|
||||
timezone = find_timezone_name(
|
||||
session_record["time_offset"], session_record["session"]["first_waypoint_time"]
|
||||
session_record["time_offset"],
|
||||
session_record["session"]["first_waypoint_time"],
|
||||
)
|
||||
|
||||
parsed_activity = {
|
||||
@@ -611,7 +612,7 @@ def parse_fit_file(file: str) -> dict:
|
||||
raise http_err
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in parse_fit_file: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in parse_fit_file: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -822,9 +823,9 @@ def find_timezone_name(offset_seconds, reference_date):
|
||||
tz = ZoneInfo(tz_name)
|
||||
if reference_date.utcoffset() is None: # Skip invalid timezones
|
||||
continue
|
||||
|
||||
|
||||
# Get the UTC offset for the reference date
|
||||
utc_offset = reference_date.astimezone(tz).utcoffset()
|
||||
|
||||
|
||||
if utc_offset.total_seconds() == offset_seconds:
|
||||
return tz_name
|
||||
return tz_name
|
||||
|
||||
@@ -25,7 +25,7 @@ def get_all_followers_by_user_id(user_id: int, db: Session):
|
||||
return followers
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_all_followers_by_user_id: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_all_followers_by_user_id: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -53,7 +53,7 @@ def get_accepted_followers_by_user_id(user_id: int, db: Session):
|
||||
return followers
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_accepted_followers_by_user_id: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_accepted_followers_by_user_id: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -78,7 +78,7 @@ def get_all_following_by_user_id(user_id: int, db: Session):
|
||||
return followings
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_all_following_by_user_id: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_all_following_by_user_id: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -106,7 +106,7 @@ def get_accepted_following_by_user_id(user_id: int, db: Session):
|
||||
return followings
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_accepted_following_by_user_id: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_accepted_following_by_user_id: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -136,7 +136,7 @@ def get_follower_for_user_id_and_target_user_id(
|
||||
return follower
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_follower_for_user_id_and_target_user_id: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_follower_for_user_id_and_target_user_id: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -162,7 +162,7 @@ def create_follower(user_id: int, target_user_id: int, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in create_follower: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in create_follower: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -201,7 +201,7 @@ def accept_follower(user_id: int, target_user_id: int, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in accept_follower: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in accept_follower: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -236,7 +236,7 @@ def delete_follower(user_id: int, target_user_id: int, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in delete_follower: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in delete_follower: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
|
||||
@@ -53,9 +53,7 @@ def fetch_and_process_activities(
|
||||
)
|
||||
continue
|
||||
|
||||
garmin_logger.print_to_log(
|
||||
f"User {user_id}: Processing activity {activity_id}"
|
||||
)
|
||||
garmin_logger.print_to_log(f"User {user_id}: Processing activity {activity_id}")
|
||||
|
||||
# Get activity gear
|
||||
activity_gear = garminconnect_client.get_activity_gear(activity_id)
|
||||
|
||||
133
backend/app/garmin/health_utils.py
Normal file
133
backend/app/garmin/health_utils.py
Normal file
@@ -0,0 +1,133 @@
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
from datetime import datetime, timedelta, date
|
||||
import garminconnect
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
import garmin.utils as garmin_utils
|
||||
import garmin.logger as garmin_logger
|
||||
|
||||
import activities.utils as activities_utils
|
||||
import activities.crud as activities_crud
|
||||
|
||||
import health_data.crud as health_data_crud
|
||||
import health_data.schema as health_data_schema
|
||||
|
||||
import users.crud as users_crud
|
||||
|
||||
from core.database import SessionLocal
|
||||
|
||||
|
||||
def fetch_and_process_bc(
|
||||
garminconnect_client: garminconnect.Garmin,
|
||||
start_date: datetime,
|
||||
user_id: int,
|
||||
db: Session,
|
||||
) -> int:
|
||||
# Fetch Garmin Connect body composition after the specified start date
|
||||
garmin_bc = garminconnect_client.get_body_composition(start_date, date.today())
|
||||
|
||||
if garmin_bc is None:
|
||||
# Log an informational event if no body composition were found
|
||||
garmin_logger.print_to_log_and_console(
|
||||
f"User {user_id}: No new Garmin Connect body composition found after {start_date}: garmin_bc is None"
|
||||
)
|
||||
|
||||
# Return 0 to indicate no body composition were processed
|
||||
return 0
|
||||
|
||||
# Process body composition
|
||||
for bc in garmin_bc["dateWeightList"]:
|
||||
health_data = health_data_schema.HealthData(
|
||||
user_id=user_id,
|
||||
date=bc["calendarDate"],
|
||||
weight=bc["weight"] / 1000,
|
||||
bmi=bc["bmi"],
|
||||
#body_fat=bc["bodyFat"],
|
||||
#body_water=bc["bodyWater"],
|
||||
#bone_mass=bc["boneMass"],
|
||||
#muscle_mass=bc["muscleMass"],
|
||||
#physique_rating=bc["physiqueRating"],
|
||||
#visceral_fat=bc["visceralFat"],
|
||||
#metabolic_age=bc["metabolicAge"],
|
||||
garminconnect_body_composition_id=str(bc["samplePk"]),
|
||||
)
|
||||
|
||||
# Check if the body composition is already stored in the database
|
||||
health_data_db = health_data_crud.get_health_data_by_date(
|
||||
user_id, health_data.date, db
|
||||
)
|
||||
|
||||
if health_data_db:
|
||||
health_data.id = health_data_db.id
|
||||
health_data_crud.edit_health_data(user_id, health_data, db)
|
||||
garmin_logger.print_to_log(
|
||||
f"User {user_id}: Body composition edited for date {health_data.date}"
|
||||
)
|
||||
else:
|
||||
health_data_crud.create_health_data(health_data, user_id, db)
|
||||
garmin_logger.print_to_log(
|
||||
f"User {user_id}: Body composition created for date {health_data.date}"
|
||||
)
|
||||
|
||||
# Return the number of body compositions processed
|
||||
return len(garmin_bc)
|
||||
|
||||
|
||||
def retrieve_garminconnect_users_bc_for_days(days: int):
|
||||
# Create a new database session
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
# Get all users
|
||||
users = users_crud.get_all_users(db)
|
||||
finally:
|
||||
# Ensure the session is closed after use
|
||||
db.close()
|
||||
|
||||
# Process the body composition for each user
|
||||
for user in users:
|
||||
get_user_garminconnect_bc_by_days(
|
||||
(datetime.utcnow() - timedelta(days=days)).strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
user.id,
|
||||
)
|
||||
|
||||
|
||||
def get_user_garminconnect_bc_by_days(start_date: datetime, user_id: int):
|
||||
# Create a new database session
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
# Get the user integrations by user ID
|
||||
user_integrations = garmin_utils.fetch_user_integrations_and_validate_token(
|
||||
user_id, db
|
||||
)
|
||||
|
||||
if user_integrations is None:
|
||||
garmin_logger.print_to_log(f"User {user_id}: Garmin Connect not linked")
|
||||
return None
|
||||
|
||||
# Log the start of the body composition processing
|
||||
garmin_logger.print_to_log(
|
||||
f"User {user_id}: Started Garmin Connect body composition processing"
|
||||
)
|
||||
|
||||
# Create a Garmin Connect client with the user's access token
|
||||
garminconnect_client = garmin_utils.login_garminconnect_using_tokens(
|
||||
user_integrations.garminconnect_oauth1,
|
||||
user_integrations.garminconnect_oauth2,
|
||||
)
|
||||
|
||||
# Fetch Garmin Connect body composition after the specified start date
|
||||
num_garminconnect_bc_processed = fetch_and_process_bc(
|
||||
garminconnect_client, start_date, user_id, db
|
||||
)
|
||||
|
||||
# Log an informational event for tracing
|
||||
garmin_logger.print_to_log(
|
||||
f"User {user_id}: {num_garminconnect_bc_processed} Garmin Connect body composition processed"
|
||||
)
|
||||
finally:
|
||||
# Ensure the session is closed after use
|
||||
db.close()
|
||||
@@ -22,12 +22,12 @@ def get_garminconnect_logger():
|
||||
return logging.getLogger("garminconnect_logger")
|
||||
|
||||
|
||||
def print_to_log(message: str, type: str = "info"):
|
||||
def print_to_log(message: str, type: str = "info", exc: Exception = None):
|
||||
garminconnect_logger = get_garminconnect_logger()
|
||||
if type == "info":
|
||||
garminconnect_logger.info(message)
|
||||
elif type == "error":
|
||||
garminconnect_logger.error(message)
|
||||
garminconnect_logger.error(message, exc_info=exc is not None)
|
||||
elif type == "warning":
|
||||
garminconnect_logger.warning(message)
|
||||
elif type == "debug":
|
||||
|
||||
@@ -26,7 +26,7 @@ def get_gear_user_by_id(gear_id: int, db: Session) -> gears_schema.Gear | None:
|
||||
return gear
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_gear_user_by_id: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_gear_user_by_id: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -61,7 +61,7 @@ def get_gear_users_with_pagination(
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_gear_users_with_pagination: {err}", "error"
|
||||
f"Error in get_gear_users_with_pagination: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -87,7 +87,7 @@ def get_gear_user(user_id: int, db: Session) -> list[gears_schema.Gear] | None:
|
||||
return gears
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_gear_user: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_gear_user: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -124,7 +124,7 @@ def get_gear_user_by_nickname(
|
||||
return gears
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_gear_user_by_nickname: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_gear_user_by_nickname: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -155,7 +155,7 @@ def get_gear_by_type_and_user(gear_type: int, user_id: int, db: Session):
|
||||
return gear
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_gear_by_type_and_user: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_gear_by_type_and_user: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -189,7 +189,7 @@ def get_gear_by_strava_id_from_user_id(
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_gear_by_strava_id_from_user_id: {err}", "error"
|
||||
f"Error in get_gear_by_strava_id_from_user_id: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -224,7 +224,7 @@ def get_gear_by_garminconnect_id_from_user_id(
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_gear_by_garminconnect_id_from_user_id: {err}", "error"
|
||||
f"Error in get_gear_by_garminconnect_id_from_user_id: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -263,7 +263,7 @@ def create_multiple_gears(gears: list[gears_schema.Gear], user_id: int, db: Sess
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in create_multiple_gears: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in create_multiple_gears: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -298,7 +298,7 @@ def create_gear(gear: gears_schema.Gear, user_id: int, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in create_gear: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in create_gear: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -339,7 +339,7 @@ def edit_gear(gear_id: int, gear: gears_schema.Gear, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in edit_gear: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in edit_gear: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -367,7 +367,7 @@ def delete_gear(gear_id: int, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in delete_gear: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in delete_gear: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -397,7 +397,7 @@ def delete_all_strava_gear_for_user(user_id: int, db: Session):
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in delete_all_strava_gear_for_user: {err}", "error"
|
||||
f"Error in delete_all_strava_gear_for_user: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
@@ -429,7 +429,7 @@ def delete_all_garminconnect_gear_for_user(user_id: int, db: Session):
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in delete_all_garminconnect_gear_for_user: {err}", "error"
|
||||
f"Error in delete_all_garminconnect_gear_for_user: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
|
||||
@@ -2,7 +2,7 @@ from sqlalchemy import (
|
||||
Column,
|
||||
Integer,
|
||||
String,
|
||||
TIMESTAMP,
|
||||
DATETIME,
|
||||
ForeignKey,
|
||||
DECIMAL,
|
||||
)
|
||||
@@ -37,7 +37,7 @@ class Gear(Base):
|
||||
nullable=False,
|
||||
comment="User ID that the gear belongs to",
|
||||
)
|
||||
created_at = Column(TIMESTAMP, nullable=False, comment="Gear creation date (TIMESTAMP)")
|
||||
created_at = Column(DATETIME, nullable=False, comment="Gear creation date (DATETIME)")
|
||||
is_active = Column(
|
||||
Integer, nullable=False, comment="Is gear active (0 - not active, 1 - active)"
|
||||
)
|
||||
|
||||
@@ -68,137 +68,150 @@ def parse_gpx_file(file: str, user_id: int) -> dict:
|
||||
with open(file, "r") as gpx_file:
|
||||
gpx = gpxpy.parse(gpx_file)
|
||||
|
||||
# Iterate over tracks in the GPX file
|
||||
for track in gpx.tracks:
|
||||
# Set activity name and type if available
|
||||
activity_name = track.name if track.name else "Workout"
|
||||
activity_type = track.type if track.type else "Workout"
|
||||
if gpx.tracks:
|
||||
# Iterate over tracks in the GPX file
|
||||
for track in gpx.tracks:
|
||||
# Set activity name and type if available
|
||||
activity_name = track.name if track.name else "Workout"
|
||||
activity_type = track.type if track.type else "Workout"
|
||||
|
||||
# Iterate over segments in each track
|
||||
for segment in track.segments:
|
||||
# Iterate over points in each segment
|
||||
for point in segment.points:
|
||||
# Extract latitude and longitude from the point
|
||||
latitude, longitude = point.latitude, point.longitude
|
||||
if track.segments:
|
||||
# Iterate over segments in each track
|
||||
for segment in track.segments:
|
||||
# Iterate over points in each segment
|
||||
for point in segment.points:
|
||||
# Extract latitude and longitude from the point
|
||||
latitude, longitude = point.latitude, point.longitude
|
||||
|
||||
# Calculate distance between waypoints
|
||||
if prev_latitude is not None and prev_longitude is not None:
|
||||
distance += geodesic(
|
||||
(prev_latitude, prev_longitude), (latitude, longitude)
|
||||
).meters
|
||||
# Calculate distance between waypoints
|
||||
if prev_latitude is not None and prev_longitude is not None:
|
||||
distance += geodesic(
|
||||
(prev_latitude, prev_longitude),
|
||||
(latitude, longitude),
|
||||
).meters
|
||||
|
||||
# Extract elevation, time, and location details
|
||||
elevation, time = point.elevation, point.time
|
||||
# Extract elevation, time, and location details
|
||||
elevation, time = point.elevation, point.time
|
||||
|
||||
if elevation != 0:
|
||||
is_elevation_set = True
|
||||
if elevation != 0:
|
||||
is_elevation_set = True
|
||||
|
||||
if first_waypoint_time is None:
|
||||
first_waypoint_time = point.time
|
||||
if first_waypoint_time is None:
|
||||
first_waypoint_time = point.time
|
||||
|
||||
if process_one_time_fields == 0:
|
||||
# Use geocoding API to get city, town, and country based on coordinates
|
||||
location_data = (
|
||||
activities_utils.location_based_on_coordinates(
|
||||
latitude, longitude
|
||||
if process_one_time_fields == 0:
|
||||
# Use geocoding API to get city, town, and country based on coordinates
|
||||
location_data = (
|
||||
activities_utils.location_based_on_coordinates(
|
||||
latitude, longitude
|
||||
)
|
||||
)
|
||||
|
||||
# Extract city, town, and country from location data
|
||||
if location_data:
|
||||
city = location_data["city"]
|
||||
town = location_data["town"]
|
||||
country = location_data["country"]
|
||||
|
||||
process_one_time_fields = 1
|
||||
|
||||
# Extract heart rate, cadence, and power data from point extensions
|
||||
heart_rate, cadence, power = 0, 0, 0
|
||||
|
||||
if point.extensions:
|
||||
# Iterate through each extension element
|
||||
for extension in point.extensions:
|
||||
if extension.tag.endswith("TrackPointExtension"):
|
||||
hr_element = extension.find(
|
||||
".//{http://www.garmin.com/xmlschemas/TrackPointExtension/v1}hr"
|
||||
)
|
||||
if hr_element is not None:
|
||||
heart_rate = hr_element.text
|
||||
cad_element = extension.find(
|
||||
".//{http://www.garmin.com/xmlschemas/TrackPointExtension/v1}cad"
|
||||
)
|
||||
if cad_element is not None:
|
||||
cadence = cad_element.text
|
||||
elif extension.tag.endswith("power"):
|
||||
# Extract 'power' value
|
||||
power = extension.text
|
||||
|
||||
# Check if heart rate, cadence, power are set
|
||||
if heart_rate != 0:
|
||||
is_heart_rate_set = True
|
||||
|
||||
if cadence != 0:
|
||||
is_cadence_set = True
|
||||
|
||||
if power != 0:
|
||||
is_power_set = True
|
||||
else:
|
||||
power = None
|
||||
|
||||
# Calculate instant speed, pace, and update waypoint arrays
|
||||
instant_speed = activities_utils.calculate_instant_speed(
|
||||
last_waypoint_time,
|
||||
time,
|
||||
latitude,
|
||||
longitude,
|
||||
prev_latitude,
|
||||
prev_longitude,
|
||||
)
|
||||
)
|
||||
|
||||
# Extract city, town, and country from location data
|
||||
if location_data:
|
||||
city = location_data["city"]
|
||||
town = location_data["town"]
|
||||
country = location_data["country"]
|
||||
# Calculate instance pace
|
||||
instant_pace = 0
|
||||
if instant_speed > 0:
|
||||
instant_pace = 1 / instant_speed
|
||||
is_velocity_set = True
|
||||
|
||||
process_one_time_fields = 1
|
||||
timestamp = time.strftime("%Y-%m-%dT%H:%M:%S")
|
||||
|
||||
# Extract heart rate, cadence, and power data from point extensions
|
||||
heart_rate, cadence, power = 0, 0, 0
|
||||
|
||||
if point.extensions:
|
||||
# Iterate through each extension element
|
||||
for extension in point.extensions:
|
||||
if extension.tag.endswith("TrackPointExtension"):
|
||||
hr_element = extension.find(
|
||||
".//{http://www.garmin.com/xmlschemas/TrackPointExtension/v1}hr"
|
||||
# Append waypoint data to respective arrays
|
||||
if latitude is not None and longitude is not None:
|
||||
lat_lon_waypoints.append(
|
||||
{
|
||||
"time": timestamp,
|
||||
"lat": latitude,
|
||||
"lon": longitude,
|
||||
}
|
||||
)
|
||||
if hr_element is not None:
|
||||
heart_rate = hr_element.text
|
||||
cad_element = extension.find(
|
||||
".//{http://www.garmin.com/xmlschemas/TrackPointExtension/v1}cad"
|
||||
)
|
||||
if cad_element is not None:
|
||||
cadence = cad_element.text
|
||||
elif extension.tag.endswith("power"):
|
||||
# Extract 'power' value
|
||||
power = extension.text
|
||||
is_lat_lon_set = True
|
||||
|
||||
# Check if heart rate, cadence, power are set
|
||||
if heart_rate != 0:
|
||||
is_heart_rate_set = True
|
||||
activities_utils.append_if_not_none(
|
||||
ele_waypoints, timestamp, elevation, "ele"
|
||||
)
|
||||
activities_utils.append_if_not_none(
|
||||
hr_waypoints, timestamp, heart_rate, "hr"
|
||||
)
|
||||
activities_utils.append_if_not_none(
|
||||
cad_waypoints, timestamp, cadence, "cad"
|
||||
)
|
||||
activities_utils.append_if_not_none(
|
||||
power_waypoints, timestamp, power, "power"
|
||||
)
|
||||
activities_utils.append_if_not_none(
|
||||
vel_waypoints, timestamp, instant_speed, "vel"
|
||||
)
|
||||
activities_utils.append_if_not_none(
|
||||
pace_waypoints, timestamp, instant_pace, "pace"
|
||||
)
|
||||
|
||||
if cadence != 0:
|
||||
is_cadence_set = True
|
||||
|
||||
if power != 0:
|
||||
is_power_set = True
|
||||
else:
|
||||
power = None
|
||||
|
||||
# Calculate instant speed, pace, and update waypoint arrays
|
||||
instant_speed = activities_utils.calculate_instant_speed(
|
||||
last_waypoint_time,
|
||||
time,
|
||||
latitude,
|
||||
longitude,
|
||||
prev_latitude,
|
||||
prev_longitude,
|
||||
)
|
||||
|
||||
# Calculate instance pace
|
||||
instant_pace = 0
|
||||
if instant_speed > 0:
|
||||
instant_pace = 1 / instant_speed
|
||||
is_velocity_set = True
|
||||
|
||||
timestamp = time.strftime("%Y-%m-%dT%H:%M:%S")
|
||||
|
||||
# Append waypoint data to respective arrays
|
||||
if latitude is not None and longitude is not None:
|
||||
lat_lon_waypoints.append(
|
||||
{
|
||||
"time": timestamp,
|
||||
"lat": latitude,
|
||||
"lon": longitude,
|
||||
}
|
||||
)
|
||||
is_lat_lon_set = True
|
||||
|
||||
activities_utils.append_if_not_none(
|
||||
ele_waypoints, timestamp, elevation, "ele"
|
||||
)
|
||||
activities_utils.append_if_not_none(
|
||||
hr_waypoints, timestamp, heart_rate, "hr"
|
||||
)
|
||||
activities_utils.append_if_not_none(
|
||||
cad_waypoints, timestamp, cadence, "cad"
|
||||
)
|
||||
activities_utils.append_if_not_none(
|
||||
power_waypoints, timestamp, power, "power"
|
||||
)
|
||||
activities_utils.append_if_not_none(
|
||||
vel_waypoints, timestamp, instant_speed, "vel"
|
||||
)
|
||||
activities_utils.append_if_not_none(
|
||||
pace_waypoints, timestamp, instant_pace, "pace"
|
||||
)
|
||||
|
||||
# Update previous latitude, longitude, and last waypoint time
|
||||
prev_latitude, prev_longitude, last_waypoint_time = (
|
||||
latitude,
|
||||
longitude,
|
||||
time,
|
||||
# Update previous latitude, longitude, and last waypoint time
|
||||
prev_latitude, prev_longitude, last_waypoint_time = (
|
||||
latitude,
|
||||
longitude,
|
||||
time,
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid GPX file - no segments found in the GPX file",
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid GPX file - no tracks found in the GPX file",
|
||||
)
|
||||
|
||||
# Calculate elevation gain/loss, pace, average speed, and average power
|
||||
if ele_waypoints:
|
||||
@@ -244,9 +257,9 @@ def parse_gpx_file(file: str, user_id: int) -> dict:
|
||||
if activity_type != 3 and activity_type != 7:
|
||||
if is_lat_lon_set:
|
||||
timezone = tf.timezone_at(
|
||||
lat=lat_lon_waypoints[0]["lat"],
|
||||
lng=lat_lon_waypoints[0]["lon"],
|
||||
)
|
||||
lat=lat_lon_waypoints[0]["lat"],
|
||||
lng=lat_lon_waypoints[0]["lon"],
|
||||
)
|
||||
|
||||
# Create an Activity object with parsed data
|
||||
activity = activities_schema.Activity(
|
||||
@@ -268,12 +281,12 @@ def parse_gpx_file(file: str, user_id: int) -> dict:
|
||||
average_speed=avg_speed,
|
||||
max_speed=max_speed,
|
||||
average_power=round(avg_power) if avg_power else None,
|
||||
max_power=max_power,
|
||||
max_power=round(max_power) if max_power else None,
|
||||
normalized_power=round(np) if np else None,
|
||||
average_hr=round(avg_hr) if avg_hr else None,
|
||||
max_hr=max_hr,
|
||||
max_hr=round(max_hr) if max_hr else None,
|
||||
average_cad=round(avg_cadence) if avg_cadence else None,
|
||||
max_cad=max_cadence,
|
||||
max_cad=round(max_cadence) if max_cadence else None,
|
||||
calories=calories,
|
||||
visibility=visibility,
|
||||
strava_gear_id=None,
|
||||
@@ -302,7 +315,9 @@ def parse_gpx_file(file: str, user_id: int) -> dict:
|
||||
raise http_err
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in parse_gpx_file - {str(err)}", "error")
|
||||
core_logger.print_to_log(
|
||||
f"Error in parse_gpx_file - {str(err)}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
|
||||
@@ -7,10 +7,38 @@ import users.crud as users_crud
|
||||
|
||||
import health_data.schema as health_data_schema
|
||||
import health_data.models as health_data_models
|
||||
import health_data.utils as health_data_utils
|
||||
|
||||
import core.logger as core_logger
|
||||
|
||||
|
||||
def get_all_health_data(db: Session):
|
||||
try:
|
||||
# Get the health_data from the database
|
||||
health_data = (
|
||||
db.query(health_data_models.HealthData)
|
||||
.order_by(desc(health_data_models.HealthData.date))
|
||||
.all()
|
||||
)
|
||||
|
||||
# Check if there are health_data if not return None
|
||||
if not health_data:
|
||||
return None
|
||||
|
||||
# Return the health_data
|
||||
return health_data
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_all_health_data: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Internal Server Error",
|
||||
) from err
|
||||
|
||||
|
||||
def get_health_data_number(user_id: int, db: Session):
|
||||
try:
|
||||
# Get the number of health_data from the database
|
||||
@@ -21,7 +49,9 @@ def get_health_data_number(user_id: int, db: Session):
|
||||
)
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_health_data_number: {err}", "error")
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_health_data_number: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -35,7 +65,7 @@ def get_health_data(user_id: int, db: Session):
|
||||
health_data = (
|
||||
db.query(health_data_models.HealthData)
|
||||
.filter(health_data_models.HealthData.user_id == user_id)
|
||||
.order_by(desc(health_data_models.HealthData.created_at))
|
||||
.order_by(desc(health_data_models.HealthData.date))
|
||||
.all()
|
||||
)
|
||||
|
||||
@@ -47,7 +77,7 @@ def get_health_data(user_id: int, db: Session):
|
||||
return health_data
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_health_data: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_health_data: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -63,7 +93,7 @@ def get_health_data_with_pagination(
|
||||
health_data = (
|
||||
db.query(health_data_models.HealthData)
|
||||
.filter(health_data_models.HealthData.user_id == user_id)
|
||||
.order_by(desc(health_data_models.HealthData.created_at))
|
||||
.order_by(desc(health_data_models.HealthData.date))
|
||||
.offset((page_number - 1) * num_records)
|
||||
.limit(num_records)
|
||||
.all()
|
||||
@@ -78,7 +108,7 @@ def get_health_data_with_pagination(
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_health_data_with_pagination: {err}", "error"
|
||||
f"Error in get_health_data_with_pagination: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -87,13 +117,13 @@ def get_health_data_with_pagination(
|
||||
) from err
|
||||
|
||||
|
||||
def get_health_data_by_created_at(user_id: int, created_at: str, db: Session):
|
||||
def get_health_data_by_date(user_id: int, date: str, db: Session):
|
||||
try:
|
||||
# Get the health_data from the database
|
||||
health_data = (
|
||||
db.query(health_data_models.HealthData)
|
||||
.filter(
|
||||
health_data_models.HealthData.created_at == created_at,
|
||||
health_data_models.HealthData.date == date,
|
||||
health_data_models.HealthData.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
@@ -108,7 +138,7 @@ def get_health_data_by_created_at(user_id: int, created_at: str, db: Session):
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_health_data_by_created_at: {err}", "error"
|
||||
f"Error in get_health_data_by_date: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -121,29 +151,29 @@ def create_health_data(
|
||||
health_data: health_data_schema.HealthData, user_id: int, db: Session
|
||||
):
|
||||
try:
|
||||
# Check if date is None
|
||||
if health_data.date is None:
|
||||
# Set the date to the current date
|
||||
health_data.date = func.now()
|
||||
|
||||
# Check if bmi is None
|
||||
if health_data.bmi is None:
|
||||
# Get the user from the database
|
||||
user = users_crud.get_user_by_id(user_id, db)
|
||||
|
||||
# Check if user is not None
|
||||
if user is not None:
|
||||
# Calculate the bmi
|
||||
health_data.bmi = health_data.weight / ((user.height / 100) ** 2)
|
||||
health_data = health_data_utils.calculate_bmi(health_data, user_id, db)
|
||||
|
||||
# Create a new health_data
|
||||
db_health_data = health_data_models.HealthData(
|
||||
user_id=user_id,
|
||||
created_at=func.now(),
|
||||
date=health_data.date,
|
||||
weight=health_data.weight,
|
||||
bmi=health_data.bmi,
|
||||
body_fat=health_data.body_fat,
|
||||
body_water=health_data.body_water,
|
||||
bone_mass=health_data.bone_mass,
|
||||
muscle_mass=health_data.muscle_mass,
|
||||
physique_rating=health_data.physique_rating,
|
||||
visceral_fat=health_data.visceral_fat,
|
||||
metabolic_age=health_data.metabolic_age,
|
||||
# body_fat=health_data.body_fat,
|
||||
# body_water=health_data.body_water,
|
||||
# bone_mass=health_data.bone_mass,
|
||||
# muscle_mass=health_data.muscle_mass,
|
||||
# physique_rating=health_data.physique_rating,
|
||||
# visceral_fat=health_data.visceral_fat,
|
||||
# metabolic_age=health_data.metabolic_age,
|
||||
garminconnect_body_composition_id=health_data.garminconnect_body_composition_id,
|
||||
)
|
||||
|
||||
# Add the health_data to the database
|
||||
@@ -170,7 +200,63 @@ def create_health_data(
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in create_health_data: {err}", "error")
|
||||
core_logger.print_to_log(
|
||||
f"Error in create_health_data: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Internal Server Error",
|
||||
) from err
|
||||
|
||||
|
||||
def edit_health_data(user_id, health_data: health_data_schema.HealthData, db: Session):
|
||||
try:
|
||||
# Get the health_data from the database
|
||||
db_health_data = (
|
||||
db.query(health_data_models.HealthData)
|
||||
.filter(
|
||||
health_data_models.HealthData.id == health_data.id,
|
||||
health_data_models.HealthData.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if db_health_data is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Health data not found",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# Check if bmi is None
|
||||
if health_data.bmi is None:
|
||||
health_data = health_data_utils.calculate_bmi(health_data, user_id, db)
|
||||
|
||||
# Dictionary of the fields to update if they are not None
|
||||
health_data_data = health_data.dict(exclude_unset=True)
|
||||
# Iterate over the fields and update the db_health_data dynamically
|
||||
for key, value in health_data_data.items():
|
||||
setattr(db_health_data, key, value)
|
||||
|
||||
# Commit the transaction
|
||||
db.commit()
|
||||
except IntegrityError as integrity_error:
|
||||
# Rollback the transaction
|
||||
db.rollback()
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Duplicate entry error. Check if date selected is not already added",
|
||||
) from integrity_error
|
||||
except Exception as err:
|
||||
# Rollback the transaction
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in edit_health_data: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -185,7 +271,7 @@ def create_health_weight_data(
|
||||
# Create a new health_data
|
||||
db_health_data = health_data_models.HealthData(
|
||||
user_id=user_id,
|
||||
created_at=health_data.created_at,
|
||||
date=health_data.date,
|
||||
weight=health_data.weight,
|
||||
)
|
||||
|
||||
@@ -212,7 +298,9 @@ def create_health_weight_data(
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in create_health_weight_data: {err}", "error")
|
||||
core_logger.print_to_log(
|
||||
f"Error in create_health_weight_data: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -236,9 +324,9 @@ def edit_health_weight_data(health_data: health_data_schema.HealthData, db: Sess
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# Update the user
|
||||
if health_data.created_at is not None:
|
||||
db_health_data.created_at = health_data.created_at
|
||||
# Update the health_data
|
||||
if health_data.date is not None:
|
||||
db_health_data.date = health_data.date
|
||||
if health_data.weight is not None:
|
||||
db_health_data.weight = health_data.weight
|
||||
|
||||
@@ -258,7 +346,9 @@ def edit_health_weight_data(health_data: health_data_schema.HealthData, db: Sess
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in edit_health_weight_data: {err}", "error")
|
||||
core_logger.print_to_log(
|
||||
f"Error in edit_health_weight_data: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -279,7 +369,7 @@ def delete_health_weight_data(health_data_id: int, user_id: int, db: Session):
|
||||
.delete()
|
||||
)
|
||||
|
||||
# Check if the gear was found and deleted
|
||||
# Check if the health_data was found and deleted
|
||||
if num_deleted == 0:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -293,7 +383,9 @@ def delete_health_weight_data(health_data_id: int, user_id: int, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in delete_health_weight_data: {err}", "error")
|
||||
core_logger.print_to_log(
|
||||
f"Error in delete_health_weight_data: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
|
||||
@@ -21,7 +21,7 @@ class HealthData(Base):
|
||||
index=True,
|
||||
comment="User ID that the health_data belongs",
|
||||
)
|
||||
created_at = Column(
|
||||
date = Column(
|
||||
Date,
|
||||
nullable=False,
|
||||
unique=True,
|
||||
@@ -37,41 +37,41 @@ class HealthData(Base):
|
||||
nullable=True,
|
||||
comment="Body mass index (BMI)",
|
||||
)
|
||||
body_fat = Column(
|
||||
DECIMAL(precision=10, scale=2),
|
||||
nullable=True,
|
||||
comment="Body fat percentage",
|
||||
)
|
||||
body_water = Column(
|
||||
DECIMAL(precision=10, scale=2),
|
||||
nullable=True,
|
||||
comment="Body hydration percentage",
|
||||
)
|
||||
bone_mass = Column(
|
||||
DECIMAL(precision=10, scale=2),
|
||||
nullable=True,
|
||||
comment="Bone mass percentage",
|
||||
)
|
||||
muscle_mass = Column(
|
||||
DECIMAL(precision=10, scale=2),
|
||||
nullable=True,
|
||||
comment="Muscle mass percentage",
|
||||
)
|
||||
physique_rating = Column(
|
||||
DECIMAL(precision=10, scale=2),
|
||||
nullable=True,
|
||||
comment="Physique rating",
|
||||
)
|
||||
visceral_fat = Column(
|
||||
DECIMAL(precision=10, scale=2),
|
||||
nullable=True,
|
||||
comment="Visceral fat rating",
|
||||
)
|
||||
metabolic_age = Column(
|
||||
DECIMAL(precision=10, scale=2),
|
||||
nullable=True,
|
||||
comment="Metabolic age",
|
||||
)
|
||||
# body_fat = Column(
|
||||
# DECIMAL(precision=10, scale=2),
|
||||
# nullable=True,
|
||||
# comment="Body fat percentage",
|
||||
# )
|
||||
# body_water = Column(
|
||||
# DECIMAL(precision=10, scale=2),
|
||||
# nullable=True,
|
||||
# comment="Body hydration percentage",
|
||||
# )
|
||||
# bone_mass = Column(
|
||||
# DECIMAL(precision=10, scale=2),
|
||||
# nullable=True,
|
||||
# comment="Bone mass percentage",
|
||||
# )
|
||||
# muscle_mass = Column(
|
||||
# DECIMAL(precision=10, scale=2),
|
||||
# nullable=True,
|
||||
# comment="Muscle mass percentage",
|
||||
# )
|
||||
# physique_rating = Column(
|
||||
# DECIMAL(precision=10, scale=2),
|
||||
# nullable=True,
|
||||
# comment="Physique rating",
|
||||
# )
|
||||
# visceral_fat = Column(
|
||||
# DECIMAL(precision=10, scale=2),
|
||||
# nullable=True,
|
||||
# comment="Visceral fat rating",
|
||||
# )
|
||||
# metabolic_age = Column(
|
||||
# DECIMAL(precision=10, scale=2),
|
||||
# nullable=True,
|
||||
# comment="Metabolic age",
|
||||
# )
|
||||
garminconnect_body_composition_id = Column(
|
||||
String(length=45), nullable=True, comment="Garmin Connect body composition ID"
|
||||
)
|
||||
|
||||
@@ -121,8 +121,8 @@ async def create_health_weight_data(
|
||||
Depends(core_database.get_db),
|
||||
],
|
||||
):
|
||||
health_for_date = health_data_crud.get_health_data_by_created_at(
|
||||
token_user_id, health_data.created_at, db
|
||||
health_for_date = health_data_crud.get_health_data_by_date(
|
||||
token_user_id, health_data.date, db
|
||||
)
|
||||
if health_for_date:
|
||||
if health_for_date.weight is None:
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import date
|
||||
from datetime import date as datetime_date
|
||||
|
||||
class HealthData(BaseModel):
|
||||
id: int | None = None
|
||||
user_id: int | None = None
|
||||
created_at: date | None = None
|
||||
date: datetime_date | None = None
|
||||
weight: float | None = None
|
||||
bmi: float | None = None
|
||||
body_fat: float | None = None
|
||||
body_water: float | None = None
|
||||
bone_mass: float | None = None
|
||||
muscle_mass: float | None = None
|
||||
physique_rating: float | None = None
|
||||
visceral_fat: float | None = None
|
||||
metabolic_age: float | None = None
|
||||
#body_fat: float | None = None
|
||||
#body_water: float | None = None
|
||||
#bone_mass: float | None = None
|
||||
#muscle_mass: float | None = None
|
||||
#physique_rating: float | None = None
|
||||
#visceral_fat: float | None = None
|
||||
#metabolic_age: float | None = None
|
||||
garminconnect_body_composition_id: str | None = None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
17
backend/app/health_data/utils.py
Normal file
17
backend/app/health_data/utils.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
import users.crud as users_crud
|
||||
|
||||
import health_data.schema as health_data_schema
|
||||
|
||||
def calculate_bmi(health_data: health_data_schema.HealthData, user_id: int, db: Session):
|
||||
# Get the user from the database
|
||||
user = users_crud.get_user_by_id(user_id, db)
|
||||
|
||||
# Check if user is not None and user height is not None
|
||||
if user is not None and user.height is not None:
|
||||
# Calculate the bmi
|
||||
health_data.bmi = health_data.weight / ((user.height / 100) ** 2)
|
||||
|
||||
# return the health data
|
||||
return health_data
|
||||
@@ -26,7 +26,7 @@ def get_user_health_targets(user_id: int, db: Session):
|
||||
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_user_health_targets: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_user_health_targets: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -68,7 +68,7 @@ def create_health_targets(user_id: int, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in create_health_targets: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in create_health_targets: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
|
||||
@@ -15,12 +15,13 @@ import core.migrations as core_migrations
|
||||
|
||||
import garmin.logger as garmin_logger
|
||||
import garmin.activity_utils as garmin_activity_utils
|
||||
import garmin.health_utils as garmin_health_utils
|
||||
|
||||
import strava.activity_utils as strava_activity_utils
|
||||
import strava.logger as strava_logger
|
||||
|
||||
import migrations.logger as migrations_logger
|
||||
|
||||
import strava.logger as strava_logger
|
||||
|
||||
from core.routes import router as api_router
|
||||
|
||||
|
||||
@@ -41,13 +42,19 @@ def startup_event():
|
||||
# Create a scheduler to run background jobs
|
||||
core_scheduler.start_scheduler()
|
||||
|
||||
# Retrieve last day activities from Garmin Connect
|
||||
# Retrieve last day activities from Garmin Connect and Strava
|
||||
core_logger.print_to_log_and_console(
|
||||
"Retrieving last day activities from Garmin Connect and Strava on startup"
|
||||
)
|
||||
garmin_activity_utils.retrieve_garminconnect_users_activities_for_days(1)
|
||||
strava_activity_utils.retrieve_strava_users_activities_for_days(1)
|
||||
|
||||
# Retrieve last day body composition from Garmin Connect
|
||||
core_logger.print_to_log_and_console(
|
||||
"Retrieving last day body composition from Garmin Connect on startup"
|
||||
)
|
||||
garmin_health_utils.retrieve_garminconnect_users_bc_for_days(1)
|
||||
|
||||
|
||||
def shutdown_event():
|
||||
# Log the shutdown event
|
||||
|
||||
@@ -23,7 +23,7 @@ def get_migrations_not_executed(db: Session):
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log_and_console(f"Error in get_migrations_not_executed. See migrations log for more information", "error")
|
||||
migrations_logger.print_to_log(f"Error in get_migrations_not_executed: {err}", "error")
|
||||
migrations_logger.print_to_log(f"Error in get_migrations_not_executed: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -58,7 +58,7 @@ def set_migration_as_executed(migration_id: int, db: Session):
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log_and_console(f"Error in set_migration_as_executed. See migrations log for more information", "error")
|
||||
migrations_logger.print_to_log(f"Error in set_migration_as_executed: {err}", "error")
|
||||
migrations_logger.print_to_log(f"Error in set_migration_as_executed: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
|
||||
@@ -15,6 +15,9 @@ import activity_streams.crud as activity_streams_crud
|
||||
import migrations.crud as migrations_crud
|
||||
import migrations.logger as migrations_logger
|
||||
|
||||
import health_data.crud as health_data_crud
|
||||
import health_data.utils as health_data_utils
|
||||
|
||||
import core.logger as core_logger
|
||||
|
||||
|
||||
@@ -56,7 +59,7 @@ def process_migration_1(db: Session):
|
||||
activities = activities_crud.get_all_activities(db)
|
||||
except Exception as err:
|
||||
migrations_logger.print_to_log(
|
||||
f"Migration 1 - Error fetching activities: {err}", "error"
|
||||
f"Migration 1 - Error fetching activities: {err}", "error", exc=err
|
||||
)
|
||||
return
|
||||
|
||||
@@ -95,6 +98,7 @@ def process_migration_1(db: Session):
|
||||
migrations_logger.print_to_log(
|
||||
f"Migration 1 - Failed to fetch streams for activity {activity.id}: {err}",
|
||||
"warning",
|
||||
exc=err,
|
||||
)
|
||||
activities_processed_with_no_errors = False
|
||||
continue
|
||||
@@ -160,7 +164,9 @@ def process_migration_1(db: Session):
|
||||
)
|
||||
|
||||
migrations_logger.print_to_log(
|
||||
f"Migration 1 - Failed to process activity {activity.id}: {err}", "error"
|
||||
f"Migration 1 - Failed to process activity {activity.id}: {err}",
|
||||
"error",
|
||||
exc=err,
|
||||
)
|
||||
|
||||
# Mark migration as executed
|
||||
@@ -174,7 +180,9 @@ def process_migration_1(db: Session):
|
||||
)
|
||||
|
||||
migrations_logger.print_to_log(
|
||||
f"Migration 1 - Failed to set migration as executed: {err}", "error"
|
||||
f"Migration 1 - Failed to set migration as executed: {err}",
|
||||
"error",
|
||||
exc=err,
|
||||
)
|
||||
return
|
||||
else:
|
||||
@@ -192,19 +200,27 @@ def process_migration_2(db: Session):
|
||||
# Create an instance of TimezoneFinder
|
||||
tf = TimezoneFinder()
|
||||
|
||||
# Initialize flag to track if all activities and health_data were processed without errors
|
||||
activities_processed_with_no_errors = True
|
||||
health_data_processed_with_no_errors = True
|
||||
|
||||
# Fetch all activities and health_data
|
||||
try:
|
||||
activities = activities_crud.get_all_activities(db)
|
||||
health_data = health_data_crud.get_all_health_data(db)
|
||||
except Exception as err:
|
||||
migrations_logger.print_to_log(
|
||||
f"Migration 2 - Error fetching activities: {err}", "error"
|
||||
f"Migration 2 - Error fetching activities and/or health_data: {err}",
|
||||
"error",
|
||||
exc=err,
|
||||
)
|
||||
return
|
||||
|
||||
if activities:
|
||||
# Process each activity and add timezone
|
||||
for activity in activities:
|
||||
try:
|
||||
# Skip if activity already has timezone
|
||||
if activity.timezone:
|
||||
migrations_logger.print_to_log(
|
||||
f"Migration 2 - {activity.id} already has timezone defined. Skipping.",
|
||||
@@ -225,6 +241,7 @@ def process_migration_2(db: Session):
|
||||
migrations_logger.print_to_log(
|
||||
f"Migration 2 - Failed to fetch streams for activity {activity.id}: {err}",
|
||||
"warning",
|
||||
exc=err,
|
||||
)
|
||||
activities_processed_with_no_errors = False
|
||||
continue
|
||||
@@ -239,7 +256,7 @@ def process_migration_2(db: Session):
|
||||
|
||||
# Update the activity in the database
|
||||
activities_crud.edit_activity(activity.user_id, activity, db)
|
||||
|
||||
|
||||
migrations_logger.print_to_log(
|
||||
f"Migration 2 - Processed activity: {activity.id} - {activity.name}"
|
||||
)
|
||||
@@ -252,11 +269,45 @@ def process_migration_2(db: Session):
|
||||
)
|
||||
|
||||
migrations_logger.print_to_log(
|
||||
f"Migration 2 - Failed to process activity {activity.id}: {err}", "error"
|
||||
f"Migration 2 - Failed to process activity {activity.id}: {err}",
|
||||
"error",
|
||||
exc=err,
|
||||
)
|
||||
|
||||
if health_data:
|
||||
# Process each weight and add timezone
|
||||
for data in health_data:
|
||||
try:
|
||||
# Skip if weight already has timezone
|
||||
if data.bmi:
|
||||
migrations_logger.print_to_log(
|
||||
f"Migration 2 - {data.id} already has BMI defined. Skipping.",
|
||||
"info",
|
||||
)
|
||||
continue
|
||||
|
||||
# Update the weight in the database
|
||||
health_data_crud.edit_health_data(data.user_id, data, db)
|
||||
|
||||
migrations_logger.print_to_log(
|
||||
f"Migration 2 - Processed BMI: {data.id}"
|
||||
)
|
||||
|
||||
except Exception as err:
|
||||
health_data_processed_with_no_errors = False
|
||||
core_logger.print_to_log_and_console(
|
||||
f"Migration 2 - Failed to process BMI {data.id}. Please check migrations log for more details.",
|
||||
"error",
|
||||
)
|
||||
|
||||
migrations_logger.print_to_log(
|
||||
f"Migration 2 - Failed to process BMI {data.id}: {err}",
|
||||
"error",
|
||||
exc=err,
|
||||
)
|
||||
|
||||
# Mark migration as executed
|
||||
if activities_processed_with_no_errors:
|
||||
if activities_processed_with_no_errors and health_data_processed_with_no_errors:
|
||||
try:
|
||||
migrations_crud.set_migration_as_executed(2, db)
|
||||
except Exception as err:
|
||||
@@ -266,7 +317,9 @@ def process_migration_2(db: Session):
|
||||
)
|
||||
|
||||
migrations_logger.print_to_log(
|
||||
f"Migration 2 - Failed to set migration as executed: {err}", "error"
|
||||
f"Migration 2 - Failed to set migration as executed: {err}",
|
||||
"error",
|
||||
exc=err,
|
||||
)
|
||||
return
|
||||
else:
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import os
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy.orm import Session
|
||||
from stravalib.client import Client
|
||||
from timezonefinder import TimezoneFinder
|
||||
|
||||
import activities.schema as activities_schema
|
||||
import activities.crud as activities_crud
|
||||
@@ -55,6 +58,10 @@ def parse_activity(
|
||||
user_integrations: user_integrations_schema.UserIntegrations,
|
||||
db: Session,
|
||||
) -> dict:
|
||||
# Create an instance of TimezoneFinder
|
||||
tf = TimezoneFinder()
|
||||
timezone = os.environ.get("TZ")
|
||||
|
||||
# Get the detailed activity
|
||||
detailedActivity = strava_client.get_activity(activity.id)
|
||||
|
||||
@@ -102,6 +109,7 @@ def parse_activity(
|
||||
# Extract data from streams
|
||||
lat_lon = streams["latlng"].data if "latlng" in streams else []
|
||||
lat_lon_waypoints = []
|
||||
is_lat_lon_set = False
|
||||
ele = streams["altitude"].data if "altitude" in streams else []
|
||||
ele_waypoints = []
|
||||
is_elevation_set = False
|
||||
@@ -128,6 +136,7 @@ def parse_activity(
|
||||
"lon": lat_lon[i][1],
|
||||
}
|
||||
)
|
||||
is_lat_lon_set = True
|
||||
|
||||
for i in range(len(ele)):
|
||||
ele_waypoints.append({"time": time[i], "ele": ele[i]})
|
||||
@@ -235,17 +244,26 @@ def parse_activity(
|
||||
if gear is not None:
|
||||
gear_id = gear.id
|
||||
|
||||
# Activity type
|
||||
activity_type = activities_utils.define_activity_type(detailedActivity.sport_type.root)
|
||||
|
||||
if activity_type != 3 and activity_type != 7:
|
||||
if is_lat_lon_set:
|
||||
timezone = tf.timezone_at(
|
||||
lat=lat_lon_waypoints[0]["lat"],
|
||||
lng=lat_lon_waypoints[0]["lon"],
|
||||
)
|
||||
|
||||
# Create the activity object
|
||||
activity_to_store = activities_schema.Activity(
|
||||
user_id=user_id,
|
||||
name=detailedActivity.name,
|
||||
distance=round(detailedActivity.distance) if detailedActivity.distance else 0,
|
||||
description=detailedActivity.description,
|
||||
activity_type=activities_utils.define_activity_type(
|
||||
detailedActivity.sport_type.root
|
||||
),
|
||||
activity_type=activity_type,
|
||||
start_time=start_date_parsed.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
end_time=end_date_parsed.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
timezone=timezone,
|
||||
total_elapsed_time=total_elapsed_time,
|
||||
total_timer_time=total_timer_time,
|
||||
city=city,
|
||||
|
||||
@@ -22,12 +22,12 @@ def get_strava_logger():
|
||||
return logging.getLogger("strava_logger")
|
||||
|
||||
|
||||
def print_to_log(message: str, type: str = "info"):
|
||||
def print_to_log(message: str, type: str = "info", exc: Exception = None):
|
||||
garminconnect_logger = get_strava_logger()
|
||||
if type == "info":
|
||||
garminconnect_logger.info(message)
|
||||
elif type == "error":
|
||||
garminconnect_logger.error(message)
|
||||
garminconnect_logger.error(message, exc_info=exc is not None)
|
||||
elif type == "warning":
|
||||
garminconnect_logger.warning(message)
|
||||
elif type == "debug":
|
||||
|
||||
@@ -91,7 +91,7 @@ async def strava_link(
|
||||
core_logger.print_to_log(
|
||||
f"Error in strava_link. For more information check Strava log.", "error"
|
||||
)
|
||||
strava_logger.print_to_log(f"Error in strava_link: {err}", "error")
|
||||
strava_logger.print_to_log(f"Error in strava_link: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
|
||||
@@ -30,7 +30,7 @@ def get_user_integrations_by_user_id(user_id: int, db: Session):
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_user_integrations_by_user_id: {err}", "error"
|
||||
f"Error in get_user_integrations_by_user_id: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -43,7 +43,9 @@ def get_user_integrations_by_strava_state(strava_state: str, db: Session):
|
||||
try:
|
||||
user_integrations = (
|
||||
db.query(user_integrations_models.UserIntegrations)
|
||||
.filter(user_integrations_models.UserIntegrations.strava_state == strava_state)
|
||||
.filter(
|
||||
user_integrations_models.UserIntegrations.strava_state == strava_state
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@@ -56,7 +58,7 @@ def get_user_integrations_by_strava_state(strava_state: str, db: Session):
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_user_integrations_by_user_id: {err}", "error"
|
||||
f"Error in get_user_integrations_by_user_id: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -86,7 +88,9 @@ def create_user_integrations(user_id: int, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in create_user_integrations: {err}", "error")
|
||||
core_logger.print_to_log(
|
||||
f"Error in create_user_integrations: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -118,7 +122,9 @@ def link_strava_account(
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in link_strava_account: {err}", "error")
|
||||
core_logger.print_to_log(
|
||||
f"Error in link_strava_account: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -153,7 +159,9 @@ def unlink_strava_account(user_id: int, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in unlink_strava_account: {err}", "error")
|
||||
core_logger.print_to_log(
|
||||
f"Error in unlink_strava_account: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -185,7 +193,9 @@ def set_user_strava_state(user_id: int, state: str, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in set_user_strava_state: {err}", "error")
|
||||
core_logger.print_to_log(
|
||||
f"Error in set_user_strava_state: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -209,7 +219,9 @@ def set_user_strava_sync_gear(user_id: int, strava_sync_gear: bool, db: Session)
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in set_user_strava_state: {err}", "error")
|
||||
core_logger.print_to_log(
|
||||
f"Error in set_user_strava_state: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -247,7 +259,9 @@ def link_garminconnect_account(
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in link_garminconnect_account: {err}", "error")
|
||||
core_logger.print_to_log(
|
||||
f"Error in link_garminconnect_account: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -274,7 +288,7 @@ def set_user_garminconnect_sync_gear(
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in set_user_garminconnect_sync_gear: {err}", "error"
|
||||
f"Error in set_user_garminconnect_sync_gear: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
@@ -310,7 +324,7 @@ def unlink_garminconnect_account(user_id: int, db: Session):
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in unlink_garminconnect_account: {err}", "error"
|
||||
f"Error in unlink_garminconnect_account: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
|
||||
@@ -25,7 +25,7 @@ def authenticate_user(username: str, db: Session):
|
||||
return user
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in authenticate_user: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in authenticate_user: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -39,7 +39,7 @@ def get_all_users(db: Session):
|
||||
return db.query(users_models.User).all()
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_all_number: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_all_number: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -55,7 +55,7 @@ def get_users_number(db: Session):
|
||||
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_users_number: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_users_number: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -84,7 +84,7 @@ def get_users_with_pagination(db: Session, page_number: int = 1, num_records: in
|
||||
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_users_with_pagination: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_users_with_pagination: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -115,7 +115,7 @@ def get_user_if_contains_username(username: str, db: Session):
|
||||
return users
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_user_if_contains_username: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_user_if_contains_username: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -139,7 +139,7 @@ def get_user_by_username(username: str, db: Session):
|
||||
return user
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_user_by_username: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_user_by_username: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -163,7 +163,7 @@ def get_user_by_id(user_id: int, db: Session):
|
||||
return user
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_user_by_id: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_user_by_id: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -188,7 +188,7 @@ def get_user_id_by_username(username: str, db: Session):
|
||||
return user_id
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_user_id_by_username: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_user_id_by_username: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -211,7 +211,7 @@ def get_user_photo_path_by_id(user_id: int, db: Session):
|
||||
return user_db.photo_path
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_user_photo_path_by_id: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in get_user_photo_path_by_id: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -257,7 +257,7 @@ def create_user(user: users_schema.UserCreate, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in create_user: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in create_user: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -304,7 +304,7 @@ def edit_user(user_id: int, user: users_schema.User, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in edit_user: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in edit_user: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -328,7 +328,7 @@ def edit_user_password(user_id: int, password: str, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in edit_user_password: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in edit_user_password: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -355,7 +355,7 @@ def edit_user_photo_path(user_id: int, photo_path: str, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in edit_user_photo_path: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in edit_user_photo_path: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -382,7 +382,7 @@ def delete_user_photo(user_id: int, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in delete_user_photo: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in delete_user_photo: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
@@ -413,7 +413,7 @@ def delete_user(user_id: int, db: Session):
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in delete_user: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in delete_user: {err}", "error", exc=err)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
|
||||
@@ -49,7 +49,7 @@ async def save_user_image(user_id: int, file: UploadFile, db: Session):
|
||||
return users_crud.edit_user_photo_path(user_id, file_path_to_save, db)
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in save_user_image: {err}", "error")
|
||||
core_logger.print_to_log(f"Error in save_user_image: {err}", "error", exc=err)
|
||||
|
||||
# Remove the file after processing
|
||||
if os.path.exists(file_path_to_save):
|
||||
|
||||
@@ -19,6 +19,7 @@ services:
|
||||
container_name: backend
|
||||
image: ghcr.io/joaovitoriasilva/endurain/backend:latest
|
||||
environment:
|
||||
- TZ=Europe/Lisbon
|
||||
- DB_PASSWORD=changeme
|
||||
- SECRET_KEY=changeme # openssl rand -hex 32
|
||||
- STRAVA_CLIENT_ID=changeme
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
<script setup>
|
||||
import { RouterView } from 'vue-router'
|
||||
import NavbarComponent from './components/Navbar/NavbarComponent.vue'
|
||||
import FooterComponent from './components/FooterComponent.vue'
|
||||
import { Notivue, Notification, NotificationProgress, pastelTheme } from 'notivue'
|
||||
import { RouterView } from "vue-router";
|
||||
import NavbarComponent from "./components/Navbar/NavbarComponent.vue";
|
||||
import FooterComponent from "./components/FooterComponent.vue";
|
||||
import {
|
||||
Notivue,
|
||||
Notification,
|
||||
NotificationProgress,
|
||||
pastelTheme,
|
||||
} from "notivue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<a class="dropdown-item" :class="{ disabled: activity.strava_activity_id || activity.garminconnect_activity_id }" href="#" data-bs-toggle="modal" data-bs-target="#deleteActivityModal">
|
||||
<a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#deleteActivityModal">
|
||||
{{ $t("activitySummary.buttonDeleteActivity") }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
<div class="fw-bold">
|
||||
{{ data.weight }}
|
||||
</div>
|
||||
{{ formatDate(data.created_at) }}
|
||||
{{ data.date }}
|
||||
{{ formatDate(data.date) }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="modal fade" :id="`${modalId}`" tabindex="-1" :aria-labelledby="`${modalId}`" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" :id="`${modalId}`">{{ title }}</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- number field -->
|
||||
<label for="numberToEmit"><b>* {{ numberFieldLabel }}</b></label>
|
||||
<input class="form-control" type="number" name="numberToEmit" :placeholder="`${numberFieldLabel}`" v-model="numberToEmit" required>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ $t("generalItems.buttonClose") }}</button>
|
||||
<a type="button" @click="submitAction()" class="btn" :class="{ 'btn-success': actionButtonType === 'success', 'btn-danger': actionButtonType === 'danger', 'btn-warning': actionButtonType === 'warning', 'btn-primary': actionButtonType === 'loading' }" data-bs-dismiss="modal">{{ actionButtonText }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref} from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modalId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
numberFieldLabel: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
actionButtonType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
actionButtonText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['numberToEmitAction'],
|
||||
setup(props, { emit }) {
|
||||
const numberToEmit = ref(7);
|
||||
|
||||
function submitAction() {
|
||||
emit('numberToEmitAction', numberToEmit.value);
|
||||
}
|
||||
|
||||
return {
|
||||
numberToEmit,
|
||||
submitAction,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -95,57 +95,19 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- modal retrieve Strava activities by days -->
|
||||
<ModalComponentNumberInput modalId="retrieveStravaActivitiesByDaysModal" :title="t('settingsIntegrationsZone.modalRetrieveActivitiesByDaysTitle')" :numberFieldLabel="`${t('settingsIntegrationsZone.modalRetrieveActivitiesByDaysLabel')}`" :actionButtonType="`success`" :actionButtonText="t('settingsIntegrationsZone.modalRetrieveButton')" @numberToEmitAction="submitRetrieveStravaActivities"/>
|
||||
|
||||
<!-- modal unlink Strava -->
|
||||
<ModalComponent modalId="unlinkStravaModal" :title="t('settingsIntegrationsZone.modalUnlinkStravaTitle')" :body="`${t('settingsIntegrationsZone.modalUnlinkStravaBody')}`" :actionButtonType="`danger`" :actionButtonText="t('settingsIntegrationsZone.modalUnlinkStravaTitle')" @submitAction="buttonStravaUnlink"/>
|
||||
|
||||
<!-- modal garmin connect auth -->
|
||||
<GarminConnectLoginModalComponent />
|
||||
|
||||
<!-- modal retrieve strava activities by days -->
|
||||
<div class="modal fade" id="retrieveStravaActivitiesByDaysModal" tabindex="-1" aria-labelledby="retrieveStravaActivitiesByDaysModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="retrieveStravaActivitiesByDaysModalLabel">{{ $t("settingsIntegrationsZone.modalRetrieveActivitiesByDaysTitle") }}</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form @submit.prevent="submitRetrieveStravaActivities">
|
||||
<div class="modal-body">
|
||||
<!-- number of days fields -->
|
||||
<label for="daysToRetrieve"><b>* {{ $t("settingsIntegrationsZone.modalRetrieveActivitiesByDaysLabel") }}</b></label>
|
||||
<input class="form-control" type="number" name="daysToRetrieve" :placeholder='$t("settingsIntegrationsZone.modalRetrieveActivitiesByDaysPlaceholder")' v-model="daysToRetrieveStrava" required>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" name="retrieveStravaActivities" data-bs-dismiss="modal">{{ $t("generalItems.buttonClose") }}</button>
|
||||
<button type="submit" class="btn btn-success" data-bs-dismiss="modal">{{ $t("settingsIntegrationsZone.modalRetrieveButton") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- modal retrieve garmin connect activities by days -->
|
||||
<div class="modal fade" id="retrieveGarminConnectActivitiesByDaysModal" tabindex="-1" aria-labelledby="retrieveGarminConnectActivitiesByDaysModal" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="retrieveGarminConnectActivitiesByDaysModal">{{ $t("settingsIntegrationsZone.modalRetrieveActivitiesByDaysTitle") }}</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form @submit.prevent="submitRetrieveGarminConnectActivities">
|
||||
<div class="modal-body">
|
||||
<!-- number of days fields -->
|
||||
<label for="daysToRetrieve"><b>* {{ $t("settingsIntegrationsZone.modalRetrieveActivitiesByDaysLabel") }}</b></label>
|
||||
<input class="form-control" type="number" name="daysToRetrieve" :placeholder='$t("settingsIntegrationsZone.modalRetrieveActivitiesByDaysPlaceholder")' v-model="daysToRetrieveGarmin" required>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" name="retrieveStravaActivities" data-bs-dismiss="modal">{{ $t("generalItems.buttonClose") }}</button>
|
||||
<button type="submit" class="btn btn-success" data-bs-dismiss="modal">{{ $t("settingsIntegrationsZone.modalRetrieveButton") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ModalComponent modalId="unlinkStravaModal" :title="t('settingsIntegrationsZone.modalUnlinkStravaTitle')" :body="`${t('settingsIntegrationsZone.modalUnlinkStravaBody')}`" :actionButtonType="`danger`" :actionButtonText="t('settingsIntegrationsZone.modalUnlinkStravaTitle')" @submitAction="buttonStravaUnlink"/>
|
||||
<!-- modal retrieve Garmin Connect activities by days -->
|
||||
<ModalComponentNumberInput modalId="retrieveGarminConnectActivitiesByDaysModal" :title="t('settingsIntegrationsZone.modalRetrieveActivitiesByDaysTitle')" :numberFieldLabel="`${t('settingsIntegrationsZone.modalRetrieveActivitiesByDaysLabel')}`" :actionButtonType="`success`" :actionButtonText="t('settingsIntegrationsZone.modalRetrieveButton')" @numberToEmitAction="submitRetrieveGarminConnectActivities"/>
|
||||
|
||||
<!-- modal unlink Garmin Connect -->
|
||||
<ModalComponent modalId="unlinkGarminConnectModal" :title="t('settingsIntegrationsZone.modalUnlinkGarminConnectTitle')" :body="`${t('settingsIntegrationsZone.modalUnlinkGarminConnectBody')}`" :actionButtonType="`danger`" :actionButtonText="t('settingsIntegrationsZone.modalUnlinkGarminConnectTitle')" @submitAction="buttonGarminConnectUnlink"/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -163,6 +125,7 @@ import { activities } from "@/services/activitiesService";
|
||||
import { garminConnect } from "@/services/garminConnectService";
|
||||
// Import the components
|
||||
import ModalComponent from "@/components/Modals/ModalComponent.vue";
|
||||
import ModalComponentNumberInput from "@/components/Modals/ModalComponentNumberInput.vue";
|
||||
import GarminConnectLoginModalComponent from "./SettingsIntegrations/GarminConnectLoginModalComponent.vue";
|
||||
|
||||
//import Modal from 'bootstrap/js/dist/modal';
|
||||
@@ -170,15 +133,12 @@ import GarminConnectLoginModalComponent from "./SettingsIntegrations/GarminConne
|
||||
export default {
|
||||
components: {
|
||||
ModalComponent,
|
||||
ModalComponentNumberInput,
|
||||
GarminConnectLoginModalComponent,
|
||||
},
|
||||
setup() {
|
||||
const authStore = useAuthStore();
|
||||
const { locale, t } = useI18n();
|
||||
const daysToRetrieveStrava = ref(7);
|
||||
const daysToRetrieveGarmin = ref(7);
|
||||
const garminConnectUsername = ref("");
|
||||
const garminConnectPassword = ref("");
|
||||
|
||||
async function submitConnectStrava() {
|
||||
const array = new Uint8Array(16);
|
||||
@@ -199,9 +159,9 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
async function submitRetrieveStravaActivities() {
|
||||
async function submitRetrieveStravaActivities(daysToRetrieveStrava) {
|
||||
try {
|
||||
await strava.getStravaActivitiesLastDays(daysToRetrieveStrava.value);
|
||||
await strava.getStravaActivitiesLastDays(daysToRetrieveStrava);
|
||||
|
||||
// Show the loading alert.
|
||||
push.info(
|
||||
@@ -268,9 +228,9 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
async function submitRetrieveGarminConnectActivities() {
|
||||
async function submitRetrieveGarminConnectActivities(daysToRetrieveGarmin) {
|
||||
try {
|
||||
await garminConnect.getGarminConnectActivitiesLastDays(daysToRetrieveGarmin.value);
|
||||
await garminConnect.getGarminConnectActivitiesLastDays(daysToRetrieveGarmin);
|
||||
|
||||
// Show the loading alert.
|
||||
push.info(
|
||||
@@ -328,14 +288,10 @@ export default {
|
||||
t,
|
||||
submitConnectStrava,
|
||||
submitRetrieveStravaActivities,
|
||||
daysToRetrieveStrava,
|
||||
submitRetrieveStravaGear,
|
||||
buttonStravaUnlink,
|
||||
submitBulkImport,
|
||||
garminConnectUsername,
|
||||
garminConnectPassword,
|
||||
submitRetrieveGarminConnectActivities,
|
||||
daysToRetrieveGarmin,
|
||||
submitRetrieveGarminConnectGear,
|
||||
buttonGarminConnectUnlink,
|
||||
};
|
||||
|
||||
@@ -278,7 +278,7 @@ export default {
|
||||
gearId.value = activity.value.gear_id;
|
||||
}
|
||||
|
||||
if (activity.value.activity_type === 1 || activity.value.activity_type === 2 || activity.value.activity_type === 3) {
|
||||
if (activity.value.activity_type === 1 || activity.value.activity_type === 2 || activity.value.activity_type === 3 || activity.value.activity_type === 11 || activity.value.activity_type === 12) {
|
||||
gearsByType.value = await gears.getGearFromType(2);
|
||||
} else {
|
||||
if (activity.value.activity_type === 4 || activity.value.activity_type === 5 || activity.value.activity_type === 6 || activity.value.activity_type === 7) {
|
||||
|
||||
@@ -232,7 +232,7 @@ export default {
|
||||
userNumberOfActivities.value ++;
|
||||
} catch (error) {
|
||||
// Set the error message
|
||||
notification.reject(`${t('generalItems.errorFetchingInfo')} - ${error}`)
|
||||
notification.reject(`${error}`)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user