implemented progressive message fetching on scroll
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user