diff --git a/backend/app/activities/activity/utils.py b/backend/app/activities/activity/utils.py index 89e599a9b..92d9522d7 100644 --- a/backend/app/activities/activity/utils.py +++ b/backend/app/activities/activity/utils.py @@ -86,6 +86,7 @@ ACTIVITY_ID_TO_NAME = { 37: "Ice Skate", 38: "Soccer", 39: "Padel", + 40: "Treadmill", # Add other mappings as needed based on the full list in define_activity_type comments if required # "AlpineSki", # "BackcountrySki", @@ -211,6 +212,7 @@ ACTIVITY_NAME_TO_ID.update( "padel": 39, "padelball": 39, "paddelball": 39, + "treadmill": 40, } ) @@ -797,7 +799,7 @@ def calculate_activity_distances(activities: list[activities_schema.Activity]): if activities is not None: # Calculate the distances for activity in activities: - if activity.activity_type in [1, 2, 3, 34]: + if activity.activity_type in [1, 2, 3, 34, 40]: run += activity.distance elif activity.activity_type in [4, 5, 6, 7, 27, 28, 29, 35, 36]: bike += activity.distance diff --git a/backend/app/profile/export_service.py b/backend/app/profile/export_service.py index 7c42dceea..ac7bc0412 100644 --- a/backend/app/profile/export_service.py +++ b/backend/app/profile/export_service.py @@ -406,7 +406,7 @@ class ExportService: """ chunk_buffer = [] file_counter = 0 - max_items_per_file = 500 + max_items_per_file = batch_size total_items = 0 # Collect component data in batches diff --git a/backend/app/profile/router.py b/backend/app/profile/router.py index f33aae276..f34d19c8e 100644 --- a/backend/app/profile/router.py +++ b/backend/app/profile/router.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException, status, UploadFile from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session -from safeuploads import FileValidator +from safeuploads import FileValidator, FileSecurityConfig, SecurityLimits from safeuploads.exceptions import FileValidationError import users.user.schema as users_schema @@ -39,8 +39,15 @@ import websocket.schema as websocket_schema # Define the API router router = APIRouter() +custom_limits = SecurityLimits( + max_uncompressed_size=2 * 1024 * 1024 * 1024, + max_number_files_same_type=2000, +) +custom_config = FileSecurityConfig() +custom_config.limits = custom_limits + # Initialize the file validator -file_validator = FileValidator() +file_validator = FileValidator(config=custom_config) @router.get("", response_model=users_schema.UserMe) diff --git a/backend/app/users/user_goals/utils.py b/backend/app/users/user_goals/utils.py index aff520747..7f896b0df 100644 --- a/backend/app/users/user_goals/utils.py +++ b/backend/app/users/user_goals/utils.py @@ -40,8 +40,7 @@ def calculate_user_goals( return None return [ - calculate_goal_progress_by_activity_type(goal, date, db) - for goal in goals + calculate_goal_progress_by_activity_type(goal, date, db) for goal in goals ] except HTTPException as http_err: raise http_err @@ -76,19 +75,19 @@ def calculate_goal_progress_by_activity_type( user_goals_schema.UserGoalProgress | None: An object containing progress details for the goal, or None if no activities are found. Raises: HTTPException: If an error occurs during processing or database access. - """ + """ try: start_date, end_date = get_start_end_date_by_interval(goal.interval, date) # Define activity type mappings TYPE_MAP = { - user_goals_schema.ActivityType.RUN: [1, 2, 3, 34], + user_goals_schema.ActivityType.RUN: [1, 2, 3, 34, 40], user_goals_schema.ActivityType.BIKE: [4, 5, 6, 7, 27, 28, 29, 35, 36], user_goals_schema.ActivityType.SWIM: [8, 9], user_goals_schema.ActivityType.WALK: [11, 12], } DEFAULT_TYPES = (19, 20) - + # Get activity types based on goal.activity_type, default to [10, 19, 20] activity_types = TYPE_MAP.get(goal.activity_type, DEFAULT_TYPES) @@ -96,7 +95,7 @@ def calculate_goal_progress_by_activity_type( activities = activity_crud.get_user_activities_per_timeframe_and_activity_types( goal.user_id, activity_types, start_date, end_date, db, True ) - + # Calculate totals based on goal type percentage_completed = 0 total_calories = 0 @@ -113,14 +112,20 @@ def calculate_goal_progress_by_activity_type( total_distance = sum(activity.distance or 0 for activity in activities) percentage_completed = (total_distance / goal.goal_distance) * 100 elif goal.goal_type == user_goals_schema.GoalType.ELEVATION: - total_elevation = sum(activity.elevation_gain or 0 for activity in activities) + total_elevation = sum( + activity.elevation_gain or 0 for activity in activities + ) percentage_completed = (total_elevation / goal.goal_elevation) * 100 elif goal.goal_type == user_goals_schema.GoalType.DURATION: - total_duration = sum(activity.total_elapsed_time or 0 for activity in activities) + total_duration = sum( + activity.total_elapsed_time or 0 for activity in activities + ) percentage_completed = (total_duration / goal.goal_duration) * 100 elif goal.goal_type == user_goals_schema.GoalType.ACTIVITIES: total_activities_number = len(activities) - percentage_completed = (total_activities_number / goal.goal_activities_number) * 100 + percentage_completed = ( + total_activities_number / goal.goal_activities_number + ) * 100 if percentage_completed > 100: percentage_completed = 100 @@ -208,7 +213,9 @@ def get_start_end_date_by_interval( """ date_obj = datetime.strptime(date, "%Y-%m-%d") if interval == "yearly": - start_date = date_obj.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0) + start_date = date_obj.replace( + month=1, day=1, hour=0, minute=0, second=0, microsecond=0 + ) # Calculate the last second of December 31st of the same year end_date = datetime(date_obj.year, 12, 31, 23, 59, 59) elif interval == "weekly": diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 82d3977d3..3ed59f0f0 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -245,6 +245,7 @@ The table bellow details the activity types supported by Endurain. | Run | 1 | | Trail run | 2 | | Track run | 34 | +| Treadmill run | 40 | | Virtual run | 3 | | Road cycling | 4 | | Gravel cycling | 5 | diff --git a/frontend/app/package-lock.json b/frontend/app/package-lock.json index 6ff7bf7a5..e455a7786 100644 --- a/frontend/app/package-lock.json +++ b/frontend/app/package-lock.json @@ -10245,4 +10245,4 @@ } } } -} +} \ No newline at end of file diff --git a/frontend/app/src/components/Activities/Modals/EditActivityModalComponent.vue b/frontend/app/src/components/Activities/Modals/EditActivityModalComponent.vue index d3a84d26b..311867d97 100644 --- a/frontend/app/src/components/Activities/Modals/EditActivityModalComponent.vue +++ b/frontend/app/src/components/Activities/Modals/EditActivityModalComponent.vue @@ -81,6 +81,9 @@ + diff --git a/frontend/app/src/i18n/us/activityItems.json b/frontend/app/src/i18n/us/activityItems.json index 0bf315638..38c023bc6 100644 --- a/frontend/app/src/i18n/us/activityItems.json +++ b/frontend/app/src/i18n/us/activityItems.json @@ -38,5 +38,6 @@ "iceSkate": "Ice skate", "soccer": "Soccer", "padel": "Padel", + "treadmillRun": "Treadmill run", "labelWorkout": " workout" -} +} \ No newline at end of file diff --git a/frontend/app/src/i18n/us/components/activities/modals/editActivityModalComponent.json b/frontend/app/src/i18n/us/components/activities/modals/editActivityModalComponent.json index e5f929c94..49ea4a727 100644 --- a/frontend/app/src/i18n/us/components/activities/modals/editActivityModalComponent.json +++ b/frontend/app/src/i18n/us/components/activities/modals/editActivityModalComponent.json @@ -46,6 +46,7 @@ "modalEditActivityTypeOption37": "Ice skate", "modalEditActivityTypeOption38": "Soccer", "modalEditActivityTypeOption39": "Padel", + "modalEditActivityTypeOption40": "Treadmill run", "modalEditActivityVisibilityLabel": "Visibility", "modalEditActivityVisibilityOption0": "Public", "modalEditActivityVisibilityOption1": "Followers", @@ -65,4 +66,4 @@ "modalEditActivityHideGearLabel": "Hide gear", "successActivityEdit": "Activity edited successfully", "errorActivityEdit": "Error editing activity" -} +} \ No newline at end of file diff --git a/frontend/app/src/utils/activityUtils.js b/frontend/app/src/utils/activityUtils.js index f301553b5..30a0118e1 100644 --- a/frontend/app/src/utils/activityUtils.js +++ b/frontend/app/src/utils/activityUtils.js @@ -8,7 +8,7 @@ import { formatDateMed, formatTime, formatSecondsToMinutes } from '@/utils/dateT */ const ACTIVITY_TYPES = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, - 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39 + 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 ] /** @@ -60,7 +60,8 @@ const activityLabelMap = { 36: (t) => t('activityItems.ebikeMountainRide'), 37: (t) => t('activityItems.iceSkate'), 38: (t) => t('activityItems.soccer'), - 39: (t) => t('activityItems.padel') + 39: (t) => t('activityItems.padel'), + 40: (t) => t('activityItems.treadmillRun') } /** @@ -282,7 +283,8 @@ export function activityTypeIsRunning(activity) { activity.activity_type === 1 || activity.activity_type === 2 || activity.activity_type === 3 || - activity.activity_type === 34 + activity.activity_type === 34 || + activity.activity_type === 40 ) } /** @@ -290,14 +292,15 @@ export function activityTypeIsRunning(activity) { * * @param {Object} activity - The activity object to check. * @param {number} activity.activity_type - The type identifier of the activity. - * @returns {boolean} Returns true if the activity is not running-related (types 1,2,3 or 34), otherwise false. + * @returns {boolean} Returns true if the activity is not running-related (types 1,2,3,34 and 40), otherwise false. */ export function activityTypeNotRunning(activity) { return ( activity.activity_type !== 1 && activity.activity_type !== 2 && activity.activity_type !== 3 && - activity.activity_type !== 34 + activity.activity_type !== 34 && + activity.activity_type !== 40 ) } @@ -710,7 +713,8 @@ export function getIcon(typeId) { 36: ['fas', 'person-biking'], 37: ['fas', 'person-skating'], 38: ['fas', 'futbol'], - 39: ['fas', 'table-tennis-paddle-ball'] + 39: ['fas', 'table-tennis-paddle-ball'], + 40: ['fas', 'person-running'], // Treadmill run icon might be better if available } return iconMap[typeId] || ['fas', 'dumbbell'] diff --git a/frontend/app/src/views/SearchView.vue b/frontend/app/src/views/SearchView.vue index 8d3bbe536..d56523282 100644 --- a/frontend/app/src/views/SearchView.vue +++ b/frontend/app/src/views/SearchView.vue @@ -258,7 +258,7 @@ const fetchGearResults = debounce(async (query) => { function updateSearchResultsBasedOnActivityType() { if (searchSelectActivityType.value === '1') { searchResults.value = searchResultsOriginal.value.filter((user) => - [1, 2, 3, 34].includes(user.activity_type) + [1, 2, 3, 34, 40].includes(user.activity_type) ) } else if (searchSelectActivityType.value === '2') { searchResults.value = searchResultsOriginal.value.filter((user) =>