From f54dfde7f7bdf9a20f27a227fc8ef254c11d31a7 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Fri, 16 Jun 2023 17:51:51 +0200 Subject: [PATCH] Can create a family --- .../2023-05-24-102711_create_users/up.sql | 6 +-- geneit_backend/src/constants.rs | 5 ++ .../src/controllers/families_controller.rs | 24 ++++++++++ geneit_backend/src/controllers/mod.rs | 3 +- ...user_controller.rs => users_controller.rs} | 0 geneit_backend/src/main.rs | 21 ++++++--- geneit_backend/src/models.rs | 45 +++++++++++++++++- geneit_backend/src/schema.rs | 6 +-- .../src/services/families_service.rs | 46 +++++++++++++++++++ geneit_backend/src/services/mod.rs | 1 + 10 files changed, 140 insertions(+), 17 deletions(-) create mode 100644 geneit_backend/src/controllers/families_controller.rs rename geneit_backend/src/controllers/{user_controller.rs => users_controller.rs} (100%) create mode 100644 geneit_backend/src/services/families_service.rs diff --git a/geneit_backend/migrations/2023-05-24-102711_create_users/up.sql b/geneit_backend/migrations/2023-05-24-102711_create_users/up.sql index fd1c0a2..75bd7a1 100644 --- a/geneit_backend/migrations/2023-05-24-102711_create_users/up.sql +++ b/geneit_backend/migrations/2023-05-24-102711_create_users/up.sql @@ -14,18 +14,18 @@ CREATE TABLE users ( admin BOOLEAN NOT NULL DEFAULT FALSE ); -CREATE TABLE families( +CREATE TABLE families ( id SERIAL PRIMARY KEY, time_create BIGINT NOT NULL, name VARCHAR(30) NOT NULL, invitation_code VARCHAR(7) NOT NULL ); -CREATE TABLE memberships( +CREATE TABLE memberships ( user_id integer NOT NULL REFERENCES users, family_id integer NOT NULL REFERENCES families, time_create BIGINT NOT NULL, - is_admin BOOLEAN NOT NULL DEFAULT TRUE, + is_admin BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY(user_id, family_id) ); \ No newline at end of file diff --git a/geneit_backend/src/constants.rs b/geneit_backend/src/constants.rs index e6ecac8..ad5473d 100644 --- a/geneit_backend/src/constants.rs +++ b/geneit_backend/src/constants.rs @@ -22,6 +22,7 @@ pub struct StaticConstraints { pub mail_len: SizeConstraint, pub user_name_len: SizeConstraint, pub password_len: SizeConstraint, + pub family_name_len: SizeConstraint, } impl Default for StaticConstraints { @@ -30,6 +31,7 @@ impl Default for StaticConstraints { mail_len: SizeConstraint::new(5, 255), user_name_len: SizeConstraint::new(3, 30), password_len: SizeConstraint::new(8, 255), + family_name_len: SizeConstraint::new(3, 30), } } } @@ -42,3 +44,6 @@ pub const ACCOUNT_DELETE_TOKEN_DURATION: Duration = Duration::from_secs(3600 * 1 /// OpenID state duration pub const OPEN_ID_STATE_DURATION: Duration = Duration::from_secs(3600); + +/// Length of family invitation code +pub const FAMILY_INVITATION_CODE_LEN: usize = 7; diff --git a/geneit_backend/src/controllers/families_controller.rs b/geneit_backend/src/controllers/families_controller.rs new file mode 100644 index 0000000..cc85e2e --- /dev/null +++ b/geneit_backend/src/controllers/families_controller.rs @@ -0,0 +1,24 @@ +use crate::constants::StaticConstraints; +use crate::controllers::HttpResult; +use crate::services::families_service; +use crate::services::login_token_service::LoginToken; +use actix_web::{web, HttpResponse}; + +#[derive(Debug, serde::Deserialize)] +pub struct CreateFamilyReq { + name: String, +} + +/// Create a new family +pub async fn create(req: web::Json, token: LoginToken) -> HttpResult { + if !StaticConstraints::default() + .family_name_len + .validate(&req.name) + { + return Ok(HttpResponse::BadRequest().body("Invalid family name!")); + } + + let family = families_service::create(&req.name, token.user_id).await?; + + Ok(HttpResponse::Created().json(family)) +} diff --git a/geneit_backend/src/controllers/mod.rs b/geneit_backend/src/controllers/mod.rs index 234b923..78186b8 100644 --- a/geneit_backend/src/controllers/mod.rs +++ b/geneit_backend/src/controllers/mod.rs @@ -5,8 +5,9 @@ use actix_web::HttpResponse; use std::fmt::{Debug, Display, Formatter}; pub mod auth_controller; +pub mod families_controller; pub mod server_controller; -pub mod user_controller; +pub mod users_controller; /// Custom error to ease controller writing #[derive(Debug)] diff --git a/geneit_backend/src/controllers/user_controller.rs b/geneit_backend/src/controllers/users_controller.rs similarity index 100% rename from geneit_backend/src/controllers/user_controller.rs rename to geneit_backend/src/controllers/users_controller.rs diff --git a/geneit_backend/src/main.rs b/geneit_backend/src/main.rs index a1cf15a..b0555b3 100644 --- a/geneit_backend/src/main.rs +++ b/geneit_backend/src/main.rs @@ -3,7 +3,9 @@ use actix_remote_ip::RemoteIPConfig; use actix_web::middleware::Logger; use actix_web::{web, App, HttpServer}; use geneit_backend::app_config::AppConfig; -use geneit_backend::controllers::{auth_controller, server_controller, user_controller}; +use geneit_backend::controllers::{ + auth_controller, families_controller, server_controller, users_controller, +}; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -63,26 +65,31 @@ async fn main() -> std::io::Result<()> { ) .route("/auth/logout", web::get().to(auth_controller::logout)) // User controller - .route("/user/info", web::get().to(user_controller::auth_info)) + .route("/user/info", web::get().to(users_controller::auth_info)) .route( "/user/update_profile", - web::post().to(user_controller::update_profile), + web::post().to(users_controller::update_profile), ) .route( "/user/replace_password", - web::post().to(user_controller::replace_password), + web::post().to(users_controller::replace_password), ) .route( "/user/request_delete", - web::get().to(user_controller::request_delete_account), + web::get().to(users_controller::request_delete_account), ) .route( "/user/check_delete_token", - web::post().to(user_controller::check_delete_token), + web::post().to(users_controller::check_delete_token), ) .route( "/user/delete_account", - web::post().to(user_controller::delete_account), + web::post().to(users_controller::delete_account), + ) + // Families controller + .route( + "/family/create", + web::post().to(families_controller::create), ) }) .bind(AppConfig::get().listen_address.as_str())? diff --git a/geneit_backend/src/models.rs b/geneit_backend/src/models.rs index f393cd3..6ff6103 100644 --- a/geneit_backend/src/models.rs +++ b/geneit_backend/src/models.rs @@ -1,4 +1,4 @@ -use crate::schema::users; +use crate::schema::{families, memberships, users}; use diesel::prelude::*; /// User ID holder @@ -51,3 +51,46 @@ pub struct NewUser<'a> { pub email: &'a str, pub time_create: i64, } + +/// Family ID holder +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] +pub struct FamilyID(pub i32); + +#[derive(Queryable, Debug, serde::Serialize)] +pub struct Family { + pub id: i32, + pub time_create: i64, + pub name: String, + pub invitation_code: String, +} + +impl Family { + pub fn id(&self) -> FamilyID { + FamilyID(self.id) + } +} + +#[derive(Insertable)] +#[diesel(table_name = families)] +pub struct NewFamily<'a> { + pub name: &'a str, + pub invitation_code: String, + pub time_create: i64, +} + +#[derive(Queryable, Debug, serde::Serialize)] +pub struct Membership { + user_id: i32, + family_id: i32, + time_create: i64, + is_admin: bool, +} + +#[derive(Insertable)] +#[diesel(table_name = memberships)] +pub struct NewMembership { + pub user_id: i32, + pub family_id: i32, + pub time_create: i64, + pub is_admin: bool, +} diff --git a/geneit_backend/src/schema.rs b/geneit_backend/src/schema.rs index a9b1103..c46db26 100644 --- a/geneit_backend/src/schema.rs +++ b/geneit_backend/src/schema.rs @@ -38,8 +38,4 @@ diesel::table! { diesel::joinable!(memberships -> families (family_id)); diesel::joinable!(memberships -> users (user_id)); -diesel::allow_tables_to_appear_in_same_query!( - families, - memberships, - users, -); +diesel::allow_tables_to_appear_in_same_query!(families, memberships, users,); diff --git a/geneit_backend/src/services/families_service.rs b/geneit_backend/src/services/families_service.rs new file mode 100644 index 0000000..c41eaea --- /dev/null +++ b/geneit_backend/src/services/families_service.rs @@ -0,0 +1,46 @@ +use crate::connections::db_connection; +use crate::constants::FAMILY_INVITATION_CODE_LEN; +use crate::models::{Family, FamilyID, Membership, NewFamily, NewMembership, UserID}; +use crate::schema::{families, memberships}; +use crate::utils::string_utils::rand_str; +use crate::utils::time_utils::time; +use diesel::RunQueryDsl; + +/// Create a new family, with an initial administrator +pub async fn create(name: &str, user_id: UserID) -> anyhow::Result { + let family = db_connection::execute(|conn| { + let res: Family = diesel::insert_into(families::table) + .values(&NewFamily { + name: name.trim(), + invitation_code: rand_str(FAMILY_INVITATION_CODE_LEN), + time_create: time() as i64, + }) + .get_result(conn)?; + + Ok(res) + })?; + + add_member(family.id(), user_id, true).await?; + + Ok(family) +} + +/// Add a member to a family +pub async fn add_member( + family_id: FamilyID, + user_id: UserID, + admin: bool, +) -> anyhow::Result { + db_connection::execute(|conn| { + let res = diesel::insert_into(memberships::table) + .values(&NewMembership { + user_id: user_id.0, + family_id: family_id.0, + time_create: time() as i64, + is_admin: admin, + }) + .get_result(conn)?; + + Ok(res) + }) +} diff --git a/geneit_backend/src/services/mod.rs b/geneit_backend/src/services/mod.rs index 122af28..4ccdf03 100644 --- a/geneit_backend/src/services/mod.rs +++ b/geneit_backend/src/services/mod.rs @@ -1,5 +1,6 @@ //! # Backend services +pub mod families_service; pub mod login_token_service; pub mod mail_service; pub mod openid_service;