Skip to main content

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}