Detailed changes
@@ -1,8 +1,7 @@
use crate::{
auth,
- db::{Invite, NewSignup, NewUserParams, User, UserId, WaitlistSummary},
- rpc::{self, ResultExt},
- AppState, Error, Result,
+ db::{User, UserId},
+ rpc, AppState, Error, Result,
};
use anyhow::anyhow;
use axum::{
@@ -11,7 +10,7 @@ use axum::{
http::{self, Request, StatusCode},
middleware::{self, Next},
response::IntoResponse,
- routing::{get, post, put},
+ routing::{get, post},
Extension, Json, Router,
};
use axum_extra::response::ErasedJson;
@@ -23,18 +22,9 @@ use tracing::instrument;
pub fn routes(rpc_server: Arc<rpc::Server>, state: Arc<AppState>) -> Router<Body> {
Router::new()
.route("/user", get(get_authenticated_user))
- .route("/users", get(get_users).post(create_user))
- .route("/users/:id", put(update_user).delete(destroy_user))
.route("/users/:id/access_tokens", post(create_access_token))
- .route("/users_with_no_invites", get(get_users_with_no_invites))
- .route("/invite_codes/:code", get(get_user_for_invite_code))
.route("/panic", post(trace_panic))
.route("/rpc_server_snapshot", get(get_rpc_server_snapshot))
- .route("/signups", post(create_signup))
- .route("/signups_summary", get(get_waitlist_summary))
- .route("/user_invites", post(create_invite_from_code))
- .route("/unsent_invites", get(get_unsent_invites))
- .route("/sent_invites", post(record_sent_invites))
.layer(
ServiceBuilder::new()
.layer(Extension(state))
@@ -104,28 +94,6 @@ async fn get_authenticated_user(
return Ok(Json(AuthenticatedUserResponse { user, metrics_id }));
}
-#[derive(Debug, Deserialize)]
-struct GetUsersQueryParams {
- query: Option<String>,
- page: Option<u32>,
- limit: Option<u32>,
-}
-
-async fn get_users(
- Query(params): Query<GetUsersQueryParams>,
- Extension(app): Extension<Arc<AppState>>,
-) -> Result<Json<Vec<User>>> {
- let limit = params.limit.unwrap_or(100);
- let users = if let Some(query) = params.query {
- app.db.fuzzy_search_users(&query, limit).await?
- } else {
- app.db
- .get_all_users(params.page.unwrap_or(0), limit)
- .await?
- };
- Ok(Json(users))
-}
-
#[derive(Deserialize, Debug)]
struct CreateUserParams {
github_user_id: i32,
@@ -145,119 +113,6 @@ struct CreateUserResponse {
metrics_id: String,
}
-async fn create_user(
- Json(params): Json<CreateUserParams>,
- Extension(app): Extension<Arc<AppState>>,
- Extension(rpc_server): Extension<Arc<rpc::Server>>,
-) -> Result<Json<Option<CreateUserResponse>>> {
- let user = NewUserParams {
- github_login: params.github_login,
- github_user_id: params.github_user_id,
- invite_count: params.invite_count,
- };
-
- // Creating a user via the normal signup process
- let result = if let Some(email_confirmation_code) = params.email_confirmation_code {
- if let Some(result) = app
- .db
- .create_user_from_invite(
- &Invite {
- email_address: params.email_address,
- email_confirmation_code,
- },
- user,
- )
- .await?
- {
- result
- } else {
- return Ok(Json(None));
- }
- }
- // Creating a user as an admin
- else if params.admin {
- app.db
- .create_user(¶ms.email_address, false, user)
- .await?
- } else {
- Err(Error::Http(
- StatusCode::UNPROCESSABLE_ENTITY,
- "email confirmation code is required".into(),
- ))?
- };
-
- if let Some(inviter_id) = result.inviting_user_id {
- rpc_server
- .invite_code_redeemed(inviter_id, result.user_id)
- .await
- .trace_err();
- }
-
- let user = app
- .db
- .get_user_by_id(result.user_id)
- .await?
- .ok_or_else(|| anyhow!("couldn't find the user we just created"))?;
-
- Ok(Json(Some(CreateUserResponse {
- user,
- metrics_id: result.metrics_id,
- signup_device_id: result.signup_device_id,
- })))
-}
-
-#[derive(Deserialize)]
-struct UpdateUserParams {
- admin: Option<bool>,
- invite_count: Option<i32>,
-}
-
-async fn update_user(
- Path(user_id): Path<i32>,
- Json(params): Json<UpdateUserParams>,
- Extension(app): Extension<Arc<AppState>>,
- Extension(rpc_server): Extension<Arc<rpc::Server>>,
-) -> Result<()> {
- let user_id = UserId(user_id);
-
- if let Some(admin) = params.admin {
- app.db.set_user_is_admin(user_id, admin).await?;
- }
-
- if let Some(invite_count) = params.invite_count {
- app.db
- .set_invite_count_for_user(user_id, invite_count)
- .await?;
- rpc_server.invite_count_updated(user_id).await.trace_err();
- }
-
- Ok(())
-}
-
-async fn destroy_user(
- Path(user_id): Path<i32>,
- Extension(app): Extension<Arc<AppState>>,
-) -> Result<()> {
- app.db.destroy_user(UserId(user_id)).await?;
- Ok(())
-}
-
-#[derive(Debug, Deserialize)]
-struct GetUsersWithNoInvites {
- invited_by_another_user: bool,
-}
-
-async fn get_users_with_no_invites(
- Query(params): Query<GetUsersWithNoInvites>,
- Extension(app): Extension<Arc<AppState>>,
-) -> Result<Json<Vec<User>>> {
- Ok(Json(
- app.db
- .get_users_with_no_invites(params.invited_by_another_user)
- .await?,
- ))
-}
-
#[derive(Debug, Deserialize)]
struct Panic {
version: String,
@@ -327,69 +182,3 @@ async fn create_access_token(
encrypted_access_token,
}))
}
-
-async fn get_user_for_invite_code(
- Path(code): Path<String>,
- Extension(app): Extension<Arc<AppState>>,
-) -> Result<Json<User>> {
- Ok(Json(app.db.get_user_for_invite_code(&code).await?))
-}
-
-async fn create_signup(
- Json(params): Json<NewSignup>,
- Extension(app): Extension<Arc<AppState>>,
-) -> Result<()> {
- app.db.create_signup(¶ms).await?;
- Ok(())
-}
-
-async fn get_waitlist_summary(
- Extension(app): Extension<Arc<AppState>>,
-) -> Result<Json<WaitlistSummary>> {
- Ok(Json(app.db.get_waitlist_summary().await?))
-}
-
-#[derive(Deserialize)]
-pub struct CreateInviteFromCodeParams {
- invite_code: String,
- email_address: String,
- device_id: Option<String>,
- #[serde(default)]
- added_to_mailing_list: bool,
-}
-
-async fn create_invite_from_code(
- Json(params): Json<CreateInviteFromCodeParams>,
- Extension(app): Extension<Arc<AppState>>,
-) -> Result<Json<Invite>> {
- Ok(Json(
- app.db
- .create_invite_from_code(
- ¶ms.invite_code,
- ¶ms.email_address,
- params.device_id.as_deref(),
- params.added_to_mailing_list,
- )
- .await?,
- ))
-}
-
-#[derive(Deserialize)]
-pub struct GetUnsentInvitesParams {
- pub count: usize,
-}
-
-async fn get_unsent_invites(
- Query(params): Query<GetUnsentInvitesParams>,
- Extension(app): Extension<Arc<AppState>>,
-) -> Result<Json<Vec<Invite>>> {
- Ok(Json(app.db.get_unsent_invites(params.count).await?))
-}
-
-async fn record_sent_invites(
- Json(params): Json<Vec<Invite>>,
- Extension(app): Extension<Arc<AppState>>,
-) -> Result<()> {
- app.db.record_sent_invites(¶ms).await?;
- Ok(())
-}
@@ -7,5 +7,4 @@ pub mod contacts;
pub mod projects;
pub mod rooms;
pub mod servers;
-pub mod signups;
pub mod users;
@@ -1,349 +0,0 @@
-use super::*;
-use hyper::StatusCode;
-
-impl Database {
- pub async fn create_invite_from_code(
- &self,
- code: &str,
- email_address: &str,
- device_id: Option<&str>,
- added_to_mailing_list: bool,
- ) -> Result<Invite> {
- self.transaction(|tx| async move {
- let existing_user = user::Entity::find()
- .filter(user::Column::EmailAddress.eq(email_address))
- .one(&*tx)
- .await?;
-
- if existing_user.is_some() {
- Err(anyhow!("email address is already in use"))?;
- }
-
- let inviting_user_with_invites = match user::Entity::find()
- .filter(
- user::Column::InviteCode
- .eq(code)
- .and(user::Column::InviteCount.gt(0)),
- )
- .one(&*tx)
- .await?
- {
- Some(inviting_user) => inviting_user,
- None => {
- return Err(Error::Http(
- StatusCode::UNAUTHORIZED,
- "unable to find an invite code with invites remaining".to_string(),
- ))?
- }
- };
- user::Entity::update_many()
- .filter(
- user::Column::Id
- .eq(inviting_user_with_invites.id)
- .and(user::Column::InviteCount.gt(0)),
- )
- .col_expr(
- user::Column::InviteCount,
- Expr::col(user::Column::InviteCount).sub(1),
- )
- .exec(&*tx)
- .await?;
-
- let signup = signup::Entity::insert(signup::ActiveModel {
- email_address: ActiveValue::set(email_address.into()),
- email_confirmation_code: ActiveValue::set(random_email_confirmation_code()),
- email_confirmation_sent: ActiveValue::set(false),
- inviting_user_id: ActiveValue::set(Some(inviting_user_with_invites.id)),
- platform_linux: ActiveValue::set(false),
- platform_mac: ActiveValue::set(false),
- platform_windows: ActiveValue::set(false),
- platform_unknown: ActiveValue::set(true),
- device_id: ActiveValue::set(device_id.map(|device_id| device_id.into())),
- added_to_mailing_list: ActiveValue::set(added_to_mailing_list),
- ..Default::default()
- })
- .on_conflict(
- OnConflict::column(signup::Column::EmailAddress)
- .update_column(signup::Column::InvitingUserId)
- .to_owned(),
- )
- .exec_with_returning(&*tx)
- .await?;
-
- Ok(Invite {
- email_address: signup.email_address,
- email_confirmation_code: signup.email_confirmation_code,
- })
- })
- .await
- }
-
- pub async fn create_user_from_invite(
- &self,
- invite: &Invite,
- user: NewUserParams,
- ) -> Result<Option<NewUserResult>> {
- self.transaction(|tx| async {
- let tx = tx;
- let signup = signup::Entity::find()
- .filter(
- signup::Column::EmailAddress
- .eq(invite.email_address.as_str())
- .and(
- signup::Column::EmailConfirmationCode
- .eq(invite.email_confirmation_code.as_str()),
- ),
- )
- .one(&*tx)
- .await?
- .ok_or_else(|| Error::Http(StatusCode::NOT_FOUND, "no such invite".to_string()))?;
-
- if signup.user_id.is_some() {
- return Ok(None);
- }
-
- let user = user::Entity::insert(user::ActiveModel {
- email_address: ActiveValue::set(Some(invite.email_address.clone())),
- github_login: ActiveValue::set(user.github_login.clone()),
- github_user_id: ActiveValue::set(Some(user.github_user_id)),
- admin: ActiveValue::set(false),
- invite_count: ActiveValue::set(user.invite_count),
- invite_code: ActiveValue::set(Some(random_invite_code())),
- metrics_id: ActiveValue::set(Uuid::new_v4()),
- ..Default::default()
- })
- .on_conflict(
- OnConflict::column(user::Column::GithubLogin)
- .update_columns([
- user::Column::EmailAddress,
- user::Column::GithubUserId,
- user::Column::Admin,
- ])
- .to_owned(),
- )
- .exec_with_returning(&*tx)
- .await?;
-
- let mut signup = signup.into_active_model();
- signup.user_id = ActiveValue::set(Some(user.id));
- let signup = signup.update(&*tx).await?;
-
- if let Some(inviting_user_id) = signup.inviting_user_id {
- let (user_id_a, user_id_b, a_to_b) = if inviting_user_id < user.id {
- (inviting_user_id, user.id, true)
- } else {
- (user.id, inviting_user_id, false)
- };
-
- contact::Entity::insert(contact::ActiveModel {
- user_id_a: ActiveValue::set(user_id_a),
- user_id_b: ActiveValue::set(user_id_b),
- a_to_b: ActiveValue::set(a_to_b),
- should_notify: ActiveValue::set(true),
- accepted: ActiveValue::set(true),
- ..Default::default()
- })
- .on_conflict(OnConflict::new().do_nothing().to_owned())
- .exec_without_returning(&*tx)
- .await?;
- }
-
- Ok(Some(NewUserResult {
- user_id: user.id,
- metrics_id: user.metrics_id.to_string(),
- inviting_user_id: signup.inviting_user_id,
- signup_device_id: signup.device_id,
- }))
- })
- .await
- }
-
- pub async fn set_invite_count_for_user(&self, id: UserId, count: i32) -> Result<()> {
- self.transaction(|tx| async move {
- if count > 0 {
- user::Entity::update_many()
- .filter(
- user::Column::Id
- .eq(id)
- .and(user::Column::InviteCode.is_null()),
- )
- .set(user::ActiveModel {
- invite_code: ActiveValue::set(Some(random_invite_code())),
- ..Default::default()
- })
- .exec(&*tx)
- .await?;
- }
-
- user::Entity::update_many()
- .filter(user::Column::Id.eq(id))
- .set(user::ActiveModel {
- invite_count: ActiveValue::set(count),
- ..Default::default()
- })
- .exec(&*tx)
- .await?;
- Ok(())
- })
- .await
- }
-
- pub async fn get_invite_code_for_user(&self, id: UserId) -> Result<Option<(String, i32)>> {
- self.transaction(|tx| async move {
- match user::Entity::find_by_id(id).one(&*tx).await? {
- Some(user) if user.invite_code.is_some() => {
- Ok(Some((user.invite_code.unwrap(), user.invite_count)))
- }
- _ => Ok(None),
- }
- })
- .await
- }
-
- pub async fn get_user_for_invite_code(&self, code: &str) -> Result<User> {
- self.transaction(|tx| async move {
- user::Entity::find()
- .filter(user::Column::InviteCode.eq(code))
- .one(&*tx)
- .await?
- .ok_or_else(|| {
- Error::Http(
- StatusCode::NOT_FOUND,
- "that invite code does not exist".to_string(),
- )
- })
- })
- .await
- }
-
- pub async fn create_signup(&self, signup: &NewSignup) -> Result<()> {
- self.transaction(|tx| async move {
- signup::Entity::insert(signup::ActiveModel {
- email_address: ActiveValue::set(signup.email_address.clone()),
- email_confirmation_code: ActiveValue::set(random_email_confirmation_code()),
- email_confirmation_sent: ActiveValue::set(false),
- platform_mac: ActiveValue::set(signup.platform_mac),
- platform_windows: ActiveValue::set(signup.platform_windows),
- platform_linux: ActiveValue::set(signup.platform_linux),
- platform_unknown: ActiveValue::set(false),
- editor_features: ActiveValue::set(Some(signup.editor_features.clone())),
- programming_languages: ActiveValue::set(Some(signup.programming_languages.clone())),
- device_id: ActiveValue::set(signup.device_id.clone()),
- added_to_mailing_list: ActiveValue::set(signup.added_to_mailing_list),
- ..Default::default()
- })
- .on_conflict(
- OnConflict::column(signup::Column::EmailAddress)
- .update_columns([
- signup::Column::PlatformMac,
- signup::Column::PlatformWindows,
- signup::Column::PlatformLinux,
- signup::Column::EditorFeatures,
- signup::Column::ProgrammingLanguages,
- signup::Column::DeviceId,
- signup::Column::AddedToMailingList,
- ])
- .to_owned(),
- )
- .exec(&*tx)
- .await?;
- Ok(())
- })
- .await
- }
-
- pub async fn get_signup(&self, email_address: &str) -> Result<signup::Model> {
- self.transaction(|tx| async move {
- let signup = signup::Entity::find()
- .filter(signup::Column::EmailAddress.eq(email_address))
- .one(&*tx)
- .await?
- .ok_or_else(|| {
- anyhow!("signup with email address {} doesn't exist", email_address)
- })?;
-
- Ok(signup)
- })
- .await
- }
-
- pub async fn get_waitlist_summary(&self) -> Result<WaitlistSummary> {
- self.transaction(|tx| async move {
- let query = "
- SELECT
- COUNT(*) as count,
- COALESCE(SUM(CASE WHEN platform_linux THEN 1 ELSE 0 END), 0) as linux_count,
- COALESCE(SUM(CASE WHEN platform_mac THEN 1 ELSE 0 END), 0) as mac_count,
- COALESCE(SUM(CASE WHEN platform_windows THEN 1 ELSE 0 END), 0) as windows_count,
- COALESCE(SUM(CASE WHEN platform_unknown THEN 1 ELSE 0 END), 0) as unknown_count
- FROM (
- SELECT *
- FROM signups
- WHERE
- NOT email_confirmation_sent
- ) AS unsent
- ";
- Ok(
- WaitlistSummary::find_by_statement(Statement::from_sql_and_values(
- self.pool.get_database_backend(),
- query.into(),
- vec![],
- ))
- .one(&*tx)
- .await?
- .ok_or_else(|| anyhow!("invalid result"))?,
- )
- })
- .await
- }
-
- pub async fn record_sent_invites(&self, invites: &[Invite]) -> Result<()> {
- let emails = invites
- .iter()
- .map(|s| s.email_address.as_str())
- .collect::<Vec<_>>();
- self.transaction(|tx| async {
- let tx = tx;
- signup::Entity::update_many()
- .filter(signup::Column::EmailAddress.is_in(emails.iter().copied()))
- .set(signup::ActiveModel {
- email_confirmation_sent: ActiveValue::set(true),
- ..Default::default()
- })
- .exec(&*tx)
- .await?;
- Ok(())
- })
- .await
- }
-
- pub async fn get_unsent_invites(&self, count: usize) -> Result<Vec<Invite>> {
- self.transaction(|tx| async move {
- Ok(signup::Entity::find()
- .select_only()
- .column(signup::Column::EmailAddress)
- .column(signup::Column::EmailConfirmationCode)
- .filter(
- signup::Column::EmailConfirmationSent.eq(false).and(
- signup::Column::PlatformMac
- .eq(true)
- .or(signup::Column::PlatformUnknown.eq(true)),
- ),
- )
- .order_by_asc(signup::Column::CreatedAt)
- .limit(count as u64)
- .into_model()
- .all(&*tx)
- .await?)
- })
- .await
- }
-}
-
-fn random_invite_code() -> String {
- nanoid::nanoid!(16)
-}
-
-fn random_email_confirmation_code() -> String {
- nanoid::nanoid!(64)
-}
@@ -123,27 +123,6 @@ impl Database {
.await
}
- pub async fn get_users_with_no_invites(
- &self,
- invited_by_another_user: bool,
- ) -> Result<Vec<User>> {
- self.transaction(|tx| async move {
- Ok(user::Entity::find()
- .filter(
- user::Column::InviteCount
- .eq(0)
- .and(if invited_by_another_user {
- user::Column::InviterId.is_not_null()
- } else {
- user::Column::InviterId.is_null()
- }),
- )
- .all(&*tx)
- .await?)
- })
- .await
- }
-
pub async fn get_user_metrics_id(&self, id: UserId) -> Result<String> {
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryAs {
@@ -163,21 +142,6 @@ impl Database {
.await
}
- pub async fn set_user_is_admin(&self, id: UserId, is_admin: bool) -> Result<()> {
- self.transaction(|tx| async move {
- user::Entity::update_many()
- .filter(user::Column::Id.eq(id))
- .set(user::ActiveModel {
- admin: ActiveValue::set(is_admin),
- ..Default::default()
- })
- .exec(&*tx)
- .await?;
- Ok(())
- })
- .await
- }
-
pub async fn set_user_connected_once(&self, id: UserId, connected_once: bool) -> Result<()> {
self.transaction(|tx| async move {
user::Entity::update_many()
@@ -575,308 +575,6 @@ async fn test_fuzzy_search_users() {
}
}
-#[gpui::test]
-async fn test_invite_codes() {
- let test_db = TestDb::postgres(build_background_executor());
- let db = test_db.db();
-
- let NewUserResult { user_id: user1, .. } = db
- .create_user(
- "user1@example.com",
- false,
- NewUserParams {
- github_login: "user1".into(),
- github_user_id: 0,
- invite_count: 0,
- },
- )
- .await
- .unwrap();
-
- // Initially, user 1 has no invite code
- assert_eq!(db.get_invite_code_for_user(user1).await.unwrap(), None);
-
- // Setting invite count to 0 when no code is assigned does not assign a new code
- db.set_invite_count_for_user(user1, 0).await.unwrap();
- assert!(db.get_invite_code_for_user(user1).await.unwrap().is_none());
-
- // User 1 creates an invite code that can be used twice.
- db.set_invite_count_for_user(user1, 2).await.unwrap();
- let (invite_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
- assert_eq!(invite_count, 2);
-
- // User 2 redeems the invite code and becomes a contact of user 1.
- let user2_invite = db
- .create_invite_from_code(
- &invite_code,
- "user2@example.com",
- Some("user-2-device-id"),
- true,
- )
- .await
- .unwrap();
- let NewUserResult {
- user_id: user2,
- inviting_user_id,
- signup_device_id,
- metrics_id,
- } = db
- .create_user_from_invite(
- &user2_invite,
- NewUserParams {
- github_login: "user2".into(),
- github_user_id: 2,
- invite_count: 7,
- },
- )
- .await
- .unwrap()
- .unwrap();
- let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
- assert_eq!(invite_count, 1);
- assert_eq!(inviting_user_id, Some(user1));
- assert_eq!(signup_device_id.unwrap(), "user-2-device-id");
- assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id);
- assert_eq!(
- db.get_contacts(user1).await.unwrap(),
- [Contact::Accepted {
- user_id: user2,
- should_notify: true,
- busy: false,
- }]
- );
- assert_eq!(
- db.get_contacts(user2).await.unwrap(),
- [Contact::Accepted {
- user_id: user1,
- should_notify: false,
- busy: false,
- }]
- );
- assert!(db.has_contact(user1, user2).await.unwrap());
- assert!(db.has_contact(user2, user1).await.unwrap());
- assert_eq!(
- db.get_invite_code_for_user(user2).await.unwrap().unwrap().1,
- 7
- );
-
- // User 3 redeems the invite code and becomes a contact of user 1.
- let user3_invite = db
- .create_invite_from_code(&invite_code, "user3@example.com", None, true)
- .await
- .unwrap();
- let NewUserResult {
- user_id: user3,
- inviting_user_id,
- signup_device_id,
- ..
- } = db
- .create_user_from_invite(
- &user3_invite,
- NewUserParams {
- github_login: "user-3".into(),
- github_user_id: 3,
- invite_count: 3,
- },
- )
- .await
- .unwrap()
- .unwrap();
- let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
- assert_eq!(invite_count, 0);
- assert_eq!(inviting_user_id, Some(user1));
- assert!(signup_device_id.is_none());
- assert_eq!(
- db.get_contacts(user1).await.unwrap(),
- [
- Contact::Accepted {
- user_id: user2,
- should_notify: true,
- busy: false,
- },
- Contact::Accepted {
- user_id: user3,
- should_notify: true,
- busy: false,
- }
- ]
- );
- assert_eq!(
- db.get_contacts(user3).await.unwrap(),
- [Contact::Accepted {
- user_id: user1,
- should_notify: false,
- busy: false,
- }]
- );
- assert!(db.has_contact(user1, user3).await.unwrap());
- assert!(db.has_contact(user3, user1).await.unwrap());
- assert_eq!(
- db.get_invite_code_for_user(user3).await.unwrap().unwrap().1,
- 3
- );
-
- // Trying to reedem the code for the third time results in an error.
- db.create_invite_from_code(
- &invite_code,
- "user4@example.com",
- Some("user-4-device-id"),
- true,
- )
- .await
- .unwrap_err();
-
- // Invite count can be updated after the code has been created.
- db.set_invite_count_for_user(user1, 2).await.unwrap();
- let (latest_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
- assert_eq!(latest_code, invite_code); // Invite code doesn't change when we increment above 0
- assert_eq!(invite_count, 2);
-
- // User 4 can now redeem the invite code and becomes a contact of user 1.
- let user4_invite = db
- .create_invite_from_code(
- &invite_code,
- "user4@example.com",
- Some("user-4-device-id"),
- true,
- )
- .await
- .unwrap();
- let user4 = db
- .create_user_from_invite(
- &user4_invite,
- NewUserParams {
- github_login: "user-4".into(),
- github_user_id: 4,
- invite_count: 5,
- },
- )
- .await
- .unwrap()
- .unwrap()
- .user_id;
-
- let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
- assert_eq!(invite_count, 1);
- assert_eq!(
- db.get_contacts(user1).await.unwrap(),
- [
- Contact::Accepted {
- user_id: user2,
- should_notify: true,
- busy: false,
- },
- Contact::Accepted {
- user_id: user3,
- should_notify: true,
- busy: false,
- },
- Contact::Accepted {
- user_id: user4,
- should_notify: true,
- busy: false,
- }
- ]
- );
- assert_eq!(
- db.get_contacts(user4).await.unwrap(),
- [Contact::Accepted {
- user_id: user1,
- should_notify: false,
- busy: false,
- }]
- );
- assert!(db.has_contact(user1, user4).await.unwrap());
- assert!(db.has_contact(user4, user1).await.unwrap());
- assert_eq!(
- db.get_invite_code_for_user(user4).await.unwrap().unwrap().1,
- 5
- );
-
- // An existing user cannot redeem invite codes.
- db.create_invite_from_code(
- &invite_code,
- "user2@example.com",
- Some("user-2-device-id"),
- true,
- )
- .await
- .unwrap_err();
- let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
- assert_eq!(invite_count, 1);
-
- // A newer user can invite an existing one via a different email address
- // than the one they used to sign up.
- let user5 = db
- .create_user(
- "user5@example.com",
- false,
- NewUserParams {
- github_login: "user5".into(),
- github_user_id: 5,
- invite_count: 0,
- },
- )
- .await
- .unwrap()
- .user_id;
- db.set_invite_count_for_user(user5, 5).await.unwrap();
- let (user5_invite_code, _) = db.get_invite_code_for_user(user5).await.unwrap().unwrap();
- let user5_invite_to_user1 = db
- .create_invite_from_code(&user5_invite_code, "user1@different.com", None, true)
- .await
- .unwrap();
- let user1_2 = db
- .create_user_from_invite(
- &user5_invite_to_user1,
- NewUserParams {
- github_login: "user1".into(),
- github_user_id: 1,
- invite_count: 5,
- },
- )
- .await
- .unwrap()
- .unwrap()
- .user_id;
- assert_eq!(user1_2, user1);
- assert_eq!(
- db.get_contacts(user1).await.unwrap(),
- [
- Contact::Accepted {
- user_id: user2,
- should_notify: true,
- busy: false,
- },
- Contact::Accepted {
- user_id: user3,
- should_notify: true,
- busy: false,
- },
- Contact::Accepted {
- user_id: user4,
- should_notify: true,
- busy: false,
- },
- Contact::Accepted {
- user_id: user5,
- should_notify: false,
- busy: false,
- }
- ]
- );
- assert_eq!(
- db.get_contacts(user5).await.unwrap(),
- [Contact::Accepted {
- user_id: user1,
- should_notify: true,
- busy: false,
- }]
- );
- assert!(db.has_contact(user1, user5).await.unwrap());
- assert!(db.has_contact(user5, user1).await.unwrap());
-}
-
test_both_dbs!(test_channels, test_channels_postgres, test_channels_sqlite);
async fn test_channels(db: &Arc<Database>) {
@@ -1329,245 +1027,6 @@ async fn test_channel_renames(db: &Arc<Database>) {
assert!(bad_name_rename.is_err())
}
-#[gpui::test]
-async fn test_multiple_signup_overwrite() {
- let test_db = TestDb::postgres(build_background_executor());
- let db = test_db.db();
-
- let email_address = "user_1@example.com".to_string();
-
- let initial_signup_created_at_milliseconds = 0;
-
- let initial_signup = NewSignup {
- email_address: email_address.clone(),
- platform_mac: false,
- platform_linux: true,
- platform_windows: false,
- editor_features: vec!["speed".into()],
- programming_languages: vec!["rust".into(), "c".into()],
- device_id: Some(format!("device_id")),
- added_to_mailing_list: false,
- created_at: Some(
- DateTime::from_timestamp_millis(initial_signup_created_at_milliseconds).unwrap(),
- ),
- };
-
- db.create_signup(&initial_signup).await.unwrap();
-
- let initial_signup_from_db = db.get_signup(&email_address).await.unwrap();
-
- assert_eq!(
- initial_signup_from_db.clone(),
- signup::Model {
- email_address: initial_signup.email_address,
- platform_mac: initial_signup.platform_mac,
- platform_linux: initial_signup.platform_linux,
- platform_windows: initial_signup.platform_windows,
- editor_features: Some(initial_signup.editor_features),
- programming_languages: Some(initial_signup.programming_languages),
- added_to_mailing_list: initial_signup.added_to_mailing_list,
- ..initial_signup_from_db
- }
- );
-
- let subsequent_signup = NewSignup {
- email_address: email_address.clone(),
- platform_mac: true,
- platform_linux: false,
- platform_windows: true,
- editor_features: vec!["git integration".into(), "clean design".into()],
- programming_languages: vec!["d".into(), "elm".into()],
- device_id: Some(format!("different_device_id")),
- added_to_mailing_list: true,
- // subsequent signup happens next day
- created_at: Some(
- DateTime::from_timestamp_millis(
- initial_signup_created_at_milliseconds + (1000 * 60 * 60 * 24),
- )
- .unwrap(),
- ),
- };
-
- db.create_signup(&subsequent_signup).await.unwrap();
-
- let subsequent_signup_from_db = db.get_signup(&email_address).await.unwrap();
-
- assert_eq!(
- subsequent_signup_from_db.clone(),
- signup::Model {
- platform_mac: subsequent_signup.platform_mac,
- platform_linux: subsequent_signup.platform_linux,
- platform_windows: subsequent_signup.platform_windows,
- editor_features: Some(subsequent_signup.editor_features),
- programming_languages: Some(subsequent_signup.programming_languages),
- device_id: subsequent_signup.device_id,
- added_to_mailing_list: subsequent_signup.added_to_mailing_list,
- // shouldn't overwrite their creation Datetime - user shouldn't lose their spot in line
- created_at: initial_signup_from_db.created_at,
- ..subsequent_signup_from_db
- }
- );
-}
-
-#[gpui::test]
-async fn test_signups() {
- let test_db = TestDb::postgres(build_background_executor());
- let db = test_db.db();
-
- let usernames = (0..8).map(|i| format!("person-{i}")).collect::<Vec<_>>();
-
- let all_signups = usernames
- .iter()
- .enumerate()
- .map(|(i, username)| NewSignup {
- email_address: format!("{username}@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()],
- device_id: Some(format!("device_id_{i}")),
- added_to_mailing_list: i != 0, // One user failed to subscribe
- created_at: Some(DateTime::from_timestamp_millis(i as i64).unwrap()), // Signups are consecutive
- })
- .collect::<Vec<NewSignup>>();
-
- // people sign up on the waitlist
- for signup in &all_signups {
- // users can sign up multiple times without issues
- for _ in 0..2 {
- db.create_signup(&signup).await.unwrap();
- }
- }
-
- assert_eq!(
- db.get_waitlist_summary().await.unwrap(),
- WaitlistSummary {
- count: 8,
- mac_count: 8,
- linux_count: 4,
- windows_count: 2,
- unknown_count: 0,
- }
- );
-
- // retrieve the next batch of signup emails to send
- let signups_batch1 = db.get_unsent_invites(3).await.unwrap();
- let addresses = signups_batch1
- .iter()
- .map(|s| &s.email_address)
- .collect::<Vec<_>>();
- assert_eq!(
- addresses,
- &[
- all_signups[0].email_address.as_str(),
- all_signups[1].email_address.as_str(),
- all_signups[2].email_address.as_str()
- ]
- );
- assert_ne!(
- signups_batch1[0].email_confirmation_code,
- signups_batch1[1].email_confirmation_code
- );
-
- // the waitlist isn't updated until we record that the emails
- // were successfully sent.
- let signups_batch = db.get_unsent_invites(3).await.unwrap();
- assert_eq!(signups_batch, signups_batch1);
-
- // once the emails go out, we can retrieve the next batch
- // of signups.
- db.record_sent_invites(&signups_batch1).await.unwrap();
- let signups_batch2 = db.get_unsent_invites(3).await.unwrap();
- let addresses = signups_batch2
- .iter()
- .map(|s| &s.email_address)
- .collect::<Vec<_>>();
- assert_eq!(
- addresses,
- &[
- all_signups[3].email_address.as_str(),
- all_signups[4].email_address.as_str(),
- all_signups[5].email_address.as_str()
- ]
- );
-
- // the sent invites are excluded from the summary.
- assert_eq!(
- db.get_waitlist_summary().await.unwrap(),
- WaitlistSummary {
- count: 5,
- mac_count: 5,
- linux_count: 2,
- windows_count: 1,
- unknown_count: 0,
- }
- );
-
- // user completes the signup process by providing their
- // github account.
- let NewUserResult {
- user_id,
- inviting_user_id,
- signup_device_id,
- ..
- } = db
- .create_user_from_invite(
- &Invite {
- ..signups_batch1[0].clone()
- },
- NewUserParams {
- github_login: usernames[0].clone(),
- github_user_id: 0,
- invite_count: 5,
- },
- )
- .await
- .unwrap()
- .unwrap();
- let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
- assert!(inviting_user_id.is_none());
- assert_eq!(user.github_login, usernames[0]);
- assert_eq!(
- user.email_address,
- Some(all_signups[0].email_address.clone())
- );
- assert_eq!(user.invite_count, 5);
- assert_eq!(signup_device_id.unwrap(), "device_id_0");
-
- // cannot redeem the same signup again.
- assert!(db
- .create_user_from_invite(
- &Invite {
- email_address: signups_batch1[0].email_address.clone(),
- email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
- },
- NewUserParams {
- github_login: "some-other-github_account".into(),
- github_user_id: 1,
- invite_count: 5,
- },
- )
- .await
- .unwrap()
- .is_none());
-
- // cannot redeem a signup with the wrong confirmation code.
- db.create_user_from_invite(
- &Invite {
- email_address: signups_batch1[1].email_address.clone(),
- email_confirmation_code: "the-wrong-code".to_string(),
- },
- NewUserParams {
- github_login: usernames[1].clone(),
- github_user_id: 2,
- invite_count: 5,
- },
- )
- .await
- .unwrap_err();
-}
-
fn build_background_executor() -> Arc<Background> {
Deterministic::new(0).build_background()
}
@@ -553,9 +553,8 @@ impl Server {
this.app_state.db.set_user_connected_once(user_id, true).await?;
}
- let (contacts, invite_code, channels_for_user, channel_invites) = future::try_join4(
+ let (contacts, channels_for_user, channel_invites) = future::try_join3(
this.app_state.db.get_contacts(user_id),
- this.app_state.db.get_invite_code_for_user(user_id),
this.app_state.db.get_channels_for_user(user_id),
this.app_state.db.get_channel_invites_for_user(user_id)
).await?;
@@ -568,13 +567,6 @@ impl Server {
channels_for_user,
channel_invites
))?;
-
- if let Some((code, count)) = invite_code {
- this.peer.send(connection_id, proto::UpdateInviteInfo {
- url: format!("{}{}", this.app_state.config.invite_link_prefix, code),
- count: count as u32,
- })?;
- }
}
if let Some(incoming_call) = this.app_state.db.incoming_call_for_user(user_id).await? {
@@ -3146,6 +3146,7 @@ async fn test_local_settings(
)
.await;
let (project_a, _) = client_a.build_local_project("/dir", cx_a).await;
+ deterministic.run_until_parked();
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await