Skip to main content

hypercall_db_diesel/
push.rs

1//! PushSubscriptionReader + PushSubscriptionWriter trait implementations for DieselDb.
2//!
3//! Manages Web Push subscription CRUD (endpoints, auth keys, preferences).
4
5use anyhow::Result;
6use async_trait::async_trait;
7use diesel::prelude::*;
8use diesel_async::RunQueryDsl;
9
10use crate::diesel_db::DieselDb;
11use crate::models::{NewPushSubscription, PushSubscription};
12use crate::schema::push_subscriptions;
13use hypercall_db::{
14    PushSubscriptionReader, PushSubscriptionRecord, PushSubscriptionWriter,
15    UpsertPushSubscriptionInput,
16};
17
18// -- Conversions from Diesel model to domain type --
19
20impl From<PushSubscription> for PushSubscriptionRecord {
21    fn from(row: PushSubscription) -> Self {
22        Self {
23            id: row.id,
24            wallet_address: row.wallet_address,
25            endpoint: row.endpoint,
26            auth_key: row.auth_key,
27            p256dh_key: row.p256dh_key,
28            preferences: row.preferences,
29            created_at: row.created_at,
30        }
31    }
32}
33
34// =============================================================================
35// PushSubscriptionReader
36// =============================================================================
37
38#[async_trait]
39impl PushSubscriptionReader for DieselDb {
40    async fn get_push_subscriptions(&self, wallet: &str) -> Result<Vec<PushSubscriptionRecord>> {
41        let mut conn = self.get_conn().await?;
42        let wallet_lower = wallet.to_lowercase();
43
44        let rows = push_subscriptions::table
45            .filter(push_subscriptions::wallet_address.eq(&wallet_lower))
46            .load::<PushSubscription>(&mut conn)
47            .await?;
48
49        Ok(rows.into_iter().map(Into::into).collect())
50    }
51
52    async fn count_push_subscriptions(&self, wallet: &str) -> Result<i64> {
53        let mut conn = self.get_conn().await?;
54        let wallet_lower = wallet.to_lowercase();
55
56        let count = push_subscriptions::table
57            .filter(push_subscriptions::wallet_address.eq(&wallet_lower))
58            .count()
59            .get_result::<i64>(&mut conn)
60            .await?;
61
62        Ok(count)
63    }
64
65    async fn push_subscription_exists(&self, wallet: &str, endpoint: &str) -> Result<bool> {
66        let mut conn = self.get_conn().await?;
67        let wallet_lower = wallet.to_lowercase();
68
69        let count = push_subscriptions::table
70            .filter(push_subscriptions::wallet_address.eq(&wallet_lower))
71            .filter(push_subscriptions::endpoint.eq(endpoint))
72            .count()
73            .get_result::<i64>(&mut conn)
74            .await?;
75
76        Ok(count > 0)
77    }
78}
79
80// =============================================================================
81// PushSubscriptionWriter
82// =============================================================================
83
84#[async_trait]
85impl PushSubscriptionWriter for DieselDb {
86    async fn upsert_push_subscription(
87        &self,
88        input: UpsertPushSubscriptionInput,
89    ) -> Result<PushSubscriptionRecord> {
90        let mut conn = self.get_conn().await?;
91
92        let row = diesel::insert_into(push_subscriptions::table)
93            .values(NewPushSubscription {
94                wallet_address: input.wallet_address,
95                endpoint: input.endpoint,
96                auth_key: input.auth_key.clone(),
97                p256dh_key: input.p256dh_key.clone(),
98                preferences: input.preferences.clone(),
99            })
100            .on_conflict((
101                push_subscriptions::wallet_address,
102                push_subscriptions::endpoint,
103            ))
104            .do_update()
105            .set((
106                push_subscriptions::auth_key.eq(&input.auth_key),
107                push_subscriptions::p256dh_key.eq(&input.p256dh_key),
108                push_subscriptions::preferences.eq(&input.preferences),
109            ))
110            .get_result::<PushSubscription>(&mut conn)
111            .await?;
112
113        Ok(row.into())
114    }
115
116    async fn update_push_preferences(
117        &self,
118        wallet: &str,
119        endpoint: &str,
120        preferences: serde_json::Value,
121    ) -> Result<bool> {
122        let mut conn = self.get_conn().await?;
123        let wallet_lower = wallet.to_lowercase();
124
125        let updated = diesel::update(
126            push_subscriptions::table
127                .filter(push_subscriptions::wallet_address.eq(&wallet_lower))
128                .filter(push_subscriptions::endpoint.eq(endpoint)),
129        )
130        .set(push_subscriptions::preferences.eq(&preferences))
131        .execute(&mut conn)
132        .await?;
133
134        Ok(updated > 0)
135    }
136
137    async fn delete_push_subscription(&self, wallet: &str, endpoint: &str) -> Result<bool> {
138        let mut conn = self.get_conn().await?;
139        let wallet_lower = wallet.to_lowercase();
140
141        let deleted = diesel::delete(
142            push_subscriptions::table
143                .filter(push_subscriptions::wallet_address.eq(&wallet_lower))
144                .filter(push_subscriptions::endpoint.eq(endpoint)),
145        )
146        .execute(&mut conn)
147        .await?;
148
149        Ok(deleted > 0)
150    }
151
152    async fn delete_push_subscription_by_id(&self, id: i64) -> Result<bool> {
153        let mut conn = self.get_conn().await?;
154
155        let deleted = diesel::delete(push_subscriptions::table.find(id))
156            .execute(&mut conn)
157            .await?;
158
159        Ok(deleted > 0)
160    }
161}