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) =>