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>, user: AuthenticatedUser, Json(payload): Json, ) -> AppResult> { let user_sub = user.0.sub; tracing::info!( "Registering push subscription for user: {} with endpoint: {}", user_sub, payload.endpoint ); // 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>, user: AuthenticatedUser, Path(task_id): Path, ) -> AppResult> { 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>, user: AuthenticatedUser, Path(task_id): Path, ) -> AppResult> { 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>, ) -> AppResult> { 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>, user: AuthenticatedUser, Path(task_id): Path, ) -> AppResult> { 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() }), )) }