summaryrefslogtreecommitdiff
path: root/src/login/create.rs
diff options
context:
space:
mode:
authorKit La Touche <kit@transneptune.net>2024-10-30 16:50:06 -0400
committerKit La Touche <kit@transneptune.net>2024-10-30 16:50:06 -0400
commit113096a2cca42008c0a19110abe322180dbdf66b (patch)
treecb871dae060e60be7fd2114ee4741027ae38bd78 /src/login/create.rs
parent610f6839d2e449d172aa6ac35e6c1de0677a0754 (diff)
parent06c839436900ce07ec5c53175b01f3c5011e507c (diff)
Merge branch 'main' into wip/mobile
Diffstat (limited to 'src/login/create.rs')
-rw-r--r--src/login/create.rs95
1 files changed, 95 insertions, 0 deletions
diff --git a/src/login/create.rs b/src/login/create.rs
new file mode 100644
index 0000000..693daaf
--- /dev/null
+++ b/src/login/create.rs
@@ -0,0 +1,95 @@
+use sqlx::{sqlite::Sqlite, Transaction};
+
+use super::{password::StoredHash, repo::Provider as _, validate, History, Password};
+use crate::{
+ clock::DateTime,
+ event::{repo::Provider as _, Broadcaster, Event},
+ name::Name,
+};
+
+pub struct Create<'a> {
+ name: &'a Name,
+ password: &'a Password,
+ created_at: &'a DateTime,
+}
+
+impl<'a> Create<'a> {
+ #[must_use = "dropping a login creation attempt is likely a mistake"]
+ pub fn begin(name: &'a Name, password: &'a Password, created_at: &'a DateTime) -> Self {
+ Self {
+ name,
+ password,
+ created_at,
+ }
+ }
+
+ #[must_use = "dropping a login creation attempt is likely a mistake"]
+ pub fn validate(self) -> Result<Validated<'a>, Error> {
+ let Self {
+ name,
+ password,
+ created_at,
+ } = self;
+
+ if !validate::name(name) {
+ return Err(Error::InvalidName(name.clone()));
+ }
+
+ let password_hash = password.hash()?;
+
+ Ok(Validated {
+ name,
+ password_hash,
+ created_at,
+ })
+ }
+}
+
+pub struct Validated<'a> {
+ name: &'a Name,
+ password_hash: StoredHash,
+ created_at: &'a DateTime,
+}
+
+impl<'a> Validated<'a> {
+ #[must_use = "dropping a login creation attempt is likely a mistake"]
+ pub async fn store<'c>(self, tx: &mut Transaction<'c, Sqlite>) -> Result<Stored, sqlx::Error> {
+ let Self {
+ name,
+ password_hash,
+ created_at,
+ } = self;
+
+ let created = tx.sequence().next(created_at).await?;
+ let login = tx.logins().create(name, &password_hash, &created).await?;
+
+ Ok(Stored { login })
+ }
+}
+
+pub struct Stored {
+ login: History,
+}
+
+impl Stored {
+ #[must_use = "dropping a login creation attempt is likely a mistake"]
+ pub fn publish(self, events: &Broadcaster) -> History {
+ let Self { login } = self;
+
+ events.broadcast(login.events().map(Event::from).collect::<Vec<_>>());
+
+ login
+ }
+
+ pub fn login(&self) -> &History {
+ &self.login
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("invalid login name: {0}")]
+ InvalidName(Name),
+ #[error(transparent)]
+ PasswordHash(#[from] password_hash::Error),
+}