Return the metrics id from the signup-creation API

Max Brunsfeld and Nathan Sobo created

Co-authored-by: Nathan Sobo <nathan@zed.dev>

Change summary

crates/collab/src/api.rs      | 11 ++++++++---
crates/collab/src/db.rs       | 24 +++++++++++++++---------
crates/collab/src/db_tests.rs | 24 ++++++++++++++----------
3 files changed, 37 insertions(+), 22 deletions(-)

Detailed changes

crates/collab/src/api.rs 🔗

@@ -396,12 +396,17 @@ async fn get_user_for_invite_code(
     Ok(Json(app.db.get_user_for_invite_code(&code).await?))
 }
 
+#[derive(Serialize)]
+struct CreateSignupResponse {
+    metrics_id: i32,
+}
+
 async fn create_signup(
     Json(params): Json<Signup>,
     Extension(app): Extension<Arc<AppState>>,
-) -> Result<()> {
-    app.db.create_signup(params).await?;
-    Ok(())
+) -> Result<Json<CreateSignupResponse>> {
+    let metrics_id = app.db.create_signup(params).await?;
+    Ok(Json(CreateSignupResponse { metrics_id }))
 }
 
 async fn get_waitlist_summary(

crates/collab/src/db.rs 🔗

@@ -37,7 +37,7 @@ pub trait Db: Send + Sync {
     async fn get_user_for_invite_code(&self, code: &str) -> Result<User>;
     async fn create_invite_from_code(&self, code: &str, email_address: &str) -> Result<Invite>;
 
-    async fn create_signup(&self, signup: Signup) -> Result<()>;
+    async fn create_signup(&self, signup: Signup) -> Result<i32>;
     async fn get_waitlist_summary(&self) -> Result<WaitlistSummary>;
     async fn get_unsent_invites(&self, count: usize) -> Result<Vec<Invite>>;
     async fn record_sent_invites(&self, invites: &[Invite]) -> Result<()>;
@@ -364,8 +364,8 @@ impl Db for PostgresDb {
 
     // signups
 
-    async fn create_signup(&self, signup: Signup) -> Result<()> {
-        sqlx::query(
+    async fn create_signup(&self, signup: Signup) -> Result<i32> {
+        Ok(sqlx::query_scalar(
             "
             INSERT INTO signups
             (
@@ -381,6 +381,7 @@ impl Db for PostgresDb {
             )
             VALUES
                 ($1, $2, 'f', $3, $4, $5, 'f', $6, $7)
+            RETURNING id
             ",
         )
         .bind(&signup.email_address)
@@ -390,9 +391,8 @@ impl Db for PostgresDb {
         .bind(&signup.platform_windows)
         .bind(&signup.editor_features)
         .bind(&signup.programming_languages)
-        .execute(&self.pool)
-        .await?;
-        Ok(())
+        .fetch_one(&self.pool)
+        .await?)
     }
 
     async fn get_waitlist_summary(&self) -> Result<WaitlistSummary> {
@@ -479,7 +479,10 @@ impl Db for PostgresDb {
         .ok_or_else(|| Error::Http(StatusCode::NOT_FOUND, "no such invite".to_string()))?;
 
         if existing_user_id.is_some() {
-            Err(Error::Http(StatusCode::UNPROCESSABLE_ENTITY, "invitation already redeemed".to_string()))?;
+            Err(Error::Http(
+                StatusCode::UNPROCESSABLE_ENTITY,
+                "invitation already redeemed".to_string(),
+            ))?;
         }
 
         let user_id: UserId = sqlx::query_scalar(
@@ -1564,6 +1567,7 @@ pub struct User {
     pub id: UserId,
     pub github_login: String,
     pub github_user_id: Option<i32>,
+    pub metrics_id: i32,
     pub email_address: Option<String>,
     pub admin: bool,
     pub invite_code: Option<String>,
@@ -1789,7 +1793,8 @@ mod test {
             {
                 Ok(user.id)
             } else {
-                let user_id = UserId(post_inc(&mut *self.next_user_id.lock()));
+                let id = post_inc(&mut *self.next_user_id.lock());
+                let user_id = UserId(id);
                 users.insert(
                     user_id,
                     User {
@@ -1797,6 +1802,7 @@ mod test {
                         github_login: params.github_login,
                         github_user_id: Some(params.github_user_id),
                         email_address: Some(email_address.to_string()),
+                        metrics_id: id + 100,
                         admin,
                         invite_code: None,
                         invite_count: 0,
@@ -1878,7 +1884,7 @@ mod test {
 
         // signups
 
-        async fn create_signup(&self, _signup: Signup) -> Result<()> {
+        async fn create_signup(&self, _signup: Signup) -> Result<i32> {
             unimplemented!()
         }
 

crates/collab/src/db_tests.rs 🔗

@@ -1139,17 +1139,20 @@ async fn test_signups() {
     let db = postgres.db();
 
     // people sign up on the waitlist
+    let mut signup_metric_ids = Vec::new();
     for i in 0..8 {
-        db.create_signup(Signup {
-            email_address: format!("person-{i}@example.com"),
-            platform_mac: true,
-            platform_linux: i % 2 == 0,
-            platform_windows: i % 4 == 0,
-            editor_features: vec!["speed".into()],
-            programming_languages: vec!["rust".into(), "c".into()],
-        })
-        .await
-        .unwrap();
+        signup_metric_ids.push(
+            db.create_signup(Signup {
+                email_address: format!("person-{i}@example.com"),
+                platform_mac: true,
+                platform_linux: i % 2 == 0,
+                platform_windows: i % 4 == 0,
+                editor_features: vec!["speed".into()],
+                programming_languages: vec!["rust".into(), "c".into()],
+            })
+            .await
+            .unwrap(),
+        );
     }
 
     assert_eq!(
@@ -1235,6 +1238,7 @@ async fn test_signups() {
     assert_eq!(user.github_login, "person-0");
     assert_eq!(user.email_address.as_deref(), Some("person-0@example.com"));
     assert_eq!(user.invite_count, 5);
+    assert_eq!(user.metrics_id, signup_metric_ids[0]);
 
     // cannot redeem the same signup again.
     db.create_user_from_invite(