use serde::{Deserialize, Serialize};
use uuid::Uuid;
use vti_common::error::AppError;
use vti_common::pagination::{Cursor, MAX_LIMIT, Paginated};
use crate::error::TaskError;
use crate::join::{JoinRequest, JoinStatus, get_join_request, list_join_requests_filtered};
use crate::server::AppState;
pub const SHOW_ERR_NOT_FOUND: &str =
trust_tasks_rs::specs::vtc::join_requests::show::v0_1::error_codes::NOT_FOUND.code;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[derive(utoipa::ToSchema, utoipa::IntoParams)]
pub struct ListJoinRequestsQuery {
pub status: Option<JoinStatus>,
pub cursor: Option<String>,
pub limit: Option<usize>,
}
pub(crate) async fn list_join_requests_inner(
state: &AppState,
query: ListJoinRequestsQuery,
) -> Result<Paginated<JoinRequest>, AppError> {
let limit = query.limit.unwrap_or(50).clamp(1, MAX_LIMIT);
let audit_writer = state
.audit_writer
.as_ref()
.ok_or_else(|| AppError::Internal("audit_writer not initialised".into()))?;
let audit_key = audit_writer.active_key().await?;
let decoded_cursor = match &query.cursor {
Some(s) => Some(Cursor::decode(s, &audit_key.key)?),
None => None,
};
let filter_status = query.status.unwrap_or(JoinStatus::Pending);
list_join_requests_filtered(
&state.join_requests_ks,
&audit_key,
decoded_cursor.as_ref(),
limit,
|r| r.status == filter_status,
)
.await
}
pub(crate) async fn show_join_request(
state: &AppState,
id: Uuid,
) -> Result<JoinRequestEnvelope, TaskError> {
let req = get_join_request(&state.join_requests_ks, id)
.await?
.ok_or_else(|| {
TaskError::declared(
SHOW_ERR_NOT_FOUND,
AppError::NotFound(format!("join request not found: {id}")),
)
})?;
Ok(JoinRequestEnvelope { request: req })
}
pub(crate) async fn vetting_of(
state: &AppState,
id: Uuid,
) -> Result<JoinRequestVettingResponse, AppError> {
get_join_request(&state.join_requests_ks, id)
.await?
.ok_or_else(|| AppError::NotFound(format!("join request not found: {id}")))?;
let Some(stored) = crate::join::get_vetting_facts(&state.join_requests_ks, id).await? else {
return Ok(JoinRequestVettingResponse {
request_id: id,
vetting: None,
});
};
let withdrawn: std::collections::HashSet<(String, String)> =
crate::vetting::revocation::list_notices(&state.vetting_revocations_ks)
.await?
.into_iter()
.map(|n| (n.issuer, n.statement_id))
.collect();
let facts = stored.facts;
Ok(JoinRequestVettingResponse {
request_id: id,
vetting: Some(JoinRequestVetting {
criterion_id: facts.criterion_id,
requirements_digest: facts.requirements_digest,
applicant_digest_matches: facts.applicant_digest_matches,
statements: facts
.statements
.into_iter()
.map(|s| JoinRequestVettingStatement {
withdrawn_now: match (&s.issuer, &s.id) {
(Some(issuer), Some(sid)) => {
withdrawn.contains(&(issuer.clone(), sid.clone()))
}
_ => false,
},
id: s.id,
issuer: s.issuer,
verified: s.verified,
eligible: s.eligible,
revoked: s.revoked,
method: s.method,
declared_relationship: s.declared_relationship,
counted: s.counted,
failures: s.failures,
})
.collect(),
distinct_counted_vetters: facts.distinct_counted_vetters,
by_method: facts.by_method,
commitments_consistent: facts.commitments_consistent,
independence_ok: facts.independence_ok,
invitation_required: facts.invitation_required,
satisfied: facts.satisfied,
needs: facts.needs,
recorded_at: stored.recorded_at,
}),
})
}
#[derive(Debug, Serialize, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct JoinRequestVettingResponse {
pub request_id: Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
pub vetting: Option<JoinRequestVetting>,
}
#[derive(Debug, Serialize, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct JoinRequestVetting {
pub criterion_id: String,
pub requirements_digest: String,
pub applicant_digest_matches: bool,
pub statements: Vec<JoinRequestVettingStatement>,
pub distinct_counted_vetters: u32,
pub by_method: std::collections::BTreeMap<String, u32>,
pub commitments_consistent: bool,
pub independence_ok: bool,
pub invitation_required: bool,
pub satisfied: bool,
pub needs: Vec<String>,
pub recorded_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Serialize, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct JoinRequestVettingStatement {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub issuer: Option<String>,
pub verified: bool,
pub eligible: bool,
pub revoked: bool,
pub withdrawn_now: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub declared_relationship: Option<String>,
pub counted: bool,
pub failures: Vec<String>,
}
#[derive(Debug, Serialize, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct JoinRequestEnvelope {
pub request: JoinRequest,
}