Expose project metadata via `GET /project_metadata`

Antonio Scandurra created

Change summary

crates/collab/src/api.rs | 20 +++++++++++++++++
crates/collab/src/db.rs  | 46 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 64 insertions(+), 2 deletions(-)

Detailed changes

crates/collab/src/api.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
     auth,
-    db::{User, UserId},
+    db::{ProjectId, User, UserId},
     rpc::{self, ResultExt},
     AppState, Error, Result,
 };
@@ -16,6 +16,7 @@ use axum::{
 };
 use axum_extra::response::ErasedJson;
 use serde::{Deserialize, Serialize};
+use serde_json::json;
 use std::sync::Arc;
 use time::OffsetDateTime;
 use tower::ServiceBuilder;
@@ -37,6 +38,7 @@ pub fn routes(rpc_server: &Arc<rpc::Server>, state: Arc<AppState>) -> Router<Bod
             "/project_activity_summary",
             get(get_project_activity_summary),
         )
+        .route("/project_metadata", get(get_project_metadata))
         .layer(
             ServiceBuilder::new()
                 .layer(Extension(state))
@@ -263,6 +265,22 @@ async fn get_project_activity_summary(
     Ok(ErasedJson::pretty(summary))
 }
 
+#[derive(Deserialize)]
+struct GetProjectMetadataParams {
+    project_id: u64,
+}
+
+async fn get_project_metadata(
+    Query(params): Query<GetProjectMetadataParams>,
+    Extension(app): Extension<Arc<AppState>>,
+) -> Result<ErasedJson> {
+    let extensions = app
+        .db
+        .get_project_extensions(ProjectId::from_proto(params.project_id))
+        .await?;
+    Ok(ErasedJson::pretty(json!({ "extensions": extensions })))
+}
+
 #[derive(Deserialize)]
 struct CreateAccessTokenQueryParams {
     public_key: String,

crates/collab/src/db.rs 🔗

@@ -46,7 +46,7 @@ pub trait Db: Send + Sync {
     /// Unregisters a project for the given project id.
     async fn unregister_project(&self, project_id: ProjectId) -> Result<()>;
 
-    /// Create a new project for the given user.
+    /// Update file counts by extension for the given project and worktree.
     async fn update_worktree_extensions(
         &self,
         project_id: ProjectId,
@@ -54,6 +54,12 @@ pub trait Db: Send + Sync {
         extensions: HashMap<String, usize>,
     ) -> Result<()>;
 
+    /// Get the file counts on the given project keyed by their worktree and extension.
+    async fn get_project_extensions(
+        &self,
+        project_id: ProjectId,
+    ) -> Result<HashMap<u64, HashMap<String, usize>>>;
+
     /// Record which users have been active in which projects during
     /// a given period of time.
     async fn record_project_activity(
@@ -501,6 +507,37 @@ impl Db for PostgresDb {
         Ok(())
     }
 
+    async fn get_project_extensions(
+        &self,
+        project_id: ProjectId,
+    ) -> Result<HashMap<u64, HashMap<String, usize>>> {
+        #[derive(Clone, Debug, Default, FromRow, Serialize, PartialEq)]
+        struct WorktreeExtension {
+            worktree_id: i32,
+            extension: String,
+            count: i32,
+        }
+
+        let query = "
+            SELECT worktree_id, extension, count
+            FROM worktree_extensions
+            WHERE project_id = $1
+        ";
+        let counts = sqlx::query_as::<_, WorktreeExtension>(query)
+            .bind(&project_id)
+            .fetch_all(&self.pool)
+            .await?;
+
+        let mut extension_counts = HashMap::default();
+        for count in counts {
+            extension_counts
+                .entry(count.worktree_id as u64)
+                .or_insert(HashMap::default())
+                .insert(count.extension, count.count as usize);
+        }
+        Ok(extension_counts)
+    }
+
     async fn record_project_activity(
         &self,
         time_period: Range<OffsetDateTime>,
@@ -2173,6 +2210,13 @@ pub mod tests {
             Ok(())
         }
 
+        async fn get_project_extensions(
+            &self,
+            _project_id: ProjectId,
+        ) -> Result<HashMap<u64, HashMap<String, usize>>> {
+            unimplemented!()
+        }
+
         async fn record_project_activity(
             &self,
             _period: Range<OffsetDateTime>,