Add Strava activity retrieval by date range

Updated backend and frontend to support retrieving Strava activities using a start and end date instead of days. Refactored API endpoints, utility functions, and UI components to handle date range selection and requests. Deprecated the previous days-based retrieval in favor of more flexible date range queries.
This commit is contained in:
João Vitória Silva
2025-11-04 16:32:07 +00:00
parent a296b47eca
commit c1abf95b2d
5 changed files with 81 additions and 36 deletions

View File

@@ -1,4 +1,3 @@
import logging
from typing import Annotated
from fastapi import APIRouter, Depends, BackgroundTasks
from sqlalchemy.orm import Session

View File

@@ -38,6 +38,7 @@ from core.database import SessionLocal
async def fetch_and_process_activities(
strava_client: Client,
start_date: datetime,
end_date: datetime,
user_id: int,
user_integrations: user_integrations_schema.UsersIntegrations,
websocket_manager: websocket_schema.WebSocketManager,
@@ -49,7 +50,9 @@ async def fetch_and_process_activities(
# Fetch Strava activities after the specified start date
try:
strava_activities = list(strava_client.get_activities(after=start_date))
strava_activities = list(
strava_client.get_activities(after=start_date, before=end_date)
)
except AccessUnauthorized as auth_err:
# Log a more specific error message for authentication issues
core_logger.print_to_log(
@@ -699,14 +702,17 @@ async def retrieve_strava_users_activities_for_days(
# Get all users
users = users_crud.get_all_users(db)
# Calculate the start date and end date
calculated_start_date = datetime.now(timezone.utc) - timedelta(days=days)
calculated_end_date = datetime.now(timezone.utc)
# Process the activities for each user
if users:
for user in users:
try:
await get_user_strava_activities_by_days(
(datetime.now(timezone.utc) - timedelta(days=days)).strftime(
"%Y-%m-%dT%H:%M:%S"
),
await get_user_garminconnect_activities_by_dates(
calculated_start_date,
calculated_end_date,
user.id,
None,
None,
@@ -764,8 +770,9 @@ async def retrieve_strava_users_activities_for_days(
db.close()
async def get_user_strava_activities_by_days(
async def get_user_garminconnect_activities_by_dates(
start_date: datetime,
end_date: datetime,
user_id: int,
websocket_manager: websocket_schema.WebSocketManager = None,
db: Session = None,
@@ -804,6 +811,7 @@ async def get_user_strava_activities_by_days(
strava_activities_processed = await fetch_and_process_activities(
strava_client,
start_date,
end_date,
user_id,
user_integrations,
websocket_manager,

View File

@@ -1,5 +1,5 @@
import os
from datetime import datetime, timedelta, timezone
from datetime import datetime, timedelta, timezone, date
from typing import Annotated, Callable
from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks, Security
from sqlalchemy.orm import Session
@@ -102,12 +102,13 @@ async def strava_link(
@router.get(
"/activities/days/{days}",
"/activities",
status_code=202,
)
async def strava_retrieve_activities_days(
days: int,
validate_access_token: Annotated[
start_date: date,
end_date: date,
_validate_access_token: Annotated[
Callable,
Depends(auth_security.validate_access_token),
],
@@ -126,12 +127,16 @@ async def strava_retrieve_activities_days(
# db: Annotated[Session, Depends(core_database.get_db)],
background_tasks: BackgroundTasks,
):
start_datetime = datetime.combine(
start_date, datetime.min.time(), tzinfo=timezone.utc
)
end_datetime = datetime.combine(end_date, datetime.max.time(), tzinfo=timezone.utc)
# Process strava activities in the background
background_tasks.add_task(
strava_activity_utils.get_user_strava_activities_by_days,
(datetime.now(timezone.utc) - timedelta(days=days)).strftime(
"%Y-%m-%dT%H:%M:%S"
),
strava_activity_utils.get_user_garminconnect_activities_by_dates,
start_datetime,
end_datetime,
token_user_id,
websocket_manager,
)
@@ -147,7 +152,7 @@ async def strava_retrieve_activities_days(
@router.get("/gear", status_code=201)
async def strava_retrieve_gear(
validate_access_token: Annotated[
_validate_access_token: Annotated[
Callable,
Depends(auth_security.validate_access_token),
],
@@ -291,7 +296,7 @@ async def import_shoes_from_strava_export(
@router.put("/client")
async def strava_set_user_client(
client: strava_schema.StravaClient,
validate_access_token: Annotated[
_validate_access_token: Annotated[
Callable,
Depends(auth_security.validate_access_token),
],
@@ -319,7 +324,7 @@ async def strava_set_user_client(
)
async def strava_set_user_unique_state(
state: str | None,
validate_access_token: Annotated[
_validate_access_token: Annotated[
Callable,
Depends(auth_security.validate_access_token),
],
@@ -342,7 +347,7 @@ async def strava_set_user_unique_state(
@router.delete("/unlink")
async def strava_unlink(
validate_access_token: Annotated[
_validate_access_token: Annotated[
Callable,
Depends(auth_security.validate_access_token),
],

View File

@@ -48,6 +48,17 @@
>{{ $t('settingsIntegrationsZone.modalRetrieveActivitiesByDaysTitle') }}</a
>
</li>
<li>
<!-- retrieve strava activities by date range -->
<a
class="dropdown-item"
href="#"
role="button"
data-bs-toggle="modal"
data-bs-target="#retrieveStravaActivitiesByDateRangeModal"
>{{ $t('settingsIntegrationsZone.modalRetrieveActivitiesByDateRangeTitle') }}</a
>
</li>
<li>
<!-- retrieve gear -->
<a href="#" class="dropdown-item" @click="submitRetrieveStravaGear">{{
@@ -219,7 +230,16 @@
:numberFieldLabel="`${t('settingsIntegrationsZone.modalRetrieveActivitiesByDaysLabel')}`"
:actionButtonType="`success`"
:actionButtonText="t('settingsIntegrationsZone.modalRetrieveButton')"
@numberToEmitAction="submitRetrieveStravaActivities"
@numberToEmitAction="submitRetrieveStravaActivitiesDays"
/>
<!-- modal retrieve Strava activities by date range -->
<ModalComponentDateRangeInput
modalId="retrieveStravaActivitiesByDateRangeModal"
:title="t('settingsIntegrationsZone.modalRetrieveActivitiesByDateRangeTitle')"
:actionButtonType="`success`"
:actionButtonText="t('settingsIntegrationsZone.modalRetrieveButton')"
@datesToEmitAction="submitRetrieveStravaActivitiesDataRange"
/>
<!-- modal unlink Strava -->
@@ -303,6 +323,15 @@ import { INTEGRATION_LOGOS } from '@/constants/integrationLogoConstants'
const authStore = useAuthStore()
const { locale, t } = useI18n()
function getStartAndEndDateFromDaysAgo(days) {
const endDate = new Date()
const startDate = new Date()
startDate.setDate(endDate.getDate() - days)
const formattedStartDate = startDate.toISOString().split('T')[0]
const formattedEndDate = endDate.toISOString().split('T')[0]
return { startDate: formattedStartDate, endDate: formattedEndDate }
}
async function submitConnectStrava(stravaClient) {
const array = new Uint8Array(16)
window.crypto.getRandomValues(array)
@@ -326,9 +355,21 @@ async function submitConnectStrava(stravaClient) {
}
}
async function submitRetrieveStravaActivities(daysToRetrieveStrava) {
async function submitRetrieveStravaActivitiesDays(days) {
try {
await strava.getStravaActivitiesLastDays(daysToRetrieveStrava)
const dates = getStartAndEndDateFromDaysAgo(days)
await strava.getStravaActivitiesByDates(dates.startDate, dates.endDate)
push.info(t('settingsIntegrationsZone.loadingMessageRetrievingStravaActivities'))
} catch (error) {
push.error(
`${t('settingsIntegrationsZone.errorMessageUnableToGetStravaActivities')} - ${error}`
)
}
}
async function submitRetrieveStravaActivitiesDataRange(dateRange) {
try {
await strava.getStravaActivitiesByDates(dateRange.startDate, dateRange.endDate)
push.info(t('settingsIntegrationsZone.loadingMessageRetrievingStravaActivities'))
} catch (error) {
push.error(
@@ -363,12 +404,8 @@ async function buttonStravaUnlink() {
async function submitRetrieveGarminConnectActivitiesDays(days) {
try {
const endDate = new Date()
const startDate = new Date()
startDate.setDate(endDate.getDate() - days)
const formattedStartDate = startDate.toISOString().split('T')[0]
const formattedEndDate = endDate.toISOString().split('T')[0]
await garminConnect.getGarminConnectActivitiesByDates(formattedStartDate, formattedEndDate)
const dates = getStartAndEndDateFromDaysAgo(days)
await garminConnect.getGarminConnectActivitiesByDates(dates.startDate, dates.endDate)
push.info(t('settingsIntegrationsZone.loadingMessageRetrievingGarminConnectActivities'))
} catch (error) {
push.error(
@@ -412,12 +449,8 @@ async function submitRetrieveGarminConnectHealthDataDataRange(dateRange) {
async function submitRetrieveGarminConnectHealthDataDays(days) {
try {
const endDate = new Date()
const startDate = new Date()
startDate.setDate(endDate.getDate() - days)
const formattedStartDate = startDate.toISOString().split('T')[0]
const formattedEndDate = endDate.toISOString().split('T')[0]
await garminConnect.getGarminConnectHealthDataByDates(formattedStartDate, formattedEndDate)
const dates = getStartAndEndDateFromDaysAgo(days)
await garminConnect.getGarminConnectHealthDataByDates(dates.startDate, dates.endDate)
push.info(t('settingsIntegrationsZone.loadingMessageRetrievingGarminConnectHealthData'))
} catch (error) {
push.error(

View File

@@ -35,8 +35,8 @@ export const strava = {
linkStravaCallback(state, code, scope) {
return fetchPutRequest(`strava/link?state=${state}&code=${code}&scope=${scope}`)
},
getStravaActivitiesLastDays(days) {
return fetchGetRequest(`strava/activities/days/${days}`)
getStravaActivitiesByDates(startDate, endDate) {
return fetchGetRequest(`strava/activities?start_date=${startDate}&end_date=${endDate}`)
},
getStravaGear() {
return fetchGetRequest('strava/gear')