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:
João Vitória Silva
2025-02-11 22:45:31 +00:00
parent c8c54a87ab
commit 8974a028c7
28 changed files with 532 additions and 61 deletions

View File

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

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

View File

@@ -1,6 +1,5 @@
import os
import glob
import logging
import calendar
from typing import Annotated, Callable

View File

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

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

View File

@@ -1,5 +1,3 @@
import logging
from typing import Annotated, Callable
from fastapi import APIRouter, Depends, Security

View File

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

View File

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

View File

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

View File

View 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

View 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"),)

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

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

View File

@@ -0,0 +1,9 @@
from pydantic import BaseModel
class ServerSettings(BaseModel):
id: int
units: int
class Config:
orm_mode = True

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "endurain",
"version": "0.8.1",
"version": "0.9.0",
"lockfileVersion": 3,
"requires": true,
"packages": {

View File

@@ -1,6 +1,6 @@
{
"name": "endurain",
"version": "0.8.1",
"version": "0.9.0",
"private": true,
"type": "module",
"scripts": {

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
<template>
<footer class="py-5 bg-body-tertiary">
<div class="container">
<p class="text-center text-muted">&copy; {{ 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">&copy; {{ 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>

View File

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

View File

@@ -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}`);
},
};

View File

@@ -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}`);
},
};

View 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);
}

View File

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

View File

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