diff --git a/controllers/activitiesController.py b/controllers/activitiesController.py index f684ac6f9..8e3ed5efe 100644 --- a/controllers/activitiesController.py +++ b/controllers/activitiesController.py @@ -3,7 +3,7 @@ import logging from fastapi import APIRouter, Depends, HTTPException, Form, Response, File, UploadFile, Request from fastapi.security import OAuth2PasswordBearer from typing import List, Optional -from sqlalchemy import func, DECIMAL, DateTime +from sqlalchemy import func, DECIMAL, DateTime, desc from db.db import get_db_session, Activity from jose import jwt, JWTError from dotenv import load_dotenv @@ -31,7 +31,7 @@ async def read_activities_all(token: str = Depends(oauth2_scheme)): #user_id = payload.get("id") # Query the activities records using SQLAlchemy - activity_records = db_session.query(Activity).all() + activity_records = db_session.query(Activity).order_by(desc(Activity.start_time)).all() # Convert the SQLAlchemy objects to dictionaries results = [activity.to_dict() for activity in activity_records] @@ -78,6 +78,7 @@ async def read_activities_all_pagination( # Use SQLAlchemy to query the gear records with pagination activity_records = ( db_session.query(Activity) + .order_by(desc(Activity.start_time)) .offset((pageNumber - 1) * numRecords) .limit(numRecords) .all() @@ -99,10 +100,13 @@ class CreateActivityRequest(BaseModel): type: str starttime: str endtime: str - city: str - town: str - country: str + city: Optional[str] + town: Optional[str] + country: Optional[str] waypoints: List[dict] + elevationGain: int + elevationLoss: int + pace: float @router.post("/activities/create") async def create_activity( @@ -119,19 +123,25 @@ async def create_activity( user_id = payload.get("id") # Convert the 'starttime' string to a datetime - starttime = datetime.strptime(activity_data.starttime, "%Y-%m-%dT%H:%M:%SZ") + #starttime = datetime.strptime(activity_data.starttime, "%Y-%m-%dT%H:%M:%SZ") + starttime = parse_timestamp(activity_data.starttime) # Convert the 'endtime' string to a datetime - endtime = datetime.strptime(activity_data.endtime, "%Y-%m-%dT%H:%M:%SZ") + #endtime = datetime.strptime(activity_data.endtime, "%Y-%m-%dT%H:%M:%SZ") + endtime = parse_timestamp(activity_data.endtime) auxType = 10 # Default value type_mapping = { "running": 1, "trail running": 2, "VirtualRun": 3, + "cycling": 4, "Ride": 4, "GravelRide": 5, "EBikeRide": 6, - "VirtualRide": 7 + "VirtualRide": 7, + "virtual_ride": 7, + "swimming": 8, + "open_water_swimming": 8 } auxType = type_mapping.get(activity_data.type, 10) @@ -147,7 +157,10 @@ async def create_activity( town=activity_data.town, country=activity_data.country, created_at=func.now(), # Use func.now() to set 'created_at' to the current timestamp - waypoints=activity_data.waypoints + waypoints=activity_data.waypoints, + elevation_gain=activity_data.elevationGain, + elevation_loss=activity_data.elevationLoss, + pace=activity_data.pace ) # Store the Activity record in the database @@ -156,7 +169,8 @@ async def create_activity( db_session.commit() db_session.refresh(activity) # This will ensure that the activity object is updated with the ID from the database - return {"message": "Activity stored successfully", "id": activity.id} + #return {"message": "Activity stored successfully", "id": activity.id} + return {"message": "Activity stored successfully"} except JWTError: raise HTTPException(status_code=401, detail="Unauthorized") @@ -165,4 +179,12 @@ async def create_activity( logger.error(err) raise HTTPException(status_code=500, detail="Failed to store activity") - #return {"message": "Activity stored successfully"} \ No newline at end of file + #return {"message": "Activity stored successfully"} + +def parse_timestamp(timestamp_string): + try: + # Try to parse with milliseconds + return datetime.strptime(timestamp_string, "%Y-%m-%dT%H:%M:%S.%fZ") + except ValueError: + # If milliseconds are not present, use a default value of 0 + return datetime.strptime(timestamp_string, "%Y-%m-%dT%H:%M:%SZ") \ No newline at end of file diff --git a/db/createdb.sql b/db/createdb.sql index 337fadd5c..7046da362 100644 --- a/db/createdb.sql +++ b/db/createdb.sql @@ -72,6 +72,29 @@ CREATE TABLE IF NOT EXISTS `gearguardian`.`gear` ( ON UPDATE NO ACTION) ENGINE = InnoDB; +-- ----------------------------------------------------- +-- Table `gearguardian`.`user_settings` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `gearguardian`.`user_settings` ( + `id` INT(10) NOT NULL AUTO_INCREMENT , + `user_id` INT(10) NOT NULL COMMENT 'User ID that the activity belongs' , + `activity_type` INT(2) NULL COMMENT 'Gear type' , + `gear_id` INT(10) NULL COMMENT 'Gear ID associated with this activity' , + PRIMARY KEY (`id`) , + INDEX `FK_user_id_idx` (`user_id` ASC) , + CONSTRAINT `FK_user_settings_user` + FOREIGN KEY (`user_id` ) + REFERENCES `gearguardian`.`users` (`id` ) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + INDEX `FK_gear_id_idx` (`gear_id` ASC) , + CONSTRAINT `FK_user_settings_gear` + FOREIGN KEY (`gear_id` ) + REFERENCES `gearguardian`.`gear` (`id` ) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + -- ----------------------------------------------------- -- Table `gearguardian`.`activities` -- ----------------------------------------------------- @@ -80,7 +103,7 @@ CREATE TABLE IF NOT EXISTS `gearguardian`.`activities` ( `user_id` INT(10) NOT NULL COMMENT 'User ID that the activity belongs' , `name` VARCHAR(45) NULL COMMENT 'Activity name (May include spaces)' , `distance` INT(9) NOT NULL COMMENT 'Distance in meters' , - `activity_type` INT(2) NOT NULL COMMENT 'Gear type (1 - mountain bike, 2 - gravel bike, 3 - road bike, 4 - indoor bike, 5 - road run, 6 - trail run, 7 - indoor run, 8 - indoor swim, 9 - openwater swim, 10 - other)' , + `activity_type` INT(2) NOT NULL COMMENT 'Gear type' , `start_time` DATETIME NOT NULL COMMENT 'Actvitiy start date (datetime)' , `end_time` DATETIME NOT NULL COMMENT 'Actvitiy end date (datetime)' , `city` VARCHAR(45) NULL COMMENT 'Activity city (May include spaces)' , @@ -88,34 +111,23 @@ CREATE TABLE IF NOT EXISTS `gearguardian`.`activities` ( `country` VARCHAR(45) NULL COMMENT 'Activity country (May include spaces)' , `created_at` DATETIME NOT NULL COMMENT 'Actvitiy creation date (datetime)' , `waypoints` LONGTEXT NULL COMMENT 'Store waypoints data', + `elevation_gain` INT(5) NOT NULL COMMENT 'Elevation gain in meters' , + `elevation_loss` INT(5) NOT NULL COMMENT 'Elevation loss in meters' , + `pace` DECIMAL(20, 10) NOT NULL COMMENT 'Pace seconds per meter (s/m)' , + `gear_id` INT(10) NULL COMMENT 'Gear ID associated with this activity' , PRIMARY KEY (`id`) , INDEX `FK_user_id_idx` (`user_id` ASC) , CONSTRAINT `FK_activity_user` FOREIGN KEY (`user_id` ) REFERENCES `gearguardian`.`users` (`id` ) ON DELETE NO ACTION - ON UPDATE NO ACTION) -ENGINE = InnoDB; - --- ----------------------------------------------------- --- Table `gearguardian`.`waypoints` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `gearguardian`.`waypoints` ( - `id` INT(10) NOT NULL AUTO_INCREMENT , - `activity_id` INT(10) NOT NULL COMMENT 'Activity ID that the waypoint belongs' , - `latitude` DECIMAL(10, 6) NULL COMMENT 'Latitude with 6 decimal places' , - `longitude` DECIMAL(10, 6) NULL COMMENT 'Longitude with 6 decimal places' , - `elevation` DECIMAL(8, 2) NULL COMMENT 'Elevation with 2 decimal places' , - `time` DATETIME NULL COMMENT 'Timestamp of the waypoint' , - `heart_rate` INT NULL COMMENT 'Heart rate data' , - `cadence` INT NULL COMMENT 'Cadence data' , - PRIMARY KEY (`id`) , - INDEX `FK_activity_id_idx` (`activity_id` ASC) , - CONSTRAINT `FK_waypoint_activity` - FOREIGN KEY (`activity_id` ) - REFERENCES `gearguardian`.`activities` (`id` ) + ON UPDATE NO ACTION, + INDEX `FK_gear_id_idx` (`gear_id` ASC) , + CONSTRAINT `FK_activity_gear` + FOREIGN KEY (`gear_id` ) + REFERENCES `gearguardian`.`gear` (`id` ) ON DELETE NO ACTION - ON UPDATE NO ACTION) + ON UPDATE NO ACTION,) ENGINE = InnoDB; -- ----------------------------------------------------- diff --git a/db/db.py b/db/db.py index 443770ef3..634a33d74 100644 --- a/db/db.py +++ b/db/db.py @@ -54,6 +54,8 @@ class User(Base): gear = relationship('Gear', back_populates='user') # Establish a one-to-many relationship with 'activities' activities = relationship('Activity', back_populates='user') + # Establish a one-to-many relationship between User and UserSettings + user_settings = relationship("UserSettings", back_populates="user") # Data model for access_tokens table using SQLAlchemy's ORM class AccessToken(Base): @@ -83,6 +85,22 @@ class Gear(Base): # Define a relationship to the User model user = relationship('User', back_populates='gear') + # Establish a one-to-many relationship with 'activities' + activities = relationship('Activity', back_populates='gear') + # Establish a one-to-many relationship between Gear and UserSettings + user_settings = relationship("UserSettings", back_populates="gear") + +class UserSettings(Base): + __tablename__ = 'user_settings' + + id = Column(Integer, primary_key=True, autoincrement=True) + user_id = Column(Integer, nullable=False, doc='User ID that the activity belongs') + activity_type = Column(Integer, nullable=True, doc='Gear type') + gear_id = Column(Integer, nullable=True, doc='Gear ID associated with this activity') + + # Define the foreign key relationships + user = relationship("User", back_populates="user_settings") + gear = relationship("Gear", back_populates="user_settings") # Data model for activities table using SQLAlchemy's ORM class Activity(Base): @@ -100,29 +118,16 @@ class Activity(Base): country = Column(String(length=45), nullable=True, comment='Activity country (May include spaces)') created_at = Column(DateTime, nullable=False, comment='Activity creation date (datetime)') waypoints = Column(JSON, nullable=True, doc='Store waypoints data') + elevation_gain = Column(Integer, nullable=False, comment='Elevation gain in meters') + elevation_loss = Column(Integer, nullable=False, comment='Elevation loss in meters') + pace = Column(DECIMAL(precision=20, scale=10), nullable=False, comment='Pace seconds per meter (s/m)') + gear_id = Column(Integer, ForeignKey('gear.id'), nullable=True, comment='Gear ID associated with this activity') # Define a relationship to the User model user = relationship('User', back_populates='activities') - # Establish a one-to-many relationship with 'waypoints' - #waypoints = relationship('Waypoint', back_populates='activity') - -# Data model for waypoints table using SQLAlchemy's ORM -# class Waypoint(Base): -# __tablename__ = 'waypoints' - -# id = Column(Integer, primary_key=True, autoincrement=True) -# activity_id = Column(Integer, ForeignKey('activities.id'), nullable=False, comment='Activity ID that the waypoint belongs') -# latitude = Column(DECIMAL(precision=10, scale=6), nullable=True, comment='Latitude with 6 decimal places') -# longitude = Column(DECIMAL(precision=10, scale=6), nullable=True, comment='Longitude with 6 decimal places') -# elevation = Column(DECIMAL(precision=8, scale=2), nullable=True, comment='Elevation with 2 decimal places') -# time = Column(DateTime, nullable=True, comment='Timestamp of the waypoint') -# heart_rate = Column(Integer, nullable=True, comment='Heart rate data') -# cadence = Column(Integer, nullable=True, comment='Cadence data') -# power = Column(Integer, nullable=True, comment='Power data') - -# # Define a relationship to the Activity model -# activity = relationship('Activity', back_populates='waypoints') + # Define a relationship to the Gear model + gear = relationship('Gear', back_populates='activities') # Context manager to get a database session from contextlib import contextmanager