mirror of
https://github.com/Blah-IM/blahrs.git
synced 2025-05-01 08:41:09 +00:00
Impl room leaving and fix frontend room combobox
This commit is contained in:
parent
cc51d53575
commit
2eb884766a
4 changed files with 159 additions and 40 deletions
|
@ -12,8 +12,8 @@ use axum::routing::{get, post};
|
||||||
use axum::{Json, Router};
|
use axum::{Json, Router};
|
||||||
use axum_extra::extract::WithRejection;
|
use axum_extra::extract::WithRejection;
|
||||||
use blah::types::{
|
use blah::types::{
|
||||||
ChatItem, ChatPayload, CreateRoomPayload, MemberPermission, RoomAdminPayload, RoomAttrs,
|
ChatItem, ChatPayload, CreateRoomPayload, MemberPermission, RoomAdminOp, RoomAdminPayload,
|
||||||
ServerPermission, Signee, UserKey, WithSig,
|
RoomAttrs, ServerPermission, Signee, UserKey, WithSig,
|
||||||
};
|
};
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use ed25519_dalek::SIGNATURE_LENGTH;
|
use ed25519_dalek::SIGNATURE_LENGTH;
|
||||||
|
@ -737,7 +737,7 @@ async fn room_admin(
|
||||||
Path(ruuid): Path<Uuid>,
|
Path(ruuid): Path<Uuid>,
|
||||||
SignedJson(op): SignedJson<RoomAdminPayload>,
|
SignedJson(op): SignedJson<RoomAdminPayload>,
|
||||||
) -> Result<StatusCode, ApiError> {
|
) -> Result<StatusCode, ApiError> {
|
||||||
if ruuid != *op.signee.payload.room() {
|
if ruuid != op.signee.payload.room {
|
||||||
return Err(error_response!(
|
return Err(error_response!(
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
|
@ -745,24 +745,45 @@ async fn room_admin(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let RoomAdminPayload::AddMember {
|
match op.signee.payload.op {
|
||||||
permission, user, ..
|
RoomAdminOp::AddMember { user, permission } => {
|
||||||
} = op.signee.payload;
|
if user != op.signee.user {
|
||||||
if user != op.signee.user {
|
return Err(error_response!(
|
||||||
return Err(error_response!(
|
StatusCode::NOT_IMPLEMENTED,
|
||||||
StatusCode::NOT_IMPLEMENTED,
|
"not_implemented",
|
||||||
"not_implemented",
|
"only self-adding is implemented yet",
|
||||||
"only self-adding is implemented yet",
|
));
|
||||||
));
|
}
|
||||||
}
|
if permission.is_empty() || !MemberPermission::MAX_SELF_ADD.contains(permission) {
|
||||||
if permission.is_empty() || !MemberPermission::MAX_SELF_ADD.contains(permission) {
|
return Err(error_response!(
|
||||||
return Err(error_response!(
|
StatusCode::BAD_REQUEST,
|
||||||
StatusCode::BAD_REQUEST,
|
"deserialization",
|
||||||
"deserialization",
|
"invalid permission",
|
||||||
"invalid permission",
|
));
|
||||||
));
|
}
|
||||||
|
room_join(&st, ruuid, user, permission).await?;
|
||||||
|
}
|
||||||
|
RoomAdminOp::RemoveMember { user } => {
|
||||||
|
if user != op.signee.user {
|
||||||
|
return Err(error_response!(
|
||||||
|
StatusCode::NOT_IMPLEMENTED,
|
||||||
|
"not_implemented",
|
||||||
|
"only self-removing is implemented yet",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
room_leave(&st, ruuid, user).await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(StatusCode::NO_CONTENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn room_join(
|
||||||
|
st: &AppState,
|
||||||
|
ruuid: Uuid,
|
||||||
|
user: UserKey,
|
||||||
|
permission: MemberPermission,
|
||||||
|
) -> Result<(), ApiError> {
|
||||||
let mut conn = st.conn.lock().unwrap();
|
let mut conn = st.conn.lock().unwrap();
|
||||||
let txn = conn.transaction()?;
|
let txn = conn.transaction()?;
|
||||||
let Some(rid) = txn
|
let Some(rid) = txn
|
||||||
|
@ -811,6 +832,49 @@ async fn room_admin(
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
txn.commit()?;
|
txn.commit()?;
|
||||||
|
Ok(())
|
||||||
Ok(StatusCode::NO_CONTENT)
|
}
|
||||||
|
|
||||||
|
async fn room_leave(st: &AppState, ruuid: Uuid, user: UserKey) -> Result<(), ApiError> {
|
||||||
|
let mut conn = st.conn.lock().unwrap();
|
||||||
|
let txn = conn.transaction()?;
|
||||||
|
|
||||||
|
let Some((rid, uid)) = txn
|
||||||
|
.query_row(
|
||||||
|
r"
|
||||||
|
SELECT `rid`, `uid`
|
||||||
|
FROM `room_member`
|
||||||
|
JOIN `room` USING (`rid`)
|
||||||
|
JOIN `user` USING (`uid`)
|
||||||
|
WHERE `ruuid` = :ruuid AND
|
||||||
|
`userkey` = :userkey
|
||||||
|
",
|
||||||
|
named_params! {
|
||||||
|
":ruuid": ruuid,
|
||||||
|
":userkey": user,
|
||||||
|
},
|
||||||
|
|row| Ok((row.get::<_, u64>("rid")?, row.get::<_, u64>("uid")?)),
|
||||||
|
)
|
||||||
|
.optional()?
|
||||||
|
else {
|
||||||
|
return Err(error_response!(
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
"not_found",
|
||||||
|
"room does not exists or user is not a room member",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
txn.execute(
|
||||||
|
r"
|
||||||
|
DELETE FROM `room_member`
|
||||||
|
WHERE `rid` = :rid AND
|
||||||
|
`uid` = :uid
|
||||||
|
",
|
||||||
|
named_params! {
|
||||||
|
":rid": rid,
|
||||||
|
":uid": uid,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
txn.commit()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@
|
||||||
<label for="join-new-room">join public room:</label>
|
<label for="join-new-room">join public room:</label>
|
||||||
<select id="join-new-room"></select>
|
<select id="join-new-room"></select>
|
||||||
|
|
||||||
|
<button id="leave-room">leave room</select>
|
||||||
<button id="refresh-rooms">refresh room list</select>
|
<button id="refresh-rooms">refresh room list</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -2,6 +2,7 @@ const msgFlow = document.querySelector('#msg-flow');
|
||||||
const userPubkeyDisplay = document.querySelector('#user-pubkey');
|
const userPubkeyDisplay = document.querySelector('#user-pubkey');
|
||||||
const serverUrlInput = document.querySelector('#server-url');
|
const serverUrlInput = document.querySelector('#server-url');
|
||||||
const roomsInput = document.querySelector('#rooms');
|
const roomsInput = document.querySelector('#rooms');
|
||||||
|
const leaveRoomBtn = document.querySelector('#leave-room');
|
||||||
const joinNewRoomInput = document.querySelector('#join-new-room');
|
const joinNewRoomInput = document.querySelector('#join-new-room');
|
||||||
const chatInput = document.querySelector('#chat');
|
const chatInput = document.querySelector('#chat');
|
||||||
const regenKeyBtn = document.querySelector('#regen-key');
|
const regenKeyBtn = document.querySelector('#regen-key');
|
||||||
|
@ -172,6 +173,7 @@ async function genAuthHeader() {
|
||||||
async function enterRoom(ruuid) {
|
async function enterRoom(ruuid) {
|
||||||
log(`loading room: ${ruuid}`);
|
log(`loading room: ${ruuid}`);
|
||||||
curRoom = ruuid;
|
curRoom = ruuid;
|
||||||
|
roomsInput.value = ruuid;
|
||||||
|
|
||||||
genAuthHeader()
|
genAuthHeader()
|
||||||
.then(opts => fetch(`${serverUrl}/room/${ruuid}`, opts))
|
.then(opts => fetch(`${serverUrl}/room/${ruuid}`, opts))
|
||||||
|
@ -218,7 +220,7 @@ async function connectServer(newServerUrl) {
|
||||||
|
|
||||||
log('connecting server');
|
log('connecting server');
|
||||||
wsUrl.protocol = wsUrl.protocol == 'http:' ? 'ws:' : 'wss:';
|
wsUrl.protocol = wsUrl.protocol == 'http:' ? 'ws:' : 'wss:';
|
||||||
wsUrl.pathname += '/ws';
|
wsUrl.pathname += wsUrl.pathname.endsWith('/') ? 'ws' : '/ws';
|
||||||
ws = new WebSocket(wsUrl);
|
ws = new WebSocket(wsUrl);
|
||||||
ws.onopen = async (_) => {
|
ws.onopen = async (_) => {
|
||||||
const auth = await signData({ typ: 'auth' });
|
const auth = await signData({ typ: 'auth' });
|
||||||
|
@ -256,8 +258,14 @@ async function loadRoomList(autoJoin) {
|
||||||
log('loading room list');
|
log('loading room list');
|
||||||
|
|
||||||
async function loadInto(targetEl, filter) {
|
async function loadInto(targetEl, filter) {
|
||||||
|
const emptyEl = document.createElement('option');
|
||||||
|
emptyEl.value = '';
|
||||||
|
emptyEl.innerText = '-';
|
||||||
|
emptyEl.disabled = true;
|
||||||
|
targetEl.replaceChildren(emptyEl);
|
||||||
|
targetEl.value = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
targetEl.replaceChildren();
|
|
||||||
const resp = await fetch(`${serverUrl}/room?filter=${filter}`, await genAuthHeader())
|
const resp = await fetch(`${serverUrl}/room?filter=${filter}`, await genAuthHeader())
|
||||||
const json = await resp.json()
|
const json = await resp.json()
|
||||||
if (resp.status !== 200) throw new Error(`status ${resp.status}: ${json.error.message}`);
|
if (resp.status !== 200) throw new Error(`status ${resp.status}: ${json.error.message}`);
|
||||||
|
@ -274,8 +282,11 @@ async function loadRoomList(autoJoin) {
|
||||||
|
|
||||||
loadInto(roomsInput, 'joined')
|
loadInto(roomsInput, 'joined')
|
||||||
.then(async (_) => {
|
.then(async (_) => {
|
||||||
if (autoJoin && roomsInput.value !== '') {
|
if (autoJoin) {
|
||||||
await enterRoom(roomsInput.value);
|
const el = roomsInput.querySelector('option:nth-child(2)');
|
||||||
|
if (el !== null) {
|
||||||
|
await enterRoom(el.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -303,6 +314,25 @@ async function joinRoom(ruuid) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function leaveRoom() {
|
||||||
|
try {
|
||||||
|
leaveRoomBtn.disabled = true;
|
||||||
|
await signAndPost(`${serverUrl}/room/${curRoom}/admin`, {
|
||||||
|
room: curRoom,
|
||||||
|
typ: 'remove_member',
|
||||||
|
user: await getUserPubkey(),
|
||||||
|
});
|
||||||
|
log('left room');
|
||||||
|
await loadRoomList(true);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
log(`failed to leave room: ${e}`);
|
||||||
|
} finally {
|
||||||
|
leaveRoomBtn.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async function signAndPost(url, data) {
|
async function signAndPost(url, data) {
|
||||||
const signedPayload = await signData(data);
|
const signedPayload = await signData(data);
|
||||||
const resp = await fetch(url, {
|
const resp = await fetch(url, {
|
||||||
|
@ -398,6 +428,9 @@ chatInput.onkeypress = async (e) => {
|
||||||
regenKeyBtn.onclick = async (_) => {
|
regenKeyBtn.onclick = async (_) => {
|
||||||
await generateKeypair();
|
await generateKeypair();
|
||||||
};
|
};
|
||||||
|
leaveRoomBtn.onclick = async (_) => {
|
||||||
|
await leaveRoom();
|
||||||
|
};
|
||||||
roomsInput.onchange = async (_) => {
|
roomsInput.onchange = async (_) => {
|
||||||
await enterRoom(roomsInput.value);
|
await enterRoom(roomsInput.value);
|
||||||
};
|
};
|
||||||
|
|
51
src/types.rs
51
src/types.rs
|
@ -326,23 +326,25 @@ pub struct RoomMember {
|
||||||
#[serde(tag = "typ", rename = "auth")]
|
#[serde(tag = "typ", rename = "auth")]
|
||||||
pub struct AuthPayload {}
|
pub struct AuthPayload {}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(deny_unknown_fields, tag = "typ", rename_all = "snake_case")]
|
#[serde(tag = "typ", rename_all = "snake_case")]
|
||||||
pub enum RoomAdminPayload {
|
pub struct RoomAdminPayload {
|
||||||
AddMember {
|
pub room: Uuid,
|
||||||
permission: MemberPermission,
|
#[serde(flatten)]
|
||||||
room: Uuid,
|
pub op: RoomAdminOp,
|
||||||
user: UserKey,
|
|
||||||
},
|
|
||||||
// TODO: CRUD
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RoomAdminPayload {
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub fn room(&self) -> &Uuid {
|
#[serde(tag = "typ", rename_all = "snake_case")]
|
||||||
match self {
|
pub enum RoomAdminOp {
|
||||||
RoomAdminPayload::AddMember { room, .. } => room,
|
AddMember {
|
||||||
}
|
permission: MemberPermission,
|
||||||
}
|
user: UserKey,
|
||||||
|
},
|
||||||
|
RemoveMember {
|
||||||
|
user: UserKey,
|
||||||
|
},
|
||||||
|
// TODO: RU
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
@ -497,4 +499,23 @@ mod tests {
|
||||||
let got = serde_json::to_string(&text).unwrap();
|
let got = serde_json::to_string(&text).unwrap();
|
||||||
assert_eq!(got, raw);
|
assert_eq!(got, raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn room_admin_serde() {
|
||||||
|
let data = RoomAdminPayload {
|
||||||
|
room: Uuid::nil(),
|
||||||
|
op: RoomAdminOp::AddMember {
|
||||||
|
permission: MemberPermission::POST_CHAT,
|
||||||
|
user: UserKey([0x42; PUBLIC_KEY_LENGTH]),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let raw = serde_jcs::to_string(&data).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
raw,
|
||||||
|
r#"{"permission":1,"room":"00000000-0000-0000-0000-000000000000","typ":"add_member","user":"4242424242424242424242424242424242424242424242424242424242424242"}"#
|
||||||
|
);
|
||||||
|
let got = serde_json::from_str::<RoomAdminPayload>(&raw).unwrap();
|
||||||
|
assert_eq!(got, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue