summaryrefslogtreecommitdiff
path: root/src/login/create.rs
diff options
context:
space:
mode:
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),
+}