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:
parent
eaf65ab704
commit
50fc54c321
2 changed files with 114 additions and 37 deletions
|
@ -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
|
||||||
.get_extensions(params.filter.as_deref(), params.max_schema_version, 500)
|
.as_ref()
|
||||||
.await?;
|
.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)
|
||||||
|
.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(¶ms.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>,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use sea_orm::sea_query::IntoCondition;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -10,37 +11,83 @@ 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()
|
||||||
extension::Column::LatestVersion
|
.add(
|
||||||
.into_expr()
|
extension::Column::LatestVersion
|
||||||
.eq(extension_version::Column::Version.into_expr()),
|
.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));
|
||||||
}
|
}
|
||||||
|
|
||||||
let extensions = extension::Entity::find()
|
self.get_extensions_where(condition, Some(limit as u64), &tx)
|
||||||
.inner_join(extension_version::Entity)
|
.await
|
||||||
.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?;
|
|
||||||
|
|
||||||
Ok(extensions
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|(extension, version)| {
|
|
||||||
Some(metadata_from_extension_and_version(extension, version?))
|
|
||||||
})
|
|
||||||
.collect())
|
|
||||||
})
|
})
|
||||||
.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()
|
||||||
|
.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<Option<ExtensionMetadata>> {
|
pub async fn get_extension(&self, extension_id: &str) -> Result<Option<ExtensionMetadata>> {
|
||||||
self.transaction(|tx| async move {
|
self.transaction(|tx| async move {
|
||||||
let extension = extension::Entity::find()
|
let extension = extension::Entity::find()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue