Backend revamp

[README] Added new FRONTEND_HOST to backend env table
[README] Added link to Endurain Mastodon profile
[docker-compose] Added new env variable
[backend] (Re)Added scheduler to refresh strava tokens periodically
[backend] Strava activities flow finished
[backend] Strava gear id in activity and gear DB tables changed from int to str (Strava uses a string for gear id)
[frontend] Fixed some issues related to new backend logic
[frontend] It is not possible to delete a Strava activity from UI
[frontend] Link to Strava activity replaced with a logo
This commit is contained in:
João Vitória Silva
2024-02-09 11:24:54 +00:00
parent bbae13c1bf
commit 61a0be196f
17 changed files with 335 additions and 207 deletions

View File

@@ -91,7 +91,7 @@ JAEGER_PROTOCOL | http | Yes
JAEGER_HOST | jaeger | Yes
JAGGER_PORT | 4317 | Yes
STRAVA_DAYS_ACTIVITIES_ONLINK | 30 | Yes
API_ENDPOINT* | changeme | Yes
API_ENDPOINT* | backend | Yes
FRONTEND_HOST* | frontend | Yes
GEOCODES_MAPS_API** | changeme | `No`

View File

@@ -15,6 +15,6 @@ JAEGER_PROTOCOL=http
JAEGER_HOST=jaeger
JAGGER_PORT=4317
STRAVA_DAYS_ACTIVITIES_ONLINK=30
API_ENDPOINT=changeme
API_ENDPOINT=backend
FRONTEND_HOST=frontend
GEOCODES_MAPS_API=changeme

View File

@@ -44,6 +44,20 @@ def authenticate_user(username: str, password: str, db: Session):
) from err
def get_all_users(db: Session):
try:
# Get the number of users from the database
return db.query(models.User).all()
except Exception as err:
# Log the exception
logger.error(f"Error in get_all_number: {err}", exc_info=True)
# Raise an HTTPException with a 500 Internal Server Error status code
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Internal Server Error",
) from err
def get_users_number(db: Session):
try:
# Get the number of users from the database

View File

@@ -19,12 +19,13 @@ from routers import (
router_activity_streams,
router_gear,
router_followers,
router_strava
router_strava,
)
from constants import API_VERSION
from database import engine
from schemas import schema_access_tokens
from dependencies import dependencies_database
from processors import strava_processor
import models
models.Base.metadata.create_all(bind=engine)
@@ -40,6 +41,8 @@ def startup_event():
# Job to remove expired tokens every 5 minutes
logger.info("Added scheduler job to remove expired tokens every 5 minutes")
scheduler.add_job(remove_expired_tokens_job, "interval", minutes=5)
logger.info("Added scheduler job to refresh strava user tokens every 60 minutes")
scheduler.add_job(remove_expired_tokens_job, "interval", minutes=60)
def shutdown_event():
@@ -61,6 +64,17 @@ def remove_expired_tokens_job():
db.close()
def refresh_strava_tokens_job():
# Get the first (and only) item from the generator
db = next(dependencies_database.get_db())
try:
# Refresh Strava tokens
strava_processor.refresh_strava_tokens(db=db)
finally:
# Ensure the session is closed after use
db.close()
# Create loggger
logger = logging.getLogger("myLogger")
logger.setLevel(logging.DEBUG)
@@ -153,4 +167,4 @@ FastAPIInstrumentor.instrument_app(app)
app.add_event_handler("startup", startup_event)
# Register the shutdown event handler
app.add_event_handler("shutdown", shutdown_event)
app.add_event_handler("shutdown", shutdown_event)

View File

