push notifications
All checks were successful
/ upload (release) Successful in 1m4s

This commit is contained in:
pavel 2026-02-12 01:28:24 +01:00
commit 91db5861c8
21 changed files with 1263 additions and 79 deletions

View file

@ -0,0 +1,128 @@
use axum::{
Json,
extract::{Path, State},
};
use chrono::Utc;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
use std::sync::Arc;
use uuid::Uuid;
use crate::entities::{push_subscription, task_subscription};
use crate::error::{AppError, AppResult};
use crate::server::AppState;
use crate::server::auth::AuthenticatedUser;
use base64::Engine;
#[derive(serde::Deserialize)]
pub struct RegisterPushRequest {
pub endpoint: String,
pub p256dh: String,
pub auth: String,
}
pub async fn register_push(
State(state): State<Arc<AppState>>,
user: AuthenticatedUser,
Json(payload): Json<RegisterPushRequest>,
) -> AppResult<Json<serde_json::Value>> {
let user_sub = user.0.sub;
// Check if subscription exists
let existing = push_subscription::Entity::find()
.filter(push_subscription::Column::UserSub.eq(user_sub.clone()))
.filter(push_subscription::Column::Endpoint.eq(payload.endpoint.clone()))
.one(&state.db)
.await
.map_err(AppError::Database)?;
if existing.is_none() {
let new_sub = push_subscription::ActiveModel {
id: Set(Uuid::new_v4()),
user_sub: Set(user_sub),
endpoint: Set(payload.endpoint),
p256dh: Set(payload.p256dh),
auth: Set(payload.auth),
created_at: Set(Utc::now().into()),
};
new_sub
.insert(&state.db)
.await
.map_err(AppError::Database)?;
}
Ok(Json(serde_json::json!({ "status": "registered" })))
}
pub async fn subscribe_task(
State(state): State<Arc<AppState>>,
user: AuthenticatedUser,
Path(task_id): Path<Uuid>,
) -> AppResult<Json<serde_json::Value>> {
let user_sub = user.0.sub;
let existing = task_subscription::Entity::find()
.filter(task_subscription::Column::UserSub.eq(user_sub.clone()))
.filter(task_subscription::Column::TaskId.eq(task_id))
.one(&state.db)
.await
.map_err(AppError::Database)?;
if existing.is_none() {
let new_sub = task_subscription::ActiveModel {
id: Set(Uuid::new_v4()),
user_sub: Set(user_sub),
task_id: Set(task_id),
created_at: Set(Utc::now().into()),
};
new_sub
.insert(&state.db)
.await
.map_err(AppError::Database)?;
}
Ok(Json(serde_json::json!({ "status": "subscribed" })))
}
pub async fn unsubscribe_task(
State(state): State<Arc<AppState>>,
user: AuthenticatedUser,
Path(task_id): Path<Uuid>,
) -> AppResult<Json<serde_json::Value>> {
let user_sub = user.0.sub;
task_subscription::Entity::delete_many()
.filter(task_subscription::Column::UserSub.eq(user_sub))
.filter(task_subscription::Column::TaskId.eq(task_id))
.exec(&state.db)
.await
.map_err(AppError::Database)?;
Ok(Json(serde_json::json!({ "status": "unsubscribed" })))
}
pub async fn get_vapid_key(
State(state): State<Arc<AppState>>,
) -> AppResult<Json<serde_json::Value>> {
let public_key = state.scheduler.push_sender.get_public_key()?;
let encoded = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(public_key);
Ok(Json(serde_json::json!({ "publicKey": encoded })))
}
pub async fn get_subscription_status(
State(state): State<Arc<AppState>>,
user: AuthenticatedUser,
Path(task_id): Path<Uuid>,
) -> AppResult<Json<serde_json::Value>> {
let user_sub = user.0.sub;
let existing = task_subscription::Entity::find()
.filter(task_subscription::Column::UserSub.eq(user_sub))
.filter(task_subscription::Column::TaskId.eq(task_id))
.one(&state.db)
.await
.map_err(AppError::Database)?;
Ok(Json(
serde_json::json!({ "isSubscribed": existing.is_some() }),
))
}