mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
collab: Replace TransitionalUserService with CloudUserService (#56538)
This PR replaces the `TransitionalUserService` with the `CloudUserService`, as all of the calls are now all going through Cloud. This allows us to delete the `TransitionalUserService`, the `DatabaseUserService`, as well as the backing database queries that are no longer used. Closes CLO-758. Release Notes: - N/A
This commit is contained in:
parent
b13cb40b97
commit
6f1409b31c
6 changed files with 38 additions and 395 deletions
|
|
@ -4,7 +4,7 @@ use rpc::{
|
|||
ErrorCode, ErrorCodeExt,
|
||||
proto::{ChannelBufferVersion, VectorClockEntry},
|
||||
};
|
||||
use sea_orm::{ActiveValue, DbBackend, TryGetableMany};
|
||||
use sea_orm::{ActiveValue, TryGetableMany};
|
||||
|
||||
impl Database {
|
||||
#[cfg(feature = "test-support")]
|
||||
|
|
@ -704,57 +704,29 @@ impl Database {
|
|||
.await
|
||||
}
|
||||
|
||||
/// Returns the details for the specified channel member.
|
||||
pub async fn get_channel_participant_details(
|
||||
/// Returns the members for the given channel.
|
||||
#[cfg(feature = "test-support")]
|
||||
pub async fn get_channel_members(
|
||||
&self,
|
||||
channel: &Channel,
|
||||
filter: &str,
|
||||
limit: u64,
|
||||
) -> Result<(Vec<channel_member::Model>, Vec<user::Model>)> {
|
||||
let members = self
|
||||
.transaction(move |tx| async move {
|
||||
let mut query = channel_member::Entity::find()
|
||||
.find_also_related(user::Entity)
|
||||
.filter(channel_member::Column::ChannelId.eq(channel.root_id()));
|
||||
) -> Result<Vec<channel_member::Model>> {
|
||||
self.transaction(move |tx| async move {
|
||||
let members = channel_member::Entity::find()
|
||||
.filter(channel_member::Column::ChannelId.eq(channel.root_id()))
|
||||
.order_by(
|
||||
Expr::cust(
|
||||
"not role = 'admin', not role = 'member', not role = 'guest', not accepted",
|
||||
),
|
||||
sea_orm::Order::Asc,
|
||||
)
|
||||
.limit(limit)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
if cfg!(any(test, feature = "sqlite")) && self.pool.get_database_backend() == DbBackend::Sqlite {
|
||||
query = query.filter(Expr::cust_with_values(
|
||||
"UPPER(github_login) LIKE ?",
|
||||
[Self::fuzzy_like_string(&filter.to_uppercase())],
|
||||
))
|
||||
} else {
|
||||
query = query.filter(Expr::cust_with_values(
|
||||
"github_login ILIKE $1",
|
||||
[Self::fuzzy_like_string(filter)],
|
||||
))
|
||||
}
|
||||
let members = query.order_by(
|
||||
Expr::cust(
|
||||
"not role = 'admin', not role = 'member', not role = 'guest', not accepted, github_login",
|
||||
),
|
||||
sea_orm::Order::Asc,
|
||||
)
|
||||
.limit(limit)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
Ok(members)
|
||||
})
|
||||
.await?;
|
||||
|
||||
let mut users: Vec<user::Model> = Vec::with_capacity(members.len());
|
||||
|
||||
let members = members
|
||||
.into_iter()
|
||||
.map(|(member, user)| {
|
||||
if let Some(user) = user {
|
||||
users.push(user)
|
||||
}
|
||||
member
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok((members, users))
|
||||
Ok(members)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns whether the given user is an admin in the specified channel.
|
||||
|
|
|
|||
|
|
@ -44,35 +44,6 @@ impl Database {
|
|||
.await
|
||||
}
|
||||
|
||||
/// Returns all users by ID. There are no access checks here, so this should only be used internally.
|
||||
pub async fn get_users_by_ids(&self, ids: Vec<UserId>) -> Result<Vec<user::Model>> {
|
||||
if ids.len() >= 10000_usize {
|
||||
return Err(anyhow!("too many users"))?;
|
||||
}
|
||||
self.transaction(|tx| async {
|
||||
let tx = tx;
|
||||
Ok(user::Entity::find()
|
||||
.filter(user::Column::Id.is_in(ids.iter().copied()))
|
||||
.all(&*tx)
|
||||
.await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns a user by GitHub login. There are no access checks here, so this should only be used internally.
|
||||
pub async fn get_user_by_github_login(
|
||||
&self,
|
||||
github_login: &str,
|
||||
) -> Result<Option<user::Model>> {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(user::Entity::find()
|
||||
.filter(user::Column::GithubLogin.eq(github_login))
|
||||
.one(&*tx)
|
||||
.await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_or_create_user_by_github_account(
|
||||
&self,
|
||||
github_login: &str,
|
||||
|
|
@ -208,47 +179,4 @@ impl Database {
|
|||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Find users where github_login ILIKE name_query.
|
||||
pub async fn fuzzy_search_users(
|
||||
&self,
|
||||
name_query: &str,
|
||||
limit: u32,
|
||||
) -> Result<Vec<user::Model>> {
|
||||
self.transaction(|tx| async {
|
||||
let tx = tx;
|
||||
let like_string = Self::fuzzy_like_string(name_query);
|
||||
let query = "
|
||||
SELECT users.*
|
||||
FROM users
|
||||
WHERE github_login ILIKE $1
|
||||
ORDER BY github_login <-> $2
|
||||
LIMIT $3
|
||||
";
|
||||
|
||||
Ok(user::Entity::find()
|
||||
.from_raw_sql(Statement::from_sql_and_values(
|
||||
self.pool.get_database_backend(),
|
||||
query,
|
||||
vec![like_string.into(), name_query.into(), limit.into()],
|
||||
))
|
||||
.all(&*tx)
|
||||
.await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// fuzzy_like_string creates a string for matching in-order using fuzzy_search_users.
|
||||
/// e.g. "cir" would become "%c%i%r%"
|
||||
pub fn fuzzy_like_string(string: &str) -> String {
|
||||
let mut result = String::with_capacity(string.len() * 2 + 1);
|
||||
for c in string.chars() {
|
||||
if c.is_alphanumeric() {
|
||||
result.push('%');
|
||||
result.push(c);
|
||||
}
|
||||
}
|
||||
result.push('%');
|
||||
result
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,9 +20,7 @@ use serde::Deserialize;
|
|||
use std::{path::PathBuf, sync::Arc};
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::services::{
|
||||
CloudUserService, DatabaseUserService, TransitionalUserService, UserService,
|
||||
};
|
||||
use crate::services::{CloudUserService, UserService};
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
pub const REVISION: Option<&'static str> = option_env!("GITHUB_SHA");
|
||||
|
|
@ -265,19 +263,11 @@ impl AppState {
|
|||
} else {
|
||||
None
|
||||
},
|
||||
user_service: {
|
||||
let database_user_service = DatabaseUserService::new(db);
|
||||
let cloud_user_service = CloudUserService::new(
|
||||
http_client,
|
||||
config.zed_cloud_url().to_string(),
|
||||
config.zed_cloud_internal_api_key.clone(),
|
||||
);
|
||||
|
||||
Arc::new(TransitionalUserService::new(
|
||||
cloud_user_service,
|
||||
database_user_service,
|
||||
))
|
||||
},
|
||||
user_service: Arc::new(CloudUserService::new(
|
||||
http_client,
|
||||
config.zed_cloud_url().to_string(),
|
||||
config.zed_cloud_internal_api_key.clone(),
|
||||
)),
|
||||
config,
|
||||
};
|
||||
Ok(Arc::new(this))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context as _, anyhow};
|
||||
use async_trait::async_trait;
|
||||
use cloud_api_types::internal_api::{
|
||||
|
|
@ -13,7 +11,7 @@ use rpc::proto;
|
|||
use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::Result;
|
||||
use crate::db::{Channel, Database, UserId};
|
||||
use crate::db::{Channel, UserId};
|
||||
use crate::entities::User;
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
|
|
@ -40,60 +38,11 @@ pub trait UserService: Send + Sync + 'static {
|
|||
) -> Result<(Vec<proto::ChannelMember>, Vec<User>)>;
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
fn as_fake(&self) -> Arc<FakeUserService> {
|
||||
fn as_fake(&self) -> std::sync::Arc<FakeUserService> {
|
||||
panic!("called as_fake on a real `UserService`");
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`UserService`] implementation for transitioning from reading from the database to reading from Cloud.
|
||||
pub struct TransitionalUserService {
|
||||
cloud_user_service: CloudUserService,
|
||||
#[expect(dead_code)]
|
||||
database_user_service: DatabaseUserService,
|
||||
}
|
||||
|
||||
impl TransitionalUserService {
|
||||
pub fn new(
|
||||
cloud_user_service: CloudUserService,
|
||||
database_user_service: DatabaseUserService,
|
||||
) -> Self {
|
||||
Self {
|
||||
cloud_user_service,
|
||||
database_user_service,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UserService for TransitionalUserService {
|
||||
async fn get_users_by_ids(&self, ids: Vec<UserId>) -> Result<Vec<User>> {
|
||||
self.cloud_user_service.get_users_by_ids(ids).await
|
||||
}
|
||||
|
||||
async fn get_user_by_github_login(&self, github_login: &str) -> Result<Option<User>> {
|
||||
self.cloud_user_service
|
||||
.get_user_by_github_login(github_login)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn fuzzy_search_users(&self, query: &str, limit: u32) -> Result<Vec<User>> {
|
||||
self.cloud_user_service
|
||||
.fuzzy_search_users(query, limit)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn search_channel_members(
|
||||
&self,
|
||||
channel: &Channel,
|
||||
query: &str,
|
||||
limit: u32,
|
||||
) -> Result<(Vec<proto::ChannelMember>, Vec<User>)> {
|
||||
self.cloud_user_service
|
||||
.search_channel_members(channel, query, limit)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`UserService`] implementation backed by Cloud.
|
||||
pub struct CloudUserService {
|
||||
http_client: reqwest::Client,
|
||||
|
|
@ -271,65 +220,15 @@ impl From<internal_api::User> for User {
|
|||
}
|
||||
}
|
||||
|
||||
/// A [`UserService`] implementation backed by the database.
|
||||
pub struct DatabaseUserService {
|
||||
database: Arc<Database>,
|
||||
}
|
||||
|
||||
impl DatabaseUserService {
|
||||
pub fn new(database: Arc<Database>) -> Self {
|
||||
Self { database }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UserService for DatabaseUserService {
|
||||
async fn get_users_by_ids(&self, ids: Vec<UserId>) -> Result<Vec<User>> {
|
||||
let users = self.database.get_users_by_ids(ids).await?;
|
||||
|
||||
Ok(users.into_iter().map(User::from).collect())
|
||||
}
|
||||
|
||||
async fn get_user_by_github_login(&self, github_login: &str) -> Result<Option<User>> {
|
||||
let user = self.database.get_user_by_github_login(github_login).await?;
|
||||
|
||||
Ok(user.map(User::from))
|
||||
}
|
||||
|
||||
async fn fuzzy_search_users(&self, query: &str, limit: u32) -> Result<Vec<User>> {
|
||||
let users = self.database.fuzzy_search_users(query, limit).await?;
|
||||
|
||||
Ok(users.into_iter().map(User::from).collect())
|
||||
}
|
||||
|
||||
async fn search_channel_members(
|
||||
&self,
|
||||
channel: &Channel,
|
||||
query: &str,
|
||||
limit: u32,
|
||||
) -> Result<(Vec<proto::ChannelMember>, Vec<User>)> {
|
||||
let (members, users) = self
|
||||
.database
|
||||
.get_channel_participant_details(channel, query, limit as u64)
|
||||
.await?;
|
||||
|
||||
Ok((
|
||||
members
|
||||
.into_iter()
|
||||
.map(proto::ChannelMember::from)
|
||||
.collect(),
|
||||
users.into_iter().map(User::from).collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
mod fake_user_service {
|
||||
use std::sync::Weak;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use collections::HashMap;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::db::Database;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
|||
|
|
@ -37,10 +37,7 @@ async fn test_channels(db: &Arc<Database>) {
|
|||
.unwrap();
|
||||
|
||||
let replace_channel = db.get_channel(replace_id, a_id).await.unwrap();
|
||||
let (members, _) = db
|
||||
.get_channel_participant_details(&replace_channel, "", 10)
|
||||
.await
|
||||
.unwrap();
|
||||
let members = db.get_channel_members(&replace_channel, 10).await.unwrap();
|
||||
let ids = members.into_iter().map(|m| m.user_id).collect::<Vec<_>>();
|
||||
assert_eq!(ids, &[a_id, b_id]);
|
||||
|
||||
|
|
@ -191,10 +188,7 @@ async fn test_channel_invites(db: &Arc<Database>) {
|
|||
assert_eq!(user_3_invites, &[channel_1_1_id]);
|
||||
|
||||
let channel_1_1 = db.get_channel(channel_1_1_id, user_1).await.unwrap();
|
||||
let (members, _) = db
|
||||
.get_channel_participant_details(&channel_1_1, "", 100)
|
||||
.await
|
||||
.unwrap();
|
||||
let members = db.get_channel_members(&channel_1_1, 100).await.unwrap();
|
||||
let mut members = members
|
||||
.into_iter()
|
||||
.map(proto::ChannelMember::from)
|
||||
|
|
@ -231,10 +225,7 @@ async fn test_channel_invites(db: &Arc<Database>) {
|
|||
.unwrap();
|
||||
|
||||
let channel_1_3 = db.get_channel(channel_1_3_id, user_1).await.unwrap();
|
||||
let (members, _) = db
|
||||
.get_channel_participant_details(&channel_1_3, "", 100)
|
||||
.await
|
||||
.unwrap();
|
||||
let members = db.get_channel_members(&channel_1_3, 100).await.unwrap();
|
||||
let members = members
|
||||
.into_iter()
|
||||
.map(proto::ChannelMember::from)
|
||||
|
|
@ -735,10 +726,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
.unwrap();
|
||||
|
||||
let public_channel = db.get_channel(public_channel_id, admin).await.unwrap();
|
||||
let (members, _) = db
|
||||
.get_channel_participant_details(&public_channel, "", 100)
|
||||
.await
|
||||
.unwrap();
|
||||
let members = db.get_channel_members(&public_channel, 100).await.unwrap();
|
||||
let mut members = members
|
||||
.into_iter()
|
||||
.map(proto::ChannelMember::from)
|
||||
|
|
@ -814,10 +802,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
);
|
||||
|
||||
let public_channel = db.get_channel(public_channel_id, admin).await.unwrap();
|
||||
let (members, _) = db
|
||||
.get_channel_participant_details(&public_channel, "", 100)
|
||||
.await
|
||||
.unwrap();
|
||||
let members = db.get_channel_members(&public_channel, 100).await.unwrap();
|
||||
let mut members = members
|
||||
.into_iter()
|
||||
.map(proto::ChannelMember::from)
|
||||
|
|
@ -854,10 +839,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
|
||||
// currently people invited to parent channels are not shown here
|
||||
let public_channel = db.get_channel(public_channel_id, admin).await.unwrap();
|
||||
let (members, _) = db
|
||||
.get_channel_participant_details(&public_channel, "", 100)
|
||||
.await
|
||||
.unwrap();
|
||||
let members = db.get_channel_members(&public_channel, 100).await.unwrap();
|
||||
let mut members = members
|
||||
.into_iter()
|
||||
.map(proto::ChannelMember::from)
|
||||
|
|
@ -927,10 +909,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
.unwrap();
|
||||
|
||||
let public_channel = db.get_channel(public_channel_id, admin).await.unwrap();
|
||||
let (members, _) = db
|
||||
.get_channel_participant_details(&public_channel, "", 100)
|
||||
.await
|
||||
.unwrap();
|
||||
let members = db.get_channel_members(&public_channel, 100).await.unwrap();
|
||||
let mut members = members
|
||||
.into_iter()
|
||||
.map(proto::ChannelMember::from)
|
||||
|
|
|
|||
|
|
@ -7,71 +7,6 @@ use pretty_assertions::assert_eq;
|
|||
use rpc::ConnectionId;
|
||||
use std::sync::Arc;
|
||||
|
||||
test_both_dbs!(
|
||||
test_get_users,
|
||||
test_get_users_by_ids_postgres,
|
||||
test_get_users_by_ids_sqlite
|
||||
);
|
||||
|
||||
async fn test_get_users(db: &Arc<Database>) {
|
||||
let mut user_ids = Vec::new();
|
||||
for i in 1..=4 {
|
||||
let user = db
|
||||
.create_user(
|
||||
&format!("user{i}@example.com"),
|
||||
None,
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: format!("user{i}"),
|
||||
github_user_id: i,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
user_ids.push(user.user_id);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
db.get_users_by_ids(user_ids.clone())
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|user| (
|
||||
user.id,
|
||||
user.github_login,
|
||||
user.github_user_id,
|
||||
user.email_address
|
||||
))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
(
|
||||
user_ids[0],
|
||||
"user1".to_string(),
|
||||
1,
|
||||
Some("user1@example.com".to_string()),
|
||||
),
|
||||
(
|
||||
user_ids[1],
|
||||
"user2".to_string(),
|
||||
2,
|
||||
Some("user2@example.com".to_string()),
|
||||
),
|
||||
(
|
||||
user_ids[2],
|
||||
"user3".to_string(),
|
||||
3,
|
||||
Some("user3@example.com".to_string()),
|
||||
),
|
||||
(
|
||||
user_ids[3],
|
||||
"user4".to_string(),
|
||||
4,
|
||||
Some("user4@example.com".to_string()),
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_add_contacts,
|
||||
test_add_contacts_postgres,
|
||||
|
|
@ -329,66 +264,6 @@ async fn test_project_count(db: &Arc<Database>) {
|
|||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzy_like_string() {
|
||||
assert_eq!(Database::fuzzy_like_string("abcd"), "%a%b%c%d%");
|
||||
assert_eq!(Database::fuzzy_like_string("x y"), "%x%y%");
|
||||
assert_eq!(Database::fuzzy_like_string(" z "), "%z%");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_fuzzy_search_users(cx: &mut gpui::TestAppContext) {
|
||||
// In CI, only run postgres tests on Linux (where we have the postgres service).
|
||||
// Locally, always run them (assuming postgres is available).
|
||||
if std::env::var("CI").is_ok() && !cfg!(target_os = "linux") {
|
||||
return;
|
||||
}
|
||||
let test_db = TestDb::postgres(cx.executor());
|
||||
let db = test_db.db();
|
||||
for (i, github_login) in [
|
||||
"California",
|
||||
"colorado",
|
||||
"oregon",
|
||||
"washington",
|
||||
"florida",
|
||||
"delaware",
|
||||
"rhode-island",
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
db.create_user(
|
||||
&format!("{github_login}@example.com"),
|
||||
None,
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: github_login.into(),
|
||||
github_user_id: i as i32,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
fuzzy_search_user_names(db, "clr").await,
|
||||
&["colorado", "California"]
|
||||
);
|
||||
assert_eq!(
|
||||
fuzzy_search_user_names(db, "ro").await,
|
||||
&["rhode-island", "colorado", "oregon"],
|
||||
);
|
||||
|
||||
async fn fuzzy_search_user_names(db: &Database, query: &str) -> Vec<String> {
|
||||
db.fuzzy_search_users(query, 10)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|user| user.github_login)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_upsert_shared_thread,
|
||||
test_upsert_shared_thread_postgres,
|
||||
|
|
|
|||
Loading…
Reference in a new issue