Files
AstralRinth/apps/labrinth/src/routes/mod.rs
Alejandro González 8dd32bbe98 Fix macOS Theseus build issue, cleanup platform specific code (#3604)
* chore(theseus): significantly cleanup MacOS-specific code

* fix(labrinth): only use jemalloc allocator for Linux targets

The upstream crate asserts that its tests only pass for Linux targets,
and there's little point in supporting other OS for now since practical
Labrinth deployments run under a Linux environment anyway. This change
made it easier for me to cross-compile Labrinth.

* chore(theseus): tweak traffic lights pos according to c39bb78e38

As far as I understand it, that PR introduced the seemingly ad-hoc
additions of 6 and 12 units to the traffic light position calculations,
not directly modifying the `const` values introduced by
d6a72fbfc4.

* fix: re-enable app window shadows on Linux

* chore: log `window.set_shadow` errors

* chore: trigger CI
2025-05-05 19:38:10 +00:00

224 lines
9.1 KiB
Rust

use crate::file_hosting::FileHostingError;
use crate::routes::analytics::{page_view_ingest, playtime_ingest};
use crate::util::cors::default_cors;
use crate::util::env::parse_strings_from_var;
use actix_cors::Cors;
use actix_files::Files;
use actix_web::http::StatusCode;
use actix_web::{web, HttpResponse};
use futures::FutureExt;
pub mod internal;
pub mod v2;
pub mod v3;
#[cfg(target_os = "linux")]
pub mod debug;
pub mod v2_reroute;
mod analytics;
mod index;
mod maven;
mod not_found;
mod updates;
pub use self::not_found::not_found;
pub fn root_config(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("maven")
.wrap(default_cors())
.configure(maven::config),
);
cfg.service(
web::scope("updates")
.wrap(default_cors())
.configure(updates::config),
);
cfg.service(
web::scope("analytics")
.wrap(
Cors::default()
.allowed_origin_fn(|origin, _req_head| {
let allowed_origins =
parse_strings_from_var("ANALYTICS_ALLOWED_ORIGINS")
.unwrap_or_default();
allowed_origins.contains(&"*".to_string())
|| allowed_origins.contains(
&origin
.to_str()
.unwrap_or_default()
.to_string(),
)
})
.allowed_methods(vec!["GET", "POST"])
.allowed_headers(vec![
actix_web::http::header::AUTHORIZATION,
actix_web::http::header::ACCEPT,
actix_web::http::header::CONTENT_TYPE,
])
.max_age(3600),
)
.service(page_view_ingest)
.service(playtime_ingest),
);
cfg.service(
web::scope("api/v1")
.wrap(default_cors())
.wrap_fn(|req, _srv| {
async {
Ok(req.into_response(
HttpResponse::Gone()
.content_type("application/json")
.body(r#"{"error":"api_deprecated","description":"You are using an application that uses an outdated version of Modrinth's API. Please either update it or switch to another application. For developers: https://docs.modrinth.com/api/#versioning"}"#)
))
}.boxed_local()
})
);
cfg.service(
web::scope("")
.wrap(default_cors())
.service(index::index_get)
.service(Files::new("/", "assets/")),
);
}
#[derive(thiserror::Error, Debug)]
pub enum ApiError {
#[error("Environment Error")]
Env(#[from] dotenvy::Error),
#[error("Error while uploading file: {0}")]
FileHosting(#[from] FileHostingError),
#[error("Database Error: {0}")]
Database(#[from] crate::database::models::DatabaseError),
#[error("Database Error: {0}")]
SqlxDatabase(#[from] sqlx::Error),
#[error("Database Error: {0}")]
RedisDatabase(#[from] redis::RedisError),
#[error("Clickhouse Error: {0}")]
Clickhouse(#[from] clickhouse::error::Error),
#[error("Internal server error: {0}")]
Xml(String),
#[error("Deserialization error: {0}")]
Json(#[from] serde_json::Error),
#[error("Authentication Error: {0}")]
Authentication(#[from] crate::auth::AuthenticationError),
#[error("Authentication Error: {0}")]
CustomAuthentication(String),
#[error("Invalid Input: {0}")]
InvalidInput(String),
#[error("Error while validating input: {0}")]
Validation(String),
#[error("Search Error: {0}")]
Search(#[from] meilisearch_sdk::errors::Error),
#[error("Indexing Error: {0}")]
Indexing(#[from] crate::search::indexing::IndexingError),
#[error("Payments Error: {0}")]
Payments(String),
#[error("Discord Error: {0}")]
Discord(String),
#[error("Captcha Error. Try resubmitting the form.")]
Turnstile,
#[error("Error while decoding Base62: {0}")]
Decoding(#[from] ariadne::ids::DecodingError),
#[error("Image Parsing Error: {0}")]
ImageParse(#[from] image::ImageError),
#[error("Password Hashing Error: {0}")]
PasswordHashing(#[from] argon2::password_hash::Error),
#[error("Password strength checking error: {0}")]
PasswordStrengthCheck(#[from] zxcvbn::ZxcvbnError),
#[error("{0}")]
Mail(#[from] crate::auth::email::MailError),
#[error("Error while rerouting request: {0}")]
Reroute(#[from] reqwest::Error),
#[error("Unable to read Zip Archive: {0}")]
Zip(#[from] zip::result::ZipError),
#[error("IO Error: {0}")]
Io(#[from] std::io::Error),
#[error("Resource not found")]
NotFound,
#[error("You are being rate-limited. Please wait {0} milliseconds. 0/{1} remaining.")]
RateLimitError(u128, u32),
#[error("Error while interacting with payment processor: {0}")]
Stripe(#[from] stripe::StripeError),
}
impl ApiError {
pub fn as_api_error<'a>(&self) -> crate::models::error::ApiError<'a> {
crate::models::error::ApiError {
error: match self {
ApiError::Env(..) => "environment_error",
ApiError::Database(..) => "database_error",
ApiError::SqlxDatabase(..) => "database_error",
ApiError::RedisDatabase(..) => "database_error",
ApiError::Authentication(..) => "unauthorized",
ApiError::CustomAuthentication(..) => "unauthorized",
ApiError::Xml(..) => "xml_error",
ApiError::Json(..) => "json_error",
ApiError::Search(..) => "search_error",
ApiError::Indexing(..) => "indexing_error",
ApiError::FileHosting(..) => "file_hosting_error",
ApiError::InvalidInput(..) => "invalid_input",
ApiError::Validation(..) => "invalid_input",
ApiError::Payments(..) => "payments_error",
ApiError::Discord(..) => "discord_error",
ApiError::Turnstile => "turnstile_error",
ApiError::Decoding(..) => "decoding_error",
ApiError::ImageParse(..) => "invalid_image",
ApiError::PasswordHashing(..) => "password_hashing_error",
ApiError::PasswordStrengthCheck(..) => "strength_check_error",
ApiError::Mail(..) => "mail_error",
ApiError::Clickhouse(..) => "clickhouse_error",
ApiError::Reroute(..) => "reroute_error",
ApiError::NotFound => "not_found",
ApiError::Zip(..) => "zip_error",
ApiError::Io(..) => "io_error",
ApiError::RateLimitError(..) => "ratelimit_error",
ApiError::Stripe(..) => "stripe_error",
},
description: self.to_string(),
}
}
}
impl actix_web::ResponseError for ApiError {
fn status_code(&self) -> StatusCode {
match self {
ApiError::Env(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::Database(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::SqlxDatabase(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::RedisDatabase(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::Clickhouse(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::Authentication(..) => StatusCode::UNAUTHORIZED,
ApiError::CustomAuthentication(..) => StatusCode::UNAUTHORIZED,
ApiError::Xml(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::Json(..) => StatusCode::BAD_REQUEST,
ApiError::Search(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::Indexing(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::FileHosting(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::InvalidInput(..) => StatusCode::BAD_REQUEST,
ApiError::Validation(..) => StatusCode::BAD_REQUEST,
ApiError::Payments(..) => StatusCode::FAILED_DEPENDENCY,
ApiError::Discord(..) => StatusCode::FAILED_DEPENDENCY,
ApiError::Turnstile => StatusCode::BAD_REQUEST,
ApiError::Decoding(..) => StatusCode::BAD_REQUEST,
ApiError::ImageParse(..) => StatusCode::BAD_REQUEST,
ApiError::PasswordHashing(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::PasswordStrengthCheck(..) => StatusCode::BAD_REQUEST,
ApiError::Mail(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::Reroute(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::NotFound => StatusCode::NOT_FOUND,
ApiError::Zip(..) => StatusCode::BAD_REQUEST,
ApiError::Io(..) => StatusCode::BAD_REQUEST,
ApiError::RateLimitError(..) => StatusCode::TOO_MANY_REQUESTS,
ApiError::Stripe(..) => StatusCode::FAILED_DEPENDENCY,
}
}
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code()).json(self.as_api_error())
}
}