//! Push notifications (SPEC §14.6). M2a defined the trait; M4 owns `src/push/**` and implements it with the bounded //! queue [`PushHub`] drained by the FCM [`Sender`] (started by `tasks::start`). [`NoopPushQueue`] remains for push //! disabled (no credentials file). //! //! [`PushQueue::enqueue`] never blocks and never fails the caller: sending happens on a background task and never //! delays an HTTP response (SPEC §14.6). Call it after the write transaction has committed. use uuid::Uuid; pub mod fcm; pub mod queue; pub mod sender; pub use fcm::{Endpoints, FcmClient}; pub use queue::PushHub; pub use sender::{RetryPolicy, Sender}; /// The notification types of SPEC §14.6 (exhaustive). #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum PushType { ExpenseCreated, ExpenseUpdated, ExpenseDeleted, SettlementCreated, SettlementUpdated, SettlementDeleted, RecurringExpenseCreated, SyncBatch, } impl PushType { /// The `type` value of the data message. pub fn as_str(self) -> &'static str { match self { Self::ExpenseCreated => "expense_created", Self::ExpenseUpdated => "expense_updated", Self::ExpenseDeleted => "expense_deleted", Self::SettlementCreated => "settlement_created", Self::SettlementUpdated => "settlement_updated", Self::SettlementDeleted => "settlement_deleted", Self::RecurringExpenseCreated => "recurring_expense_created", Self::SyncBatch => "sync_batch", } } } /// One notification to send. Ids only, never personal content (SPEC §8.7). #[derive(Debug, Clone, PartialEq, Eq)] pub struct PushMessage { pub push_type: PushType, /// `None` for `sync_batch` (sent as `""` with `entity_type` `""`). pub entity_id: Option, /// The creator, editor, deleter, template creator or pusher. pub actor_user_id: Uuid, /// 1, or the number of notifiable changes for `sync_batch`. pub count: u32, } /// Queues notifications for the background sender, which resolves the target devices per SPEC §14.6. pub trait PushQueue: Send + Sync + 'static { fn enqueue(&self, message: PushMessage); } /// Drops every message: push disabled (no FCM credentials). #[derive(Debug, Clone, Copy, Default)] pub struct NoopPushQueue; impl PushQueue for NoopPushQueue { fn enqueue(&self, message: PushMessage) { tracing::debug!( push_type = message.push_type.as_str(), "push disabled, message dropped" ); } }