diff options
Diffstat (limited to 'src/push/app.rs')
| -rw-r--r-- | src/push/app.rs | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/src/push/app.rs b/src/push/app.rs new file mode 100644 index 0000000..2d6e15c --- /dev/null +++ b/src/push/app.rs @@ -0,0 +1,138 @@ +use sqlx::SqlitePool; +use web_push::{ + ContentEncoding, IsahcWebPushClient, PartialVapidSignatureBuilder, SubscriptionInfo, + WebPushClient, WebPushMessageBuilder, +}; + +use super::{Id, repo::Provider as _}; + +use crate::{ + db::NotFound as _, + user::{self, User}, +}; + +pub struct Push<'a> { + db: &'a SqlitePool, + vapid_public_key: &'a str, + vapid_signer: &'a PartialVapidSignatureBuilder, +} + +impl<'a> Push<'a> { + pub const fn new( + db: &'a SqlitePool, + vapid_public_key: &'a str, + vapid_signer: &'a PartialVapidSignatureBuilder, + ) -> Self { + Self { + db, + vapid_public_key, + vapid_signer, + } + } + + pub fn public_key(&self) -> &str { + self.vapid_public_key + } + + pub async fn register( + &self, + user: &User, + subscription: &SubscriptionInfo, + ) -> Result<Id, RegisterError> { + let mut tx = self.db.begin().await?; + let id = tx.subscriptions().create(user, subscription).await?; + tx.commit().await?; + + Ok(id) + } + + pub async fn echo( + &self, + user: &User, + subscription: &Id, + message: &str, + ) -> Result<(), EchoError> { + let mut tx = self.db.begin().await?; + let subscription = tx + .subscriptions() + .by_id(subscription) + .await + .not_found(|| EchoError::NotFound(subscription.clone()))?; + if subscription.user != user.id { + return Err(EchoError::NotSubscriber(subscription.id, user.id.clone())); + } + + tx.commit().await?; + + self.send(&subscription.info, message).await?; + + Ok(()) + } + + async fn send(&self, subscription: &SubscriptionInfo, message: &str) -> Result<(), EchoError> { + let sig_builder = self + .vapid_signer + .clone() + .add_sub_info(subscription) + .build()?; + + let payload = message.as_bytes(); + + let mut message_builder = WebPushMessageBuilder::new(subscription); + message_builder.set_payload(ContentEncoding::Aes128Gcm, payload); + message_builder.set_vapid_signature(sig_builder); + let message = message_builder.build()?; + + let client = IsahcWebPushClient::new()?; + client.send(message).await?; + + Ok(()) + } + + pub async fn unregister(&self, user: &User, subscription: &Id) -> Result<(), UnregisterError> { + let mut tx = self.db.begin().await?; + let subscription = tx + .subscriptions() + .by_id(subscription) + .await + .not_found(|| UnregisterError::NotFound(subscription.clone()))?; + if subscription.user != user.id { + return Err(UnregisterError::NotSubscriber( + subscription.id, + user.id.clone(), + )); + } + tx.subscriptions().delete(&subscription).await?; + tx.commit().await?; + + Ok(()) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum RegisterError { + #[error(transparent)] + Database(#[from] sqlx::Error), +} + +#[derive(Debug, thiserror::Error)] +pub enum EchoError { + #[error("subscription {0} not found")] + NotFound(Id), + #[error("user {1} is not the subscriber for subscription {0}")] + NotSubscriber(Id, user::Id), + #[error(transparent)] + WebPush(#[from] web_push::WebPushError), + #[error(transparent)] + Database(#[from] sqlx::Error), +} + +#[derive(Debug, thiserror::Error)] +pub enum UnregisterError { + #[error("subscription {0} not found")] + NotFound(Id), + #[error("user {1} is not the subscriber for subscription {0}")] + NotSubscriber(Id, user::Id), + #[error(transparent)] + Database(#[from] sqlx::Error), +} |
