diff --git a/Cargo.lock b/Cargo.lock index 92cc9f38..af57103a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4503,6 +4503,7 @@ dependencies = [ "actix-http", "actix-multipart", "actix-rt", + "actix-utils", "actix-web", "actix-web-prom", "actix-ws", diff --git a/Cargo.toml b/Cargo.toml index 089eb20d..bd984c61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ actix-files = "0.6.8" actix-http = "3.11.2" actix-multipart = "0.7.2" actix-rt = "2.11.0" +actix-utils = "3.0.1" actix-web = "4.11.0" actix-web-prom = "0.10.0" actix-ws = "0.3.0" diff --git a/apps/labrinth/.env.docker-compose b/apps/labrinth/.env.docker-compose index 6102fc99..fcb821cb 100644 --- a/apps/labrinth/.env.docker-compose +++ b/apps/labrinth/.env.docker-compose @@ -5,6 +5,7 @@ SENTRY_DSN=none SITE_URL=http://localhost:3000 # This CDN URL matches the local storage backend set below, which uses MOCK_FILE_PATH CDN_URL=file:///tmp/modrinth +CDN_ALT_URL=file:///tmp/modrinth LABRINTH_ADMIN_KEY=feedbeef RATE_LIMIT_IGNORE_KEY=feedbeef diff --git a/apps/labrinth/.env.local b/apps/labrinth/.env.local index 261513ed..95c7712d 100644 --- a/apps/labrinth/.env.local +++ b/apps/labrinth/.env.local @@ -5,6 +5,7 @@ SENTRY_DSN=none SITE_URL=http://localhost:3000 # This CDN URL matches the local storage backend set below, which uses MOCK_FILE_PATH CDN_URL=file:///tmp/modrinth +CDN_ALT_URL=file:///tmp/modrinth LABRINTH_ADMIN_KEY=feedbeef LABRINTH_EXTERNAL_NOTIFICATION_KEY=beeffeed RATE_LIMIT_IGNORE_KEY=feedbeef diff --git a/apps/labrinth/Cargo.toml b/apps/labrinth/Cargo.toml index 381e4f7b..3994118c 100644 --- a/apps/labrinth/Cargo.toml +++ b/apps/labrinth/Cargo.toml @@ -12,9 +12,10 @@ path = "src/main.rs" [dependencies] actix-cors = { workspace = true } actix-files = { workspace = true } -actix-http = { workspace = true, optional = true } +actix-http = { workspace = true } actix-multipart = { workspace = true } actix-rt = { workspace = true } +actix-utils = { workspace = true } actix-web = { workspace = true } actix-web-prom = { workspace = true, features = ["process"] } actix-ws = { workspace = true } @@ -150,7 +151,7 @@ tikv-jemallocator = { workspace = true, features = [ ] } [features] -test = ["dep:actix-http"] +test = [] [lints] workspace = true diff --git a/apps/labrinth/src/auth/checks.rs b/apps/labrinth/src/auth/checks.rs index f56036df..cc7ba451 100644 --- a/apps/labrinth/src/auth/checks.rs +++ b/apps/labrinth/src/auth/checks.rs @@ -4,6 +4,7 @@ use crate::database::models::version_item::VersionQueryResult; use crate::database::models::{DBCollection, DBOrganization, DBTeamMember}; use crate::database::redis::RedisPool; use crate::database::{DBProject, DBVersion, models}; +use crate::file_hosting::CdnChoice; use crate::models::users::User; use crate::routes::ApiError; use futures::TryStreamExt; @@ -195,6 +196,7 @@ pub async fn filter_visible_versions( user_option: &Option, pool: &PgPool, redis: &RedisPool, + cdn_choice: &CdnChoice, ) -> Result, ApiError> { let filtered_version_ids = filter_visible_version_ids( versions.iter().map(|x| &x.inner).collect_vec(), @@ -204,7 +206,10 @@ pub async fn filter_visible_versions( ) .await?; versions.retain(|x| filtered_version_ids.contains(&x.inner.id)); - Ok(versions.into_iter().map(|x| x.into()).collect()) + Ok(versions + .into_iter() + .map(|v| crate::models::projects::Version::from(v, cdn_choice)) + .collect()) } impl ValidateAuthorized for models::DBOAuthClient { diff --git a/apps/labrinth/src/file_hosting/cdn.rs b/apps/labrinth/src/file_hosting/cdn.rs new file mode 100644 index 00000000..fb2e4f8c --- /dev/null +++ b/apps/labrinth/src/file_hosting/cdn.rs @@ -0,0 +1,79 @@ +use std::{convert::Infallible, sync::Arc}; + +use actix_http::header::HeaderName; +use actix_utils::future::{Ready, ready}; +use actix_web::FromRequest; +use eyre::Result; + +use crate::util::env::env_var; + +#[derive(Debug, Clone, Copy)] +pub struct UseAltCdn(pub bool); + +const HEADER_NAME: HeaderName = HeaderName::from_static("labrinth-alt-cdn"); + +impl FromRequest for UseAltCdn { + type Error = Infallible; + type Future = Ready>; + + fn from_request( + req: &actix_web::HttpRequest, + _payload: &mut actix_http::Payload, + ) -> Self::Future { + ready(Ok(Self(use_alt_cdn(req)))) + } +} + +fn use_alt_cdn(req: &actix_web::HttpRequest) -> bool { + let Some(use_alt_cdn) = req.headers().get(HEADER_NAME) else { + return false; + }; + use_alt_cdn.as_bytes() == b"true" +} + +#[derive(Debug, Clone)] +pub enum CdnChoice { + Default, + Alt { + base_url: Arc, + alt_url: Arc, + }, +} + +impl CdnChoice { + pub fn transform_file_url(&self, file_url: impl Into) -> String { + let file_url = file_url.into(); + match self { + Self::Default => file_url, + Self::Alt { base_url, alt_url } => { + file_url.replace(&**base_url, alt_url) + } + } + } +} + +#[derive(Debug, Clone)] +pub struct CdnConfig { + pub url: Arc, + pub alt_url: Arc, +} + +impl CdnConfig { + pub fn from_env() -> Result { + Ok(Self { + url: Arc::from(env_var("CDN_URL")?), + alt_url: Arc::from(env_var("CDN_ALT_URL")?), + }) + } + + pub fn make_choice(&self, use_alt_cdn: bool) -> CdnChoice { + if use_alt_cdn { + CdnChoice::Alt { + base_url: self.url.clone(), + alt_url: self.alt_url.clone(), + } + } else { + CdnChoice::Default + } + } +} diff --git a/apps/labrinth/src/file_hosting/mod.rs b/apps/labrinth/src/file_hosting/mod.rs index 7de0ff6a..810d0d60 100644 --- a/apps/labrinth/src/file_hosting/mod.rs +++ b/apps/labrinth/src/file_hosting/mod.rs @@ -1,10 +1,12 @@ use async_trait::async_trait; use thiserror::Error; +mod cdn; mod mock; mod s3_host; use bytes::Bytes; +pub use cdn::*; pub use mock::MockHost; pub use s3_host::{S3BucketConfig, S3Host}; diff --git a/apps/labrinth/src/lib.rs b/apps/labrinth/src/lib.rs index 27995d32..a81837e0 100644 --- a/apps/labrinth/src/lib.rs +++ b/apps/labrinth/src/lib.rs @@ -18,6 +18,7 @@ use util::gotenberg::GotenbergClient; use crate::background_task::update_versions; use crate::database::ReadOnlyPgPool; +use crate::file_hosting::CdnConfig; use crate::queue::billing::{index_billing, index_subscriptions}; use crate::queue::moderation::AutomatedModerationQueue; use crate::util::anrok; @@ -55,6 +56,7 @@ pub struct LabrinthConfig { pub redis_pool: RedisPool, pub clickhouse: Client, pub file_host: Arc, + pub cdn_config: web::Data, pub maxmind: web::Data, pub scheduler: Arc, pub ip_salt: Pepper, @@ -275,6 +277,7 @@ pub fn app_setup( redis_pool, clickhouse: clickhouse.clone(), file_host, + cdn_config: web::Data::new(CdnConfig::from_env().unwrap()), maxmind: web::Data::new(maxmind), scheduler: Arc::new(scheduler), ip_salt, @@ -316,6 +319,7 @@ pub fn app_config( .app_data(web::Data::new(labrinth_config.pool.clone())) .app_data(web::Data::new(labrinth_config.ro_pool.clone())) .app_data(web::Data::new(labrinth_config.file_host.clone())) + .app_data(labrinth_config.cdn_config.clone()) .app_data(web::Data::new(labrinth_config.search_config.clone())) .app_data(web::Data::new(labrinth_config.gotenberg_client.clone())) .app_data(labrinth_config.session_queue.clone()) @@ -374,6 +378,7 @@ pub fn check_env_vars() -> bool { failed |= check_var::("SITE_URL"); failed |= check_var::("CDN_URL"); + failed |= check_var::("CDN_ALT_URL"); failed |= check_var::("LABRINTH_ADMIN_KEY"); failed |= check_var::("LABRINTH_EXTERNAL_NOTIFICATION_KEY"); failed |= check_var::("RATE_LIMIT_IGNORE_KEY"); diff --git a/apps/labrinth/src/models/v3/projects.rs b/apps/labrinth/src/models/v3/projects.rs index 22f5050a..8355ff3b 100644 --- a/apps/labrinth/src/models/v3/projects.rs +++ b/apps/labrinth/src/models/v3/projects.rs @@ -4,6 +4,7 @@ use std::mem; use crate::database::models::loader_fields::VersionField; use crate::database::models::project_item::{LinkUrl, ProjectQueryResult}; use crate::database::models::version_item::VersionQueryResult; +use crate::file_hosting::CdnChoice; use crate::models::ids::{ OrganizationId, ProjectId, TeamId, ThreadId, VersionId, }; @@ -702,8 +703,8 @@ where Ok(map) } -impl From for Version { - fn from(data: VersionQueryResult) -> Version { +impl Version { + pub fn from(data: VersionQueryResult, cdn_choice: &CdnChoice) -> Self { let v = data.inner; Version { id: v.id.into(), @@ -731,7 +732,7 @@ impl From for Version { .files .into_iter() .map(|f| VersionFile { - url: f.url, + url: cdn_choice.transform_file_url(f.url), filename: f.filename, hashes: f.hashes, primary: f.primary, diff --git a/apps/labrinth/src/queue/moderation.rs b/apps/labrinth/src/queue/moderation.rs index cb17fe84..0ac5ad2f 100644 --- a/apps/labrinth/src/queue/moderation.rs +++ b/apps/labrinth/src/queue/moderation.rs @@ -3,6 +3,7 @@ use crate::database; use crate::database::models::notification_item::NotificationBuilder; use crate::database::models::thread_item::ThreadMessageBuilder; use crate::database::redis::RedisPool; +use crate::file_hosting::CdnChoice; use crate::models::ids::ProjectId; use crate::models::notifications::NotificationBody; use crate::models::pack::{PackFile, PackFileHash, PackFormat}; @@ -362,6 +363,7 @@ impl AutomatedModerationQueue { &None, &pool, &redis, + &CdnChoice::Default, ) .await?; diff --git a/apps/labrinth/src/routes/updates.rs b/apps/labrinth/src/routes/updates.rs index 08fe83fb..b5ffdc46 100644 --- a/apps/labrinth/src/routes/updates.rs +++ b/apps/labrinth/src/routes/updates.rs @@ -9,6 +9,7 @@ use crate::auth::get_user_from_headers; use crate::database; use crate::database::models::legacy_loader_fields::MinecraftGameVersion; use crate::database::redis::RedisPool; +use crate::file_hosting::{CdnConfig, UseAltCdn}; use crate::models::pats::Scopes; use crate::models::projects::VersionType; use crate::queue::session::AuthQueue; @@ -37,6 +38,8 @@ pub async fn forge_updates( pool: web::Data, redis: web::Data, session_queue: web::Data, + cdn_config: web::Data, + UseAltCdn(use_alt_cdn): UseAltCdn, ) -> Result { const ERROR: &str = "The specified project does not exist!"; @@ -82,6 +85,7 @@ pub async fn forge_updates( &user_option, &pool, &redis, + &cdn_config.make_choice(use_alt_cdn), ) .await?; diff --git a/apps/labrinth/src/routes/v2/projects.rs b/apps/labrinth/src/routes/v2/projects.rs index 234c4586..c4f26e81 100644 --- a/apps/labrinth/src/routes/v2/projects.rs +++ b/apps/labrinth/src/routes/v2/projects.rs @@ -1,7 +1,8 @@ use crate::database::models::categories::LinkPlatform; use crate::database::models::{project_item, version_item}; use crate::database::redis::RedisPool; -use crate::file_hosting::FileHost; +use crate::file_hosting::{CdnChoice, CdnConfig}; +use crate::file_hosting::{FileHost, UseAltCdn}; use crate::models::projects::{ Link, MonetizationStatus, Project, ProjectStatus, SearchRequest, Version, }; @@ -267,6 +268,8 @@ pub async fn dependency_list( pool: web::Data, redis: web::Data, session_queue: web::Data, + cdn_config: web::Data, + use_alt_cdn: UseAltCdn, ) -> Result { // TODO: tests, probably let response = v3::projects::dependency_list( @@ -275,6 +278,8 @@ pub async fn dependency_list( pool.clone(), redis.clone(), session_queue, + cdn_config, + use_alt_cdn, ) .await .or_else(v2_reroute::flatten_404_error)?; @@ -546,7 +551,7 @@ pub async fn project_edit( version_item::DBVersion::get_many(&version_ids, &**pool, &redis) .await?; for version in versions { - let version = Version::from(version); + let version = Version::from(version, &CdnChoice::Default); let mut fields = version.fields; let (current_client_side, current_server_side) = v2_reroute::convert_v3_side_types_to_v2_side_types( diff --git a/apps/labrinth/src/routes/v2/version_file.rs b/apps/labrinth/src/routes/v2/version_file.rs index 1d843b5b..769cc85c 100644 --- a/apps/labrinth/src/routes/v2/version_file.rs +++ b/apps/labrinth/src/routes/v2/version_file.rs @@ -1,6 +1,7 @@ use super::ApiError; use crate::database::ReadOnlyPgPool; use crate::database::redis::RedisPool; +use crate::file_hosting::{CdnConfig, UseAltCdn}; use crate::models::projects::{Project, Version, VersionType}; use crate::models::v2::projects::{LegacyProject, LegacyVersion}; use crate::queue::session::AuthQueue; @@ -38,6 +39,8 @@ pub async fn get_version_from_hash( redis: web::Data, hash_query: web::Query, session_queue: web::Data, + cdn_config: web::Data, + use_alt_cdn: UseAltCdn, ) -> Result { let response = v3::version_file::get_version_from_hash( req, @@ -46,6 +49,8 @@ pub async fn get_version_from_hash( redis, hash_query, session_queue, + cdn_config, + use_alt_cdn, ) .await .or_else(v2_reroute::flatten_404_error)?; @@ -69,6 +74,7 @@ pub async fn download_version( redis: web::Data, hash_query: web::Query, session_queue: web::Data, + use_alt_cdn: UseAltCdn, ) -> Result { // Returns TemporaryRedirect, so no need to convert to V2 v3::version_file::download_version( @@ -78,6 +84,7 @@ pub async fn download_version( redis, hash_query, session_queue, + use_alt_cdn, ) .await .or_else(v2_reroute::flatten_404_error) @@ -122,6 +129,8 @@ pub async fn get_update_from_hash( hash_query: web::Query, update_data: web::Json, session_queue: web::Data, + cdn_config: web::Data, + use_alt_cdn: UseAltCdn, ) -> Result { let update_data = update_data.into_inner(); let mut loader_fields = HashMap::new(); @@ -146,6 +155,8 @@ pub async fn get_update_from_hash( hash_query, web::Json(update_data), session_queue, + cdn_config, + use_alt_cdn, ) .await .or_else(v2_reroute::flatten_404_error)?; @@ -175,6 +186,8 @@ pub async fn get_versions_from_hashes( redis: web::Data, file_data: web::Json, session_queue: web::Data, + cdn_config: web::Data, + use_alt_cdn: UseAltCdn, ) -> Result { let file_data = file_data.into_inner(); let file_data = v3::version_file::FileHashes { @@ -187,6 +200,8 @@ pub async fn get_versions_from_hashes( redis, web::Json(file_data), session_queue, + cdn_config, + use_alt_cdn, ) .await .or_else(v2_reroute::flatten_404_error)?; @@ -281,6 +296,8 @@ pub async fn update_files( pool: web::Data, redis: web::Data, update_data: web::Json, + cdn_config: web::Data, + use_alt_cdn: UseAltCdn, ) -> Result { let update_data = update_data.into_inner(); let update_data = v3::version_file::ManyUpdateData { @@ -291,10 +308,15 @@ pub async fn update_files( hashes: update_data.hashes, }; - let response = - v3::version_file::update_files(pool, redis, web::Json(update_data)) - .await - .or_else(v2_reroute::flatten_404_error)?; + let response = v3::version_file::update_files( + pool, + redis, + web::Json(update_data), + cdn_config, + use_alt_cdn, + ) + .await + .or_else(v2_reroute::flatten_404_error)?; // Convert response to V2 format match v2_reroute::extract_ok_json::>(response) @@ -335,6 +357,8 @@ pub async fn update_individual_files( redis: web::Data, update_data: web::Json, session_queue: web::Data, + cdn_config: web::Data, + use_alt_cdn: UseAltCdn, ) -> Result { let update_data = update_data.into_inner(); let update_data = v3::version_file::ManyFileUpdateData { @@ -368,6 +392,8 @@ pub async fn update_individual_files( redis, web::Json(update_data), session_queue, + cdn_config, + use_alt_cdn, ) .await .or_else(v2_reroute::flatten_404_error)?; diff --git a/apps/labrinth/src/routes/v2/versions.rs b/apps/labrinth/src/routes/v2/versions.rs index 6e91f8a3..bc6013ae 100644 --- a/apps/labrinth/src/routes/v2/versions.rs +++ b/apps/labrinth/src/routes/v2/versions.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use super::ApiError; use crate::database::redis::RedisPool; +use crate::file_hosting::{CdnConfig, UseAltCdn}; use crate::models; use crate::models::ids::VersionId; use crate::models::projects::{ @@ -47,6 +48,8 @@ pub async fn version_list( pool: web::Data, redis: web::Data, session_queue: web::Data, + cdn_config: web::Data, + use_alt_cdn: UseAltCdn, ) -> Result { let loaders = if let Some(loaders) = filters.loaders { if let Ok(mut loaders) = serde_json::from_str::>(&loaders) { @@ -104,6 +107,8 @@ pub async fn version_list( pool, redis, session_queue, + cdn_config, + use_alt_cdn, ) .await .or_else(v2_reroute::flatten_404_error)?; @@ -129,6 +134,8 @@ pub async fn version_project_get( pool: web::Data, redis: web::Data, session_queue: web::Data, + cdn_config: web::Data, + use_alt_cdn: UseAltCdn, ) -> Result { let id = info.into_inner(); let response = v3::versions::version_project_get_helper( @@ -137,6 +144,8 @@ pub async fn version_project_get( pool, redis, session_queue, + cdn_config, + use_alt_cdn, ) .await .or_else(v2_reroute::flatten_404_error)?; @@ -162,6 +171,8 @@ pub async fn versions_get( pool: web::Data, redis: web::Data, session_queue: web::Data, + cdn_config: web::Data, + use_alt_cdn: UseAltCdn, ) -> Result { let ids = v3::versions::VersionIds { ids: ids.ids }; let response = v3::versions::versions_get( @@ -170,6 +181,8 @@ pub async fn versions_get( pool, redis, session_queue, + cdn_config, + use_alt_cdn, ) .await .or_else(v2_reroute::flatten_404_error)?; @@ -194,12 +207,21 @@ pub async fn version_get( pool: web::Data, redis: web::Data, session_queue: web::Data, + cdn_config: web::Data, + use_alt_cdn: UseAltCdn, ) -> Result { let id = info.into_inner().0; - let response = - v3::versions::version_get_helper(req, id, pool, redis, session_queue) - .await - .or_else(v2_reroute::flatten_404_error)?; + let response = v3::versions::version_get_helper( + req, + id, + pool, + redis, + session_queue, + cdn_config, + use_alt_cdn, + ) + .await + .or_else(v2_reroute::flatten_404_error)?; // Convert response to V2 format match v2_reroute::extract_ok_json::(response).await { Ok(version) => { @@ -253,6 +275,8 @@ pub async fn version_edit( redis: web::Data, new_version: web::Json, session_queue: web::Data, + cdn_config: web::Data, + use_alt_cdn: UseAltCdn, ) -> Result { let new_version = new_version.into_inner(); @@ -271,6 +295,8 @@ pub async fn version_edit( pool.clone(), redis.clone(), session_queue.clone(), + cdn_config, + use_alt_cdn, ) .await .or_else(v2_reroute::flatten_404_error)?; diff --git a/apps/labrinth/src/routes/v3/projects.rs b/apps/labrinth/src/routes/v3/projects.rs index 0b93d989..9f151bc7 100644 --- a/apps/labrinth/src/routes/v3/projects.rs +++ b/apps/labrinth/src/routes/v3/projects.rs @@ -9,7 +9,7 @@ use crate::database::models::thread_item::ThreadMessageBuilder; use crate::database::models::{DBTeamMember, ids as db_ids, image_item}; use crate::database::redis::RedisPool; use crate::database::{self, models as db_models}; -use crate::file_hosting::{FileHost, FileHostPublicity}; +use crate::file_hosting::{CdnConfig, FileHost, FileHostPublicity, UseAltCdn}; use crate::models; use crate::models::ids::{ProjectId, VersionId}; use crate::models::images::ImageContext; @@ -1068,6 +1068,8 @@ pub async fn dependency_list( pool: web::Data, redis: web::Data, session_queue: web::Data, + cdn_config: web::Data, + UseAltCdn(use_alt_cdn): UseAltCdn, ) -> Result { let string = info.into_inner().0; @@ -1136,6 +1138,7 @@ pub async fn dependency_list( &user_option, &pool, &redis, + &cdn_config.make_choice(use_alt_cdn), ) .await?; diff --git a/apps/labrinth/src/routes/v3/version_file.rs b/apps/labrinth/src/routes/v3/version_file.rs index 29a7fedd..cd72cbbb 100644 --- a/apps/labrinth/src/routes/v3/version_file.rs +++ b/apps/labrinth/src/routes/v3/version_file.rs @@ -3,6 +3,7 @@ use crate::auth::checks::{filter_visible_versions, is_visible_version}; use crate::auth::{filter_visible_projects, get_user_from_headers}; use crate::database::ReadOnlyPgPool; use crate::database::redis::RedisPool; +use crate::file_hosting::{CdnConfig, UseAltCdn}; use crate::models::ids::VersionId; use crate::models::pats::Scopes; use crate::models::projects::VersionType; @@ -41,6 +42,8 @@ pub async fn get_version_from_hash( redis: web::Data, hash_query: web::Query, session_queue: web::Data, + cdn_config: web::Data, + UseAltCdn(use_alt_cdn): UseAltCdn, ) -> Result { let user_option = get_user_from_headers( &req, @@ -75,8 +78,10 @@ pub async fn get_version_from_hash( return Err(ApiError::NotFound); } - Ok(HttpResponse::Ok() - .json(models::projects::Version::from(version))) + Ok(HttpResponse::Ok().json(models::projects::Version::from( + version, + &cdn_config.make_choice(use_alt_cdn), + ))) } else { Err(ApiError::NotFound) } @@ -127,6 +132,8 @@ pub async fn get_update_from_hash( hash_query: web::Query, update_data: web::Json, session_queue: web::Data, + cdn_config: web::Data, + UseAltCdn(use_alt_cdn): UseAltCdn, ) -> Result { let user_option = get_user_from_headers( &req, @@ -195,9 +202,12 @@ pub async fn get_update_from_hash( return Err(ApiError::NotFound); } - return Ok( - HttpResponse::Ok().json(models::projects::Version::from(first)) - ); + return Ok(HttpResponse::Ok().json( + models::projects::Version::from( + first, + &cdn_config.make_choice(use_alt_cdn), + ), + )); } } Err(ApiError::NotFound) @@ -216,6 +226,8 @@ pub async fn get_versions_from_hashes( redis: web::Data, file_data: web::Json, session_queue: web::Data, + cdn_config: web::Data, + UseAltCdn(use_alt_cdn): UseAltCdn, ) -> Result { let user_option = get_user_from_headers( &req, @@ -248,6 +260,7 @@ pub async fn get_versions_from_hashes( &user_option, &pool, &redis, + &cdn_config.make_choice(use_alt_cdn), ) .await?; @@ -335,6 +348,8 @@ pub async fn update_files( pool: web::Data, redis: web::Data, update_data: web::Json, + cdn_config: web::Data, + UseAltCdn(use_alt_cdn): UseAltCdn, ) -> Result { let algorithm = update_data .algorithm @@ -392,10 +407,11 @@ pub async fn update_files( .find(|x| x.inner.project_id == file.project_id) && let Some(hash) = file.hashes.get(&algorithm) { - response.insert( - hash.clone(), - models::projects::Version::from(version.clone()), + let version = models::projects::Version::from( + version.clone(), + &cdn_config.make_choice(use_alt_cdn), ); + response.insert(hash.clone(), version); } } @@ -422,6 +438,8 @@ pub async fn update_individual_files( redis: web::Data, update_data: web::Json, session_queue: web::Data, + cdn_config: web::Data, + UseAltCdn(use_alt_cdn): UseAltCdn, ) -> Result { let user_option = get_user_from_headers( &req, @@ -524,10 +542,11 @@ pub async fn update_individual_files( ) .await? { - response.insert( - hash.clone(), - models::projects::Version::from(version.clone()), + let version = models::projects::Version::from( + version.clone(), + &cdn_config.make_choice(use_alt_cdn), ); + response.insert(hash.clone(), version); } } } @@ -674,6 +693,7 @@ pub async fn download_version( redis: web::Data, hash_query: web::Query, session_queue: web::Data, + UseAltCdn(use_alt_cdn): UseAltCdn, ) -> Result { let user_option = get_user_from_headers( &req, @@ -704,6 +724,15 @@ pub async fn download_version( database::models::DBVersion::get(file.version_id, &**pool, &redis) .await?; + let url = if use_alt_cdn { + let cdn_url = dotenvy::var("CDN_URL").unwrap(); + let cdn_alt_url = dotenvy::var("CDN_ALT_URL").unwrap(); + + file.url.replace(&cdn_url, &cdn_alt_url) + } else { + file.url.clone() + }; + if let Some(version) = version { if !is_visible_version(&version.inner, &user_option, &pool, &redis) .await? @@ -712,8 +741,8 @@ pub async fn download_version( } Ok(HttpResponse::TemporaryRedirect() - .append_header(("Location", &*file.url)) - .json(DownloadRedirect { url: file.url })) + .append_header(("Location", &*url)) + .json(DownloadRedirect { url })) } else { Err(ApiError::NotFound) } diff --git a/apps/labrinth/src/routes/v3/versions.rs b/apps/labrinth/src/routes/v3/versions.rs index cdca2407..5c5964c3 100644 --- a/apps/labrinth/src/routes/v3/versions.rs +++ b/apps/labrinth/src/routes/v3/versions.rs @@ -14,6 +14,7 @@ use crate::database::models::version_item::{ }; use crate::database::models::{DBOrganization, image_item}; use crate::database::redis::RedisPool; +use crate::file_hosting::{CdnConfig, UseAltCdn}; use crate::models; use crate::models::ids::VersionId; use crate::models::images::ImageContext; @@ -61,16 +62,30 @@ pub async fn version_project_get( pool: web::Data, redis: web::Data, session_queue: web::Data, + cdn_config: web::Data, + use_alt_cdn: UseAltCdn, ) -> Result { let info = info.into_inner(); - version_project_get_helper(req, info, pool, redis, session_queue).await + version_project_get_helper( + req, + info, + pool, + redis, + session_queue, + cdn_config, + use_alt_cdn, + ) + .await } + pub async fn version_project_get_helper( req: HttpRequest, id: (String, String), pool: web::Data, redis: web::Data, session_queue: web::Data, + cdn_config: web::Data, + UseAltCdn(use_alt_cdn): UseAltCdn, ) -> Result { let result = database::models::DBProject::get(&id.0, &**pool, &redis).await?; @@ -110,8 +125,12 @@ pub async fn version_project_get_helper( && is_visible_version(&version.inner, &user_option, &pool, &redis) .await? { - return Ok(HttpResponse::Ok() - .json(models::projects::Version::from(version))); + return Ok(HttpResponse::Ok().json( + models::projects::Version::from( + version, + &cdn_config.make_choice(use_alt_cdn), + ), + )); } } @@ -129,6 +148,8 @@ pub async fn versions_get( pool: web::Data, redis: web::Data, session_queue: web::Data, + cdn_config: web::Data, + UseAltCdn(use_alt_cdn): UseAltCdn, ) -> Result { let version_ids = serde_json::from_str::>(&ids.ids)? @@ -150,9 +171,14 @@ pub async fn versions_get( .map(|x| x.1) .ok(); - let versions = - filter_visible_versions(versions_data, &user_option, &pool, &redis) - .await?; + let versions = filter_visible_versions( + versions_data, + &user_option, + &pool, + &redis, + &cdn_config.make_choice(use_alt_cdn), + ) + .await?; Ok(HttpResponse::Ok().json(versions)) } @@ -163,9 +189,20 @@ pub async fn version_get( pool: web::Data, redis: web::Data, session_queue: web::Data, + cdn_config: web::Data, + use_alt_cdn: UseAltCdn, ) -> Result { let id = info.into_inner().0; - version_get_helper(req, id, pool, redis, session_queue).await + version_get_helper( + req, + id, + pool, + redis, + session_queue, + cdn_config, + use_alt_cdn, + ) + .await } pub async fn version_get_helper( @@ -174,6 +211,8 @@ pub async fn version_get_helper( pool: web::Data, redis: web::Data, session_queue: web::Data, + cdn_config: web::Data, + UseAltCdn(use_alt_cdn): UseAltCdn, ) -> Result { let version_data = database::models::DBVersion::get(id.into(), &**pool, &redis).await?; @@ -192,9 +231,10 @@ pub async fn version_get_helper( if let Some(data) = version_data && is_visible_version(&data.inner, &user_option, &pool, &redis).await? { - return Ok( - HttpResponse::Ok().json(models::projects::Version::from(data)) - ); + return Ok(HttpResponse::Ok().json(models::projects::Version::from( + data, + &cdn_config.make_choice(use_alt_cdn), + ))); } Err(ApiError::NotFound) @@ -724,6 +764,8 @@ pub async fn version_list( pool: web::Data, redis: web::Data, session_queue: web::Data, + cdn_config: web::Data, + UseAltCdn(use_alt_cdn): UseAltCdn, ) -> Result { let string = info.into_inner().0; @@ -856,9 +898,14 @@ pub async fn version_list( }); response.dedup_by(|a, b| a.inner.id == b.inner.id); - let response = - filter_visible_versions(response, &user_option, &pool, &redis) - .await?; + let response = filter_visible_versions( + response, + &user_option, + &pool, + &redis, + &cdn_config.make_choice(use_alt_cdn), + ) + .await?; Ok(HttpResponse::Ok().json(response)) } else {