cloudillo_push/
handler.rs1use axum::{extract::State, http::StatusCode, Json};
4use serde::{Deserialize, Serialize};
5
6use crate::prelude::*;
7use cloudillo_core::extract::Auth;
8use cloudillo_types::meta_adapter::{PushSubscriptionData, PushSubscriptionKeys};
9
10#[derive(Debug, Deserialize)]
12pub struct CreateSubscriptionRequest {
13 pub subscription: BrowserSubscription,
15}
16
17#[derive(Debug, Deserialize)]
19pub struct BrowserSubscription {
20 pub endpoint: String,
22 #[serde(rename = "expirationTime")]
24 pub expiration_time: Option<i64>,
25 pub keys: BrowserSubscriptionKeys,
27}
28
29#[derive(Debug, Deserialize)]
31pub struct BrowserSubscriptionKeys {
32 pub p256dh: String,
34 pub auth: String,
36}
37
38#[derive(Debug, Serialize)]
40pub struct SubscriptionResponse {
41 pub id: u64,
43}
44
45pub async fn post_subscription(
51 State(app): State<App>,
52 Auth(auth): Auth,
53 Json(body): Json<CreateSubscriptionRequest>,
54) -> Result<Json<SubscriptionResponse>, (StatusCode, String)> {
55 tracing::info!(
56 tn_id = %auth.tn_id.0,
57 endpoint = %body.subscription.endpoint,
58 "Registering push subscription"
59 );
60
61 let subscription_data = PushSubscriptionData {
63 endpoint: body.subscription.endpoint,
64 expiration_time: body.subscription.expiration_time.map(|ms| ms / 1000),
66 keys: PushSubscriptionKeys {
67 p256dh: body.subscription.keys.p256dh,
68 auth: body.subscription.keys.auth,
69 },
70 };
71
72 let id = app
74 .meta_adapter
75 .create_push_subscription(auth.tn_id, &subscription_data)
76 .await
77 .map_err(|e| {
78 tracing::error!(error = %e, "Failed to create push subscription");
79 (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to save subscription: {}", e))
80 })?;
81
82 tracing::debug!(
83 tn_id = %auth.tn_id.0,
84 subscription_id = %id,
85 "Push subscription created"
86 );
87
88 Ok(Json(SubscriptionResponse { id }))
89}
90
91pub async fn delete_subscription(
95 State(app): State<App>,
96 Auth(auth): Auth,
97 axum::extract::Path(subscription_id): axum::extract::Path<u64>,
98) -> Result<StatusCode, (StatusCode, String)> {
99 tracing::info!(
100 tn_id = %auth.tn_id.0,
101 subscription_id = %subscription_id,
102 "Deleting push subscription"
103 );
104
105 app.meta_adapter
106 .delete_push_subscription(auth.tn_id, subscription_id)
107 .await
108 .map_err(|e| {
109 tracing::error!(error = %e, "Failed to delete push subscription");
110 (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to delete subscription: {}", e))
111 })?;
112
113 Ok(StatusCode::NO_CONTENT)
114}
115
116pub async fn get_vapid_public_key(
122 State(app): State<App>,
123 Auth(auth): Auth,
124) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
125 let public_key = match app.auth_adapter.read_vapid_public_key(auth.tn_id).await {
127 Ok(key) => key,
128 Err(Error::NotFound) => {
129 tracing::info!(tn_id = %auth.tn_id.0, "Creating VAPID key on demand");
131 let keypair = app.auth_adapter.create_vapid_key(auth.tn_id).await.map_err(|e| {
132 tracing::error!(error = %e, "Failed to create VAPID key");
133 (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to create VAPID key: {}", e))
134 })?;
135 keypair.public_key
136 }
137 Err(e) => {
138 tracing::error!(error = %e, "Failed to read VAPID public key");
139 return Err((
140 StatusCode::INTERNAL_SERVER_ERROR,
141 format!("Failed to get VAPID key: {}", e),
142 ));
143 }
144 };
145
146 Ok(Json(serde_json::json!({
147 "vapidPublicKey": public_key
148 })))
149}
150
151