From c7430dc5cf0b6f917c5333c6983b83a061315297 Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Fri, 22 Nov 2024 21:38:23 -0700 Subject: [PATCH 01/14] get one loader version --- ...41122212336_create_geode_versions.down.sql | 1 + ...0241122212336_create_geode_versions.up.sql | 24 +++ src/endpoints/loader.rs | 57 +++++++ src/endpoints/mod.rs | 1 + src/main.rs | 1 + src/types/models/loader_version.rs | 148 ++++++++++++++++++ src/types/models/mod.rs | 1 + 7 files changed, 233 insertions(+) create mode 100644 migrations/20241122212336_create_geode_versions.down.sql create mode 100644 migrations/20241122212336_create_geode_versions.up.sql create mode 100644 src/endpoints/loader.rs create mode 100644 src/types/models/loader_version.rs diff --git a/migrations/20241122212336_create_geode_versions.down.sql b/migrations/20241122212336_create_geode_versions.down.sql new file mode 100644 index 0000000..119f205 --- /dev/null +++ b/migrations/20241122212336_create_geode_versions.down.sql @@ -0,0 +1 @@ +DROP TABLE geode_versions; \ No newline at end of file diff --git a/migrations/20241122212336_create_geode_versions.up.sql b/migrations/20241122212336_create_geode_versions.up.sql new file mode 100644 index 0000000..fe0d004 --- /dev/null +++ b/migrations/20241122212336_create_geode_versions.up.sql @@ -0,0 +1,24 @@ +CREATE TABLE geode_versions ( + tag TEXT PRIMARY KEY NOT NULL, + mac gd_version, + win gd_version, + android gd_version, + prerelease BOOLEAN DEFAULT FALSE NOT NULL, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +-- prefill some versions for the android updater +INSERT INTO geode_versions + (tag, mac, win, android, created_at, prerelease) VALUES + ('v4.0.1', '2.2074', '2.2074', '2.2074', '2024-11-20T21:39:54Z', FALSE), + ('v4.0.0', '2.2074', '2.2074', '2.2074', '2024-11-19T14:40:29Z', FALSE), + ('v4.0.0-beta.2', '2.2074', '2.2074', '2.2074', '2024-11-19T03:37:04Z', TRUE), + ('v4.0.0-beta.1', '2.2074', '2.2074', '2.2074', '2024-11-15T20:15:17Z', TRUE), + ('v4.0.0-alpha.1', '2.2074', '2.2074', '2.2074', '2024-11-13T16:38:10Z', TRUE), + ('v3.9.3', '2.206', '2.206', '2.206', '2024-11-22T19:05:51Z', FALSE), + ('v3.9.2', '2.206', '2.206', '2.206', '2024-11-14T22:01:58Z', FALSE), + ('v3.9.1', '2.206', '2.206', '2.206', '2024-11-14T00:39:09Z', FALSE), + ('v3.9.0', '2.206', '2.206', '2.206', '2024-10-30T18:29:44Z', FALSE), + ('v2.0.0-beta.27', '2.200', '2.204', '2.205', '2024-05-26T14:37:03Z', FALSE), + ('v2.0.0-beta.4', '2.200', '2.204', '2.200', '2024-01-21T16:37:45Z', FALSE); + diff --git a/src/endpoints/loader.rs b/src/endpoints/loader.rs new file mode 100644 index 0000000..92797b7 --- /dev/null +++ b/src/endpoints/loader.rs @@ -0,0 +1,57 @@ +use std::str::FromStr; +use actix_web::{web, get, Responder}; +use serde::Deserialize; + +use crate::{ + types::{ + api::{ApiError, ApiResponse}, + models::{ + loader_version::LoaderVersion, + mod_gd_version::{GDVersionEnum, VerPlatform} + } + }, + AppData, +}; + +#[derive(Deserialize)] +struct GetOneQuery { + platform: Option, + gd: Option, + #[serde(default)] + prerelease: bool, +} + +#[derive(Deserialize)] +struct GetOnePath { + version: String, +} + +#[get("v1/loader/versions/{version}")] +pub async fn get_one( + path: web::Path, + data: web::Data, + query: web::Query +) -> Result { + let mut pool = data.db.acquire().await.or(Err(ApiError::DbAcquireError))?; + + let version = if path.version == "latest" { + let gd = query.gd.as_ref() + .map(|s| GDVersionEnum::from_str(s)) + .transpose() + .map_err(|_| ApiError::BadRequest("Invalid gd".to_string()))?; + + let platform = query.platform.as_ref() + .map(|s| VerPlatform::from_str(s)) + .transpose() + .map_err(|_| ApiError::BadRequest("Invalid platform".to_string()))?; + + LoaderVersion::get_latest(gd, platform, query.prerelease, &mut pool).await? + } else { + LoaderVersion::get_one(&path.version, &mut pool).await? + }; + + Ok(web::Json(ApiResponse { + error: "".to_string(), + payload: version, + })) +} diff --git a/src/endpoints/mod.rs b/src/endpoints/mod.rs index 8a59284..4df7080 100644 --- a/src/endpoints/mod.rs +++ b/src/endpoints/mod.rs @@ -4,3 +4,4 @@ pub mod mod_versions; pub mod mods; pub mod tags; pub mod stats; +pub mod loader; diff --git a/src/main.rs b/src/main.rs index 0b746bb..838238f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -126,6 +126,7 @@ async fn main() -> anyhow::Result<()> { .service(endpoints::tags::index) .service(endpoints::tags::detailed_index) .service(endpoints::stats::get_stats) + .service(endpoints::loader::get_one) .service(health) }) .bind((addr, port))?; diff --git a/src/types/models/loader_version.rs b/src/types/models/loader_version.rs new file mode 100644 index 0000000..0a6b278 --- /dev/null +++ b/src/types/models/loader_version.rs @@ -0,0 +1,148 @@ +use crate::types::{ + models::mod_gd_version::{GDVersionEnum, VerPlatform}, + api::ApiError, +}; + +use chrono::SecondsFormat; +use serde::Serialize; + +use sqlx::{ + types::chrono::{DateTime, Utc}, PgConnection, Postgres, QueryBuilder +}; + +#[derive(sqlx::FromRow, Serialize, Copy, Clone, Debug)] +pub struct LoaderGDVersion { + pub win: Option, + pub android: Option, + pub mac: Option, +} + +#[derive(Serialize, Clone, Debug)] +pub struct LoaderVersion { + pub tag: String, + pub gd: LoaderGDVersion, + pub prerelease: bool, + pub created_at: String, +} + +#[derive(sqlx::FromRow, Clone, Debug)] +pub struct LoaderVersionGetOne { + pub tag: String, + #[sqlx(flatten)] + pub gd: LoaderGDVersion, + pub prerelease: bool, + pub created_at: DateTime +} + +impl LoaderVersionGetOne { + pub fn into_loader_version(self) -> LoaderVersion { + LoaderVersion { + tag: self.tag, + prerelease: self.prerelease, + created_at: self.created_at.to_rfc3339_opts(SecondsFormat::Secs, true), + gd: self.gd + } + } +} + +impl LoaderVersion { + pub async fn get_latest( + gd: Option, + platform: Option, + accept_prereleases: bool, + pool: &mut PgConnection, + ) -> Result { + let mut query_builder: QueryBuilder = QueryBuilder::new( + r#"SELECT + mac, win, android, tag, created_at, prerelease + FROM geode_versions"# + ); + + match (platform, gd) { + (Some(p), Some(g)) => { + match p { + VerPlatform::Android | VerPlatform::Android32 | VerPlatform::Android64 => query_builder.push(" WHERE android="), + VerPlatform::Mac | VerPlatform::MacIntel | VerPlatform::MacArm => query_builder.push(" WHERE mac="), + VerPlatform::Win => query_builder.push(" WHERE win="), + _ => return Err(ApiError::BadRequest("Invalid platform".to_string())), + }; + + query_builder.push_bind(g); + } + (Some(_), None) => { + // this option will be handled later by ordering tricks + query_builder.push(" WHERE 1=1"); + } + (None, Some(g)) => { + query_builder.push(" WHERE android="); + query_builder.push_bind(g); + query_builder.push(" or mac="); + query_builder.push_bind(g); + query_builder.push(" or win="); + query_builder.push_bind(g); + } + (None, None) => { + // if gd version isn't specifed, select whatever versions have the latest gd version + query_builder.push( + r#" WHERE + android=enum_last(NULL::gd_version) OR + win=enum_last(NULL::gd_version) OR + mac=enum_last(NULL::gd_version) + "#); + } + } + + if !accept_prereleases { + query_builder.push(" AND prerelease=FALSE "); + } + + query_builder.push(" ORDER BY "); + + if gd.is_none() { + if let Some(p) = platform { + // if there's a platform but no gd, order by the latest gd for that platform + match p { + VerPlatform::Android | VerPlatform::Android32 | VerPlatform::Android64 => query_builder.push(" android"), + VerPlatform::Mac | VerPlatform::MacIntel | VerPlatform::MacArm => query_builder.push(" mac"), + VerPlatform::Win => query_builder.push(" win"), + _ => return Err(ApiError::BadRequest("Invalid platform".to_string())), + }; + query_builder.push(" DESC, "); + } + } + + query_builder.push(" created_at DESC LIMIT 1;"); + + match query_builder + .build_query_as::() + .fetch_optional(&mut *pool) + .await + { + Ok(Some(r)) => Ok(r.into_loader_version()), + Ok(None) => Err(ApiError::NotFound("".to_string())), + Err(e) => { + log::error!("{:?}", e); + Err(ApiError::DbError) + } + } + } + + pub async fn get_one(tag: &str, pool: &mut PgConnection) -> Result { + match sqlx::query_as::( + r#"SELECT + mac, win, android, tag, created_at, prerelease + FROM geode_versions + WHERE tag = $1"#) + .bind(tag) + .fetch_optional(&mut *pool) + .await + { + Ok(Some(r)) => Ok(r.into_loader_version()), + Ok(None) => Err(ApiError::NotFound("".to_string())), + Err(e) => { + log::error!("{:?}", e); + Err(ApiError::DbError) + } + } + } +} diff --git a/src/types/models/mod.rs b/src/types/models/mod.rs index 142755b..c038e28 100644 --- a/src/types/models/mod.rs +++ b/src/types/models/mod.rs @@ -10,3 +10,4 @@ pub mod mod_version; pub mod mod_version_status; pub mod stats; pub mod tag; +pub mod loader_version; From dda9df473754379cdecb522d3dc1b3eec0f24098 Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Fri, 22 Nov 2024 22:19:14 -0700 Subject: [PATCH 02/14] version creation --- src/endpoints/loader.rs | 37 ++++++++++++++++++++++++++-- src/main.rs | 1 + src/types/models/loader_version.rs | 39 +++++++++++++++++++++++++++--- 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/src/endpoints/loader.rs b/src/endpoints/loader.rs index 92797b7..353e65a 100644 --- a/src/endpoints/loader.rs +++ b/src/endpoints/loader.rs @@ -1,12 +1,15 @@ use std::str::FromStr; -use actix_web::{web, get, Responder}; +use actix_web::{web, get, post, Responder, HttpResponse}; use serde::Deserialize; +use sqlx::Acquire; + use crate::{ + extractors::auth::Auth, types::{ api::{ApiError, ApiResponse}, models::{ - loader_version::LoaderVersion, + loader_version::{LoaderVersion, LoaderVersionCreate}, mod_gd_version::{GDVersionEnum, VerPlatform} } }, @@ -55,3 +58,33 @@ pub async fn get_one( payload: version, })) } + +#[post("v1/loader/versions")] +pub async fn create_version( + data: web::Data, + payload: web::Json, + auth: Auth, +) -> Result { + let dev = auth.developer()?; + let mut pool = data.db.acquire().await.or(Err(ApiError::DbAcquireError))?; + + if !dev.admin { + return Err(ApiError::Forbidden); + } + + let mut transaction = pool.begin().await.or(Err(ApiError::TransactionError))?; + if let Err(e) = LoaderVersion::create_version(payload.into_inner(), &mut transaction).await { + transaction + .rollback() + .await + .or(Err(ApiError::TransactionError))?; + return Err(e); + } + + transaction + .commit() + .await + .or(Err(ApiError::TransactionError))?; + + Ok(HttpResponse::NoContent()) +} diff --git a/src/main.rs b/src/main.rs index 838238f..acd434f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -127,6 +127,7 @@ async fn main() -> anyhow::Result<()> { .service(endpoints::tags::detailed_index) .service(endpoints::stats::get_stats) .service(endpoints::loader::get_one) + .service(endpoints::loader::create_version) .service(health) }) .bind((addr, port))?; diff --git a/src/types/models/loader_version.rs b/src/types/models/loader_version.rs index 0a6b278..14408a4 100644 --- a/src/types/models/loader_version.rs +++ b/src/types/models/loader_version.rs @@ -4,20 +4,28 @@ use crate::types::{ }; use chrono::SecondsFormat; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use sqlx::{ types::chrono::{DateTime, Utc}, PgConnection, Postgres, QueryBuilder }; -#[derive(sqlx::FromRow, Serialize, Copy, Clone, Debug)] +#[derive(sqlx::FromRow, Deserialize, Serialize, Debug)] pub struct LoaderGDVersion { pub win: Option, pub android: Option, pub mac: Option, } -#[derive(Serialize, Clone, Debug)] +#[derive(Deserialize, Debug)] +pub struct LoaderVersionCreate { + pub tag: String, + pub gd: LoaderGDVersion, + #[serde(default)] + pub prerelease: bool +} + +#[derive(Serialize, Debug)] pub struct LoaderVersion { pub tag: String, pub gd: LoaderGDVersion, @@ -25,7 +33,7 @@ pub struct LoaderVersion { pub created_at: String, } -#[derive(sqlx::FromRow, Clone, Debug)] +#[derive(sqlx::FromRow, Debug)] pub struct LoaderVersionGetOne { pub tag: String, #[sqlx(flatten)] @@ -145,4 +153,27 @@ impl LoaderVersion { } } } + + pub async fn create_version(version: LoaderVersionCreate, pool: &mut PgConnection) -> Result<(), ApiError> { + match sqlx::query( + r#"INSERT INTO geode_versions + (tag, prerelease, mac, win, android) + VALUES + ($1, $2, $3, $4, $5)"#) + .bind(version.tag) + .bind(version.prerelease) + .bind(version.gd.mac) + .bind(version.gd.win) + .bind(version.gd.android) + .execute(&mut *pool) + .await + { + Ok(_) => Ok(()), + Err(e) => { + log::error!("{:?}", e); + Err(ApiError::DbError) + } + } + + } } From 225b933b7f341a58366a838000d1c3b71ca9de4f Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Fri, 22 Nov 2024 22:20:13 -0700 Subject: [PATCH 03/14] sure --- src/types/models/loader_version.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types/models/loader_version.rs b/src/types/models/loader_version.rs index 14408a4..d492965 100644 --- a/src/types/models/loader_version.rs +++ b/src/types/models/loader_version.rs @@ -174,6 +174,5 @@ impl LoaderVersion { Err(ApiError::DbError) } } - } } From b1e5c5dbad91a864be12a5ca180a8d93745eed52 Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Fri, 22 Nov 2024 23:41:12 -0700 Subject: [PATCH 04/14] version aliases --- ...1123052145_create_version_aliases.down.sql | 1 + ...241123052145_create_version_aliases.up.sql | 23 ++++ src/endpoints/loader.rs | 22 +++- src/types/models/gd_version_alias.rs | 116 ++++++++++++++++++ src/types/models/mod.rs | 1 + 5 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 migrations/20241123052145_create_version_aliases.down.sql create mode 100644 migrations/20241123052145_create_version_aliases.up.sql create mode 100644 src/types/models/gd_version_alias.rs diff --git a/migrations/20241123052145_create_version_aliases.down.sql b/migrations/20241123052145_create_version_aliases.down.sql new file mode 100644 index 0000000..beb2a86 --- /dev/null +++ b/migrations/20241123052145_create_version_aliases.down.sql @@ -0,0 +1 @@ +DROP TABLE gd_version_aliases; diff --git a/migrations/20241123052145_create_version_aliases.up.sql b/migrations/20241123052145_create_version_aliases.up.sql new file mode 100644 index 0000000..ab01d24 --- /dev/null +++ b/migrations/20241123052145_create_version_aliases.up.sql @@ -0,0 +1,23 @@ +CREATE TABLE gd_version_aliases ( + version_name gd_version PRIMARY KEY NOT NULL, + mac_arm_uuid uuid UNIQUE, + mac_intel_uuid uuid UNIQUE, + android_manifest_id INTEGER UNIQUE, + windows_timestamp INTEGER UNIQUE, + added_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +-- yet again, some default values, idk +INSERT INTO gd_version_aliases + (version_name, mac_arm_uuid, mac_intel_uuid, android_manifest_id, windows_timestamp) VALUES + -- not bothering with the mac uuids for most versions + -- it's not like they'll ever be used by the updater anyways + ('2.200', null, null, 37, 1702921605), + ('2.204', null, null, null, 1705041028), + ('2.205', null, null, 38, null), + ('2.206', null, null, 39, 1717243515), + ('2.207', null, null, null, 1731098609), + ('2.2071', null, null, null, 1731117052), + ('2.2072', null, null, null, 1731130219), + ('2.2073', null, null, null, 1731156923), + ('2.2074', '27044C8B-76BD-303C-A035-5314AF1D9E6E', 'DB5CADC0-E533-3123-8A63-5A434FE391ED', 40, 1731376950); diff --git a/src/endpoints/loader.rs b/src/endpoints/loader.rs index 353e65a..0f490e8 100644 --- a/src/endpoints/loader.rs +++ b/src/endpoints/loader.rs @@ -9,6 +9,7 @@ use crate::{ types::{ api::{ApiError, ApiResponse}, models::{ + gd_version_alias::GDVersionAlias, loader_version::{LoaderVersion, LoaderVersionCreate}, mod_gd_version::{GDVersionEnum, VerPlatform} } @@ -20,6 +21,7 @@ use crate::{ struct GetOneQuery { platform: Option, gd: Option, + identifier: Option, #[serde(default)] prerelease: bool, } @@ -38,16 +40,26 @@ pub async fn get_one( let mut pool = data.db.acquire().await.or(Err(ApiError::DbAcquireError))?; let version = if path.version == "latest" { - let gd = query.gd.as_ref() - .map(|s| GDVersionEnum::from_str(s)) - .transpose() - .map_err(|_| ApiError::BadRequest("Invalid gd".to_string()))?; - let platform = query.platform.as_ref() .map(|s| VerPlatform::from_str(s)) .transpose() .map_err(|_| ApiError::BadRequest("Invalid platform".to_string()))?; + // my mess + let gd = match (&query.gd, &query.identifier) { + (Some(_), Some(_)) => Err(ApiError::BadRequest("Fields identifier and gd are mutually exclusive".to_string()))?, + (Some(gd), None) => { + Some(GDVersionEnum::from_str(gd) + .map_err(|_| ApiError::BadRequest("Invalid gd".to_string()))?) + } + (None, Some(i)) => { + let platform = platform + .ok_or_else(|| ApiError::BadRequest("Field platform is required when a version identifier is provided".to_string()))?; + Some(GDVersionAlias::find(platform, i, &mut pool).await?) + }, + (None, None) => None + }; + LoaderVersion::get_latest(gd, platform, query.prerelease, &mut pool).await? } else { LoaderVersion::get_one(&path.version, &mut pool).await? diff --git a/src/types/models/gd_version_alias.rs b/src/types/models/gd_version_alias.rs new file mode 100644 index 0000000..292ca92 --- /dev/null +++ b/src/types/models/gd_version_alias.rs @@ -0,0 +1,116 @@ +use crate::types::{ + models::mod_gd_version::{GDVersionEnum, VerPlatform}, + api::ApiError, +}; + +use chrono::SecondsFormat; +use serde::Serialize; + +use sqlx::{ + types::{ + chrono::{DateTime, Utc}, + Uuid + }, PgConnection, Postgres, QueryBuilder +}; + +#[derive(Serialize)] +pub struct GDVersionAlias { + pub version_name: GDVersionEnum, + pub mac_arm_uuid: Option, + pub mac_intel_uuid: Option, + pub android_manifest_id: Option, + pub windows_timestamp: Option, + pub added_at: String +} + +#[derive(Debug)] +pub struct GDVersionAliasRecord { + pub version_name: GDVersionEnum, + pub mac_arm_uuid: Option, + pub mac_intel_uuid: Option, + pub android_manifest_id: Option, + pub windows_timestamp: Option, + pub added_at: DateTime +} + +impl GDVersionAliasRecord { + pub fn to_gd_version_alias(self) -> GDVersionAlias { + GDVersionAlias { + version_name: self.version_name, + mac_arm_uuid: self.mac_arm_uuid.map(|u| u.to_string()), + mac_intel_uuid: self.mac_intel_uuid.map(|u| u.to_string()), + android_manifest_id: self.android_manifest_id, + windows_timestamp: self.windows_timestamp, + added_at: self.added_at.to_rfc3339_opts(SecondsFormat::Secs, true), + } + } +} + +impl GDVersionAlias { + pub async fn find( + platform: VerPlatform, + identifier: &str, + pool: &mut PgConnection, + ) -> Result { + let mut query_builder: QueryBuilder = QueryBuilder::new( + r#"SELECT version_name FROM gd_version_aliases"# + ); + + match platform { + VerPlatform::Android | VerPlatform::Android32 | VerPlatform::Android64 => { + let manifest_id = identifier.parse::() + .map_err(|_| ApiError::BadRequest("Identifier is not a valid manifest id".to_string()))?; + + query_builder.push(" WHERE android_manifest_id="); + query_builder.push_bind(manifest_id); + }, + VerPlatform::Mac => { + let uuid = Uuid::parse_str(identifier) + .map_err(|_| ApiError::BadRequest("Identifier is not a valid UUID".to_string()))?; + + query_builder.push(" WHERE mac_arm_uuid="); + query_builder.push_bind(uuid); + query_builder.push(" OR mac_intel_uuid="); + query_builder.push_bind(uuid); + }, + VerPlatform::MacArm => { + let uuid = Uuid::parse_str(identifier) + .map_err(|_| ApiError::BadRequest("Identifier is not a valid UUID".to_string()))?; + + query_builder.push(" WHERE mac_arm_uuid="); + query_builder.push_bind(uuid); + }, + VerPlatform::MacIntel => { + let uuid = Uuid::parse_str(identifier) + .map_err(|_| ApiError::BadRequest("Identifier is not a valid UUID".to_string()))?; + + query_builder.push(" WHERE mac_intel_uuid="); + query_builder.push_bind(uuid); + }, + VerPlatform::Win => { + let timestamp = identifier.parse::() + .map_err(|_| ApiError::BadRequest("Identifier is not a valid timestamp".to_string()))?; + + query_builder.push(" WHERE windows_timestamp="); + query_builder.push_bind(timestamp); + }, + _ => return Err(ApiError::BadRequest("Invalid platform".to_string())), + }; + + // probably useless? + query_builder.push(" ORDER BY added_at DESC LIMIT 1"); + + match query_builder + .build_query_scalar::() + .fetch_optional(&mut *pool) + .await + { + Ok(Some(v)) => Ok(v), + Ok(None) => Err(ApiError::NotFound("".to_string())), + Err(e) => { + log::error!("{:?}", e); + Err(ApiError::DbError) + } + } + } +} diff --git a/src/types/models/mod.rs b/src/types/models/mod.rs index c038e28..1202dc5 100644 --- a/src/types/models/mod.rs +++ b/src/types/models/mod.rs @@ -11,3 +11,4 @@ pub mod mod_version_status; pub mod stats; pub mod tag; pub mod loader_version; +pub mod gd_version_alias; From 67b041c35e46571e0a873f43af88ebdff67e6ce4 Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Fri, 22 Nov 2024 23:43:21 -0700 Subject: [PATCH 05/14] huh --- .../20241122212336_create_geode_versions.up.sql | 12 ++++++------ .../20241123052145_create_version_aliases.up.sql | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/migrations/20241122212336_create_geode_versions.up.sql b/migrations/20241122212336_create_geode_versions.up.sql index fe0d004..6da9452 100644 --- a/migrations/20241122212336_create_geode_versions.up.sql +++ b/migrations/20241122212336_create_geode_versions.up.sql @@ -1,10 +1,10 @@ CREATE TABLE geode_versions ( - tag TEXT PRIMARY KEY NOT NULL, - mac gd_version, - win gd_version, - android gd_version, - prerelease BOOLEAN DEFAULT FALSE NOT NULL, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL + tag TEXT PRIMARY KEY NOT NULL, + mac gd_version, + win gd_version, + android gd_version, + prerelease BOOLEAN DEFAULT FALSE NOT NULL, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL ); -- prefill some versions for the android updater diff --git a/migrations/20241123052145_create_version_aliases.up.sql b/migrations/20241123052145_create_version_aliases.up.sql index ab01d24..de0616e 100644 --- a/migrations/20241123052145_create_version_aliases.up.sql +++ b/migrations/20241123052145_create_version_aliases.up.sql @@ -1,10 +1,10 @@ CREATE TABLE gd_version_aliases ( - version_name gd_version PRIMARY KEY NOT NULL, - mac_arm_uuid uuid UNIQUE, - mac_intel_uuid uuid UNIQUE, - android_manifest_id INTEGER UNIQUE, - windows_timestamp INTEGER UNIQUE, - added_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL + version_name gd_version PRIMARY KEY NOT NULL, + mac_arm_uuid uuid UNIQUE, + mac_intel_uuid uuid UNIQUE, + android_manifest_id INTEGER UNIQUE, + windows_timestamp INTEGER UNIQUE, + added_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL ); -- yet again, some default values, idk From 7e03d11dd5bc49ce37f7e795aa9e7492cded6fb2 Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Sat, 23 Nov 2024 02:55:58 -0700 Subject: [PATCH 06/14] unify identifier and version --- src/endpoints/loader.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/endpoints/loader.rs b/src/endpoints/loader.rs index 0f490e8..5e9565d 100644 --- a/src/endpoints/loader.rs +++ b/src/endpoints/loader.rs @@ -21,7 +21,6 @@ use crate::{ struct GetOneQuery { platform: Option, gd: Option, - identifier: Option, #[serde(default)] prerelease: bool, } @@ -45,19 +44,17 @@ pub async fn get_one( .transpose() .map_err(|_| ApiError::BadRequest("Invalid platform".to_string()))?; - // my mess - let gd = match (&query.gd, &query.identifier) { - (Some(_), Some(_)) => Err(ApiError::BadRequest("Fields identifier and gd are mutually exclusive".to_string()))?, - (Some(gd), None) => { - Some(GDVersionEnum::from_str(gd) - .map_err(|_| ApiError::BadRequest("Invalid gd".to_string()))?) - } - (None, Some(i)) => { + + let gd = if let Some(i) = &query.gd { + if let Ok(g) = GDVersionEnum::from_str(i) { + Some(g) + } else { let platform = platform - .ok_or_else(|| ApiError::BadRequest("Field platform is required when a version identifier is provided".to_string()))?; + .ok_or_else(|| ApiError::BadRequest("Platform is required when a version alias is given".to_string()))?; Some(GDVersionAlias::find(platform, i, &mut pool).await?) - }, - (None, None) => None + } + } else { + None }; LoaderVersion::get_latest(gd, platform, query.prerelease, &mut pool).await? From f9c89ae3922d9987d5342d7e0a82707e9e7a7226 Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Sat, 23 Nov 2024 05:14:07 -0700 Subject: [PATCH 07/14] impl get many for loader version --- src/endpoints/loader.rs | 68 +++++++++++++++++++----- src/main.rs | 1 + src/types/models/loader_version.rs | 84 ++++++++++++++++++++++++++++-- 3 files changed, 137 insertions(+), 16 deletions(-) diff --git a/src/endpoints/loader.rs b/src/endpoints/loader.rs index 5e9565d..bcd609c 100644 --- a/src/endpoints/loader.rs +++ b/src/endpoints/loader.rs @@ -10,7 +10,12 @@ use crate::{ api::{ApiError, ApiResponse}, models::{ gd_version_alias::GDVersionAlias, - loader_version::{LoaderVersion, LoaderVersionCreate}, + loader_version::{ + LoaderVersion, + LoaderVersionCreate, + LoaderGDVersion, + GetVersionsQuery + }, mod_gd_version::{GDVersionEnum, VerPlatform} } }, @@ -19,7 +24,7 @@ use crate::{ #[derive(Deserialize)] struct GetOneQuery { - platform: Option, + platform: Option, gd: Option, #[serde(default)] prerelease: bool, @@ -39,17 +44,11 @@ pub async fn get_one( let mut pool = data.db.acquire().await.or(Err(ApiError::DbAcquireError))?; let version = if path.version == "latest" { - let platform = query.platform.as_ref() - .map(|s| VerPlatform::from_str(s)) - .transpose() - .map_err(|_| ApiError::BadRequest("Invalid platform".to_string()))?; - - let gd = if let Some(i) = &query.gd { if let Ok(g) = GDVersionEnum::from_str(i) { Some(g) } else { - let platform = platform + let platform = query.platform .ok_or_else(|| ApiError::BadRequest("Platform is required when a version alias is given".to_string()))?; Some(GDVersionAlias::find(platform, i, &mut pool).await?) } @@ -57,7 +56,7 @@ pub async fn get_one( None }; - LoaderVersion::get_latest(gd, platform, query.prerelease, &mut pool).await? + LoaderVersion::get_latest(gd, query.platform, query.prerelease, &mut pool).await? } else { LoaderVersion::get_one(&path.version, &mut pool).await? }; @@ -68,10 +67,18 @@ pub async fn get_one( })) } +#[derive(Deserialize)] +struct CreateVersionBody { + pub tag: String, + pub gd: LoaderGDVersion, + #[serde(default)] + pub prerelease: bool +} + #[post("v1/loader/versions")] pub async fn create_version( data: web::Data, - payload: web::Json, + payload: web::Json, auth: Auth, ) -> Result { let dev = auth.developer()?; @@ -82,7 +89,11 @@ pub async fn create_version( } let mut transaction = pool.begin().await.or(Err(ApiError::TransactionError))?; - if let Err(e) = LoaderVersion::create_version(payload.into_inner(), &mut transaction).await { + if let Err(e) = LoaderVersion::create_version(LoaderVersionCreate { + tag: payload.tag.clone(), + gd: payload.gd.clone(), + prerelease: payload.prerelease + }, &mut transaction).await { transaction .rollback() .await @@ -97,3 +108,36 @@ pub async fn create_version( Ok(HttpResponse::NoContent()) } + +#[derive(Deserialize)] +struct GetManyQuery { + pub gd: Option, + pub platform: Option, + pub per_page: Option, + pub page: Option, + pub prerelease: Option +} + +#[get("v1/loader/versions")] +pub async fn get_many( + data: web::Data, + query: web::Query, +) -> Result { + let mut pool = data.db.acquire().await.or(Err(ApiError::DbAcquireError))?; + + let versions = LoaderVersion::get_many( + GetVersionsQuery { + gd: query.gd, + platform: query.platform, + prerelease: query.prerelease.unwrap_or(true) + }, + query.per_page.unwrap_or(10), + query.page.unwrap_or(1), + &mut pool + ).await?; + + Ok(web::Json(ApiResponse { + error: "".to_string(), + payload: versions, + })) +} diff --git a/src/main.rs b/src/main.rs index acd434f..1e38156 100644 --- a/src/main.rs +++ b/src/main.rs @@ -128,6 +128,7 @@ async fn main() -> anyhow::Result<()> { .service(endpoints::stats::get_stats) .service(endpoints::loader::get_one) .service(endpoints::loader::create_version) + .service(endpoints::loader::get_many) .service(health) }) .bind((addr, port))?; diff --git a/src/types/models/loader_version.rs b/src/types/models/loader_version.rs index d492965..69b201a 100644 --- a/src/types/models/loader_version.rs +++ b/src/types/models/loader_version.rs @@ -7,21 +7,21 @@ use chrono::SecondsFormat; use serde::{Deserialize, Serialize}; use sqlx::{ - types::chrono::{DateTime, Utc}, PgConnection, Postgres, QueryBuilder + types::chrono::{DateTime, Utc}, + PgConnection, Postgres, QueryBuilder }; -#[derive(sqlx::FromRow, Deserialize, Serialize, Debug)] +#[derive(sqlx::FromRow, Deserialize, Serialize, Debug, Clone)] pub struct LoaderGDVersion { pub win: Option, pub android: Option, pub mac: Option, } -#[derive(Deserialize, Debug)] +#[derive(Debug)] pub struct LoaderVersionCreate { pub tag: String, pub gd: LoaderGDVersion, - #[serde(default)] pub prerelease: bool } @@ -42,6 +42,12 @@ pub struct LoaderVersionGetOne { pub created_at: DateTime } +pub struct GetVersionsQuery { + pub gd: Option, + pub platform: Option, + pub prerelease: bool +} + impl LoaderVersionGetOne { pub fn into_loader_version(self) -> LoaderVersion { LoaderVersion { @@ -175,4 +181,74 @@ impl LoaderVersion { } } } + + pub async fn get_many( + query: GetVersionsQuery, + per_page: i64, + page: i64, + pool: &mut PgConnection + ) -> Result, ApiError> { + let limit = per_page; + let offset = (page - 1) * per_page; + + let mut query_builder = QueryBuilder::new(r#" + SELECT mac, win, android, tag, created_at, prerelease FROM geode_versions + "#); + + match (query.platform, query.gd) { + (Some(p), Some(g)) => { + match p { + VerPlatform::Android | VerPlatform::Android32 | VerPlatform::Android64 => query_builder.push(" WHERE android="), + VerPlatform::Mac | VerPlatform::MacIntel | VerPlatform::MacArm => query_builder.push(" WHERE mac="), + VerPlatform::Win => query_builder.push(" WHERE win="), + _ => return Err(ApiError::BadRequest("Invalid platform".to_string())), + }; + + query_builder.push_bind(g); + } + (Some(p), None) => { + match p { + VerPlatform::Android | VerPlatform::Android32 | VerPlatform::Android64 => query_builder.push(" WHERE android IS NOT NULL"), + VerPlatform::Mac | VerPlatform::MacIntel | VerPlatform::MacArm => query_builder.push(" WHERE mac IS NOT NULL"), + VerPlatform::Win => query_builder.push(" WHERE win IS NOT NULL"), + _ => return Err(ApiError::BadRequest("Invalid platform".to_string())), + }; + } + (None, Some(g)) => { + query_builder.push(" WHERE android="); + query_builder.push_bind(g); + query_builder.push(" or mac="); + query_builder.push_bind(g); + query_builder.push(" or win="); + query_builder.push_bind(g); + } + _ => { + query_builder.push(" WHERE 1=1"); + } + } + + if !query.prerelease { + query_builder.push(" AND prerelease=FALSE "); + } + + query_builder.push(" ORDER BY created_at DESC "); + + query_builder.push(" LIMIT "); + query_builder.push_bind(limit); + query_builder.push(" OFFSET "); + query_builder.push_bind(offset); + + match query_builder + .build_query_as::() + .fetch_all(&mut *pool) + .await + { + Ok(r) => + Ok(r.into_iter().map(|x| x.into_loader_version()).collect()), + Err(e) => { + log::error!("{:?}", e); + Err(ApiError::DbError) + } + } + } } From 857d65a4e2f2fd87072c0fdf2913572148a0595a Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:52:12 -0700 Subject: [PATCH 08/14] remove the v for consistency --- ...0241122212336_create_geode_versions.up.sql | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/migrations/20241122212336_create_geode_versions.up.sql b/migrations/20241122212336_create_geode_versions.up.sql index 6da9452..336be4d 100644 --- a/migrations/20241122212336_create_geode_versions.up.sql +++ b/migrations/20241122212336_create_geode_versions.up.sql @@ -10,15 +10,15 @@ CREATE TABLE geode_versions ( -- prefill some versions for the android updater INSERT INTO geode_versions (tag, mac, win, android, created_at, prerelease) VALUES - ('v4.0.1', '2.2074', '2.2074', '2.2074', '2024-11-20T21:39:54Z', FALSE), - ('v4.0.0', '2.2074', '2.2074', '2.2074', '2024-11-19T14:40:29Z', FALSE), - ('v4.0.0-beta.2', '2.2074', '2.2074', '2.2074', '2024-11-19T03:37:04Z', TRUE), - ('v4.0.0-beta.1', '2.2074', '2.2074', '2.2074', '2024-11-15T20:15:17Z', TRUE), - ('v4.0.0-alpha.1', '2.2074', '2.2074', '2.2074', '2024-11-13T16:38:10Z', TRUE), - ('v3.9.3', '2.206', '2.206', '2.206', '2024-11-22T19:05:51Z', FALSE), - ('v3.9.2', '2.206', '2.206', '2.206', '2024-11-14T22:01:58Z', FALSE), - ('v3.9.1', '2.206', '2.206', '2.206', '2024-11-14T00:39:09Z', FALSE), - ('v3.9.0', '2.206', '2.206', '2.206', '2024-10-30T18:29:44Z', FALSE), - ('v2.0.0-beta.27', '2.200', '2.204', '2.205', '2024-05-26T14:37:03Z', FALSE), - ('v2.0.0-beta.4', '2.200', '2.204', '2.200', '2024-01-21T16:37:45Z', FALSE); + ('4.0.1', '2.2074', '2.2074', '2.2074', '2024-11-20T21:39:54Z', FALSE), + ('4.0.0', '2.2074', '2.2074', '2.2074', '2024-11-19T14:40:29Z', FALSE), + ('4.0.0-beta.2', '2.2074', '2.2074', '2.2074', '2024-11-19T03:37:04Z', TRUE), + ('4.0.0-beta.1', '2.2074', '2.2074', '2.2074', '2024-11-15T20:15:17Z', TRUE), + ('4.0.0-alpha.1', '2.2074', '2.2074', '2.2074', '2024-11-13T16:38:10Z', TRUE), + ('3.9.3', '2.206', '2.206', '2.206', '2024-11-22T19:05:51Z', FALSE), + ('3.9.2', '2.206', '2.206', '2.206', '2024-11-14T22:01:58Z', FALSE), + ('3.9.1', '2.206', '2.206', '2.206', '2024-11-14T00:39:09Z', FALSE), + ('3.9.0', '2.206', '2.206', '2.206', '2024-10-30T18:29:44Z', FALSE), + ('2.0.0-beta.27', '2.200', '2.204', '2.205', '2024-05-26T14:37:03Z', FALSE), + ('2.0.0-beta.4', '2.200', '2.204', '2.200', '2024-01-21T16:37:45Z', FALSE); From 0aef3152752a1f40a9e1ae8a638d50da757387d6 Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Sat, 23 Nov 2024 14:48:39 -0700 Subject: [PATCH 09/14] cleanup structure --- ...105e4ad57f76f1795f847236e37381d703d86.json | 115 ++++++++++++++++++ ...0241122212336_create_geode_versions.up.sql | 25 ++-- src/endpoints/loader.rs | 22 ++-- src/types/models/gd_version_alias.rs | 2 +- src/types/models/loader_version.rs | 68 ++++++----- 5 files changed, 184 insertions(+), 48 deletions(-) create mode 100644 .sqlx/query-3fe4c27dc4b8a75d027ef79d0ca105e4ad57f76f1795f847236e37381d703d86.json diff --git a/.sqlx/query-3fe4c27dc4b8a75d027ef79d0ca105e4ad57f76f1795f847236e37381d703d86.json b/.sqlx/query-3fe4c27dc4b8a75d027ef79d0ca105e4ad57f76f1795f847236e37381d703d86.json new file mode 100644 index 0000000..c2063a8 --- /dev/null +++ b/.sqlx/query-3fe4c27dc4b8a75d027ef79d0ca105e4ad57f76f1795f847236e37381d703d86.json @@ -0,0 +1,115 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n\t\t\t\tmac as \"mac: _\", win as \"win: _\", android as \"android: _\",\n\t\t\t\ttag, created_at, commit_hash, prerelease\n\t\t\tFROM geode_versions\n\t\t\t\tWHERE tag = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "mac: _", + "type_info": { + "Custom": { + "name": "gd_version", + "kind": { + "Enum": [ + "*", + "2.113", + "2.200", + "2.204", + "2.205", + "2.206", + "2.207", + "2.2071", + "2.2072", + "2.2073", + "2.2074" + ] + } + } + } + }, + { + "ordinal": 1, + "name": "win: _", + "type_info": { + "Custom": { + "name": "gd_version", + "kind": { + "Enum": [ + "*", + "2.113", + "2.200", + "2.204", + "2.205", + "2.206", + "2.207", + "2.2071", + "2.2072", + "2.2073", + "2.2074" + ] + } + } + } + }, + { + "ordinal": 2, + "name": "android: _", + "type_info": { + "Custom": { + "name": "gd_version", + "kind": { + "Enum": [ + "*", + "2.113", + "2.200", + "2.204", + "2.205", + "2.206", + "2.207", + "2.2071", + "2.2072", + "2.2073", + "2.2074" + ] + } + } + } + }, + { + "ordinal": 3, + "name": "tag", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 5, + "name": "commit_hash", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "prerelease", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + true, + true, + true, + false, + false, + false, + false + ] + }, + "hash": "3fe4c27dc4b8a75d027ef79d0ca105e4ad57f76f1795f847236e37381d703d86" +} diff --git a/migrations/20241122212336_create_geode_versions.up.sql b/migrations/20241122212336_create_geode_versions.up.sql index 336be4d..763aeea 100644 --- a/migrations/20241122212336_create_geode_versions.up.sql +++ b/migrations/20241122212336_create_geode_versions.up.sql @@ -3,22 +3,23 @@ CREATE TABLE geode_versions ( mac gd_version, win gd_version, android gd_version, + commit_hash TEXT NOT NULL, prerelease BOOLEAN DEFAULT FALSE NOT NULL, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL ); -- prefill some versions for the android updater INSERT INTO geode_versions - (tag, mac, win, android, created_at, prerelease) VALUES - ('4.0.1', '2.2074', '2.2074', '2.2074', '2024-11-20T21:39:54Z', FALSE), - ('4.0.0', '2.2074', '2.2074', '2.2074', '2024-11-19T14:40:29Z', FALSE), - ('4.0.0-beta.2', '2.2074', '2.2074', '2.2074', '2024-11-19T03:37:04Z', TRUE), - ('4.0.0-beta.1', '2.2074', '2.2074', '2.2074', '2024-11-15T20:15:17Z', TRUE), - ('4.0.0-alpha.1', '2.2074', '2.2074', '2.2074', '2024-11-13T16:38:10Z', TRUE), - ('3.9.3', '2.206', '2.206', '2.206', '2024-11-22T19:05:51Z', FALSE), - ('3.9.2', '2.206', '2.206', '2.206', '2024-11-14T22:01:58Z', FALSE), - ('3.9.1', '2.206', '2.206', '2.206', '2024-11-14T00:39:09Z', FALSE), - ('3.9.0', '2.206', '2.206', '2.206', '2024-10-30T18:29:44Z', FALSE), - ('2.0.0-beta.27', '2.200', '2.204', '2.205', '2024-05-26T14:37:03Z', FALSE), - ('2.0.0-beta.4', '2.200', '2.204', '2.200', '2024-01-21T16:37:45Z', FALSE); + (tag, mac, win, android, created_at, commit_hash, prerelease) VALUES + ('4.0.1', '2.2074', '2.2074', '2.2074', '2024-11-20T21:39:54Z', 'ed97f3b0405a63f7edfa98df8d493a327e844111', FALSE), + ('4.0.0', '2.2074', '2.2074', '2.2074', '2024-11-19T14:40:29Z', 'cb8d7571ddcff608d3a4de8f59f1a969ec0aff39', FALSE), + ('4.0.0-beta.2', '2.2074', '2.2074', '2.2074', '2024-11-19T03:37:04Z', 'c0514b191583d6002dbf5c4f387471cff8fa535e', TRUE), + ('4.0.0-beta.1', '2.2074', '2.2074', '2.2074', '2024-11-15T20:15:17Z', '9fe3d133e93191d6225f2521b0714788293995c6', TRUE), + ('4.0.0-alpha.1', '2.2074', '2.2074', '2.2074', '2024-11-13T16:38:10Z', 'ebd4c920f5775287aea82fe758edec000108ff04', TRUE), + ('3.9.3', '2.206', '2.206', '2.206', '2024-11-22T19:05:51Z', 'e363a3e44c4a0af5b8980de4b06ed4c17e7b92ae', FALSE), + ('3.9.2', '2.206', '2.206', '2.206', '2024-11-14T22:01:58Z', '948e0d453dec9ea814974b17c68fde4006629611', FALSE), + ('3.9.1', '2.206', '2.206', '2.206', '2024-11-14T00:39:09Z', 'c4f6758ab42717816e121bcc656701fc7fd14395', FALSE), + ('3.9.0', '2.206', '2.206', '2.206', '2024-10-30T18:29:44Z', 'bd8387df1bc0bbba06a332c655e833121eddd9ff', FALSE), + ('2.0.0-beta.27', '2.200', '2.204', '2.205', '2024-05-26T14:37:03Z', '6510df7c8557668744044471ed2a0391759c3f7f', FALSE), + ('2.0.0-beta.4', '2.200', '2.204', '2.200', '2024-01-21T16:37:45Z', 'c2f626b93767ef3678b9df1daca14b89fec6c6f7', FALSE); diff --git a/src/endpoints/loader.rs b/src/endpoints/loader.rs index bcd609c..c98dc9a 100644 --- a/src/endpoints/loader.rs +++ b/src/endpoints/loader.rs @@ -13,7 +13,6 @@ use crate::{ loader_version::{ LoaderVersion, LoaderVersionCreate, - LoaderGDVersion, GetVersionsQuery }, mod_gd_version::{GDVersionEnum, VerPlatform} @@ -70,9 +69,15 @@ pub async fn get_one( #[derive(Deserialize)] struct CreateVersionBody { pub tag: String, - pub gd: LoaderGDVersion, #[serde(default)] - pub prerelease: bool + pub prerelease: bool, + pub commit_hash: String, + #[serde(default)] + pub win: Option, + #[serde(default)] + pub mac: Option, + #[serde(default)] + pub android: Option, } #[post("v1/loader/versions")] @@ -90,9 +95,12 @@ pub async fn create_version( let mut transaction = pool.begin().await.or(Err(ApiError::TransactionError))?; if let Err(e) = LoaderVersion::create_version(LoaderVersionCreate { - tag: payload.tag.clone(), - gd: payload.gd.clone(), - prerelease: payload.prerelease + tag: payload.tag.trim_start_matches('v').to_string(), + prerelease: payload.prerelease, + commit_hash: payload.commit_hash.clone(), + win: payload.win.clone(), + mac: payload.mac.clone(), + android: payload.android.clone(), }, &mut transaction).await { transaction .rollback() @@ -129,7 +137,7 @@ pub async fn get_many( GetVersionsQuery { gd: query.gd, platform: query.platform, - prerelease: query.prerelease.unwrap_or(true) + prerelease: query.prerelease.unwrap_or_default() }, query.per_page.unwrap_or(10), query.page.unwrap_or(1), diff --git a/src/types/models/gd_version_alias.rs b/src/types/models/gd_version_alias.rs index 292ca92..d9488bc 100644 --- a/src/types/models/gd_version_alias.rs +++ b/src/types/models/gd_version_alias.rs @@ -34,7 +34,7 @@ pub struct GDVersionAliasRecord { } impl GDVersionAliasRecord { - pub fn to_gd_version_alias(self) -> GDVersionAlias { + pub fn into_gd_version_alias(self) -> GDVersionAlias { GDVersionAlias { version_name: self.version_name, mac_arm_uuid: self.mac_arm_uuid.map(|u| u.to_string()), diff --git a/src/types/models/loader_version.rs b/src/types/models/loader_version.rs index 69b201a..561cc94 100644 --- a/src/types/models/loader_version.rs +++ b/src/types/models/loader_version.rs @@ -1,45 +1,45 @@ use crate::types::{ - models::mod_gd_version::{GDVersionEnum, VerPlatform}, + models::mod_gd_version::{GDVersionEnum, VerPlatform, DetailedGDVersion}, api::ApiError, }; use chrono::SecondsFormat; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use sqlx::{ types::chrono::{DateTime, Utc}, PgConnection, Postgres, QueryBuilder }; -#[derive(sqlx::FromRow, Deserialize, Serialize, Debug, Clone)] -pub struct LoaderGDVersion { - pub win: Option, - pub android: Option, - pub mac: Option, -} - #[derive(Debug)] pub struct LoaderVersionCreate { pub tag: String, - pub gd: LoaderGDVersion, - pub prerelease: bool + pub prerelease: bool, + pub commit_hash: String, + pub mac: Option, + pub win: Option, + pub android: Option, } #[derive(Serialize, Debug)] pub struct LoaderVersion { + pub version: String, pub tag: String, - pub gd: LoaderGDVersion, + pub gd: DetailedGDVersion, pub prerelease: bool, + pub commit_hash: String, pub created_at: String, } #[derive(sqlx::FromRow, Debug)] pub struct LoaderVersionGetOne { pub tag: String, - #[sqlx(flatten)] - pub gd: LoaderGDVersion, pub prerelease: bool, - pub created_at: DateTime + pub commit_hash: String, + pub created_at: DateTime, + pub mac: Option, + pub win: Option, + pub android: Option, } pub struct GetVersionsQuery { @@ -51,10 +51,21 @@ pub struct GetVersionsQuery { impl LoaderVersionGetOne { pub fn into_loader_version(self) -> LoaderVersion { LoaderVersion { - tag: self.tag, + tag: format!("v{}", self.tag), + version: self.tag, prerelease: self.prerelease, created_at: self.created_at.to_rfc3339_opts(SecondsFormat::Secs, true), - gd: self.gd + commit_hash: self.commit_hash, + gd: DetailedGDVersion { + win: self.win, + mac: self.mac, + mac_arm: self.mac, + mac_intel: self.mac, + android: self.android, + android32: self.android, + android64: self.android, + ios: None, + } } } } @@ -68,7 +79,7 @@ impl LoaderVersion { ) -> Result { let mut query_builder: QueryBuilder = QueryBuilder::new( r#"SELECT - mac, win, android, tag, created_at, prerelease + mac, win, android, tag, commit_hash, created_at, prerelease FROM geode_versions"# ); @@ -142,12 +153,12 @@ impl LoaderVersion { } pub async fn get_one(tag: &str, pool: &mut PgConnection) -> Result { - match sqlx::query_as::( + match sqlx::query_as!(LoaderVersionGetOne, r#"SELECT - mac, win, android, tag, created_at, prerelease + mac as "mac: _", win as "win: _", android as "android: _", + tag, created_at, commit_hash, prerelease FROM geode_versions - WHERE tag = $1"#) - .bind(tag) + WHERE tag = $1"#, tag) .fetch_optional(&mut *pool) .await { @@ -163,14 +174,15 @@ impl LoaderVersion { pub async fn create_version(version: LoaderVersionCreate, pool: &mut PgConnection) -> Result<(), ApiError> { match sqlx::query( r#"INSERT INTO geode_versions - (tag, prerelease, mac, win, android) + (tag, prerelease, mac, win, android, commit_hash) VALUES - ($1, $2, $3, $4, $5)"#) + ($1, $2, $3, $4, $5, $6)"#) .bind(version.tag) .bind(version.prerelease) - .bind(version.gd.mac) - .bind(version.gd.win) - .bind(version.gd.android) + .bind(version.mac) + .bind(version.win) + .bind(version.android) + .bind(version.commit_hash) .execute(&mut *pool) .await { @@ -192,7 +204,7 @@ impl LoaderVersion { let offset = (page - 1) * per_page; let mut query_builder = QueryBuilder::new(r#" - SELECT mac, win, android, tag, created_at, prerelease FROM geode_versions + SELECT mac, win, android, tag, created_at, commit_hash, prerelease FROM geode_versions "#); match (query.platform, query.gd) { From 5d6c54ec5e8f6b20c02e48aef5b656e119db79be Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Sat, 23 Nov 2024 14:48:44 -0700 Subject: [PATCH 10/14] document it --- openapi.yml | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) diff --git a/openapi.yml b/openapi.yml index 0145531..a24e316 100644 --- a/openapi.yml +++ b/openapi.yml @@ -14,6 +14,9 @@ tags: - name: user description: User management + - name: loader + description: Loader information + paths: /: get: @@ -628,6 +631,160 @@ paths: "500": $ref: "#/components/responses/InternalServerError" + /v1/loader/versions: + get: + tags: + - loader + summary: Get all loader versions, paginated + description: Returns a paginated list of all loader versions. This list is currently sorted by when the mods were added, not by their versions. + + parameters: + - name: gd + in: query + description: Geometry Dash version + required: false + schema: + $ref: "#/components/schemas/GDVersionString" + - name: platform + in: query + description: Platform to filter version by [win, android, mac] + required: false + schema: + $ref: "#/components/schemas/Platform" + - name: prerelease + in: query + description: If prerelease builds should be included in results + required: false + schema: + type: boolean + - $ref: "#/components/parameters/Page" + - $ref: "#/components/parameters/PerPage" + + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/LoaderVersion" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" + post: + tags: + - loader + summary: Create a new loader version (admin only) + security: + - bearerAuth: [] + + requestBody: + content: + application/json: + schema: + type: object + properties: + tag: + required: true + type: string + description: "Git tag that references the release" + commit_hash: + required: true + type: string + prerelease: + required: false + type: boolean + win: + required: true + $ref: "#/components/schemas/GDVersionString" + mac: + required: true + $ref: "#/components/schemas/GDVersionString" + android: + required: true + $ref: "#/components/schemas/GDVersionString" + + responses: + "204": + description: No Content (Version created) + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/Unauthorized" + "500": + $ref: "#/components/responses/InternalServerError" + + /v1/loader/versions/latest: + get: + tags: + - loader + summary: Gets the latest loader version + description: Returns the latest loader version, filtered based on the given parameters + + parameters: + - name: gd + in: query + description: Geometry Dash version + required: false + schema: + $ref: "#/components/schemas/GDVersionString" + - name: platform + in: query + description: Platform to filter version by [win, android, mac] + required: false + schema: + $ref: "#/components/schemas/Platform" + - name: prerelease + in: query + description: If prerelease builds should be accepted + required: false + schema: + type: boolean + + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/LoaderVersion" + "404": + $ref: "#/components/responses/NotFoundError" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" + + /v1/loader/versions/{version}: + get: + tags: + - loader + summary: Gets a loader version + + parameters: + - name: version + description: Geode Version + in: path + required: true + schema: + $ref: "#/components/schemas/ModVersionString" + + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/LoaderVersion" + "404": + $ref: "#/components/responses/NotFoundError" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" + components: securitySchemes: bearerAuth: @@ -965,6 +1122,23 @@ components: nullable: true description: Information given with the version's status (typically a reason) + LoaderVersion: + type: object + properties: + tag: + type: string + commit_hash: + type: string + version: + $ref: "#/components/schemas/ModVersionString" + prerelease: + type: boolean + gd: + $ref: "#/components/schemas/GDVersionObject" + created_at: + type: string + format: date-time + Platform: type: string enum: From 0fd0cc8ad57ffb2614bd6adcd7f134ba858111e1 Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:02:56 -0700 Subject: [PATCH 11/14] simplification --- openapi.yml | 11 ++--------- src/endpoints/loader.rs | 23 ++++++++++------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/openapi.yml b/openapi.yml index a24e316..8cd959e 100644 --- a/openapi.yml +++ b/openapi.yml @@ -696,15 +696,8 @@ paths: prerelease: required: false type: boolean - win: - required: true - $ref: "#/components/schemas/GDVersionString" - mac: - required: true - $ref: "#/components/schemas/GDVersionString" - android: - required: true - $ref: "#/components/schemas/GDVersionString" + gd: + $ref: "#/components/schemas/GDVersionObject" responses: "204": diff --git a/src/endpoints/loader.rs b/src/endpoints/loader.rs index c98dc9a..de9b8ff 100644 --- a/src/endpoints/loader.rs +++ b/src/endpoints/loader.rs @@ -11,11 +11,13 @@ use crate::{ models::{ gd_version_alias::GDVersionAlias, loader_version::{ - LoaderVersion, - LoaderVersionCreate, - GetVersionsQuery + GetVersionsQuery, LoaderVersion, LoaderVersionCreate }, - mod_gd_version::{GDVersionEnum, VerPlatform} + mod_gd_version::{ + DetailedGDVersion, + GDVersionEnum, + VerPlatform, + } } }, AppData, @@ -72,12 +74,7 @@ struct CreateVersionBody { #[serde(default)] pub prerelease: bool, pub commit_hash: String, - #[serde(default)] - pub win: Option, - #[serde(default)] - pub mac: Option, - #[serde(default)] - pub android: Option, + pub gd: DetailedGDVersion, } #[post("v1/loader/versions")] @@ -98,9 +95,9 @@ pub async fn create_version( tag: payload.tag.trim_start_matches('v').to_string(), prerelease: payload.prerelease, commit_hash: payload.commit_hash.clone(), - win: payload.win.clone(), - mac: payload.mac.clone(), - android: payload.android.clone(), + win: payload.gd.win.clone(), + mac: payload.gd.mac.clone(), + android: payload.gd.android.clone(), }, &mut transaction).await { transaction .rollback() From 04beb1200c0ed3ea392be9215d5385ffdb68f1fa Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Sun, 24 Nov 2024 10:22:22 -0700 Subject: [PATCH 12/14] fix docs typo --- openapi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi.yml b/openapi.yml index 8cd959e..5d267bd 100644 --- a/openapi.yml +++ b/openapi.yml @@ -636,7 +636,7 @@ paths: tags: - loader summary: Get all loader versions, paginated - description: Returns a paginated list of all loader versions. This list is currently sorted by when the mods were added, not by their versions. + description: Returns a paginated list of all loader versions. This list is sorted by when the version was added, not by their version number. parameters: - name: gd From b71940a7d5650984af515fa24a58b75db4af4df9 Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Sun, 24 Nov 2024 10:23:22 -0700 Subject: [PATCH 13/14] add indexes for timestamp columns --- migrations/20241122212336_create_geode_versions.up.sql | 1 + migrations/20241123052145_create_version_aliases.up.sql | 2 ++ 2 files changed, 3 insertions(+) diff --git a/migrations/20241122212336_create_geode_versions.up.sql b/migrations/20241122212336_create_geode_versions.up.sql index 763aeea..7ea7fac 100644 --- a/migrations/20241122212336_create_geode_versions.up.sql +++ b/migrations/20241122212336_create_geode_versions.up.sql @@ -23,3 +23,4 @@ INSERT INTO geode_versions ('2.0.0-beta.27', '2.200', '2.204', '2.205', '2024-05-26T14:37:03Z', '6510df7c8557668744044471ed2a0391759c3f7f', FALSE), ('2.0.0-beta.4', '2.200', '2.204', '2.200', '2024-01-21T16:37:45Z', 'c2f626b93767ef3678b9df1daca14b89fec6c6f7', FALSE); +CREATE INDEX idx_geode_versions_created_at ON geode_versions(created_at); diff --git a/migrations/20241123052145_create_version_aliases.up.sql b/migrations/20241123052145_create_version_aliases.up.sql index de0616e..537c7ec 100644 --- a/migrations/20241123052145_create_version_aliases.up.sql +++ b/migrations/20241123052145_create_version_aliases.up.sql @@ -21,3 +21,5 @@ INSERT INTO gd_version_aliases ('2.2072', null, null, null, 1731130219), ('2.2073', null, null, null, 1731156923), ('2.2074', '27044C8B-76BD-303C-A035-5314AF1D9E6E', 'DB5CADC0-E533-3123-8A63-5A434FE391ED', 40, 1731376950); + +CREATE INDEX idx_gd_version_aliases_added_at ON gd_version_aliases(added_at); From 82684093381b458c1b8a629311a79104da9628c3 Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Sun, 24 Nov 2024 10:29:06 -0700 Subject: [PATCH 14/14] this identation was bothering me --- src/endpoints/loader.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/endpoints/loader.rs b/src/endpoints/loader.rs index de9b8ff..2fdda22 100644 --- a/src/endpoints/loader.rs +++ b/src/endpoints/loader.rs @@ -63,8 +63,8 @@ pub async fn get_one( }; Ok(web::Json(ApiResponse { - error: "".to_string(), - payload: version, + error: "".to_string(), + payload: version, })) } @@ -142,7 +142,7 @@ pub async fn get_many( ).await?; Ok(web::Json(ApiResponse { - error: "".to_string(), - payload: versions, + error: "".to_string(), + payload: versions, })) }