diff --git a/geneit_backend/src/controllers/families_controller.rs b/geneit_backend/src/controllers/families_controller.rs index cc85e2e..3ee3980 100644 --- a/geneit_backend/src/controllers/families_controller.rs +++ b/geneit_backend/src/controllers/families_controller.rs @@ -1,7 +1,9 @@ use crate::constants::StaticConstraints; use crate::controllers::HttpResult; -use crate::services::families_service; use crate::services::login_token_service::LoginToken; +use crate::services::rate_limiter_service::RatedAction; +use crate::services::{families_service, rate_limiter_service}; +use actix_remote_ip::RemoteIP; use actix_web::{web, HttpResponse}; #[derive(Debug, serde::Deserialize)] @@ -22,3 +24,42 @@ pub async fn create(req: web::Json, token: LoginToken) -> HttpR Ok(HttpResponse::Created().json(family)) } + +#[derive(Debug, serde::Deserialize)] +pub struct JoinFamilyReq { + code: String, +} + +/// Join a family +pub async fn join( + remote_ip: RemoteIP, + req: web::Json, + token: LoginToken, +) -> HttpResult { + // Rate limiting + if rate_limiter_service::should_block_action(remote_ip.0, RatedAction::JoinFamily).await? { + return Ok(HttpResponse::TooManyRequests().finish()); + } + rate_limiter_service::record_action(remote_ip.0, RatedAction::JoinFamily).await?; + + let family = match families_service::get_by_invitation_code(&req.code).await { + Ok(f) => f, + Err(e) => { + log::error!("Could not find family by invitation code! {e}"); + return Ok(HttpResponse::NotFound().finish()); + } + }; + + if families_service::is_member(family.id(), token.user_id).await? { + log::error!( + "Could not add {:?} to family {:?} because it is already a member of the family!", + token.user_id, + family.id() + ); + return Ok(HttpResponse::Conflict().finish()); + } + + families_service::add_member(family.id(), token.user_id, false).await?; + + Ok(HttpResponse::Accepted().finish()) +} diff --git a/geneit_backend/src/main.rs b/geneit_backend/src/main.rs index b0555b3..45a9dc4 100644 --- a/geneit_backend/src/main.rs +++ b/geneit_backend/src/main.rs @@ -91,6 +91,7 @@ async fn main() -> std::io::Result<()> { "/family/create", web::post().to(families_controller::create), ) + .route("/family/join", web::post().to(families_controller::join)) }) .bind(AppConfig::get().listen_address.as_str())? .run() diff --git a/geneit_backend/src/services/families_service.rs b/geneit_backend/src/services/families_service.rs index 597b0bd..03c0d16 100644 --- a/geneit_backend/src/services/families_service.rs +++ b/geneit_backend/src/services/families_service.rs @@ -4,7 +4,7 @@ use crate::models::{Family, FamilyID, Membership, NewFamily, NewMembership, User use crate::schema::{families, memberships}; use crate::utils::string_utils::rand_str; use crate::utils::time_utils::time; -use diesel::RunQueryDsl; +use diesel::prelude::*; /// Create a new family, with an initial administrator pub async fn create(name: &str, user_id: UserID) -> anyhow::Result { @@ -45,12 +45,33 @@ pub async fn add_member( }) } +/// Find a family by invitation code +pub async fn get_by_invitation_code(code: &str) -> anyhow::Result { + db_connection::execute(|conn| { + families::table + .filter(families::dsl::invitation_code.eq(code)) + .first(conn) + }) +} + +/// Check if a given user is member of a family or not +pub async fn is_member(family_id: FamilyID, user_id: UserID) -> anyhow::Result { + db_connection::execute(|conn| { + memberships::table + .filter(memberships::dsl::family_id.eq(family_id.0)) + .filter(memberships::dsl::user_id.eq(user_id.0)) + .count() + .get_result(conn) + }) + .map(|c: i64| c > 0) +} + /// Remove a membership to a family -pub async fn remove_membership(family_id: FamilyID, user_id: UserID) { +pub async fn remove_membership(_family_id: FamilyID, _user_id: UserID) { todo!() } /// Remove all memberships of user -pub async fn remove_all_user_membership(user_id: UserID) -> anyhow::Result<()> { +pub async fn remove_all_user_membership(_user_id: UserID) -> anyhow::Result<()> { todo!() } diff --git a/geneit_backend/src/services/rate_limiter_service.rs b/geneit_backend/src/services/rate_limiter_service.rs index 3541d8d..e43eea2 100644 --- a/geneit_backend/src/services/rate_limiter_service.rs +++ b/geneit_backend/src/services/rate_limiter_service.rs @@ -12,6 +12,7 @@ pub enum RatedAction { StartOpenIDLogin, RequestReplacePasswordSignedIn, RequestDeleteAccount, + JoinFamily, } impl RatedAction { @@ -24,6 +25,7 @@ impl RatedAction { RatedAction::StartOpenIDLogin => "start-oidc-login", RatedAction::RequestReplacePasswordSignedIn => "req-pwd-signed-in", RatedAction::RequestDeleteAccount => "req-del-acct", + RatedAction::JoinFamily => "join-family", } } @@ -36,6 +38,7 @@ impl RatedAction { RatedAction::StartOpenIDLogin => 30, RatedAction::RequestReplacePasswordSignedIn => 5, RatedAction::RequestDeleteAccount => 5, + RatedAction::JoinFamily => 10, } }