You've already forked AstralRinth
forked from didirus/AstralRinth
* chore: fix typo in status message * feat(labrinth): overhaul malware scanner report storage and routes * chore: address some review comments * feat: add Delphi to Docker Compose `with-delphi` profile * chore: fix unused import Clippy lint * feat(labrinth/delphi): use PAT token authorization with project read scopes * chore: expose file IDs in version queries * fix: accept null decompiled source payloads from Delphi * tweak(labrinth): expose base62 file IDs more consistently for Delphi * feat(labrinth/delphi): support new Delphi report severity field * chore(labrinth): run `cargo sqlx prepare` to fix Docker build errors * tweak: add route for fetching Delphi issue type schema, abstract Labrinth away from issue types * chore: run `cargo sqlx prepare` * chore: fix typo on frontend generated state file message * feat: update to use new Delphi issue schema * wip: tech review endpoints * wip: add ToSchema for dependent types * wip: report issues return * wip * wip: returning more data * wip * Fix up db query * Delphi configuration to talk to Labrinth * Get Delphi working with Labrinth * Add Delphi dummy fixture * Better Delphi logging * Improve utoipa for tech review routes * Add more sorting options for tech review queue * Oops join * New routes for fetching issues and reports * Fix which kind of ID is returned in tech review endpoints * Deduplicate tech review report rows * Reduce info sent for projects * Fetch more thread info * Address PR comments * fix ci * fix postgres version mismatch * fix version creation * Implement routes * fix up tech review * Allow adding a moderation comment to Delphi rejections * fix up rebase * exclude rejected projects from tech review * add status change msg to tech review thread * cargo sqlx prepare * also ignore withheld projects * More filtering on issue search * wip: report routes * Fix up for build * cargo sqlx prepare * fix thread message privacy * New tech review search route * submit route * details have statuses now * add default to drid status * dedup issue details * fix sqlx query on empty files * fixes * Dedupe issue detail statuses and message on entering tech rev * Fix qa issues * Fix qa issues * fix review comments * typos * fix ci * feat: tech review frontend (#4781) * chore: fix typo in status message * feat(labrinth): overhaul malware scanner report storage and routes * chore: address some review comments * feat: add Delphi to Docker Compose `with-delphi` profile * chore: fix unused import Clippy lint * feat(labrinth/delphi): use PAT token authorization with project read scopes * chore: expose file IDs in version queries * fix: accept null decompiled source payloads from Delphi * tweak(labrinth): expose base62 file IDs more consistently for Delphi * feat(labrinth/delphi): support new Delphi report severity field * chore(labrinth): run `cargo sqlx prepare` to fix Docker build errors * tweak: add route for fetching Delphi issue type schema, abstract Labrinth away from issue types * chore: run `cargo sqlx prepare` * chore: fix typo on frontend generated state file message * feat: update to use new Delphi issue schema * wip: tech review endpoints * wip: add ToSchema for dependent types * wip: report issues return * wip * wip: returning more data * wip * Fix up db query * Delphi configuration to talk to Labrinth * Get Delphi working with Labrinth * Add Delphi dummy fixture * Better Delphi logging * Improve utoipa for tech review routes * Add more sorting options for tech review queue * Oops join * New routes for fetching issues and reports * Fix which kind of ID is returned in tech review endpoints * Deduplicate tech review report rows * Reduce info sent for projects * Fetch more thread info * Address PR comments * fix ci * fix ci * fix postgres version mismatch * fix version creation * Implement routes * feat: batch scan alert * feat: layout * feat: introduce surface variables * fix: theme selector * feat: rough draft of tech review card * feat: tab switcher * feat: batch scan btn * feat: api-client module for tech review * draft: impl * feat: auto icons * fix: layout issues * feat: fixes to code blocks + flag labels * feat: temp remove mock data * fix: search sort types * fix: intl & lint * chore: re-enable mock data * fix: flag badges + auto open first issue in file tab * feat: update for new routes * fix: more qa issues * feat: lazy load sources * fix: re-enable auth middleware * feat: impl threads * fix: lint & severity * feat: download btn + switch to using NavTabs with new local mode option * feat: re-add toplevel btns * feat: reports page consistency * fix: consistency on project queue * fix: icons + sizing * fix: colors and gaps * fix: impl endpoints * feat: load all flags on file tab * feat: thread generics changes * feat: more qa * feat: fix collapse * fix: qa * feat: msg modal * fix: ISO import * feat: qa fixes * fix: empty state basic * fix: collapsible region * fix: collapse thread by default * feat: rough draft of new process/flow * fix labrinth build * fix thread message privacy * New tech review search route * feat: qa fixes * feat: QA changes * fix: verdict on detail not whole issue * fix: lint + intl * fix: lint * fix: thread message for tech rev verdict * feat: use anim frames * fix: exports + typecheck * polish: qa changes * feat: qa * feat: qa polish * feat: fix malic modal * fix: lint * fix: qa + lint * fix: pagination * fix: lint * fix: qa * intl extract * fix ci --------- Signed-off-by: Calum H. <contact@cal.engineer> Co-authored-by: Alejandro González <me@alegon.dev> Co-authored-by: aecsocket <aecsocket@tutanota.com> --------- Signed-off-by: Calum H. <contact@cal.engineer> Co-authored-by: Alejandro González <me@alegon.dev> Co-authored-by: Calum H. <contact@cal.engineer>
290 lines
12 KiB
Rust
290 lines
12 KiB
Rust
use crate::database::models::DelphiReportIssueDetailsId;
|
|
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::{HttpResponse, web};
|
|
use futures::FutureExt;
|
|
use serde_json::json;
|
|
|
|
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/")),
|
|
);
|
|
}
|
|
|
|
/// Error when calling an HTTP endpoint.
|
|
#[derive(thiserror::Error, Debug)]
|
|
pub enum ApiError {
|
|
/// Error occurred on the server side, which the caller has no fault in.
|
|
#[error(transparent)]
|
|
Internal(eyre::Report),
|
|
/// Caller made an invalid or malformed request.
|
|
#[error(transparent)]
|
|
Request(eyre::Report),
|
|
/// Caller attempted a request which they are not allowed to make.
|
|
#[error(transparent)]
|
|
Auth(eyre::Report),
|
|
#[error("Invalid input: {0}")]
|
|
InvalidInput(String),
|
|
#[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("SQLx database error: {0}")]
|
|
SqlxDatabase(#[from] sqlx::Error),
|
|
#[error("Redis database error: {0}")]
|
|
RedisDatabase(#[from] redis::RedisError),
|
|
#[error("Clickhouse error: {0}")]
|
|
Clickhouse(#[from] clickhouse::error::Error),
|
|
#[error("XML 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("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("Slack webhook error: {0}")]
|
|
Slack(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("{0}")]
|
|
Mail(#[from] crate::queue::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("Conflict: {0}")]
|
|
Conflict(String),
|
|
#[error("External tax compliance API error")]
|
|
TaxComplianceApi,
|
|
#[error(transparent)]
|
|
TaxProcessor(#[from] crate::util::anrok::AnrokError),
|
|
#[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),
|
|
#[error("Error while interacting with Delphi: {0}")]
|
|
Delphi(reqwest::Error),
|
|
#[error(transparent)]
|
|
Mural(#[from] Box<muralpay::ApiError>),
|
|
#[error("report still has {} issue details with no verdict", details.len())]
|
|
TechReviewDetailsWithNoVerdict {
|
|
details: Vec<DelphiReportIssueDetailsId>,
|
|
},
|
|
}
|
|
|
|
impl ApiError {
|
|
pub fn as_api_error<'a>(&self) -> crate::models::error::ApiError<'a> {
|
|
crate::models::error::ApiError {
|
|
error: match self {
|
|
Self::Internal(..) => "internal_error",
|
|
Self::Request(..) => "request_error",
|
|
Self::Auth(..) => "auth_error",
|
|
Self::Env(..) => "environment_error",
|
|
Self::Database(..) => "database_error",
|
|
Self::SqlxDatabase(..) => "database_error",
|
|
Self::RedisDatabase(..) => "database_error",
|
|
Self::Authentication(..) => "unauthorized",
|
|
Self::CustomAuthentication(..) => "unauthorized",
|
|
Self::Xml(..) => "xml_error",
|
|
Self::Json(..) => "json_error",
|
|
Self::Search(..) => "search_error",
|
|
Self::Indexing(..) => "indexing_error",
|
|
Self::FileHosting(..) => "file_hosting_error",
|
|
Self::InvalidInput(..) => "invalid_input",
|
|
Self::Validation(..) => "invalid_input",
|
|
Self::Payments(..) => "payments_error",
|
|
Self::Discord(..) => "discord_error",
|
|
Self::Turnstile => "turnstile_error",
|
|
Self::Decoding(..) => "decoding_error",
|
|
Self::ImageParse(..) => "invalid_image",
|
|
Self::PasswordHashing(..) => "password_hashing_error",
|
|
Self::Mail(..) => "mail_error",
|
|
Self::Clickhouse(..) => "clickhouse_error",
|
|
Self::Reroute(..) => "reroute_error",
|
|
Self::NotFound => "not_found",
|
|
Self::Conflict(..) => "conflict",
|
|
Self::TaxComplianceApi => "tax_compliance_api_error",
|
|
Self::Zip(..) => "zip_error",
|
|
Self::Io(..) => "io_error",
|
|
Self::RateLimitError(..) => "ratelimit_error",
|
|
Self::Stripe(..) => "stripe_error",
|
|
Self::TaxProcessor(..) => "tax_processor_error",
|
|
Self::Slack(..) => "slack_error",
|
|
Self::Delphi(..) => "delphi_error",
|
|
Self::Mural(..) => "mural_error",
|
|
Self::TechReviewDetailsWithNoVerdict { .. } => {
|
|
"tech_review_issues_with_no_verdict"
|
|
}
|
|
},
|
|
description: match self {
|
|
Self::Internal(e) => format!("{e:#?}"),
|
|
Self::Request(e) => format!("{e:#?}"),
|
|
Self::Auth(e) => format!("{e:#?}"),
|
|
_ => self.to_string(),
|
|
},
|
|
details: match self {
|
|
Self::Mural(err) => serde_json::to_value(err.clone()).ok(),
|
|
Self::TechReviewDetailsWithNoVerdict { details } => {
|
|
let details = serde_json::to_value(details)
|
|
.expect("details should never fail to serialize");
|
|
Some(json!({
|
|
"issue_details": details
|
|
}))
|
|
}
|
|
_ => None,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl actix_web::ResponseError for ApiError {
|
|
fn status_code(&self) -> StatusCode {
|
|
match self {
|
|
Self::Internal(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::Request(..) => StatusCode::BAD_REQUEST,
|
|
Self::Auth(..) => StatusCode::UNAUTHORIZED,
|
|
Self::InvalidInput(..) => StatusCode::BAD_REQUEST,
|
|
Self::Env(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::Database(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::SqlxDatabase(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::RedisDatabase(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::Clickhouse(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::Authentication(..) => StatusCode::UNAUTHORIZED,
|
|
Self::CustomAuthentication(..) => StatusCode::UNAUTHORIZED,
|
|
Self::Xml(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::Json(..) => StatusCode::BAD_REQUEST,
|
|
Self::Search(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::Indexing(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::FileHosting(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::Validation(..) => StatusCode::BAD_REQUEST,
|
|
Self::Payments(..) => StatusCode::FAILED_DEPENDENCY,
|
|
Self::Discord(..) => StatusCode::FAILED_DEPENDENCY,
|
|
Self::Turnstile => StatusCode::BAD_REQUEST,
|
|
Self::Decoding(..) => StatusCode::BAD_REQUEST,
|
|
Self::ImageParse(..) => StatusCode::BAD_REQUEST,
|
|
Self::PasswordHashing(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::Mail(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::Reroute(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::NotFound => StatusCode::NOT_FOUND,
|
|
Self::Conflict(..) => StatusCode::CONFLICT,
|
|
Self::TaxComplianceApi => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::Zip(..) => StatusCode::BAD_REQUEST,
|
|
Self::Io(..) => StatusCode::BAD_REQUEST,
|
|
Self::RateLimitError(..) => StatusCode::TOO_MANY_REQUESTS,
|
|
Self::Stripe(..) => StatusCode::FAILED_DEPENDENCY,
|
|
Self::TaxProcessor(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::Slack(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::Delphi(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
Self::Mural(..) => StatusCode::BAD_REQUEST,
|
|
Self::TechReviewDetailsWithNoVerdict { .. } => {
|
|
StatusCode::BAD_REQUEST
|
|
}
|
|
}
|
|
}
|
|
|
|
fn error_response(&self) -> HttpResponse {
|
|
HttpResponse::build(self.status_code()).json(self.as_api_error())
|
|
}
|
|
}
|