From 7c80b616669ae81ca5b891413c61cac0923e70cb Mon Sep 17 00:00:00 2001 From: ramidzkh Date: Sun, 20 Feb 2022 07:09:09 +1100 Subject: [PATCH] Automatically generate updates.json for Forge mods (#298) * Automatically generate updates.json for Forge mods https://api.modrinth.com/updates/{id}/forge_updates.json serves a minimal update JSON for the Forge update checker Closes #281 * Authenticate update JSON requests Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com> --- src/main.rs | 1 + src/models/projects.rs | 2 +- src/routes/mod.rs | 5 +++ src/routes/updates.rs | 76 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/routes/updates.rs diff --git a/src/main.rs b/src/main.rs index 5a5a42dd..b66f5994 100644 --- a/src/main.rs +++ b/src/main.rs @@ -283,6 +283,7 @@ async fn main() -> std::io::Result<()> { .service(routes::index_get) .service(routes::health_get) .service(web::scope("maven").configure(routes::maven_config)) + .service(web::scope("updates").configure(routes::updates)) .default_service(web::get().to(routes::not_found)) }) .bind(dotenv::var("BIND_ADDR").unwrap())? diff --git a/src/models/projects.rs b/src/models/projects.rs index 693eafbe..cbaf18ce 100644 --- a/src/models/projects.rs +++ b/src/models/projects.rs @@ -395,7 +395,7 @@ pub struct Dependency { pub dependency_type: DependencyType, } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Eq, PartialEq)] #[serde(rename_all = "lowercase")] pub enum VersionType { Release, diff --git a/src/routes/mod.rs b/src/routes/mod.rs index ff088478..047a78a5 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -13,6 +13,7 @@ mod projects; mod reports; mod tags; mod teams; +mod updates; mod users; mod version_creation; mod version_file; @@ -75,6 +76,10 @@ pub fn maven_config(cfg: &mut web::ServiceConfig) { cfg.service(maven::version_file); } +pub fn updates(cfg: &mut web::ServiceConfig) { + cfg.service(updates::forge_updates); +} + pub fn versions_config(cfg: &mut web::ServiceConfig) { cfg.service(versions::versions_get); cfg.service(version_creation::version_create); diff --git a/src/routes/updates.rs b/src/routes/updates.rs new file mode 100644 index 00000000..40b947b6 --- /dev/null +++ b/src/routes/updates.rs @@ -0,0 +1,76 @@ +use std::collections::HashMap; + +use actix_web::{get, web, HttpRequest, HttpResponse}; +use serde::Serialize; +use sqlx::PgPool; + +use crate::database; +use crate::models::projects::{Version, VersionType}; +use crate::util::auth::{get_user_from_headers, is_authorized}; + +use super::ApiError; + +#[get("{id}/forge_updates.json")] +pub async fn forge_updates( + req: HttpRequest, + info: web::Path<(String,)>, + pool: web::Data, +) -> Result { + const ERROR: &str = "The specified project does not exist!"; + + let (id,) = info.into_inner(); + + let project = database::models::Project::get_full_from_slug_or_project_id(&id, &**pool) + .await? + .ok_or_else(|| ApiError::InvalidInputError(ERROR.to_string()))?; + + let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); + + if !is_authorized(&project, &user_option, &pool).await? { + return Err(ApiError::InvalidInputError(ERROR.to_string())); + } + + let version_ids = database::models::Version::get_project_versions( + project.inner.id, + None, + Some(vec!["forge".to_string()]), + &**pool, + ) + .await?; + + let mut versions = database::models::Version::get_many_full(version_ids, &**pool).await?; + versions.sort_by(|a, b| b.date_published.cmp(&a.date_published)); + + #[derive(Serialize)] + struct ForgeUpdates { + homepage: String, + promos: HashMap, + } + + let mut response = ForgeUpdates { + homepage: format!("{}/mod/{}", dotenv::var("SITE_URL").unwrap_or_default(), id), + promos: HashMap::new(), + }; + + for version in versions { + let version = Version::from(version); + + if version.version_type == VersionType::Release { + for game_version in &version.game_versions { + response + .promos + .entry(format!("{}-recommended", game_version.0)) + .or_insert_with(|| version.version_number.clone()); + } + } + + for game_version in &version.game_versions { + response + .promos + .entry(format!("{}-latest", game_version.0)) + .or_insert_with(|| version.version_number.clone()); + } + } + + Ok(HttpResponse::Ok().json(response)) +}