From ba96974bdebd6d4ec345907d49944b5ee644ed47 Mon Sep 17 00:00:00 2001
From: Owen Jacobson <owen@grimoire.ca>
Date: Wed, 9 Oct 2024 00:57:31 -0400
Subject: Provide a view of logins to clients.

---
 hi-ui/src/routes/+page.svelte | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

(limited to 'hi-ui/src/routes/+page.svelte')

diff --git a/hi-ui/src/routes/+page.svelte b/hi-ui/src/routes/+page.svelte
index 932582d..39f8b62 100644
--- a/hi-ui/src/routes/+page.svelte
+++ b/hi-ui/src/routes/+page.svelte
@@ -2,7 +2,7 @@
 	import { onMount } from 'svelte';
 
 	import { boot, subscribeToEvents } from '../apiServer';
-	import { currentUser, channelsList, messages } from '../store';
+	import { currentUser, logins, channelsList, messages } from '../store';
 
     import ActiveChannel from '../lib/ActiveChannel.svelte';
     import ChannelList from '../lib/ChannelList.svelte';
@@ -18,6 +18,7 @@
     });
 
     function onBooted(boot) {
+        logins.update((value) => value.addLogins(boot.logins));
         currentUser.update(() => ({
             id: boot.login.id,
             username: boot.login.name,
-- 
cgit v1.2.3


From 2f0b77e8fd02a137047c8975a573626cd76310ff Mon Sep 17 00:00:00 2001
From: Owen Jacobson <owen@grimoire.ca>
Date: Wed, 9 Oct 2024 01:43:34 -0400
Subject: Return a flat message list on boot, not nested lists by channel.

This is a bit easier to compute, and sets us up nicely for pulling message boot out of the `/api/boot` response entirely.
---
 ...7e56e515d121964d3481e48aae3558b53a5123ce7d.json | 50 +++++++++++++++++
 docs/api.md                                        | 17 +++---
 hi-ui/src/apiServer.js                             |  2 +-
 hi-ui/src/routes/+page.svelte                      | 14 ++---
 hi-ui/src/store/logins.js                          |  3 +-
 hi-ui/src/store/messages.js                        | 16 ++++--
 src/boot/app.rs                                    | 42 ++++++---------
 src/boot/mod.rs                                    | 63 +---------------------
 src/message/repo.rs                                | 35 ++++++++++++
 9 files changed, 128 insertions(+), 114 deletions(-)
 create mode 100644 .sqlx/query-191255b9e55c9b36d0fd047e56e515d121964d3481e48aae3558b53a5123ce7d.json

(limited to 'hi-ui/src/routes/+page.svelte')

diff --git a/.sqlx/query-191255b9e55c9b36d0fd047e56e515d121964d3481e48aae3558b53a5123ce7d.json b/.sqlx/query-191255b9e55c9b36d0fd047e56e515d121964d3481e48aae3558b53a5123ce7d.json
new file mode 100644
index 0000000..fe443f9
--- /dev/null
+++ b/.sqlx/query-191255b9e55c9b36d0fd047e56e515d121964d3481e48aae3558b53a5123ce7d.json
@@ -0,0 +1,50 @@
+{
+  "db_name": "SQLite",
+  "query": "\n                select\n                    channel as \"channel: channel::Id\",\n                    sender as \"sender: login::Id\",\n                    id as \"id: Id\",\n                    body,\n                    sent_at as \"sent_at: DateTime\",\n                    sent_sequence as \"sent_sequence: Sequence\"\n                from message\n                where coalesce(sent_sequence <= $2, true)\n                order by sent_sequence\n            ",
+  "describe": {
+    "columns": [
+      {
+        "name": "channel: channel::Id",
+        "ordinal": 0,
+        "type_info": "Text"
+      },
+      {
+        "name": "sender: login::Id",
+        "ordinal": 1,
+        "type_info": "Text"
+      },
+      {
+        "name": "id: Id",
+        "ordinal": 2,
+        "type_info": "Text"
+      },
+      {
+        "name": "body",
+        "ordinal": 3,
+        "type_info": "Text"
+      },
+      {
+        "name": "sent_at: DateTime",
+        "ordinal": 4,
+        "type_info": "Text"
+      },
+      {
+        "name": "sent_sequence: Sequence",
+        "ordinal": 5,
+        "type_info": "Integer"
+      }
+    ],
+    "parameters": {
+      "Right": 1
+    },
+    "nullable": [
+      false,
+      false,
+      false,
+      false,
+      false,
+      false
+    ]
+  },
+  "hash": "191255b9e55c9b36d0fd047e56e515d121964d3481e48aae3558b53a5123ce7d"
+}
diff --git a/docs/api.md b/docs/api.md
index 4145aa8..7414ccf 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -35,14 +35,15 @@ Returns information needed to boot the client. Also the recommended way to check
         {
             "name": "nonsense and such",
             "id": "C1234abcd",
-            "messages": [
-                {
-                    "at": "2024-09-27T23:19:10.208147Z",
-                    "sender": "L1234abcd",
-                    "id": "M1312acab",
-                    "body": "beep"
-                }
-            ]
+        }
+    ],
+    "messages": [
+        {
+            "at": "2024-09-27T23:19:10.208147Z",
+            "channel": "C1234abcd",
+            "sender": "L1234abcd",
+            "id": "M1312acab",
+            "body": "beep"
         }
     ]
 }
diff --git a/hi-ui/src/apiServer.js b/hi-ui/src/apiServer.js
index 4421ef5..3aa3f1b 100644
--- a/hi-ui/src/apiServer.js
+++ b/hi-ui/src/apiServer.js
@@ -92,7 +92,7 @@ function onChannelEvent(data) {
 function onMessageEvent(data) {
   switch (data.event) {
     case 'sent':
-      messages.update((value) => value.addMessage(data.channel, data.at, data.sender, data.body));
+      messages.update((value) => value.addMessage(data.channel, data.id, data.at, data.sender, data.body));
       break;
     case 'deleted':
       messages.update((value) => value.deleteMessage(data.id));
diff --git a/hi-ui/src/routes/+page.svelte b/hi-ui/src/routes/+page.svelte
index 39f8b62..dd5f2f7 100644
--- a/hi-ui/src/routes/+page.svelte
+++ b/hi-ui/src/routes/+page.svelte
@@ -18,20 +18,13 @@
     });
 
     function onBooted(boot) {
-        logins.update((value) => value.addLogins(boot.logins));
         currentUser.update(() => ({
             id: boot.login.id,
             username: boot.login.name,
         }));
-        let channels = boot.channels.map((channel) => ({
-            id: channel.id,
-            name: channel.name,
-        }));
-        channelsList.update((value) => value.setChannels(channels));
-        let bootMessages = boot.channels.map((channel) => [channel.id, channel.messages]);
-        for (let [channel, channelMessages] of bootMessages) {
-            messages.update((value) => value.addMessages(channel, channelMessages));
-        }
+        logins.update((value) => value.setLogins(boot.logins));
+        channelsList.update((value) => value.setChannels(boot.channels));
+        messages.update((value) => value.setMessages(boot.messages));
     }
 
 	onMount(async () => {
@@ -39,6 +32,7 @@
             let response = await boot();
             switch (response.status) {
                 case 200:
+                    debugger;
                     onBooted(response.data);
                     subscribeToEvents(response.data.resume_point);
                     break;
diff --git a/hi-ui/src/store/logins.js b/hi-ui/src/store/logins.js
index 207b757..5b45206 100644
--- a/hi-ui/src/store/logins.js
+++ b/hi-ui/src/store/logins.js
@@ -8,7 +8,8 @@ export class Logins {
 		return this;
 	}
 
-	addLogins(logins) {
+	setLogins(logins) {
+		this.logins = {};
 		for (let { id, name } of logins) {
 			this.addLogin(id, name);
 		}
diff --git a/hi-ui/src/store/messages.js b/hi-ui/src/store/messages.js
index f9e0856..931b8fb 100644
--- a/hi-ui/src/store/messages.js
+++ b/hi-ui/src/store/messages.js
@@ -4,16 +4,22 @@ export class Messages {
 	}
 
 	inChannel(channel) {
-		return this.channels[channel] || [];
+		return this.channels[channel] = (this.channels[channel] || []);
 	}
 
-	addMessage(channel, at, sender, body) {
-		this.updateChannel(channel, (messages) => [...messages, { at, sender, body }]);
+	addMessage(channel, id, at, sender, body) {
+		this.updateChannel(channel, (messages) => [...messages, { id, at, sender, body }]);
 		return this;
 	}
 
-	addMessages(channel, payloads) {
-		this.updateChannel(channel, (messages) => [...messages, ...payloads]);
+	setMessages(messages) {
+		this.channels = {};
+		for (let { channel, id, at, sender, body } of messages) {
+			this.inChannel(channel).push({ id, at, sender, body, });
+		}
+		for (let channel in this.channels) {
+			this.channels[channel].sort((a, b) => a.at - b.at);
+		}
 		return this;
 	}
 
diff --git a/src/boot/app.rs b/src/boot/app.rs
index 03e7230..ef48b2f 100644
--- a/src/boot/app.rs
+++ b/src/boot/app.rs
@@ -1,6 +1,6 @@
 use sqlx::sqlite::SqlitePool;
 
-use super::{Channel, Snapshot};
+use super::Snapshot;
 use crate::{
     channel::repo::Provider as _, event::repo::Provider as _, login::repo::Provider as _,
     message::repo::Provider as _,
@@ -20,43 +20,31 @@ impl<'a> Boot<'a> {
         let resume_point = tx.sequence().current().await?;
 
         let logins = tx.logins().all(resume_point.into()).await?;
+        let channels = tx.channels().all(resume_point.into()).await?;
+        let messages = tx.messages().all(resume_point.into()).await?;
+
+        tx.commit().await?;
+
         let logins = logins
             .into_iter()
             .filter_map(|login| login.as_of(resume_point))
             .collect();
 
-        let channels = tx.channels().all(resume_point.into()).await?;
-        let channels = {
-            let mut snapshots = Vec::with_capacity(channels.len());
-
-            let channels = channels.into_iter().filter_map(|channel| {
-                channel
-                    .as_of(resume_point)
-                    .map(|snapshot| (channel, snapshot))
-            });
-
-            for (channel, snapshot) in channels {
-                let messages = tx
-                    .messages()
-                    .in_channel(&channel, resume_point.into())
-                    .await?;
-
-                let messages = messages
-                    .into_iter()
-                    .filter_map(|message| message.as_of(resume_point));
-
-                snapshots.push(Channel::new(snapshot, messages));
-            }
-
-            snapshots
-        };
+        let channels = channels
+            .into_iter()
+            .filter_map(|channel| channel.as_of(resume_point))
+            .collect();
 
-        tx.commit().await?;
+        let messages = messages
+            .into_iter()
+            .filter_map(|message| message.as_of(resume_point))
+            .collect();
 
         Ok(Snapshot {
             resume_point,
             logins,
             channels,
+            messages,
         })
     }
 }
diff --git a/src/boot/mod.rs b/src/boot/mod.rs
index 1f94106..ed4764a 100644
--- a/src/boot/mod.rs
+++ b/src/boot/mod.rs
@@ -1,12 +1,7 @@
 pub mod app;
 mod routes;
 
-use crate::{
-    channel,
-    event::{Instant, Sequence},
-    login::{self, Login},
-    message,
-};
+use crate::{channel::Channel, event::Sequence, login::Login, message::Message};
 
 pub use self::routes::router;
 
@@ -15,61 +10,5 @@ pub struct Snapshot {
     pub resume_point: Sequence,
     pub logins: Vec<Login>,
     pub channels: Vec<Channel>,
-}
-
-#[derive(serde::Serialize)]
-pub struct Channel {
-    pub id: channel::Id,
-    pub name: String,
     pub messages: Vec<Message>,
 }
-
-impl Channel {
-    fn new(
-        channel: channel::Channel,
-        messages: impl IntoIterator<Item = message::Message>,
-    ) -> Self {
-        // The declarations are like this to guarantee that we aren't omitting any important fields from the corresponding types.
-        let channel::Channel { id, name } = channel;
-
-        Self {
-            id,
-            name,
-            messages: messages.into_iter().map(Message::from).collect(),
-        }
-    }
-}
-
-#[derive(serde::Serialize)]
-pub struct Message {
-    #[serde(flatten)]
-    pub sent: Instant,
-    pub sender: login::Id,
-    pub id: message::Id,
-    pub body: String,
-}
-
-impl From<message::Message> for Message {
-    fn from(message: message::Message) -> Self {
-        let message::Message {
-            sent,
-            channel: _,
-            sender,
-            id,
-            body,
-        } = message;
-
-        Self {
-            sent,
-            sender,
-            id,
-            body,
-        }
-    }
-}
-
-#[derive(serde::Serialize)]
-pub struct Body {
-    id: message::Id,
-    body: String,
-}
diff --git a/src/message/repo.rs b/src/message/repo.rs
index 0560f4a..71c6d10 100644
--- a/src/message/repo.rs
+++ b/src/message/repo.rs
@@ -112,6 +112,41 @@ impl<'c> Messages<'c> {
         Ok(messages)
     }
 
+    pub async fn all(&mut self, resume_at: ResumePoint) -> Result<Vec<History>, sqlx::Error> {
+        let messages = sqlx::query!(
+            r#"
+                select
+                    channel as "channel: channel::Id",
+                    sender as "sender: login::Id",
+                    id as "id: Id",
+                    body,
+                    sent_at as "sent_at: DateTime",
+                    sent_sequence as "sent_sequence: Sequence"
+                from message
+                where coalesce(sent_sequence <= $2, true)
+                order by sent_sequence
+            "#,
+            resume_at,
+        )
+        .map(|row| History {
+            message: Message {
+                sent: Instant {
+                    at: row.sent_at,
+                    sequence: row.sent_sequence,
+                },
+                channel: row.channel,
+                sender: row.sender,
+                id: row.id,
+                body: row.body,
+            },
+            deleted: None,
+        })
+        .fetch_all(&mut *self.0)
+        .await?;
+
+        Ok(messages)
+    }
+
     async fn by_id(&mut self, message: &Id) -> Result<History, sqlx::Error> {
         let message = sqlx::query!(
             r#"
-- 
cgit v1.2.3

