mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-31 15:01:58 +00:00
Add REST APIs for getting and adding contributors
Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
5b906e731d
commit
1981de4cae
12 changed files with 206 additions and 56 deletions
|
@ -344,3 +344,9 @@ CREATE INDEX
|
||||||
"index_notifications_on_recipient_id_is_read_kind_entity_id"
|
"index_notifications_on_recipient_id_is_read_kind_entity_id"
|
||||||
ON "notifications"
|
ON "notifications"
|
||||||
("recipient_id", "is_read", "kind", "entity_id");
|
("recipient_id", "is_read", "kind", "entity_id");
|
||||||
|
|
||||||
|
CREATE TABLE contributors (
|
||||||
|
user_id INTEGER REFERENCES users(id),
|
||||||
|
signed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (user_id)
|
||||||
|
);
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
CREATE TABLE contributors (
|
||||||
|
user_id INTEGER REFERENCES users(id),
|
||||||
|
signed_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
PRIMARY KEY (user_id)
|
||||||
|
);
|
|
@ -25,6 +25,7 @@ pub fn routes(rpc_server: Arc<rpc::Server>, state: Arc<AppState>) -> Router<Body
|
||||||
.route("/users/:id/access_tokens", post(create_access_token))
|
.route("/users/:id/access_tokens", post(create_access_token))
|
||||||
.route("/panic", post(trace_panic))
|
.route("/panic", post(trace_panic))
|
||||||
.route("/rpc_server_snapshot", get(get_rpc_server_snapshot))
|
.route("/rpc_server_snapshot", get(get_rpc_server_snapshot))
|
||||||
|
.route("/contributors", get(get_contributors).post(add_contributor))
|
||||||
.layer(
|
.layer(
|
||||||
ServiceBuilder::new()
|
ServiceBuilder::new()
|
||||||
.layer(Extension(state))
|
.layer(Extension(state))
|
||||||
|
@ -66,7 +67,7 @@ pub async fn validate_api_token<B>(req: Request<B>, next: Next<B>) -> impl IntoR
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct AuthenticatedUserParams {
|
struct AuthenticatedUserParams {
|
||||||
github_user_id: Option<i32>,
|
github_user_id: i32,
|
||||||
github_login: String,
|
github_login: String,
|
||||||
github_email: Option<String>,
|
github_email: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -88,8 +89,7 @@ async fn get_authenticated_user(
|
||||||
params.github_user_id,
|
params.github_user_id,
|
||||||
params.github_email.as_deref(),
|
params.github_email.as_deref(),
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or_else(|| Error::Http(StatusCode::NOT_FOUND, "user not found".into()))?;
|
|
||||||
let metrics_id = app.db.get_user_metrics_id(user.id).await?;
|
let metrics_id = app.db.get_user_metrics_id(user.id).await?;
|
||||||
return Ok(Json(AuthenticatedUserResponse { user, metrics_id }));
|
return Ok(Json(AuthenticatedUserResponse { user, metrics_id }));
|
||||||
}
|
}
|
||||||
|
@ -133,6 +133,24 @@ async fn get_rpc_server_snapshot(
|
||||||
Ok(ErasedJson::pretty(rpc_server.snapshot().await))
|
Ok(ErasedJson::pretty(rpc_server.snapshot().await))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_contributors(Extension(app): Extension<Arc<AppState>>) -> Result<Json<Vec<String>>> {
|
||||||
|
Ok(Json(app.db.get_contributors().await?))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_contributor(
|
||||||
|
Json(params): Json<AuthenticatedUserParams>,
|
||||||
|
Extension(app): Extension<Arc<AppState>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
Ok(app
|
||||||
|
.db
|
||||||
|
.add_contributor(
|
||||||
|
¶ms.github_login,
|
||||||
|
params.github_user_id,
|
||||||
|
params.github_email.as_deref(),
|
||||||
|
)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct CreateAccessTokenQueryParams {
|
struct CreateAccessTokenQueryParams {
|
||||||
public_key: String,
|
public_key: String,
|
||||||
|
|
|
@ -4,6 +4,7 @@ pub mod access_tokens;
|
||||||
pub mod buffers;
|
pub mod buffers;
|
||||||
pub mod channels;
|
pub mod channels;
|
||||||
pub mod contacts;
|
pub mod contacts;
|
||||||
|
pub mod contributors;
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
pub mod notifications;
|
pub mod notifications;
|
||||||
pub mod projects;
|
pub mod projects;
|
||||||
|
|
50
crates/collab/src/db/queries/contributors.rs
Normal file
50
crates/collab/src/db/queries/contributors.rs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl Database {
|
||||||
|
/// Retrieves the GitHub logins of all users who have signed the CLA.
|
||||||
|
pub async fn get_contributors(&self) -> Result<Vec<String>> {
|
||||||
|
self.transaction(|tx| async move {
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||||
|
enum QueryGithubLogin {
|
||||||
|
GithubLogin,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(contributor::Entity::find()
|
||||||
|
.inner_join(user::Entity)
|
||||||
|
.order_by_asc(contributor::Column::SignedAt)
|
||||||
|
.select_only()
|
||||||
|
.column(user::Column::GithubLogin)
|
||||||
|
.into_values::<_, QueryGithubLogin>()
|
||||||
|
.all(&*tx)
|
||||||
|
.await?)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records that a given user has signed the CLA.
|
||||||
|
pub async fn add_contributor(
|
||||||
|
&self,
|
||||||
|
github_login: &str,
|
||||||
|
github_user_id: i32,
|
||||||
|
github_email: Option<&str>,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.transaction(|tx| async move {
|
||||||
|
let user = self
|
||||||
|
.get_or_create_user_by_github_account_tx(
|
||||||
|
github_login,
|
||||||
|
github_user_id,
|
||||||
|
github_email,
|
||||||
|
&*tx,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
contributor::ActiveModel {
|
||||||
|
user_id: ActiveValue::Set(user.id),
|
||||||
|
signed_at: ActiveValue::NotSet,
|
||||||
|
}
|
||||||
|
.insert(&*tx)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
|
@ -72,12 +72,28 @@ impl Database {
|
||||||
pub async fn get_or_create_user_by_github_account(
|
pub async fn get_or_create_user_by_github_account(
|
||||||
&self,
|
&self,
|
||||||
github_login: &str,
|
github_login: &str,
|
||||||
github_user_id: Option<i32>,
|
github_user_id: i32,
|
||||||
github_email: Option<&str>,
|
github_email: Option<&str>,
|
||||||
) -> Result<Option<User>> {
|
) -> Result<User> {
|
||||||
self.transaction(|tx| async move {
|
self.transaction(|tx| async move {
|
||||||
let tx = &*tx;
|
self.get_or_create_user_by_github_account_tx(
|
||||||
if let Some(github_user_id) = github_user_id {
|
github_login,
|
||||||
|
github_user_id,
|
||||||
|
github_email,
|
||||||
|
&*tx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_or_create_user_by_github_account_tx(
|
||||||
|
&self,
|
||||||
|
github_login: &str,
|
||||||
|
github_user_id: i32,
|
||||||
|
github_email: Option<&str>,
|
||||||
|
tx: &DatabaseTransaction,
|
||||||
|
) -> Result<User> {
|
||||||
if let Some(user_by_github_user_id) = user::Entity::find()
|
if let Some(user_by_github_user_id) = user::Entity::find()
|
||||||
.filter(user::Column::GithubUserId.eq(github_user_id))
|
.filter(user::Column::GithubUserId.eq(github_user_id))
|
||||||
.one(tx)
|
.one(tx)
|
||||||
|
@ -85,7 +101,7 @@ impl Database {
|
||||||
{
|
{
|
||||||
let mut user_by_github_user_id = user_by_github_user_id.into_active_model();
|
let mut user_by_github_user_id = user_by_github_user_id.into_active_model();
|
||||||
user_by_github_user_id.github_login = ActiveValue::set(github_login.into());
|
user_by_github_user_id.github_login = ActiveValue::set(github_login.into());
|
||||||
Ok(Some(user_by_github_user_id.update(tx).await?))
|
Ok(user_by_github_user_id.update(tx).await?)
|
||||||
} else if let Some(user_by_github_login) = user::Entity::find()
|
} else if let Some(user_by_github_login) = user::Entity::find()
|
||||||
.filter(user::Column::GithubLogin.eq(github_login))
|
.filter(user::Column::GithubLogin.eq(github_login))
|
||||||
.one(tx)
|
.one(tx)
|
||||||
|
@ -93,7 +109,7 @@ impl Database {
|
||||||
{
|
{
|
||||||
let mut user_by_github_login = user_by_github_login.into_active_model();
|
let mut user_by_github_login = user_by_github_login.into_active_model();
|
||||||
user_by_github_login.github_user_id = ActiveValue::set(Some(github_user_id));
|
user_by_github_login.github_user_id = ActiveValue::set(Some(github_user_id));
|
||||||
Ok(Some(user_by_github_login.update(tx).await?))
|
Ok(user_by_github_login.update(tx).await?)
|
||||||
} else {
|
} else {
|
||||||
let user = user::Entity::insert(user::ActiveModel {
|
let user = user::Entity::insert(user::ActiveModel {
|
||||||
email_address: ActiveValue::set(github_email.map(|email| email.into())),
|
email_address: ActiveValue::set(github_email.map(|email| email.into())),
|
||||||
|
@ -107,16 +123,8 @@ impl Database {
|
||||||
})
|
})
|
||||||
.exec_with_returning(&*tx)
|
.exec_with_returning(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(Some(user))
|
Ok(user)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Ok(user::Entity::find()
|
|
||||||
.filter(user::Column::GithubLogin.eq(github_login))
|
|
||||||
.one(tx)
|
|
||||||
.await?)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get_all_users returns the next page of users. To get more call again with
|
/// get_all_users returns the next page of users. To get more call again with
|
||||||
|
|
|
@ -9,6 +9,7 @@ pub mod channel_member;
|
||||||
pub mod channel_message;
|
pub mod channel_message;
|
||||||
pub mod channel_message_mention;
|
pub mod channel_message_mention;
|
||||||
pub mod contact;
|
pub mod contact;
|
||||||
|
pub mod contributor;
|
||||||
pub mod feature_flag;
|
pub mod feature_flag;
|
||||||
pub mod follower;
|
pub mod follower;
|
||||||
pub mod language_server;
|
pub mod language_server;
|
||||||
|
|
30
crates/collab/src/db/tables/contributor.rs
Normal file
30
crates/collab/src/db/tables/contributor.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
use crate::db::UserId;
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
/// A user who has signed the CLA.
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel, Serialize)]
|
||||||
|
#[sea_orm(table_name = "contributors")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub user_id: UserId,
|
||||||
|
pub signed_at: DateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::user::Entity",
|
||||||
|
from = "Column::UserId",
|
||||||
|
to = "super::user::Column::Id"
|
||||||
|
)]
|
||||||
|
User,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
|
impl Related<super::user::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::User.def()
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,8 @@ pub enum Relation {
|
||||||
ChannelMemberships,
|
ChannelMemberships,
|
||||||
#[sea_orm(has_many = "super::user_feature::Entity")]
|
#[sea_orm(has_many = "super::user_feature::Entity")]
|
||||||
UserFeatures,
|
UserFeatures,
|
||||||
|
#[sea_orm(has_one = "super::contributor::Entity")]
|
||||||
|
Contributor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Related<super::access_token::Entity> for Entity {
|
impl Related<super::access_token::Entity> for Entity {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
mod buffer_tests;
|
mod buffer_tests;
|
||||||
mod channel_tests;
|
mod channel_tests;
|
||||||
|
mod contributor_tests;
|
||||||
mod db_tests;
|
mod db_tests;
|
||||||
mod feature_flag_tests;
|
mod feature_flag_tests;
|
||||||
mod message_tests;
|
mod message_tests;
|
||||||
|
|
37
crates/collab/src/db/tests/contributor_tests.rs
Normal file
37
crates/collab/src/db/tests/contributor_tests.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use super::Database;
|
||||||
|
use crate::{db::NewUserParams, test_both_dbs};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
test_both_dbs!(
|
||||||
|
test_contributors,
|
||||||
|
test_contributors_postgres,
|
||||||
|
test_contributors_sqlite
|
||||||
|
);
|
||||||
|
|
||||||
|
async fn test_contributors(db: &Arc<Database>) {
|
||||||
|
db.create_user(
|
||||||
|
&format!("user1@example.com"),
|
||||||
|
false,
|
||||||
|
NewUserParams {
|
||||||
|
github_login: format!("user1"),
|
||||||
|
github_user_id: 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.user_id;
|
||||||
|
|
||||||
|
assert_eq!(db.get_contributors().await.unwrap(), Vec::<String>::new());
|
||||||
|
|
||||||
|
db.add_contributor("user1", 1, None).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
db.get_contributors().await.unwrap(),
|
||||||
|
vec!["user1".to_string()]
|
||||||
|
);
|
||||||
|
|
||||||
|
db.add_contributor("user2", 2, None).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
db.get_contributors().await.unwrap(),
|
||||||
|
vec!["user1".to_string(), "user2".to_string()]
|
||||||
|
);
|
||||||
|
}
|
|
@ -106,33 +106,24 @@ async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
|
||||||
.user_id;
|
.user_id;
|
||||||
|
|
||||||
let user = db
|
let user = db
|
||||||
.get_or_create_user_by_github_account("login1", None, None)
|
.get_or_create_user_by_github_account("login1", 1, None)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(user.id, user_id1);
|
assert_eq!(user.id, user_id1);
|
||||||
assert_eq!(&user.github_login, "login1");
|
assert_eq!(&user.github_login, "login1");
|
||||||
assert_eq!(user.github_user_id, Some(101));
|
assert_eq!(user.github_user_id, Some(101));
|
||||||
|
|
||||||
assert!(db
|
|
||||||
.get_or_create_user_by_github_account("non-existent-login", None, None)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.is_none());
|
|
||||||
|
|
||||||
let user = db
|
let user = db
|
||||||
.get_or_create_user_by_github_account("the-new-login2", Some(102), None)
|
.get_or_create_user_by_github_account("the-new-login2", 102, None)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(user.id, user_id2);
|
assert_eq!(user.id, user_id2);
|
||||||
assert_eq!(&user.github_login, "the-new-login2");
|
assert_eq!(&user.github_login, "the-new-login2");
|
||||||
assert_eq!(user.github_user_id, Some(102));
|
assert_eq!(user.github_user_id, Some(102));
|
||||||
|
|
||||||
let user = db
|
let user = db
|
||||||
.get_or_create_user_by_github_account("login3", Some(103), Some("user3@example.com"))
|
.get_or_create_user_by_github_account("login3", 103, Some("user3@example.com"))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(&user.github_login, "login3");
|
assert_eq!(&user.github_login, "login3");
|
||||||
assert_eq!(user.github_user_id, Some(103));
|
assert_eq!(user.github_user_id, Some(103));
|
||||||
|
|
Loading…
Reference in a new issue