Merge branch 'master' into 0.16.0

This commit is contained in:
João Vitória Silva
2025-10-31 16:33:08 +00:00
11 changed files with 50 additions and 24 deletions

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -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":

View File

@@ -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 |

View File

@@ -10245,4 +10245,4 @@
}
}
}
}
}

View File

@@ -81,6 +81,9 @@
<option value="34">
{{ $t('editActivityModalComponent.modalEditActivityTypeOption34') }}
</option>
<option value="40">
{{ $t('editActivityModalComponent.modalEditActivityTypeOption40') }}
</option>
<option value="3">
{{ $t('editActivityModalComponent.modalEditActivityTypeOption3') }}
</option>

View File

@@ -38,5 +38,6 @@
"iceSkate": "Ice skate",
"soccer": "Soccer",
"padel": "Padel",
"treadmillRun": "Treadmill run",
"labelWorkout": " workout"
}
}

View File

@@ -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"
}
}

View File

@@ -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']

View File

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