Rename all crate directories, package names, binary names, proto package/module paths, ALPN strings, env var prefixes, config filenames, mDNS service names, and plugin ABI symbols from quicproquo/qpq to quicprochat/qpc.
163 lines
5.0 KiB
Rust
163 lines
5.0 KiB
Rust
//! Group management handlers — remove member, update metadata, list members, rotate keys.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use bytes::Bytes;
|
|
use prost::Message;
|
|
use quicprochat_proto::qpc::v1;
|
|
use quicprochat_rpc::error::RpcStatus;
|
|
use quicprochat_rpc::method::{HandlerResult, RequestContext};
|
|
|
|
use crate::domain::groups::GroupService;
|
|
use crate::domain::types::{ListGroupMembersReq, UpdateGroupMetadataReq};
|
|
|
|
use super::{domain_err, require_auth, ServerState};
|
|
|
|
/// Handle RemoveMember (410): track member removal server-side.
|
|
///
|
|
/// Note: actual MLS removal (Remove proposal + Commit) is done client-side
|
|
/// via the SDK. This handler records the membership change on the server.
|
|
pub async fn handle_remove_member(
|
|
state: Arc<ServerState>,
|
|
ctx: RequestContext,
|
|
) -> HandlerResult {
|
|
let identity_key = match require_auth(&state, &ctx) {
|
|
Ok(ik) => ik,
|
|
Err(e) => return e,
|
|
};
|
|
|
|
let req = match v1::RemoveMemberRequest::decode(ctx.payload) {
|
|
Ok(r) => r,
|
|
Err(e) => return HandlerResult::err(RpcStatus::BadRequest, &format!("decode: {e}")),
|
|
};
|
|
|
|
if req.group_id.is_empty() || req.member_identity_key.is_empty() {
|
|
return HandlerResult::err(
|
|
RpcStatus::BadRequest,
|
|
"group_id and member_identity_key required",
|
|
);
|
|
}
|
|
|
|
let svc = GroupService {
|
|
store: Arc::clone(&state.store),
|
|
};
|
|
|
|
match svc.remove_member(&req.group_id, &req.member_identity_key) {
|
|
Ok(_) => {
|
|
let _ = identity_key; // caller is authorized; removal tracked
|
|
let proto = v1::RemoveMemberResponse {
|
|
commit: Vec::new(), // commit is generated client-side
|
|
};
|
|
HandlerResult::ok(Bytes::from(proto.encode_to_vec()))
|
|
}
|
|
Err(e) => domain_err(e),
|
|
}
|
|
}
|
|
|
|
/// Handle UpdateGroupMetadata (411): store group name, description, avatar.
|
|
pub async fn handle_update_group_metadata(
|
|
state: Arc<ServerState>,
|
|
ctx: RequestContext,
|
|
) -> HandlerResult {
|
|
let identity_key = match require_auth(&state, &ctx) {
|
|
Ok(ik) => ik,
|
|
Err(e) => return e,
|
|
};
|
|
|
|
let req = match v1::UpdateGroupMetadataRequest::decode(ctx.payload) {
|
|
Ok(r) => r,
|
|
Err(e) => return HandlerResult::err(RpcStatus::BadRequest, &format!("decode: {e}")),
|
|
};
|
|
|
|
let svc = GroupService {
|
|
store: Arc::clone(&state.store),
|
|
};
|
|
|
|
let domain_req = UpdateGroupMetadataReq {
|
|
group_id: req.group_id,
|
|
name: req.name,
|
|
description: req.description,
|
|
avatar_hash: req.avatar_hash,
|
|
};
|
|
|
|
match svc.update_metadata(domain_req, &identity_key) {
|
|
Ok(()) => {
|
|
let proto = v1::UpdateGroupMetadataResponse { success: true };
|
|
HandlerResult::ok(Bytes::from(proto.encode_to_vec()))
|
|
}
|
|
Err(e) => domain_err(e),
|
|
}
|
|
}
|
|
|
|
/// Handle ListGroupMembers (412): return member list with resolved usernames.
|
|
pub async fn handle_list_group_members(
|
|
state: Arc<ServerState>,
|
|
ctx: RequestContext,
|
|
) -> HandlerResult {
|
|
let _identity_key = match require_auth(&state, &ctx) {
|
|
Ok(ik) => ik,
|
|
Err(e) => return e,
|
|
};
|
|
|
|
let req = match v1::ListGroupMembersRequest::decode(ctx.payload) {
|
|
Ok(r) => r,
|
|
Err(e) => return HandlerResult::err(RpcStatus::BadRequest, &format!("decode: {e}")),
|
|
};
|
|
|
|
let svc = GroupService {
|
|
store: Arc::clone(&state.store),
|
|
};
|
|
|
|
let domain_req = ListGroupMembersReq {
|
|
group_id: req.group_id,
|
|
};
|
|
|
|
match svc.list_members(domain_req) {
|
|
Ok(resp) => {
|
|
let proto = v1::ListGroupMembersResponse {
|
|
members: resp
|
|
.members
|
|
.into_iter()
|
|
.map(|m| v1::GroupMemberInfo {
|
|
identity_key: m.identity_key,
|
|
username: m.username,
|
|
joined_at: m.joined_at,
|
|
})
|
|
.collect(),
|
|
};
|
|
HandlerResult::ok(Bytes::from(proto.encode_to_vec()))
|
|
}
|
|
Err(e) => domain_err(e),
|
|
}
|
|
}
|
|
|
|
/// Handle RotateKeys (413): acknowledge key rotation.
|
|
///
|
|
/// Actual MLS key rotation (Update proposal + Commit) is done client-side.
|
|
/// This handler exists for server-side tracking and future rate limiting.
|
|
pub async fn handle_rotate_keys(
|
|
state: Arc<ServerState>,
|
|
ctx: RequestContext,
|
|
) -> HandlerResult {
|
|
let _identity_key = match require_auth(&state, &ctx) {
|
|
Ok(ik) => ik,
|
|
Err(e) => return e,
|
|
};
|
|
|
|
let req = match v1::RotateKeysRequest::decode(ctx.payload) {
|
|
Ok(r) => r,
|
|
Err(e) => return HandlerResult::err(RpcStatus::BadRequest, &format!("decode: {e}")),
|
|
};
|
|
|
|
if req.group_id.is_empty() {
|
|
return HandlerResult::err(RpcStatus::BadRequest, "group_id required");
|
|
}
|
|
|
|
// Key rotation is handled entirely client-side in MLS.
|
|
// This endpoint is for server-side auditing and future rate limiting.
|
|
let proto = v1::RotateKeysResponse {
|
|
commit: Vec::new(), // commit is generated client-side
|
|
};
|
|
HandlerResult::ok(Bytes::from(proto.encode_to_vec()))
|
|
}
|