Skip to main content

koprogo_api/application/ports/
portfolio_repository.rs

1//! Port (trait) pour le repository Portfolio — Story 2.1.
2//!
3//! Hexagonal : trait côté application, implémentation PostgreSQL dans
4//! `infrastructure/database/repositories/portfolio_repository_impl.rs`.
5//!
6//! Toutes les méthodes retournent `Result<_, AppError>` (CRITICAL.md §4 —
7//! pas de `Result<_, String>` pour les NEW use-cases).
8//!
9//! Source : `docs/maury/refonte-ux-multi-role-acp/architecture.md` §3.1.
10
11use crate::application::error::AppError;
12use crate::domain::entities::{Portfolio, PortfolioBuilding, PortfolioShare};
13use async_trait::async_trait;
14use uuid::Uuid;
15
16/// Entrée de listing : un building du portfolio + flag favori + ordre
17/// déterministe (favoris d'abord, puis `added_at DESC`).
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct PortfolioBuildingEntry {
20    pub portfolio_id: Uuid,
21    pub building_id: Uuid,
22    pub is_favorite: bool,
23}
24
25/// Port repository Portfolio.
26#[async_trait]
27pub trait PortfolioRepository: Send + Sync {
28    /// Persiste un nouveau portfolio. Retourne l'entité telle que stockée.
29    async fn create(&self, portfolio: &Portfolio) -> Result<Portfolio, AppError>;
30
31    /// Récupère par id. `None` si absent (pas une erreur).
32    async fn find_by_id(&self, id: Uuid) -> Result<Option<Portfolio>, AppError>;
33
34    /// Liste les portfolios dont `user_id` est `owner_user_id` ou
35    /// figure dans `portfolio_shares`. Tri : `created_at DESC`.
36    async fn list_for_user(&self, user_id: Uuid) -> Result<Vec<Portfolio>, AppError>;
37
38    /// Met à jour un portfolio existant. Retourne `AppError::NotFound`
39    /// si aucune ligne affectée.
40    async fn update(&self, portfolio: &Portfolio) -> Result<Portfolio, AppError>;
41
42    /// Supprime un portfolio (DELETE physique — cascade sur
43    /// `portfolio_buildings` et `portfolio_shares`).
44    async fn delete(&self, id: Uuid) -> Result<(), AppError>;
45
46    /// Ajoute (ou remplace) un building dans le portfolio.
47    /// Idempotent : un `ON CONFLICT (portfolio_id, building_id) DO UPDATE`
48    /// rafraîchit `is_favorite` si la ligne existe déjà.
49    async fn add_building(
50        &self,
51        portfolio_id: Uuid,
52        building_id: Uuid,
53        is_favorite: bool,
54    ) -> Result<PortfolioBuilding, AppError>;
55
56    /// Retire un building du portfolio. `AppError::NotFound` si aucune
57    /// ligne supprimée.
58    async fn remove_building(&self, portfolio_id: Uuid, building_id: Uuid) -> Result<(), AppError>;
59
60    /// Liste les buildings d'un portfolio.
61    /// **Tri stable** : favoris d'abord (`is_favorite DESC`) puis
62    /// `added_at DESC` (cf. AC @happy Story 2.1).
63    async fn list_buildings(
64        &self,
65        portfolio_id: Uuid,
66    ) -> Result<Vec<PortfolioBuildingEntry>, AppError>;
67
68    /// Partage le portfolio avec un autre user.
69    /// Idempotent : `ON CONFLICT (portfolio_id, shared_with_user_id) DO UPDATE`
70    /// rafraîchit `can_edit`.
71    async fn share_with(
72        &self,
73        portfolio_id: Uuid,
74        shared_with_user_id: Uuid,
75        can_edit: bool,
76    ) -> Result<PortfolioShare, AppError>;
77
78    /// Retire un partage. `AppError::NotFound` si aucune ligne supprimée.
79    async fn unshare(&self, portfolio_id: Uuid, shared_with_user_id: Uuid) -> Result<(), AppError>;
80
81    /// Liste les partages d'un portfolio.
82    async fn list_shares(&self, portfolio_id: Uuid) -> Result<Vec<PortfolioShare>, AppError>;
83}