koprogo_api/application/ports/mandate_repository.rs
1//! Port for persisting [`Mandate`] entities (Story 3.4 — FR7 INV-14).
2//!
3//! All methods return `Result<_, AppError>` natively — no legacy `String`
4//! error debt to migrate later (CRITICAL.md #4 / #555).
5
6use crate::application::error::AppError;
7use crate::domain::entities::{Mandate, MandateScope};
8use async_trait::async_trait;
9use chrono::{DateTime, Utc};
10use uuid::Uuid;
11
12#[async_trait]
13pub trait MandateRepository: Send + Sync {
14 /// Persist a freshly issued mandate.
15 async fn save(&self, mandate: &Mandate) -> Result<(), AppError>;
16
17 /// Look up a mandate by its primary key.
18 async fn find_by_id(&self, id: Uuid) -> Result<Option<Mandate>, AppError>;
19
20 /// List all *currently active* (non-revoked, within validity window)
21 /// mandates whose subject is the given user.
22 async fn list_active_for_subject(
23 &self,
24 subject_user_id: Uuid,
25 ) -> Result<Vec<Mandate>, AppError>;
26
27 /// List all mandates that target the given scope (regardless of status).
28 /// Useful for syndic audit views.
29 async fn list_for_scope(&self, scope: &MandateScope) -> Result<Vec<Mandate>, AppError>;
30
31 /// Atomically revoke a mandate. Implementation MUST do
32 /// `UPDATE ... SET revoked_at = $2 WHERE id = $1 AND revoked_at IS NULL`
33 /// so a double-revoke is a no-op (idempotent).
34 async fn revoke(&self, id: Uuid, revoked_at: DateTime<Utc>) -> Result<(), AppError>;
35}