diff --git a/blah-types/src/lib.rs b/blah-types/src/lib.rs index c753224..56e13cf 100644 --- a/blah-types/src/lib.rs +++ b/blah-types/src/lib.rs @@ -14,7 +14,7 @@ use serde_with::{serde_as, DisplayFromStr}; pub use bitflags; pub use ed25519_dalek; -/// An opaque server-specific ID for room, chat item, and etc. +/// An opaque server-specific ID for rooms, messages, and etc. /// It's currently serialized as a string for JavaScript's convenience. #[serde_as] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] @@ -34,15 +34,15 @@ impl Id { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct WithItemId { +pub struct WithMsgId { pub cid: Id, #[serde(flatten)] - pub item: T, + pub msg: T, } -impl WithItemId { - pub fn new(cid: Id, item: T) -> Self { - Self { cid, item } +impl WithMsgId { + pub fn new(cid: Id, msg: T) -> Self { + Self { cid, msg } } } @@ -308,7 +308,7 @@ impl RichText { } } -pub type ChatItem = WithSig; +pub type SignedChatMsg = WithSig; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RoomMetadata { @@ -320,14 +320,14 @@ pub struct RoomMetadata { pub attrs: RoomAttrs, // Extra information is only available for some APIs. - /// The last item in the room. + /// The last message in the room. #[serde(skip_serializing_if = "Option::is_none")] - pub last_item: Option>, - /// The current user's last seen item id. + pub last_msg: Option>, + /// The current user's last seen message's `cid`. #[serde(skip_serializing_if = "Option::is_none")] pub last_seen_cid: Option, - /// The number of unseen messages, ie. the number of items from `last_seen_cid` to - /// `last_item.cid`. + /// The number of unseen messages, ie. the number of messages from `last_seen_cid` to + /// `last_msg.cid`. /// This may or may not be a precise number. #[serde(skip_serializing_if = "Option::is_none")] pub unseen_cnt: Option, @@ -547,11 +547,11 @@ mod tests { use super::*; #[test] - fn canonical_chat() { + fn canonical_msg() { let mut fake_rng = rand::rngs::mock::StepRng::new(0x42, 1); let signing_key = SigningKey::from_bytes(&[0x42; 32]); let timestamp = 0xDEAD_BEEF; - let item = WithSig::sign( + let msg = WithSig::sign( &signing_key, timestamp, &mut fake_rng, @@ -562,15 +562,15 @@ mod tests { ) .unwrap(); - let json = serde_jcs::to_string(&item).unwrap(); + let json = serde_jcs::to_string(&msg).unwrap(); let expect = expect![[ r#"{"sig":"18ee190722bebfd438c82f34890540d91578b4ba9f6c0c6011cc4fd751a321e32e9442d00dad1920799c54db011694c72a9ba993b408922e9997119209aa5e09","signee":{"nonce":66,"payload":{"rich_text":["hello"],"room":"42","typ":"chat"},"timestamp":3735928559,"user":"2152f8d19b791d24453242e15f2eab6cb7cffa7b6a5ed30097960e069881db12"}}"# ]]; expect.assert_eq(&json); - let roundtrip_item = serde_json::from_str::>(&json).unwrap(); - assert_eq!(roundtrip_item, item); - roundtrip_item.verify().unwrap(); + let roundtrip_msg = serde_json::from_str::>(&json).unwrap(); + assert_eq!(roundtrip_msg, msg); + roundtrip_msg.verify().unwrap(); } #[test] diff --git a/blahd/schema.sql b/blahd/schema.sql index 0fadb97..710c998 100644 --- a/blahd/schema.sql +++ b/blahd/schema.sql @@ -31,7 +31,7 @@ CREATE TABLE IF NOT EXISTS `room_member` ( `rid` INTEGER NOT NULL REFERENCES `room` ON DELETE CASCADE, `uid` INTEGER NOT NULL REFERENCES `user` ON DELETE RESTRICT, `permission` INTEGER NOT NULL, - -- Optionally references `room_item`(`cid`). + -- Optionally references `msg`(`cid`). `last_seen_cid` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (`rid`, `uid`) ) STRICT; @@ -39,7 +39,7 @@ CREATE TABLE IF NOT EXISTS `room_member` ( CREATE INDEX IF NOT EXISTS `ix_member_room` ON `room_member` (`uid` ASC, `rid` ASC, `permission`, `last_seen_cid`); -CREATE TABLE IF NOT EXISTS `room_item` ( +CREATE TABLE IF NOT EXISTS `msg` ( `cid` INTEGER NOT NULL PRIMARY KEY, `rid` INTEGER NOT NULL REFERENCES `room` ON DELETE CASCADE, `uid` INTEGER NOT NULL REFERENCES `user` ON DELETE RESTRICT, @@ -49,4 +49,4 @@ CREATE TABLE IF NOT EXISTS `room_item` ( `rich_text` TEXT NOT NULL ) STRICT; -CREATE INDEX IF NOT EXISTS `room_latest_item` ON `room_item` (`rid` ASC, `cid` DESC); +CREATE INDEX IF NOT EXISTS `room_latest_msg` ON `msg` (`rid` ASC, `cid` DESC); diff --git a/blahd/src/database.rs b/blahd/src/database.rs index 99323c2..5cbdd93 100644 --- a/blahd/src/database.rs +++ b/blahd/src/database.rs @@ -10,7 +10,7 @@ static INIT_SQL: &str = include_str!("../schema.sql"); // Simple and stupid version check for now. // `echo -n 'blahd-database-0' | sha256sum | head -c5` || version -const APPLICATION_ID: i32 = 0xd9e_8403; +const APPLICATION_ID: i32 = 0xd9e_8404; #[derive(Debug)] pub struct Database { diff --git a/blahd/src/event.rs b/blahd/src/event.rs index 17dfe65..f2986c8 100644 --- a/blahd/src/event.rs +++ b/blahd/src/event.rs @@ -8,7 +8,7 @@ use std::task::{Context, Poll}; use anyhow::{bail, Context as _, Result}; use axum::extract::ws::{Message, WebSocket}; -use blah_types::{AuthPayload, ChatItem, WithSig}; +use blah_types::{AuthPayload, SignedChatMsg, WithSig}; use futures_util::future::Either; use futures_util::stream::SplitSink; use futures_util::{stream_select, SinkExt as _, Stream, StreamExt}; @@ -28,8 +28,8 @@ pub enum Incoming {} #[derive(Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum Outgoing<'a> { - /// A chat message from a joined room. - Chat(&'a ChatItem), + /// A message from a joined room. + Msg(&'a SignedChatMsg), /// The receiver is too slow to receive and some events and are dropped. // FIXME: Should we indefinitely buffer them or just disconnect the client instead? Lagged, @@ -71,11 +71,11 @@ impl WsSenderWrapper<'_, '_> { } } -type UserEventSender = broadcast::Sender>; +type UserEventSender = broadcast::Sender>; #[derive(Debug)] struct UserEventReceiver { - rx: BroadcastStream>, + rx: BroadcastStream>, st: Arc, uid: u64, } @@ -93,7 +93,7 @@ impl Drop for UserEventReceiver { } impl Stream for UserEventReceiver { - type Item = Result, BroadcastStreamRecvError>; + type Item = Result, BroadcastStreamRecvError>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.rx.poll_next_unpin(cx) @@ -155,7 +155,7 @@ pub async fn handle_ws(st: Arc, ws: &mut WebSocket) -> Result match serde_json::from_str::(&msg?)? {}, Either::Right(ret) => { let msg = match &ret { - Ok(chat) => Outgoing::Chat(chat), + Ok(chat) => Outgoing::Msg(chat), Err(BroadcastStreamRecvError::Lagged(_)) => Outgoing::Lagged, }; // TODO: Concurrent send. diff --git a/blahd/src/lib.rs b/blahd/src/lib.rs index 2d1fc28..927d86c 100644 --- a/blahd/src/lib.rs +++ b/blahd/src/lib.rs @@ -11,9 +11,9 @@ use axum::routing::{get, post}; use axum::{Json, Router}; use axum_extra::extract::WithRejection as R; use blah_types::{ - ChatItem, ChatPayload, CreateGroup, CreatePeerChat, CreateRoomPayload, Id, MemberPermission, - RoomAdminOp, RoomAdminPayload, RoomAttrs, RoomMetadata, ServerPermission, Signee, UserKey, - WithItemId, WithSig, + ChatPayload, CreateGroup, CreatePeerChat, CreateRoomPayload, Id, MemberPermission, RoomAdminOp, + RoomAdminPayload, RoomAttrs, RoomMetadata, ServerPermission, SignedChatMsg, Signee, UserKey, + WithMsgId, WithSig, }; use config::ServerConfig; use ed25519_dalek::SIGNATURE_LENGTH; @@ -100,8 +100,8 @@ pub fn router(st: Arc) -> Router { .route("/room/:rid", get(room_get_metadata)) // NB. Sync with `feed_url` and `next_url` generation. .route("/room/:rid/feed.json", get(room_get_feed)) - .route("/room/:rid/item", get(room_item_list).post(room_item_post)) - .route("/room/:rid/item/:cid/seen", post(room_item_mark_seen)) + .route("/room/:rid/msg", get(room_msg_list).post(room_msg_post)) + .route("/room/:rid/msg/:cid/seen", post(room_msg_mark_seen)) .route("/room/:rid/admin", post(room_admin)) .layer(tower_http::limit::RequestBodyLimitLayer::new( st.config.max_request_len, @@ -188,12 +188,12 @@ async fn room_list( .query_map(params, |row| { // TODO: Extract this into a function. let rid = row.get("rid")?; - let last_item = row + let last_msg = row .get::<_, Option>("cid")? .map(|cid| { - Ok::<_, rusqlite::Error>(WithItemId { + Ok::<_, rusqlite::Error>(WithMsgId { cid, - item: ChatItem { + msg: SignedChatMsg { sig: row.get("sig")?, signee: Signee { nonce: row.get("nonce")?, @@ -212,7 +212,7 @@ async fn room_list( rid, title: row.get("title")?, attrs: row.get("attrs")?, - last_item, + last_msg, last_seen_cid: Some(row.get::<_, Id>("last_seen_cid")?) .filter(|cid| cid.0 != 0), unseen_cnt: row.get("unseen_cnt").ok(), @@ -232,7 +232,7 @@ async fn room_list( SELECT `rid`, `title`, `attrs`, 0 AS `last_seen_cid`, `cid`, `last_author`.`userkey`, `timestamp`, `nonce`, `sig`, `rich_text` FROM `room` - LEFT JOIN `room_item` USING (`rid`) + LEFT JOIN `msg` USING (`rid`) LEFT JOIN `user` AS `last_author` USING (`uid`) WHERE `rid` > :start_rid AND (`attrs` & :perm) = :perm @@ -257,8 +257,8 @@ async fn room_list( FROM `user` JOIN `room_member` USING (`uid`) JOIN `room` USING (`rid`) - LEFT JOIN `room_item` USING (`rid`) - LEFT JOIN `user` AS `last_author` ON (`last_author`.`uid` = `room_item`.`uid`) + LEFT JOIN `msg` USING (`rid`) + LEFT JOIN `user` AS `last_author` ON (`last_author`.`uid` = `msg`.`uid`) LEFT JOIN `user` AS `peer_user` ON (`peer_user`.`uid` = `room`.`peer1` + `room`.`peer2` - `user`.`uid`) WHERE `user`.`userkey` = :userkey AND @@ -283,14 +283,14 @@ async fn room_list( `cid`, `last_author`.`userkey`, `timestamp`, `nonce`, `sig`, `rich_text`, `peer_user`.`userkey` AS `peer_userkey`, (SELECT COUNT(*) - FROM `room_item` AS `unseen_item` - WHERE `unseen_item`.`rid` = `room`.`rid` AND - `last_seen_cid` < `unseen_item`.`cid`) AS `unseen_cnt` + FROM `msg` AS `unseen_msg` + WHERE `unseen_msg`.`rid` = `room`.`rid` AND + `last_seen_cid` < `unseen_msg`.`cid`) AS `unseen_cnt` FROM `user` JOIN `room_member` USING (`uid`) JOIN `room` USING (`rid`) - LEFT JOIN `room_item` USING (`rid`) - LEFT JOIN `user` AS `last_author` ON (`last_author`.`uid` = `room_item`.`uid`) + LEFT JOIN `msg` USING (`rid`) + LEFT JOIN `user` AS `last_author` ON (`last_author`.`uid` = `msg`.`uid`) LEFT JOIN `user` AS `peer_user` ON (`peer_user`.`uid` = `room`.`peer1` + `room`.`peer2` - `user`.`uid`) WHERE `user`.`userkey` = :userkey AND @@ -536,7 +536,7 @@ struct Pagination { /// Maximum page size. top: Option, /// Only return items before (excluding) this token. - /// Useful for `room_item_list` to pass `last_seen_cid` without over-fetching. + /// Useful for `room_msg_list` to pass `last_seen_cid` without over-fetching. until_token: Option, } @@ -550,24 +550,24 @@ impl Pagination { } #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct RoomItems { - pub items: Vec>, +pub struct RoomMsgs { + pub msgs: Vec>, #[serde(skip_serializing_if = "Option::is_none")] pub skip_token: Option, } -async fn room_item_list( +async fn room_msg_list( st: ArcState, R(Path(rid), _): RE>, R(Query(pagination), _): RE>, auth: MaybeAuth, -) -> Result, ApiError> { - let (items, skip_token) = { +) -> Result, ApiError> { + let (msgs, skip_token) = { let conn = st.db.get(); get_room_if_readable(&conn, rid, auth.into_optional()?.as_ref(), |_row| Ok(()))?; - query_room_items(&st, &conn, rid, pagination)? + query_room_msgs(&st, &conn, rid, pagination)? }; - Ok(Json(RoomItems { items, skip_token })) + Ok(Json(RoomMsgs { msgs, skip_token })) } async fn room_get_metadata( @@ -589,7 +589,7 @@ async fn room_get_metadata( attrs, // TODO: Should we include these here? - last_item: None, + last_msg: None, last_seen_cid: None, unseen_cnt: None, member_permission: None, @@ -603,28 +603,28 @@ async fn room_get_feed( R(Query(pagination), _): RE>, ) -> Result { let title; - let (items, skip_token) = { + let (msgs, skip_token) = { let conn = st.db.get(); title = get_room_if_readable(&conn, rid, None, |row| row.get::<_, String>("title"))?; - query_room_items(&st, &conn, rid, pagination)? + query_room_msgs(&st, &conn, rid, pagination)? }; - let items = items + let items = msgs .into_iter() - .map(|WithItemId { cid, item }| { - let time = SystemTime::UNIX_EPOCH + Duration::from_secs(item.signee.timestamp); + .map(|WithMsgId { cid, msg }| { + let time = SystemTime::UNIX_EPOCH + Duration::from_secs(msg.signee.timestamp); let author = FeedAuthor { - name: item.signee.user.to_string(), + name: msg.signee.user.to_string(), }; FeedItem { id: cid.to_string(), - content_html: item.signee.payload.rich_text.html().to_string(), + content_html: msg.signee.payload.rich_text.html().to_string(), date_published: humantime::format_rfc3339(time).to_string(), authors: (author,), extra: FeedItemExtra { - timestamp: item.signee.timestamp, - nonce: item.signee.nonce, - sig: item.sig, + timestamp: msg.signee.timestamp, + nonce: msg.signee.nonce, + sig: msg.sig, }, } }) @@ -734,19 +734,19 @@ fn get_room_if_readable( }) } -/// Get room items with pagination parameters, -/// return a page of items and the next skip_token if this is not the last page. -fn query_room_items( +/// Get room messages with pagination parameters, +/// return a page of messages and the next `skip_token` if this is not the last page. +fn query_room_msgs( st: &AppState, conn: &Connection, rid: Id, pagination: Pagination, -) -> Result<(Vec>, Option), ApiError> { +) -> Result<(Vec>, Option), ApiError> { let page_len = pagination.effective_page_len(st); let mut stmt = conn.prepare( r" SELECT `cid`, `timestamp`, `nonce`, `sig`, `userkey`, `sig`, `rich_text` - FROM `room_item` + FROM `msg` JOIN `user` USING (`uid`) WHERE `rid` = :rid AND :after_cid < `cid` AND @@ -755,7 +755,7 @@ fn query_room_items( LIMIT :limit ", )?; - let items = stmt + let msgs = stmt .query_and_then( named_params! { ":rid": rid, @@ -764,9 +764,9 @@ fn query_room_items( ":limit": page_len, }, |row| { - Ok(WithItemId { + Ok(WithMsgId { cid: row.get("cid")?, - item: ChatItem { + msg: SignedChatMsg { sig: row.get("sig")?, signee: Signee { nonce: row.get("nonce")?, @@ -783,12 +783,12 @@ fn query_room_items( )? .collect::>>()?; let skip_token = - (items.len() == page_len).then(|| items.last().expect("page must not be empty").cid); + (msgs.len() == page_len).then(|| msgs.last().expect("page must not be empty").cid); - Ok((items, skip_token)) + Ok((msgs, skip_token)) } -async fn room_item_post( +async fn room_msg_post( st: ArcState, R(Path(rid), _): RE>, SignedJson(chat): SignedJson, @@ -836,14 +836,14 @@ async fn room_item_post( return Err(error_response!( StatusCode::FORBIDDEN, "permission_denied", - "the user does not have permission to post item in the room", + "the user does not have permission to post in the room", )); } let cid = Id::gen(); conn.execute( r" - INSERT INTO `room_item` (`cid`, `rid`, `uid`, `timestamp`, `nonce`, `sig`, `rich_text`) + INSERT INTO `msg` (`cid`, `rid`, `uid`, `timestamp`, `nonce`, `sig`, `rich_text`) VALUES (:cid, :rid, :uid, :timestamp, :nonce, :sig, :rich_text) ", named_params! { @@ -1048,7 +1048,7 @@ async fn room_leave(st: &AppState, rid: Id, user: UserKey) -> Result<(), ApiErro Ok(()) } -async fn room_item_mark_seen( +async fn room_msg_mark_seen( st: ArcState, R(Path((rid, cid)), _): RE>, Auth(user): Auth, diff --git a/blahd/tests/webapi.rs b/blahd/tests/webapi.rs index 4b6e29d..c3e5996 100644 --- a/blahd/tests/webapi.rs +++ b/blahd/tests/webapi.rs @@ -7,11 +7,11 @@ use std::sync::{Arc, LazyLock}; use anyhow::Result; use blah_types::{ - get_timestamp, AuthPayload, ChatItem, ChatPayload, CreateGroup, CreatePeerChat, - CreateRoomPayload, Id, MemberPermission, RichText, RoomAdminOp, RoomAdminPayload, RoomAttrs, - RoomMember, RoomMemberList, RoomMetadata, ServerPermission, UserKey, WithItemId, WithSig, + get_timestamp, AuthPayload, ChatPayload, CreateGroup, CreatePeerChat, CreateRoomPayload, Id, + MemberPermission, RichText, RoomAdminOp, RoomAdminPayload, RoomAttrs, RoomMember, + RoomMemberList, RoomMetadata, ServerPermission, SignedChatMsg, UserKey, WithMsgId, WithSig, }; -use blahd::{ApiError, AppState, Database, RoomItems, RoomList}; +use blahd::{ApiError, AppState, Database, RoomList, RoomMsgs}; use ed25519_dalek::SigningKey; use futures_util::TryFutureExt; use rand::rngs::mock::StepRng; @@ -176,8 +176,8 @@ impl Server { rid: Id, key: &SigningKey, text: &str, - ) -> impl Future>> + use<'_> { - let item = sign( + ) -> impl Future>> + use<'_> { + let msg = sign( key, &mut *self.rng.borrow_mut(), ChatPayload { @@ -189,13 +189,13 @@ impl Server { let cid = self .request::<_, Id>( Method::POST, - &format!("/room/{rid}/item"), + &format!("/room/{rid}/msg"), None, - Some(item.clone()), + Some(msg.clone()), ) .await? .unwrap(); - Ok(WithItemId { cid, item }) + Ok(WithMsgId { cid, msg }) } } } @@ -263,7 +263,7 @@ async fn room_create_get(server: Server, ref mut rng: impl RngCore, #[case] publ } else { RoomAttrs::empty() }, - last_item: None, + last_msg: None, last_seen_cid: None, unseen_cnt: None, member_permission: None, @@ -406,7 +406,7 @@ async fn room_join_leave(server: Server, ref mut rng: impl RngCore) { #[rstest] #[tokio::test] -async fn room_item_post_read(server: Server, ref mut rng: impl RngCore) { +async fn room_chat_post_read(server: Server, ref mut rng: impl RngCore) { let rid_pub = server .create_room( &ALICE_PRIV, @@ -430,9 +430,9 @@ async fn room_item_post_read(server: Server, ref mut rng: impl RngCore) { }, ) }; - let post = |rid: Id, chat: ChatItem| { + let post = |rid: Id, chat: SignedChatMsg| { server - .request::<_, Id>(Method::POST, &format!("/room/{rid}/item"), None, Some(chat)) + .request::<_, Id>(Method::POST, &format!("/room/{rid}/msg"), None, Some(chat)) .map_ok(|opt| opt.unwrap()) }; @@ -471,94 +471,88 @@ async fn room_item_post_read(server: Server, ref mut rng: impl RngCore) { .await .expect_api_err(StatusCode::NOT_FOUND, "not_found"); - //// Item listing //// + //// Msgs listing //// - let chat1 = WithItemId::new(cid1, chat1); - let chat2 = WithItemId::new(cid2, chat2); + let chat1 = WithMsgId::new(cid1, chat1); + let chat2 = WithMsgId::new(cid2, chat2); // List with default page size. - let items = server - .get::(&format!("/room/{rid_pub}/item"), None) + let msgs = server + .get::(&format!("/room/{rid_pub}/msg"), None) .await .unwrap(); assert_eq!( - items, - RoomItems { - items: vec![chat2.clone(), chat1.clone()], + msgs, + RoomMsgs { + msgs: vec![chat2.clone(), chat1.clone()], skip_token: None, }, ); // List with small page size. - let items = server - .get::(&format!("/room/{rid_pub}/item?top=1"), None) + let msgs = server + .get::(&format!("/room/{rid_pub}/msg?top=1"), None) .await .unwrap(); assert_eq!( - items, - RoomItems { - items: vec![chat2.clone()], + msgs, + RoomMsgs { + msgs: vec![chat2.clone()], skip_token: Some(cid2), }, ); // Second page. - let items = server - .get::( - &format!("/room/{rid_pub}/item?skipToken={cid2}&top=1"), - None, - ) + let msgs = server + .get::(&format!("/room/{rid_pub}/msg?skipToken={cid2}&top=1"), None) .await .unwrap(); assert_eq!( - items, - RoomItems { - items: vec![chat1.clone()], + msgs, + RoomMsgs { + msgs: vec![chat1.clone()], skip_token: Some(cid1), }, ); // No more. - let items = server - .get::( - &format!("/room/{rid_pub}/item?skipToken={cid1}&top=1"), - None, - ) + let msgs = server + .get::(&format!("/room/{rid_pub}/msg?skipToken={cid1}&top=1"), None) .await .unwrap(); - assert_eq!(items, RoomItems::default()); + assert_eq!(msgs, RoomMsgs::default()); //// Private room //// // Access without token. server - .get::(&format!("/room/{rid_priv}/item"), None) + .get::(&format!("/room/{rid_priv}/msg"), None) .await .expect_api_err(StatusCode::NOT_FOUND, "not_found"); // Not a member. server - .get::( - &format!("/room/{rid_priv}/item"), + .get::( + &format!("/room/{rid_priv}/msg"), Some(&auth(&BOB_PRIV, rng)), ) .await .expect_api_err(StatusCode::NOT_FOUND, "not_found"); // Ok. - let items = server - .get::( - &format!("/room/{rid_priv}/item"), + let msgs = server + .get::( + &format!("/room/{rid_priv}/msg"), Some(&auth(&ALICE_PRIV, rng)), ) .await .unwrap(); - assert_eq!(items, RoomItems::default()); + assert_eq!(msgs, RoomMsgs::default()); } #[rstest] #[tokio::test] -async fn last_seen_item(server: Server, ref mut rng: impl RngCore) { +async fn last_seen(server: Server, ref mut rng: impl RngCore) { let title = "public room"; let attrs = RoomAttrs::PUBLIC_READABLE | RoomAttrs::PUBLIC_JOINABLE; let member_perm = MemberPermission::ALL; @@ -571,7 +565,7 @@ async fn last_seen_item(server: Server, ref mut rng: impl RngCore) { let alice_chat1 = server.post_chat(rid, &ALICE_PRIV, "alice1").await.unwrap(); let alice_chat2 = server.post_chat(rid, &ALICE_PRIV, "alice2").await.unwrap(); - // 2 new items. + // 2 new msgs. let rooms = server .get::("/room?filter=unseen", Some(&auth(&ALICE_PRIV, rng))) .await @@ -583,7 +577,7 @@ async fn last_seen_item(server: Server, ref mut rng: impl RngCore) { rid, title: Some(title.into()), attrs, - last_item: Some(alice_chat2.clone()), + last_msg: Some(alice_chat2.clone()), last_seen_cid: None, unseen_cnt: Some(2), member_permission: Some(member_perm), @@ -596,7 +590,7 @@ async fn last_seen_item(server: Server, ref mut rng: impl RngCore) { let seen = |key: &SigningKey, cid: Id| { server.request::( Method::POST, - &format!("/room/{rid}/item/{cid}/seen"), + &format!("/room/{rid}/msg/{cid}/seen"), Some(&auth(key, &mut *server.rng.borrow_mut())), None, ) @@ -605,7 +599,7 @@ async fn last_seen_item(server: Server, ref mut rng: impl RngCore) { // Mark the first one seen. seen(&ALICE_PRIV, alice_chat1.cid).await.unwrap(); - // 1 new item. + // 1 new msg. let rooms = server .get::("/room?filter=unseen", Some(&auth(&ALICE_PRIV, rng))) .await @@ -617,7 +611,7 @@ async fn last_seen_item(server: Server, ref mut rng: impl RngCore) { rid, title: Some(title.into()), attrs, - last_item: Some(alice_chat2.clone()), + last_msg: Some(alice_chat2.clone()), last_seen_cid: Some(alice_chat1.cid), unseen_cnt: Some(1), member_permission: Some(member_perm), @@ -627,7 +621,7 @@ async fn last_seen_item(server: Server, ref mut rng: impl RngCore) { } ); - // Mark the second one seen. Now there is no new items. + // Mark the second one seen. Now there is no new messages. seen(&ALICE_PRIV, alice_chat2.cid).await.unwrap(); let rooms = server .get::("/room?filter=unseen", Some(&auth(&ALICE_PRIV, rng))) @@ -635,7 +629,7 @@ async fn last_seen_item(server: Server, ref mut rng: impl RngCore) { .unwrap(); assert_eq!(rooms, RoomList::default()); - // Marking a seen item seen is a no-op. + // Marking a seen message seen is a no-op. seen(&ALICE_PRIV, alice_chat2.cid).await.unwrap(); let rooms = server .get::("/room?filter=unseen", Some(&auth(&ALICE_PRIV, rng))) @@ -688,7 +682,7 @@ async fn peer_chat(server: Server, ref mut rng: impl RngCore) { rid, title: None, attrs: RoomAttrs::PEER_CHAT, - last_item: None, + last_msg: None, last_seen_cid: None, unseen_cnt: None, member_permission: None, diff --git a/docs/webapi.yaml b/docs/webapi.yaml index 4064502..19563cf 100644 --- a/docs/webapi.yaml +++ b/docs/webapi.yaml @@ -72,8 +72,8 @@ paths: schema: type: string description: - The maximum number of items returned in each page. This is only an - advice and server can clamp it to a smaller value. + The maximum count of rooms returned in a single response. This is + only an advice and server can clamp it to a smaller value. - name: skipToken in: query @@ -107,7 +107,7 @@ paths: /room/create: post: - summary: Create room + summary: Create a room description: When `typ="create_room"`, create a multi-user room. @@ -229,13 +229,13 @@ paths: schema: $ref: '#/components/schemas/ApiError' - /room/{rid}/item: + /room/{rid}/msg: get: - summary: List items in room + summary: List messages in a room description: | - Return items in reversed time order, up to `skipToken` items in a - single response, from room {rid}. - The last (oldest) chat `cid` will be returned as `skipToken` in + Return a list of messages in reversed server time order, up to length `top` + in a single response, from room {rid}. + The last (oldest) message's `cid` will be returned as `skipToken` in response, which can be used as query parameter for the next GET, to repeatedly fetch more history. @@ -251,8 +251,9 @@ paths: schema: type: integer description: | - The maximum number of items to return. This is an advice and may be - further clamped by the server. It must not be zero. + The number of items returned in a single response. This is + an advice and may be further clamped by the server. It must not be + zero. - name: skipToken in: query @@ -267,7 +268,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RoomItems' + $ref: '#/components/schemas/RoomMsgs' 404: description: | @@ -278,7 +279,7 @@ paths: $ref: '#/components/schemas/ApiError' post: - summary: Post item in room + summary: Post a `Msg` into a room requestBody: content: application/json: @@ -291,7 +292,7 @@ paths: application/json: schema: type: string - description: Newly created item `cid`. + description: Newly created message id `cid`. 403: description: The user does not have permission to post in this room. @@ -307,15 +308,16 @@ paths: schema: $ref: '#/components/schemas/ApiError' - /room/{rid}/item/{cid}/seen: + /room/{rid}/msg/{cid}/seen: post: - summary: Mark item seen + summary: Mark a message seen description: | - Mark item {cid} in room {rid} seen by the current user. + Mark message {cid} and everything before it in room {rid} seen by the + current user. - Server may enforce that last seen item does not go backward. Marking - an older item seen or sending the same request multiple times can be a - no-op. + Server may enforce that last seen message does not go backward. Marking + an older message seen or sending the same request multiple times can be + a no-op. parameters: - name: Authorization @@ -398,8 +400,8 @@ components: description: Room attributes bitset, see `RoomAttrs`. type: integer format: int64 - last_item: - $ref: '#/components/schemas/WithItemId-WithSig-Chat' + last_msg: + $ref: '#/components/schemas/WithMsgId-WithSig-Chat' last_seen_cid: description: The `cid` of the last chat being marked as seen. type: string @@ -429,16 +431,16 @@ components: type: integer format: int64 - RoomItems: + RoomMsgs: type: object required: - - items + - msgs properties: - items: - description: Room items in reversed server-received time order. + msgs: + description: Room messages in reversed server-received time order. type: array items: - $ref: '#/components/schemas/WithItemId-WithSig-Chat' + $ref: '#/components/schemas/WithMsgId-WithSig-Chat' skip_token: description: The token for fetching the next page. type: string @@ -581,14 +583,14 @@ components: timestamp: 1724966284 user: 83ce46ced47ec0391c64846cbb6c507250ead4985b6a044d68751edc46015dd7 - WithItemId-WithSig-Chat: + WithMsgId-WithSig-Chat: allOf: - $ref: '#/components/schemas/WithSig-Chat' - type: object properties: cid: type: string - description: An opaque server-specific item identifier. + description: An opaque server-specific identifier. WithSig-CreateRoom: type: object diff --git a/test-frontend/main.js b/test-frontend/main.js index 64942d5..3519e30 100644 --- a/test-frontend/main.js +++ b/test-frontend/main.js @@ -186,15 +186,15 @@ async function enterRoom(rid) { }); genAuthHeader() - .then(opts => fetch(`${serverUrl}/room/${rid}/item`, opts)) + .then(opts => fetch(`${serverUrl}/room/${rid}/msg`, opts)) .then(async (resp) => { return [resp.status, await resp.json()]; }) .then(async ([status, json]) => { if (status !== 200) throw new Error(`status ${status}: ${json.error.message}`); - const { items } = json - items.reverse(); - for (const chat of items) { - lastCid = chat.cid; - await showChatMsg(chat); + const { msgs } = json + msgs.reverse(); + for (const msg of msgs) { + lastCid = msg.cid; + await showChatMsg(msg); } log('---history---'); }) @@ -242,12 +242,12 @@ async function connectServer(newServerUrl) { if (msg.chat.signee.payload.room === curRoom) { await showChatMsg(msg.chat); } else { - console.log('ignore background room item'); + console.log('ignore background room msg'); } } else if (msg.lagged !== undefined) { log('some events are dropped because of queue overflow') } else { - log(`unknown ws message: ${e.data}`); + log(`unknown ws msg: ${e.data}`); } }; @@ -269,11 +269,11 @@ async function loadRoomList(autoJoin) { const resp = await fetch(`${serverUrl}/room?filter=${filter}`, await genAuthHeader()) const json = await resp.json() if (resp.status !== 200) throw new Error(`status ${resp.status}: ${json.error.message}`); - for (const { rid, title, attrs, last_item, last_seen_cid } of json.rooms) { + for (const { rid, title, attrs, last_msg, last_seen_cid } of json.rooms) { const el = document.createElement('option'); el.value = rid; el.innerText = `${title} (rid=${rid}, attrs=${attrs})`; - if (last_item !== undefined && last_item.cid !== last_seen_cid) { + if (last_msg !== undefined && last_msg.cid !== last_seen_cid) { el.innerText += ' (unread)'; } targetEl.appendChild(el); @@ -381,7 +381,7 @@ async function postChat(text) { } else { richText = [text]; } - await signAndPost(`${serverUrl}/room/${curRoom}/item`, { + await signAndPost(`${serverUrl}/room/${curRoom}/msg`, { // sorted fields. rich_text: richText, room: curRoom, @@ -398,7 +398,7 @@ async function postChat(text) { async function markSeen() { try { - const resp = await fetch(`${serverUrl}/room/${curRoom}/item/${lastCid}/seen`, { + const resp = await fetch(`${serverUrl}/room/${curRoom}/msg/${lastCid}/seen`, { method: 'POST', headers: (await genAuthHeader()).headers, })