diff --git a/migrations/2025-03-21-212723_create_fcm/up.sql b/migrations/2025-03-21-212723_create_fcm/up.sql index 218a90d..2611912 100644 --- a/migrations/2025-03-21-212723_create_fcm/up.sql +++ b/migrations/2025-03-21-212723_create_fcm/up.sql @@ -2,5 +2,5 @@ CREATE TABLE fcm ( user_id text PRIMARY KEY NOT NULL REFERENCES users (id), token text NOT NULL, - topics text[] NOT NULL + topics text[] NOT NULL CHECK ( array_position(topics, null) is null ) ); \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 1291311..7c1cc74 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,7 +50,8 @@ pub fn get_api_scope< let fcm_scope = utoipa_actix_web::scope("/fcm") .wrap(JWTAuthorization) - .service(routes::fcm::update_callback); + .service(routes::fcm::update_callback) + .service(routes::fcm::set_token); let vk_id_scope = utoipa_actix_web::scope("/vkid") // .service(routes::vk_id::oauth); diff --git a/src/routes/fcm/mod.rs b/src/routes/fcm/mod.rs index c7b4780..b422206 100644 --- a/src/routes/fcm/mod.rs +++ b/src/routes/fcm/mod.rs @@ -1,3 +1,5 @@ mod update_callback; +mod set_token; pub use update_callback::*; +pub use set_token::*; diff --git a/src/routes/fcm/set_token.rs b/src/routes/fcm/set_token.rs new file mode 100644 index 0000000..6ce1faf --- /dev/null +++ b/src/routes/fcm/set_token.rs @@ -0,0 +1,112 @@ +use crate::app_state::AppState; +use crate::database; +use crate::database::models::FCM; +use crate::extractors::authorized_user::UserExtractor; +use crate::extractors::base::SyncExtractor; +use crate::utility::mutex::{MutexScope, MutexScopeAsync}; +use actix_web::{HttpResponse, Responder, patch, web}; +use diesel::{RunQueryDsl, SaveChangesDsl}; +use firebase_messaging_rs::FCMClient; +use firebase_messaging_rs::topic::{TopicManagementError, TopicManagementSupport}; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +struct Params { + pub token: String, +} + +async fn get_fcm( + app_state: &web::Data, + user_data: &UserExtractor, + token: String, +) -> Result { + match user_data.fcm() { + Some(fcm) => { + let mut fcm = fcm.clone(); + fcm.token = token; + + Ok(fcm) + } + None => { + let fcm = FCM { + user_id: user_data.user().id.clone(), + token, + topics: vec![], + }; + + match app_state.database.scope(|conn| { + diesel::insert_into(database::schema::fcm::table) + .values(&fcm) + .execute(conn) + }) { + Ok(_) => Ok(fcm), + Err(e) => Err(e), + } + } + } +} + +#[utoipa::path(responses((status = OK)))] +#[patch("/set-token")] +pub async fn set_token( + app_state: web::Data, + web::Query(params): web::Query, + user_data: SyncExtractor>, +) -> impl Responder { + let user_data = user_data.into_inner(); + + // If token not changes - exit. + if let Some(fcm) = user_data.fcm() { + if fcm.token == params.token { + return HttpResponse::Ok(); + } + } + + let fcm = get_fcm(&app_state, &user_data, params.token.clone()).await; + if let Err(e) = fcm { + eprintln!("Failed to get FCM: {e}"); + return HttpResponse::Ok(); + } + + let mut fcm = fcm.ok().unwrap(); + + // Add default topics. + if !fcm.topics.contains(&Some("common".to_string())) { + fcm.topics.push(Some("common".to_string())); + } + + // Subscribe to default topics. + if let Some(e) = app_state + .fcm_client + .async_scope( + async |client: &mut FCMClient| -> Result<(), TopicManagementError> { + let mut tokens: Vec = Vec::new(); + tokens.push(fcm.token.clone()); + + for topic in fcm.topics.clone() { + if let Some(topic) = topic { + client.register_tokens_to_topic(topic.clone(), tokens.clone()).await?; + } + } + + Ok(()) + }, + ) + .await + .err() + { + eprintln!("Failed to subscribe token to topic: {:?}", e); + return HttpResponse::Ok(); + } + + // Write updates to db. + if let Some(e) = app_state + .database + .scope(|conn| fcm.save_changes::(conn)) + .err() + { + eprintln!("Failed to update FCM object: {e}"); + } + + HttpResponse::Ok() +}