Allow checking CLA signatures by GitHub login

Max Brunsfeld and Marshall created

This will be used by CLA Bot.

Co-authored-by: Marshall <marshall@zed.dev>

Change summary

crates/collab/src/api.rs                     | 24 +++++++++++++++++++--
crates/collab/src/db.rs                      |  1 
crates/collab/src/db/queries/contributors.rs | 23 +++++++++++++++-----
3 files changed, 39 insertions(+), 9 deletions(-)

Detailed changes

crates/collab/src/api.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
     auth,
-    db::{User, UserId},
+    db::{ContributorSelector, User, UserId},
     rpc, AppState, Error, Result,
 };
 use anyhow::anyhow;
@@ -141,7 +141,24 @@ async fn get_contributors(Extension(app): Extension<Arc<AppState>>) -> Result<Js
 
 #[derive(Debug, Deserialize)]
 struct CheckIsContributorParams {
-    github_user_id: i32,
+    github_user_id: Option<i32>,
+    github_login: Option<String>,
+}
+
+impl CheckIsContributorParams {
+    fn as_contributor_selector(self) -> Result<ContributorSelector> {
+        if let Some(github_user_id) = self.github_user_id {
+            return Ok(ContributorSelector::GitHubUserId { github_user_id });
+        }
+
+        if let Some(github_login) = self.github_login {
+            return Ok(ContributorSelector::GitHubLogin { github_login });
+        }
+
+        Err(anyhow!(
+            "must be one of `github_user_id` or `github_login`."
+        ))?
+    }
 }
 
 #[derive(Debug, Serialize)]
@@ -153,10 +170,11 @@ async fn check_is_contributor(
     Extension(app): Extension<Arc<AppState>>,
     Query(params): Query<CheckIsContributorParams>,
 ) -> Result<Json<CheckIsContributorResponse>> {
+    let params = params.as_contributor_selector()?;
     Ok(Json(CheckIsContributorResponse {
         signed_at: app
             .db
-            .get_contributor_sign_timestamp(params.github_user_id)
+            .get_contributor_sign_timestamp(&params)
             .await?
             .map(|ts| ts.and_utc().to_rfc3339_opts(SecondsFormat::Millis, true)),
     }))

crates/collab/src/db.rs 🔗

@@ -44,6 +44,7 @@ use tables::*;
 use tokio::sync::{Mutex, OwnedMutexGuard};
 
 pub use ids::*;
+pub use queries::contributors::ContributorSelector;
 pub use sea_orm::ConnectOptions;
 pub use tables::user::Model as User;
 

crates/collab/src/db/queries/contributors.rs 🔗

@@ -1,5 +1,11 @@
 use super::*;
 
+#[derive(Debug)]
+pub enum ContributorSelector {
+    GitHubUserId { github_user_id: i32 },
+    GitHubLogin { github_login: String },
+}
+
 impl Database {
     /// Retrieves the GitHub logins of all users who have signed the CLA.
     pub async fn get_contributors(&self) -> Result<Vec<String>> {
@@ -24,14 +30,19 @@ impl Database {
     /// Records that a given user has signed the CLA.
     pub async fn get_contributor_sign_timestamp(
         &self,
-        github_user_id: i32,
+        selector: &ContributorSelector,
     ) -> Result<Option<DateTime>> {
         self.transaction(|tx| async move {
-            let Some(user) = user::Entity::find()
-                .filter(user::Column::GithubUserId.eq(github_user_id))
-                .one(&*tx)
-                .await?
-            else {
+            let condition = match selector {
+                ContributorSelector::GitHubUserId { github_user_id } => {
+                    user::Column::GithubUserId.eq(*github_user_id)
+                }
+                ContributorSelector::GitHubLogin { github_login } => {
+                    user::Column::GithubLogin.eq(github_login)
+                }
+            };
+
+            let Some(user) = user::Entity::find().filter(condition).one(&*tx).await? else {
                 return Ok(None);
             };
             let Some(contributor) = contributor::Entity::find_by_id(user.id).one(&*tx).await?