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 <max@zed.dev>
This commit is contained in:
Marshall Bowers 2024-03-28 14:20:57 -04:00 committed by GitHub
parent eaf65ab704
commit 50fc54c321
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 114 additions and 37 deletions

View file

@ -18,6 +18,7 @@ use util::ResultExt;
pub fn router() -> Router { pub fn router() -> Router {
Router::new() Router::new()
.route("/extensions", get(get_extensions)) .route("/extensions", get(get_extensions))
.route("/extensions/:extension_id", get(get_extension_versions))
.route( .route(
"/extensions/:extension_id/download", "/extensions/:extension_id/download",
get(download_latest_extension), get(download_latest_extension),
@ -32,31 +33,54 @@ pub fn router() -> Router {
struct GetExtensionsParams { struct GetExtensionsParams {
filter: Option<String>, filter: Option<String>,
#[serde(default)] #[serde(default)]
ids: Option<String>,
#[serde(default)]
max_schema_version: i32, max_schema_version: i32,
} }
#[derive(Debug, Deserialize)]
struct DownloadLatestExtensionParams {
extension_id: String,
}
#[derive(Debug, Deserialize)]
struct DownloadExtensionParams {
extension_id: String,
version: String,
}
async fn get_extensions( async fn get_extensions(
Extension(app): Extension<Arc<AppState>>, Extension(app): Extension<Arc<AppState>>,
Query(params): Query<GetExtensionsParams>, Query(params): Query<GetExtensionsParams>,
) -> Result<Json<GetExtensionsResponse>> { ) -> Result<Json<GetExtensionsResponse>> {
let extensions = app let extension_ids = params
.db .ids
.as_ref()
.map(|s| s.split(',').map(|s| s.trim()).collect::<Vec<_>>());
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) .get_extensions(params.filter.as_deref(), params.max_schema_version, 500)
.await?; .await?
};
Ok(Json(GetExtensionsResponse { data: extensions })) Ok(Json(GetExtensionsResponse { data: extensions }))
} }
#[derive(Debug, Deserialize)]
struct GetExtensionVersionsParams {
extension_id: String,
}
async fn get_extension_versions(
Extension(app): Extension<Arc<AppState>>,
Path(params): Path<GetExtensionVersionsParams>,
) -> Result<Json<GetExtensionsResponse>> {
let extension_versions = app.db.get_extension_versions(&params.extension_id).await?;
Ok(Json(GetExtensionsResponse {
data: extension_versions,
}))
}
#[derive(Debug, Deserialize)]
struct DownloadLatestExtensionParams {
extension_id: String,
}
async fn download_latest_extension( async fn download_latest_extension(
Extension(app): Extension<Arc<AppState>>, Extension(app): Extension<Arc<AppState>>,
Path(params): Path<DownloadLatestExtensionParams>, Path(params): Path<DownloadLatestExtensionParams>,
@ -76,6 +100,12 @@ async fn download_latest_extension(
.await .await
} }
#[derive(Debug, Deserialize)]
struct DownloadExtensionParams {
extension_id: String,
version: String,
}
async fn download_extension( async fn download_extension(
Extension(app): Extension<Arc<AppState>>, Extension(app): Extension<Arc<AppState>>,
Path(params): Path<DownloadExtensionParams>, Path(params): Path<DownloadExtensionParams>,

View file

@ -1,4 +1,5 @@
use chrono::Utc; use chrono::Utc;
use sea_orm::sea_query::IntoCondition;
use super::*; use super::*;
@ -10,25 +11,73 @@ impl Database {
limit: usize, limit: usize,
) -> Result<Vec<ExtensionMetadata>> { ) -> Result<Vec<ExtensionMetadata>> {
self.transaction(|tx| async move { self.transaction(|tx| async move {
let mut condition = Condition::all().add( let mut condition = Condition::all()
.add(
extension::Column::LatestVersion extension::Column::LatestVersion
.into_expr() .into_expr()
.eq(extension_version::Column::Version.into_expr()), .eq(extension_version::Column::Version.into_expr()),
); )
.add(extension_version::Column::SchemaVersion.lte(max_schema_version));
if let Some(filter) = filter { if let Some(filter) = filter {
let fuzzy_name_filter = Self::fuzzy_like_string(filter); let fuzzy_name_filter = Self::fuzzy_like_string(filter);
condition = condition.add(Expr::cust_with_expr("name ILIKE $1", fuzzy_name_filter)); condition = condition.add(Expr::cust_with_expr("name ILIKE $1", fuzzy_name_filter));
} }
self.get_extensions_where(condition, Some(limit as u64), &tx)
.await
})
.await
}
pub async fn get_extensions_by_ids(
&self,
ids: &[&str],
max_schema_version: i32,
) -> Result<Vec<ExtensionMetadata>> {
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<Vec<ExtensionMetadata>> {
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<u64>,
tx: &DatabaseTransaction,
) -> Result<Vec<ExtensionMetadata>> {
let extensions = extension::Entity::find() let extensions = extension::Entity::find()
.inner_join(extension_version::Entity) .inner_join(extension_version::Entity)
.select_also(extension_version::Entity) .select_also(extension_version::Entity)
.filter(condition) .filter(condition)
.filter(extension_version::Column::SchemaVersion.lte(max_schema_version))
.order_by_desc(extension::Column::TotalDownloadCount) .order_by_desc(extension::Column::TotalDownloadCount)
.order_by_asc(extension::Column::Name) .order_by_asc(extension::Column::Name)
.limit(Some(limit as u64)) .limit(limit)
.all(&*tx) .all(tx)
.await?; .await?;
Ok(extensions Ok(extensions
@ -37,8 +86,6 @@ impl Database {
Some(metadata_from_extension_and_version(extension, version?)) Some(metadata_from_extension_and_version(extension, version?))
}) })
.collect()) .collect())
})
.await
} }
pub async fn get_extension(&self, extension_id: &str) -> Result<Option<ExtensionMetadata>> { pub async fn get_extension(&self, extension_id: &str) -> Result<Option<ExtensionMetadata>> {