Can create a family

This commit is contained in:
Pierre HUBERT 2023-06-16 17:51:51 +02:00
parent e6c896efa2
commit f54dfde7f7
10 changed files with 140 additions and 17 deletions

View File

@ -25,7 +25,7 @@ CREATE TABLE memberships(
user_id integer NOT NULL REFERENCES users, user_id integer NOT NULL REFERENCES users,
family_id integer NOT NULL REFERENCES families, family_id integer NOT NULL REFERENCES families,
time_create BIGINT NOT NULL, 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) PRIMARY KEY(user_id, family_id)
); );

View File

@ -22,6 +22,7 @@ pub struct StaticConstraints {
pub mail_len: SizeConstraint, pub mail_len: SizeConstraint,
pub user_name_len: SizeConstraint, pub user_name_len: SizeConstraint,
pub password_len: SizeConstraint, pub password_len: SizeConstraint,
pub family_name_len: SizeConstraint,
} }
impl Default for StaticConstraints { impl Default for StaticConstraints {
@ -30,6 +31,7 @@ impl Default for StaticConstraints {
mail_len: SizeConstraint::new(5, 255), mail_len: SizeConstraint::new(5, 255),
user_name_len: SizeConstraint::new(3, 30), user_name_len: SizeConstraint::new(3, 30),
password_len: SizeConstraint::new(8, 255), 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 /// OpenID state duration
pub const OPEN_ID_STATE_DURATION: Duration = Duration::from_secs(3600); pub const OPEN_ID_STATE_DURATION: Duration = Duration::from_secs(3600);
/// Length of family invitation code
pub const FAMILY_INVITATION_CODE_LEN: usize = 7;

View File

@ -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<CreateFamilyReq>, 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))
}

View File

@ -5,8 +5,9 @@ use actix_web::HttpResponse;
use std::fmt::{Debug, Display, Formatter}; use std::fmt::{Debug, Display, Formatter};
pub mod auth_controller; pub mod auth_controller;
pub mod families_controller;
pub mod server_controller; pub mod server_controller;
pub mod user_controller; pub mod users_controller;
/// Custom error to ease controller writing /// Custom error to ease controller writing
#[derive(Debug)] #[derive(Debug)]

View File

@ -3,7 +3,9 @@ use actix_remote_ip::RemoteIPConfig;
use actix_web::middleware::Logger; use actix_web::middleware::Logger;
use actix_web::{web, App, HttpServer}; use actix_web::{web, App, HttpServer};
use geneit_backend::app_config::AppConfig; 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] #[actix_web::main]
async fn main() -> std::io::Result<()> { 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)) .route("/auth/logout", web::get().to(auth_controller::logout))
// User controller // User controller
.route("/user/info", web::get().to(user_controller::auth_info)) .route("/user/info", web::get().to(users_controller::auth_info))
.route( .route(
"/user/update_profile", "/user/update_profile",
web::post().to(user_controller::update_profile), web::post().to(users_controller::update_profile),
) )
.route( .route(
"/user/replace_password", "/user/replace_password",
web::post().to(user_controller::replace_password), web::post().to(users_controller::replace_password),
) )
.route( .route(
"/user/request_delete", "/user/request_delete",
web::get().to(user_controller::request_delete_account), web::get().to(users_controller::request_delete_account),
) )
.route( .route(
"/user/check_delete_token", "/user/check_delete_token",
web::post().to(user_controller::check_delete_token), web::post().to(users_controller::check_delete_token),
) )
.route( .route(
"/user/delete_account", "/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())? .bind(AppConfig::get().listen_address.as_str())?

View File

@ -1,4 +1,4 @@
use crate::schema::users; use crate::schema::{families, memberships, users};
use diesel::prelude::*; use diesel::prelude::*;
/// User ID holder /// User ID holder
@ -51,3 +51,46 @@ pub struct NewUser<'a> {
pub email: &'a str, pub email: &'a str,
pub time_create: i64, 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,
}

View File

@ -38,8 +38,4 @@ diesel::table! {
diesel::joinable!(memberships -> families (family_id)); diesel::joinable!(memberships -> families (family_id));
diesel::joinable!(memberships -> users (user_id)); diesel::joinable!(memberships -> users (user_id));
diesel::allow_tables_to_appear_in_same_query!( diesel::allow_tables_to_appear_in_same_query!(families, memberships, users,);
families,
memberships,
users,
);

View File

@ -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<Family> {
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<Membership> {
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)
})
}

View File

@ -1,5 +1,6 @@
//! # Backend services //! # Backend services
pub mod families_service;
pub mod login_token_service; pub mod login_token_service;
pub mod mail_service; pub mod mail_service;
pub mod openid_service; pub mod openid_service;