use axum::{
extract::{Path, State},
http::HeaderMap,
Json,
};
use std::sync::Arc;
use uuid::Uuid;
use crate::callback::AuthCallback;
use crate::errors::AppError;
use crate::models::{MemberResponse, UpdateMemberRoleRequest};
use crate::repositories::OrgRole;
use crate::services::EmailService;
use crate::utils::authenticate;
use crate::AppState;
pub async fn update_member_role<C: AuthCallback, E: EmailService>(
State(state): State<Arc<AppState<C, E>>>,
headers: HeaderMap,
Path((org_id, user_id)): Path<(Uuid, Uuid)>,
Json(req): Json<UpdateMemberRoleRequest>,
) -> Result<Json<MemberResponse>, AppError> {
let auth = authenticate(&state, &headers).await?;
if user_id == auth.user_id {
return Err(AppError::Forbidden("Cannot change your own role".into()));
}
let new_role =
OrgRole::from_str(&req.role).ok_or_else(|| AppError::Validation("Invalid role".into()))?;
let (caller_result, target_result) = tokio::join!(
state
.membership_repo
.find_by_user_and_org(auth.user_id, org_id),
state.membership_repo.find_by_user_and_org(user_id, org_id)
);
let caller_membership = caller_result?.ok_or(AppError::Forbidden(
"Not a member of this organization".into(),
))?;
let target_membership = target_result?.ok_or(AppError::NotFound("Member not found".into()))?;
if !caller_membership.role.has_at_least(OrgRole::Admin) {
return Err(AppError::Forbidden(
"Only owners and admins can change member roles".into(),
));
}
if target_membership.role == OrgRole::Owner && caller_membership.role != OrgRole::Owner {
return Err(AppError::Forbidden(
"Only owners can change the role of other owners".into(),
));
}
if new_role == OrgRole::Owner && caller_membership.role != OrgRole::Owner {
return Err(AppError::Forbidden(
"Only owners can promote members to owner".into(),
));
}
let old_role = target_membership.role;
let updated = state
.membership_repo
.update_role_if_not_last_owner(target_membership.id, org_id, new_role)
.await?
.ok_or_else(|| {
AppError::Validation(
"Cannot demote owner - organization must have at least one owner".into(),
)
})?;
let org = state.org_repo.find_by_id(org_id).await?;
let org_slug = org.map(|o| o.slug).unwrap_or_default();
if new_role == OrgRole::Owner || old_role == OrgRole::Owner {
let _ = state
.comms_service
.notify_owner_transfer(org_id, &org_slug, auth.user_id, user_id)
.await;
} else if old_role != new_role {
let _ = state
.comms_service
.notify_role_change(
org_id,
&org_slug,
user_id,
auth.user_id,
old_role.as_str(),
new_role.as_str(),
)
.await;
}
let (email, name) = if let Some(user) = state.user_repo.find_by_id(user_id).await? {
(user.email, user.name)
} else {
(None, None)
};
Ok(Json(MemberResponse::from_membership(&updated, email, name)))
}