implemented progressive message fetching on scroll

This commit is contained in:
2026-01-05 19:34:29 +01:00
parent 79ce9f7db2
commit 7732fbec32
3 changed files with 48 additions and 28 deletions

View File

@@ -46,7 +46,7 @@ CREATE TABLE IF NOT EXISTS room_invite_ (
CREATE TABLE IF NOT EXISTS message_ ( CREATE TABLE IF NOT EXISTS message_ (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
-- uuid UUID NOT NULL, uuid UUID NOT NULL,
sender INT REFERENCES user_(id) NOT NULL, sender INT REFERENCES user_(id) NOT NULL,
room INT REFERENCES room_(id) NOT NULL, room INT REFERENCES room_(id) NOT NULL,
message_type VARCHAR(32) NOT NULL, message_type VARCHAR(32) NOT NULL,

View File

@@ -14,13 +14,13 @@ INSERT INTO membership_ (user_id, room) VALUES
(3, 1), -- Carol in General Discussion (3, 1), -- Carol in General Discussion
(1, 3); -- Alice in Random Memes (1, 3); -- Alice in Random Memes
INSERT INTO message_ (sender, room, message_type, content) VALUES INSERT INTO message_ (sender, room, message_type, content, uuid) VALUES
(1, 1, 'text', 'Hey everyone, hows it going?'), (1, 1, 'text', 'Hey everyone, hows it going?', '3ae85002-8a82-479f-b1c9-6faa3dceb2f3'),
(2, 1, 'text', 'All good! Just trying to get through some work.'), (2, 1, 'text', 'All good! Just trying to get through some work.', '8e60aa27-9eef-4f1c-a913-47ac6ea1229b'),
(3, 1, 'text', 'Hello! How are you guys?'), (3, 1, 'text', 'Hello! How are you guys?', 'f2b688f8-6678-465c-8092-9636a9ae2f16'),
(2, 2, 'text', 'Anyone seen the new tech updates?'), (2, 2, 'text', 'Anyone seen the new tech updates?', '20c6b5d4-c8b1-4afe-844c-339e128fc344'),
(1, 3, 'image', 'Heres a funny meme I found!'), (1, 3, 'image', 'Heres a funny meme I found!', '7dd79706-9187-47a5-b4f0-86e07cbb4564'),
(3, 1, 'text', 'I love how active this room is!'); (3, 1, 'text', 'I love how active this room is!', '9024823f-1b0c-436b-b81d-08dc06ac34df');
INSERT INTO friendship_ (user_first, user_second) VALUES INSERT INTO friendship_ (user_first, user_second) VALUES
(1, 3), -- Alice and Carol (1, 3), -- Alice and Carol

View File

@@ -1,6 +1,6 @@
use axum::{ use axum::{
Extension, Json, Router, Extension, Json, Router,
extract::Path, extract::{Path, Query},
http::{HeaderMap, StatusCode}, http::{HeaderMap, StatusCode},
routing::{get, post}, routing::{get, post},
}; };
@@ -15,6 +15,7 @@ use crate::{
#[derive(sqlx::FromRow, serde::Serialize, Debug)] #[derive(sqlx::FromRow, serde::Serialize, Debug)]
pub struct MessageRow { pub struct MessageRow {
pub uuid: Uuid,
pub sender: String, pub sender: String,
pub message_type: String, pub message_type: String,
pub content: String, pub content: String,
@@ -36,6 +37,12 @@ pub struct NewMessagePayload {
pub content: String, pub content: String,
} }
#[derive(serde::Deserialize)]
struct MessageFetchQuery {
limit: Option<i32>,
before: Option<Uuid>,
}
pub fn routes() -> Router { pub fn routes() -> Router {
Router::new() Router::new()
.route("/messages/{room_uuid}", get(list_messages)) .route("/messages/{room_uuid}", get(list_messages))
@@ -44,6 +51,7 @@ pub fn routes() -> Router {
async fn list_messages( async fn list_messages(
Path(room_uuid): Path<Uuid>, Path(room_uuid): Path<Uuid>,
Query(query): Query<MessageFetchQuery>,
headers: HeaderMap, headers: HeaderMap,
Extension(db): Extension<PgPool>, Extension(db): Extension<PgPool>,
) -> Result<Json<Vec<Message>>, (StatusCode, String)> { ) -> Result<Json<Vec<Message>>, (StatusCode, String)> {
@@ -59,35 +67,42 @@ async fn list_messages(
)); ));
} }
let limit: i32 = query.limit.unwrap_or(30).abs().min(80);
let messages = sqlx::query_as::<_, MessageRow>( let messages = sqlx::query_as::<_, MessageRow>(
r#" r#"
SELECT SELECT
u.username AS sender, m.uuid,
r.uuid AS room, u.username AS sender,
m.message_type, r.uuid AS room,
m.content, m.message_type,
m.sent_at m.content,
FROM message_ m m.sent_at
JOIN user_ u ON u.id = m.sender FROM message_ m
JOIN room_ r ON r.id = m.room JOIN user_ u ON u.id = m.sender
WHERE m.room = $1 JOIN room_ r ON r.id = m.room
ORDER BY m.id WHERE m.room = $1
"#, AND ($2::uuid IS NULL OR m.id < (SELECT id FROM message_ WHERE uuid = $2))
ORDER BY m.id DESC
LIMIT $3
"#,
) )
.bind(room_id) .bind(room_id)
.bind(query.before)
.bind(limit)
.fetch_all(&db) .fetch_all(&db)
.await .await
.map_err(|_| { .map_err(|e| {
( (
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
"Failed to list messages".into(), format!("Failed to list messages: {e}"),
) )
})?; })?;
let messages: Vec<Message> = messages let mut messages: Vec<Message> = messages
.into_iter() .into_iter()
.map(|m| Message { .map(|m| Message {
uuid: uuid::Uuid::now_v7(), uuid: m.uuid,
sender: m.sender, sender: m.sender,
message_type: m.message_type, message_type: m.message_type,
content: m.content, content: m.content,
@@ -95,6 +110,8 @@ async fn list_messages(
}) })
.collect(); .collect();
messages.reverse();
Ok(Json(messages)) Ok(Json(messages))
} }
@@ -117,14 +134,17 @@ async fn create_message(
)); ));
} }
let uuid = Uuid::now_v7();
let sent_at: chrono::NaiveDateTime = sqlx::query_scalar( let sent_at: chrono::NaiveDateTime = sqlx::query_scalar(
"INSERT INTO message_ (sender, room, message_type, content) "INSERT INTO message_ (sender, room, message_type, content, uuid)
VALUES ($1, $2, $3, $4) RETURNING sent_at", VALUES ($1, $2, $3, $4, $5) RETURNING sent_at",
) )
.bind(user_id) .bind(user_id)
.bind(room_id) .bind(room_id)
.bind(&payload.message_type) .bind(&payload.message_type)
.bind(&payload.content) .bind(&payload.content)
.bind(&uuid)
.fetch_one(&db) .fetch_one(&db)
.await .await
.map_err(|_| (StatusCode::BAD_REQUEST, "Could not create message".into()))?; .map_err(|_| (StatusCode::BAD_REQUEST, "Could not create message".into()))?;
@@ -132,7 +152,7 @@ async fn create_message(
let sender_name = username_from_uuid(&db, claims.sub).await?; let sender_name = username_from_uuid(&db, claims.sub).await?;
let message = Message { let message = Message {
uuid: uuid::Uuid::now_v7(), uuid: uuid,
sender: sender_name, sender: sender_name,
message_type: payload.message_type, message_type: payload.message_type,
content: payload.content, content: payload.content,