From 20d389f662b8bb3861a8baedb409b4e162af64fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Gonz=C3=A1lez?= Date: Thu, 17 Jul 2025 22:20:37 +0200 Subject: [PATCH] feat(app): configurable Modrinth endpoints through .env files --- .github/workflows/theseus-build.yml | 4 ++- .github/workflows/turbo-ci.yml | 4 +++ Cargo.lock | 1 + packages/app-lib/.env.local | 12 ++++++-- packages/app-lib/.env.prod | 10 +++++++ packages/app-lib/.env.staging | 10 +++++++ packages/app-lib/Cargo.toml | 1 + packages/app-lib/build.rs | 20 +++++++++++++ packages/app-lib/src/api/mr_auth.rs | 2 +- packages/app-lib/src/config.rs | 13 --------- packages/app-lib/src/lib.rs | 1 - packages/app-lib/src/state/cache.rs | 42 +++++++++++++++------------ packages/app-lib/src/state/friends.rs | 10 +++---- packages/app-lib/src/state/mr_auth.rs | 9 +++--- packages/app-lib/src/util/fetch.rs | 5 ++-- 15 files changed, 95 insertions(+), 49 deletions(-) create mode 100644 packages/app-lib/.env.prod create mode 100644 packages/app-lib/.env.staging delete mode 100644 packages/app-lib/src/config.rs diff --git a/.github/workflows/theseus-build.yml b/.github/workflows/theseus-build.yml index 76ae5f900c..64ae2b3349 100644 --- a/.github/workflows/theseus-build.yml +++ b/.github/workflows/theseus-build.yml @@ -75,7 +75,7 @@ jobs: rename-to: ${{ startsWith(matrix.platform, 'windows') && 'dasel.exe' || 'dasel' }} chmod: 0755 - - name: ⚙️ Set application version + - name: ⚙️ Set application version and environment shell: bash run: | APP_VERSION="$(git describe --tags --always | sed -E 's/-([0-9]+)-(g[0-9a-fA-F]+)$/-canary+\1.\2/')" @@ -84,6 +84,8 @@ jobs: dasel put -f packages/app-lib/Cargo.toml -t string -v "${APP_VERSION#v}" 'package.version' dasel put -f apps/app-frontend/package.json -t string -v "${APP_VERSION#v}" 'version' + cp packages/app-lib/.env.prod packages/app-lib/.env + - name: 💨 Setup Turbo cache uses: rharkor/caching-for-turbo@v1.8 diff --git a/.github/workflows/turbo-ci.yml b/.github/workflows/turbo-ci.yml index 6f82db1b2e..eb557b06b7 100644 --- a/.github/workflows/turbo-ci.yml +++ b/.github/workflows/turbo-ci.yml @@ -74,6 +74,10 @@ jobs: cp .env.local .env sqlx database setup + - name: ⚙️ Set app environment + working-directory: packages/app-lib + run: cp .env.staging .env + - name: 🔍 Lint and test run: pnpm run ci diff --git a/Cargo.lock b/Cargo.lock index f6519c3b1f..3d55cb9b64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8930,6 +8930,7 @@ dependencies = [ "data-url", "dirs", "discord-rich-presence", + "dotenvy", "dunce", "either", "encoding_rs", diff --git a/packages/app-lib/.env.local b/packages/app-lib/.env.local index e648f5b565..171b37b9e6 100644 --- a/packages/app-lib/.env.local +++ b/packages/app-lib/.env.local @@ -1,2 +1,10 @@ -# SQLite database file location -DATABASE_URL=sqlite:///tmp/Modrinth/code/packages/app-lib/.sqlx/generated/state.db +MODRINTH_URL=http://localhost:3000/ +MODRINTH_API_URL=http://127.0.0.1:8000/v2/ +MODRINTH_API_URL_V3=http://127.0.0.1:8000/v3/ +MODRINTH_SOCKET_URL=ws://127.0.0.1:8000/ +MODRINTH_LAUNCHER_META_URL=https://launcher-meta.modrinth.com/ + +# SQLite database file used by sqlx for type checking. Uncomment this to a valid path +# in your system and run `cargo sqlx database setup` to generate an empty database that +# can be used for developing the app DB schema +#DATABASE_URL=sqlite:///tmp/Modrinth/code/packages/app-lib/.sqlx/generated/state.db diff --git a/packages/app-lib/.env.prod b/packages/app-lib/.env.prod new file mode 100644 index 0000000000..f721d50783 --- /dev/null +++ b/packages/app-lib/.env.prod @@ -0,0 +1,10 @@ +MODRINTH_URL=https://modrinth.com/ +MODRINTH_API_URL=https://api.modrinth.com/v2/ +MODRINTH_API_URL_V3=https://api.modrinth.com/v3/ +MODRINTH_SOCKET_URL=wss://api.modrinth.com/ +MODRINTH_LAUNCHER_META_URL=https://launcher-meta.modrinth.com/ + +# SQLite database file used by sqlx for type checking. Uncomment this to a valid path +# in your system and run `cargo sqlx database setup` to generate an empty database that +# can be used for developing the app DB schema +#DATABASE_URL=sqlite:///tmp/Modrinth/code/packages/app-lib/.sqlx/generated/state.db diff --git a/packages/app-lib/.env.staging b/packages/app-lib/.env.staging new file mode 100644 index 0000000000..be0daeb9b2 --- /dev/null +++ b/packages/app-lib/.env.staging @@ -0,0 +1,10 @@ +MODRINTH_URL=https://staging.modrinth.com/ +MODRINTH_API_URL=https://staging-api.modrinth.com/v2/ +MODRINTH_API_URL_V3=https://staging-api.modrinth.com/v3/ +MODRINTH_SOCKET_URL=wss://staging-api.modrinth.com/ +MODRINTH_LAUNCHER_META_URL=https://launcher-meta.modrinth.com/ + +# SQLite database file used by sqlx for type checking. Uncomment this to a valid path +# in your system and run `cargo sqlx database setup` to generate an empty database that +# can be used for developing the app DB schema +#DATABASE_URL=sqlite:///tmp/Modrinth/code/packages/app-lib/.sqlx/generated/state.db diff --git a/packages/app-lib/Cargo.toml b/packages/app-lib/Cargo.toml index 85200eb258..127edd852c 100644 --- a/packages/app-lib/Cargo.toml +++ b/packages/app-lib/Cargo.toml @@ -82,6 +82,7 @@ ariadne.workspace = true winreg.workspace = true [build-dependencies] +dotenvy.workspace = true dunce.workspace = true [features] diff --git a/packages/app-lib/build.rs b/packages/app-lib/build.rs index 251c4e8480..10ed29b99f 100644 --- a/packages/app-lib/build.rs +++ b/packages/app-lib/build.rs @@ -4,12 +4,31 @@ use std::process::{Command, exit}; use std::{env, fs}; fn main() { + println!("cargo::rerun-if-changed=.env"); println!("cargo::rerun-if-changed=java/gradle"); println!("cargo::rerun-if-changed=java/src"); println!("cargo::rerun-if-changed=java/build.gradle.kts"); println!("cargo::rerun-if-changed=java/settings.gradle.kts"); println!("cargo::rerun-if-changed=java/gradle.properties"); + set_env(); + build_java_jars(); +} + +fn set_env() { + for (var_name, var_value) in + dotenvy::dotenv_iter().into_iter().flatten().flatten() + { + if var_name == "DATABASE_URL" { + // The sqlx database URL is a build-time detail that should not be exposed to the crate + continue; + } + + println!("cargo::rustc-env={var_name}={var_value}"); + } +} + +fn build_java_jars() { let out_dir = dunce::canonicalize(PathBuf::from(env::var_os("OUT_DIR").unwrap())) .unwrap(); @@ -37,6 +56,7 @@ fn main() { .current_dir(dunce::canonicalize("java").unwrap()) .status() .expect("Failed to wait on Gradle build"); + if !exit_status.success() { println!("cargo::error=Gradle build failed with {exit_status}"); exit(exit_status.code().unwrap_or(1)); diff --git a/packages/app-lib/src/api/mr_auth.rs b/packages/app-lib/src/api/mr_auth.rs index 3c99d1f5b5..42ce41fe5e 100644 --- a/packages/app-lib/src/api/mr_auth.rs +++ b/packages/app-lib/src/api/mr_auth.rs @@ -1,7 +1,7 @@ use crate::state::ModrinthCredentials; #[tracing::instrument] -pub fn authenticate_begin_flow() -> String { +pub fn authenticate_begin_flow() -> &'static str { crate::state::get_login_url() } diff --git a/packages/app-lib/src/config.rs b/packages/app-lib/src/config.rs deleted file mode 100644 index bf7e47e1e3..0000000000 --- a/packages/app-lib/src/config.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Configuration structs - -// pub const MODRINTH_URL: &str = "https://staging.modrinth.com/"; -// pub const MODRINTH_API_URL: &str = "https://staging-api.modrinth.com/v2/"; -// pub const MODRINTH_API_URL_V3: &str = "https://staging-api.modrinth.com/v3/"; - -pub const MODRINTH_URL: &str = "https://modrinth.com/"; -pub const MODRINTH_API_URL: &str = "https://api.modrinth.com/v2/"; -pub const MODRINTH_API_URL_V3: &str = "https://api.modrinth.com/v3/"; - -pub const MODRINTH_SOCKET_URL: &str = "wss://api.modrinth.com/"; - -pub const META_URL: &str = "https://launcher-meta.modrinth.com/"; diff --git a/packages/app-lib/src/lib.rs b/packages/app-lib/src/lib.rs index 55f4459acd..258e72423a 100644 --- a/packages/app-lib/src/lib.rs +++ b/packages/app-lib/src/lib.rs @@ -11,7 +11,6 @@ and launching Modrinth mod packs mod util; mod api; -mod config; mod error; mod event; mod launcher; diff --git a/packages/app-lib/src/state/cache.rs b/packages/app-lib/src/state/cache.rs index cd6ee1df2b..98f1bb79ce 100644 --- a/packages/app-lib/src/state/cache.rs +++ b/packages/app-lib/src/state/cache.rs @@ -1,4 +1,3 @@ -use crate::config::{META_URL, MODRINTH_API_URL, MODRINTH_API_URL_V3}; use crate::state::ProjectType; use crate::util::fetch::{FetchSemaphore, fetch_json, sha1_async}; use chrono::{DateTime, Utc}; @@ -8,6 +7,7 @@ use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use sqlx::SqlitePool; use std::collections::HashMap; +use std::env; use std::fmt::Display; use std::hash::Hash; use std::path::{Path, PathBuf}; @@ -945,7 +945,7 @@ impl CachedEntry { CacheValueType::Project => { fetch_original_values!( Project, - MODRINTH_API_URL, + env!("MODRINTH_API_URL"), "projects", CacheValue::Project ) @@ -953,7 +953,7 @@ impl CachedEntry { CacheValueType::Version => { fetch_original_values!( Version, - MODRINTH_API_URL, + env!("MODRINTH_API_URL"), "versions", CacheValue::Version ) @@ -961,7 +961,7 @@ impl CachedEntry { CacheValueType::User => { fetch_original_values!( User, - MODRINTH_API_URL, + env!("MODRINTH_API_URL"), "users", CacheValue::User ) @@ -969,7 +969,7 @@ impl CachedEntry { CacheValueType::Team => { let mut teams = fetch_many_batched::>( Method::GET, - MODRINTH_API_URL_V3, + env!("MODRINTH_API_URL_V3"), "teams?ids=", &keys, fetch_semaphore, @@ -1008,7 +1008,7 @@ impl CachedEntry { CacheValueType::Organization => { let mut orgs = fetch_many_batched::( Method::GET, - MODRINTH_API_URL_V3, + env!("MODRINTH_API_URL_V3"), "organizations?ids=", &keys, fetch_semaphore, @@ -1063,7 +1063,7 @@ impl CachedEntry { CacheValueType::File => { let mut versions = fetch_json::>( Method::POST, - &format!("{MODRINTH_API_URL}version_files"), + concat!(env!("MODRINTH_API_URL"), "version_files"), None, Some(serde_json::json!({ "algorithm": "sha1", @@ -1119,7 +1119,11 @@ impl CachedEntry { .map(|x| { ( x.key().to_string(), - format!("{META_URL}{}/v0/manifest.json", x.key()), + format!( + "{}{}/v0/manifest.json", + env!("MODRINTH_LAUNCHER_META_URL"), + x.key() + ), ) }) .collect::>(); @@ -1154,7 +1158,7 @@ impl CachedEntry { CacheValueType::MinecraftManifest => { fetch_original_value!( MinecraftManifest, - META_URL, + env!("MODRINTH_LAUNCHER_META_URL"), format!( "minecraft/v{}/manifest.json", daedalus::minecraft::CURRENT_FORMAT_VERSION @@ -1165,7 +1169,7 @@ impl CachedEntry { CacheValueType::Categories => { fetch_original_value!( Categories, - MODRINTH_API_URL, + env!("MODRINTH_API_URL"), "tag/category", CacheValue::Categories ) @@ -1173,7 +1177,7 @@ impl CachedEntry { CacheValueType::ReportTypes => { fetch_original_value!( ReportTypes, - MODRINTH_API_URL, + env!("MODRINTH_API_URL"), "tag/report_type", CacheValue::ReportTypes ) @@ -1181,7 +1185,7 @@ impl CachedEntry { CacheValueType::Loaders => { fetch_original_value!( Loaders, - MODRINTH_API_URL, + env!("MODRINTH_API_URL"), "tag/loader", CacheValue::Loaders ) @@ -1189,7 +1193,7 @@ impl CachedEntry { CacheValueType::GameVersions => { fetch_original_value!( GameVersions, - MODRINTH_API_URL, + env!("MODRINTH_API_URL"), "tag/game_version", CacheValue::GameVersions ) @@ -1197,7 +1201,7 @@ impl CachedEntry { CacheValueType::DonationPlatforms => { fetch_original_value!( DonationPlatforms, - MODRINTH_API_URL, + env!("MODRINTH_API_URL"), "tag/donation_platform", CacheValue::DonationPlatforms ) @@ -1297,14 +1301,12 @@ impl CachedEntry { } }); - let version_update_url = - format!("{MODRINTH_API_URL}version_files/update"); let variations = futures::future::try_join_all(filtered_keys.iter().map( |((loaders_key, game_version), hashes)| { fetch_json::>( Method::POST, - &version_update_url, + concat!(env!("MODRINTH_API_URL"), "version_files/update"), None, Some(serde_json::json!({ "algorithm": "sha1", @@ -1368,7 +1370,11 @@ impl CachedEntry { .map(|x| { ( x.key().to_string(), - format!("{MODRINTH_API_URL}search{}", x.key()), + format!( + "{}search{}", + env!("MODRINTH_API_URL"), + x.key() + ), ) }) .collect::>(); diff --git a/packages/app-lib/src/state/friends.rs b/packages/app-lib/src/state/friends.rs index 008660d9fc..0079daa40b 100644 --- a/packages/app-lib/src/state/friends.rs +++ b/packages/app-lib/src/state/friends.rs @@ -1,4 +1,3 @@ -use crate::config::{MODRINTH_API_URL_V3, MODRINTH_SOCKET_URL}; use crate::data::ModrinthCredentials; use crate::event::FriendPayload; use crate::event::emit::emit_friend; @@ -77,7 +76,8 @@ impl FriendsSocket { if let Some(credentials) = credentials { let mut request = format!( - "{MODRINTH_SOCKET_URL}_internal/launcher_socket?code={}", + "{}_internal/launcher_socket?code={}", + env!("MODRINTH_SOCKET_URL"), credentials.session ) .into_client_request()?; @@ -303,7 +303,7 @@ impl FriendsSocket { ) -> crate::Result> { fetch_json( Method::GET, - &format!("{MODRINTH_API_URL_V3}friends"), + concat!(env!("MODRINTH_API_URL_V3"), "friends"), None, None, semaphore, @@ -328,7 +328,7 @@ impl FriendsSocket { ) -> crate::Result<()> { fetch_advanced( Method::POST, - &format!("{MODRINTH_API_URL_V3}friend/{user_id}"), + &format!("{}friend/{user_id}", env!("MODRINTH_API_URL_V3")), None, None, None, @@ -349,7 +349,7 @@ impl FriendsSocket { ) -> crate::Result<()> { fetch_advanced( Method::DELETE, - &format!("{MODRINTH_API_URL_V3}friend/{user_id}"), + &format!("{}friend/{user_id}", env!("MODRINTH_API_URL_V3")), None, None, None, diff --git a/packages/app-lib/src/state/mr_auth.rs b/packages/app-lib/src/state/mr_auth.rs index 8aab2a37de..2d0d4ff324 100644 --- a/packages/app-lib/src/state/mr_auth.rs +++ b/packages/app-lib/src/state/mr_auth.rs @@ -1,4 +1,3 @@ -use crate::config::{MODRINTH_API_URL, MODRINTH_URL}; use crate::state::{CacheBehaviour, CachedEntry}; use crate::util::fetch::{FetchSemaphore, fetch_advanced}; use chrono::{DateTime, Duration, TimeZone, Utc}; @@ -31,7 +30,7 @@ impl ModrinthCredentials { let resp = fetch_advanced( Method::POST, - &format!("{MODRINTH_API_URL}session/refresh"), + concat!(env!("MODRINTH_API_URL"), "session/refresh"), None, None, Some(("Authorization", &*creds.session)), @@ -190,8 +189,8 @@ impl ModrinthCredentials { } } -pub fn get_login_url() -> String { - format!("{MODRINTH_URL}auth/sign-in?launcher=true") +pub const fn get_login_url() -> &'static str { + concat!(env!("MODRINTH_URL"), "auth/sign-in?launcher=true") } pub async fn finish_login_flow( @@ -216,7 +215,7 @@ async fn fetch_info( ) -> crate::Result { let result = fetch_advanced( Method::GET, - &format!("{MODRINTH_API_URL}user"), + concat!(env!("MODRINTH_API_URL"), "user"), None, None, Some(("Authorization", token)), diff --git a/packages/app-lib/src/util/fetch.rs b/packages/app-lib/src/util/fetch.rs index fb62386aac..9b2e872086 100644 --- a/packages/app-lib/src/util/fetch.rs +++ b/packages/app-lib/src/util/fetch.rs @@ -1,6 +1,5 @@ //! Functions for fetching information from the Internet use super::io::{self, IOError}; -use crate::config::{MODRINTH_API_URL, MODRINTH_API_URL_V3}; use crate::event::LoadingBarId; use crate::event::emit::emit_loading; use bytes::Bytes; @@ -84,8 +83,8 @@ pub async fn fetch_advanced( .as_ref() .is_none_or(|x| &*x.0.to_lowercase() != "authorization") && (url.starts_with("https://cdn.modrinth.com") - || url.starts_with(MODRINTH_API_URL) - || url.starts_with(MODRINTH_API_URL_V3)) + || url.starts_with(env!("MODRINTH_API_URL")) + || url.starts_with(env!("MODRINTH_API_URL_V3"))) { crate::state::ModrinthCredentials::get_active(exec).await? } else {