diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2025-08-26 18:18:24 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2025-08-26 18:18:24 -0400 |
| commit | a2fee1c18d9def1486a570fb3c98db5372c51238 (patch) | |
| tree | 3a6254672193b2f1b6c2375bc2f09b80ebc0fe0a | |
| parent | 1e0493f079d011df56fe2ec93c44a0fea38f0531 (diff) | |
Store `User` instances using their events.
| -rw-r--r-- | .sqlx/query-1feaf96621fff37a456dcd81f11f2217c3dcfe768b08c8733d3c9a00e9c0c7a7.json | 12 | ||||
| -rw-r--r-- | .sqlx/query-2fefbfc13bbc92fd69a1e2dd2926baf08b119ec169fc6a3d8b507b9701526e69.json (renamed from .sqlx/query-8dae7dbe085898659013167a6bbb9dfe26bce0812a215573a276043095cd872c.json) | 12 | ||||
| -rw-r--r-- | .sqlx/query-9e220610e6e22f4dc5a2b9b58a7e1dbd65c61badd6a9f8c4e01c12b6c2a3f3b6.json (renamed from .sqlx/query-be644101e1fd50880fa7c82b07cc6e9f69c64bb790d6c52ad84872f256c749aa.json) | 12 | ||||
| -rw-r--r-- | .sqlx/query-f9a6a39c45c3b039f139da0475c25112ffb2b26584ada60c324fc2f945c3d2fa.json | 12 | ||||
| -rw-r--r-- | src/user/create.rs | 39 | ||||
| -rw-r--r-- | src/user/history.rs | 22 | ||||
| -rw-r--r-- | src/user/repo.rs | 50 |
7 files changed, 100 insertions, 59 deletions
diff --git a/.sqlx/query-1feaf96621fff37a456dcd81f11f2217c3dcfe768b08c8733d3c9a00e9c0c7a7.json b/.sqlx/query-1feaf96621fff37a456dcd81f11f2217c3dcfe768b08c8733d3c9a00e9c0c7a7.json new file mode 100644 index 0000000..69c2e47 --- /dev/null +++ b/.sqlx/query-1feaf96621fff37a456dcd81f11f2217c3dcfe768b08c8733d3c9a00e9c0c7a7.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n insert\n into user (id, created_at, created_sequence)\n values ($1, $2, $3)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "1feaf96621fff37a456dcd81f11f2217c3dcfe768b08c8733d3c9a00e9c0c7a7" +} diff --git a/.sqlx/query-8dae7dbe085898659013167a6bbb9dfe26bce0812a215573a276043095cd872c.json b/.sqlx/query-2fefbfc13bbc92fd69a1e2dd2926baf08b119ec169fc6a3d8b507b9701526e69.json index cbe1cdf..0849dbc 100644 --- a/.sqlx/query-8dae7dbe085898659013167a6bbb9dfe26bce0812a215573a276043095cd872c.json +++ b/.sqlx/query-2fefbfc13bbc92fd69a1e2dd2926baf08b119ec169fc6a3d8b507b9701526e69.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n select\n id as \"id: Id\",\n login.display_name as \"display_name: String\",\n login.canonical_name as \"canonical_name: String\",\n user.created_sequence as \"created_sequence: Sequence\",\n user.created_at as \"created_at: DateTime\"\n from user\n join login using (id)\n where user.created_sequence > $1\n ", + "query": "\n select\n id as \"id: Id\",\n login.display_name as \"display_name: String\",\n login.canonical_name as \"canonical_name: String\",\n user.created_at as \"created_at: DateTime\",\n user.created_sequence as \"created_sequence: Sequence\"\n from user\n join login using (id)\n where user.created_sequence > $1\n ", "describe": { "columns": [ { @@ -19,14 +19,14 @@ "type_info": "Text" }, { - "name": "created_sequence: Sequence", + "name": "created_at: DateTime", "ordinal": 3, - "type_info": "Integer" + "type_info": "Text" }, { - "name": "created_at: DateTime", + "name": "created_sequence: Sequence", "ordinal": 4, - "type_info": "Text" + "type_info": "Integer" } ], "parameters": { @@ -40,5 +40,5 @@ false ] }, - "hash": "8dae7dbe085898659013167a6bbb9dfe26bce0812a215573a276043095cd872c" + "hash": "2fefbfc13bbc92fd69a1e2dd2926baf08b119ec169fc6a3d8b507b9701526e69" } diff --git a/.sqlx/query-be644101e1fd50880fa7c82b07cc6e9f69c64bb790d6c52ad84872f256c749aa.json b/.sqlx/query-9e220610e6e22f4dc5a2b9b58a7e1dbd65c61badd6a9f8c4e01c12b6c2a3f3b6.json index e56faa9..6dfc7ef 100644 --- a/.sqlx/query-be644101e1fd50880fa7c82b07cc6e9f69c64bb790d6c52ad84872f256c749aa.json +++ b/.sqlx/query-9e220610e6e22f4dc5a2b9b58a7e1dbd65c61badd6a9f8c4e01c12b6c2a3f3b6.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n select\n id as \"id: Id\",\n login.display_name as \"display_name: String\",\n login.canonical_name as \"canonical_name: String\",\n user.created_sequence as \"created_sequence: Sequence\",\n user.created_at as \"created_at: DateTime\"\n from user\n join login using (id)\n where user.created_sequence <= $1\n order by canonical_name\n ", + "query": "\n select\n id as \"id: Id\",\n login.display_name as \"display_name: String\",\n login.canonical_name as \"canonical_name: String\",\n user.created_at as \"created_at: DateTime\",\n user.created_sequence as \"created_sequence: Sequence\"\n from user\n join login using (id)\n where user.created_sequence <= $1\n ", "describe": { "columns": [ { @@ -19,14 +19,14 @@ "type_info": "Text" }, { - "name": "created_sequence: Sequence", + "name": "created_at: DateTime", "ordinal": 3, - "type_info": "Integer" + "type_info": "Text" }, { - "name": "created_at: DateTime", + "name": "created_sequence: Sequence", "ordinal": 4, - "type_info": "Text" + "type_info": "Integer" } ], "parameters": { @@ -40,5 +40,5 @@ false ] }, - "hash": "be644101e1fd50880fa7c82b07cc6e9f69c64bb790d6c52ad84872f256c749aa" + "hash": "9e220610e6e22f4dc5a2b9b58a7e1dbd65c61badd6a9f8c4e01c12b6c2a3f3b6" } diff --git a/.sqlx/query-f9a6a39c45c3b039f139da0475c25112ffb2b26584ada60c324fc2f945c3d2fa.json b/.sqlx/query-f9a6a39c45c3b039f139da0475c25112ffb2b26584ada60c324fc2f945c3d2fa.json deleted file mode 100644 index 6050e22..0000000 --- a/.sqlx/query-f9a6a39c45c3b039f139da0475c25112ffb2b26584ada60c324fc2f945c3d2fa.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n insert into user (id, created_sequence, created_at)\n values ($1, $2, $3)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "f9a6a39c45c3b039f139da0475c25112ffb2b26584ada60c324fc2f945c3d2fa" -} diff --git a/src/user/create.rs b/src/user/create.rs index 5c060c9..21c61d1 100644 --- a/src/user/create.rs +++ b/src/user/create.rs @@ -3,7 +3,7 @@ use sqlx::{Transaction, sqlite::Sqlite}; use super::{History, repo::Provider as _, validate}; use crate::{ clock::DateTime, - event::{Broadcaster, Event, repo::Provider as _}, + event::{Broadcaster, Event, Sequence, repo::Provider as _}, login::{self, Login, repo::Provider as _}, name::Name, password::{Password, StoredHash}, @@ -54,7 +54,10 @@ pub struct Validated<'a> { } impl Validated<'_> { - pub async fn store(self, tx: &mut Transaction<'_, Sqlite>) -> Result<Stored, sqlx::Error> { + pub async fn store( + self, + tx: &mut Transaction<'_, Sqlite>, + ) -> Result<Stored<impl IntoIterator<Item = Event> + use<>>, sqlx::Error> { let Self { name, password, @@ -63,28 +66,40 @@ impl Validated<'_> { let login = Login { id: login::Id::generate(), - name: name.to_owned(), + name: name.clone(), }; + tx.logins().create(&login, &password).await?; let created = tx.sequence().next(created_at).await?; - tx.logins().create(&login, &password).await?; - let user = tx.users().create(&login, &created).await?; + let user = History::begin(&login, created); + + let events = user.events().filter(Sequence::start_from(created)); + tx.users().record_events(events.clone()).await?; - Ok(Stored { user, login }) + Ok(Stored { + events: events.map(Event::from), + login, + }) } } #[must_use = "dropping a user creation attempt is likely a mistake"] -pub struct Stored { - user: History, +pub struct Stored<E> { + events: E, login: Login, } -impl Stored { - pub fn publish(self, broadcaster: &Broadcaster) { - let Self { user, login: _ } = self; +impl<E> Stored<E> +where + E: IntoIterator<Item = Event>, +{ + pub fn publish(self, events: &Broadcaster) { + let Self { + events: user_events, + login: _, + } = self; - broadcaster.broadcast(user.events().map(Event::from).collect::<Vec<_>>()); + events.broadcast(user_events.into_iter().collect::<Vec<_>>()); } pub fn login(&self) -> &Login { diff --git a/src/user/history.rs b/src/user/history.rs index f58e9c7..7c06a2d 100644 --- a/src/user/history.rs +++ b/src/user/history.rs @@ -2,7 +2,10 @@ use super::{ User, event::{Created, Event}, }; -use crate::event::{Instant, Sequence}; +use crate::{ + event::{Instant, Sequence}, + login::Login, +}; #[derive(Clone, Debug, Eq, PartialEq)] pub struct History { @@ -10,6 +13,21 @@ pub struct History { pub created: Instant, } +// Lifecycle interface +impl History { + pub fn begin(login: &Login, created: Instant) -> Self { + let Login { id, name } = login.clone(); + + Self { + user: User { + id: id.into(), + name, + }, + created, + } + } +} + // State interface impl History { pub fn as_of<S>(&self, sequence: S) -> Option<User> @@ -32,7 +50,7 @@ impl History { .into() } - pub fn events(&self) -> impl Iterator<Item = Event> + use<> { + pub fn events(&self) -> impl Iterator<Item = Event> + Clone + use<> { [self.created()].into_iter() } } diff --git a/src/user/repo.rs b/src/user/repo.rs index aaf3b73..292d72e 100644 --- a/src/user/repo.rs +++ b/src/user/repo.rs @@ -1,13 +1,13 @@ use futures::stream::{StreamExt as _, TryStreamExt as _}; use sqlx::{SqliteConnection, Transaction, sqlite::Sqlite}; +use super::{Event, History, Id, User, event::Created}; use crate::{ clock::DateTime, db::NotFound, event::{Instant, Sequence}, login::Login, name::{self, Name}, - user::{History, Id, User}, }; pub trait Provider { @@ -23,30 +23,39 @@ impl Provider for Transaction<'_, Sqlite> { pub struct Users<'t>(&'t mut SqliteConnection); impl Users<'_> { - pub async fn create( + pub async fn record_events( &mut self, - login: &Login, - created: &Instant, - ) -> Result<History, sqlx::Error> { + events: impl IntoIterator<Item = Event>, + ) -> Result<(), sqlx::Error> { + for event in events { + self.record_event(&event).await?; + } + Ok(()) + } + + pub async fn record_event(&mut self, event: &Event) -> Result<(), sqlx::Error> { + match event { + Event::Created(created) => self.record_created(created).await, + } + } + + async fn record_created(&mut self, created: &Created) -> Result<(), sqlx::Error> { + let Created { user, instant } = created; + sqlx::query!( r#" - insert into user (id, created_sequence, created_at) + insert + into user (id, created_at, created_sequence) values ($1, $2, $3) "#, - login.id, - created.sequence, - created.at, + user.id, + instant.at, + instant.sequence, ) .execute(&mut *self.0) .await?; - Ok(History { - user: User { - id: login.id.clone().into(), - name: login.name.clone(), - }, - created: *created, - }) + Ok(()) } pub async fn by_login(&mut self, login: &Login) -> Result<History, LoadError> { @@ -86,12 +95,11 @@ impl Users<'_> { id as "id: Id", login.display_name as "display_name: String", login.canonical_name as "canonical_name: String", - user.created_sequence as "created_sequence: Sequence", - user.created_at as "created_at: DateTime" + user.created_at as "created_at: DateTime", + user.created_sequence as "created_sequence: Sequence" from user join login using (id) where user.created_sequence <= $1 - order by canonical_name "#, resume_at, ) @@ -119,8 +127,8 @@ impl Users<'_> { id as "id: Id", login.display_name as "display_name: String", login.canonical_name as "canonical_name: String", - user.created_sequence as "created_sequence: Sequence", - user.created_at as "created_at: DateTime" + user.created_at as "created_at: DateTime", + user.created_sequence as "created_sequence: Sequence" from user join login using (id) where user.created_sequence > $1 |
