From 5c7c81f123a948ab0c29cfe27a60f414a696f9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vit=C3=B3ria=20Silva?= <8648976+joaovitoriasilva@users.noreply.github.com> Date: Tue, 24 Jun 2025 23:03:14 +0100 Subject: [PATCH 1/3] Refactor env var usage and runtime config for frontend/backend Backend now uses core.config for environment variables with sensible defaults, reducing direct os.environ access and improving robustness. Dockerfile and start.sh were updated to remove hardcoded env vars and generate a runtime env.js for frontend configuration. Frontend code now reads ENDURAIN_HOST from window.env instead of Vite env, enabling runtime configuration. Obsolete .env file was removed, and documentation was updated to fix a typo in JAEGER_PORT. --- backend/app/activities/activity_laps/utils.py | 5 ++-- backend/app/activities/activity_sets/utils.py | 4 ++- backend/app/core/config.py | 17 ++--------- backend/app/core/database.py | 29 +++++++------------ backend/app/core/tracing.py | 8 ++--- backend/app/fit/utils.py | 4 ++- backend/app/gpx/utils.py | 5 ++-- backend/app/migrations/migration_2.py | 3 +- backend/app/session/constants.py | 6 ++-- backend/app/strava/activity_utils.py | 5 ++-- docker/Dockerfile | 16 +--------- docker/start.sh | 20 ++----------- docs/getting-started.md | 2 +- frontend/app/.env | 1 - frontend/app/index.html | 1 + frontend/app/public/env.js | 3 ++ .../components/Users/UserAvatarComponent.vue | 2 +- frontend/app/src/services/stravaService.js | 2 +- frontend/app/src/utils/serviceUtils.js | 4 +-- frontend/app/src/views/LoginView.vue | 2 +- 20 files changed, 47 insertions(+), 92 deletions(-) delete mode 100644 frontend/app/.env create mode 100644 frontend/app/public/env.js diff --git a/backend/app/activities/activity_laps/utils.py b/backend/app/activities/activity_laps/utils.py index be51925a6..d3957429b 100644 --- a/backend/app/activities/activity_laps/utils.py +++ b/backend/app/activities/activity_laps/utils.py @@ -1,4 +1,3 @@ -import os from zoneinfo import ZoneInfo from datetime import datetime @@ -7,6 +6,8 @@ import activities.activity_laps.schema as activity_laps_schema import activities.activity.schema as activities_schema +import core.config as core_config + def serialize_activity_lap(activity: activities_schema.Activity, activity_lap: activity_laps_schema.ActivityLaps): def make_aware_and_format(dt, timezone): if isinstance(dt, str): @@ -18,7 +19,7 @@ def serialize_activity_lap(activity: activities_schema.Activity, activity_lap: a timezone = ( ZoneInfo(activity.timezone) if activity.timezone - else ZoneInfo(os.environ.get("TZ")) + else ZoneInfo(core_config.TZ) ) activity_lap.start_time = make_aware_and_format(activity_lap.start_time, timezone) diff --git a/backend/app/activities/activity_sets/utils.py b/backend/app/activities/activity_sets/utils.py index fed913c2e..e45f4d2ad 100644 --- a/backend/app/activities/activity_sets/utils.py +++ b/backend/app/activities/activity_sets/utils.py @@ -7,6 +7,8 @@ import activities.activity_sets.schema as activity_sets_schema import activities.activity.schema as activities_schema +import core.config as core_config + def serialize_activity_set(activity: activities_schema.Activity, activity_set: activity_sets_schema.ActivitySets): def make_aware_and_format(dt, timezone): if isinstance(dt, str): @@ -18,7 +20,7 @@ def serialize_activity_set(activity: activities_schema.Activity, activity_set: a timezone = ( ZoneInfo(activity.timezone) if activity.timezone - else ZoneInfo(os.environ.get("TZ")) + else ZoneInfo(core_config.TZ) ) activity_set.start_time = make_aware_and_format(activity_set.start_time, timezone) diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 9312e9feb..85bee49f8 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -9,30 +9,17 @@ LICENSE_IDENTIFIER = "AGPL-3.0-or-later" LICENSE_URL = "https://spdx.org/licenses/AGPL-3.0-or-later.html" ROOT_PATH = "/api/v1" FRONTEND_DIR = "/app/frontend/dist" -ENVIRONMENT = os.getenv("ENVIRONMENT") +ENVIRONMENT = os.getenv("ENVIRONMENT", "production") +TZ = os.getenv("TZ", "UTC") def check_required_env_vars(): required_env_vars = [ - "TZ", - "DB_TYPE", - "DB_HOST", - "DB_PORT", - "DB_USER", "DB_PASSWORD", - "DB_DATABASE", "SECRET_KEY", "FERNET_KEY", - "ALGORITHM", - "ACCESS_TOKEN_EXPIRE_MINUTES", - "REFRESH_TOKEN_EXPIRE_DAYS", - "JAEGER_ENABLED", - "JAEGER_PROTOCOL", - "JAEGER_HOST", - "JAGGER_PORT", "ENDURAIN_HOST", "GEOCODES_MAPS_API", - "ENVIRONMENT", ] for var in required_env_vars: diff --git a/backend/app/core/database.py b/backend/app/core/database.py index bfb1a946d..f4cb733db 100644 --- a/backend/app/core/database.py +++ b/backend/app/core/database.py @@ -5,34 +5,25 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.engine.url import URL -# Helper to fetch environment variables -def get_env_variable(var_name: str) -> str: - value = os.environ.get(var_name) - if value is None: - raise EnvironmentError(f"Environment variable {var_name} is not set") - return value - - # Fetch the database type (e.g., mariadb or postgresql) -db_type = get_env_variable("DB_TYPE").lower() +db_type = os.environ.get("DB_TYPE", "postgres").lower() # Define supported database drivers -supported_drivers = { - "mariadb": "mysql+mysqldb", - "postgres": "postgresql+psycopg" -} +supported_drivers = {"mariadb": "mysql+mysqldb", "postgres": "postgresql+psycopg"} if db_type not in supported_drivers: - raise ValueError(f"Unsupported DB_TYPE: {db_type}. Supported types are {list(supported_drivers.keys())}") + raise ValueError( + f"Unsupported DB_TYPE: {db_type}. Supported types are {list(supported_drivers.keys())}" + ) # Define the database connection URL using environment variables db_url = URL.create( drivername=supported_drivers[db_type], - username=get_env_variable("DB_USER"), - password=get_env_variable("DB_PASSWORD"), - host=get_env_variable("DB_HOST"), - port=get_env_variable("DB_PORT"), - database=get_env_variable("DB_DATABASE"), + username=os.environ.get("DB_USER", "endurain"), + password=os.environ.get("DB_PASSWORD"), + host=os.environ.get("DB_HOST", "endurain"), + port=os.environ.get("DB_PORT", "5432"), + database=os.environ.get("DB_DATABASE", "endurain"), ) # Create the SQLAlchemy engine diff --git a/backend/app/core/tracing.py b/backend/app/core/tracing.py index 29e8ad4d5..f34485344 100644 --- a/backend/app/core/tracing.py +++ b/backend/app/core/tracing.py @@ -9,7 +9,7 @@ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor def setup_tracing(app): # Check if Jaeger tracing is enabled using the 'JAEGER_ENABLED' environment variable - if os.environ.get("JAEGER_ENABLED") == "true": + if os.environ.get("JAEGER_ENABLED", "true") == "true": # Configure OpenTelemetry with a specified service name trace.set_tracer_provider( TracerProvider(resource=Resource.create({"service.name": "backend_api"})) @@ -17,11 +17,11 @@ def setup_tracing(app): trace.get_tracer_provider().add_span_processor( BatchSpanProcessor( OTLPSpanExporter( - endpoint=os.environ.get("JAEGER_PROTOCOL") + endpoint=os.environ.get("JAEGER_PROTOCOL", "http") + "://" - + os.environ.get("JAEGER_HOST") + + os.environ.get("JAEGER_HOST", "jaeger") + ":" - + os.environ.get("JAGGER_PORT") + + os.environ.get("JAEGER_PORT", "4317") ) ) ) diff --git a/backend/app/fit/utils.py b/backend/app/fit/utils.py index 9aad49743..cd77c1116 100644 --- a/backend/app/fit/utils.py +++ b/backend/app/fit/utils.py @@ -26,6 +26,8 @@ import users.user_privacy_settings.schema as users_privacy_settings_schema import core.logger as core_logger +import core.config as core_config + def create_activity_objects( sessions_records: dict, @@ -38,7 +40,7 @@ def create_activity_objects( try: # Create an instance of TimezoneFinder tf = TimezoneFinder() - timezone = os.environ.get("TZ") + timezone = core_config.TZ # Define variables gear_id = None diff --git a/backend/app/gpx/utils.py b/backend/app/gpx/utils.py index d195d3ea5..aec2d87e4 100644 --- a/backend/app/gpx/utils.py +++ b/backend/app/gpx/utils.py @@ -1,6 +1,4 @@ import gpxpy -import gpxpy.gpx -import os from geopy.distance import geodesic from timezonefinder import TimezoneFinder from sqlalchemy.orm import Session @@ -16,6 +14,7 @@ import users.user_default_gear.utils as user_default_gear_utils import users.user_privacy_settings.schema as users_privacy_settings_schema import core.logger as core_logger +import core.config as core_config def parse_gpx_file( @@ -27,7 +26,7 @@ def parse_gpx_file( try: # Create an instance of TimezoneFinder tf = TimezoneFinder() - timezone = os.environ.get("TZ") + timezone = core_config.TZ # Initialize default values for various variables activity_type = "Workout" diff --git a/backend/app/migrations/migration_2.py b/backend/app/migrations/migration_2.py index 7a9b72f17..2133afa54 100644 --- a/backend/app/migrations/migration_2.py +++ b/backend/app/migrations/migration_2.py @@ -11,6 +11,7 @@ import migrations.crud as migrations_crud import health_data.crud as health_data_crud import core.logger as core_logger +import core.config as core_config def process_migration_2(db: Session): @@ -47,7 +48,7 @@ def process_migration_2(db: Session): ) continue - timezone = os.environ.get("TZ") + timezone = core_config.TZ # Get activity stream try: diff --git a/backend/app/session/constants.py b/backend/app/session/constants.py index 6d950d880..eb70698bb 100644 --- a/backend/app/session/constants.py +++ b/backend/app/session/constants.py @@ -1,9 +1,9 @@ import os # JWT Token constants -JWT_ALGORITHM = os.environ.get("ALGORITHM") -JWT_ACCESS_TOKEN_EXPIRE_MINUTES = int(os.environ.get("ACCESS_TOKEN_EXPIRE_MINUTES")) -JWT_REFRESH_TOKEN_EXPIRE_DAYS = int(os.environ.get("REFRESH_TOKEN_EXPIRE_DAYS")) +JWT_ALGORITHM = os.environ.get("ALGORITHM", "HS256") +JWT_ACCESS_TOKEN_EXPIRE_MINUTES = int(os.environ.get("ACCESS_TOKEN_EXPIRE_MINUTES", "15")) +JWT_REFRESH_TOKEN_EXPIRE_DAYS = int(os.environ.get("REFRESH_TOKEN_EXPIRE_DAYS", "7")) JWT_SECRET_KEY = os.environ.get("SECRET_KEY") # Scopes definition diff --git a/backend/app/strava/activity_utils.py b/backend/app/strava/activity_utils.py index 0c5cc6d01..1144597a4 100644 --- a/backend/app/strava/activity_utils.py +++ b/backend/app/strava/activity_utils.py @@ -1,5 +1,3 @@ -import os - from fastapi import HTTPException, status from datetime import datetime, timedelta, timezone from sqlalchemy.orm import Session @@ -8,6 +6,7 @@ from stravalib.exc import AccessUnauthorized from timezonefinder import TimezoneFinder import core.logger as core_logger +import core.config as core_config import activities.activity.schema as activities_schema import activities.activity.crud as activities_crud @@ -131,7 +130,7 @@ def parse_activity( ) -> dict: # Create an instance of TimezoneFinder tf = TimezoneFinder() - timezone = os.environ.get("TZ") + timezone = core_config.TZ # Get the detailed activity try: diff --git a/docker/Dockerfile b/docker/Dockerfile index c33b94bd2..476f08136 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -62,21 +62,7 @@ RUN set -eux; \ # Define environment variables ENV UID=1000 \ GID=1000 \ - TZ="UTC" \ - DB_TYPE="postgres" \ - DB_HOST="postgres" \ - DB_PORT=5432 \ - DB_USER="endurain" \ - DB_DATABASE="endurain" \ - ALGORITHM="HS256" \ - ACCESS_TOKEN_EXPIRE_MINUTES=15 \ - REFRESH_TOKEN_EXPIRE_DAYS=7 \ - JAEGER_ENABLED="false" \ - JAEGER_HOST="jaeger" \ - JAEGER_PROTOCOL="http" \ - JAGGER_PORT=4317 \ - BEHIND_PROXY=false \ - ENVIRONMENT="production" + BEHIND_PROXY=false # Set the working directory to /app/frontend WORKDIR /app/frontend diff --git a/docker/start.sh b/docker/start.sh index f08758436..6d58c64da 100644 --- a/docker/start.sh +++ b/docker/start.sh @@ -19,30 +19,14 @@ validate_id() { esac } -adjust_folder_ownership() { - if [ -d "$1" ]; then - chown -R "$UID:$GID" "$1" - echo_info_log "Ownership adjusted for $1" - else - echo_info_log "Directory $1 does not exist, skipping." - fi -} - validate_id "$UID" validate_id "$GID" echo_info_log "UID=$UID, GID=$GID" -# List of directories (space-separated for POSIX shell) -directories="/app/backend/logs /app/backend/user_images /app/backend/files /app/backend/server_images" - -for dir in $directories; do - adjust_folder_ownership "$dir" -done - if [ -n "$ENDURAIN_HOST" ]; then - echo_info_log "Substituting MY_APP_ENDURAIN_HOST with $ENDURAIN_HOST" - find /app/frontend/dist -type f \( -name '*.js' -o -name '*.css' \) -exec sed -i "s|MY_APP_ENDURAIN_HOST|$ENDURAIN_HOST|g" {} + + echo "window.env = { ENDURAIN_HOST: \"$ENDURAIN_HOST\" };" > /app/frontend/dist/env.js + echo_info_log "Runtime env.js written with ENDURAIN_HOST=$ENDURAIN_HOST" fi echo_info_log "Starting FastAPI with BEHIND_PROXY=$BEHIND_PROXY" diff --git a/docs/getting-started.md b/docs/getting-started.md index c4aeeac32..12e25e886 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -39,7 +39,7 @@ Environment variable | Default value | Optional | Notes | | JAEGER_ENABLED | false | Yes | N/A | | JAEGER_PROTOCOL | http | Yes | N/A | | JAEGER_HOST | jaeger | Yes | N/A | -| JAGGER_PORT | 4317 | Yes | N/A | +| JAEGER_PORT | 4317 | Yes | N/A | | BEHIND_PROXY | false | Yes | Change to true if behind reverse proxy | | ENVIRONMENT | production | Yes | "production" and "development" allowed. "development" allows connections from localhost:8080 and localhost:5173 at the CORS level | diff --git a/frontend/app/.env b/frontend/app/.env deleted file mode 100644 index d6fe9559c..000000000 --- a/frontend/app/.env +++ /dev/null @@ -1 +0,0 @@ -VITE_ENDURAIN_HOST=MY_APP_ENDURAIN_HOST \ No newline at end of file diff --git a/frontend/app/index.html b/frontend/app/index.html index 55495a4ad..674915a77 100644 --- a/frontend/app/index.html +++ b/frontend/app/index.html @@ -15,6 +15,7 @@ +
diff --git a/frontend/app/public/env.js b/frontend/app/public/env.js new file mode 100644 index 000000000..3eedcd6d0 --- /dev/null +++ b/frontend/app/public/env.js @@ -0,0 +1,3 @@ +window.env = { + ENDURAIN_HOST: "my-test" +}; \ No newline at end of file diff --git a/frontend/app/src/components/Users/UserAvatarComponent.vue b/frontend/app/src/components/Users/UserAvatarComponent.vue index 6c6edf957..0e2ca99fd 100644 --- a/frontend/app/src/components/Users/UserAvatarComponent.vue +++ b/frontend/app/src/components/Users/UserAvatarComponent.vue @@ -31,7 +31,7 @@ export default { emits: ['userDeleted'], setup(props) { const altText = ref('User Avatar'); - const userPhotoUrl = ref(props.user?.photo_path ? `${import.meta.env.VITE_ENDURAIN_HOST}/${props.user.photo_path}` : null); + const userPhotoUrl = ref(props.user?.photo_path ? `${window.env.ENDURAIN_HOST}/${props.user.photo_path}` : null); const alignTopValue = ref(props.alignTop); return { diff --git a/frontend/app/src/services/stravaService.js b/frontend/app/src/services/stravaService.js index 9fe46a595..33cd6ade5 100644 --- a/frontend/app/src/services/stravaService.js +++ b/frontend/app/src/services/stravaService.js @@ -12,7 +12,7 @@ export const strava = { return fetchPutRequest('strava/client', data); }, linkStrava(state, stravaClientId) { - let redirectUri = `${import.meta.env.VITE_ENDURAIN_HOST}`; + let redirectUri = `${window.env.ENDURAIN_HOST}`; redirectUri = encodeURIComponent(redirectUri); const scope = 'read,read_all,profile:read_all,activity:read,activity:read_all'; diff --git a/frontend/app/src/utils/serviceUtils.js b/frontend/app/src/utils/serviceUtils.js index 89c7671da..67c0a7fe6 100644 --- a/frontend/app/src/utils/serviceUtils.js +++ b/frontend/app/src/utils/serviceUtils.js @@ -3,8 +3,8 @@ import { useAuthStore } from "@/stores/authStore"; let refreshTokenPromise = null; -export const API_URL = `${import.meta.env.VITE_ENDURAIN_HOST}/api/v1/`; -export const FRONTEND_URL = `${import.meta.env.VITE_ENDURAIN_HOST}/`; +export const API_URL = `${window.env.ENDURAIN_HOST}/api/v1/` +export const FRONTEND_URL = `${window.env.ENDURAIN_HOST}/` async function fetchWithRetry(url, options) { // Add CSRF token to headers for state-changing requests diff --git a/frontend/app/src/views/LoginView.vue b/frontend/app/src/views/LoginView.vue index 572d13a0e..af6df83ad 100644 --- a/frontend/app/src/views/LoginView.vue +++ b/frontend/app/src/views/LoginView.vue @@ -65,7 +65,7 @@ export default { const serverSettingsStore = useServerSettingsStore(); const showPassword = ref(false); const loginPhotoUrl = serverSettingsStore.serverSettings.login_photo_set - ? `${import.meta.env.VITE_ENDURAIN_HOST}/server_images/login.png` + ? `${window.env.ENDURAIN_HOST}/server_images/login.png` : null; // Toggle password visibility From b138f81f1e4a1bbaf3676984f9949f72088a91f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vit=C3=B3ria=20Silva?= <8648976+joaovitoriasilva@users.noreply.github.com> Date: Tue, 24 Jun 2025 23:14:58 +0100 Subject: [PATCH 2/3] Update database.py --- backend/app/core/database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/core/database.py b/backend/app/core/database.py index f4cb733db..59771a0da 100644 --- a/backend/app/core/database.py +++ b/backend/app/core/database.py @@ -21,7 +21,7 @@ db_url = URL.create( drivername=supported_drivers[db_type], username=os.environ.get("DB_USER", "endurain"), password=os.environ.get("DB_PASSWORD"), - host=os.environ.get("DB_HOST", "endurain"), + host=os.environ.get("DB_HOST", "postgres"), port=os.environ.get("DB_PORT", "5432"), database=os.environ.get("DB_DATABASE", "endurain"), ) From 10668d3ed6fd0ffbf24ef50c766ab4e1f3c2b6b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vit=C3=B3ria=20Silva?= <8648976+joaovitoriasilva@users.noreply.github.com> Date: Wed, 25 Jun 2025 09:26:34 +0100 Subject: [PATCH 3/3] Update env.js --- frontend/app/public/env.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/public/env.js b/frontend/app/public/env.js index 3eedcd6d0..ef7dad559 100644 --- a/frontend/app/public/env.js +++ b/frontend/app/public/env.js @@ -1,3 +1,3 @@ window.env = { - ENDURAIN_HOST: "my-test" + ENDURAIN_HOST: "http://localhost:8080" }; \ No newline at end of file