plumbing database fields

This commit is contained in:
Brandon Thomas committed 2026-01-30 20:39:04 -05:00
1 parent 55f557278a
commit aa66d77a8a
6 files changed
+89 -13

No files matched your search

@@ -207,17 +207,17 @@ CREATE TABLE users (
maybe_source VARCHAR(255) DEFAULT NULL,
-- How users created their account
-- Initially this will be "email_password" or "google_sign_in"
-- Older users do not have a value and are assumed to be "email_sign_in"
-- Older users do not have a value and are assumed to be "email_password"
-- Values:
-- - "email_sign_in" / "email_password" (todo: check which is correct)
-- - "email_password"
-- - "google_sign_in"
-- - "stripe_checkout" (eager stripe checkout account provisioning)
maybe_signup_method VARCHAR(16) DEFAULT NULL,
-- Account was provisioned automatically (eg. stripe eager checkout flow)
-- This will remain true even if the user finishes account setup, email change,
-- password setup, etc.
-- password setup, etc. (It's a permanent label on the account for later
-- statistical evaluation.)
was_eagerly_provisioned BOOLEAN NOT NULL DEFAULT false,
-- ========== MODERATION DETAILS ==========
@@ -64,6 +64,11 @@ pub async fn create_account_from_email_and_password(
// NB: This is just for testing.
maybe_user_token: args.maybe_user_token,
// These flags represent eagerly created users and are false for this flow
is_temporary: false,
email_is_synthetic: false,
was_eagerly_provisioned: false,
},
Transactor::for_pool(mysql_pool),
).await?;
@@ -59,6 +59,11 @@ pub async fn create_account_from_google_sso(
// NB: This is just for testing.
maybe_user_token: None,
// These flags represent eagerly created users and are false for this flow
is_temporary: false,
email_is_synthetic: false,
was_eagerly_provisioned: false,
},
transactor,
).await?;
@@ -16,6 +16,14 @@ use tokens::tokens::users::UserToken;
const SSO_PASSWORD : &str = "*";
pub struct CreateAccountFromGoogleSsoArgs<'a> {
// NB: Username will likely be generated by our code, not the user.
pub username: &'a str,
pub display_name: &'a str,
// NB: Email will likely be generated by our code, not the user.
pub email_address: &'a str,
pub email_gravatar_hash: &'a str,
pub ip_address: &'a str,
pub maybe_source: Option<UserSignupSource>,
@@ -30,23 +38,32 @@ pub async fn create_account_from_google_sso(
{
let result= create_account_generic(
GenericCreateAccountArgs {
maybe_signup_method: Some(UserSignupMethod::GoogleSignIn),
maybe_signup_method: Some(UserSignupMethod::StripeCheckout),
username: args.username,
display_name: args.display_name,
ip_address: args.ip_address,
maybe_feature_flags: args.maybe_feature_flags,
maybe_source: args.maybe_source,
// SSO accounts have an email reported from Google
email_address: args.email_address,
email_gravatar_hash: args.email_gravatar_hash,
email_confirmed_by_google: args.email_confirmed_by_google,
// These are synthetic user accounts that are being eagerly created
// If the user abandons the onboarding flow, they can be deleted later.
is_temporary: true,
was_eagerly_provisioned: true,
// User-created accounts in this flow have randomly generated usernames
// Stripe checkout users have randomly generated usernames
username: args.username,
display_name: args.display_name,
username_is_generated: true,
username_is_not_customized: true,
// Stripe checkout users have NO REAL EMAIL ADDRESS yet.
// Stripe will tell us what their email address is once
// they complete the checkout (or the user can update it,
// and their write wins.)
email_address: args.email_address,
email_gravatar_hash: args.email_gravatar_hash,
email_confirmed_by_google: false,
email_is_synthetic: true,
// SSO accounts start without a password
password_hash: SSO_PASSWORD,
is_without_password: true,
@@ -23,10 +23,35 @@ pub struct GenericCreateAccountArgs<'a> {
pub email_address: &'a str,
pub email_gravatar_hash: &'a str,
/// Google did email IDV and told us it's legitimate.
pub email_confirmed_by_google: bool,
/// The email address is not real yet. We generated a fake/synthetic value
/// that will hopefully be replaced later. This is for automated user creation
/// flows, like Stripe Checkout onboarding flow.
pub email_is_synthetic: bool,
pub password_hash: &'a str,
pub is_without_password: bool,
/// This is initially TRUE for accounts that were provisioned automatically,
/// without user intervention, e.g. in the eager Stripe Checkout onboarding
/// flow. Accounts in this state have not yet been customized by users and may
/// not in fact represent real users if the users abandon the flow. Once the
/// user finishes setup, this will be set to false.
///
/// Existing and normal setup flow sets this flag to false.
///
/// If enough time has passed without the user interacting with the account,
/// then it might be prudent to delete these records.
pub is_temporary: bool,
/// Whether the account was created eagerly (e.g. via stripe checkout flow)
/// without the user explicitly setting up an account, choosing an email or
/// password, etc. (It's a technical choice to do faster checkout flow.)
/// This is a permanent label on the account.
pub was_eagerly_provisioned: bool,
pub ip_address: &'a str,
pub maybe_source: Option<UserSignupSource>,
@@ -66,6 +91,9 @@ pub async fn create_account_generic(
INSERT INTO users
SET
token = ?,
is_temporary = ?,
username = ?,
display_name = ?,
@@ -77,6 +105,9 @@ SET
email_confirmed = FALSE,
email_confirmed_by_google = ?,
email_is_synthetic = ?,
was_eagerly_provisioned = ?,
profile_markdown = ?,
profile_rendered_html = ?,
@@ -96,6 +127,9 @@ SET
maybe_signup_method = ?
"#,
user_token.as_str(),
args.is_temporary,
args.username,
args.display_name,
@@ -106,6 +140,9 @@ SET
args.email_gravatar_hash,
args.email_confirmed_by_google,
args.email_is_synthetic,
args.was_eagerly_provisioned,
INITIAL_PROFILE_MARKDOWN,
INITIAL_PROFILE_RENDERED_HTML,
@@ -18,6 +18,11 @@ pub enum UserSignupMethod {
/// "Sign in With Google" SSO
GoogleSignIn,
/// Stripe Checkout flow, where we provision user accounts for users with a
/// synthetic/fake email address and no password. After checkout completes,
/// the user gets a real email and password - or the user can set them.
StripeCheckout,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -31,6 +36,7 @@ impl UserSignupMethod {
match self {
Self::EmailPassword => "email_password",
Self::GoogleSignIn=> "google_sign_in",
Self::StripeCheckout => "stripe_checkout",
}
}
@@ -38,6 +44,7 @@ impl UserSignupMethod {
match value {
"email_password" => Ok(Self::EmailPassword),
"google_sign_in" => Ok(Self::GoogleSignIn),
"stripe_checkout" => Ok(Self::StripeCheckout),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -48,6 +55,7 @@ impl UserSignupMethod {
BTreeSet::from([
Self::EmailPassword,
Self::GoogleSignIn,
Self::StripeCheckout,
])
}
}
@@ -64,6 +72,7 @@ mod tests {
fn test_serialization() {
assert_serialization(UserSignupMethod::EmailPassword, "email_password");
assert_serialization(UserSignupMethod::GoogleSignIn, "google_sign_in");
assert_serialization(UserSignupMethod::StripeCheckout, "stripe_checkout");
}
}
@@ -74,12 +83,14 @@ mod tests {
fn to_str() {
assert_eq!(UserSignupMethod::EmailPassword.to_str(), "email_password");
assert_eq!(UserSignupMethod::GoogleSignIn.to_str(), "google_sign_in");
assert_eq!(UserSignupMethod::StripeCheckout.to_str(), "stripe_checkout");
}
#[test]
fn from_str() {
assert_eq!(UserSignupMethod::from_str("email_password").unwrap(), UserSignupMethod::EmailPassword);
assert_eq!(UserSignupMethod::from_str("google_sign_in").unwrap(), UserSignupMethod::GoogleSignIn);
assert_eq!(UserSignupMethod::from_str("stripe_checkout").unwrap(), UserSignupMethod::StripeCheckout);
assert!(UserSignupMethod::from_str("foo").is_err());
}
}
@@ -90,9 +101,10 @@ mod tests {
#[test]
fn all_variants() {
let mut variants = UserSignupMethod::all_variants();
assert_eq!(variants.len(), 2);
assert_eq!(variants.len(), 3);
assert_eq!(variants.pop_first(), Some(UserSignupMethod::EmailPassword));
assert_eq!(variants.pop_first(), Some(UserSignupMethod::GoogleSignIn));
assert_eq!(variants.pop_first(), Some(UserSignupMethod::StripeCheckout));
assert_eq!(variants.pop_first(), None);
}
}