@@ -217,7 +217,7 @@ class Gear(Base):
is_active = Column(
Integer, nullable=False, comment="Is gear active (0 - not active, 1 - active)"
)
strava_gear_id = Column(BigInteger, nullable=True, comment="Strava gear ID")
strava_gear_id = Column(String(length=45), nullable=True, comment="Strava gear ID")
# Define a relationship to the User model
user = relationship("User", back_populates="gear")
@@ -295,7 +295,7 @@ class Activity(Base):
index=True,
comment="Gear ID associated with this activity",
)
strava_gear_id = Column(BigInteger, nullable=True, comment="Strava gear ID")
strava_gear_id = Column(String(length=45), nullable=True, comment="Strava gear ID")
strava_activity_id = Column(BigInteger, nullable=True, comment="Strava activity ID")
# Define a relationship to the User model

View File

@@ -215,9 +215,10 @@ def define_activity_type(activity_type):
"cycling": 4,
"Ride": 4,
"GravelRide": 5,
"EBikeRide": 6,
"MountainBikeRide": 6,
"VirtualRide": 7,
"virtual_ride": 7,
"Swim": 8,
"swimming": 8,
"open_water_swimming": 8,
"Walk": 9,

View File

@@ -1,21 +1,78 @@
import logging
import os
import requests
from datetime import datetime, timedelta
from fastapi import HTTPException, status
from sqlalchemy.orm import Session
from stravalib.client import Client
from concurrent.futures import ThreadPoolExecutor
from pint import Quantity
from schemas import schema_activities, schema_activity_streams
from crud import crud_user_integrations, crud_activities, crud_activity_streams
from schemas import schema_activities, schema_activity_streams, schema_user_integrations
from crud import crud_user_integrations, crud_activities, crud_activity_streams, crud_users
from dependencies import dependencies_database
from processors import activity_processor
# Define a loggger created on main.py
logger = logging.getLogger("myLogger")
def get_user_strava_activities_by_days(start_date: datetime, user_id: int, db: Session):
def refresh_strava_token(db: Session):
# Get all users
users = crud_users.get_all_users(db)
# Iterate through all users
for user in users:
# Get the user integrations by user ID
user_integrations = crud_user_integrations.get_user_integrations_by_user_id(user.id, db)
# Check if user_integrations strava token is not None
if user_integrations.strava_token is not None:
refresh_time = user_integrations.strava_token_expires_at - timedelta(
minutes=60
)
if datetime.utcnow() > refresh_time:
# Strava token refresh endpoint
token_url = "https://www.strava.com/oauth/token"
# Parameters for the token refresh request
payload = {
"client_id": os.environ.get("STRAVA_CLIENT_ID"),
"client_secret": os.environ.get("STRAVA_CLIENT_SECRET"),
"refresh_token": user_integrations.strava_refresh_token,
"grant_type": "refresh_token",
}
try:
# Send a POST request to the token URL
response = requests.post(token_url, data=payload)
# Check if the response status code is not 200
if response.status_code != 200:
# Raise an HTTPException with a 424 Failed Dependency status code
logger.error("Unable to retrieve tokens for refresh process from Strava")
tokens = response.json()
except Exception as err:
# Log the exception
logger.error(f"Error in refresh_strava_token: {err}", exc_info=True)
# Raise an HTTPException with a 500 Internal Server Error status code
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Internal Server Error",
) from err
finally:
# Update the user integrations with the tokens
crud_user_integrations.link_strava_account(user_integrations, tokens, db)
#else:
# Log an informational event if the Strava access token is not found
#logger.info(f"User {user.id}: Strava access token not found")
def fetch_user_integrations_and_validate_token(
user_id: int, db: Session
) -> schema_user_integrations.UserIntegrations:
# Get the user integrations by user ID
user_integrations = crud_user_integrations.get_user_integrations_by_user_id(
user_id, db
@@ -35,12 +92,20 @@ def get_user_strava_activities_by_days(start_date: datetime, user_id: int, db: S
detail="Strava access token not found",
)
# Log the start of the activities processing
logger.info(f"User {user_id}: Started activities processing")
# Return the user integrations
return user_integrations
# Create a Strava client with the user's access token
strava_client = Client(access_token=user_integrations.strava_token)
def create_strava_client(
user_integrations: schema_user_integrations.UserIntegrations,
) -> Client:
# Create a Strava client with the user's access token and return it
return Client(access_token=user_integrations.strava_token)
def fetch_and_process_activities(
strava_client: Client, start_date: datetime, user_id: int, db: Session
) -> int:
# Fetch Strava activities after the specified start date
strava_activities = list(strava_client.get_activities(after=start_date))
@@ -50,32 +115,39 @@ def get_user_strava_activities_by_days(start_date: datetime, user_id: int, db: S
f"User {user_id}: No new activities found after {start_date}: strava_activities is None"
)
# Use ThreadPoolExecutor for parallel processing of activities
with ThreadPoolExecutor() as executor:
executor.map(
lambda activity: process_activity(activity, user_id, strava_client, db),
strava_activities,
)
# Return 0 to indicate no activities were processed
return 0
# Log an informational event for tracing
logger.info(f"User {user_id}: {len(strava_activities)} activities processed")
# Process the activities
for activity in strava_activities:
process_activity(activity, user_id, strava_client, db)
# Return the number of activities processed
return len(strava_activities)
def process_activity(activity, user_id, strava_client, db: Session):
def fetch_and_validate_activity(
activity_id: int, user_id: int, db: Session
) -> schema_activities.Activity | None:
# Get the activity by Strava ID from the user
activity = crud_activities.get_activity_by_strava_id_from_user_id(
activity.id, user_id, db
activity_db = crud_activities.get_activity_by_strava_id_from_user_id(
activity_id, user_id, db
)
# Check if activity is None
if activity:
if activity_db:
# Log an informational event if the activity already exists
logger.info(
f"User {user_id}: Activity {activity.id} already exists. Will skip processing"
f"User {user_id}: Activity {activity_id} already exists. Will skip processing"
)
# Return None
return activity_db
else:
return None
def parse_activity(activity, user_id: int, strava_client: Client) -> dict:
# Parse start and end dates
start_date_parsed = activity.start_date
@@ -183,8 +255,15 @@ def process_activity(activity, user_id, strava_client, db: Session):
is_power_set = True
for i in range(len(vel)):
# Append velocity to the velocity waypoints
vel_waypoints.append({"time": time[i], "vel": vel[i]})
pace_calculation = 1 / vel[i]
# Calculate pace on-the-fly. If velocity is 0, pace is 0
pace_calculation = 0
if vel[i] != 0:
pace_calculation = 1 / vel[i]
# Append pace to the pace waypoints
pace_waypoints.append({"time": time[i], "pace": pace_calculation})
is_velocity_set = True
@@ -203,37 +282,6 @@ def process_activity(activity, user_id, strava_client, db: Session):
if activity.average_watts is not None:
average_watts = activity.average_watts
# Create the activity in the database
created_activity = crud_activities.create_activity(
schema_activities.Activity(
user_id=user_id,
name=activity.name,
distance=(
round(float(activity.distance))
if isinstance(activity.distance, Quantity)
else round(activity.distance)
),
activity_type=activity_processor.define_activity_type(activity.sport_type),
start_time=start_date_parsed,
end_time=end_date_parsed,
city=city,
town=town,
country=country,
waypoints=waypoints,
elevation_gain=elevation_gain,
elevation_loss=elevation_loss,
pace=average_pace,
average_speed=average_speed,
average_power=average_watts,
calories=activity.calories,
strava_gear_id=activity.gear_id,
strava_activity_id=activity.id,
),
db,
)
activity_streams = []
# List of conditions, stream types, and corresponding waypoints
stream_data = [
(is_heart_rate_set, 1, hr_waypoints),
@@ -245,8 +293,48 @@ def process_activity(activity, user_id, strava_client, db: Session):
(latitude is not None and longitude is not None, 7, lat_lon_waypoints),
]
for condition, stream_type, waypoints in stream_data:
if condition:
# Create the activity object
activity_to_store = schema_activities.Activity(
user_id=user_id,
name=activity.name,
distance=(
round(float(activity.distance))
if isinstance(activity.distance, Quantity)
else round(activity.distance)
),
activity_type=activity_processor.define_activity_type(activity.sport_type),
start_time=start_date_parsed.strftime("%Y-%m-%dT%H:%M:%S"),
end_time=end_date_parsed.strftime("%Y-%m-%dT%H:%M:%S"),
city=city,
town=town,
country=country,
waypoints=waypoints,
elevation_gain=elevation_gain,
elevation_loss=elevation_loss,
pace=average_pace,
average_speed=average_speed,
average_power=average_watts,
calories=activity.calories,
strava_gear_id=activity.gear_id,
strava_activity_id=int(activity.id),
)
# Return the activity and stream data
return {"activity_to_store": activity_to_store, "stream_data": stream_data}
def save_activity_and_streams(
activity: schema_activities.Activity, stream_data: list, db: Session
):
# Create the activity and get the ID
created_activity = crud_activities.create_activity(activity, db)
# Create the empty array of activity streams
activity_streams = []
# Create the activity streams objects
for is_set, stream_type, waypoints in stream_data:
if is_set:
activity_streams.append(
schema_activity_streams.ActivityStreams(
activity_id=created_activity.id,
@@ -256,77 +344,53 @@ def process_activity(activity, user_id, strava_client, db: Session):
)
)
# Create the activity streams in the database
crud_activity_streams.create_activity_streams(activity_streams, db)
""" activity_streams = []
if is_heart_rate_set:
activity_streams.append(
schema_activity_streams.ActivityStreams(
activity_id=created_activity.id,
stream_type=1,
stream_waypoints=hr_waypoints,
strava_activity_stream_id=None,
)
)
if is_power_set:
activity_streams.append(
schema_activity_streams.ActivityStreams(
activity_id=created_activity.id,
stream_type=2,
stream_waypoints=power_waypoints,
strava_activity_stream_id=None,
)
def process_activity(activity, user_id: int, strava_client: Client, db: Session):
# Get the activity by Strava ID from the user
activity_db = fetch_and_validate_activity(activity.id, user_id, db)
# Check if activity is None and return None if it is
if activity_db is not None:
return None
# Log an informational event for activity processing
logger.info(f"User {user_id}: Activity {activity.id} will be processed")
# Parse the activity and streams
parsed_activity = parse_activity(activity, user_id, strava_client)
# Save the activity and streams to the database
save_activity_and_streams(
parsed_activity["activity_to_store"], parsed_activity["stream_data"], db
)
def get_user_strava_activities_by_days(start_date: datetime, user_id: int):
# Get the first (and only) item from the generator
db = next(dependencies_database.get_db())
try:
# Get the user integrations by user ID
user_integrations = fetch_user_integrations_and_validate_token(user_id, db)
# Log the start of the activities processing
logger.info(f"User {user_id}: Started activities processing")
# Create a Strava client with the user's access token
strava_client = create_strava_client(user_integrations)
# Fetch Strava activities after the specified start date
num_strava_activities_processed = fetch_and_process_activities(
strava_client, start_date, user_id, db
)
if is_cadence_set:
activity_streams.append(
schema_activity_streams.ActivityStreams(
activity_id=created_activity.id,
stream_type=3,
stream_waypoints=cad_waypoints,
strava_activity_stream_id=None,
)
# Log an informational event for tracing
logger.info(
f"User {user_id}: {num_strava_activities_processed} activities processed"
)
if is_elevation_set:
activity_streams.append(
schema_activity_streams.ActivityStreams(
activity_id=created_activity.id,
stream_type=4,
stream_waypoints=ele_waypoints,
strava_activity_stream_id=None,
)
)
if is_velocity_set:
activity_streams.append(
schema_activity_streams.ActivityStreams(
activity_id=created_activity.id,
stream_type=5,
stream_waypoints=vel_waypoints,
strava_activity_stream_id=None,
)
)
if is_velocity_set:
activity_streams.append(
schema_activity_streams.ActivityStreams(
activity_id=created_activity.id,
stream_type=6,
stream_waypoints=pace_waypoints,
strava_activity_stream_id=None,
)
)
if latitude is not None and longitude is not None:
activity_streams.append(
schema_activity_streams.ActivityStreams(
activity_id=created_activity.id,
stream_type=7,
stream_waypoints=lat_lon_waypoints,
strava_activity_stream_id=None,
)
) """
finally:
# Ensure the session is closed after use
db.close()

View File

@@ -3,7 +3,7 @@ import requests
import os
from datetime import datetime, timedelta
from typing import Annotated, Callable
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks
from fastapi.security import OAuth2PasswordBearer
from fastapi.responses import RedirectResponse
@@ -80,7 +80,7 @@ async def strava_link(
redirect_url = (
"https://"
+ os.environ.get("FRONTEND_HOST")
+ "/settings/settings.php?profileSettings=1&stravaLinked=1"
+ "/settings/settings.php?integrationsSettings=1&stravaLinked=1"
)
# Return a RedirectResponse to the redirect URL
@@ -106,7 +106,7 @@ async def strava_retrieve_activities_days(
user_id: Annotated[
int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)
],
db: Annotated[Session, Depends(dependencies_database.get_db)],
#db: Annotated[Session, Depends(dependencies_database.get_db)],
background_tasks: BackgroundTasks,
):
# Process strava activities in the background
@@ -114,13 +114,12 @@ async def strava_retrieve_activities_days(
strava_processor.get_user_strava_activities_by_days,
(datetime.utcnow() - timedelta(days=days)).strftime("%Y-%m-%dT%H:%M:%S"),
user_id,
db,
)
# Return success message and status code 202
logger.info(f"Strava activities will be processed in the background for {user_id}")
logger.info(f"Strava activities will be processed in the background for user {user_id}")
return {
"detail": f"Strava activities will be processed in the background for {user_id}"
"detail": f"Strava activities will be processed in the background for for {user_id}"
}

View File

@@ -21,7 +21,7 @@ class Activity(BaseModel):
calories: int | None = None
visibility: int | None = None
gear_id: int | None = None
strava_gear_id: int | None = None
strava_gear_id: str | None = None
strava_activity_id: int | None = None
class Config:

View File

@@ -9,7 +9,7 @@ class Gear(BaseModel):
user_id: int | None = None
created_at: str
is_active: int | None = None
strava_gear_id: int | None = None
strava_gear_id: str | None = None
class Config:
orm_mode = True

View File

@@ -240,19 +240,28 @@ if($activityUser["id"] == $_SESSION["id"]){
</div>
</div>
<div class="dropdown d-flex">
<?php if (isset($activity['strava_activity_id'])) { ?>
<a class="btn btn-link btn-lg mt-1" href="https://www.strava.com/activities/<?php echo $activity['strava_activity_id']; ?>" role="button">
<i class="fa-brands fa-strava"></i>
</a>
<?php } ?>
<button class="btn btn-link btn-lg" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-ellipsis-vertical"></i>
</button>
<ul class="dropdown-menu">
<?php if ($activity['strava_activity_id'] != null) { ?>
<li><a class="dropdown-item"
<!--<?php if (isset($activity['strava_activity_id'])) { ?>
<li>
<a class="dropdown-item"
href="https://www.strava.com/activities/<?php echo $activity['strava_activity_id']; ?>">
<?php echo $translationsActivitiesActivity['activity_title_dropdown_seeItOnStrava']; ?>
</a></li>
<?php } ?>
<li><a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#deleteActivityModal">
</a>
</li>
<?php } ?>-->
<li>
<a class="dropdown-item <?php if(isset($activity["strava_activity_id"])){ echo "disabled"; } ?>" href="#" data-bs-toggle="modal" data-bs-target="#deleteActivityModal">
<?php echo $translationsActivitiesActivity['activity_title_dropdown_deleteActivity']; ?>
</a></li>
</a>
</li>
</ul>
</div>
</div>
@@ -415,6 +424,15 @@ if($activityUser["id"] == $_SESSION["id"]){
<div class="mt-3 mb-3" id="map" style="height: 500px"></div>
<?php } ?>
<!--<?php if (isset($activity['strava_activity_id'])) { ?>
<?php if (!isset($latlonStream)) { ?>
<br>
<?php } ?>
<a href="https://www.strava.com/activities/<?php echo $activity['strava_activity_id']; ?>">
<?php echo $translationsActivitiesActivity['activity_title_dropdown_seeItOnStrava']; ?>
</a>
<?php } ?>-->
<script>
// JavaScript code to create the map for this activity

View File

@@ -33,23 +33,26 @@ function unsetUniqueUserStateStravaLink()
function linkStrava($state)
{
// Example PHP code for the authentication link
$client_id = '115321';
$redirect_uri = urlencode(getenv('BACKEND_PROTOCOL').'://'.getenv('BACKEND_URL').'/strava/link');
$scope = 'read,read_all,profile:read_all,activity:read,activity:read_all'; // Set your required scope
$redirect_uri = urlencode(getenv('BACKEND_PROTOCOL').'://api-gearguardian.jvslab.pt/strava/link');
$scope = 'read,read_all,profile:read_all,activity:read,activity:read_all';
$strava_auth_url = "http://www.strava.com/oauth/authorize?client_id={$client_id}&response_type=code&redirect_uri={$redirect_uri}&approval_prompt=force&scope={$scope}&state={$state}";
header("Location: " . $strava_auth_url);
#header("Location: " . $strava_auth_url);
echo "<script>location.href = '$strava_auth_url';</script>";
#<meta http-equiv="Location" content=$strava_auth_url>
}
function getStravaActivitiesLastDays($days)
{
$response = callAPIRoute("/strava/activities/days/$days", 0, 0, NULL);
$response = callAPIRoute("/strava/activities/days/$days", 1, 0, NULL);
if ($response[0] === false) {
return -1;
} else {
if ($response[1] === 200) {
if ($response[1] === 202) {
return 0;
} else {
return -2;

View File

@@ -341,66 +341,79 @@ $thisMonthDistances = getUserActivitiesThisMonthDistances($_SESSION["id"]);
<?php if (isset($userActivities)) { ?>
<?php foreach ($userActivities as $activity) { ?>
<?php
$activityStream = getActivityActivitiesStreamByStreamType($activity["id"],7);
if($activityStream["stream_type"] == 7){
$latlonStream = $activityStream["stream_waypoints"];
$activityStream = getActivityActivitiesStreamByStreamType($activity["id"],7);
if(isset($activityStream)){
if($activityStream["stream_type"] == 7){
$latlonStream = $activityStream["stream_waypoints"];
}
}else{
$latlonStream = NULL;
}
?>
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<img src=<?php if (is_null($_SESSION["photo_path"])) {
if ($_SESSION["gender"] == 1) {
echo ("../img/avatar/male1.png");
} else {
echo ("../img/avatar/female1.png");
}
} else {
echo ($_SESSION["photo_path"]);
} ?> alt="userPicture" class="rounded-circle" width="55" height="55">
<div class="ms-3 me-3">
<div class="fw-bold">
<a href="activities/activity.php?activityID=<?php echo ($activity["id"]); ?>"
class="link-underline-opacity-25 link-underline-opacity-100-hover">
<?php echo ($activity["name"]); ?>
</a>
</div>
<h7>
<?php if ($activity["activity_type"] == 1 || $activity["activity_type"] == 2) {
echo '<i class="fa-solid fa-person-running"></i>';
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center">
<img src=<?php if (is_null($_SESSION["photo_path"])) {
if ($_SESSION["gender"] == 1) {
echo ("../img/avatar/male1.png");
} else {
if ($activity["activity_type"] == 3) {
echo '<i class="fa-solid fa-person-running"></i> (Virtual)';
echo ("../img/avatar/female1.png");
}
} else {
echo ($_SESSION["photo_path"]);
} ?> alt="userPicture" class="rounded-circle" width="55" height="55">
<div class="ms-3 me-3">
<div class="fw-bold">
<a href="activities/activity.php?activityID=<?php echo ($activity["id"]); ?>"
class="link-underline-opacity-25 link-underline-opacity-100-hover">
<?php echo ($activity["name"]); ?>
</a>
</div>
<h7>
<?php if ($activity["activity_type"] == 1 || $activity["activity_type"] == 2) {
echo '<i class="fa-solid fa-person-running"></i>';
} else {
if ($activity["activity_type"] == 4 || $activity["activity_type"] == 5 || $activity["activity_type"] == 6) {
echo '<i class="fa-solid fa-person-biking"></i>';
if ($activity["activity_type"] == 3) {
echo '<i class="fa-solid fa-person-running"></i> (Virtual)';
} else {
if ($activity["activity_type"] == 7) {
echo '<i class="fa-solid fa-person-biking"></i> (Virtual)';
if ($activity["activity_type"] == 4 || $activity["activity_type"] == 5 || $activity["activity_type"] == 6) {
echo '<i class="fa-solid fa-person-biking"></i>';
} else {
if ($activity["activity_type"] == 8 || $activity["activity_type"] == 9) {
echo '<i class="fa-solid fa-person-swimming"></i>';
if ($activity["activity_type"] == 7) {
echo '<i class="fa-solid fa-person-biking"></i> (Virtual)';
} else {
if ($activity["activity_type"] == 10) {
echo '<i class="fa-solid fa-dumbbell"></i>';
if ($activity["activity_type"] == 8 || $activity["activity_type"] == 9) {
echo '<i class="fa-solid fa-person-swimming"></i>';
} else {
if ($activity["activity_type"] == 10) {
echo '<i class="fa-solid fa-dumbbell"></i>';
}
}
}
}
}
}
} ?>
<?php echo (new DateTime($activity["start_time"]))->format("d/m/y"); ?>@
<?php echo (new DateTime($activity["start_time"]))->format("H:i"); ?>
<?php if (isset($activity["city"]) || isset($activity["country"])) {
echo " - ";
} ?>
<?php if (isset($activity["city"]) && !empty($activity["city"])) {
echo $activity["city"] . ", ";
} ?>
<?php if (isset($activity["country"]) && !empty($activity["country"])) {
echo $activity["country"];
} ?>
</h7>
} ?>
<?php echo (new DateTime($activity["start_time"]))->format("d/m/y"); ?>@
<?php echo (new DateTime($activity["start_time"]))->format("H:i"); ?>
<?php if (isset($activity["city"]) || isset($activity["country"])) {
echo " - ";
} ?>
<?php if (isset($activity["city"]) && !empty($activity["city"])) {
echo $activity["city"] . ", ";
} ?>
<?php if (isset($activity["country"]) && !empty($activity["country"])) {
echo $activity["country"];
} ?>
</h7>
</div>
</div>
<div class="dropdown d-flex">
<?php if (isset($activity['strava_activity_id'])) { ?>
<a class="btn btn-link btn-lg mt-1" href="https://www.strava.com/activities/<?php echo $activity['strava_activity_id']; ?>" role="button">
<i class="fa-brands fa-strava"></i>
</a>
<?php } ?>
</div>
</div>
<div class="row d-flex mt-3">
@@ -468,7 +481,7 @@ $thisMonthDistances = getUserActivitiesThisMonthDistances($_SESSION["id"]);
echo "mb-3";
} ?>" id="map_<?php echo $activity['id']; ?>" style="height: 300px"></div>
<?php } ?>
<?php if ($activity['strava_activity_id'] != null) { ?>
<!--<?php if ($activity['strava_activity_id'] != null) { ?>
<div class="mb-3">
<span class="fw-lighter ms-3 me-3">
<?php echo $translationsIndex['index_activities_stravaText1']; ?><a
@@ -478,7 +491,7 @@ $thisMonthDistances = getUserActivitiesThisMonthDistances($_SESSION["id"]);
</a>
</span>
</div>
<?php } ?>
<?php } ?>-->
</div>
<br>
<script>

View File

@@ -5,7 +5,7 @@ return [
// success banner zone
"settings_integration_settings_success_stravaLinked" => "Strava linked with success",
"settings_integration_settings_success_stravaGear" => "Strava gear retrieved",
"settings_integration_settings_success_stravaActivities" => "Strava activities retrieved",
"settings_integration_settings_success_stravaActivities" => "Strava activities retrieval background process started",
// card zone
"settings_integration_settings_strava_title" => "Strava",
"settings_integration_settings_strava_body" => "Strava is an American internet service for tracking physical exercise which incorporates social network features.",

View File

@@ -36,7 +36,6 @@
$strava_gear_result = getStravaGear();
}
?>
<!-- Error banners -->
<?php if ($strava_gear_result == -1 || $strava_gear_result == -2 || $strava_activities_result == -1 || $strava_activities_result == -2) { ?>
<div class="alert alert-danger alert-dismissible d-flex align-items-center" role="alert">
@@ -84,12 +83,12 @@
<div class="card-body">
<h4 class="card-title"><?php echo $translationsSettingsIntegrationSettings['settings_integration_settings_strava_title']; ?></h4>
<p class="card-text"><?php echo $translationsSettingsIntegrationSettings['settings_integration_settings_strava_body']; ?></p>
<a href="../settings/settings.php?integrationSettings=1&linkStrava=1" class="btn btn-primary <?php if ($_SESSION["is_strava_linked"] == 1) { echo "disabled"; } ?>"><?php echo $translationsSettingsIntegrationSettings['settings_integration_settings_connect_button']; ?></a>
<a href="../settings/settings.php?integrationsSettings=1&linkStrava=1" class="btn btn-primary <?php if ($_SESSION["is_strava_linked"] == 1) { echo "disabled"; } ?>"><?php echo $translationsSettingsIntegrationSettings['settings_integration_settings_connect_button']; ?></a>
<?php if ($_SESSION["is_strava_linked"] == 1) { ?>
<hr>
<a href="../settings/settings.php?integrationSettings=1&getUserStravaActivities=1" class="btn btn-primary"><?php echo $translationsSettingsIntegrationSettings['settings_integration_settings_retrieve_last_week_activities_button']; ?></a>
<a href="../settings/settings.php?integrationsSettings=1&getUserStravaActivities=1" class="btn btn-primary"><?php echo $translationsSettingsIntegrationSettings['settings_integration_settings_retrieve_last_week_activities_button']; ?></a>
<a href="../settings/settings.php?integrationSettings=1&getUserStravaGear=1" class="btn btn-primary mt-3"><?php echo $translationsSettingsIntegrationSettings['settings_integration_settings_retrieve_gear_button']; ?></a>
<a href="../settings/settings.php?integrationsSettings=1&getUserStravaGear=1" class="btn btn-primary mt-3"><?php echo $translationsSettingsIntegrationSettings['settings_integration_settings_retrieve_gear_button']; ?></a>
<?php } ?>
</div>
</div>

View File

@@ -36,7 +36,6 @@ switch ($_SESSION["preferred_language"]) {
default:
$translationsSettings = include $_SERVER['DOCUMENT_ROOT'] . '/lang/settings/en.php';
}
?>
<?php require_once $_SERVER['DOCUMENT_ROOT'] . "/inc/Template-Top.php" ?>
<div class="container mt-4">

View File

@@ -531,8 +531,12 @@ $userFollowingAll = getUserFollowingAll($_GET["userID"]);
<?php } else { ?>
<?php foreach ($weekActivities as $activity) { ?>
<?php $activityStream = getActivityActivitiesStreamByStreamType($activity["id"],7);
if($activityStream["stream_type"] == 7){
$latlonStream = $activityStream["stream_waypoints"];
if (isset($activityStream)){
if($activityStream["stream_type"] == 7){
$latlonStream = $activityStream["stream_waypoints"];
}
}else{
$latlonStream = null;
}
?>
<div class="card">