collab: Route get_users_by_ids through Cloud (#56105)
This PR makes it so we route the `UserService::get_users_by_ids` call through Cloud instead of hitting the database. We've introduced a new `CloudUserService` that will fetch the users from Cloud using the internal API. Note that we've only implemented the `get_users_by_ids` method on this service, as the endpoints for the other methods don't yet exist. We have also introduced a `TransitionalUserService` for the purposes of gradually transitioning these calls over to Cloud. Right now it uses the `CloudUserService` for the `get_users_by_ids` implementation, but then uses the `DatabaseUserService` for the other methods. Closes CLO-740. Release Notes: - N/A
This commit is contained in:
1 parent
45362c24f1
commit
463beda9ca
7 files changed
+196
-3
No files matched your search
@@ -1,4 +1,5 @@
|
||||
mod extension;
|
||||
pub mod internal_api;
|
||||
mod known_or_unknown;
|
||||
mod plan;
|
||||
mod timestamp;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub legacy_user_id: i32,
|
||||
pub github_login: String,
|
||||
pub github_user_id: i32,
|
||||
pub name: Option<String>,
|
||||
pub admin: bool,
|
||||
pub connected_once: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct LookUpUsersByLegacyIdBody {
|
||||
pub legacy_user_ids: Vec<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct LookUpUsersByLegacyIdResponse {
|
||||
pub users: Vec<User>,
|
||||
}
|
||||
@@ -3,6 +3,7 @@ DATABASE_URL = "postgres://postgres@localhost/zed"
|
||||
DATABASE_MAX_CONNECTIONS = 5
|
||||
HTTP_PORT = 8080
|
||||
ZED_ENVIRONMENT = "development"
|
||||
ZED_CLOUD_INTERNAL_API_KEY = "internal-api-key-secret"
|
||||
LIVEKIT_SERVER = "http://localhost:7880"
|
||||
LIVEKIT_KEY = "devkey"
|
||||
LIVEKIT_SECRET = "secret"
|
||||
|
||||
@@ -92,6 +92,11 @@ spec:
|
||||
secretKeyRef:
|
||||
name: zed-client
|
||||
key: checksum-seed
|
||||
- name: ZED_CLOUD_INTERNAL_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: zed-cloud
|
||||
key: internal-api-key
|
||||
- name: LIVEKIT_SERVER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
|
||||
@@ -20,7 +20,9 @@ use serde::Deserialize;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::services::{DatabaseUserService, UserService};
|
||||
use crate::services::{
|
||||
CloudUserService, DatabaseUserService, TransitionalUserService, UserService,
|
||||
};
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
pub const REVISION: Option<&'static str> = option_env!("GITHUB_SHA");
|
||||
@@ -139,6 +141,7 @@ pub struct Config {
|
||||
pub kinesis_access_key: Option<String>,
|
||||
pub kinesis_secret_key: Option<String>,
|
||||
pub zed_environment: Arc<str>,
|
||||
pub zed_cloud_internal_api_key: String,
|
||||
pub zed_client_checksum_seed: Option<String>,
|
||||
}
|
||||
|
||||
@@ -176,6 +179,7 @@ impl Config {
|
||||
rust_log: None,
|
||||
log_json: None,
|
||||
zed_environment: "test".into(),
|
||||
zed_cloud_internal_api_key: "test-internal-api-key".into(),
|
||||
blob_store_url: None,
|
||||
blob_store_region: None,
|
||||
blob_store_access_key: None,
|
||||
@@ -252,7 +256,7 @@ impl AppState {
|
||||
let db = Arc::new(db);
|
||||
let this = Self {
|
||||
db: db.clone(),
|
||||
http_client: Some(http_client),
|
||||
http_client: Some(http_client.clone()),
|
||||
livekit_client,
|
||||
blob_store_client: build_blob_store_client(&config).await.log_err(),
|
||||
executor,
|
||||
@@ -261,7 +265,19 @@ impl AppState {
|
||||
} else {
|
||||
None
|
||||
},
|
||||
user_service: Arc::new(DatabaseUserService::new(db)),
|
||||
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,
|
||||
))
|
||||
},
|
||||
config,
|
||||
};
|
||||
Ok(Arc::new(this))
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context as _, anyhow};
|
||||
use async_trait::async_trait;
|
||||
use cloud_api_types::internal_api::{
|
||||
self, LookUpUsersByLegacyIdBody, LookUpUsersByLegacyIdResponse,
|
||||
};
|
||||
use rpc::proto;
|
||||
|
||||
use crate::Result;
|
||||
@@ -36,6 +40,149 @@ pub trait UserService: Send + Sync + 'static {
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`UserService`] implementation for transitioning from reading from the database to reading from Cloud.
|
||||
pub struct TransitionalUserService {
|
||||
cloud_user_service: CloudUserService,
|
||||
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.database_user_service
|
||||
.get_user_by_github_login(github_login)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn fuzzy_search_users(&self, query: &str, limit: u32) -> Result<Vec<User>> {
|
||||
self.database_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.database_user_service
|
||||
.search_channel_members(channel, query, limit)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`UserService`] implementation backed by Cloud.
|
||||
pub struct CloudUserService {
|
||||
http_client: reqwest::Client,
|
||||
zed_cloud_url: String,
|
||||
internal_api_key: String,
|
||||
}
|
||||
|
||||
impl CloudUserService {
|
||||
pub fn new(
|
||||
http_client: reqwest::Client,
|
||||
zed_cloud_url: String,
|
||||
internal_api_key: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
http_client,
|
||||
zed_cloud_url,
|
||||
internal_api_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UserService for CloudUserService {
|
||||
async fn get_users_by_ids(&self, ids: Vec<UserId>) -> Result<Vec<User>> {
|
||||
let response = self
|
||||
.http_client
|
||||
.post(format!(
|
||||
"{}/internal/users/look_up_by_legacy_id",
|
||||
&self.zed_cloud_url
|
||||
))
|
||||
.header("Content-Type", "application/json")
|
||||
.header(
|
||||
"Authorization",
|
||||
format!("Bearer {}", &self.internal_api_key),
|
||||
)
|
||||
.json(&LookUpUsersByLegacyIdBody {
|
||||
legacy_user_ids: ids.into_iter().map(|id| id.0).collect(),
|
||||
})
|
||||
.send()
|
||||
.await
|
||||
.context("failed to get users by legacy IDs")?;
|
||||
|
||||
match response.error_for_status() {
|
||||
Ok(response) => {
|
||||
let response_body: LookUpUsersByLegacyIdResponse = response
|
||||
.json()
|
||||
.await
|
||||
.context("failed to parse response body")?;
|
||||
|
||||
Ok(response_body.users.into_iter().map(User::from).collect())
|
||||
}
|
||||
Err(_err) => Err(anyhow!("failed to get users by legacy IDs"))?,
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_user_by_github_login(&self, github_login: &str) -> Result<Option<User>> {
|
||||
let _ = github_login;
|
||||
|
||||
unimplemented!("not yet implemented in Cloud")
|
||||
}
|
||||
|
||||
async fn fuzzy_search_users(&self, query: &str, limit: u32) -> Result<Vec<User>> {
|
||||
let _ = query;
|
||||
let _ = limit;
|
||||
|
||||
unimplemented!("not yet implemented in Cloud")
|
||||
}
|
||||
|
||||
async fn search_channel_members(
|
||||
&self,
|
||||
channel: &Channel,
|
||||
query: &str,
|
||||
limit: u32,
|
||||
) -> Result<(Vec<proto::ChannelMember>, Vec<User>)> {
|
||||
let _ = channel;
|
||||
let _ = query;
|
||||
let _ = limit;
|
||||
|
||||
unimplemented!("not yet implemented in Cloud")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<internal_api::User> for User {
|
||||
fn from(user: internal_api::User) -> Self {
|
||||
Self {
|
||||
id: UserId(user.legacy_user_id),
|
||||
github_login: user.github_login,
|
||||
github_user_id: user.github_user_id,
|
||||
name: user.name,
|
||||
admin: user.admin,
|
||||
connected_once: user.connected_once,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`UserService`] implementation backed by the database.
|
||||
pub struct DatabaseUserService {
|
||||
database: Arc<Database>,
|
||||
|
||||
@@ -592,6 +592,7 @@ impl TestServer {
|
||||
rust_log: None,
|
||||
log_json: None,
|
||||
zed_environment: "test".into(),
|
||||
zed_cloud_internal_api_key: "test-internal-api-key".into(),
|
||||
blob_store_url: None,
|
||||
blob_store_region: None,
|
||||
blob_store_access_key: None,
|
||||
|
||||
Reference in new issue
Block a user