Improve environment variable handling and reading (#5389)
* wip: better env var reading * move most env vars to env.rs * migrate more env vars * more migration * more migrations * More migration * 🦀 dotenvy is gone (almost) * 🦀 dotenvy is gone 🦀 * Fix mural source account env var handling * Remove defaults from admin key vars * dummy commit to update github pr * fix ci
This commit is contained in:
@@ -8,6 +8,8 @@ SITE_URL=http://localhost:3000
|
|||||||
# This CDN URL matches the local storage backend set below, which uses MOCK_FILE_PATH
|
# This CDN URL matches the local storage backend set below, which uses MOCK_FILE_PATH
|
||||||
CDN_URL=file:///tmp/modrinth
|
CDN_URL=file:///tmp/modrinth
|
||||||
LABRINTH_ADMIN_KEY=feedbeef
|
LABRINTH_ADMIN_KEY=feedbeef
|
||||||
|
LABRINTH_MEDAL_KEY=
|
||||||
|
LABRINTH_EXTERNAL_NOTIFICATION_KEY=beeffeed
|
||||||
RATE_LIMIT_IGNORE_KEY=feedbeef
|
RATE_LIMIT_IGNORE_KEY=feedbeef
|
||||||
|
|
||||||
DATABASE_URL=postgresql://labrinth:labrinth@labrinth-postgres/labrinth
|
DATABASE_URL=postgresql://labrinth:labrinth@labrinth-postgres/labrinth
|
||||||
@@ -152,6 +154,6 @@ ARCHON_URL=none
|
|||||||
MURALPAY_API_URL=https://api.muralpay.com
|
MURALPAY_API_URL=https://api.muralpay.com
|
||||||
MURALPAY_API_KEY=none
|
MURALPAY_API_KEY=none
|
||||||
MURALPAY_TRANSFER_API_KEY=none
|
MURALPAY_TRANSFER_API_KEY=none
|
||||||
MURALPAY_SOURCE_ACCOUNT_ID=none
|
MURALPAY_SOURCE_ACCOUNT_ID=00000000-0000-0000-0000-000000000000
|
||||||
|
|
||||||
DEFAULT_AFFILIATE_REVENUE_SPLIT=0.1
|
DEFAULT_AFFILIATE_REVENUE_SPLIT=0.1
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ SITE_URL=http://localhost:3000
|
|||||||
# This CDN URL matches the local storage backend set below, which uses MOCK_FILE_PATH
|
# This CDN URL matches the local storage backend set below, which uses MOCK_FILE_PATH
|
||||||
CDN_URL=file:///tmp/modrinth
|
CDN_URL=file:///tmp/modrinth
|
||||||
LABRINTH_ADMIN_KEY=feedbeef
|
LABRINTH_ADMIN_KEY=feedbeef
|
||||||
|
LABRINTH_MEDAL_KEY=
|
||||||
LABRINTH_EXTERNAL_NOTIFICATION_KEY=beeffeed
|
LABRINTH_EXTERNAL_NOTIFICATION_KEY=beeffeed
|
||||||
RATE_LIMIT_IGNORE_KEY=feedbeef
|
RATE_LIMIT_IGNORE_KEY=feedbeef
|
||||||
|
|
||||||
@@ -163,6 +164,6 @@ ARCHON_URL=none
|
|||||||
MURALPAY_API_URL=https://api-staging.muralpay.com
|
MURALPAY_API_URL=https://api-staging.muralpay.com
|
||||||
MURALPAY_API_KEY=none
|
MURALPAY_API_KEY=none
|
||||||
MURALPAY_TRANSFER_API_KEY=none
|
MURALPAY_TRANSFER_API_KEY=none
|
||||||
MURALPAY_SOURCE_ACCOUNT_ID=none
|
MURALPAY_SOURCE_ACCOUNT_ID=00000000-0000-0000-0000-000000000000
|
||||||
|
|
||||||
DEFAULT_AFFILIATE_REVENUE_SPLIT=0.1
|
DEFAULT_AFFILIATE_REVENUE_SPLIT=0.1
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use super::AuthProvider;
|
|||||||
use crate::auth::AuthenticationError;
|
use crate::auth::AuthenticationError;
|
||||||
use crate::database::models::{DBUser, user_item};
|
use crate::database::models::{DBUser, user_item};
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::models::pats::Scopes;
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::users::User;
|
use crate::models::users::User;
|
||||||
use crate::queue::session::AuthQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
@@ -146,7 +147,7 @@ where
|
|||||||
user_item::DBUser::get_id(session.user_id, executor, redis)
|
user_item::DBUser::get_id(session.user_id, executor, redis)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let rate_limit_ignore = dotenvy::var("RATE_LIMIT_IGNORE_KEY")?;
|
let rate_limit_ignore = &ENV.RATE_LIMIT_IGNORE_KEY;
|
||||||
if req
|
if req
|
||||||
.headers()
|
.headers()
|
||||||
.get("x-ratelimit-key")
|
.get("x-ratelimit-key")
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ mod fetch;
|
|||||||
|
|
||||||
pub use fetch::*;
|
pub use fetch::*;
|
||||||
|
|
||||||
|
use crate::env::ENV;
|
||||||
|
|
||||||
pub async fn init_client() -> clickhouse::error::Result<clickhouse::Client> {
|
pub async fn init_client() -> clickhouse::error::Result<clickhouse::Client> {
|
||||||
init_client_with_database(&dotenvy::var("CLICKHOUSE_DATABASE").unwrap())
|
init_client_with_database(&ENV.CLICKHOUSE_DATABASE).await
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init_client_with_database(
|
pub async fn init_client_with_database(
|
||||||
@@ -24,9 +25,9 @@ pub async fn init_client_with_database(
|
|||||||
.build(https_connector);
|
.build(https_connector);
|
||||||
|
|
||||||
clickhouse::Client::with_http_client(hyper_client)
|
clickhouse::Client::with_http_client(hyper_client)
|
||||||
.with_url(dotenvy::var("CLICKHOUSE_URL").unwrap())
|
.with_url(&ENV.CLICKHOUSE_URL)
|
||||||
.with_user(dotenvy::var("CLICKHOUSE_USER").unwrap())
|
.with_user(&ENV.CLICKHOUSE_USER)
|
||||||
.with_password(dotenvy::var("CLICKHOUSE_PASSWORD").unwrap())
|
.with_password(&ENV.CLICKHOUSE_PASSWORD)
|
||||||
.with_validation(false)
|
.with_validation(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,8 +36,7 @@ pub async fn init_client_with_database(
|
|||||||
.execute()
|
.execute()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let clickhouse_replicated =
|
let clickhouse_replicated = ENV.CLICKHOUSE_REPLICATED;
|
||||||
dotenvy::var("CLICKHOUSE_REPLICATED").unwrap() == "true";
|
|
||||||
let cluster_line = if clickhouse_replicated {
|
let cluster_line = if clickhouse_replicated {
|
||||||
"ON cluster '{cluster}'"
|
"ON cluster '{cluster}'"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ pub type PgTransaction<'c> = sqlx_tracing::Transaction<'c, Postgres>;
|
|||||||
pub use sqlx_tracing::Acquire;
|
pub use sqlx_tracing::Acquire;
|
||||||
pub use sqlx_tracing::Executor;
|
pub use sqlx_tracing::Executor;
|
||||||
|
|
||||||
|
use crate::env::ENV;
|
||||||
|
|
||||||
// pub type PgPool = sqlx::PgPool;
|
// pub type PgPool = sqlx::PgPool;
|
||||||
// pub type PgTransaction<'c> = sqlx::Transaction<'c, Postgres>;
|
// pub type PgTransaction<'c> = sqlx::Transaction<'c, Postgres>;
|
||||||
// pub use sqlx::Acquire;
|
// pub use sqlx::Acquire;
|
||||||
@@ -50,57 +52,27 @@ impl DerefMut for ReadOnlyPgPool {
|
|||||||
|
|
||||||
pub async fn connect_all() -> Result<(PgPool, ReadOnlyPgPool), sqlx::Error> {
|
pub async fn connect_all() -> Result<(PgPool, ReadOnlyPgPool), sqlx::Error> {
|
||||||
info!("Initializing database connection");
|
info!("Initializing database connection");
|
||||||
let database_url =
|
let database_url = &ENV.DATABASE_URL;
|
||||||
dotenvy::var("DATABASE_URL").expect("`DATABASE_URL` not in .env");
|
|
||||||
|
|
||||||
let acquire_timeout =
|
let acquire_timeout =
|
||||||
dotenvy::var("DATABASE_ACQUIRE_TIMEOUT_MS")
|
Duration::from_millis(ENV.DATABASE_ACQUIRE_TIMEOUT_MS);
|
||||||
.ok()
|
|
||||||
.map_or_else(
|
|
||||||
|| Duration::from_millis(30000),
|
|
||||||
|x| {
|
|
||||||
Duration::from_millis(x.parse::<u64>().expect(
|
|
||||||
"DATABASE_ACQUIRE_TIMEOUT_MS must be a valid u64",
|
|
||||||
))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let pool = PgPoolOptions::new()
|
let pool = PgPoolOptions::new()
|
||||||
.acquire_timeout(acquire_timeout)
|
.acquire_timeout(acquire_timeout)
|
||||||
.min_connections(
|
.min_connections(ENV.DATABASE_MIN_CONNECTIONS)
|
||||||
dotenvy::var("DATABASE_MIN_CONNECTIONS")
|
.max_connections(ENV.DATABASE_MAX_CONNECTIONS)
|
||||||
.ok()
|
|
||||||
.and_then(|x| x.parse().ok())
|
|
||||||
.unwrap_or(0),
|
|
||||||
)
|
|
||||||
.max_connections(
|
|
||||||
dotenvy::var("DATABASE_MAX_CONNECTIONS")
|
|
||||||
.ok()
|
|
||||||
.and_then(|x| x.parse().ok())
|
|
||||||
.unwrap_or(16),
|
|
||||||
)
|
|
||||||
.max_lifetime(Some(Duration::from_secs(60 * 60)))
|
.max_lifetime(Some(Duration::from_secs(60 * 60)))
|
||||||
.connect(&database_url)
|
.connect(database_url)
|
||||||
.await?;
|
.await?;
|
||||||
let pool = PgPool::from(pool);
|
let pool = PgPool::from(pool);
|
||||||
|
|
||||||
if let Ok(url) = dotenvy::var("READONLY_DATABASE_URL") {
|
if !ENV.READONLY_DATABASE_URL.is_empty() {
|
||||||
let ro_pool = PgPoolOptions::new()
|
let ro_pool = PgPoolOptions::new()
|
||||||
.acquire_timeout(acquire_timeout)
|
.acquire_timeout(acquire_timeout)
|
||||||
.min_connections(
|
.min_connections(ENV.READONLY_DATABASE_MIN_CONNECTIONS)
|
||||||
dotenvy::var("READONLY_DATABASE_MIN_CONNECTIONS")
|
.max_connections(ENV.READONLY_DATABASE_MAX_CONNECTIONS)
|
||||||
.ok()
|
|
||||||
.and_then(|x| x.parse().ok())
|
|
||||||
.unwrap_or(0),
|
|
||||||
)
|
|
||||||
.max_connections(
|
|
||||||
dotenvy::var("READONLY_DATABASE_MAX_CONNECTIONS")
|
|
||||||
.ok()
|
|
||||||
.and_then(|x| x.parse().ok())
|
|
||||||
.unwrap_or(1),
|
|
||||||
)
|
|
||||||
.max_lifetime(Some(Duration::from_secs(60 * 60)))
|
.max_lifetime(Some(Duration::from_secs(60 * 60)))
|
||||||
.connect(&url)
|
.connect(&ENV.READONLY_DATABASE_URL)
|
||||||
.await?;
|
.await?;
|
||||||
let ro_pool = PgPool::from(ro_pool);
|
let ro_pool = PgPool::from(ro_pool);
|
||||||
|
|
||||||
@@ -112,8 +84,7 @@ pub async fn connect_all() -> Result<(PgPool, ReadOnlyPgPool), sqlx::Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn check_for_migrations() -> eyre::Result<()> {
|
pub async fn check_for_migrations() -> eyre::Result<()> {
|
||||||
let uri =
|
let uri = &ENV.DATABASE_URL;
|
||||||
dotenvy::var("DATABASE_URL").wrap_err("`DATABASE_URL` not in .env")?;
|
|
||||||
let uri = uri.as_str();
|
let uri = uri.as_str();
|
||||||
if !Postgres::database_exists(uri)
|
if !Postgres::database_exists(uri)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use crate::env::ENV;
|
||||||
|
|
||||||
use super::models::DatabaseError;
|
use super::models::DatabaseError;
|
||||||
use ariadne::ids::base62_impl::{parse_base62, to_base62};
|
use ariadne::ids::base62_impl::{parse_base62, to_base62};
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
@@ -42,44 +44,26 @@ impl RedisPool {
|
|||||||
// testing pool uses a hashmap to mimic redis behaviour for very small data sizes (ie: tests)
|
// testing pool uses a hashmap to mimic redis behaviour for very small data sizes (ie: tests)
|
||||||
// PANICS: production pool will panic if redis url is not set
|
// PANICS: production pool will panic if redis url is not set
|
||||||
pub fn new(meta_namespace: impl Into<Arc<str>>) -> Self {
|
pub fn new(meta_namespace: impl Into<Arc<str>>) -> Self {
|
||||||
let wait_timeout =
|
let wait_timeout = Duration::from_millis(ENV.REDIS_WAIT_TIMEOUT_MS);
|
||||||
dotenvy::var("REDIS_WAIT_TIMEOUT_MS").ok().map_or_else(
|
|
||||||
|| Duration::from_millis(15000),
|
|
||||||
|x| {
|
|
||||||
Duration::from_millis(
|
|
||||||
x.parse::<u64>().expect(
|
|
||||||
"REDIS_WAIT_TIMEOUT_MS must be a valid u64",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let url = dotenvy::var("REDIS_URL").expect("Redis URL not set");
|
let url = &ENV.REDIS_URL;
|
||||||
let pool = Config::from_url(url.clone())
|
let pool = Config::from_url(url.clone())
|
||||||
.builder()
|
.builder()
|
||||||
.expect("Error building Redis pool")
|
.expect("Error building Redis pool")
|
||||||
.max_size(
|
.max_size(ENV.REDIS_MAX_CONNECTIONS as usize)
|
||||||
dotenvy::var("REDIS_MAX_CONNECTIONS")
|
|
||||||
.ok()
|
|
||||||
.and_then(|x| x.parse().ok())
|
|
||||||
.unwrap_or(10000),
|
|
||||||
)
|
|
||||||
.wait_timeout(Some(wait_timeout))
|
.wait_timeout(Some(wait_timeout))
|
||||||
.runtime(Runtime::Tokio1)
|
.runtime(Runtime::Tokio1)
|
||||||
.build()
|
.build()
|
||||||
.expect("Redis connection failed");
|
.expect("Redis connection failed");
|
||||||
|
|
||||||
let pool = RedisPool {
|
let pool = RedisPool {
|
||||||
url,
|
url: url.clone(),
|
||||||
pool,
|
pool,
|
||||||
cache_list: Arc::new(DashMap::with_capacity(2048)),
|
cache_list: Arc::new(DashMap::with_capacity(2048)),
|
||||||
meta_namespace: meta_namespace.into(),
|
meta_namespace: meta_namespace.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let redis_min_connections = dotenvy::var("REDIS_MIN_CONNECTIONS")
|
let redis_min_connections = ENV.REDIS_MIN_CONNECTIONS;
|
||||||
.ok()
|
|
||||||
.and_then(|x| x.parse::<usize>().ok())
|
|
||||||
.unwrap_or(0);
|
|
||||||
let spawn_min_connections = (0..redis_min_connections)
|
let spawn_min_connections = (0..redis_min_connections)
|
||||||
.map(|_| {
|
.map(|_| {
|
||||||
let pool = pool.clone();
|
let pool = pool.clone();
|
||||||
|
|||||||
280
apps/labrinth/src/env.rs
Normal file
280
apps/labrinth/src/env.rs
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
use std::{any::type_name, convert::Infallible, str::FromStr, sync::LazyLock};
|
||||||
|
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
|
use eyre::{Context, eyre};
|
||||||
|
use rust_decimal::Decimal;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
|
macro_rules! vars {
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
$field:ident: $ty:ty $(= $default:expr)?;
|
||||||
|
)*
|
||||||
|
) => {
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[allow(
|
||||||
|
non_snake_case,
|
||||||
|
reason = "environment variables are UPPER_SNAKE_CASE",
|
||||||
|
)]
|
||||||
|
pub struct EnvVars {
|
||||||
|
$(
|
||||||
|
pub $field: $ty,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnvVars {
|
||||||
|
pub fn from_env() -> eyre::Result<Self> {
|
||||||
|
let mut err = eyre!("failed to read environment variables");
|
||||||
|
|
||||||
|
$(
|
||||||
|
#[expect(
|
||||||
|
non_snake_case,
|
||||||
|
reason = "environment variables are UPPER_SNAKE_CASE",
|
||||||
|
)]
|
||||||
|
#[allow(
|
||||||
|
unused_assignments,
|
||||||
|
unused_mut,
|
||||||
|
reason = "`default` is not used if there is no default",
|
||||||
|
)]
|
||||||
|
let $field: Option<$ty> = {
|
||||||
|
let mut default = None::<$ty>;
|
||||||
|
$( default = Some({ $default }.into()); )?
|
||||||
|
|
||||||
|
match parse_value::<$ty>(stringify!($field), default) {
|
||||||
|
Ok(value) => Some(value),
|
||||||
|
Err(source) => {
|
||||||
|
err = err.wrap_err(eyre!("{source:#}"));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
)*
|
||||||
|
|
||||||
|
Ok(EnvVars {
|
||||||
|
$(
|
||||||
|
$field: match $field {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return Err(err),
|
||||||
|
},
|
||||||
|
)*
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static ENV: LazyLock<EnvVars> = LazyLock::new(|| {
|
||||||
|
EnvVars::from_env().unwrap_or_else(|err| panic!("{err:?}"))
|
||||||
|
});
|
||||||
|
|
||||||
|
fn parse_value<T>(key: &str, default: Option<T>) -> eyre::Result<T>
|
||||||
|
where
|
||||||
|
T: FromStr,
|
||||||
|
T::Err: std::error::Error + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
match (dotenvy::var(key), default) {
|
||||||
|
(Ok(value), _) => value.parse::<T>().wrap_err_with(|| {
|
||||||
|
eyre!("`{key}` is not a valid `{}`", type_name::<T>())
|
||||||
|
}),
|
||||||
|
(Err(_), Some(default)) => Ok(default),
|
||||||
|
(Err(_), None) => Err(eyre!("`{key}` missing")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init() -> eyre::Result<()> {
|
||||||
|
EnvVars::from_env()?;
|
||||||
|
LazyLock::force(&ENV);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Deref, DerefMut,
|
||||||
|
)]
|
||||||
|
pub struct Json<T: DeserializeOwned>(pub T);
|
||||||
|
|
||||||
|
impl<T: DeserializeOwned> FromStr for Json<T> {
|
||||||
|
type Err = serde_json::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
serde_json::from_str(s).map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Deref, DerefMut,
|
||||||
|
)]
|
||||||
|
pub struct StringCsv(pub Vec<String>);
|
||||||
|
|
||||||
|
impl FromStr for StringCsv {
|
||||||
|
type Err = Infallible;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let v = s
|
||||||
|
.split(',')
|
||||||
|
.filter(|s| !s.trim().is_empty())
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
Ok(Self(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vars! {
|
||||||
|
SENTRY_ENVIRONMENT: String;
|
||||||
|
SENTRY_TRACES_SAMPLE_RATE: f32;
|
||||||
|
SITE_URL: String;
|
||||||
|
CDN_URL: String;
|
||||||
|
LABRINTH_ADMIN_KEY: String;
|
||||||
|
LABRINTH_MEDAL_KEY: String;
|
||||||
|
LABRINTH_EXTERNAL_NOTIFICATION_KEY: String;
|
||||||
|
RATE_LIMIT_IGNORE_KEY: String;
|
||||||
|
DATABASE_URL: String;
|
||||||
|
MEILISEARCH_READ_ADDR: String;
|
||||||
|
MEILISEARCH_WRITE_ADDRS: StringCsv;
|
||||||
|
MEILISEARCH_KEY: String;
|
||||||
|
REDIS_URL: String;
|
||||||
|
BIND_ADDR: String;
|
||||||
|
SELF_ADDR: String;
|
||||||
|
|
||||||
|
LOCAL_INDEX_INTERVAL: u64;
|
||||||
|
VERSION_INDEX_INTERVAL: u64;
|
||||||
|
|
||||||
|
WHITELISTED_MODPACK_DOMAINS: Json<Vec<String>>;
|
||||||
|
ALLOWED_CALLBACK_URLS: Json<Vec<String>>;
|
||||||
|
ANALYTICS_ALLOWED_ORIGINS: Json<Vec<String>>;
|
||||||
|
|
||||||
|
// storage
|
||||||
|
STORAGE_BACKEND: crate::file_hosting::FileHostKind;
|
||||||
|
|
||||||
|
// s3
|
||||||
|
S3_PUBLIC_BUCKET_NAME: String = "";
|
||||||
|
S3_PUBLIC_USES_PATH_STYLE_BUCKET: bool = false;
|
||||||
|
S3_PUBLIC_REGION: String = "";
|
||||||
|
S3_PUBLIC_URL: String = "";
|
||||||
|
S3_PUBLIC_ACCESS_TOKEN: String = "";
|
||||||
|
S3_PUBLIC_SECRET: String = "";
|
||||||
|
|
||||||
|
S3_PRIVATE_BUCKET_NAME: String = "";
|
||||||
|
S3_PRIVATE_USES_PATH_STYLE_BUCKET: bool = false;
|
||||||
|
S3_PRIVATE_REGION: String = "";
|
||||||
|
S3_PRIVATE_URL: String = "";
|
||||||
|
S3_PRIVATE_ACCESS_TOKEN: String = "";
|
||||||
|
S3_PRIVATE_SECRET: String = "";
|
||||||
|
|
||||||
|
// local
|
||||||
|
MOCK_FILE_PATH: String = "";
|
||||||
|
|
||||||
|
GITHUB_CLIENT_ID: String;
|
||||||
|
GITHUB_CLIENT_SECRET: String;
|
||||||
|
GITLAB_CLIENT_ID: String;
|
||||||
|
GITLAB_CLIENT_SECRET: String;
|
||||||
|
DISCORD_CLIENT_ID: String;
|
||||||
|
DISCORD_CLIENT_SECRET: String;
|
||||||
|
MICROSOFT_CLIENT_ID: String;
|
||||||
|
MICROSOFT_CLIENT_SECRET: String;
|
||||||
|
GOOGLE_CLIENT_ID: String;
|
||||||
|
GOOGLE_CLIENT_SECRET: String;
|
||||||
|
STEAM_API_KEY: String;
|
||||||
|
|
||||||
|
TREMENDOUS_API_URL: String;
|
||||||
|
TREMENDOUS_API_KEY: String;
|
||||||
|
TREMENDOUS_PRIVATE_KEY: String;
|
||||||
|
|
||||||
|
PAYPAL_API_URL: String;
|
||||||
|
PAYPAL_WEBHOOK_ID: String;
|
||||||
|
PAYPAL_CLIENT_ID: String;
|
||||||
|
PAYPAL_CLIENT_SECRET: String;
|
||||||
|
PAYPAL_NVP_USERNAME: String;
|
||||||
|
PAYPAL_NVP_PASSWORD: String;
|
||||||
|
PAYPAL_NVP_SIGNATURE: String;
|
||||||
|
|
||||||
|
PAYPAL_BALANCE_ALERT_THRESHOLD: u64 = 0u64;
|
||||||
|
BREX_BALANCE_ALERT_THRESHOLD: u64 = 0u64;
|
||||||
|
TREMENDOUS_BALANCE_ALERT_THRESHOLD: u64 = 0u64;
|
||||||
|
MURAL_BALANCE_ALERT_THRESHOLD: u64 = 0u64;
|
||||||
|
|
||||||
|
HCAPTCHA_SECRET: String;
|
||||||
|
|
||||||
|
SMTP_USERNAME: String;
|
||||||
|
SMTP_PASSWORD: String;
|
||||||
|
SMTP_HOST: String;
|
||||||
|
SMTP_PORT: u16;
|
||||||
|
SMTP_TLS: String;
|
||||||
|
SMTP_FROM_NAME: String;
|
||||||
|
SMTP_FROM_ADDRESS: String;
|
||||||
|
|
||||||
|
SITE_VERIFY_EMAIL_PATH: String;
|
||||||
|
SITE_RESET_PASSWORD_PATH: String;
|
||||||
|
SITE_BILLING_PATH: String;
|
||||||
|
|
||||||
|
SENDY_URL: String;
|
||||||
|
SENDY_LIST_ID: String;
|
||||||
|
SENDY_API_KEY: String;
|
||||||
|
|
||||||
|
CLICKHOUSE_REPLICATED: bool;
|
||||||
|
CLICKHOUSE_URL: String;
|
||||||
|
CLICKHOUSE_USER: String;
|
||||||
|
CLICKHOUSE_PASSWORD: String;
|
||||||
|
CLICKHOUSE_DATABASE: String;
|
||||||
|
|
||||||
|
FLAME_ANVIL_URL: String;
|
||||||
|
|
||||||
|
GOTENBERG_URL: String;
|
||||||
|
GOTENBERG_CALLBACK_BASE: String;
|
||||||
|
GOTENBERG_TIMEOUT: u64;
|
||||||
|
|
||||||
|
STRIPE_API_KEY: String;
|
||||||
|
STRIPE_WEBHOOK_SECRET: String;
|
||||||
|
|
||||||
|
ADITUDE_API_KEY: String;
|
||||||
|
|
||||||
|
PYRO_API_KEY: String;
|
||||||
|
|
||||||
|
BREX_API_URL: String;
|
||||||
|
BREX_API_KEY: String;
|
||||||
|
|
||||||
|
DELPHI_URL: String;
|
||||||
|
|
||||||
|
AVALARA_1099_API_URL: String;
|
||||||
|
AVALARA_1099_API_KEY: String;
|
||||||
|
AVALARA_1099_API_TEAM_ID: String;
|
||||||
|
AVALARA_1099_COMPANY_ID: String;
|
||||||
|
|
||||||
|
ANROK_API_URL: String;
|
||||||
|
ANROK_API_KEY: String;
|
||||||
|
|
||||||
|
COMPLIANCE_PAYOUT_THRESHOLD: String;
|
||||||
|
|
||||||
|
PAYOUT_ALERT_SLACK_WEBHOOK: String;
|
||||||
|
CLOUDFLARE_INTEGRATION: bool = false;
|
||||||
|
|
||||||
|
ARCHON_URL: String;
|
||||||
|
|
||||||
|
MURALPAY_API_URL: String;
|
||||||
|
MURALPAY_API_KEY: String;
|
||||||
|
MURALPAY_TRANSFER_API_KEY: String;
|
||||||
|
MURALPAY_SOURCE_ACCOUNT_ID: muralpay::AccountId = muralpay::AccountId(uuid::Uuid::nil());
|
||||||
|
|
||||||
|
DEFAULT_AFFILIATE_REVENUE_SPLIT: Decimal;
|
||||||
|
|
||||||
|
DATABASE_ACQUIRE_TIMEOUT_MS: u64 = 30000u64;
|
||||||
|
DATABASE_MIN_CONNECTIONS: u32 = 0u32;
|
||||||
|
DATABASE_MAX_CONNECTIONS: u32 = 16u32;
|
||||||
|
READONLY_DATABASE_URL: String = "";
|
||||||
|
READONLY_DATABASE_MIN_CONNECTIONS: u32 = 0u32;
|
||||||
|
READONLY_DATABASE_MAX_CONNECTIONS: u32 = 1u32;
|
||||||
|
|
||||||
|
REDIS_WAIT_TIMEOUT_MS: u64 = 15000u64;
|
||||||
|
REDIS_MAX_CONNECTIONS: u32 = 10000u32;
|
||||||
|
REDIS_MIN_CONNECTIONS: usize = 0usize;
|
||||||
|
|
||||||
|
SEARCH_OPERATION_TIMEOUT: u64 = 300000u64;
|
||||||
|
|
||||||
|
SMTP_REPLY_TO_NAME: String = "";
|
||||||
|
SMTP_REPLY_TO_ADDRESS: String = "";
|
||||||
|
|
||||||
|
PUBLIC_DISCORD_WEBHOOK: String = "";
|
||||||
|
MODERATION_SLACK_WEBHOOK: String = "";
|
||||||
|
DELPHI_SLACK_WEBHOOK: String = "";
|
||||||
|
|
||||||
|
TREMENDOUS_CAMPAIGN_ID: String = "";
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ use hex::ToHex;
|
|||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::env::ENV;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct MockHost(());
|
pub struct MockHost(());
|
||||||
|
|
||||||
@@ -54,8 +56,7 @@ impl FileHost for MockHost {
|
|||||||
file_name: &str,
|
file_name: &str,
|
||||||
_expiry_secs: u32,
|
_expiry_secs: u32,
|
||||||
) -> Result<String, FileHostingError> {
|
) -> Result<String, FileHostingError> {
|
||||||
let cdn_url = dotenvy::var("CDN_URL").unwrap();
|
Ok(format!("{}/private/{file_name}", ENV.CDN_URL))
|
||||||
Ok(format!("{cdn_url}/private/{file_name}"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_file(
|
async fn delete_file(
|
||||||
@@ -77,7 +78,7 @@ fn get_file_path(
|
|||||||
file_name: &str,
|
file_name: &str,
|
||||||
file_publicity: FileHostPublicity,
|
file_publicity: FileHostPublicity,
|
||||||
) -> PathBuf {
|
) -> PathBuf {
|
||||||
let mut path = PathBuf::from(dotenvy::var("MOCK_FILE_PATH").unwrap());
|
let mut path = PathBuf::from(ENV.MOCK_FILE_PATH.clone());
|
||||||
|
|
||||||
if matches!(file_publicity, FileHostPublicity::Private) {
|
if matches!(file_publicity, FileHostPublicity::Private) {
|
||||||
path.push("private");
|
path.push("private");
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
@@ -63,3 +65,25 @@ pub trait FileHost {
|
|||||||
file_publicity: FileHostPublicity,
|
file_publicity: FileHostPublicity,
|
||||||
) -> Result<DeleteFileData, FileHostingError>;
|
) -> Result<DeleteFileData, FileHostingError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum FileHostKind {
|
||||||
|
S3,
|
||||||
|
Local,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error("invalid file host kind")]
|
||||||
|
pub struct InvalidFileHostKind;
|
||||||
|
|
||||||
|
impl FromStr for FileHostKind {
|
||||||
|
type Err = InvalidFileHostKind;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
"s3" => Self::S3,
|
||||||
|
"local" => Self::Local,
|
||||||
|
_ => return Err(InvalidFileHostKind),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ use util::gotenberg::GotenbergClient;
|
|||||||
|
|
||||||
use crate::background_task::update_versions;
|
use crate::background_task::update_versions;
|
||||||
use crate::database::{PgPool, ReadOnlyPgPool};
|
use crate::database::{PgPool, ReadOnlyPgPool};
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::queue::billing::{index_billing, index_subscriptions};
|
use crate::queue::billing::{index_billing, index_subscriptions};
|
||||||
use crate::queue::moderation::AutomatedModerationQueue;
|
use crate::queue::moderation::AutomatedModerationQueue;
|
||||||
use crate::util::anrok;
|
use crate::util::anrok;
|
||||||
use crate::util::archon::ArchonClient;
|
use crate::util::archon::ArchonClient;
|
||||||
use crate::util::env::{parse_strings_from_var, parse_var};
|
|
||||||
use crate::util::ratelimit::{AsyncRateLimiter, GCRAParameters};
|
use crate::util::ratelimit::{AsyncRateLimiter, GCRAParameters};
|
||||||
use sync::friends::handle_pubsub;
|
use sync::friends::handle_pubsub;
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ pub mod auth;
|
|||||||
pub mod background_task;
|
pub mod background_task;
|
||||||
pub mod clickhouse;
|
pub mod clickhouse;
|
||||||
pub mod database;
|
pub mod database;
|
||||||
|
pub mod env;
|
||||||
pub mod file_hosting;
|
pub mod file_hosting;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod queue;
|
pub mod queue;
|
||||||
@@ -83,10 +84,7 @@ pub fn app_setup(
|
|||||||
gotenberg_client: GotenbergClient,
|
gotenberg_client: GotenbergClient,
|
||||||
enable_background_tasks: bool,
|
enable_background_tasks: bool,
|
||||||
) -> LabrinthConfig {
|
) -> LabrinthConfig {
|
||||||
info!(
|
info!("Starting labrinth on {}", &ENV.BIND_ADDR);
|
||||||
"Starting labrinth on {}",
|
|
||||||
dotenvy::var("BIND_ADDR").unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
let automated_moderation_queue =
|
let automated_moderation_queue =
|
||||||
web::Data::new(AutomatedModerationQueue::default());
|
web::Data::new(AutomatedModerationQueue::default());
|
||||||
@@ -112,9 +110,8 @@ pub fn app_setup(
|
|||||||
if enable_background_tasks {
|
if enable_background_tasks {
|
||||||
// The interval in seconds at which the local database is indexed
|
// The interval in seconds at which the local database is indexed
|
||||||
// for searching. Defaults to 1 hour if unset.
|
// for searching. Defaults to 1 hour if unset.
|
||||||
let local_index_interval = Duration::from_secs(
|
let local_index_interval =
|
||||||
parse_var("LOCAL_INDEX_INTERVAL").unwrap_or(3600),
|
Duration::from_secs(ENV.LOCAL_INDEX_INTERVAL);
|
||||||
);
|
|
||||||
let pool_ref = pool.clone();
|
let pool_ref = pool.clone();
|
||||||
let search_config_ref = search_config.clone();
|
let search_config_ref = search_config.clone();
|
||||||
let redis_pool_ref = redis_pool.clone();
|
let redis_pool_ref = redis_pool.clone();
|
||||||
@@ -142,9 +139,8 @@ pub fn app_setup(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let version_index_interval = Duration::from_secs(
|
let version_index_interval =
|
||||||
parse_var("VERSION_INDEX_INTERVAL").unwrap_or(1800),
|
Duration::from_secs(ENV.VERSION_INDEX_INTERVAL);
|
||||||
);
|
|
||||||
let pool_ref = pool.clone();
|
let pool_ref = pool.clone();
|
||||||
let redis_pool_ref = redis_pool.clone();
|
let redis_pool_ref = redis_pool.clone();
|
||||||
scheduler.run(version_index_interval, move || {
|
scheduler.run(version_index_interval, move || {
|
||||||
@@ -349,188 +345,3 @@ pub fn utoipa_app_config(
|
|||||||
.configure(routes::v3::utoipa_config)
|
.configure(routes::v3::utoipa_config)
|
||||||
.configure(routes::internal::utoipa_config);
|
.configure(routes::internal::utoipa_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is so that env vars not used immediately don't panic at runtime
|
|
||||||
pub fn check_env_vars() -> bool {
|
|
||||||
let mut failed = false;
|
|
||||||
|
|
||||||
fn check_var<T: std::str::FromStr>(var: &str) -> bool {
|
|
||||||
let check = parse_var::<T>(var).is_none();
|
|
||||||
if check {
|
|
||||||
warn!(
|
|
||||||
"Variable `{}` missing in dotenv or not of type `{}`",
|
|
||||||
var,
|
|
||||||
std::any::type_name::<T>()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
check
|
|
||||||
}
|
|
||||||
|
|
||||||
failed |= check_var::<String>("SENTRY_ENVIRONMENT");
|
|
||||||
failed |= check_var::<String>("SENTRY_TRACES_SAMPLE_RATE");
|
|
||||||
failed |= check_var::<String>("SITE_URL");
|
|
||||||
failed |= check_var::<String>("CDN_URL");
|
|
||||||
failed |= check_var::<String>("LABRINTH_ADMIN_KEY");
|
|
||||||
failed |= check_var::<String>("LABRINTH_EXTERNAL_NOTIFICATION_KEY");
|
|
||||||
failed |= check_var::<String>("RATE_LIMIT_IGNORE_KEY");
|
|
||||||
failed |= check_var::<String>("DATABASE_URL");
|
|
||||||
failed |= check_var::<String>("MEILISEARCH_READ_ADDR");
|
|
||||||
failed |= check_var::<String>("MEILISEARCH_WRITE_ADDRS");
|
|
||||||
failed |= check_var::<String>("MEILISEARCH_KEY");
|
|
||||||
failed |= check_var::<String>("REDIS_URL");
|
|
||||||
failed |= check_var::<String>("BIND_ADDR");
|
|
||||||
failed |= check_var::<String>("SELF_ADDR");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("STORAGE_BACKEND");
|
|
||||||
|
|
||||||
let storage_backend = dotenvy::var("STORAGE_BACKEND").ok();
|
|
||||||
match storage_backend.as_deref() {
|
|
||||||
Some("s3") => {
|
|
||||||
let mut check_var_set = |var_prefix| {
|
|
||||||
failed |= check_var::<String>(&format!(
|
|
||||||
"S3_{var_prefix}_BUCKET_NAME"
|
|
||||||
));
|
|
||||||
failed |= check_var::<bool>(&format!(
|
|
||||||
"S3_{var_prefix}_USES_PATH_STYLE_BUCKET"
|
|
||||||
));
|
|
||||||
failed |=
|
|
||||||
check_var::<String>(&format!("S3_{var_prefix}_REGION"));
|
|
||||||
failed |= check_var::<String>(&format!("S3_{var_prefix}_URL"));
|
|
||||||
failed |= check_var::<String>(&format!(
|
|
||||||
"S3_{var_prefix}_ACCESS_TOKEN"
|
|
||||||
));
|
|
||||||
failed |=
|
|
||||||
check_var::<String>(&format!("S3_{var_prefix}_SECRET"));
|
|
||||||
};
|
|
||||||
|
|
||||||
check_var_set("PUBLIC");
|
|
||||||
check_var_set("PRIVATE");
|
|
||||||
}
|
|
||||||
Some("local") => {
|
|
||||||
failed |= check_var::<String>("MOCK_FILE_PATH");
|
|
||||||
}
|
|
||||||
Some(backend) => {
|
|
||||||
warn!(
|
|
||||||
"Variable `STORAGE_BACKEND` contains an invalid value: {backend}. Expected \"s3\" or \"local\"."
|
|
||||||
);
|
|
||||||
failed |= true;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
warn!("Variable `STORAGE_BACKEND` is not set!");
|
|
||||||
failed |= true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
failed |= check_var::<usize>("LOCAL_INDEX_INTERVAL");
|
|
||||||
failed |= check_var::<usize>("VERSION_INDEX_INTERVAL");
|
|
||||||
|
|
||||||
if parse_strings_from_var("WHITELISTED_MODPACK_DOMAINS").is_none() {
|
|
||||||
warn!(
|
|
||||||
"Variable `WHITELISTED_MODPACK_DOMAINS` missing in dotenv or not a json array of strings"
|
|
||||||
);
|
|
||||||
failed |= true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if parse_strings_from_var("ALLOWED_CALLBACK_URLS").is_none() {
|
|
||||||
warn!(
|
|
||||||
"Variable `ALLOWED_CALLBACK_URLS` missing in dotenv or not a json array of strings"
|
|
||||||
);
|
|
||||||
failed |= true;
|
|
||||||
}
|
|
||||||
|
|
||||||
failed |= check_var::<String>("GITHUB_CLIENT_ID");
|
|
||||||
failed |= check_var::<String>("GITHUB_CLIENT_SECRET");
|
|
||||||
failed |= check_var::<String>("GITLAB_CLIENT_ID");
|
|
||||||
failed |= check_var::<String>("GITLAB_CLIENT_SECRET");
|
|
||||||
failed |= check_var::<String>("DISCORD_CLIENT_ID");
|
|
||||||
failed |= check_var::<String>("DISCORD_CLIENT_SECRET");
|
|
||||||
failed |= check_var::<String>("MICROSOFT_CLIENT_ID");
|
|
||||||
failed |= check_var::<String>("MICROSOFT_CLIENT_SECRET");
|
|
||||||
failed |= check_var::<String>("GOOGLE_CLIENT_ID");
|
|
||||||
failed |= check_var::<String>("GOOGLE_CLIENT_SECRET");
|
|
||||||
failed |= check_var::<String>("STEAM_API_KEY");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("TREMENDOUS_API_URL");
|
|
||||||
failed |= check_var::<String>("TREMENDOUS_API_KEY");
|
|
||||||
failed |= check_var::<String>("TREMENDOUS_PRIVATE_KEY");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("PAYPAL_API_URL");
|
|
||||||
failed |= check_var::<String>("PAYPAL_WEBHOOK_ID");
|
|
||||||
failed |= check_var::<String>("PAYPAL_CLIENT_ID");
|
|
||||||
failed |= check_var::<String>("PAYPAL_CLIENT_SECRET");
|
|
||||||
failed |= check_var::<String>("PAYPAL_NVP_USERNAME");
|
|
||||||
failed |= check_var::<String>("PAYPAL_NVP_PASSWORD");
|
|
||||||
failed |= check_var::<String>("PAYPAL_NVP_SIGNATURE");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("HCAPTCHA_SECRET");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("SMTP_USERNAME");
|
|
||||||
failed |= check_var::<String>("SMTP_PASSWORD");
|
|
||||||
failed |= check_var::<String>("SMTP_HOST");
|
|
||||||
failed |= check_var::<u16>("SMTP_PORT");
|
|
||||||
failed |= check_var::<String>("SMTP_TLS");
|
|
||||||
failed |= check_var::<String>("SMTP_FROM_NAME");
|
|
||||||
failed |= check_var::<String>("SMTP_FROM_ADDRESS");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("SITE_VERIFY_EMAIL_PATH");
|
|
||||||
failed |= check_var::<String>("SITE_RESET_PASSWORD_PATH");
|
|
||||||
failed |= check_var::<String>("SITE_BILLING_PATH");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("SENDY_URL");
|
|
||||||
failed |= check_var::<String>("SENDY_LIST_ID");
|
|
||||||
failed |= check_var::<String>("SENDY_API_KEY");
|
|
||||||
|
|
||||||
if parse_strings_from_var("ANALYTICS_ALLOWED_ORIGINS").is_none() {
|
|
||||||
warn!(
|
|
||||||
"Variable `ANALYTICS_ALLOWED_ORIGINS` missing in dotenv or not a json array of strings"
|
|
||||||
);
|
|
||||||
failed |= true;
|
|
||||||
}
|
|
||||||
|
|
||||||
failed |= check_var::<bool>("CLICKHOUSE_REPLICATED");
|
|
||||||
failed |= check_var::<String>("CLICKHOUSE_URL");
|
|
||||||
failed |= check_var::<String>("CLICKHOUSE_USER");
|
|
||||||
failed |= check_var::<String>("CLICKHOUSE_PASSWORD");
|
|
||||||
failed |= check_var::<String>("CLICKHOUSE_DATABASE");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("FLAME_ANVIL_URL");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("GOTENBERG_URL");
|
|
||||||
failed |= check_var::<String>("GOTENBERG_CALLBACK_BASE");
|
|
||||||
failed |= check_var::<String>("GOTENBERG_TIMEOUT");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("STRIPE_API_KEY");
|
|
||||||
failed |= check_var::<String>("STRIPE_WEBHOOK_SECRET");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("ADITUDE_API_KEY");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("PYRO_API_KEY");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("BREX_API_URL");
|
|
||||||
failed |= check_var::<String>("BREX_API_KEY");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("DELPHI_URL");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("AVALARA_1099_API_URL");
|
|
||||||
failed |= check_var::<String>("AVALARA_1099_API_KEY");
|
|
||||||
failed |= check_var::<String>("AVALARA_1099_API_TEAM_ID");
|
|
||||||
failed |= check_var::<String>("AVALARA_1099_COMPANY_ID");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("ANROK_API_URL");
|
|
||||||
failed |= check_var::<String>("ANROK_API_KEY");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("COMPLIANCE_PAYOUT_THRESHOLD");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("PAYOUT_ALERT_SLACK_WEBHOOK");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("ARCHON_URL");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("MURALPAY_API_URL");
|
|
||||||
failed |= check_var::<String>("MURALPAY_API_KEY");
|
|
||||||
failed |= check_var::<String>("MURALPAY_TRANSFER_API_KEY");
|
|
||||||
failed |= check_var::<String>("MURALPAY_SOURCE_ACCOUNT_ID");
|
|
||||||
|
|
||||||
failed |= check_var::<String>("DEFAULT_AFFILIATE_REVENUE_SPLIT");
|
|
||||||
|
|
||||||
failed
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,21 +4,21 @@ use actix_web::{App, HttpServer};
|
|||||||
use actix_web_prom::PrometheusMetricsBuilder;
|
use actix_web_prom::PrometheusMetricsBuilder;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use labrinth::app_config;
|
|
||||||
use labrinth::background_task::BackgroundTask;
|
use labrinth::background_task::BackgroundTask;
|
||||||
use labrinth::database::redis::RedisPool;
|
use labrinth::database::redis::RedisPool;
|
||||||
use labrinth::file_hosting::{S3BucketConfig, S3Host};
|
use labrinth::env::ENV;
|
||||||
|
use labrinth::file_hosting::{FileHostKind, S3BucketConfig, S3Host};
|
||||||
use labrinth::queue::email::EmailQueue;
|
use labrinth::queue::email::EmailQueue;
|
||||||
use labrinth::search;
|
use labrinth::search;
|
||||||
use labrinth::util::anrok;
|
use labrinth::util::anrok;
|
||||||
use labrinth::util::env::parse_var;
|
|
||||||
use labrinth::util::gotenberg::GotenbergClient;
|
use labrinth::util::gotenberg::GotenbergClient;
|
||||||
use labrinth::util::ratelimit::rate_limit_middleware;
|
use labrinth::util::ratelimit::rate_limit_middleware;
|
||||||
use labrinth::utoipa_app_config;
|
use labrinth::utoipa_app_config;
|
||||||
use labrinth::{check_env_vars, clickhouse, database, file_hosting};
|
use labrinth::{app_config, env};
|
||||||
|
use labrinth::{clickhouse, database, file_hosting};
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing::{Instrument, error, info, info_span};
|
use tracing::{Instrument, info, info_span};
|
||||||
use tracing_actix_web::TracingLogger;
|
use tracing_actix_web::TracingLogger;
|
||||||
use utoipa::OpenApi;
|
use utoipa::OpenApi;
|
||||||
use utoipa::openapi::security::{ApiKey, ApiKeyValue, SecurityScheme};
|
use utoipa::openapi::security::{ApiKey, ApiKeyValue, SecurityScheme};
|
||||||
@@ -58,11 +58,7 @@ fn main() -> std::io::Result<()> {
|
|||||||
color_eyre::install().expect("failed to install `color-eyre`");
|
color_eyre::install().expect("failed to install `color-eyre`");
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
modrinth_util::log::init().expect("failed to initialize logging");
|
modrinth_util::log::init().expect("failed to initialize logging");
|
||||||
|
env::init().expect("failed to initialize environment variables");
|
||||||
if check_env_vars() {
|
|
||||||
error!("Some environment variables are missing!");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sentry must be set up before the async runtime is started
|
// Sentry must be set up before the async runtime is started
|
||||||
// <https://docs.sentry.io/platforms/rust/guides/actix-web/>
|
// <https://docs.sentry.io/platforms/rust/guides/actix-web/>
|
||||||
@@ -70,11 +66,8 @@ fn main() -> std::io::Result<()> {
|
|||||||
// Has no effect if not set.
|
// Has no effect if not set.
|
||||||
let sentry = sentry::init(sentry::ClientOptions {
|
let sentry = sentry::init(sentry::ClientOptions {
|
||||||
release: sentry::release_name!(),
|
release: sentry::release_name!(),
|
||||||
traces_sample_rate: dotenvy::var("SENTRY_TRACES_SAMPLE_RATE")
|
traces_sample_rate: ENV.SENTRY_TRACES_SAMPLE_RATE,
|
||||||
.unwrap()
|
environment: Some((&ENV.SENTRY_ENVIRONMENT).into()),
|
||||||
.parse()
|
|
||||||
.expect("failed to parse `SENTRY_TRACES_SAMPLE_RATE` as number"),
|
|
||||||
environment: Some(dotenvy::var("SENTRY_ENVIRONMENT").unwrap().into()),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
if sentry.is_enabled() {
|
if sentry.is_enabled() {
|
||||||
@@ -99,10 +92,7 @@ async fn app() -> std::io::Result<()> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if args.run_background_task.is_none() {
|
if args.run_background_task.is_none() {
|
||||||
info!(
|
info!("Starting labrinth on {}", &ENV.BIND_ADDR);
|
||||||
"Starting labrinth on {}",
|
|
||||||
dotenvy::var("BIND_ADDR").unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
if !args.no_migrations {
|
if !args.no_migrations {
|
||||||
database::check_for_migrations()
|
database::check_for_migrations()
|
||||||
@@ -119,40 +109,44 @@ async fn app() -> std::io::Result<()> {
|
|||||||
// Redis connector
|
// Redis connector
|
||||||
let redis_pool = RedisPool::new("");
|
let redis_pool = RedisPool::new("");
|
||||||
|
|
||||||
let storage_backend =
|
let storage_backend = ENV.STORAGE_BACKEND;
|
||||||
dotenvy::var("STORAGE_BACKEND").unwrap_or_else(|_| "local".to_string());
|
|
||||||
|
|
||||||
let file_host: Arc<dyn file_hosting::FileHost + Send + Sync> =
|
let file_host: Arc<dyn file_hosting::FileHost + Send + Sync> =
|
||||||
match storage_backend.as_str() {
|
match storage_backend {
|
||||||
"s3" => {
|
FileHostKind::S3 => {
|
||||||
let config_from_env = |bucket_type| S3BucketConfig {
|
let not_empty = |v: &str| -> String {
|
||||||
name: parse_var(&format!("S3_{bucket_type}_BUCKET_NAME"))
|
assert!(!v.is_empty(), "S3 env var is empty");
|
||||||
.unwrap(),
|
v.to_string()
|
||||||
uses_path_style: parse_var(&format!(
|
|
||||||
"S3_{bucket_type}_USES_PATH_STYLE_BUCKET"
|
|
||||||
))
|
|
||||||
.unwrap(),
|
|
||||||
region: parse_var(&format!("S3_{bucket_type}_REGION"))
|
|
||||||
.unwrap(),
|
|
||||||
url: parse_var(&format!("S3_{bucket_type}_URL")).unwrap(),
|
|
||||||
access_token: parse_var(&format!(
|
|
||||||
"S3_{bucket_type}_ACCESS_TOKEN"
|
|
||||||
))
|
|
||||||
.unwrap(),
|
|
||||||
secret: parse_var(&format!("S3_{bucket_type}_SECRET"))
|
|
||||||
.unwrap(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Arc::new(
|
Arc::new(
|
||||||
S3Host::new(
|
S3Host::new(
|
||||||
config_from_env("PUBLIC"),
|
S3BucketConfig {
|
||||||
config_from_env("PRIVATE"),
|
name: not_empty(&ENV.S3_PUBLIC_BUCKET_NAME),
|
||||||
|
uses_path_style: ENV
|
||||||
|
.S3_PUBLIC_USES_PATH_STYLE_BUCKET,
|
||||||
|
region: not_empty(&ENV.S3_PUBLIC_REGION),
|
||||||
|
url: not_empty(&ENV.S3_PUBLIC_URL),
|
||||||
|
access_token: not_empty(
|
||||||
|
&ENV.S3_PUBLIC_ACCESS_TOKEN,
|
||||||
|
),
|
||||||
|
secret: not_empty(&ENV.S3_PUBLIC_SECRET),
|
||||||
|
},
|
||||||
|
S3BucketConfig {
|
||||||
|
name: not_empty(&ENV.S3_PRIVATE_BUCKET_NAME),
|
||||||
|
uses_path_style: ENV
|
||||||
|
.S3_PRIVATE_USES_PATH_STYLE_BUCKET,
|
||||||
|
region: not_empty(&ENV.S3_PRIVATE_REGION),
|
||||||
|
url: not_empty(&ENV.S3_PRIVATE_URL),
|
||||||
|
access_token: not_empty(
|
||||||
|
&ENV.S3_PRIVATE_ACCESS_TOKEN,
|
||||||
|
),
|
||||||
|
secret: not_empty(&ENV.S3_PRIVATE_SECRET),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
"local" => Arc::new(file_hosting::MockHost::new()),
|
FileHostKind::Local => Arc::new(file_hosting::MockHost::new()),
|
||||||
_ => panic!("Invalid storage backend specified. Aborting startup!"),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("Initializing clickhouse connection");
|
info!("Initializing clickhouse connection");
|
||||||
@@ -160,8 +154,7 @@ async fn app() -> std::io::Result<()> {
|
|||||||
|
|
||||||
let search_config = search::SearchConfig::new(None);
|
let search_config = search::SearchConfig::new(None);
|
||||||
|
|
||||||
let stripe_client =
|
let stripe_client = stripe::Client::new(ENV.STRIPE_API_KEY.clone());
|
||||||
stripe::Client::new(dotenvy::var("STRIPE_API_KEY").unwrap());
|
|
||||||
|
|
||||||
let anrok_client = anrok::Client::from_env().unwrap();
|
let anrok_client = anrok::Client::from_env().unwrap();
|
||||||
let email_queue =
|
let email_queue =
|
||||||
@@ -272,7 +265,7 @@ async fn app() -> std::io::Result<()> {
|
|||||||
.into_app()
|
.into_app()
|
||||||
.configure(|cfg| app_config(cfg, labrinth_config.clone()))
|
.configure(|cfg| app_config(cfg, labrinth_config.clone()))
|
||||||
})
|
})
|
||||||
.bind(dotenvy::var("BIND_ADDR").unwrap())?
|
.bind(&ENV.BIND_ADDR)?
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::{
|
use crate::{env::ENV, models::v2::projects::LegacySideType};
|
||||||
models::v2::projects::LegacySideType, util::env::parse_strings_from_var,
|
|
||||||
};
|
|
||||||
use path_util::SafeRelativeUtf8UnixPathBuf;
|
use path_util::SafeRelativeUtf8UnixPathBuf;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
@@ -44,9 +42,7 @@ fn validate_download_url(
|
|||||||
return Err(validator::ValidationError::new("invalid URL"));
|
return Err(validator::ValidationError::new("invalid URL"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let domains = parse_strings_from_var("WHITELISTED_MODPACK_DOMAINS")
|
if !ENV.WHITELISTED_MODPACK_DOMAINS.contains(
|
||||||
.unwrap_or_default();
|
|
||||||
if !domains.contains(
|
|
||||||
&url.domain()
|
&url.domain()
|
||||||
.ok_or_else(|| validator::ValidationError::new("invalid URL"))?
|
.ok_or_else(|| validator::ValidationError::new("invalid URL"))?
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::database::models::{
|
|||||||
};
|
};
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::database::{PgPool, PgTransaction};
|
use crate::database::{PgPool, PgTransaction};
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::models::billing::{
|
use crate::models::billing::{
|
||||||
ChargeStatus, ChargeType, PaymentPlatform, Price, PriceDuration,
|
ChargeStatus, ChargeType, PaymentPlatform, Price, PriceDuration,
|
||||||
ProductMetadata, SubscriptionMetadata, SubscriptionStatus,
|
ProductMetadata, SubscriptionMetadata, SubscriptionStatus,
|
||||||
@@ -913,10 +914,10 @@ async fn unprovision_subscriptions(
|
|||||||
let res = reqwest::Client::new()
|
let res = reqwest::Client::new()
|
||||||
.post(format!(
|
.post(format!(
|
||||||
"{}/modrinth/v0/servers/{}/suspend",
|
"{}/modrinth/v0/servers/{}/suspend",
|
||||||
dotenvy::var("ARCHON_URL")?,
|
ENV.ARCHON_URL,
|
||||||
server_id
|
server_id
|
||||||
))
|
))
|
||||||
.header("X-Master-Key", dotenvy::var("PYRO_API_KEY")?)
|
.header("X-Master-Key", &ENV.PYRO_API_KEY)
|
||||||
.json(&serde_json::json!({
|
.json(&serde_json::json!({
|
||||||
"reason": if charge.status == ChargeStatus::Cancelled || charge.status == ChargeStatus::Expiring {
|
"reason": if charge.status == ChargeStatus::Cancelled || charge.status == ChargeStatus::Expiring {
|
||||||
"cancelled"
|
"cancelled"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use crate::database::models::notifications_template_item::NotificationTemplate;
|
|||||||
use crate::database::models::user_item::DBUser;
|
use crate::database::models::user_item::DBUser;
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::database::{PgPool, PgTransaction};
|
use crate::database::{PgPool, PgTransaction};
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::models::notifications::{NotificationBody, NotificationType};
|
use crate::models::notifications::{NotificationBody, NotificationType};
|
||||||
use crate::models::v3::notifications::{
|
use crate::models::v3::notifications::{
|
||||||
NotificationChannel, NotificationDeliveryStatus,
|
NotificationChannel, NotificationDeliveryStatus,
|
||||||
@@ -36,16 +37,16 @@ impl Mailer {
|
|||||||
) -> Result<Arc<AsyncSmtpTransport<Tokio1Executor>>, MailError> {
|
) -> Result<Arc<AsyncSmtpTransport<Tokio1Executor>>, MailError> {
|
||||||
let maybe_transport = match self {
|
let maybe_transport = match self {
|
||||||
Mailer::Uninitialized => {
|
Mailer::Uninitialized => {
|
||||||
let username = dotenvy::var("SMTP_USERNAME")?;
|
let username = &ENV.SMTP_USERNAME;
|
||||||
let password = dotenvy::var("SMTP_PASSWORD")?;
|
let password = &ENV.SMTP_PASSWORD;
|
||||||
let host = dotenvy::var("SMTP_HOST")?;
|
let host = &ENV.SMTP_HOST;
|
||||||
let port =
|
let port = ENV.SMTP_PORT;
|
||||||
dotenvy::var("SMTP_PORT")?.parse::<u16>().unwrap_or(465);
|
|
||||||
|
|
||||||
let creds = (!username.is_empty())
|
let creds = (!username.is_empty()).then(|| {
|
||||||
.then(|| Credentials::new(username, password));
|
Credentials::new(username.clone(), password.clone())
|
||||||
|
});
|
||||||
|
|
||||||
let tls_setting = match dotenvy::var("SMTP_TLS")?.as_str() {
|
let tls_setting = match ENV.SMTP_TLS.as_str() {
|
||||||
"none" => Tls::None,
|
"none" => Tls::None,
|
||||||
"opportunistic_start_tls" => Tls::Opportunistic(
|
"opportunistic_start_tls" => Tls::Opportunistic(
|
||||||
TlsParameters::new(host.to_string())?,
|
TlsParameters::new(host.to_string())?,
|
||||||
@@ -65,7 +66,7 @@ impl Mailer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut mailer =
|
let mut mailer =
|
||||||
AsyncSmtpTransport::<Tokio1Executor>::relay(&host)?
|
AsyncSmtpTransport::<Tokio1Executor>::relay(host)?
|
||||||
.port(port)
|
.port(port)
|
||||||
.tls(tls_setting);
|
.tls(tls_setting);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use crate::database::models::{
|
|||||||
DBOrganization, DBProject, DBUser, DatabaseError,
|
DBOrganization, DBProject, DBUser, DatabaseError,
|
||||||
};
|
};
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::models::v3::notifications::NotificationBody;
|
use crate::models::v3::notifications::NotificationBody;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::util::error::Context;
|
use crate::util::error::Context;
|
||||||
@@ -96,10 +97,18 @@ pub struct MailingIdentity {
|
|||||||
impl MailingIdentity {
|
impl MailingIdentity {
|
||||||
pub fn from_env() -> dotenvy::Result<Self> {
|
pub fn from_env() -> dotenvy::Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
from_name: dotenvy::var("SMTP_FROM_NAME")?,
|
from_name: ENV.SMTP_FROM_NAME.clone(),
|
||||||
from_address: dotenvy::var("SMTP_FROM_ADDRESS")?,
|
from_address: ENV.SMTP_FROM_ADDRESS.clone(),
|
||||||
reply_name: dotenvy::var("SMTP_REPLY_TO_NAME").ok(),
|
reply_name: if ENV.SMTP_REPLY_TO_NAME.is_empty() {
|
||||||
reply_address: dotenvy::var("SMTP_REPLY_TO_ADDRESS").ok(),
|
None
|
||||||
|
} else {
|
||||||
|
Some(ENV.SMTP_REPLY_TO_NAME.clone())
|
||||||
|
},
|
||||||
|
reply_address: if ENV.SMTP_REPLY_TO_ADDRESS.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ENV.SMTP_REPLY_TO_ADDRESS.clone())
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -558,9 +567,7 @@ async fn collect_template_variables(
|
|||||||
NotificationBody::ResetPassword { flow } => {
|
NotificationBody::ResetPassword { flow } => {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/{}?flow={}",
|
"{}/{}?flow={}",
|
||||||
dotenvy::var("SITE_URL")?,
|
ENV.SITE_URL, ENV.SITE_RESET_PASSWORD_PATH, flow
|
||||||
dotenvy::var("SITE_RESET_PASSWORD_PATH")?,
|
|
||||||
flow
|
|
||||||
);
|
);
|
||||||
|
|
||||||
map.insert(RESETPASSWORD_URL, url);
|
map.insert(RESETPASSWORD_URL, url);
|
||||||
@@ -571,9 +578,7 @@ async fn collect_template_variables(
|
|||||||
NotificationBody::VerifyEmail { flow } => {
|
NotificationBody::VerifyEmail { flow } => {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/{}?flow={}",
|
"{}/{}?flow={}",
|
||||||
dotenvy::var("SITE_URL")?,
|
ENV.SITE_URL, ENV.SITE_VERIFY_EMAIL_PATH, flow
|
||||||
dotenvy::var("SITE_VERIFY_EMAIL_PATH")?,
|
|
||||||
flow
|
|
||||||
);
|
);
|
||||||
|
|
||||||
map.insert(VERIFYEMAIL_URL, url);
|
map.insert(VERIFYEMAIL_URL, url);
|
||||||
@@ -603,11 +608,7 @@ async fn collect_template_variables(
|
|||||||
}
|
}
|
||||||
|
|
||||||
NotificationBody::PaymentFailed { amount, service } => {
|
NotificationBody::PaymentFailed { amount, service } => {
|
||||||
let url = format!(
|
let url = format!("{}/{}", ENV.SITE_URL, ENV.SITE_BILLING_PATH,);
|
||||||
"{}/{}",
|
|
||||||
dotenvy::var("SITE_URL")?,
|
|
||||||
dotenvy::var("SITE_BILLING_PATH")?,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert(PAYMENTFAILED_AMOUNT, amount.clone());
|
map.insert(PAYMENTFAILED_AMOUNT, amount.clone());
|
||||||
@@ -748,8 +749,7 @@ async fn dynamic_email_body(
|
|||||||
key: &str,
|
key: &str,
|
||||||
) -> Result<String, ApiError> {
|
) -> Result<String, ApiError> {
|
||||||
get_or_set_cached_dynamic_html(redis, key, || async {
|
get_or_set_cached_dynamic_html(redis, key, || async {
|
||||||
let site_url = dotenvy::var("SITE_URL")
|
let site_url = &ENV.SITE_URL;
|
||||||
.wrap_internal_err("SITE_URL is not set")?;
|
|
||||||
let site_url = site_url.trim_end_matches('/');
|
let site_url = site_url.trim_end_matches('/');
|
||||||
|
|
||||||
let url = format!("{site_url}/_internal/templates/email/dynamic");
|
let url = format!("{site_url}/_internal/templates/email/dynamic");
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::database::PgPool;
|
|||||||
use crate::database::models::notification_item::NotificationBuilder;
|
use crate::database::models::notification_item::NotificationBuilder;
|
||||||
use crate::database::models::thread_item::ThreadMessageBuilder;
|
use crate::database::models::thread_item::ThreadMessageBuilder;
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::models::ids::ProjectId;
|
use crate::models::ids::ProjectId;
|
||||||
use crate::models::notifications::NotificationBody;
|
use crate::models::notifications::NotificationBody;
|
||||||
use crate::models::pack::{PackFile, PackFileHash, PackFormat};
|
use crate::models::pack::{PackFile, PackFileHash, PackFormat};
|
||||||
@@ -454,7 +455,7 @@ impl AutomatedModerationQueue {
|
|||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let res = client
|
let res = client
|
||||||
.post(format!("{}/v1/fingerprints", dotenvy::var("FLAME_ANVIL_URL")?))
|
.post(format!("{}/v1/fingerprints", ENV.FLAME_ANVIL_URL))
|
||||||
.json(&serde_json::json!({
|
.json(&serde_json::json!({
|
||||||
"fingerprints": hashes.iter().filter_map(|x| x.3).collect::<Vec<u32>>()
|
"fingerprints": hashes.iter().filter_map(|x| x.3).collect::<Vec<u32>>()
|
||||||
}))
|
}))
|
||||||
@@ -553,11 +554,11 @@ impl AutomatedModerationQueue {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let flame_projects = if flame_files.is_empty() {
|
let flame_projects = if flame_files.is_empty() {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
} else {
|
} else {
|
||||||
let res = client
|
let res = client
|
||||||
.post(format!("{}v1/mods", dotenvy::var("FLAME_ANVIL_URL")?))
|
.post(format!("{}v1/mods", ENV.FLAME_ANVIL_URL))
|
||||||
.json(&serde_json::json!({
|
.json(&serde_json::json!({
|
||||||
"modIds": flame_files.iter().map(|x| x.1).collect::<Vec<_>>()
|
"modIds": flame_files.iter().map(|x| x.1).collect::<Vec<_>>()
|
||||||
}))
|
}))
|
||||||
@@ -664,16 +665,16 @@ impl AutomatedModerationQueue {
|
|||||||
.insert_many(members.into_iter().map(|x| x.user_id).collect(), &mut transaction, &redis)
|
.insert_many(members.into_iter().map(|x| x.user_id).collect(), &mut transaction, &redis)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Ok(webhook_url) = dotenvy::var("MODERATION_SLACK_WEBHOOK") {
|
if !ENV.MODERATION_SLACK_WEBHOOK.is_empty() {
|
||||||
crate::util::webhook::send_slack_project_webhook(
|
crate::util::webhook::send_slack_project_webhook(
|
||||||
project.inner.id.into(),
|
project.inner.id.into(),
|
||||||
&pool,
|
&pool,
|
||||||
&redis,
|
&redis,
|
||||||
webhook_url,
|
&ENV.MODERATION_SLACK_WEBHOOK,
|
||||||
Some(
|
Some(
|
||||||
format!(
|
format!(
|
||||||
"*<{}/user/AutoMod|AutoMod>* changed project status from *{}* to *Rejected*",
|
"*<{}/user/AutoMod|AutoMod>* changed project status from *{}* to *Rejected*",
|
||||||
dotenvy::var("SITE_URL")?,
|
ENV.SITE_URL,
|
||||||
&project.inner.status.as_friendly_str(),
|
&project.inner.status.as_friendly_str(),
|
||||||
)
|
)
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::database::PgPool;
|
use crate::database::PgPool;
|
||||||
|
use crate::env::ENV;
|
||||||
use chrono::{Datelike, Duration, TimeZone, Utc};
|
use chrono::{Datelike, Duration, TimeZone, Utc};
|
||||||
use eyre::{Context, Result, eyre};
|
use eyre::{Context, Result, eyre};
|
||||||
use rust_decimal::{Decimal, dec};
|
use rust_decimal::{Decimal, dec};
|
||||||
@@ -62,11 +63,7 @@ pub async fn process_affiliate_payouts(postgres: &PgPool) -> Result<()> {
|
|||||||
.await
|
.await
|
||||||
.wrap_err("failed to fetch charges awaiting affiliate payout")?;
|
.wrap_err("failed to fetch charges awaiting affiliate payout")?;
|
||||||
|
|
||||||
let default_affiliate_revenue_split =
|
let default_affiliate_revenue_split = ENV.DEFAULT_AFFILIATE_REVENUE_SPLIT;
|
||||||
dotenvy::var("DEFAULT_AFFILIATE_REVENUE_SPLIT")
|
|
||||||
.wrap_err("no env var `DEFAULT_AFFILIATE_REVENUE_SPLIT`")?
|
|
||||||
.parse::<Decimal>()
|
|
||||||
.wrap_err("`DEFAULT_AFFILIATE_REVENUE_SPLIT` is not a decimal")?;
|
|
||||||
|
|
||||||
let (
|
let (
|
||||||
mut insert_usap_charges,
|
mut insert_usap_charges,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use serde_json::json;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::models::payout_item::DBPayout,
|
database::models::payout_item::DBPayout,
|
||||||
|
env::ENV,
|
||||||
models::payouts::{
|
models::payouts::{
|
||||||
PayoutMethod, PayoutMethodFee, PayoutMethodType, PayoutStatus,
|
PayoutMethod, PayoutMethodFee, PayoutMethodType, PayoutStatus,
|
||||||
TremendousCurrency, TremendousDetails, TremendousForexResponse,
|
TremendousCurrency, TremendousDetails, TremendousForexResponse,
|
||||||
@@ -210,7 +211,7 @@ pub(super) async fn execute(
|
|||||||
"products": [
|
"products": [
|
||||||
method_id,
|
method_id,
|
||||||
],
|
],
|
||||||
"campaign_id": dotenvy::var("TREMENDOUS_CAMPAIGN_ID")?,
|
"campaign_id": ENV.TREMENDOUS_CAMPAIGN_ID.as_str(),
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ use crate::database::models::notification_item::NotificationBuilder;
|
|||||||
use crate::database::models::payouts_values_notifications;
|
use crate::database::models::payouts_values_notifications;
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::database::{PgPool, PgTransaction};
|
use crate::database::{PgPool, PgTransaction};
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::models::payouts::{
|
use crate::models::payouts::{
|
||||||
PayoutDecimal, PayoutInterval, PayoutMethod, PayoutMethodType,
|
PayoutDecimal, PayoutInterval, PayoutMethod, PayoutMethodType,
|
||||||
TremendousForexResponse,
|
TremendousForexResponse,
|
||||||
};
|
};
|
||||||
use crate::models::projects::MonetizationStatus;
|
use crate::models::projects::MonetizationStatus;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::util::env::env_var;
|
|
||||||
use crate::util::error::Context;
|
use crate::util::error::Context;
|
||||||
use crate::util::webhook::{
|
use crate::util::webhook::{
|
||||||
PayoutSourceAlertType, send_slack_payout_source_alert_webhook,
|
PayoutSourceAlertType, send_slack_payout_source_alert_webhook,
|
||||||
@@ -76,21 +76,18 @@ impl Default for PayoutsQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_muralpay_client() -> Result<muralpay::Client> {
|
pub fn create_muralpay_client() -> Result<muralpay::Client> {
|
||||||
let api_url = env_var("MURALPAY_API_URL")?;
|
Ok(muralpay::Client::new(
|
||||||
let api_key = env_var("MURALPAY_API_KEY")?;
|
&ENV.MURALPAY_API_URL,
|
||||||
let transfer_api_key = env_var("MURALPAY_TRANSFER_API_KEY")?;
|
ENV.MURALPAY_API_KEY.as_str(),
|
||||||
Ok(muralpay::Client::new(api_url, api_key, transfer_api_key))
|
ENV.MURALPAY_TRANSFER_API_KEY.as_str(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_muralpay() -> Result<MuralPayConfig> {
|
pub fn create_muralpay() -> Result<MuralPayConfig> {
|
||||||
let client = create_muralpay_client()?;
|
let client = create_muralpay_client()?;
|
||||||
let source_account_id = env_var("MURALPAY_SOURCE_ACCOUNT_ID")?
|
|
||||||
.parse::<muralpay::AccountId>()
|
|
||||||
.wrap_err("failed to parse source account ID")?;
|
|
||||||
|
|
||||||
Ok(MuralPayConfig {
|
Ok(MuralPayConfig {
|
||||||
client,
|
client,
|
||||||
source_account_id,
|
source_account_id: ENV.MURALPAY_SOURCE_ACCOUNT_ID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,11 +182,8 @@ impl PayoutsQueue {
|
|||||||
let mut creds = self.credential.write().await;
|
let mut creds = self.credential.write().await;
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
let combined_key = format!(
|
let combined_key =
|
||||||
"{}:{}",
|
format!("{}:{}", ENV.PAYPAL_CLIENT_ID, ENV.PAYPAL_CLIENT_SECRET);
|
||||||
dotenvy::var("PAYPAL_CLIENT_ID")?,
|
|
||||||
dotenvy::var("PAYPAL_CLIENT_SECRET")?
|
|
||||||
);
|
|
||||||
let formatted_key = format!(
|
let formatted_key = format!(
|
||||||
"Basic {}",
|
"Basic {}",
|
||||||
base64::engine::general_purpose::STANDARD.encode(combined_key)
|
base64::engine::general_purpose::STANDARD.encode(combined_key)
|
||||||
@@ -206,7 +200,7 @@ impl PayoutsQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let credential: PaypalCredential = client
|
let credential: PaypalCredential = client
|
||||||
.post(format!("{}oauth2/token", dotenvy::var("PAYPAL_API_URL")?))
|
.post(format!("{}oauth2/token", ENV.PAYPAL_API_URL))
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.header("Accept-Language", "en_US")
|
.header("Accept-Language", "en_US")
|
||||||
.header("Authorization", formatted_key)
|
.header("Authorization", formatted_key)
|
||||||
@@ -274,7 +268,7 @@ impl PayoutsQueue {
|
|||||||
if no_api_prefix.unwrap_or(false) {
|
if no_api_prefix.unwrap_or(false) {
|
||||||
path.to_string()
|
path.to_string()
|
||||||
} else {
|
} else {
|
||||||
format!("{}{path}", dotenvy::var("PAYPAL_API_URL")?)
|
format!("{}{path}", ENV.PAYPAL_API_URL)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.header(
|
.header(
|
||||||
@@ -355,13 +349,10 @@ impl PayoutsQueue {
|
|||||||
) -> Result<X, ApiError> {
|
) -> Result<X, ApiError> {
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let mut request = client
|
let mut request = client
|
||||||
.request(
|
.request(method, format!("{}{path}", ENV.TREMENDOUS_API_URL))
|
||||||
method,
|
|
||||||
format!("{}{path}", dotenvy::var("TREMENDOUS_API_URL")?),
|
|
||||||
)
|
|
||||||
.header(
|
.header(
|
||||||
"Authorization",
|
"Authorization",
|
||||||
format!("Bearer {}", dotenvy::var("TREMENDOUS_API_KEY")?),
|
format!("Bearer {}", ENV.TREMENDOUS_API_KEY),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(body) = body {
|
if let Some(body) = body {
|
||||||
@@ -511,8 +502,8 @@ impl PayoutsQueue {
|
|||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let res = client
|
let res = client
|
||||||
.get(format!("{}accounts/cash", dotenvy::var("BREX_API_URL")?))
|
.get(format!("{}accounts/cash", ENV.BREX_API_URL))
|
||||||
.bearer_auth(&dotenvy::var("BREX_API_KEY")?)
|
.bearer_auth(&ENV.BREX_API_KEY)
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await?
|
||||||
.json::<BrexResponse>()
|
.json::<BrexResponse>()
|
||||||
@@ -538,16 +529,16 @@ impl PayoutsQueue {
|
|||||||
|
|
||||||
pub async fn get_paypal_balance() -> Result<Option<AccountBalance>, ApiError>
|
pub async fn get_paypal_balance() -> Result<Option<AccountBalance>, ApiError>
|
||||||
{
|
{
|
||||||
let api_username = dotenvy::var("PAYPAL_NVP_USERNAME")?;
|
let api_username = &ENV.PAYPAL_NVP_USERNAME;
|
||||||
let api_password = dotenvy::var("PAYPAL_NVP_PASSWORD")?;
|
let api_password = &ENV.PAYPAL_NVP_PASSWORD;
|
||||||
let api_signature = dotenvy::var("PAYPAL_NVP_SIGNATURE")?;
|
let api_signature = &ENV.PAYPAL_NVP_SIGNATURE;
|
||||||
|
|
||||||
let mut params = HashMap::new();
|
let mut params = HashMap::new();
|
||||||
params.insert("METHOD", "GetBalance");
|
params.insert("METHOD", "GetBalance");
|
||||||
params.insert("VERSION", "204");
|
params.insert("VERSION", "204");
|
||||||
params.insert("USER", &api_username);
|
params.insert("USER", api_username);
|
||||||
params.insert("PWD", &api_password);
|
params.insert("PWD", api_password);
|
||||||
params.insert("SIGNATURE", &api_signature);
|
params.insert("SIGNATURE", api_signature);
|
||||||
params.insert("RETURNALLCURRENCIES", "1");
|
params.insert("RETURNALLCURRENCIES", "1");
|
||||||
|
|
||||||
let endpoint = "https://api-3t.paypal.com/nvp";
|
let endpoint = "https://api-3t.paypal.com/nvp";
|
||||||
@@ -870,7 +861,7 @@ pub async fn make_aditude_request(
|
|||||||
) -> Result<Vec<AditudePoints>, ApiError> {
|
) -> Result<Vec<AditudePoints>, ApiError> {
|
||||||
let request = reqwest::Client::new()
|
let request = reqwest::Client::new()
|
||||||
.post("https://cloud.aditude.io/api/public/insights/metrics")
|
.post("https://cloud.aditude.io/api/public/insights/metrics")
|
||||||
.bearer_auth(&dotenvy::var("ADITUDE_API_KEY")?)
|
.bearer_auth(&ENV.ADITUDE_API_KEY)
|
||||||
.json(&serde_json::json!({
|
.json(&serde_json::json!({
|
||||||
"metrics": metrics,
|
"metrics": metrics,
|
||||||
"range": range,
|
"range": range,
|
||||||
@@ -1326,25 +1317,25 @@ pub async fn insert_bank_balances_and_webhook(
|
|||||||
if inserted {
|
if inserted {
|
||||||
check_balance_with_webhook(
|
check_balance_with_webhook(
|
||||||
"paypal",
|
"paypal",
|
||||||
"PAYPAL_BALANCE_ALERT_THRESHOLD",
|
ENV.PAYPAL_BALANCE_ALERT_THRESHOLD,
|
||||||
paypal_result,
|
paypal_result,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
check_balance_with_webhook(
|
check_balance_with_webhook(
|
||||||
"brex",
|
"brex",
|
||||||
"BREX_BALANCE_ALERT_THRESHOLD",
|
ENV.BREX_BALANCE_ALERT_THRESHOLD,
|
||||||
brex_result,
|
brex_result,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
check_balance_with_webhook(
|
check_balance_with_webhook(
|
||||||
"tremendous",
|
"tremendous",
|
||||||
"TREMENDOUS_BALANCE_ALERT_THRESHOLD",
|
ENV.TREMENDOUS_BALANCE_ALERT_THRESHOLD,
|
||||||
tremendous_result,
|
tremendous_result,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
check_balance_with_webhook(
|
check_balance_with_webhook(
|
||||||
"mural",
|
"mural",
|
||||||
"MURAL_BALANCE_ALERT_THRESHOLD",
|
ENV.MURAL_BALANCE_ALERT_THRESHOLD,
|
||||||
mural_result,
|
mural_result,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -1357,14 +1348,11 @@ pub async fn insert_bank_balances_and_webhook(
|
|||||||
|
|
||||||
async fn check_balance_with_webhook(
|
async fn check_balance_with_webhook(
|
||||||
source: &str,
|
source: &str,
|
||||||
threshold_env_var_name: &str,
|
threshold: u64,
|
||||||
result: Result<Option<AccountBalance>, ApiError>,
|
result: Result<Option<AccountBalance>, ApiError>,
|
||||||
) -> Result<Option<AccountBalance>, ApiError> {
|
) -> Result<Option<AccountBalance>, ApiError> {
|
||||||
let maybe_threshold = dotenvy::var(threshold_env_var_name)
|
let maybe_threshold = if threshold > 0 { Some(threshold) } else { None };
|
||||||
.ok()
|
let payout_alert_webhook = &ENV.PAYOUT_ALERT_SLACK_WEBHOOK;
|
||||||
.and_then(|x| x.parse::<u64>().ok())
|
|
||||||
.filter(|x| *x != 0);
|
|
||||||
let payout_alert_webhook = dotenvy::var("PAYOUT_ALERT_SLACK_WEBHOOK")?;
|
|
||||||
|
|
||||||
match &result {
|
match &result {
|
||||||
Ok(Some(account_balance)) => {
|
Ok(Some(account_balance)) => {
|
||||||
@@ -1379,7 +1367,7 @@ async fn check_balance_with_webhook(
|
|||||||
threshold,
|
threshold,
|
||||||
current_balance: available,
|
current_balance: available,
|
||||||
},
|
},
|
||||||
&payout_alert_webhook,
|
payout_alert_webhook,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
@@ -1394,7 +1382,7 @@ async fn check_balance_with_webhook(
|
|||||||
source: source.to_owned(),
|
source: source.to_owned(),
|
||||||
display_error: error.to_string(),
|
display_error: error.to_string(),
|
||||||
},
|
},
|
||||||
&payout_alert_webhook,
|
payout_alert_webhook,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use crate::auth::get_user_from_headers;
|
use crate::auth::get_user_from_headers;
|
||||||
use crate::database::PgPool;
|
use crate::database::PgPool;
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::models::analytics::{PageView, Playtime};
|
use crate::models::analytics::{PageView, Playtime};
|
||||||
use crate::models::pats::Scopes;
|
use crate::models::pats::Scopes;
|
||||||
use crate::queue::analytics::AnalyticsQueue;
|
use crate::queue::analytics::AnalyticsQueue;
|
||||||
use crate::queue::session::AuthQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::util::date::get_current_tenths_of_ms;
|
use crate::util::date::get_current_tenths_of_ms;
|
||||||
use crate::util::env::parse_strings_from_var;
|
|
||||||
use actix_web::{HttpRequest, HttpResponse};
|
use actix_web::{HttpRequest, HttpResponse};
|
||||||
use actix_web::{post, web};
|
use actix_web::{post, web};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@@ -73,11 +73,10 @@ pub async fn page_view_ingest(
|
|||||||
})?;
|
})?;
|
||||||
let url_origin = url.origin().ascii_serialization();
|
let url_origin = url.origin().ascii_serialization();
|
||||||
|
|
||||||
let is_valid_url_origin =
|
let is_valid_url_origin = ENV
|
||||||
parse_strings_from_var("ANALYTICS_ALLOWED_ORIGINS")
|
.ANALYTICS_ALLOWED_ORIGINS
|
||||||
.unwrap_or_default()
|
.iter()
|
||||||
.iter()
|
.any(|origin| origin == "*" || url_origin == *origin);
|
||||||
.any(|origin| origin == "*" || url_origin == *origin);
|
|
||||||
|
|
||||||
if !is_valid_url_origin {
|
if !is_valid_url_origin {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::{collections::HashMap, net::Ipv4Addr, sync::Arc};
|
use std::{collections::HashMap, net::Ipv4Addr, sync::Arc};
|
||||||
|
|
||||||
use crate::database::PgPool;
|
use crate::database::PgPool;
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::{
|
use crate::{
|
||||||
auth::get_user_from_headers,
|
auth::get_user_from_headers,
|
||||||
database::{
|
database::{
|
||||||
@@ -13,10 +14,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
queue::{analytics::AnalyticsQueue, session::AuthQueue},
|
queue::{analytics::AnalyticsQueue, session::AuthQueue},
|
||||||
routes::analytics::FILTERED_HEADERS,
|
routes::analytics::FILTERED_HEADERS,
|
||||||
util::{
|
util::{date::get_current_tenths_of_ms, error::Context},
|
||||||
date::get_current_tenths_of_ms, env::parse_strings_from_var,
|
|
||||||
error::Context,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use actix_web::{HttpRequest, delete, get, patch, post, put, web};
|
use actix_web::{HttpRequest, delete, get, patch, post, put, web};
|
||||||
use ariadne::ids::UserId;
|
use ariadne::ids::UserId;
|
||||||
@@ -70,11 +68,10 @@ async fn ingest_click(
|
|||||||
})?;
|
})?;
|
||||||
let url_origin = url.origin().ascii_serialization();
|
let url_origin = url.origin().ascii_serialization();
|
||||||
|
|
||||||
let is_valid_url_origin =
|
let is_valid_url_origin = ENV
|
||||||
parse_strings_from_var("ANALYTICS_ALLOWED_ORIGINS")
|
.ANALYTICS_ALLOWED_ORIGINS
|
||||||
.unwrap_or_default()
|
.iter()
|
||||||
.iter()
|
.any(|origin| origin == "*" || url_origin == *origin);
|
||||||
.any(|origin| origin == "*" || url_origin == *origin);
|
|
||||||
|
|
||||||
if !is_valid_url_origin {
|
if !is_valid_url_origin {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::database::models::{
|
|||||||
};
|
};
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::database::{PgPool, PgTransaction};
|
use crate::database::{PgPool, PgTransaction};
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::models::billing::{
|
use crate::models::billing::{
|
||||||
Charge, ChargeStatus, ChargeType, PaymentPlatform, Price, PriceDuration,
|
Charge, ChargeStatus, ChargeType, PaymentPlatform, Price, PriceDuration,
|
||||||
Product, ProductMetadata, ProductPrice, SubscriptionMetadata,
|
Product, ProductMetadata, ProductPrice, SubscriptionMetadata,
|
||||||
@@ -1437,7 +1438,7 @@ pub async fn active_servers(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
query: web::Query<ActiveServersQuery>,
|
query: web::Query<ActiveServersQuery>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let master_key = dotenvy::var("PYRO_API_KEY")?;
|
let master_key = &ENV.PYRO_API_KEY;
|
||||||
|
|
||||||
if req
|
if req
|
||||||
.head()
|
.head()
|
||||||
@@ -1626,7 +1627,7 @@ pub async fn stripe_webhook(
|
|||||||
if let Ok(event) = Webhook::construct_event(
|
if let Ok(event) = Webhook::construct_event(
|
||||||
&payload,
|
&payload,
|
||||||
stripe_signature,
|
stripe_signature,
|
||||||
&dotenvy::var("STRIPE_WEBHOOK_SECRET")?,
|
&ENV.STRIPE_WEBHOOK_SECRET,
|
||||||
) {
|
) {
|
||||||
struct PaymentIntentMetadata {
|
struct PaymentIntentMetadata {
|
||||||
pub user_item: crate::database::models::user_item::DBUser,
|
pub user_item: crate::database::models::user_item::DBUser,
|
||||||
@@ -2036,23 +2037,23 @@ pub async fn stripe_webhook(
|
|||||||
client
|
client
|
||||||
.post(format!(
|
.post(format!(
|
||||||
"{}/modrinth/v0/servers/{}/unsuspend",
|
"{}/modrinth/v0/servers/{}/unsuspend",
|
||||||
dotenvy::var("ARCHON_URL")?,
|
ENV.ARCHON_URL,
|
||||||
id
|
id
|
||||||
))
|
))
|
||||||
.header("X-Master-Key", dotenvy::var("PYRO_API_KEY")?)
|
.header("X-Master-Key", &ENV.PYRO_API_KEY)
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await?
|
||||||
.error_for_status()?;
|
.error_for_status()?;
|
||||||
|
|
||||||
client
|
client
|
||||||
.post(format!(
|
.post(format!(
|
||||||
"{}/modrinth/v0/servers/{}/reallocate",
|
"{}/modrinth/v0/servers/{}/reallocate",
|
||||||
dotenvy::var("ARCHON_URL")?,
|
ENV.ARCHON_URL,
|
||||||
id
|
id
|
||||||
))
|
))
|
||||||
.header(
|
.header(
|
||||||
"X-Master-Key",
|
"X-Master-Key",
|
||||||
dotenvy::var("PYRO_API_KEY")?,
|
&ENV.PYRO_API_KEY,
|
||||||
)
|
)
|
||||||
.json(&body)
|
.json(&body)
|
||||||
.send()
|
.send()
|
||||||
@@ -2114,9 +2115,9 @@ pub async fn stripe_webhook(
|
|||||||
let res = client
|
let res = client
|
||||||
.post(format!(
|
.post(format!(
|
||||||
"{}/modrinth/v0/servers/create",
|
"{}/modrinth/v0/servers/create",
|
||||||
dotenvy::var("ARCHON_URL")?,
|
ENV.ARCHON_URL,
|
||||||
))
|
))
|
||||||
.header("X-Master-Key", dotenvy::var("PYRO_API_KEY")?)
|
.header("X-Master-Key", &ENV.PYRO_API_KEY)
|
||||||
.json(&serde_json::json!({
|
.json(&serde_json::json!({
|
||||||
"user_id": to_base62(metadata.user_item.id.0 as u64),
|
"user_id": to_base62(metadata.user_item.id.0 as u64),
|
||||||
"name": server_name,
|
"name": server_name,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::{collections::HashMap, fmt::Write, sync::LazyLock, time::Instant};
|
use std::{collections::HashMap, fmt::Write, sync::LazyLock, time::Instant};
|
||||||
|
|
||||||
use crate::database::PgPool;
|
use crate::database::PgPool;
|
||||||
|
use crate::env::ENV;
|
||||||
use actix_web::{HttpRequest, HttpResponse, get, post, web};
|
use actix_web::{HttpRequest, HttpResponse, get, post, web};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use eyre::eyre;
|
use eyre::eyre;
|
||||||
@@ -89,7 +90,7 @@ impl DelphiReport {
|
|||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
redis: &RedisPool,
|
redis: &RedisPool,
|
||||||
) -> Result<(), ApiError> {
|
) -> Result<(), ApiError> {
|
||||||
let webhook_url = dotenvy::var("DELPHI_SLACK_WEBHOOK")?;
|
let webhook_url = ENV.DELPHI_SLACK_WEBHOOK.clone();
|
||||||
|
|
||||||
let mut message_header =
|
let mut message_header =
|
||||||
format!("⚠️ Suspicious traces found at {}", self.url);
|
format!("⚠️ Suspicious traces found at {}", self.url);
|
||||||
@@ -115,7 +116,7 @@ impl DelphiReport {
|
|||||||
self.project_id,
|
self.project_id,
|
||||||
pool,
|
pool,
|
||||||
redis,
|
redis,
|
||||||
webhook_url,
|
&webhook_url,
|
||||||
Some(message_header),
|
Some(message_header),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -317,7 +318,7 @@ pub async fn run(
|
|||||||
);
|
);
|
||||||
|
|
||||||
DELPHI_CLIENT
|
DELPHI_CLIENT
|
||||||
.post(dotenvy::var("DELPHI_URL")?)
|
.post(&ENV.DELPHI_URL)
|
||||||
.json(&serde_json::json!({
|
.json(&serde_json::json!({
|
||||||
"url": file_data.url,
|
"url": file_data.url,
|
||||||
"project_id": ProjectId(file_data.project_id.0 as u64),
|
"project_id": ProjectId(file_data.project_id.0 as u64),
|
||||||
@@ -407,7 +408,7 @@ async fn issue_type_schema(
|
|||||||
&cache_entry
|
&cache_entry
|
||||||
.insert((
|
.insert((
|
||||||
DELPHI_CLIENT
|
DELPHI_CLIENT
|
||||||
.get(format!("{}/schema", dotenvy::var("DELPHI_URL")?))
|
.get(format!("{}/schema", ENV.DELPHI_URL))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.and_then(|res| res.error_for_status())
|
.and_then(|res| res.error_for_status())
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use crate::database::models::flow_item::DBFlow;
|
|||||||
use crate::database::models::notification_item::NotificationBuilder;
|
use crate::database::models::notification_item::NotificationBuilder;
|
||||||
use crate::database::models::{DBUser, DBUserId};
|
use crate::database::models::{DBUser, DBUserId};
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::file_hosting::{FileHost, FileHostPublicity};
|
use crate::file_hosting::{FileHost, FileHostPublicity};
|
||||||
use crate::models::notifications::NotificationBody;
|
use crate::models::notifications::NotificationBody;
|
||||||
use crate::models::pats::Scopes;
|
use crate::models::pats::Scopes;
|
||||||
@@ -17,7 +18,6 @@ use crate::queue::session::AuthQueue;
|
|||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::routes::internal::session::issue_session;
|
use crate::routes::internal::session::issue_session;
|
||||||
use crate::util::captcha::check_hcaptcha;
|
use crate::util::captcha::check_hcaptcha;
|
||||||
use crate::util::env::parse_strings_from_var;
|
|
||||||
use crate::util::error::Context;
|
use crate::util::error::Context;
|
||||||
use crate::util::ext::get_image_ext;
|
use crate::util::ext::get_image_ext;
|
||||||
use crate::util::img::upload_image_optimized;
|
use crate::util::img::upload_image_optimized;
|
||||||
@@ -257,41 +257,41 @@ impl AuthProvider {
|
|||||||
&self,
|
&self,
|
||||||
state: String,
|
state: String,
|
||||||
) -> Result<String, AuthenticationError> {
|
) -> Result<String, AuthenticationError> {
|
||||||
let self_addr = dotenvy::var("SELF_ADDR")?;
|
let self_addr = &ENV.SELF_ADDR;
|
||||||
let raw_redirect_uri = format!("{self_addr}/v2/auth/callback");
|
let raw_redirect_uri = format!("{self_addr}/v2/auth/callback");
|
||||||
let redirect_uri = urlencoding::encode(&raw_redirect_uri);
|
let redirect_uri = urlencoding::encode(&raw_redirect_uri);
|
||||||
|
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
AuthProvider::GitHub => {
|
AuthProvider::GitHub => {
|
||||||
let client_id = dotenvy::var("GITHUB_CLIENT_ID")?;
|
let client_id = &ENV.GITHUB_CLIENT_ID;
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"https://github.com/login/oauth/authorize?client_id={client_id}&prompt=select_account&state={state}&scope=read%3Auser%20user%3Aemail&redirect_uri={redirect_uri}",
|
"https://github.com/login/oauth/authorize?client_id={client_id}&prompt=select_account&state={state}&scope=read%3Auser%20user%3Aemail&redirect_uri={redirect_uri}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
AuthProvider::Discord => {
|
AuthProvider::Discord => {
|
||||||
let client_id = dotenvy::var("DISCORD_CLIENT_ID")?;
|
let client_id = &ENV.DISCORD_CLIENT_ID;
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"https://discord.com/api/oauth2/authorize?client_id={client_id}&state={state}&response_type=code&scope=identify%20email&redirect_uri={redirect_uri}"
|
"https://discord.com/api/oauth2/authorize?client_id={client_id}&state={state}&response_type=code&scope=identify%20email&redirect_uri={redirect_uri}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
AuthProvider::Microsoft => {
|
AuthProvider::Microsoft => {
|
||||||
let client_id = dotenvy::var("MICROSOFT_CLIENT_ID")?;
|
let client_id = &ENV.MICROSOFT_CLIENT_ID;
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"https://login.live.com/oauth20_authorize.srf?client_id={client_id}&response_type=code&scope=user.read&state={state}&prompt=select_account&redirect_uri={redirect_uri}"
|
"https://login.live.com/oauth20_authorize.srf?client_id={client_id}&response_type=code&scope=user.read&state={state}&prompt=select_account&redirect_uri={redirect_uri}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
AuthProvider::GitLab => {
|
AuthProvider::GitLab => {
|
||||||
let client_id = dotenvy::var("GITLAB_CLIENT_ID")?;
|
let client_id = &ENV.GITLAB_CLIENT_ID;
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"https://gitlab.com/oauth/authorize?client_id={client_id}&state={state}&scope=read_user+profile+email&response_type=code&redirect_uri={redirect_uri}",
|
"https://gitlab.com/oauth/authorize?client_id={client_id}&state={state}&scope=read_user+profile+email&response_type=code&redirect_uri={redirect_uri}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
AuthProvider::Google => {
|
AuthProvider::Google => {
|
||||||
let client_id = dotenvy::var("GOOGLE_CLIENT_ID")?;
|
let client_id = &ENV.GOOGLE_CLIENT_ID;
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"https://accounts.google.com/o/oauth2/v2/auth?client_id={}&state={}&scope={}&response_type=code&redirect_uri={}",
|
"https://accounts.google.com/o/oauth2/v2/auth?client_id={}&state={}&scope={}&response_type=code&redirect_uri={}",
|
||||||
@@ -317,8 +317,8 @@ impl AuthProvider {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
AuthProvider::PayPal => {
|
AuthProvider::PayPal => {
|
||||||
let api_url = dotenvy::var("PAYPAL_API_URL")?;
|
let api_url = &ENV.PAYPAL_API_URL;
|
||||||
let client_id = dotenvy::var("PAYPAL_CLIENT_ID")?;
|
let client_id = &ENV.PAYPAL_CLIENT_ID;
|
||||||
|
|
||||||
let auth_url = if api_url.contains("sandbox") {
|
let auth_url = if api_url.contains("sandbox") {
|
||||||
"sandbox.paypal.com"
|
"sandbox.paypal.com"
|
||||||
@@ -340,8 +340,7 @@ impl AuthProvider {
|
|||||||
&self,
|
&self,
|
||||||
query: HashMap<String, String>,
|
query: HashMap<String, String>,
|
||||||
) -> Result<String, AuthenticationError> {
|
) -> Result<String, AuthenticationError> {
|
||||||
let redirect_uri =
|
let redirect_uri = format!("{}/v2/auth/callback", &ENV.SELF_ADDR);
|
||||||
format!("{}/v2/auth/callback", dotenvy::var("SELF_ADDR")?);
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct AccessToken {
|
struct AccessToken {
|
||||||
@@ -353,8 +352,8 @@ impl AuthProvider {
|
|||||||
let code = query
|
let code = query
|
||||||
.get("code")
|
.get("code")
|
||||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||||
let client_id = dotenvy::var("GITHUB_CLIENT_ID")?;
|
let client_id = ENV.GITHUB_CLIENT_ID.as_str();
|
||||||
let client_secret = dotenvy::var("GITHUB_CLIENT_SECRET")?;
|
let client_secret = ENV.GITHUB_CLIENT_SECRET.as_str();
|
||||||
|
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"https://github.com/login/oauth/access_token?client_id={client_id}&client_secret={client_secret}&code={code}&redirect_uri={redirect_uri}"
|
"https://github.com/login/oauth/access_token?client_id={client_id}&client_secret={client_secret}&code={code}&redirect_uri={redirect_uri}"
|
||||||
@@ -374,12 +373,12 @@ impl AuthProvider {
|
|||||||
let code = query
|
let code = query
|
||||||
.get("code")
|
.get("code")
|
||||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||||
let client_id = dotenvy::var("DISCORD_CLIENT_ID")?;
|
let client_id = ENV.DISCORD_CLIENT_ID.as_str();
|
||||||
let client_secret = dotenvy::var("DISCORD_CLIENT_SECRET")?;
|
let client_secret = ENV.DISCORD_CLIENT_SECRET.as_str();
|
||||||
|
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert("client_id", &*client_id);
|
map.insert("client_id", client_id);
|
||||||
map.insert("client_secret", &*client_secret);
|
map.insert("client_secret", client_secret);
|
||||||
map.insert("code", code);
|
map.insert("code", code);
|
||||||
map.insert("grant_type", "authorization_code");
|
map.insert("grant_type", "authorization_code");
|
||||||
map.insert("redirect_uri", &redirect_uri);
|
map.insert("redirect_uri", &redirect_uri);
|
||||||
@@ -399,12 +398,12 @@ impl AuthProvider {
|
|||||||
let code = query
|
let code = query
|
||||||
.get("code")
|
.get("code")
|
||||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||||
let client_id = dotenvy::var("MICROSOFT_CLIENT_ID")?;
|
let client_id = ENV.MICROSOFT_CLIENT_ID.as_str();
|
||||||
let client_secret = dotenvy::var("MICROSOFT_CLIENT_SECRET")?;
|
let client_secret = ENV.MICROSOFT_CLIENT_SECRET.as_str();
|
||||||
|
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert("client_id", &*client_id);
|
map.insert("client_id", client_id);
|
||||||
map.insert("client_secret", &*client_secret);
|
map.insert("client_secret", client_secret);
|
||||||
map.insert("code", code);
|
map.insert("code", code);
|
||||||
map.insert("grant_type", "authorization_code");
|
map.insert("grant_type", "authorization_code");
|
||||||
map.insert("redirect_uri", &redirect_uri);
|
map.insert("redirect_uri", &redirect_uri);
|
||||||
@@ -424,12 +423,12 @@ impl AuthProvider {
|
|||||||
let code = query
|
let code = query
|
||||||
.get("code")
|
.get("code")
|
||||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||||
let client_id = dotenvy::var("GITLAB_CLIENT_ID")?;
|
let client_id = ENV.GITLAB_CLIENT_ID.as_str();
|
||||||
let client_secret = dotenvy::var("GITLAB_CLIENT_SECRET")?;
|
let client_secret = ENV.GITLAB_CLIENT_SECRET.as_str();
|
||||||
|
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert("client_id", &*client_id);
|
map.insert("client_id", client_id);
|
||||||
map.insert("client_secret", &*client_secret);
|
map.insert("client_secret", client_secret);
|
||||||
map.insert("code", code);
|
map.insert("code", code);
|
||||||
map.insert("grant_type", "authorization_code");
|
map.insert("grant_type", "authorization_code");
|
||||||
map.insert("redirect_uri", &redirect_uri);
|
map.insert("redirect_uri", &redirect_uri);
|
||||||
@@ -449,12 +448,12 @@ impl AuthProvider {
|
|||||||
let code = query
|
let code = query
|
||||||
.get("code")
|
.get("code")
|
||||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||||
let client_id = dotenvy::var("GOOGLE_CLIENT_ID")?;
|
let client_id = ENV.GOOGLE_CLIENT_ID.as_str();
|
||||||
let client_secret = dotenvy::var("GOOGLE_CLIENT_SECRET")?;
|
let client_secret = ENV.GOOGLE_CLIENT_SECRET.as_str();
|
||||||
|
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert("client_id", &*client_id);
|
map.insert("client_id", client_id);
|
||||||
map.insert("client_secret", &*client_secret);
|
map.insert("client_secret", client_secret);
|
||||||
map.insert("code", code);
|
map.insert("code", code);
|
||||||
map.insert("grant_type", "authorization_code");
|
map.insert("grant_type", "authorization_code");
|
||||||
map.insert("redirect_uri", &redirect_uri);
|
map.insert("redirect_uri", &redirect_uri);
|
||||||
@@ -529,9 +528,9 @@ impl AuthProvider {
|
|||||||
let code = query
|
let code = query
|
||||||
.get("code")
|
.get("code")
|
||||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||||
let api_url = dotenvy::var("PAYPAL_API_URL")?;
|
let api_url = ENV.PAYPAL_API_URL.as_str();
|
||||||
let client_id = dotenvy::var("PAYPAL_CLIENT_ID")?;
|
let client_id = ENV.PAYPAL_CLIENT_ID.as_str();
|
||||||
let client_secret = dotenvy::var("PAYPAL_CLIENT_SECRET")?;
|
let client_secret = ENV.PAYPAL_CLIENT_SECRET.as_str();
|
||||||
|
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert("code", code.as_str());
|
map.insert("code", code.as_str());
|
||||||
@@ -580,9 +579,7 @@ impl AuthProvider {
|
|||||||
.get("x-oauth-client-id")
|
.get("x-oauth-client-id")
|
||||||
.and_then(|x| x.to_str().ok());
|
.and_then(|x| x.to_str().ok());
|
||||||
|
|
||||||
if client_id
|
if client_id != Some(ENV.GITHUB_CLIENT_ID.as_str()) {
|
||||||
!= Some(&*dotenvy::var("GITHUB_CLIENT_ID").unwrap())
|
|
||||||
{
|
|
||||||
return Err(AuthenticationError::InvalidClientId);
|
return Err(AuthenticationError::InvalidClientId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -732,7 +729,7 @@ impl AuthProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
AuthProvider::Steam => {
|
AuthProvider::Steam => {
|
||||||
let api_key = dotenvy::var("STEAM_API_KEY")?;
|
let api_key = &ENV.STEAM_API_KEY;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct SteamResponse {
|
struct SteamResponse {
|
||||||
@@ -797,7 +794,7 @@ impl AuthProvider {
|
|||||||
pub country: String,
|
pub country: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
let api_url = dotenvy::var("PAYPAL_API_URL")?;
|
let api_url = &ENV.PAYPAL_API_URL;
|
||||||
|
|
||||||
let paypal_user: PayPalUser = reqwest::Client::new()
|
let paypal_user: PayPalUser = reqwest::Client::new()
|
||||||
.get(format!(
|
.get(format!(
|
||||||
@@ -1100,10 +1097,11 @@ pub async fn init(
|
|||||||
let url =
|
let url =
|
||||||
url::Url::parse(&info.url).map_err(|_| AuthenticationError::Url)?;
|
url::Url::parse(&info.url).map_err(|_| AuthenticationError::Url)?;
|
||||||
|
|
||||||
let allowed_callback_urls =
|
|
||||||
parse_strings_from_var("ALLOWED_CALLBACK_URLS").unwrap_or_default();
|
|
||||||
let domain = url.host_str().ok_or(AuthenticationError::Url)?;
|
let domain = url.host_str().ok_or(AuthenticationError::Url)?;
|
||||||
if !allowed_callback_urls.iter().any(|x| domain.ends_with(x))
|
if !ENV
|
||||||
|
.ALLOWED_CALLBACK_URLS
|
||||||
|
.iter()
|
||||||
|
.any(|x| domain.ends_with(x))
|
||||||
&& domain != "modrinth.com"
|
&& domain != "modrinth.com"
|
||||||
{
|
{
|
||||||
return Err(AuthenticationError::Url);
|
return Err(AuthenticationError::Url);
|
||||||
@@ -1396,9 +1394,9 @@ pub async fn delete_auth_provider(
|
|||||||
pub async fn check_sendy_subscription(
|
pub async fn check_sendy_subscription(
|
||||||
email: &str,
|
email: &str,
|
||||||
) -> Result<bool, AuthenticationError> {
|
) -> Result<bool, AuthenticationError> {
|
||||||
let url = dotenvy::var("SENDY_URL")?;
|
let url = &ENV.SENDY_URL;
|
||||||
let id = dotenvy::var("SENDY_LIST_ID")?;
|
let id = &ENV.SENDY_LIST_ID;
|
||||||
let api_key = dotenvy::var("SENDY_API_KEY")?;
|
let api_key = &ENV.SENDY_API_KEY;
|
||||||
|
|
||||||
if url.is_empty() || url == "none" {
|
if url.is_empty() || url == "none" {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
@@ -1408,9 +1406,9 @@ pub async fn check_sendy_subscription(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut form = HashMap::new();
|
let mut form = HashMap::new();
|
||||||
form.insert("api_key", &*api_key);
|
form.insert("api_key", api_key.as_str());
|
||||||
form.insert("email", email);
|
form.insert("email", email);
|
||||||
form.insert("list_id", &*id);
|
form.insert("list_id", id.as_str());
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let response = client
|
let response = client
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::{collections::HashMap, sync::LazyLock};
|
use std::{collections::HashMap, sync::LazyLock};
|
||||||
|
|
||||||
|
use crate::env::ENV;
|
||||||
use actix_web::{get, web};
|
use actix_web::{get, web};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -28,7 +29,8 @@ static GLOBALS: LazyLock<Globals> = LazyLock::new(|| Globals {
|
|||||||
tax_compliance_thresholds: [(2025, 600), (2026, 2000)]
|
tax_compliance_thresholds: [(2025, 600), (2026, 2000)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect(),
|
.collect(),
|
||||||
captcha_enabled: dotenvy::var("HCAPTCHA_SECRET").is_ok_and(|x| x != "none"),
|
captcha_enabled: !ENV.HCAPTCHA_SECRET.is_empty()
|
||||||
|
&& ENV.HCAPTCHA_SECRET != "none",
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Gets configured global non-secret variables for this backend instance.
|
/// Gets configured global non-secret variables for this backend instance.
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ use crate::database::models::session_item::DBSession;
|
|||||||
use crate::database::models::session_item::SessionBuilder;
|
use crate::database::models::session_item::SessionBuilder;
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::database::{PgPool, PgTransaction};
|
use crate::database::{PgPool, PgTransaction};
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::models::pats::Scopes;
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::sessions::Session;
|
use crate::models::sessions::Session;
|
||||||
use crate::queue::session::AuthQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::util::env::parse_var;
|
|
||||||
use actix_web::http::header::AUTHORIZATION;
|
use actix_web::http::header::AUTHORIZATION;
|
||||||
use actix_web::web::{Data, ServiceConfig, scope};
|
use actix_web::web::{Data, ServiceConfig, scope};
|
||||||
use actix_web::{HttpRequest, HttpResponse, delete, get, post, web};
|
use actix_web::{HttpRequest, HttpResponse, delete, get, post, web};
|
||||||
@@ -41,7 +41,7 @@ pub async fn get_session_metadata(
|
|||||||
req: &HttpRequest,
|
req: &HttpRequest,
|
||||||
) -> Result<SessionMetadata, AuthenticationError> {
|
) -> Result<SessionMetadata, AuthenticationError> {
|
||||||
let conn_info = req.connection_info().clone();
|
let conn_info = req.connection_info().clone();
|
||||||
let ip_addr = if parse_var("CLOUDFLARE_INTEGRATION").unwrap_or(false) {
|
let ip_addr = if ENV.CLOUDFLARE_INTEGRATION {
|
||||||
if let Some(header) = req.headers().get("CF-Connecting-IP") {
|
if let Some(header) = req.headers().get("CF-Connecting-IP") {
|
||||||
header.to_str().ok()
|
header.to_str().ok()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::database::models::DelphiReportIssueDetailsId;
|
use crate::database::models::DelphiReportIssueDetailsId;
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::file_hosting::FileHostingError;
|
use crate::file_hosting::FileHostingError;
|
||||||
use crate::routes::analytics::{page_view_ingest, playtime_ingest};
|
use crate::routes::analytics::{page_view_ingest, playtime_ingest};
|
||||||
use crate::util::cors::default_cors;
|
use crate::util::cors::default_cors;
|
||||||
use crate::util::env::parse_strings_from_var;
|
|
||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
use actix_files::Files;
|
use actix_files::Files;
|
||||||
use actix_web::http::StatusCode;
|
use actix_web::http::StatusCode;
|
||||||
@@ -40,10 +40,7 @@ pub fn root_config(cfg: &mut web::ServiceConfig) {
|
|||||||
.wrap(
|
.wrap(
|
||||||
Cors::default()
|
Cors::default()
|
||||||
.allowed_origin_fn(|origin, _req_head| {
|
.allowed_origin_fn(|origin, _req_head| {
|
||||||
let allowed_origins =
|
let allowed_origins = &ENV.ANALYTICS_ALLOWED_ORIGINS;
|
||||||
parse_strings_from_var("ANALYTICS_ALLOWED_ORIGINS")
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
allowed_origins.contains(&"*".to_string())
|
allowed_origins.contains(&"*".to_string())
|
||||||
|| allowed_origins.contains(
|
|| allowed_origins.contains(
|
||||||
&origin
|
&origin
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::database::PgPool;
|
use crate::database::PgPool;
|
||||||
|
use crate::env::ENV;
|
||||||
use actix_web::{HttpRequest, HttpResponse, get, web};
|
use actix_web::{HttpRequest, HttpResponse, get, web};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -94,11 +95,7 @@ pub async fn forge_updates(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut response = ForgeUpdates {
|
let mut response = ForgeUpdates {
|
||||||
homepage: format!(
|
homepage: format!("{}/mod/{}", ENV.SITE_URL, id),
|
||||||
"{}/mod/{}",
|
|
||||||
dotenvy::var("SITE_URL").unwrap_or_default(),
|
|
||||||
id
|
|
||||||
),
|
|
||||||
promos: HashMap::new(),
|
promos: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::database::PgPool;
|
|||||||
use crate::database::models::DBUserId;
|
use crate::database::models::DBUserId;
|
||||||
use crate::database::models::{generate_payout_id, users_compliance};
|
use crate::database::models::{generate_payout_id, users_compliance};
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::models::ids::PayoutId;
|
use crate::models::ids::PayoutId;
|
||||||
use crate::models::pats::Scopes;
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::payouts::{PayoutMethodType, PayoutStatus, Withdrawal};
|
use crate::models::payouts::{PayoutMethodType, PayoutStatus, Withdrawal};
|
||||||
@@ -212,7 +213,7 @@ pub async fn paypal_webhook(
|
|||||||
\"webhook_id\": \"{}\",
|
\"webhook_id\": \"{}\",
|
||||||
\"webhook_event\": {body}
|
\"webhook_event\": {body}
|
||||||
}}",
|
}}",
|
||||||
dotenvy::var("PAYPAL_WEBHOOK_ID")?
|
ENV.PAYPAL_WEBHOOK_ID,
|
||||||
)),
|
)),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
@@ -322,7 +323,7 @@ pub async fn tremendous_webhook(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut mac: Hmac<Sha256> = Hmac::new_from_slice(
|
let mut mac: Hmac<Sha256> = Hmac::new_from_slice(
|
||||||
dotenvy::var("TREMENDOUS_PRIVATE_KEY")?.as_bytes(),
|
ENV.TREMENDOUS_PRIVATE_KEY.as_bytes(),
|
||||||
)
|
)
|
||||||
.map_err(|_| ApiError::Payments("error initializing HMAC".to_string()))?;
|
.map_err(|_| ApiError::Payments("error initializing HMAC".to_string()))?;
|
||||||
mac.update(body.as_bytes());
|
mac.update(body.as_bytes());
|
||||||
@@ -1114,9 +1115,7 @@ async fn update_compliance_status(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn tax_compliance_payout_threshold() -> Option<Decimal> {
|
fn tax_compliance_payout_threshold() -> Option<Decimal> {
|
||||||
dotenvy::var("COMPLIANCE_PAYOUT_THRESHOLD")
|
ENV.COMPLIANCE_PAYOUT_THRESHOLD.parse().ok()
|
||||||
.ok()
|
|
||||||
.and_then(|s| s.parse().ok())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::database::models::{
|
|||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::database::{self, models as db_models};
|
use crate::database::{self, models as db_models};
|
||||||
use crate::database::{PgPool, PgTransaction};
|
use crate::database::{PgPool, PgTransaction};
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::file_hosting::{FileHost, FileHostPublicity};
|
use crate::file_hosting::{FileHost, FileHostPublicity};
|
||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::models::ids::{ProjectId, VersionId};
|
use crate::models::ids::{ProjectId, VersionId};
|
||||||
@@ -427,13 +428,13 @@ pub async fn project_edit(
|
|||||||
|
|
||||||
if status.is_searchable()
|
if status.is_searchable()
|
||||||
&& !project_item.inner.webhook_sent
|
&& !project_item.inner.webhook_sent
|
||||||
&& let Ok(webhook_url) = dotenvy::var("PUBLIC_DISCORD_WEBHOOK")
|
&& !ENV.PUBLIC_DISCORD_WEBHOOK.is_empty()
|
||||||
{
|
{
|
||||||
crate::util::webhook::send_discord_webhook(
|
crate::util::webhook::send_discord_webhook(
|
||||||
project_item.inner.id.into(),
|
project_item.inner.id.into(),
|
||||||
&pool,
|
&pool,
|
||||||
&redis,
|
&redis,
|
||||||
webhook_url,
|
&ENV.PUBLIC_DISCORD_WEBHOOK,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -451,18 +452,16 @@ pub async fn project_edit(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.role.is_mod()
|
if user.role.is_mod() && !ENV.MODERATION_SLACK_WEBHOOK.is_empty() {
|
||||||
&& let Ok(webhook_url) = dotenvy::var("MODERATION_SLACK_WEBHOOK")
|
|
||||||
{
|
|
||||||
crate::util::webhook::send_slack_project_webhook(
|
crate::util::webhook::send_slack_project_webhook(
|
||||||
project_item.inner.id.into(),
|
project_item.inner.id.into(),
|
||||||
&pool,
|
&pool,
|
||||||
&redis,
|
&redis,
|
||||||
webhook_url,
|
&ENV.MODERATION_SLACK_WEBHOOK,
|
||||||
Some(
|
Some(
|
||||||
format!(
|
format!(
|
||||||
"*<{}/user/{}|{}>* changed project status from *{}* to *{}*",
|
"*<{}/user/{}|{}>* changed project status from *{}* to *{}*",
|
||||||
dotenvy::var("SITE_URL")?,
|
ENV.SITE_URL,
|
||||||
user.username,
|
user.username,
|
||||||
user.username,
|
user.username,
|
||||||
&project_item.inner.status.as_friendly_str(),
|
&project_item.inner.status.as_friendly_str(),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::database::models::image_item;
|
|||||||
use crate::database::models::notification_item::NotificationBuilder;
|
use crate::database::models::notification_item::NotificationBuilder;
|
||||||
use crate::database::models::thread_item::ThreadMessageBuilder;
|
use crate::database::models::thread_item::ThreadMessageBuilder;
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::file_hosting::{FileHost, FileHostPublicity};
|
use crate::file_hosting::{FileHost, FileHostPublicity};
|
||||||
use crate::models::ids::{ThreadId, ThreadMessageId};
|
use crate::models::ids::{ThreadId, ThreadMessageId};
|
||||||
use crate::models::images::{Image, ImageContext};
|
use crate::models::images::{Image, ImageContext};
|
||||||
@@ -631,9 +632,8 @@ pub async fn message_delete(
|
|||||||
let images =
|
let images =
|
||||||
database::DBImage::get_many_contexted(context, &mut transaction)
|
database::DBImage::get_many_contexted(context, &mut transaction)
|
||||||
.await?;
|
.await?;
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
|
||||||
for image in images {
|
for image in images {
|
||||||
let name = image.url.split(&format!("{cdn_url}/")).nth(1);
|
let name = image.url.split(&format!("{}/", ENV.CDN_URL)).nth(1);
|
||||||
if let Some(icon_path) = name {
|
if let Some(icon_path) = name {
|
||||||
file_host
|
file_host
|
||||||
.delete_file(
|
.delete_file(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use crate::database::models::version_item::{
|
|||||||
};
|
};
|
||||||
use crate::database::models::{self, DBOrganization, image_item};
|
use crate::database::models::{self, DBOrganization, image_item};
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::file_hosting::{FileHost, FileHostPublicity};
|
use crate::file_hosting::{FileHost, FileHostPublicity};
|
||||||
use crate::models::ids::{ImageId, ProjectId, VersionId};
|
use crate::models::ids::{ImageId, ProjectId, VersionId};
|
||||||
use crate::models::images::{Image, ImageContext};
|
use crate::models::images::{Image, ImageContext};
|
||||||
@@ -974,7 +975,7 @@ pub async fn upload_file(
|
|||||||
|
|
||||||
version_files.push(VersionFileBuilder {
|
version_files.push(VersionFileBuilder {
|
||||||
filename: file_name.to_string(),
|
filename: file_name.to_string(),
|
||||||
url: format!("{}/{file_path_encode}", dotenvy::var("CDN_URL")?),
|
url: format!("{}/{file_path_encode}", ENV.CDN_URL),
|
||||||
hashes: vec![
|
hashes: vec![
|
||||||
models::version_item::HashBuilder {
|
models::version_item::HashBuilder {
|
||||||
algorithm: "sha1".to_string(),
|
algorithm: "sha1".to_string(),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use crate::database::PgPool;
|
use crate::database::PgPool;
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::search::{SearchConfig, UploadSearchProject};
|
use crate::search::{SearchConfig, UploadSearchProject};
|
||||||
use crate::util::error::Context;
|
use crate::util::error::Context;
|
||||||
use ariadne::ids::base62_impl::to_base62;
|
use ariadne::ids::base62_impl::to_base62;
|
||||||
@@ -43,12 +44,7 @@ pub enum IndexingError {
|
|||||||
const MEILISEARCH_CHUNK_SIZE: usize = 50000; // 10_000_000
|
const MEILISEARCH_CHUNK_SIZE: usize = 50000; // 10_000_000
|
||||||
|
|
||||||
fn search_operation_timeout() -> std::time::Duration {
|
fn search_operation_timeout() -> std::time::Duration {
|
||||||
let default_ms = 5 * 60 * 1000; // 5 minutes
|
std::time::Duration::from_millis(ENV.SEARCH_OPERATION_TIMEOUT)
|
||||||
let ms = dotenvy::var("SEARCH_OPERATION_TIMEOUT")
|
|
||||||
.ok()
|
|
||||||
.and_then(|v| v.parse::<u64>().ok())
|
|
||||||
.unwrap_or(default_ms);
|
|
||||||
std::time::Duration::from_millis(ms)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove_documents(
|
pub async fn remove_documents(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::env::ENV;
|
||||||
use crate::models::projects::SearchRequest;
|
use crate::models::projects::SearchRequest;
|
||||||
use crate::{models::error::ApiError, search::indexing::IndexingError};
|
use crate::{models::error::ApiError, search::indexing::IndexingError};
|
||||||
use actix_web::HttpResponse;
|
use actix_web::HttpResponse;
|
||||||
@@ -134,26 +135,11 @@ impl SearchConfig {
|
|||||||
// Panics if the environment variables are not set,
|
// Panics if the environment variables are not set,
|
||||||
// but these are already checked for on startup.
|
// but these are already checked for on startup.
|
||||||
pub fn new(meta_namespace: Option<String>) -> Self {
|
pub fn new(meta_namespace: Option<String>) -> Self {
|
||||||
let address_many = dotenvy::var("MEILISEARCH_WRITE_ADDRS")
|
|
||||||
.expect("MEILISEARCH_WRITE_ADDRS not set");
|
|
||||||
|
|
||||||
let read_lb_address = dotenvy::var("MEILISEARCH_READ_ADDR")
|
|
||||||
.expect("MEILISEARCH_READ_ADDR not set");
|
|
||||||
|
|
||||||
let addresses = address_many
|
|
||||||
.split(',')
|
|
||||||
.filter(|s| !s.trim().is_empty())
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
let key =
|
|
||||||
dotenvy::var("MEILISEARCH_KEY").expect("MEILISEARCH_KEY not set");
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
addresses,
|
addresses: ENV.MEILISEARCH_WRITE_ADDRS.0.clone(),
|
||||||
key,
|
key: ENV.MEILISEARCH_KEY.clone(),
|
||||||
meta_namespace: meta_namespace.unwrap_or_default(),
|
meta_namespace: meta_namespace.unwrap_or_default(),
|
||||||
read_lb_address,
|
read_lb_address: ENV.MEILISEARCH_READ_ADDR.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use super::{
|
|||||||
environment::LocalService,
|
environment::LocalService,
|
||||||
};
|
};
|
||||||
use crate::LabrinthConfig;
|
use crate::LabrinthConfig;
|
||||||
|
use crate::env::ENV;
|
||||||
use actix_web::{App, dev::ServiceResponse, test};
|
use actix_web::{App, dev::ServiceResponse, test};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -46,10 +47,7 @@ impl Api for ApiV2 {
|
|||||||
async fn reset_search_index(&self) -> ServiceResponse {
|
async fn reset_search_index(&self) -> ServiceResponse {
|
||||||
let req = actix_web::test::TestRequest::post()
|
let req = actix_web::test::TestRequest::post()
|
||||||
.uri("/v2/admin/_force_reindex")
|
.uri("/v2/admin/_force_reindex")
|
||||||
.append_header((
|
.append_header(("Modrinth-Admin", ENV.LABRINTH_ADMIN_KEY.clone()))
|
||||||
"Modrinth-Admin",
|
|
||||||
dotenvy::var("LABRINTH_ADMIN_KEY").unwrap(),
|
|
||||||
))
|
|
||||||
.to_request();
|
.to_request();
|
||||||
self.call(req).await
|
self.call(req).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use super::{
|
|||||||
environment::LocalService,
|
environment::LocalService,
|
||||||
};
|
};
|
||||||
use crate::LabrinthConfig;
|
use crate::LabrinthConfig;
|
||||||
|
use crate::env::ENV;
|
||||||
use actix_web::{App, dev::ServiceResponse, test};
|
use actix_web::{App, dev::ServiceResponse, test};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -51,10 +52,7 @@ impl Api for ApiV3 {
|
|||||||
async fn reset_search_index(&self) -> ServiceResponse {
|
async fn reset_search_index(&self) -> ServiceResponse {
|
||||||
let req = actix_web::test::TestRequest::post()
|
let req = actix_web::test::TestRequest::post()
|
||||||
.uri("/_internal/admin/_force_reindex")
|
.uri("/_internal/admin/_force_reindex")
|
||||||
.append_header((
|
.append_header(("Modrinth-Admin", ENV.LABRINTH_ADMIN_KEY.clone()))
|
||||||
"Modrinth-Admin",
|
|
||||||
dotenvy::var("LABRINTH_ADMIN_KEY").unwrap(),
|
|
||||||
))
|
|
||||||
.to_request();
|
.to_request();
|
||||||
self.call(req).await
|
self.call(req).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::database::PgPool;
|
use crate::database::PgPool;
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::database::{MIGRATOR, ReadOnlyPgPool};
|
use crate::database::{MIGRATOR, ReadOnlyPgPool};
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::search;
|
use crate::search;
|
||||||
use sqlx::postgres::PgPoolOptions;
|
use sqlx::postgres::PgPoolOptions;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -57,15 +58,14 @@ impl TemporaryDatabase {
|
|||||||
let temp_database_name = generate_random_name("labrinth_tests_db_");
|
let temp_database_name = generate_random_name("labrinth_tests_db_");
|
||||||
println!("Creating temporary database: {}", &temp_database_name);
|
println!("Creating temporary database: {}", &temp_database_name);
|
||||||
|
|
||||||
let database_url =
|
let database_url = &ENV.DATABASE_URL;
|
||||||
dotenvy::var("DATABASE_URL").expect("No database URL");
|
|
||||||
|
|
||||||
// Create the temporary (and template database, if needed)
|
// Create the temporary (and template database, if needed)
|
||||||
Self::create_temporary(&database_url, &temp_database_name).await;
|
Self::create_temporary(database_url, &temp_database_name).await;
|
||||||
|
|
||||||
// Pool to the temporary database
|
// Pool to the temporary database
|
||||||
let mut temporary_url =
|
let mut temporary_url =
|
||||||
Url::parse(&database_url).expect("Invalid database URL");
|
Url::parse(database_url).expect("Invalid database URL");
|
||||||
|
|
||||||
temporary_url.set_path(&format!("/{}", &temp_database_name));
|
temporary_url.set_path(&format!("/{}", &temp_database_name));
|
||||||
let temp_db_url = temporary_url.to_string();
|
let temp_db_url = temporary_url.to_string();
|
||||||
@@ -139,10 +139,8 @@ impl TemporaryDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Switch to template
|
// Switch to template
|
||||||
let url =
|
let mut template_url = Url::parse(&ENV.DATABASE_URL)
|
||||||
dotenvy::var("DATABASE_URL").expect("No database URL");
|
.expect("Invalid database URL");
|
||||||
let mut template_url =
|
|
||||||
Url::parse(&url).expect("Invalid database URL");
|
|
||||||
template_url.set_path(&format!("/{TEMPLATE_DATABASE_NAME}"));
|
template_url.set_path(&format!("/{TEMPLATE_DATABASE_NAME}"));
|
||||||
|
|
||||||
let pool = sqlx::PgPool::connect(template_url.as_str())
|
let pool = sqlx::PgPool::connect(template_url.as_str())
|
||||||
@@ -234,11 +232,10 @@ impl TemporaryDatabase {
|
|||||||
// If a temporary db is created, it must be cleaned up with cleanup.
|
// If a temporary db is created, it must be cleaned up with cleanup.
|
||||||
// This means that dbs will only 'remain' if a test fails (for examination of the db), and will be cleaned up otherwise.
|
// This means that dbs will only 'remain' if a test fails (for examination of the db), and will be cleaned up otherwise.
|
||||||
pub async fn cleanup(mut self) {
|
pub async fn cleanup(mut self) {
|
||||||
let database_url =
|
let database_url = &ENV.DATABASE_URL;
|
||||||
dotenvy::var("DATABASE_URL").expect("No database URL");
|
|
||||||
self.pool.close().await;
|
self.pool.close().await;
|
||||||
|
|
||||||
self.pool = sqlx::PgPool::connect(&database_url)
|
self.pool = sqlx::PgPool::connect(database_url)
|
||||||
.await
|
.await
|
||||||
.map(PgPool::from)
|
.map(PgPool::from)
|
||||||
.expect("Connection to main database failed");
|
.expect("Connection to main database failed");
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
use crate::env::ENV;
|
||||||
use crate::queue::email::EmailQueue;
|
use crate::queue::email::EmailQueue;
|
||||||
use crate::util::anrok;
|
use crate::util::anrok;
|
||||||
use crate::util::gotenberg::GotenbergClient;
|
use crate::util::gotenberg::GotenbergClient;
|
||||||
use crate::{LabrinthConfig, file_hosting};
|
use crate::{LabrinthConfig, file_hosting};
|
||||||
use crate::{check_env_vars, clickhouse};
|
use crate::{clickhouse, env};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub mod api_common;
|
pub mod api_common;
|
||||||
@@ -22,12 +23,8 @@ pub mod search;
|
|||||||
// If making a test, you should probably use environment::TestEnvironment::build() (which calls this)
|
// If making a test, you should probably use environment::TestEnvironment::build() (which calls this)
|
||||||
pub async fn setup(db: &database::TemporaryDatabase) -> LabrinthConfig {
|
pub async fn setup(db: &database::TemporaryDatabase) -> LabrinthConfig {
|
||||||
println!("Setting up labrinth config");
|
println!("Setting up labrinth config");
|
||||||
|
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
|
env::init().expect("failed to initialize environment variables");
|
||||||
if check_env_vars() {
|
|
||||||
println!("Some environment variables are missing!");
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||||
|
|
||||||
@@ -39,8 +36,7 @@ pub async fn setup(db: &database::TemporaryDatabase) -> LabrinthConfig {
|
|||||||
Arc::new(file_hosting::MockHost::new());
|
Arc::new(file_hosting::MockHost::new());
|
||||||
let mut clickhouse = clickhouse::init_client().await.unwrap();
|
let mut clickhouse = clickhouse::init_client().await.unwrap();
|
||||||
|
|
||||||
let stripe_client =
|
let stripe_client = stripe::Client::new(ENV.STRIPE_API_KEY.clone());
|
||||||
stripe::Client::new(dotenvy::var("STRIPE_API_KEY").unwrap());
|
|
||||||
|
|
||||||
let anrok_client = anrok::Client::from_env().unwrap();
|
let anrok_client = anrok::Client::from_env().unwrap();
|
||||||
let email_queue =
|
let email_queue =
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ use serde_with::{DisplayFromStr, serde_as};
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
|
use crate::env::ENV;
|
||||||
|
use crate::routes::ApiError;
|
||||||
|
|
||||||
pub fn transaction_id_stripe_pi(pi: &stripe::PaymentIntentId) -> String {
|
pub fn transaction_id_stripe_pi(pi: &stripe::PaymentIntentId) -> String {
|
||||||
format!("stripe:charge:{pi}")
|
format!("stripe:charge:{pi}")
|
||||||
}
|
}
|
||||||
@@ -154,19 +157,14 @@ pub struct Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub fn from_env() -> Result<Self, dotenvy::Error> {
|
pub fn from_env() -> Result<Self, ApiError> {
|
||||||
let api_key = dotenvy::var("ANROK_API_KEY")?;
|
|
||||||
let api_url = dotenvy::var("ANROK_API_URL")?
|
|
||||||
.trim_start_matches('/')
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
client: reqwest::Client::builder()
|
client: reqwest::Client::builder()
|
||||||
.user_agent("Modrinth")
|
.user_agent("Modrinth")
|
||||||
.build()
|
.build()
|
||||||
.expect("AnrokClient to build"),
|
.expect("AnrokClient to build"),
|
||||||
api_key,
|
api_key: ENV.ANROK_API_KEY.clone(),
|
||||||
api_url,
|
api_url: ENV.ANROK_API_URL.trim_start_matches('/').to_owned(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use reqwest::header::HeaderName;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
|
|
||||||
const X_MASTER_KEY: HeaderName = HeaderName::from_static("x-master-key");
|
const X_MASTER_KEY: HeaderName = HeaderName::from_static("x-master-key");
|
||||||
@@ -42,13 +43,12 @@ impl ArchonClient {
|
|||||||
pub fn from_env() -> Result<Self, ApiError> {
|
pub fn from_env() -> Result<Self, ApiError> {
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
let base_url =
|
let base_url = ENV.ARCHON_URL.trim_end_matches('/').to_owned();
|
||||||
dotenvy::var("ARCHON_URL")?.trim_end_matches('/').to_owned();
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
client,
|
client,
|
||||||
base_url,
|
base_url,
|
||||||
pyro_api_key: dotenvy::var("PYRO_API_KEY")?,
|
pyro_api_key: ENV.PYRO_API_KEY.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::database::models::{DBUserId, users_compliance::FormType};
|
use crate::database::models::{DBUserId, users_compliance::FormType};
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use ariadne::ids::base62_impl::to_base62;
|
use ariadne::ids::base62_impl::to_base62;
|
||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
@@ -131,10 +132,10 @@ fn team_request(
|
|||||||
method: reqwest::Method,
|
method: reqwest::Method,
|
||||||
route: &str,
|
route: &str,
|
||||||
) -> Result<(reqwest::RequestBuilder, String), ApiError> {
|
) -> Result<(reqwest::RequestBuilder, String), ApiError> {
|
||||||
let key = dotenvy::var("AVALARA_1099_API_KEY")?;
|
let key = &ENV.AVALARA_1099_API_KEY;
|
||||||
let url = dotenvy::var("AVALARA_1099_API_URL")?;
|
let url = &ENV.AVALARA_1099_API_URL;
|
||||||
let team = dotenvy::var("AVALARA_1099_API_TEAM_ID")?;
|
let team = &ENV.AVALARA_1099_API_TEAM_ID;
|
||||||
let company = dotenvy::var("AVALARA_1099_COMPANY_ID")?;
|
let company = &ENV.AVALARA_1099_COMPANY_ID;
|
||||||
|
|
||||||
let url = url.trim_end_matches('/');
|
let url = url.trim_end_matches('/');
|
||||||
|
|
||||||
@@ -144,8 +145,8 @@ fn team_request(
|
|||||||
client
|
client
|
||||||
.request(method, format!("{url}/v1/{team}{route}"))
|
.request(method, format!("{url}/v1/{team}{route}"))
|
||||||
.header(reqwest::header::USER_AGENT, "Modrinth")
|
.header(reqwest::header::USER_AGENT, "Modrinth")
|
||||||
.bearer_auth(&key),
|
.bearer_auth(key),
|
||||||
company,
|
company.to_string(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
use crate::env::ENV;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::util::env::parse_var;
|
|
||||||
use actix_web::HttpRequest;
|
use actix_web::HttpRequest;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -8,7 +8,7 @@ pub async fn check_hcaptcha(
|
|||||||
req: &HttpRequest,
|
req: &HttpRequest,
|
||||||
challenge: &str,
|
challenge: &str,
|
||||||
) -> Result<bool, ApiError> {
|
) -> Result<bool, ApiError> {
|
||||||
let secret = dotenvy::var("HCAPTCHA_SECRET")?;
|
let secret = &ENV.HCAPTCHA_SECRET;
|
||||||
|
|
||||||
if secret.is_empty() || secret == "none" {
|
if secret.is_empty() || secret == "none" {
|
||||||
tracing::info!("hCaptcha secret not set, skipping check");
|
tracing::info!("hCaptcha secret not set, skipping check");
|
||||||
@@ -16,7 +16,7 @@ pub async fn check_hcaptcha(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let conn_info = req.connection_info().clone();
|
let conn_info = req.connection_info().clone();
|
||||||
let ip_addr = if parse_var("CLOUDFLARE_INTEGRATION").unwrap_or(false) {
|
let ip_addr = if ENV.CLOUDFLARE_INTEGRATION {
|
||||||
if let Some(header) = req.headers().get("CF-Connecting-IP") {
|
if let Some(header) = req.headers().get("CF-Connecting-IP") {
|
||||||
header.to_str().ok()
|
header.to_str().ok()
|
||||||
} else {
|
} else {
|
||||||
@@ -38,7 +38,7 @@ pub async fn check_hcaptcha(
|
|||||||
let mut form = HashMap::new();
|
let mut form = HashMap::new();
|
||||||
|
|
||||||
form.insert("response", challenge);
|
form.insert("response", challenge);
|
||||||
form.insert("secret", &*secret);
|
form.insert("secret", secret);
|
||||||
form.insert("remoteip", ip_addr);
|
form.insert("remoteip", ip_addr);
|
||||||
|
|
||||||
let val: Response = client
|
let val: Response = client
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use eyre::{Context, eyre};
|
|
||||||
|
|
||||||
pub fn env_var(key: &str) -> eyre::Result<String> {
|
|
||||||
dotenvy::var(key)
|
|
||||||
.wrap_err_with(|| eyre!("missing environment variable `{key}`"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_var<T: FromStr>(var: &str) -> Option<T> {
|
|
||||||
dotenvy::var(var).ok().and_then(|i| i.parse().ok())
|
|
||||||
}
|
|
||||||
pub fn parse_strings_from_var(var: &'static str) -> Option<Vec<String>> {
|
|
||||||
dotenvy::var(var)
|
|
||||||
.ok()
|
|
||||||
.and_then(|s| serde_json::from_str::<Vec<String>>(&s).ok())
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::models::ids::PayoutId;
|
use crate::models::ids::PayoutId;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::routes::internal::gotenberg::{GotenbergDocument, GotenbergError};
|
use crate::routes::internal::gotenberg::{GotenbergDocument, GotenbergError};
|
||||||
use crate::util::env::env_var;
|
|
||||||
use crate::util::error::Context;
|
use crate::util::error::Context;
|
||||||
use actix_web::http::header::HeaderName;
|
use actix_web::http::header::HeaderName;
|
||||||
use chrono::{DateTime, Datelike, Utc};
|
use chrono::{DateTime, Datelike, Utc};
|
||||||
@@ -71,15 +71,14 @@ impl GotenbergClient {
|
|||||||
.build()
|
.build()
|
||||||
.wrap_err("failed to build reqwest client")?;
|
.wrap_err("failed to build reqwest client")?;
|
||||||
|
|
||||||
let gotenberg_url = env_var("GOTENBERG_URL")?;
|
|
||||||
let site_url = env_var("SITE_URL")?;
|
|
||||||
let callback_base = env_var("GOTENBERG_CALLBACK_BASE")?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
client,
|
client,
|
||||||
gotenberg_url: gotenberg_url.trim_end_matches('/').to_owned(),
|
gotenberg_url: ENV.GOTENBERG_URL.trim_end_matches('/').to_owned(),
|
||||||
site_url: site_url.trim_end_matches('/').to_owned(),
|
site_url: ENV.SITE_URL.trim_end_matches('/').to_owned(),
|
||||||
callback_base: callback_base.trim_end_matches('/').to_owned(),
|
callback_base: ENV
|
||||||
|
.GOTENBERG_CALLBACK_BASE
|
||||||
|
.trim_end_matches('/')
|
||||||
|
.to_owned(),
|
||||||
redis,
|
redis,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -189,12 +188,7 @@ impl GotenbergClient {
|
|||||||
|
|
||||||
self.generate_payment_statement(statement).await?;
|
self.generate_payment_statement(statement).await?;
|
||||||
|
|
||||||
let timeout_ms = env_var("GOTENBERG_TIMEOUT")
|
let timeout_ms = ENV.GOTENBERG_TIMEOUT;
|
||||||
.map_err(ApiError::Internal)?
|
|
||||||
.parse::<u64>()
|
|
||||||
.wrap_internal_err(
|
|
||||||
"`GOTENBERG_TIMEOUT` is not a valid number of milliseconds",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let [_key, document] = tokio::time::timeout(
|
let [_key, document] = tokio::time::timeout(
|
||||||
Duration::from_millis(timeout_ms),
|
Duration::from_millis(timeout_ms),
|
||||||
|
|||||||
@@ -1,47 +1,33 @@
|
|||||||
use actix_web::guard::GuardContext;
|
use actix_web::guard::GuardContext;
|
||||||
use actix_web::http::header::X_FORWARDED_FOR;
|
use actix_web::http::header::X_FORWARDED_FOR;
|
||||||
|
|
||||||
|
use crate::env::ENV;
|
||||||
|
|
||||||
pub const ADMIN_KEY_HEADER: &str = "Modrinth-Admin";
|
pub const ADMIN_KEY_HEADER: &str = "Modrinth-Admin";
|
||||||
pub const MEDAL_KEY_HEADER: &str = "X-Medal-Access-Key";
|
pub const MEDAL_KEY_HEADER: &str = "X-Medal-Access-Key";
|
||||||
pub const EXTERNAL_NOTIFICATION_KEY_HEADER: &str = "External-Notification-Key";
|
pub const EXTERNAL_NOTIFICATION_KEY_HEADER: &str = "External-Notification-Key";
|
||||||
|
|
||||||
pub fn admin_key_guard(ctx: &GuardContext) -> bool {
|
pub fn admin_key_guard(ctx: &GuardContext) -> bool {
|
||||||
let admin_key = std::env::var("LABRINTH_ADMIN_KEY").expect(
|
|
||||||
"No admin key provided, this should have been caught by check_env_vars",
|
|
||||||
);
|
|
||||||
ctx.head()
|
ctx.head()
|
||||||
.headers()
|
.headers()
|
||||||
.get(ADMIN_KEY_HEADER)
|
.get(ADMIN_KEY_HEADER)
|
||||||
.is_some_and(|it| it.as_bytes() == admin_key.as_bytes())
|
.is_some_and(|it| it.as_bytes() == ENV.LABRINTH_ADMIN_KEY.as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn medal_key_guard(ctx: &GuardContext) -> bool {
|
pub fn medal_key_guard(ctx: &GuardContext) -> bool {
|
||||||
let maybe_medal_key = dotenvy::var("LABRINTH_MEDAL_KEY").ok();
|
ctx.head()
|
||||||
|
.headers()
|
||||||
match maybe_medal_key {
|
.get(MEDAL_KEY_HEADER)
|
||||||
None => false,
|
.is_some_and(|it| it.as_bytes() == ENV.LABRINTH_MEDAL_KEY.as_bytes())
|
||||||
Some(medal_key) => ctx
|
|
||||||
.head()
|
|
||||||
.headers()
|
|
||||||
.get(MEDAL_KEY_HEADER)
|
|
||||||
.is_some_and(|it| it.as_bytes() == medal_key.as_bytes()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn external_notification_key_guard(ctx: &GuardContext) -> bool {
|
pub fn external_notification_key_guard(ctx: &GuardContext) -> bool {
|
||||||
let maybe_external_notification_key =
|
ctx.head()
|
||||||
dotenvy::var("LABRINTH_EXTERNAL_NOTIFICATION_KEY").ok();
|
.headers()
|
||||||
|
.get(EXTERNAL_NOTIFICATION_KEY_HEADER)
|
||||||
match maybe_external_notification_key {
|
.is_some_and(|it| {
|
||||||
None => false,
|
it.as_bytes() == ENV.LABRINTH_EXTERNAL_NOTIFICATION_KEY.as_bytes()
|
||||||
Some(external_notification_key) => ctx
|
})
|
||||||
.head()
|
|
||||||
.headers()
|
|
||||||
.get(EXTERNAL_NOTIFICATION_KEY_HEADER)
|
|
||||||
.is_some_and(|it| {
|
|
||||||
it.as_bytes() == external_notification_key.as_bytes()
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn internal_network_guard(ctx: &GuardContext) -> bool {
|
pub fn internal_network_guard(ctx: &GuardContext) -> bool {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::database::models::image_item;
|
use crate::database::models::image_item;
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::database::{self, PgTransaction};
|
use crate::database::{self, PgTransaction};
|
||||||
|
use crate::env::ENV;
|
||||||
use crate::file_hosting::{FileHost, FileHostPublicity};
|
use crate::file_hosting::{FileHost, FileHostPublicity};
|
||||||
use crate::models::images::ImageContext;
|
use crate::models::images::ImageContext;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
@@ -59,7 +60,7 @@ pub async fn upload_image_optimized(
|
|||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = &ENV.CDN_URL;
|
||||||
|
|
||||||
let hash = sha1::Sha1::digest(&bytes).encode_hex::<String>();
|
let hash = sha1::Sha1::digest(&bytes).encode_hex::<String>();
|
||||||
let (processed_image, processed_image_ext) = process_image(
|
let (processed_image, processed_image_ext) = process_image(
|
||||||
@@ -175,7 +176,7 @@ pub async fn delete_old_images(
|
|||||||
publicity: FileHostPublicity,
|
publicity: FileHostPublicity,
|
||||||
file_host: &dyn FileHost,
|
file_host: &dyn FileHost,
|
||||||
) -> Result<(), ApiError> {
|
) -> Result<(), ApiError> {
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = &ENV.CDN_URL;
|
||||||
let cdn_url_start = format!("{cdn_url}/");
|
let cdn_url_start = format!("{cdn_url}/");
|
||||||
if let Some(image_url) = image_url {
|
if let Some(image_url) = image_url {
|
||||||
let name = image_url.split(&cdn_url_start).nth(1);
|
let name = image_url.split(&cdn_url_start).nth(1);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ pub mod bitflag;
|
|||||||
pub mod captcha;
|
pub mod captcha;
|
||||||
pub mod cors;
|
pub mod cors;
|
||||||
pub mod date;
|
pub mod date;
|
||||||
pub mod env;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod ext;
|
pub mod ext;
|
||||||
pub mod gotenberg;
|
pub mod gotenberg;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::database::redis::RedisPool;
|
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::util::env::parse_var;
|
use crate::{database::redis::RedisPool, env::ENV};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
Error, ResponseError,
|
Error, ResponseError,
|
||||||
body::{EitherBody, MessageBody},
|
body::{EitherBody, MessageBody},
|
||||||
@@ -134,14 +133,13 @@ pub async fn rate_limit_middleware(
|
|||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if let Some(key) = req.headers().get("x-ratelimit-key")
|
if let Some(key) = req.headers().get("x-ratelimit-key")
|
||||||
&& key.to_str().ok()
|
&& key.to_str().ok() == Some(&ENV.RATE_LIMIT_IGNORE_KEY)
|
||||||
== dotenvy::var("RATE_LIMIT_IGNORE_KEY").ok().as_deref()
|
|
||||||
{
|
{
|
||||||
return Ok(next.call(req).await?.map_into_left_body());
|
return Ok(next.call(req).await?.map_into_left_body());
|
||||||
}
|
}
|
||||||
|
|
||||||
let conn_info = req.connection_info().clone();
|
let conn_info = req.connection_info().clone();
|
||||||
let ip = if parse_var("CLOUDFLARE_INTEGRATION").unwrap_or(false) {
|
let ip = if ENV.CLOUDFLARE_INTEGRATION {
|
||||||
if let Some(header) = req.headers().get("CF-Connecting-IP") {
|
if let Some(header) = req.headers().get("CF-Connecting-IP") {
|
||||||
header.to_str().ok()
|
header.to_str().ok()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::database::PgPool;
|
|
||||||
use crate::database::models::legacy_loader_fields::MinecraftGameVersion;
|
use crate::database::models::legacy_loader_fields::MinecraftGameVersion;
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::models::ids::ProjectId;
|
use crate::models::ids::ProjectId;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
|
use crate::{database::PgPool, env::ENV};
|
||||||
use ariadne::ids::base62_impl::to_base62;
|
use ariadne::ids::base62_impl::to_base62;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -69,7 +69,7 @@ async fn get_webhook_metadata(
|
|||||||
name: organization.name,
|
name: organization.name,
|
||||||
url: format!(
|
url: format!(
|
||||||
"{}/organization/{}",
|
"{}/organization/{}",
|
||||||
dotenvy::var("SITE_URL").unwrap_or_default(),
|
ENV.SITE_URL,
|
||||||
to_base62(organization.id.0 as u64)
|
to_base62(organization.id.0 as u64)
|
||||||
),
|
),
|
||||||
icon_url: organization.icon_url,
|
icon_url: organization.icon_url,
|
||||||
@@ -95,7 +95,7 @@ async fn get_webhook_metadata(
|
|||||||
owner = Some(WebhookAuthor {
|
owner = Some(WebhookAuthor {
|
||||||
url: format!(
|
url: format!(
|
||||||
"{}/user/{}",
|
"{}/user/{}",
|
||||||
dotenvy::var("SITE_URL").unwrap_or_default(),
|
ENV.SITE_URL,
|
||||||
to_base62(user.id.0 as u64)
|
to_base62(user.id.0 as u64)
|
||||||
),
|
),
|
||||||
name: user.username,
|
name: user.username,
|
||||||
@@ -142,7 +142,7 @@ async fn get_webhook_metadata(
|
|||||||
Ok(Some(WebhookMetadata {
|
Ok(Some(WebhookMetadata {
|
||||||
project_url: format!(
|
project_url: format!(
|
||||||
"{}/{}/{}",
|
"{}/{}/{}",
|
||||||
dotenvy::var("SITE_URL").unwrap_or_default(),
|
ENV.SITE_URL,
|
||||||
project_type,
|
project_type,
|
||||||
to_base62(project.inner.id.0 as u64)
|
to_base62(project.inner.id.0 as u64)
|
||||||
),
|
),
|
||||||
@@ -251,7 +251,7 @@ pub async fn send_slack_project_webhook(
|
|||||||
project_id: ProjectId,
|
project_id: ProjectId,
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
redis: &RedisPool,
|
redis: &RedisPool,
|
||||||
webhook_url: String,
|
webhook_url: &str,
|
||||||
message: Option<String>,
|
message: Option<String>,
|
||||||
) -> Result<(), ApiError> {
|
) -> Result<(), ApiError> {
|
||||||
let metadata = get_webhook_metadata(project_id, pool, redis).await?;
|
let metadata = get_webhook_metadata(project_id, pool, redis).await?;
|
||||||
@@ -350,7 +350,7 @@ pub async fn send_slack_project_webhook(
|
|||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
client
|
client
|
||||||
.post(&webhook_url)
|
.post(webhook_url)
|
||||||
.json(&serde_json::json!({
|
.json(&serde_json::json!({
|
||||||
"blocks": blocks,
|
"blocks": blocks,
|
||||||
}))
|
}))
|
||||||
@@ -422,7 +422,7 @@ pub async fn send_discord_webhook(
|
|||||||
project_id: ProjectId,
|
project_id: ProjectId,
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
redis: &RedisPool,
|
redis: &RedisPool,
|
||||||
webhook_url: String,
|
webhook_url: &str,
|
||||||
message: Option<String>,
|
message: Option<String>,
|
||||||
) -> Result<(), ApiError> {
|
) -> Result<(), ApiError> {
|
||||||
let metadata = get_webhook_metadata(project_id, pool, redis).await?;
|
let metadata = get_webhook_metadata(project_id, pool, redis).await?;
|
||||||
@@ -482,7 +482,7 @@ pub async fn send_discord_webhook(
|
|||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
client
|
client
|
||||||
.post(&webhook_url)
|
.post(webhook_url)
|
||||||
.json(&DiscordWebhook {
|
.json(&DiscordWebhook {
|
||||||
avatar_url: Some(
|
avatar_url: Some(
|
||||||
"https://cdn.modrinth.com/Modrinth_Dark_Logo.png"
|
"https://cdn.modrinth.com/Modrinth_Dark_Logo.png"
|
||||||
|
|||||||
Reference in New Issue
Block a user