Can create couple

This commit is contained in:
Pierre HUBERT 2023-08-07 16:50:22 +02:00
parent 75438f4ae0
commit 29c503a6b0
10 changed files with 367 additions and 15 deletions

View File

@ -68,17 +68,20 @@ CREATE TABLE members (
); );
CREATE TABLE couples ( CREATE TABLE couples (
wife integer NOT NULL REFERENCES members, id SERIAL PRIMARY KEY,
husband integer NOT NULL REFERENCES members, family_id integer NOT NULL REFERENCES families,
wife integer NULL REFERENCES members,
husband integer NULL REFERENCES members,
state varchar(1) NULL,
photo_id INTEGER NULL REFERENCES photos ON DELETE SET NULL, photo_id INTEGER NULL REFERENCES photos ON DELETE SET NULL,
time_create BIGINT NOT NULL,
time_update BIGINT NOT NULL,
wedding_year smallint NULL, wedding_year smallint NULL,
wedding_month smallint NULL, wedding_month smallint NULL,
wedding_day smallint NULL, wedding_day smallint NULL,
divorce_year smallint NULL, divorce_year smallint NULL,
divorce_month smallint NULL, divorce_month smallint NULL,
divorce_day smallint NULL, divorce_day smallint NULL
PRIMARY KEY(wife, husband)
); );
-- Create views -- Create views

View File

@ -0,0 +1,99 @@
use crate::controllers::members_controller::RequestDate;
use crate::controllers::HttpResult;
use crate::extractors::family_extractor::FamilyInPath;
use crate::models::{Couple, CoupleState, MemberID};
use crate::services::{couples_service, members_service};
use actix_web::{web, HttpResponse};
serde_with::with_prefix!(prefix_wedding "wedding_");
serde_with::with_prefix!(prefix_divorce "divorce_");
#[derive(thiserror::Error, Debug)]
enum CoupleControllerErr {
#[error("Wife and husband are identical!")]
IdenticalWifeHusband,
#[error("Wife does not exist!")]
WifeNotExisting,
#[error("Husband does not exist!")]
HusbandNotExisting,
#[error("Invalid date of wedding")]
MalformedDateOfWedding,
#[error("Invalid date of divorce")]
MalformedDateOfDivorce,
}
#[derive(serde::Deserialize)]
pub struct CoupleRequest {
wife: Option<MemberID>,
husband: Option<MemberID>,
state: Option<CoupleState>,
#[serde(flatten, with = "prefix_wedding")]
wedding: Option<RequestDate>,
#[serde(flatten, with = "prefix_divorce")]
divorce: Option<RequestDate>,
}
impl CoupleRequest {
pub async fn to_couple(self, couple: &mut Couple) -> anyhow::Result<()> {
if let Some(wife) = self.wife {
if !members_service::exists(couple.family_id(), wife).await? {
return Err(CoupleControllerErr::WifeNotExisting.into());
}
if self.wife == self.husband {
return Err(CoupleControllerErr::IdenticalWifeHusband.into());
}
}
if let Some(husband) = self.husband {
if !members_service::exists(couple.family_id(), husband).await? {
return Err(CoupleControllerErr::HusbandNotExisting.into());
}
}
if let Some(d) = &self.wedding {
if !d.check() {
return Err(CoupleControllerErr::MalformedDateOfWedding.into());
}
}
if let Some(d) = &self.divorce {
if !d.check() {
return Err(CoupleControllerErr::MalformedDateOfDivorce.into());
}
}
couple.set_wife(self.wife);
couple.set_husband(self.husband);
couple.set_state(self.state);
couple.wedding_year = self.wedding.as_ref().map(|m| m.year).unwrap_or_default();
couple.wedding_month = self.wedding.as_ref().map(|m| m.month).unwrap_or_default();
couple.wedding_day = self.wedding.as_ref().map(|m| m.day).unwrap_or_default();
couple.divorce_year = self.divorce.as_ref().map(|m| m.year).unwrap_or_default();
couple.divorce_month = self.divorce.as_ref().map(|m| m.month).unwrap_or_default();
couple.divorce_day = self.divorce.as_ref().map(|m| m.day).unwrap_or_default();
Ok(())
}
}
/// Create a new couple
pub async fn create(m: FamilyInPath, req: web::Json<CoupleRequest>) -> HttpResult {
let mut couple = couples_service::create(m.family_id()).await?;
if let Err(e) = req.0.to_couple(&mut couple).await {
log::error!("Failed to apply couple information! {e}");
couples_service::delete(&mut couple).await?;
return Ok(HttpResponse::BadRequest().body(e.to_string()));
}
if let Err(e) = couples_service::update(&mut couple).await {
log::error!("Failed to update couple information! {e}");
couples_service::delete(&mut couple).await?;
return Ok(HttpResponse::InternalServerError().finish());
}
Ok(HttpResponse::Ok().json(couple))
}

