summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2025-08-26 18:18:24 -0400
committerOwen Jacobson <owen@grimoire.ca>2025-08-26 18:18:24 -0400
commita2fee1c18d9def1486a570fb3c98db5372c51238 (patch)
tree3a6254672193b2f1b6c2375bc2f09b80ebc0fe0a
parent1e0493f079d011df56fe2ec93c44a0fea38f0531 (diff)
Store `User` instances using their events.
-rw-r--r--.sqlx/query-1feaf96621fff37a456dcd81f11f2217c3dcfe768b08c8733d3c9a00e9c0c7a7.json12
-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.json12
-rw-r--r--src/user/create.rs39
-rw-r--r--src/user/history.rs22
-rw-r--r--src/user/repo.rs50
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