mirror of
https://github.com/joaovitoriasilva/endurain.git
synced 2026-05-03 03:00:41 -04:00
Public activity unauthenticated + server_settings
[backend] added public router logic to activities, activity streams and server_settings [backend] ability to query activity data and activity streams data if activity is public [backend] started server_settings logic with units value [backend] bump version to v0.9.0 [frontend] bump version to v0.9.0 [frontend] added validations to allow unauthenticated activity queries if activity is public [frontend] added new public routes
This commit is contained in:
@@ -404,6 +404,40 @@ def get_activity_by_id_from_user_id_or_has_visibility(
|
||||
) from err
|
||||
|
||||
|
||||
def get_activity_by_id_if_is_public(activity_id: int, db: Session):
|
||||
try:
|
||||
# Get the activities from the database
|
||||
activity = (
|
||||
db.query(activities_models.Activity)
|
||||
.filter(
|
||||
activities_models.Activity.visibility == 0,
|
||||
activities_models.Activity.id == activity_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
# Check if there are activities if not return None
|
||||
if not activity:
|
||||
return None
|
||||
|
||||
activity = activities_utils.serialize_activity(activity)
|
||||
|
||||
# Return the activities
|
||||
return activity
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_activity_by_id_if_is_public: {err}",
|
||||
"error",
|
||||
exc=err,
|
||||
)
|
||||
# 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_activity_by_id_from_user_id(
|
||||
activity_id: int, user_id: int, db: Session
|
||||
) -> activities_schema.Activity:
|
||||
|
||||
36
backend/app/activities/public_router.py
Normal file
36
backend/app/activities/public_router.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from typing import Annotated, Callable
|
||||
|
||||
from fastapi import (
|
||||
APIRouter,
|
||||
Depends,
|
||||
Security,
|
||||
)
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
import activities.schema as activities_schema
|
||||
import activities.crud as activities_crud
|
||||
import activities.dependencies as activities_dependencies
|
||||
|
||||
import core.database as core_database
|
||||
|
||||
# Define the API router
|
||||
router = APIRouter()
|
||||
|
||||
@router.get(
|
||||
"/{activity_id}",
|
||||
response_model=activities_schema.Activity | None,
|
||||
)
|
||||
async def read_public_activities_activity_from_id(
|
||||
activity_id: int,
|
||||
validate_activity_id: Annotated[
|
||||
Callable, Depends(activities_dependencies.validate_activity_id)
|
||||
],
|
||||
db: Annotated[
|
||||
Session,
|
||||
Depends(core_database.get_db),
|
||||
],
|
||||
):
|
||||
# Get the activity from the database and return it
|
||||
return activities_crud.get_activity_by_id_if_is_public(
|
||||
activity_id, db
|
||||
)
|
||||
@@ -1,6 +1,5 @@
|
||||
import os
|
||||
import glob
|
||||
import logging
|
||||
import calendar
|
||||
|
||||
from typing import Annotated, Callable
|
||||
|
||||
@@ -6,6 +6,8 @@ from sqlalchemy.orm import Session
|
||||
import activity_streams.schema as activity_streams_schema
|
||||
import activity_streams.models as activity_streams_models
|
||||
|
||||
import activities.models as activities_models
|
||||
|
||||
import core.logger as core_logger
|
||||
|
||||
|
||||
@@ -28,7 +30,46 @@ def get_activity_streams(activity_id: int, db: Session):
|
||||
return activity_streams
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_activity_streams: {err}", "error", exc=err)
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_activity_streams: {err}", "error", exc=err
|
||||
)
|
||||
# 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_public_activity_streams(activity_id: int, db: Session):
|
||||
try:
|
||||
# Get the activity streams from the database
|
||||
activity_streams = (
|
||||
db.query(activity_streams_models.ActivityStreams)
|
||||
.join(
|
||||
activities_models.Activity,
|
||||
activities_models.Activity.id
|
||||
== activity_streams_models.ActivityStreams.activity_id,
|
||||
)
|
||||
.filter(
|
||||
activity_streams_models.ActivityStreams.activity_id == activity_id,
|
||||
activities_models.Activity.visibility == 0,
|
||||
activities_models.Activity.id
|
||||
== activity_id,
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
# Check if there are activity streams, if not return None
|
||||
if not activity_streams:
|
||||
return None
|
||||
|
||||
# Return the activity streams
|
||||
return activity_streams
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_public_activity_streams: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -56,7 +97,47 @@ def get_activity_stream_by_type(activity_id: int, stream_type: int, db: Session)
|
||||
return activity_stream
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_activity_stream_by_type: {err}", "error", exc=err)
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_activity_stream_by_type: {err}", "error", exc=err
|
||||
)
|
||||
# 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_public_activity_stream_by_type(activity_id: int, stream_type: int, db: Session):
|
||||
try:
|
||||
# Get the activity stream from the database
|
||||
activity_stream = (
|
||||
db.query(activity_streams_models.ActivityStreams)
|
||||
.join(
|
||||
activities_models.Activity,
|
||||
activities_models.Activity.id
|
||||
== activity_streams_models.ActivityStreams.activity_id,
|
||||
)
|
||||
.filter(
|
||||
activity_streams_models.ActivityStreams.activity_id == activity_id,
|
||||
activity_streams_models.ActivityStreams.stream_type == stream_type,
|
||||
activities_models.Activity.visibility == 0,
|
||||
activities_models.Activity.id
|
||||
== activity_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
# Check if there is an activity stream; if not, return None
|
||||
if not activity_stream:
|
||||
return None
|
||||
|
||||
# Return the activity stream
|
||||
return activity_stream
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_public_activity_stream_by_type: {err}", "error", exc=err
|
||||
)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
|
||||
57
backend/app/activity_streams/public_router.py
Normal file
57
backend/app/activity_streams/public_router.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from typing import Annotated, Callable
|
||||
|
||||
from fastapi import APIRouter, Depends, Security
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
import activity_streams.schema as activity_streams_schema
|
||||
import activity_streams.crud as activity_streams_crud
|
||||
import activity_streams.dependencies as activity_streams_dependencies
|
||||
|
||||
import activities.dependencies as activities_dependencies
|
||||
|
||||
import core.database as core_database
|
||||
|
||||
# Define the API router
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/activity_id/{activity_id}/all",
|
||||
response_model=list[activity_streams_schema.ActivityStreams] | None,
|
||||
)
|
||||
async def read_public_activities_streams_for_activity_all(
|
||||
activity_id: int,
|
||||
validate_id: Annotated[
|
||||
Callable, Depends(activities_dependencies.validate_activity_id)
|
||||
],
|
||||
db: Annotated[
|
||||
Session,
|
||||
Depends(core_database.get_db),
|
||||
],
|
||||
):
|
||||
# Get the activity streams from the database and return them
|
||||
return activity_streams_crud.get_public_activity_streams(activity_id, db)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/activity_id/{activity_id}/stream_type/{stream_type}",
|
||||
response_model=activity_streams_schema.ActivityStreams | None,
|
||||
)
|
||||
async def read_public_activities_streams_for_activity_stream_type(
|
||||
activity_id: int,
|
||||
validate_activity_id: Annotated[
|
||||
Callable, Depends(activities_dependencies.validate_activity_id)
|
||||
],
|
||||
stream_type: int,
|
||||
validate_activity_stream_type: Annotated[
|
||||
Callable, Depends(activity_streams_dependencies.validate_activity_stream_type)
|
||||
],
|
||||
db: Annotated[
|
||||
Session,
|
||||
Depends(core_database.get_db),
|
||||
],
|
||||
):
|
||||
# Get the activity stream from the database and return them
|
||||
return activity_streams_crud.get_public_activity_stream_by_type(
|
||||
activity_id, stream_type, db
|
||||
)
|
||||
@@ -1,5 +1,3 @@
|
||||
import logging
|
||||
|
||||
from typing import Annotated, Callable
|
||||
|
||||
from fastapi import APIRouter, Depends, Security
|
||||
|
||||
@@ -15,6 +15,7 @@ import migrations.models
|
||||
import user_integrations.models
|
||||
import users.models
|
||||
import session.models
|
||||
import server_settings.models
|
||||
|
||||
# import Base and engine from database file
|
||||
from core.database import Base, engine
|
||||
|
||||
@@ -3,7 +3,7 @@ import os
|
||||
import core.logger as core_logger
|
||||
|
||||
# Constant related to version
|
||||
API_VERSION = "v0.8.1"
|
||||
API_VERSION = "v0.9.0"
|
||||
LICENSE_NAME = "GNU Affero General Public License v3.0 or later"
|
||||
LICENSE_IDENTIFIER = "AGPL-3.0-or-later"
|
||||
LICENSE_URL = "https://spdx.org/licenses/AGPL-3.0-or-later.html"
|
||||
|
||||
@@ -7,13 +7,17 @@ import session.security as session_security
|
||||
import users.router as users_router
|
||||
import profile.router as profile_router
|
||||
import activities.router as activities_router
|
||||
import activities.public_router as activities_public_router
|
||||
import activity_streams.router as activity_streams_router
|
||||
import activity_streams.public_router as activity_streams_public_router
|
||||
import gears.router as gears_router
|
||||
import followers.router as followers_router
|
||||
import strava.router as strava_router
|
||||
import garmin.router as garmin_router
|
||||
import health_data.router as health_data_router
|
||||
import health_targets.router as health_targets_router
|
||||
import server_settings.router as server_settings_router
|
||||
import server_settings.public_router as server_settings_public_router
|
||||
import websocket.router as websocket_router
|
||||
|
||||
|
||||
@@ -47,12 +51,22 @@ router.include_router(
|
||||
tags=["activities"],
|
||||
dependencies=[Depends(session_security.validate_access_token)],
|
||||
)
|
||||
router.include_router(
|
||||
activities_public_router.router,
|
||||
prefix=core_config.ROOT_PATH + "/public/activities",
|
||||
tags=["public_activities"],
|
||||
)
|
||||
router.include_router(
|
||||
activity_streams_router.router,
|
||||
prefix=core_config.ROOT_PATH + "/activities/streams",
|
||||
tags=["activity_streams"],
|
||||
dependencies=[Depends(session_security.validate_access_token)],
|
||||
)
|
||||
router.include_router(
|
||||
activity_streams_public_router.router,
|
||||
prefix=core_config.ROOT_PATH + "/public/activities/streams",
|
||||
tags=["public_activity_streams"],
|
||||
)
|
||||
router.include_router(
|
||||
gears_router.router,
|
||||
prefix=core_config.ROOT_PATH + "/gears",
|
||||
@@ -91,6 +105,17 @@ router.include_router(
|
||||
tags=["health_targets"],
|
||||
dependencies=[Depends(session_security.validate_access_token)],
|
||||
)
|
||||
router.include_router(
|
||||
server_settings_router.router,
|
||||
prefix=core_config.ROOT_PATH + "/server_settings",
|
||||
tags=["server_settings"],
|
||||
dependencies=[Depends(session_security.validate_access_token)],
|
||||
)
|
||||
router.include_router(
|
||||
server_settings_public_router.router,
|
||||
prefix=core_config.ROOT_PATH + "/public/server_settings",
|
||||
tags=["public_server_settings"],
|
||||
)
|
||||
router.include_router(
|
||||
websocket_router.router,
|
||||
prefix=core_config.ROOT_PATH + "/ws",
|
||||
|
||||
0
backend/app/server_settings/__init__.py
Normal file
0
backend/app/server_settings/__init__.py
Normal file
75
backend/app/server_settings/crud.py
Normal file
75
backend/app/server_settings/crud.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from urllib.parse import unquote
|
||||
|
||||
import session.security as session_security
|
||||
|
||||
import server_settings.schema as server_settings_schema
|
||||
import server_settings.models as server_settings_models
|
||||
|
||||
import core.logger as core_logger
|
||||
|
||||
|
||||
def get_server_settings(db: Session):
|
||||
try:
|
||||
# Get the user from the database
|
||||
server_settings = (
|
||||
db.query(server_settings_models.ServerSettings)
|
||||
.filter(server_settings_models.ServerSettings.id == 1)
|
||||
.first()
|
||||
)
|
||||
|
||||
# If the server_settings was not found, return None
|
||||
if server_settings is None:
|
||||
return None
|
||||
|
||||
# Return the server_settings
|
||||
return server_settings
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in get_server_settings: {err}", "error", exc=err
|
||||
)
|
||||
# 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: {err}",
|
||||
) from err
|
||||
|
||||
|
||||
def edit_server_settings(server_settings: server_settings_schema.ServerSettings, db: Session):
|
||||
try:
|
||||
# Get the server_settings from the database
|
||||
db_server_settings = (
|
||||
db.query(server_settings_models.ServerSettings).filter(server_settings_models.ServerSettings.id == 1).first()
|
||||
)
|
||||
|
||||
if db_server_settings is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Server settings not found",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# Dictionary of the fields to update if they are not None
|
||||
server_settings_data = server_settings.dict(exclude_unset=True)
|
||||
# Iterate over the fields and update the db_user dynamically
|
||||
for key, value in server_settings_data.items():
|
||||
setattr(db_server_settings, key, value)
|
||||
|
||||
# Commit the transaction
|
||||
db.commit()
|
||||
except Exception as err:
|
||||
# Rollback the transaction
|
||||
db.rollback()
|
||||
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in edit_server_settings: {err}", "error", exc=err)
|
||||
|
||||
# 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: {err}",
|
||||
) from err
|
||||
18
backend/app/server_settings/models.py
Normal file
18
backend/app/server_settings/models.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from sqlalchemy import Column, Integer, CheckConstraint
|
||||
from sqlalchemy.orm import declarative_base
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class ServerSettings(Base):
|
||||
__tablename__ = "server_settings"
|
||||
|
||||
id = Column(Integer, primary_key=True, default=1, nullable=False)
|
||||
units = Column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=1,
|
||||
comment="User units (one digit)(1 - metric, 2 - imperial)",
|
||||
)
|
||||
|
||||
__table_args__ = (CheckConstraint("id = 1", name="single_row_check"),)
|
||||
23
backend/app/server_settings/public_router.py
Normal file
23
backend/app/server_settings/public_router.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
import server_settings.schema as server_settings_schema
|
||||
import server_settings.crud as server_settings_crud
|
||||
|
||||
import core.database as core_database
|
||||
|
||||
# Define the API router
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/", response_model=server_settings_schema.ServerSettings)
|
||||
async def read_public_server_settings(
|
||||
db: Annotated[
|
||||
Session,
|
||||
Depends(core_database.get_db),
|
||||
],
|
||||
):
|
||||
# Get the server_settings from the database
|
||||
return server_settings_crud.get_server_settings(db)
|
||||
48
backend/app/server_settings/router.py
Normal file
48
backend/app/server_settings/router.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from typing import Annotated, Callable
|
||||
|
||||
from fastapi import APIRouter, Depends, Security
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
import server_settings.schema as server_settings_schema
|
||||
import server_settings.crud as server_settings_crud
|
||||
|
||||
import session.security as session_security
|
||||
|
||||
import core.database as core_database
|
||||
|
||||
# Define the API router
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/", response_model=server_settings_schema.ServerSettings)
|
||||
async def read_server_settings(
|
||||
check_scopes: Annotated[
|
||||
Callable,
|
||||
Security(session_security.check_scopes, scopes=["server_settings:read"]),
|
||||
],
|
||||
db: Annotated[
|
||||
Session,
|
||||
Depends(core_database.get_db),
|
||||
],
|
||||
):
|
||||
# Get the server_settings from the database
|
||||
return server_settings_crud.get_server_settings(db)
|
||||
|
||||
|
||||
@router.put("/edit")
|
||||
async def edit_server_settings(
|
||||
server_settings_attributtes: server_settings_schema.ServerSettings,
|
||||
check_scopes: Annotated[
|
||||
Callable,
|
||||
Security(session_security.check_scopes, scopes=["server_settings:write"]),
|
||||
],
|
||||
db: Annotated[
|
||||
Session,
|
||||
Depends(core_database.get_db),
|
||||
],
|
||||
):
|
||||
# Update the server_settings in the database
|
||||
server_settings_crud.edit_server_settings(server_settings_attributtes, db)
|
||||
|
||||
# Return success message
|
||||
return {"detail": f"Server settings updated successfully"}
|
||||
9
backend/app/server_settings/schema.py
Normal file
9
backend/app/server_settings/schema.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ServerSettings(BaseModel):
|
||||
id: int
|
||||
units: int
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
@@ -12,6 +12,8 @@ USERS_ADMIN_SCOPES = ["users:write", "sessions:read", "sessions:write"]
|
||||
GEARS_SCOPES = ["gears:read", "gears:write"]
|
||||
ACTIVITIES_SCOPES = ["activities:read", "activities:write"]
|
||||
HEALTH_SCOPES = ["health:read", "health:write", "health_targets:read", "health_targets:write"]
|
||||
SERVER_SETTINGS_REGULAR_SCOPES = ["server_settings:read"]
|
||||
SERVER_SETTINGS_ADMIN_SCOPES = ["server_settings:write"]
|
||||
SCOPES_DICT = {
|
||||
"profile": "Privileges over user's own profile",
|
||||
"users:read": "Read privileges over users",
|
||||
@@ -26,13 +28,15 @@ SCOPES_DICT = {
|
||||
"health:write": "Write privileges over health data",
|
||||
"health_targets:read": "Read privileges over health targets data",
|
||||
"health_targets:write": "Write privileges over health targets data",
|
||||
"server_settings:read": "Read privileges over server settings",
|
||||
"server_settings:write": "Write privileges over server settings",
|
||||
}
|
||||
|
||||
# Constants related to user access types
|
||||
REGULAR_ACCESS = 1
|
||||
REGULAR_ACCESS_SCOPES = USERS_REGULAR_SCOPES + GEARS_SCOPES + ACTIVITIES_SCOPES + HEALTH_SCOPES
|
||||
REGULAR_ACCESS_SCOPES = USERS_REGULAR_SCOPES + GEARS_SCOPES + ACTIVITIES_SCOPES + HEALTH_SCOPES + SERVER_SETTINGS_REGULAR_SCOPES
|
||||
ADMIN_ACCESS = 2
|
||||
ADMIN_ACCESS_SCOPES = REGULAR_ACCESS_SCOPES + USERS_ADMIN_SCOPES
|
||||
ADMIN_ACCESS_SCOPES = REGULAR_ACCESS_SCOPES + USERS_ADMIN_SCOPES + SERVER_SETTINGS_ADMIN_SCOPES
|
||||
|
||||
# Constants related to user active status
|
||||
USER_ACTIVE = 1
|
||||
|
||||
@@ -19,7 +19,7 @@ from core.database import SessionLocal
|
||||
|
||||
|
||||
def get_strava_gear(gear_id: str, strava_client: Client):
|
||||
# Fetch Strava athlete
|
||||
# Fetch Strava gear
|
||||
try:
|
||||
strava_gear = strava_client.get_gear(gear_id)
|
||||
except Exception as err:
|
||||
@@ -45,7 +45,10 @@ def get_strava_gear(gear_id: str, strava_client: Client):
|
||||
|
||||
def fetch_and_process_gear(strava_client: Client, user_id: int, db: Session) -> int:
|
||||
# Fetch Strava athlete
|
||||
strava_athlete = strava_athlete_utils.get_strava_athlete(strava_client)
|
||||
try:
|
||||
strava_athlete = strava_athlete_utils.get_strava_athlete(strava_client)
|
||||
except Exception as err:
|
||||
raise err
|
||||
|
||||
# Initialize an empty list for results
|
||||
strava_gear = []
|
||||
@@ -87,7 +90,10 @@ def process_gear(
|
||||
return None
|
||||
|
||||
# Get the gear from Strava
|
||||
strava_gear = get_strava_gear(gear.id, strava_client)
|
||||
try:
|
||||
strava_gear = get_strava_gear(gear.id, strava_client)
|
||||
except Exception as err:
|
||||
raise err
|
||||
|
||||
new_gear = gears_schema.Gear(
|
||||
brand=strava_gear.brand_name,
|
||||
|
||||
2
frontend/app/package-lock.json
generated
2
frontend/app/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "endurain",
|
||||
"version": "0.8.1",
|
||||
"version": "0.9.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "endurain",
|
||||
"version": "0.8.1",
|
||||
"version": "0.9.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -14,6 +14,8 @@ import { ref, onMounted, watchEffect, nextTick } from 'vue';
|
||||
import { activityStreams } from '@/services/activityStreams';
|
||||
import LoadingComponent from '@/components/GeneralComponents/LoadingComponent.vue';
|
||||
import L from 'leaflet';
|
||||
// Importing the stores
|
||||
import { useAuthStore } from "@/stores/authStore";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -30,13 +32,18 @@ export default {
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const authStore = useAuthStore();
|
||||
const isLoading = ref(true);
|
||||
const activityStreamLatLng = ref(null);
|
||||
const activityMap = ref(null);
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
activityStreamLatLng.value = await activityStreams.getActivitySteamByStreamTypeByActivityId(props.activity.id, 7);
|
||||
if (authStore.isAuthenticated) {
|
||||
activityStreamLatLng.value = await activityStreams.getActivitySteamByStreamTypeByActivityId(props.activity.id, 7);
|
||||
} else {
|
||||
activityStreamLatLng.value = await activityStreams.getPublicActivitySteamByStreamTypeByActivityId(props.activity.id, 7);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch activity details:", error);
|
||||
} finally {
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
{{ $t("activitySummaryComponent.activityDistance") }}
|
||||
</span>
|
||||
<br>
|
||||
<span v-if="Number(authStore?.user?.units) === 1">
|
||||
<span v-if="Number(units) === 1">
|
||||
<!-- Check if activity_type is not 9 and 8 -->
|
||||
{{ activity.activity_type != 9 && activity.activity_type != 8
|
||||
? metersToKm(activity.distance) + ' ' + $t("generalItems.unitsKm") : activity.distance + ' ' + $t("generalItems.unitsM")
|
||||
@@ -166,7 +166,7 @@
|
||||
{{ $t("activitySummaryComponent.activityElevationGain") }}
|
||||
</span>
|
||||
<br>
|
||||
<span v-if="Number(authStore?.user?.units) === 1">{{ activity.elevation_gain }}{{ ' ' + $t("generalItems.unitsM") }}</span>
|
||||
<span v-if="Number(units) === 1">{{ activity.elevation_gain }}{{ ' ' + $t("generalItems.unitsM") }}</span>
|
||||
<span v-else>{{ metersToFeet(activity.elevation_gain) }}{{ ' ' + $t("generalItems.unitsFeetShort") }}</span>
|
||||
</div>
|
||||
<div v-else-if="activity.activity_type != 10 && activity.activity_type != 14">
|
||||
@@ -218,7 +218,7 @@
|
||||
<div class="col border-start border-opacity-50" v-if="activity.activity_type == 1 || activity.activity_type == 2 || activity.activity_type == 3">
|
||||
<span class="fw-lighter">{{ $t("activitySummaryComponent.activityEleGain") }}</span>
|
||||
<br>
|
||||
<span v-if="Number(authStore?.user?.units) === 1">{{ activity.elevation_gain }}{{ ' ' + $t("generalItems.unitsM") }}</span>
|
||||
<span v-if="Number(units) === 1">{{ activity.elevation_gain }}{{ ' ' + $t("generalItems.unitsM") }}</span>
|
||||
<span v-else>{{ metersToFeet(activity.elevation_gain) }}{{ ' ' + $t("generalItems.unitsFeetShort") }}</span>
|
||||
</div>
|
||||
<!-- avg_speed cycling activities -->
|
||||
@@ -227,7 +227,7 @@
|
||||
{{ $t("activitySummaryComponent.activityAvgSpeed") }}
|
||||
</span>
|
||||
<br>
|
||||
<span v-if="activity.average_speed && Number(authStore?.user?.units) === 1">{{ formatAverageSpeedMetric(activity.average_speed) }}{{ ' ' + $t("generalItems.unitsKmH") }}</span>
|
||||
<span v-if="activity.average_speed && Number(units) === 1">{{ formatAverageSpeedMetric(activity.average_speed) }}{{ ' ' + $t("generalItems.unitsKmH") }}</span>
|
||||
<span v-else-if="activity.average_speed && authStore.user.units == 2">{{ formatAverageSpeedImperial(activity.average_speed) }}{{ ' ' + $t("generalItems.unitsMph") }}</span>
|
||||
<span v-else>{{ $t("activitySummaryComponent.activityNoData") }}</span>
|
||||
</div>
|
||||
@@ -293,18 +293,20 @@ export default {
|
||||
const isLoading = ref(true);
|
||||
const userActivity = ref(null);
|
||||
let formattedPace = null;
|
||||
const units = ref(1)
|
||||
|
||||
if (
|
||||
props.activity.activity_type === 8 ||
|
||||
props.activity.activity_type === 9 ||
|
||||
props.activity.activity_type === 13
|
||||
) {
|
||||
if (Number(authStore?.user?.units) === 1) {
|
||||
if (Number(units.value) === 1) {
|
||||
formattedPace = computed(() => formatPaceSwimMetric(props.activity.pace));
|
||||
} else {
|
||||
formattedPace = computed(() => formatPaceSwimImperial(props.activity.pace));
|
||||
}
|
||||
} else {
|
||||
if (Number(authStore?.user?.units) === 1) {
|
||||
if (Number(units.value) === 1) {
|
||||
formattedPace = computed(() => formatPaceMetric(props.activity.pace));
|
||||
} else {
|
||||
formattedPace = computed(() => formatPaceImperial(props.activity.pace));
|
||||
@@ -314,7 +316,12 @@ export default {
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
userActivity.value = await users.getUserById(props.activity.user_id);
|
||||
if (authStore.isAuthenticated) {
|
||||
userActivity.value = await users.getUserById(props.activity.user_id);
|
||||
units.value = authStore.user.units;
|
||||
} else {
|
||||
units.value = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
push.error(`${t("activitySummaryComponent.errorFetchingUserById")} - ${error}`);
|
||||
} finally {
|
||||
@@ -348,6 +355,7 @@ export default {
|
||||
formatTime,
|
||||
calculateTimeDifference,
|
||||
formattedPace,
|
||||
units,
|
||||
sourceProp,
|
||||
submitDeleteActivity,
|
||||
updateActivityFieldsOnEdit,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<footer class="py-5 bg-body-tertiary">
|
||||
<div class="container">
|
||||
<p class="text-center text-muted">© {{ new Date().getFullYear() === 2023 ? '2023' : '2023 - ' + new Date().getFullYear() }} Endurain • <a class="link-body-emphasis" href="https://github.com/joaovitoriasilva/endurain" role="button"><font-awesome-icon :icon="['fab', 'fa-github']" /></a> • <a class="link-body-emphasis" href="https://docs.endurain.com"><font-awesome-icon :icon="['fas', 'book']" /></a> • <a class="link-body-emphasis" href="https://fosstodon.org/@endurain"><font-awesome-icon :icon="['fab', 'fa-mastodon']" /></a> • v0.8.1</p>
|
||||
<p class="text-center text-muted">© {{ new Date().getFullYear() === 2023 ? '2023' : '2023 - ' + new Date().getFullYear() }} Endurain • <a class="link-body-emphasis" href="https://github.com/joaovitoriasilva/endurain" role="button"><font-awesome-icon :icon="['fab', 'fa-github']" /></a> • <a class="link-body-emphasis" href="https://docs.endurain.com"><font-awesome-icon :icon="['fas', 'book']" /></a> • <a class="link-body-emphasis" href="https://fosstodon.org/@endurain"><font-awesome-icon :icon="['fab', 'fa-mastodon']" /></a> • v0.9.0</p>
|
||||
<p class="text-center text-muted"><img src="/src/assets/strava/api_logo_cptblWith_strava_horiz_light.png" alt="Compatible with STRAVA image" height="25" /> • <img src="/src/assets/garminconnect/Garmin_connect_badge_print_RESOURCE_FILE-01.png" alt="Works with Garmin Connect image" height="25" /></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -65,7 +65,7 @@ const router = createRouter({
|
||||
router.beforeEach((to, from, next) => {
|
||||
const authStore = useAuthStore();
|
||||
|
||||
if (!authStore.isAuthenticated && to.path !== "/login") {
|
||||
if (!authStore.isAuthenticated && to.path !== "/login" && !to.path.startsWith("/activity/")) {
|
||||
next("/login");
|
||||
} else if (authStore.isAuthenticated) {
|
||||
if (to.path === "/login") {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { fetchGetRequest, fetchPostFileRequest, fetchDeleteRequest, fetchPutRequest, fetchPostRequest } from '@/utils/serviceUtils';
|
||||
import { fetchPublicGetRequest } from '@/utils/servicePublicUtils';
|
||||
|
||||
export const activities = {
|
||||
// Activities authenticated
|
||||
getUserWeekActivities(user_id, week_number) {
|
||||
return fetchGetRequest(`activities/user/${user_id}/week/${week_number}`);
|
||||
},
|
||||
@@ -42,5 +44,9 @@ export const activities = {
|
||||
},
|
||||
deleteActivity(activityId) {
|
||||
return fetchDeleteRequest(`activities/${activityId}/delete`);
|
||||
}
|
||||
},
|
||||
// Activities public
|
||||
getPublicActivityById(activityId) {
|
||||
return fetchPublicGetRequest(`public/activities/${activityId}`);
|
||||
},
|
||||
};
|
||||
@@ -1,10 +1,19 @@
|
||||
import { fetchGetRequest } from '@/utils/serviceUtils';
|
||||
import { fetchPublicGetRequest } from '@/utils/servicePublicUtils';
|
||||
|
||||
export const activityStreams = {
|
||||
// Activity streams authenticated
|
||||
async getActivitySteamsByActivityId(activityId) {
|
||||
return fetchGetRequest(`activities/streams/activity_id/${activityId}/all`);
|
||||
},
|
||||
async getActivitySteamByStreamTypeByActivityId(activityId, streamType) {
|
||||
return fetchGetRequest(`activities/streams/activity_id/${activityId}/stream_type/${streamType}`);
|
||||
}
|
||||
},
|
||||
// Activity streams public
|
||||
async getPublicActivityStreamsByActivityId(activityId) {
|
||||
return fetchPublicGetRequest(`public/activities/streams/activity_id/${activityId}/all`);
|
||||
},
|
||||
async getPublicActivitySteamByStreamTypeByActivityId(activityId, streamType) {
|
||||
return fetchPublicGetRequest(`public/activities/streams/activity_id/${activityId}/stream_type/${streamType}`);
|
||||
},
|
||||
};
|
||||
12
frontend/app/src/utils/servicePublicUtils.js
Normal file
12
frontend/app/src/utils/servicePublicUtils.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { attemptFetch } from './serviceUtils';
|
||||
|
||||
export async function fetchPublicGetRequest(url) {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Client-Type': 'web',
|
||||
},
|
||||
};
|
||||
return attemptFetch(url, options);
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
// Importing the auth store
|
||||
import { useAuthStore } from '@/stores/authStore';
|
||||
|
||||
@@ -25,7 +24,7 @@ async function fetchWithRetry(url, options) {
|
||||
}
|
||||
}
|
||||
|
||||
async function attemptFetch(url, options) {
|
||||
export async function attemptFetch(url, options) {
|
||||
const fullUrl = `${API_URL}${url}`;
|
||||
const response = await fetch(fullUrl, options);
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
</div>
|
||||
|
||||
<!-- gear zone -->
|
||||
<hr class="mb-2 mt-2">
|
||||
<div class="mt-3 mb-3" v-if="isLoading">
|
||||
<hr class="mb-2 mt-2" v-if="activity && authStore.isAuthenticated">
|
||||
<div class="mt-3 mb-3" v-if="isLoading && authStore.isAuthenticated">
|
||||
<LoadingComponent />
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center" v-else-if="activity">
|
||||
<div class="d-flex justify-content-between align-items-center" v-else-if="activity && authStore.isAuthenticated">
|
||||
<p class="pt-2">
|
||||
<span class="fw-lighter">
|
||||
{{ $t("activityView.labelGear") }}
|
||||
@@ -185,11 +185,31 @@ export default {
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// Get the activity by id
|
||||
activity.value = await activities.getActivityById(route.params.id);
|
||||
if (authStore.isAuthenticated) {
|
||||
activity.value = await activities.getActivityById(route.params.id);
|
||||
} else {
|
||||
activity.value = await activities.getPublicActivityById(route.params.id);
|
||||
if (!activity.value) {
|
||||
router.push({ path: "/login" });
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the activity exists
|
||||
if (!activity.value) {
|
||||
router.push({
|
||||
path: "/",
|
||||
query: { activityFound: "false", id: route.params.id },
|
||||
});
|
||||
}
|
||||
|
||||
// Get the activity streams by activity id
|
||||
activityActivityStreams.value =
|
||||
await activityStreams.getActivitySteamsByActivityId(route.params.id);
|
||||
if (authStore.isAuthenticated) {
|
||||
activityActivityStreams.value =
|
||||
await activityStreams.getActivitySteamsByActivityId(route.params.id);
|
||||
} else {
|
||||
activityActivityStreams.value =
|
||||
await activityStreams.getPublicActivityStreamsByActivityId(route.params.id);
|
||||
}
|
||||
|
||||
// Check if the activity has the streams
|
||||
for (let i = 0; i < activityActivityStreams.value.length; i++) {
|
||||
@@ -232,40 +252,36 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (authStore.isAuthenticated) {
|
||||
if (activity.value.gear_id) {
|
||||
gear.value = await gears.getGearById(activity.value.gear_id);
|
||||
gearId.value = activity.value.gear_id;
|
||||
}
|
||||
|
||||
if (!activity.value) {
|
||||
router.push({
|
||||
path: "/",
|
||||
query: { activityFound: "false", id: route.params.id },
|
||||
});
|
||||
}
|
||||
if (activity.value.gear_id) {
|
||||
gear.value = await gears.getGearById(activity.value.gear_id);
|
||||
gearId.value = activity.value.gear_id;
|
||||
}
|
||||
|
||||
if (
|
||||
activity.value.activity_type === 1 ||
|
||||
activity.value.activity_type === 2 ||
|
||||
activity.value.activity_type === 3 ||
|
||||
activity.value.activity_type === 11 ||
|
||||
activity.value.activity_type === 12
|
||||
) {
|
||||
gearsByType.value = await gears.getGearFromType(2);
|
||||
} else {
|
||||
if (
|
||||
activity.value.activity_type === 4 ||
|
||||
activity.value.activity_type === 5 ||
|
||||
activity.value.activity_type === 6 ||
|
||||
activity.value.activity_type === 7
|
||||
activity.value.activity_type === 1 ||
|
||||
activity.value.activity_type === 2 ||
|
||||
activity.value.activity_type === 3 ||
|
||||
activity.value.activity_type === 11 ||
|
||||
activity.value.activity_type === 12
|
||||
) {
|
||||
gearsByType.value = await gears.getGearFromType(1);
|
||||
gearsByType.value = await gears.getGearFromType(2);
|
||||
} else {
|
||||
if (
|
||||
activity.value.activity_type === 8 ||
|
||||
activity.value.activity_type === 9
|
||||
activity.value.activity_type === 4 ||
|
||||
activity.value.activity_type === 5 ||
|
||||
activity.value.activity_type === 6 ||
|
||||
activity.value.activity_type === 7
|
||||
) {
|
||||
gearsByType.value = await gears.getGearFromType(3);
|
||||
gearsByType.value = await gears.getGearFromType(1);
|
||||
} else {
|
||||
if (
|
||||
activity.value.activity_type === 8 ||
|
||||
activity.value.activity_type === 9
|
||||
) {
|
||||
gearsByType.value = await gears.getGearFromType(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user