View File

@ -5,6 +5,7 @@ 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 couples_controller;
pub mod families_controller; pub mod families_controller;
pub mod members_controller; pub mod members_controller;
pub mod photos_controller; pub mod photos_controller;

View File

@ -6,8 +6,8 @@ use actix_web::{web, App, HttpServer};
use geneit_backend::app_config::AppConfig; use geneit_backend::app_config::AppConfig;
use geneit_backend::connections::s3_connection; use geneit_backend::connections::s3_connection;
use geneit_backend::controllers::{ use geneit_backend::controllers::{
auth_controller, families_controller, members_controller, photos_controller, server_controller, auth_controller, couples_controller, families_controller, members_controller,
users_controller, photos_controller, server_controller, users_controller,
}; };
#[actix_web::main] #[actix_web::main]
@ -162,6 +162,12 @@ async fn main() -> std::io::Result<()> {
"/family/{id}/member/{member_id}/photo", "/family/{id}/member/{member_id}/photo",
web::delete().to(members_controller::remove_photo), web::delete().to(members_controller::remove_photo),
) )
// Couples controller
.route(
"/family/{id}/couple/create",
web::post().to(couples_controller::create),
)
// Photos controller
.route( .route(
"/photo/{id}", "/photo/{id}",
web::get().to(photos_controller::get_full_size), web::get().to(photos_controller::get_full_size),

View File

@ -1,5 +1,5 @@
use crate::app_config::AppConfig; use crate::app_config::AppConfig;
use crate::schema::{families, members, memberships, photos, users}; use crate::schema::{couples, families, members, memberships, photos, users};
use crate::utils::crypt_utils::sha256; use crate::utils::crypt_utils::sha256;
use diesel::prelude::*; use diesel::prelude::*;
@ -297,3 +297,105 @@ pub struct NewMember {
pub time_create: i64, pub time_create: i64,
pub time_update: i64, pub time_update: i64,
} }
/// Member ID holder
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub struct CoupleID(pub i32);
#[derive(serde::Serialize, serde::Deserialize)]
pub enum CoupleState {
#[serde(rename = "N")]
None,
#[serde(rename = "E")]
Engaged,
#[serde(rename = "M")]
Married,
#[serde(rename = "D")]
Divorced,
}
impl CoupleState {
pub fn as_str(&self) -> &str {
match self {
CoupleState::None => "N",
CoupleState::Engaged => "E",
CoupleState::Married => "M",
CoupleState::Divorced => "D",
}
}
pub fn parse_str(s: &str) -> Option<Self> {
serde_json::from_str(s).ok()
}
}
#[derive(Queryable, Debug, serde::Serialize)]
pub struct Couple {
id: i32,
family_id: i32,
wife: Option<i32>,
husband: Option<i32>,
state: Option<String>,
photo_id: Option<i32>,
time_create: i64,
pub time_update: i64,
pub wedding_year: Option<i16>,
pub wedding_month: Option<i16>,
pub wedding_day: Option<i16>,
pub divorce_year: Option<i16>,
pub divorce_month: Option<i16>,
pub divorce_day: Option<i16>,
}
impl Couple {
pub fn id(&self) -> CoupleID {
CoupleID(self.id)
}
pub fn family_id(&self) -> FamilyID {
FamilyID(self.family_id)
}
pub fn state(&self) -> Option<CoupleState> {
self.state
.as_deref()
.map(CoupleState::parse_str)
.unwrap_or_default()
}
pub fn set_state(&mut self, s: Option<CoupleState>) {
self.state = s.map(|s| s.as_str().to_string())
}
pub fn set_wife(&mut self, s: Option<MemberID>) {
self.wife = s.map(|s| s.0)
}
pub fn wife(&self) -> Option<MemberID> {
self.wife.map(MemberID)
}
pub fn set_husband(&mut self, s: Option<MemberID>) {
self.husband = s.map(|s| s.0)
}
pub fn husband(&self) -> Option<MemberID> {
self.husband.map(MemberID)
}
pub fn set_photo_id(&mut self, p: Option<PhotoID>) {
self.photo_id = p.map(|p| p.0);
}
pub fn photo_id(&self) -> Option<PhotoID> {
self.photo_id.map(PhotoID)
}
}
#[derive(Insertable)]
#[diesel(table_name = couples)]
pub struct NewCouple {
pub family_id: i32,
pub time_create: i64,
pub time_update: i64,
}

View File

@ -1,10 +1,15 @@
// @generated automatically by Diesel CLI. // @generated automatically by Diesel CLI.
diesel::table! { diesel::table! {
couples (wife, husband) { couples (id) {
wife -> Int4, id -> Int4,
husband -> Int4, family_id -> Int4,
wife -> Nullable<Int4>,
husband -> Nullable<Int4>,
state -> Nullable<Varchar>,
photo_id -> Nullable<Int4>, photo_id -> Nullable<Int4>,
time_create -> Int8,
time_update -> Int8,
wedding_year -> Nullable<Int2>, wedding_year -> Nullable<Int2>,
wedding_month -> Nullable<Int2>, wedding_month -> Nullable<Int2>,
wedding_day -> Nullable<Int2>, wedding_day -> Nullable<Int2>,
@ -90,6 +95,7 @@ diesel::table! {
} }
} }
diesel::joinable!(couples -> families (family_id));
diesel::joinable!(couples -> photos (photo_id)); diesel::joinable!(couples -> photos (photo_id));
diesel::joinable!(members -> families (family_id)); diesel::joinable!(members -> families (family_id));
diesel::joinable!(members -> photos (photo_id)); diesel::joinable!(members -> photos (photo_id));

View File

@ -0,0 +1,124 @@
use crate::connections::db_connection;
use crate::models::{Couple, CoupleID, FamilyID, MemberID, NewCouple};
use crate::schema::couples;
use crate::services::photos_service;
use crate::utils::time_utils::time;
use diesel::prelude::*;
/// Create a new couple
pub async fn create(family_id: FamilyID) -> anyhow::Result<Couple> {
db_connection::execute(|conn| {
let res: Couple = diesel::insert_into(couples::table)
.values(&NewCouple {
family_id: family_id.0,
time_create: time() as i64,
time_update: time() as i64,
})
.get_result(conn)?;
Ok(res)
})
}
/// Get the information of a couple
pub async fn get_by_id(id: CoupleID) -> anyhow::Result<Couple> {
db_connection::execute(|conn| couples::table.filter(couples::dsl::id.eq(id.0)).first(conn))
}
/// Get all the couples of a family
pub async fn get_all_of_family(id: FamilyID) -> anyhow::Result<Vec<Couple>> {
db_connection::execute(|conn| {
couples::table
.filter(couples::dsl::family_id.eq(id.0))
.get_results(conn)
})
}
/// Get all the couples associated to a member
pub async fn get_all_of_member(id: MemberID) -> anyhow::Result<Vec<Couple>> {
db_connection::execute(|conn| {
couples::table
.filter(
couples::dsl::wife
.eq(id.0)
.or(couples::dsl::husband.eq(id.0)),
)
.get_results(conn)
})
}
/// Check whether a couple with a given id exists or not
pub async fn exists(family_id: FamilyID, couple_id: CoupleID) -> anyhow::Result<bool> {
db_connection::execute(|conn| {
let count: i64 = couples::table
.filter(
couples::id
.eq(couple_id.0)
.and(couples::family_id.eq(family_id.0)),
)
.count()
.get_result(conn)?;
Ok(count != 0)
})
}
/// Update the information of a couple
pub async fn update(couple: &mut Couple) -> anyhow::Result<()> {
couple.time_update = time() as i64;
db_connection::execute(|conn| {
diesel::update(couples::dsl::couples.filter(couples::dsl::id.eq(couple.id().0)))
.set((
couples::dsl::state.eq(couple.state().map(|c| c.as_str().to_string())),
couples::dsl::wife.eq(couple.wife().map(|m| m.0)),
couples::dsl::husband.eq(couple.husband().map(|m| m.0)),
couples::dsl::photo_id.eq(couple.photo_id().map(|p| p.0)),
couples::dsl::time_update.eq(couple.time_update),
couples::dsl::wedding_year.eq(couple.wedding_year),
couples::dsl::wedding_month.eq(couple.wedding_month),
couples::dsl::wedding_day.eq(couple.wedding_day),
couples::dsl::divorce_year.eq(couple.divorce_year),
couples::dsl::divorce_month.eq(couple.divorce_month),
couples::dsl::divorce_day.eq(couple.divorce_day),
))
.execute(conn)
})?;
Ok(())
}
/// Delete a couple photo
pub async fn remove_photo(couple: &mut Couple) -> anyhow::Result<()> {
match couple.photo_id() {
None => {}
Some(photo) => {
photos_service::delete(photo).await?;
couple.set_photo_id(None);
update(couple).await?;
}
}
Ok(())
}
/// Delete a couple
pub async fn delete(couple: &mut Couple) -> anyhow::Result<()> {
remove_photo(couple).await?;
// Remove the couple
db_connection::execute(|conn| {
diesel::delete(couples::dsl::couples.filter(couples::dsl::id.eq(couple.id().0)))
.execute(conn)
})?;
Ok(())
}
/// Delete all the couples of a family
pub async fn delete_all_family(family_id: FamilyID) -> anyhow::Result<()> {
for mut m in get_all_of_family(family_id).await? {
delete(&mut m).await?;
}
Ok(())
}

View File

@ -5,7 +5,7 @@ use crate::models::{
Family, FamilyID, FamilyMembership, Membership, NewFamily, NewMembership, UserID, Family, FamilyID, FamilyMembership, Membership, NewFamily, NewMembership, UserID,
}; };
use crate::schema::{families, memberships}; use crate::schema::{families, memberships};
use crate::services::{members_service, users_service}; use crate::services::{couples_service, members_service, users_service};
use crate::utils::string_utils::rand_str; use crate::utils::string_utils::rand_str;
use crate::utils::time_utils::time; use crate::utils::time_utils::time;
use diesel::prelude::*; use diesel::prelude::*;
@ -183,7 +183,8 @@ pub async fn update_family(family: &Family) -> anyhow::Result<()> {
/// Delete a family /// Delete a family
pub async fn delete_family(family_id: FamilyID) -> anyhow::Result<()> { pub async fn delete_family(family_id: FamilyID) -> anyhow::Result<()> {
// TODO : delete couples // Delete all family couples
couples_service::delete_all_family(family_id).await?;
// Remove all family members // Remove all family members
members_service::delete_all_family(family_id).await?; members_service::delete_all_family(family_id).await?;

View File

@ -1,7 +1,7 @@
use crate::connections::db_connection; use crate::connections::db_connection;
use crate::models::{FamilyID, Member, MemberID, NewMember}; use crate::models::{FamilyID, Member, MemberID, NewMember};
use crate::schema::members; use crate::schema::members;
use crate::services::photos_service; use crate::services::{couples_service, photos_service};
use crate::utils::time_utils::time; use crate::utils::time_utils::time;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::RunQueryDsl; use diesel::RunQueryDsl;
@ -102,7 +102,16 @@ pub async fn remove_photo(member: &mut Member) -> anyhow::Result<()> {
/// Delete a member /// Delete a member
pub async fn delete(member: &mut Member) -> anyhow::Result<()> { pub async fn delete(member: &mut Member) -> anyhow::Result<()> {
// TODO : remove associated couple // Remove associated couple
for mut c in couples_service::get_all_of_member(member.id()).await? {
// Check if only one person is attached to the couple
// i.e. if the current member is the last person attached to the couple
if c.wife().is_some() && c.husband().is_some() {
continue;
}
couples_service::delete(&mut c).await?;
}
remove_photo(member).await?; remove_photo(member).await?;

View File

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