use axum::{extract::State, http::HeaderMap, Json};
use std::sync::Arc;
use crate::callback::AuthCallback;
use crate::errors::AppError;
use crate::models::{AcceptInviteRequest, AcceptInviteResponse};
use crate::repositories::{hash_invite_token, TransactionalOps};
use crate::services::EmailService;
use crate::utils::authenticate;
use crate::AppState;
pub async fn accept_invite<C: AuthCallback, E: EmailService>(
State(state): State<Arc<AppState<C, E>>>,
headers: HeaderMap,
Json(req): Json<AcceptInviteRequest>,
) -> Result<Json<AcceptInviteResponse>, AppError> {
let auth = authenticate(&state, &headers).await?;
let user = state
.user_repo
.find_by_id(auth.user_id)
.await?
.ok_or(AppError::InvalidToken)?;
let token_hash = hash_invite_token(&req.token);
let invite = state
.invite_repo
.find_by_token_hash(&token_hash)
.await?
.ok_or(AppError::NotFound("Invalid or expired invite".into()))?;
if let Some(ref invite_email) = invite.email {
let user_email = user.email.as_ref().ok_or(AppError::Validation(
"You must have an email to accept this invite".into(),
))?;
if user_email.to_lowercase() != invite_email.to_lowercase() {
return Err(AppError::Forbidden(
"This invite was sent to a different email address".into(),
));
}
} else if let Some(ref invite_wallet) = invite.wallet_address {
let user_wallet = user.wallet_address.as_ref().ok_or(AppError::Validation(
"You must have a wallet to accept this invite".into(),
))?;
if user_wallet != invite_wallet {
return Err(AppError::Forbidden(
"This invite was sent to a different wallet address".into(),
));
}
} else {
return Err(AppError::Internal(anyhow::anyhow!(
"Invalid invite: missing recipient identifier"
)));
}
if state
.membership_repo
.find_by_user_and_org(auth.user_id, invite.org_id)
.await?
.is_some()
{
return Err(AppError::Validation(
"You are already a member of this organization".into(),
));
}
let pool = state
.postgres_pool
.as_ref()
.ok_or_else(|| AppError::Internal(anyhow::anyhow!("PostgreSQL pool not available")))?;
let membership = TransactionalOps::accept_invite_atomic(
pool,
invite.id,
auth.user_id,
invite.org_id,
invite.role,
)
.await?;
let org = state
.org_repo
.find_by_id(invite.org_id)
.await?
.ok_or(AppError::NotFound("Organization not found".into()))?;
Ok(Json(AcceptInviteResponse {
org_id: org.id,
org_name: org.name,
role: membership.role.as_str().to_string(),
}))
}