added server-side user unread_count to rooms

This commit is contained in:
2026-01-16 12:39:00 +01:00
parent 37e6bb25fc
commit 96837fcc21
5 changed files with 133 additions and 51 deletions

View File

@@ -53,6 +53,7 @@ pub fn routes() -> Router {
.route("/messages/{room_uuid}", post(create_message))
}
/// Also resets `last_read_at`
async fn list_messages(
Path(room_uuid): Path<Uuid>,
Query(query): Query<MessageFetchQuery>,
@@ -73,6 +74,11 @@ async fn list_messages(
let limit: i32 = query.limit.unwrap_or(30).abs().min(80);
let mut tx = db
.begin()
.await
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB error".into()))?;
let messages = sqlx::query_as::<_, MessageRow>(
r#"
SELECT
@@ -95,7 +101,7 @@ async fn list_messages(
.bind(room_id)
.bind(query.before)
.bind(limit)
.fetch_all(&db)
.fetch_all(&mut *tx)
.await
.map_err(|e| {
(
@@ -119,6 +125,30 @@ async fn list_messages(
messages.reverse();
sqlx::query(
r#"
UPDATE membership_
SET last_read_at = CURRENT_TIMESTAMP
WHERE user_id = $1
AND room = $2
"#,
)
.bind(user_id)
.bind(room_id)
.execute(&mut *tx)
.await
.map_err(|e| {
tracing::error!("Error updating membership timestamp: {e}");
(StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to "))
})?;
tx.commit().await.map_err(|_| {
(
StatusCode::INTERNAL_SERVER_ERROR,
"Could not list messages".into(),
)
})?;
Ok(Json(messages))
}

View File

@@ -17,6 +17,7 @@ pub struct Room {
pub global: bool,
pub owner_name: String,
pub owner_uuid: Uuid,
pub unread_count: i64,
}
#[derive(serde::Deserialize)]
@@ -89,24 +90,32 @@ async fn list_rooms(
let rooms = sqlx::query_as::<_, Room>(
r#"
SELECT r.uuid,
u.username AS owner_name,
u.uuid AS owner_uuid,
r.name,
r.global
SELECT
r.uuid,
u.username AS owner_name,
u.uuid AS owner_uuid,
r.name,
r.global,
(
SELECT COUNT(*)
FROM message_ m
WHERE m.room = r.id
AND m.sent_at > mem.last_read_at
AND m.sender != $1
) AS unread_count
FROM room_ r
JOIN user_ u ON u.id = r.owner
WHERE r.global OR EXISTS (
SELECT 1
FROM membership_ m
WHERE m.user_id = $1 AND m.room = r.id
)
LEFT JOIN membership_ mem ON mem.room = r.id AND mem.user_id = $1
WHERE r.global OR mem.user_id IS NOT NULL
"#,
)
.bind(user_id)
.fetch_all(&db)
.await
.unwrap_or(Vec::new());
.unwrap_or_else(|e| {
tracing::error!("Failed listing rooms: {e}");
Vec::new()
});
Ok(Json(rooms))
}
@@ -158,6 +167,7 @@ async fn create_room(
owner_uuid: claims.sub,
name: payload.name,
global: payload.global,
unread_count: 0,
}),
))
}
@@ -170,42 +180,63 @@ async fn get_room(
let claims = verify_jwt(headers)?;
let user_id = user_id_from_uuid(&db, claims.sub).await?;
let room_id = room_id_from_uuid(&db, room_uuid).await?;
if !is_member(user_id, room_id, &db).await {
return Err((
StatusCode::FORBIDDEN,
String::from("You are not a member of this room"),
));
#[derive(sqlx::FromRow)]
struct RoomRow {
uuid: Uuid,
owner_name: String,
owner_uuid: Uuid,
name: String,
global: bool,
unread_count: Option<i64>,
is_member: Option<bool>,
}
let room: Room = sqlx::query_as(
let row: RoomRow = sqlx::query_as(
r#"
SELECT
r.uuid,
u.username AS owner_name,
u.uuid AS owner_uuid,
r.name,
r.global
r.global,
(
SELECT COUNT(*)
FROM message_ m
WHERE m.room = r.id
AND m.sent_at > mem.last_read_at
AND m.sender != $2
) AS unread_count,
(r.global OR mem.user_id IS NOT NULL) AS is_member
FROM room_ r
JOIN user_ u ON u.id = r.owner
LEFT JOIN membership_ mem ON mem.room = r.id AND mem.user_id = $2
WHERE r.uuid = $1
"#,
)
.bind(room_uuid)
.bind(user_id)
.fetch_one(&db)
.await
.map_err(|e| {
tracing::error!("{e}");
tracing::error!("Failed getting room: {e}");
(StatusCode::NOT_FOUND, "Room not found".to_string())
})?;
if !row.is_member.unwrap_or(false) {
return Err((
StatusCode::FORBIDDEN,
"You are not a member of this room".to_string(),
));
}
Ok(Json(Room {
uuid: room_uuid,
owner_name: room.owner_name,
owner_uuid: room.owner_uuid,
name: room.name,
global: room.global,
uuid: row.uuid,
owner_name: row.owner_name,
owner_uuid: row.owner_uuid,
name: row.name,
global: row.global,
unread_count: row.unread_count.unwrap_or(0),
}))
}
@@ -362,7 +393,8 @@ async fn accept_request(
u.username AS owner_name,
u.uuid AS owner_uuid,
r.name,
r.global
r.global,
0::bigint AS unread_count
FROM room_ r
JOIN user_ u ON u.id = r.owner
WHERE r.id = $1 AND r.owner = $2
@@ -394,6 +426,7 @@ async fn accept_request(
owner_uuid: room.owner_uuid,
name: room.name,
global: room.global,
unread_count: room.unread_count,
}),
))
}