use vti_common::error::AppError;
use vti_rooms::mls::RoomGroup;
use crate::auth::AuthClaims;
use crate::operations::room_groups::{self, Member, RoomGroupRecord};
use crate::server::AppState;
async fn may_act_as(state: &AppState, auth: &AuthClaims, did: &str) -> Result<bool, AppError> {
let stores = crate::operations::persona::PersonaStores::from_state(state);
Ok(
crate::operations::persona::persona_signing_key(stores, auth, did)
.await?
.is_some(),
)
}
async fn attribute_legacy(
state: &AppState,
auth: &AuthClaims,
room_id: &str,
record: &RoomGroupRecord,
) -> Result<Option<String>, AppError> {
let leaf_is_member = RoomGroup::restore(&record.snapshot)
.map(|g| g.leaf_of(&record.member_did).is_some())
.unwrap_or(false);
if leaf_is_member && may_act_as(state, auth, &record.member_did).await? {
return Ok(Some(record.member_did.clone()));
}
if record.member_did == auth.did {
return match crate::operations::room_oracle::principal(state, auth, room_id).await {
Ok(p) => Ok(Some(p.did)),
Err(e @ AppError::Internal(_)) => Err(e),
Err(_) => Ok(None),
};
}
Ok(None)
}
pub async fn migrate_for(
state: &AppState,
auth: &AuthClaims,
room_id: &str,
) -> Result<(), AppError> {
let groups = &state.room_groups_ks;
let Some(record) = room_groups::legacy_group(groups, room_id).await? else {
return Ok(());
};
let Some(persona) = attribute_legacy(state, auth, room_id, &record).await? else {
return Ok(());
};
room_groups::migrate_legacy(groups, &Member::new(room_id, &persona)?).await?;
Ok(())
}
pub async fn member_as(
state: &AppState,
auth: &AuthClaims,
room_id: &str,
persona: &str,
) -> Result<Member, AppError> {
migrate_for(state, auth, room_id).await?;
Member::new(room_id, persona)
}
pub async fn acting(
state: &AppState,
auth: &AuthClaims,
room_id: &str,
) -> Result<Option<Member>, AppError> {
migrate_for(state, auth, room_id).await?;
let mut mine = Vec::new();
for m in room_groups::holders(&state.room_groups_ks, room_id).await? {
if may_act_as(state, auth, m.persona()).await? {
mine.push(m);
}
}
match mine.len() {
0 => Ok(None),
1 => Ok(mine.pop()),
n => Err(AppError::Conflict(format!(
"you may act as {n} personas holding group state for room `{room_id}`; which one \
is acting is not a question this can answer safely — act from the context of \
the one you mean"
))),
}
}
pub async fn require_acting(
state: &AppState,
auth: &AuthClaims,
room_id: &str,
) -> Result<Member, AppError> {
acting(state, auth, room_id).await?.ok_or_else(|| {
AppError::NotFound(format!(
"this VTA holds no group state for room `{room_id}`"
))
})
}
pub async fn held_by_caller(state: &AppState, auth: &AuthClaims) -> Result<Vec<Member>, AppError> {
let groups = &state.room_groups_ks;
for room_id in room_groups::legacy_rooms(groups).await? {
migrate_for(state, auth, &room_id).await?;
}
let mut mine = Vec::new();
for m in room_groups::held_members(groups).await? {
if may_act_as(state, auth, m.persona()).await? {
mine.push(m);
}
}
Ok(mine)
}