Link signups to users in telemetry via a stored device_id

Co-authored-by: Joseph Lyons <joseph@zed.dev>
This commit is contained in:
Max Brunsfeld 2022-09-23 17:06:27 -07:00
parent 04baccbea6
commit 4784dbe498
7 changed files with 124 additions and 109 deletions

View file

@ -127,44 +127,52 @@ struct CreateUserParams {
invite_count: i32,
}
#[derive(Serialize, Debug)]
struct CreateUserResponse {
user: User,
signup_device_id: Option<String>,
}
async fn create_user(
Json(params): Json<CreateUserParams>,
Extension(app): Extension<Arc<AppState>>,
Extension(rpc_server): Extension<Arc<rpc::Server>>,
) -> Result<Json<User>> {
) -> Result<Json<CreateUserResponse>> {
let user = NewUserParams {
github_login: params.github_login,
github_user_id: params.github_user_id,
invite_count: params.invite_count,
};
let (user_id, inviter_id) =
// Creating a user via the normal signup process
if let Some(email_confirmation_code) = params.email_confirmation_code {
app.db
.create_user_from_invite(
&Invite {
email_address: params.email_address,
email_confirmation_code,
},
user,
)
.await?
}
// Creating a user as an admin
else {
(
app.db
.create_user(&params.email_address, false, user)
.await?,
None,
let user_id;
let signup_device_id;
// Creating a user via the normal signup process
if let Some(email_confirmation_code) = params.email_confirmation_code {
let result = app
.db
.create_user_from_invite(
&Invite {
email_address: params.email_address,
email_confirmation_code,
},
user,
)
};
if let Some(inviter_id) = inviter_id {
rpc_server
.invite_code_redeemed(inviter_id, user_id)
.await
.trace_err();
.await?;
user_id = result.0;
signup_device_id = Some(result.2);
if let Some(inviter_id) = result.1 {
rpc_server
.invite_code_redeemed(inviter_id, user_id)
.await
.trace_err();
}
}
// Creating a user as an admin
else {
user_id = app
.db
.create_user(&params.email_address, false, user)
.await?;
signup_device_id = None;
}
let user = app
@ -173,7 +181,10 @@ async fn create_user(
.await?
.ok_or_else(|| anyhow!("couldn't find the user we just created"))?;
Ok(Json(user))
Ok(Json(CreateUserResponse {
user,
signup_device_id,
}))
}
#[derive(Deserialize)]
@ -396,17 +407,12 @@ async fn get_user_for_invite_code(
Ok(Json(app.db.get_user_for_invite_code(&code).await?))
}
#[derive(Serialize)]
struct CreateSignupResponse {
metrics_id: i32,
}
async fn create_signup(
Json(params): Json<Signup>,
Extension(app): Extension<Arc<AppState>>,
) -> Result<Json<CreateSignupResponse>> {
let metrics_id = app.db.create_signup(params).await?;
Ok(Json(CreateSignupResponse { metrics_id }))
) -> Result<()> {
app.db.create_signup(params).await?;
Ok(())
}
async fn get_waitlist_summary(

View file

@ -37,7 +37,7 @@ pub trait Db: Send + Sync {
async fn get_user_for_invite_code(&self, code: &str) -> Result<User>;
async fn create_invite_from_code(&self, code: &str, email_address: &str) -> Result<Invite>;
async fn create_signup(&self, signup: Signup) -> Result<i32>;
async fn create_signup(&self, signup: Signup) -> Result<()>;
async fn get_waitlist_summary(&self) -> Result<WaitlistSummary>;
async fn get_unsent_invites(&self, count: usize) -> Result<Vec<Invite>>;
async fn record_sent_invites(&self, invites: &[Invite]) -> Result<()>;
@ -45,7 +45,7 @@ pub trait Db: Send + Sync {
&self,
invite: &Invite,
user: NewUserParams,
) -> Result<(UserId, Option<UserId>)>;
) -> Result<(UserId, Option<UserId>, String)>;
/// Registers a new project for the given user.
async fn register_project(&self, host_user_id: UserId) -> Result<ProjectId>;
@ -364,8 +364,8 @@ impl Db for PostgresDb {
// signups
async fn create_signup(&self, signup: Signup) -> Result<i32> {
Ok(sqlx::query_scalar(
async fn create_signup(&self, signup: Signup) -> Result<()> {
sqlx::query(
"
INSERT INTO signups
(
@ -377,10 +377,11 @@ impl Db for PostgresDb {
platform_windows,
platform_unknown,
editor_features,
programming_languages
programming_languages,
device_id
)
VALUES
($1, $2, 'f', $3, $4, $5, 'f', $6, $7)
($1, $2, 'f', $3, $4, $5, 'f', $6, $7, $8)
RETURNING id
",
)
@ -391,8 +392,10 @@ impl Db for PostgresDb {
.bind(&signup.platform_windows)
.bind(&signup.editor_features)
.bind(&signup.programming_languages)
.fetch_one(&self.pool)
.await?)
.bind(&signup.device_id)
.execute(&self.pool)
.await?;
Ok(())
}
async fn get_waitlist_summary(&self) -> Result<WaitlistSummary> {
@ -455,17 +458,17 @@ impl Db for PostgresDb {
&self,
invite: &Invite,
user: NewUserParams,
) -> Result<(UserId, Option<UserId>)> {
) -> Result<(UserId, Option<UserId>, String)> {
let mut tx = self.pool.begin().await?;
let (signup_id, metrics_id, existing_user_id, inviting_user_id): (
i32,
let (signup_id, existing_user_id, inviting_user_id, device_id): (
i32,
Option<UserId>,
Option<UserId>,
String,
) = sqlx::query_as(
"
SELECT id, metrics_id, user_id, inviting_user_id
SELECT id, user_id, inviting_user_id, device_id
FROM signups
WHERE
email_address = $1 AND
@ -488,9 +491,9 @@ impl Db for PostgresDb {
let user_id: UserId = sqlx::query_scalar(
"
INSERT INTO users
(email_address, github_login, github_user_id, admin, invite_count, invite_code, metrics_id)
(email_address, github_login, github_user_id, admin, invite_count, invite_code)
VALUES
($1, $2, $3, 'f', $4, $5, $6)
($1, $2, $3, 'f', $4, $5)
RETURNING id
",
)
@ -499,7 +502,6 @@ impl Db for PostgresDb {
.bind(&user.github_user_id)
.bind(&user.invite_count)
.bind(random_invite_code())
.bind(metrics_id)
.fetch_one(&mut tx)
.await?;
@ -550,7 +552,7 @@ impl Db for PostgresDb {
}
tx.commit().await?;
Ok((user_id, inviting_user_id))
Ok((user_id, inviting_user_id, device_id))
}
// invite codes
@ -1567,7 +1569,6 @@ pub struct User {
pub id: UserId,
pub github_login: String,
pub github_user_id: Option<i32>,
pub metrics_id: i32,
pub email_address: Option<String>,
pub admin: bool,
pub invite_code: Option<String>,
@ -1674,6 +1675,7 @@ pub struct Signup {
pub platform_linux: bool,
pub editor_features: Vec<String>,
pub programming_languages: Vec<String>,
pub device_id: String,
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromRow)]
@ -1802,7 +1804,6 @@ mod test {
github_login: params.github_login,
github_user_id: Some(params.github_user_id),
email_address: Some(email_address.to_string()),
metrics_id: id + 100,
admin,
invite_code: None,
invite_count: 0,
@ -1884,7 +1885,7 @@ mod test {
// signups
async fn create_signup(&self, _signup: Signup) -> Result<i32> {
async fn create_signup(&self, _signup: Signup) -> Result<()> {
unimplemented!()
}
@ -1904,7 +1905,7 @@ mod test {
&self,
_invite: &Invite,
_user: NewUserParams,
) -> Result<(UserId, Option<UserId>)> {
) -> Result<(UserId, Option<UserId>, String)> {
unimplemented!()
}

View file

@ -957,7 +957,7 @@ async fn test_invite_codes() {
.create_invite_from_code(&invite_code, "u2@example.com")
.await
.unwrap();
let (user2, inviter) = db
let (user2, inviter, _) = db
.create_user_from_invite(
&user2_invite,
NewUserParams {
@ -1007,7 +1007,7 @@ async fn test_invite_codes() {
.create_invite_from_code(&invite_code, "u3@example.com")
.await
.unwrap();
let (user3, inviter) = db
let (user3, inviter, _) = db
.create_user_from_invite(
&user3_invite,
NewUserParams {
@ -1072,7 +1072,7 @@ async fn test_invite_codes() {
.create_invite_from_code(&invite_code, "u4@example.com")
.await
.unwrap();
let (user4, _) = db
let (user4, _, _) = db
.create_user_from_invite(
&user4_invite,
NewUserParams {
@ -1139,20 +1139,18 @@ async fn test_signups() {
let db = postgres.db();
// people sign up on the waitlist
let mut signup_metric_ids = Vec::new();
for i in 0..8 {
signup_metric_ids.push(
db.create_signup(Signup {
email_address: format!("person-{i}@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()],
})
.await
.unwrap(),
);
db.create_signup(Signup {
email_address: format!("person-{i}@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: format!("device_id_{i}"),
})
.await
.unwrap();
}
assert_eq!(
@ -1219,7 +1217,7 @@ async fn test_signups() {
// user completes the signup process by providing their
// github account.
let (user_id, inviter_id) = db
let (user_id, inviter_id, signup_device_id) = db
.create_user_from_invite(
&Invite {
email_address: signups_batch1[0].email_address.clone(),
@ -1238,7 +1236,7 @@ async fn test_signups() {
assert_eq!(user.github_login, "person-0");
assert_eq!(user.email_address.as_deref(), Some("person-0@example.com"));
assert_eq!(user.invite_count, 5);
assert_eq!(user.metrics_id, signup_metric_ids[0]);
assert_eq!(signup_device_id, "device_id_0");
// cannot redeem the same signup again.
db.create_user_from_invite(