account type is permanent and password registration is now working at checkout
This commit is contained in:
@@ -13,7 +13,6 @@ pub struct Model {
|
||||
pub id: i32,
|
||||
#[sea_orm(unique)]
|
||||
pub user_id: i32,
|
||||
pub account_type: String,
|
||||
pub company_name: Option<String>,
|
||||
pub company_id: Option<String>,
|
||||
pub tax_id: Option<String>,
|
||||
|
||||
@@ -18,6 +18,7 @@ pub struct Model {
|
||||
pub status: String,
|
||||
pub total_cents: i64,
|
||||
pub currency: String,
|
||||
pub user_id: Option<i32>,
|
||||
pub account_type: String,
|
||||
pub company_name: Option<String>,
|
||||
pub company_id: Option<String>,
|
||||
|
||||
@@ -25,6 +25,7 @@ pub struct Model {
|
||||
pub magic_link_token: Option<String>,
|
||||
pub magic_link_expiration: Option<DateTimeWithTimeZone>,
|
||||
pub theme: String,
|
||||
pub account_type: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
||||
@@ -9,11 +9,10 @@ use sea_orm::{ActiveValue, IntoActiveModel, QueryFilter, TryIntoModel};
|
||||
pub type CustomerProfiles = Entity;
|
||||
|
||||
/// The editable profile fields, shared by the profile page and the checkout
|
||||
/// "save my address" path. `account_type` is "personal" or "company"; the
|
||||
/// `company_*` fields are only meaningful for company accounts.
|
||||
/// "save my address" path. The `company_*` fields are only meaningful for
|
||||
/// company accounts (account type now lives on `users`, fixed at registration).
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ProfileFields {
|
||||
pub account_type: String,
|
||||
pub company_name: Option<String>,
|
||||
pub company_id: Option<String>,
|
||||
pub tax_id: Option<String>,
|
||||
@@ -59,7 +58,6 @@ impl Model {
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
active.account_type = ActiveValue::set(fields.account_type);
|
||||
active.company_name = ActiveValue::set(fields.company_name);
|
||||
active.company_id = ActiveValue::set(fields.company_id);
|
||||
active.tax_id = ActiveValue::set(fields.tax_id);
|
||||
|
||||
@@ -14,6 +14,9 @@ pub struct Checkout {
|
||||
pub email: String,
|
||||
pub phone: String,
|
||||
pub customer_name: Option<String>,
|
||||
/// The account that owns this order, if any (a logged-in buyer or a guest
|
||||
/// who created an account during checkout). `None` for pure guest orders.
|
||||
pub user_id: Option<i32>,
|
||||
pub account_type: String,
|
||||
pub company_name: Option<String>,
|
||||
pub company_id: Option<String>,
|
||||
@@ -75,6 +78,7 @@ pub async fn place(ctx: &AppContext, items: &[(i32, i32)], details: Checkout) ->
|
||||
status: Set("pending".to_string()),
|
||||
total_cents: Set(subtotal + details.method.price_cents),
|
||||
currency: Set(currency),
|
||||
user_id: Set(details.user_id),
|
||||
account_type: Set(details.account_type),
|
||||
company_name: Set(details.company_name),
|
||||
company_id: Set(details.company_id),
|
||||
|
||||
@@ -24,6 +24,21 @@ pub struct RegisterParams {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
pub name: String,
|
||||
/// "personal" or "company"; permanent for the account. Optional on the wire
|
||||
/// (older/JSON callers omit it) and normalized via [`normalize_account_type`].
|
||||
#[serde(default)]
|
||||
pub account_type: Option<String>,
|
||||
}
|
||||
|
||||
/// Normalize an account type to one of the two permanent values, defaulting to
|
||||
/// "personal" for anything missing or unexpected. An account's type is chosen
|
||||
/// once at registration and never changes.
|
||||
#[must_use]
|
||||
pub fn normalize_account_type(value: Option<&str>) -> String {
|
||||
match value.map(str::trim) {
|
||||
Some("company") => "company".to_string(),
|
||||
_ => "personal".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Validate, Deserialize)]
|
||||
@@ -216,6 +231,13 @@ impl Model {
|
||||
hash::verify_password(password, &self.password)
|
||||
}
|
||||
|
||||
/// Whether this is a company account (vs a personal one). Fixed at
|
||||
/// registration.
|
||||
#[must_use]
|
||||
pub fn is_company(&self) -> bool {
|
||||
self.account_type == "company"
|
||||
}
|
||||
|
||||
/// Asynchronously creates a user with a password and saves it to the
|
||||
/// database.
|
||||
///
|
||||
@@ -247,6 +269,7 @@ impl Model {
|
||||
email: ActiveValue::set(params.email.to_string()),
|
||||
password: ActiveValue::set(password_hash),
|
||||
name: ActiveValue::set(params.name.to_string()),
|
||||
account_type: ActiveValue::set(normalize_account_type(params.account_type.as_deref())),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(&txn)
|
||||
@@ -257,6 +280,41 @@ impl Model {
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
/// Creates an account on behalf of a checkout guest. The user never picks a
|
||||
/// password here (a strong random one satisfies the NOT NULL column, as in
|
||||
/// the OAuth path); they receive a "set your password" link by email. Errors
|
||||
/// with [`ModelError::EntityAlreadyExists`] if the email is already taken.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// When the email already exists or the insert fails.
|
||||
pub async fn create_guest_account(
|
||||
db: &DatabaseConnection,
|
||||
email: &str,
|
||||
name: &str,
|
||||
account_type: &str,
|
||||
) -> ModelResult<Self> {
|
||||
let password = PasswordGenerator::new()
|
||||
.length(16)
|
||||
.numbers(true)
|
||||
.lowercase_letters(true)
|
||||
.uppercase_letters(true)
|
||||
.symbols(true)
|
||||
.strict(true)
|
||||
.generate_one()
|
||||
.map_err(|e| ModelError::Any(e.into()))?;
|
||||
Self::create_with_password(
|
||||
db,
|
||||
&RegisterParams {
|
||||
email: email.to_string(),
|
||||
password,
|
||||
name: name.to_string(),
|
||||
account_type: Some(account_type.to_string()),
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Creates a JWT
|
||||
///
|
||||
/// # Errors
|
||||
|
||||
Reference in New Issue
Block a user