From 50fc54c321247c1be53cd7ab81f03e3f70840848 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 28 Mar 2024 14:20:57 -0400 Subject: [PATCH] Extend extension API to support auto-updating extensions (#9929) This PR extends the extension API with some additional features to support auto-updating extensions: - The `GET /extensions` endpoint now accepts an optional `ids` parameter that can be used to filter the results down to just the extensions with the specified IDs. - This should be a comma-delimited list of extension IDs (e.g., `wgsl,gleam,tokyo-night`). - A new `GET /extensions/:extension_id` endpoint that returns all of the extension versions for a particular extension. Extracted from #9890, as these changes can be landed and deployed independently. Release Notes: - N/A Co-authored-by: Max --- crates/collab/src/api/extensions.rs | 54 ++++++++++--- crates/collab/src/db/queries/extensions.rs | 89 +++++++++++++++++----- 2 files changed, 110 insertions(+), 33 deletions(-) diff --git a/crates/collab/src/api/extensions.rs b/crates/collab/src/api/extensions.rs index 003d9636ae9f1b5d030652fa25a5db818149266e..2e8acd6c7b5ea1beb87cb08dd0a8504f7482d834 100644 --- a/crates/collab/src/api/extensions.rs +++ b/crates/collab/src/api/extensions.rs @@ -18,6 +18,7 @@ use util::ResultExt; pub fn router() -> Router { Router::new() .route("/extensions", get(get_extensions)) + .route("/extensions/:extension_id", get(get_extension_versions)) .route( "/extensions/:extension_id/download", get(download_latest_extension), @@ -32,29 +33,52 @@ pub fn router() -> Router { struct GetExtensionsParams { filter: Option, #[serde(default)] + ids: Option, + #[serde(default)] max_schema_version: i32, } -#[derive(Debug, Deserialize)] -struct DownloadLatestExtensionParams { - extension_id: String, +async fn get_extensions( + Extension(app): Extension>, + Query(params): Query, +) -> Result> { + let extension_ids = params + .ids + .as_ref() + .map(|s| s.split(',').map(|s| s.trim()).collect::>()); + + let extensions = if let Some(extension_ids) = extension_ids { + app.db + .get_extensions_by_ids(&extension_ids, params.max_schema_version) + .await? + } else { + app.db + .get_extensions(params.filter.as_deref(), params.max_schema_version, 500) + .await? + }; + + Ok(Json(GetExtensionsResponse { data: extensions })) } #[derive(Debug, Deserialize)] -struct DownloadExtensionParams { +struct GetExtensionVersionsParams { extension_id: String, - version: String, } -async fn get_extensions( +async fn get_extension_versions( Extension(app): Extension>, - Query(params): Query, + Path(params): Path, ) -> Result> { - let extensions = app - .db - .get_extensions(params.filter.as_deref(), params.max_schema_version, 500) - .await?; - Ok(Json(GetExtensionsResponse { data: extensions })) + let extension_versions = app.db.get_extension_versions(¶ms.extension_id).await?; + + Ok(Json(GetExtensionsResponse { + data: extension_versions, + })) +} + +#[derive(Debug, Deserialize)] +struct DownloadLatestExtensionParams { + extension_id: String, } async fn download_latest_extension( @@ -76,6 +100,12 @@ async fn download_latest_extension( .await } +#[derive(Debug, Deserialize)] +struct DownloadExtensionParams { + extension_id: String, + version: String, +} + async fn download_extension( Extension(app): Extension>, Path(params): Path, diff --git a/crates/collab/src/db/queries/extensions.rs b/crates/collab/src/db/queries/extensions.rs index aaa982662b5316e7115726fbccde283c8a1276e3..fc3def1d6d99e0ee445a7bf9296fd90cae85a018 100644 --- a/crates/collab/src/db/queries/extensions.rs +++ b/crates/collab/src/db/queries/extensions.rs @@ -1,4 +1,5 @@ use chrono::Utc; +use sea_orm::sea_query::IntoCondition; use super::*; @@ -10,37 +11,83 @@ impl Database { limit: usize, ) -> Result> { self.transaction(|tx| async move { - let mut condition = Condition::all().add( - extension::Column::LatestVersion - .into_expr() - .eq(extension_version::Column::Version.into_expr()), - ); + let mut condition = Condition::all() + .add( + extension::Column::LatestVersion + .into_expr() + .eq(extension_version::Column::Version.into_expr()), + ) + .add(extension_version::Column::SchemaVersion.lte(max_schema_version)); if let Some(filter) = filter { let fuzzy_name_filter = Self::fuzzy_like_string(filter); condition = condition.add(Expr::cust_with_expr("name ILIKE $1", fuzzy_name_filter)); } - let extensions = extension::Entity::find() - .inner_join(extension_version::Entity) - .select_also(extension_version::Entity) - .filter(condition) - .filter(extension_version::Column::SchemaVersion.lte(max_schema_version)) - .order_by_desc(extension::Column::TotalDownloadCount) - .order_by_asc(extension::Column::Name) - .limit(Some(limit as u64)) - .all(&*tx) - .await?; + self.get_extensions_where(condition, Some(limit as u64), &tx) + .await + }) + .await + } - Ok(extensions - .into_iter() - .filter_map(|(extension, version)| { - Some(metadata_from_extension_and_version(extension, version?)) - }) - .collect()) + pub async fn get_extensions_by_ids( + &self, + ids: &[&str], + max_schema_version: i32, + ) -> Result> { + self.transaction(|tx| async move { + let condition = Condition::all() + .add( + extension::Column::LatestVersion + .into_expr() + .eq(extension_version::Column::Version.into_expr()), + ) + .add(extension::Column::ExternalId.is_in(ids.iter().copied())) + .add(extension_version::Column::SchemaVersion.lte(max_schema_version)); + + self.get_extensions_where(condition, None, &tx).await }) .await } + /// Returns all of the versions for the extension with the given ID. + pub async fn get_extension_versions( + &self, + extension_id: &str, + ) -> Result> { + self.transaction(|tx| async move { + let condition = extension::Column::ExternalId + .eq(extension_id) + .into_condition(); + + self.get_extensions_where(condition, None, &tx).await + }) + .await + } + + async fn get_extensions_where( + &self, + condition: Condition, + limit: Option, + tx: &DatabaseTransaction, + ) -> Result> { + let extensions = extension::Entity::find() + .inner_join(extension_version::Entity) + .select_also(extension_version::Entity) + .filter(condition) + .order_by_desc(extension::Column::TotalDownloadCount) + .order_by_asc(extension::Column::Name) + .limit(limit) + .all(tx) + .await?; + + Ok(extensions + .into_iter() + .filter_map(|(extension, version)| { + Some(metadata_from_extension_and_version(extension, version?)) + }) + .collect()) + } + pub async fn get_extension(&self, extension_id: &str) -> Result> { self.transaction(|tx| async move { let extension = extension::Entity::find()