From b056610eaa0410f72a8f8b688b460ba0ebf997e2 Mon Sep 17 00:00:00 2001 From: Geometrically <18202329+Geometrically@users.noreply.github.com> Date: Wed, 15 Feb 2023 13:38:37 -0700 Subject: [PATCH] Version slugs (#533) * Version slugs * Get rid of new field, finish it up --- sqlx-data.json | 85 ++++++++++++++++++----------- src/database/models/version_item.rs | 36 +++++++++++- src/main.rs | 6 ++ src/routes/mod.rs | 3 +- src/routes/version_file.rs | 4 +- src/routes/versions.rs | 25 +++++++++ 6 files changed, 124 insertions(+), 35 deletions(-) diff --git a/sqlx-data.json b/sqlx-data.json index 48fba0fd..4ff4d2eb 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -5832,6 +5832,29 @@ }, "query": "\n UPDATE mods\n SET license_url = $1\n WHERE (id = $2)\n " }, + "c15ec51ec0e9900e5569557a618760cb4bbb303f0f9ca1189f18557e67d18b56": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Int8", + "Text", + "Int8", + "Text" + ] + } + }, + "query": "\n SELECT v.id FROM versions v\n INNER JOIN mods m ON mod_id = m.id\n WHERE (m.id = $1 OR m.slug = $2) AND (v.id = $3 OR v.version_number = $4)\n ORDER BY date_published ASC\n " + }, "c1a3f6dcef6110d6ea884670fb82bac14b98e922bb5673c048ccce7b7300539b": { "describe": { "columns": [ @@ -7418,6 +7441,37 @@ }, "query": "\n SELECT name FROM project_types pt\n INNER JOIN mods ON mods.project_type = pt.id\n WHERE mods.id = $1\n " }, + "ef9391658df31e28c53525c3338a05d9025bb31101547c4cb9d439f793bc7721": { + "describe": { + "columns": [ + { + "name": "version_id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "date_published", + "ordinal": 1, + "type_info": "Timestamptz" + } + ], + "nullable": [ + false, + false + ], + "parameters": { + "Left": [ + "Int8", + "VarcharArray", + "VarcharArray", + "Varchar", + "Int8", + "Int8" + ] + } + }, + "query": "\n SELECT DISTINCT ON(v.date_published, v.id) version_id, v.date_published FROM versions v\n INNER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id\n INNER JOIN game_versions gv on gvv.game_version_id = gv.id AND (cardinality($2::varchar[]) = 0 OR gv.version = ANY($2::varchar[]))\n INNER JOIN loaders_versions lv ON lv.version_id = v.id\n INNER JOIN loaders l on lv.loader_id = l.id AND (cardinality($3::varchar[]) = 0 OR l.loader = ANY($3::varchar[]))\n WHERE v.mod_id = $1 AND ($4::varchar IS NULL OR v.version_type = $4)\n ORDER BY v.date_published, v.id DESC\n LIMIT $5 OFFSET $6\n " + }, "f0db9d8606ccc2196a9cfafe0e7090dab42bf790f25e0469b8947fac1cf043d5": { "describe": { "columns": [ @@ -7767,37 +7821,6 @@ }, "query": "SELECT EXISTS(SELECT 1 FROM notifications WHERE id=$1)" }, - "fcc7bedf9709bf49ae152064240a6e8bfa8ff4d5a63707e8b1450d9d77fb6f14": { - "describe": { - "columns": [ - { - "name": "version_id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "date_published", - "ordinal": 1, - "type_info": "Timestamptz" - } - ], - "nullable": [ - false, - false - ], - "parameters": { - "Left": [ - "Int8", - "VarcharArray", - "VarcharArray", - "Varchar", - "Int8", - "Int8" - ] - } - }, - "query": "\n SELECT DISTINCT ON(v.date_published, v.id) version_id, v.date_published FROM versions v\n INNER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id\n INNER JOIN game_versions gv on gvv.game_version_id = gv.id AND (cardinality($2::varchar[]) = 0 OR gv.version = ANY($2::varchar[]))\n INNER JOIN loaders_versions lv ON lv.version_id = v.id\n INNER JOIN loaders l on lv.loader_id = l.id AND (cardinality($3::varchar[]) = 0 OR l.loader = ANY($3::varchar[]))\n WHERE v.mod_id = $1 AND ($4::varchar IS NULL OR v.version_type = $4)\n ORDER BY v.date_published, v.id ASC\n LIMIT $5 OFFSET $6\n " - }, "fcd15905507769ab7f9839d64d1be3ee3f61cd555aee57dace76f8e53e91d344": { "describe": { "columns": [], diff --git a/src/database/models/version_item.rs b/src/database/models/version_item.rs index 2651f4ae..90a208de 100644 --- a/src/database/models/version_item.rs +++ b/src/database/models/version_item.rs @@ -1,5 +1,6 @@ use super::ids::*; use super::DatabaseError; +use crate::models::ids::base62_impl::parse_base62; use crate::models::projects::{FileType, VersionStatus, VersionType}; use chrono::{DateTime, Utc}; use serde::Deserialize; @@ -499,7 +500,7 @@ impl Version { INNER JOIN loaders_versions lv ON lv.version_id = v.id INNER JOIN loaders l on lv.loader_id = l.id AND (cardinality($3::varchar[]) = 0 OR l.loader = ANY($3::varchar[])) WHERE v.mod_id = $1 AND ($4::varchar IS NULL OR v.version_type = $4) - ORDER BY v.date_published, v.id ASC + ORDER BY v.date_published, v.id DESC LIMIT $5 OFFSET $6 ", project_id as ProjectId, @@ -905,6 +906,39 @@ impl Version { .try_collect::>() .await } + + pub async fn get_full_from_id_slug<'a, 'b, E>( + project_id_or_slug: &str, + slug: &str, + executor: E, + ) -> Result, sqlx::error::Error> + where + E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, + { + let project_id_opt = + parse_base62(project_id_or_slug).ok().map(|x| x as i64); + let id_opt = parse_base62(slug).ok().map(|x| x as i64); + let id = sqlx::query!( + " + SELECT v.id FROM versions v + INNER JOIN mods m ON mod_id = m.id + WHERE (m.id = $1 OR m.slug = $2) AND (v.id = $3 OR v.version_number = $4) + ORDER BY date_published ASC + ", + project_id_opt, + project_id_or_slug, + id_opt, + slug + ) + .fetch_optional(executor) + .await?; + + if let Some(version_id) = id { + Version::get_full(VersionId(version_id.id), executor).await + } else { + Ok(None) + } + } } #[derive(Clone)] diff --git a/src/main.rs b/src/main.rs index d65d7591..ac4ba81e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -277,6 +277,12 @@ async fn main() -> std::io::Result<()> { dotenvy::var("RATE_LIMIT_IGNORE_KEY").ok(), ), ) + .app_data(web::FormConfig::default().error_handler(|err, _req| { + routes::ApiError::Validation(err.to_string()).into() + })) + .app_data(web::PathConfig::default().error_handler(|err, _req| { + routes::ApiError::Validation(err.to_string()).into() + })) .app_data(web::QueryConfig::default().error_handler(|err, _req| { routes::ApiError::Validation(err.to_string()).into() })) diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 46e6c7e8..d74a5355 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -75,7 +75,8 @@ pub fn projects_config(cfg: &mut web::ServiceConfig) { .service( web::scope("{project_id}") .service(versions::version_list) - .service(projects::dependency_list), + .service(projects::dependency_list) + .service(versions::version_project_get), ), ); } diff --git a/src/routes/version_file.rs b/src/routes/version_file.rs index e7d0b191..80093bb0 100644 --- a/src/routes/version_file.rs +++ b/src/routes/version_file.rs @@ -308,7 +308,7 @@ pub async fn get_update_from_hash( ) .await?; - if let Some(version_id) = version_ids.last() { + if let Some(version_id) = version_ids.first() { let version_data = database::models::Version::get_full(*version_id, &**pool) .await?; @@ -503,7 +503,7 @@ pub async fn update_files( ) .await?; - if let Some(latest_version) = updated_versions.last() { + if let Some(latest_version) = updated_versions.first() { let mut version_ids = version_ids.write().await; version_ids.insert(*latest_version, row.hash); diff --git a/src/routes/versions.rs b/src/routes/versions.rs index 3ed8857e..d3635f3a 100644 --- a/src/routes/versions.rs +++ b/src/routes/versions.rs @@ -144,6 +144,31 @@ pub async fn version_list( } } +// Given a project ID/slug and a version slug +#[get("version/{slug}")] +pub async fn version_project_get( + req: HttpRequest, + info: web::Path<(String, String)>, + pool: web::Data, +) -> Result { + let id = info.into_inner(); + let version_data = + database::models::Version::get_full_from_id_slug(&id.0, &id.1, &**pool) + .await?; + + let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); + + if let Some(data) = version_data { + if is_authorized_version(&data.inner, &user_option, &pool).await? { + return Ok( + HttpResponse::Ok().json(models::projects::Version::from(data)) + ); + } + } + + Ok(HttpResponse::NotFound().body("")) +} + #[derive(Serialize, Deserialize)] pub struct VersionIds { pub ids: String,