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::{InviteResponse, InviteWithTokenResponse};
use crate::repositories::{
default_invite_expiry, generate_invite_token, hash_invite_token, InviteEntity, OrgRole,
};
use crate::services::EmailService;
use crate::utils::authenticate;
use crate::AppState;
pub async fn resend_invite<C: AuthCallback, E: EmailService>(
State(state): State<Arc<AppState<C, E>>>,
headers: HeaderMap,
Path((org_id, invite_id)): Path<(Uuid, Uuid)>,
) -> Result<Json<InviteWithTokenResponse>, AppError> {
let auth = authenticate(&state, &headers).await?;
let caller_membership = state
.membership_repo
.find_by_user_and_org(auth.user_id, org_id)
.await?
.ok_or(AppError::Forbidden(
"Not a member of this organization".into(),
))?;
if !caller_membership.role.has_at_least(OrgRole::Admin) {
return Err(AppError::Forbidden(
"Only owners and admins can resend invites".into(),
));
}
let old_invite = state
.invite_repo
.find_by_id(invite_id)
.await?
.ok_or(AppError::NotFound("Invite not found".into()))?;
if old_invite.org_id != org_id {
return Err(AppError::NotFound("Invite not found".into()));
}
if old_invite.accepted_at.is_some() {
return Err(AppError::Validation(
"Invite has already been accepted".into(),
));
}
state.invite_repo.delete(invite_id).await?;
let invite_token = generate_invite_token();
let token_hash = hash_invite_token(&invite_token);
let new_invite = InviteEntity {
id: Uuid::new_v4(),
org_id,
email: old_invite.email.clone(),
wallet_address: old_invite.wallet_address.clone(),
role: old_invite.role,
token_hash,
invited_by: auth.user_id, created_at: chrono::Utc::now(),
expires_at: default_invite_expiry(),
accepted_at: None,
};
let created = state.invite_repo.create(new_invite).await?;
if let Some(ref email) = created.email {
let org = state
.org_repo
.find_by_id(org_id)
.await?
.ok_or(AppError::NotFound("Organization not found".into()))?;
let inviter = state.user_repo.find_by_id(auth.user_id).await?;
let inviter_name = inviter.and_then(|u| u.name);
state
.comms_service
.queue_invite_email(
email,
&org.name,
inviter_name.as_deref(),
created.role.as_str(),
&invite_token,
org_id,
auth.user_id,
)
.await?;
}
Ok(Json(InviteWithTokenResponse {
invite: InviteResponse::from_entity(&created),
token: invite_token,
}))
}