use serde::Serialize;
use sqlx::FromRow;
use crate::error::{Error, Result};
use crate::writer::rfc3339_from_epoch_ms;
pub(super) async fn subject_has_pending_actions(
pool: &sqlx::Pool<sqlx::Sqlite>,
subject_did: &str,
) -> Result<bool> {
let row = sqlx::query!(
r#"SELECT EXISTS(SELECT 1 FROM pending_policy_actions WHERE subject_did = ?1) AS "exists!: i64""#,
subject_did,
)
.fetch_one(pool)
.await?;
Ok(row.exists != 0)
}
#[derive(Debug, FromRow)]
pub(super) struct PendingActionRow {
pub id: i64,
pub subject_did: String,
pub subject_uri: Option<String>,
pub action_type: String,
pub duration_ms: Option<i64>,
pub reason_codes: String,
pub triggered_by_policy_rule: String,
pub triggered_at: i64,
pub triggering_action_id: i64,
pub resolution: Option<String>,
pub resolved_at: Option<i64>,
pub resolved_by_did: Option<String>,
pub confirmed_action_id: Option<i64>,
}
#[derive(Debug, Serialize)]
pub(super) struct PendingActionEntry {
pub id: i64,
#[serde(rename = "subjectDid")]
pub subject_did: String,
#[serde(rename = "subjectUri", skip_serializing_if = "Option::is_none")]
pub subject_uri: Option<String>,
#[serde(rename = "actionType")]
pub action_type: String,
#[serde(rename = "durationIso", skip_serializing_if = "Option::is_none")]
pub duration_iso: Option<String>,
#[serde(rename = "reasonCodes")]
pub reason_codes: Vec<String>,
#[serde(rename = "triggeredByPolicyRule")]
pub triggered_by_policy_rule: String,
#[serde(rename = "triggeredAt")]
pub triggered_at: String,
#[serde(rename = "triggeringActionId")]
pub triggering_action_id: i64,
pub resolution: String,
#[serde(rename = "resolvedAt", skip_serializing_if = "Option::is_none")]
pub resolved_at: Option<String>,
#[serde(rename = "resolvedByDid", skip_serializing_if = "Option::is_none")]
pub resolved_by_did: Option<String>,
#[serde(rename = "confirmedActionId", skip_serializing_if = "Option::is_none")]
pub confirmed_action_id: Option<i64>,
}
pub(super) fn project(row: PendingActionRow) -> Result<PendingActionEntry> {
let reason_codes: Vec<String> = serde_json::from_str(&row.reason_codes).map_err(|e| {
Error::Signing(format!(
"pending_policy_actions row {} has malformed reason_codes JSON: {e}",
row.id
))
})?;
let duration_iso = row.duration_ms.map(|ms| format!("PT{}S", ms / 1000));
let resolution = match row.resolution.as_deref() {
None => "pending".to_string(),
Some("confirmed") => "confirmed".to_string(),
Some("dismissed") => "dismissed".to_string(),
Some(other) => {
return Err(Error::Signing(format!(
"pending_policy_actions row {} has invalid resolution {:?}",
row.id, other
)));
}
};
Ok(PendingActionEntry {
id: row.id,
subject_did: row.subject_did,
subject_uri: row.subject_uri,
action_type: row.action_type,
duration_iso,
reason_codes,
triggered_by_policy_rule: row.triggered_by_policy_rule,
triggered_at: rfc3339_from_epoch_ms(row.triggered_at)?,
triggering_action_id: row.triggering_action_id,
resolution,
resolved_at: row.resolved_at.map(rfc3339_from_epoch_ms).transpose()?,
resolved_by_did: row.resolved_by_did,
confirmed_action_id: row.confirmed_action_id,
})
}