Can import basic data
This commit is contained in:
parent
3b5efb46cd
commit
57d919ee63
@ -25,10 +25,10 @@ enum CoupleControllerErr {
|
|||||||
MalformedDateOfDivorce,
|
MalformedDateOfDivorce,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, Clone)]
|
||||||
pub struct CoupleRequest {
|
pub struct CoupleRequest {
|
||||||
wife: Option<MemberID>,
|
pub wife: Option<MemberID>,
|
||||||
husband: Option<MemberID>,
|
pub husband: Option<MemberID>,
|
||||||
state: Option<CoupleState>,
|
state: Option<CoupleState>,
|
||||||
#[serde(flatten, with = "prefix_wedding")]
|
#[serde(flatten, with = "prefix_wedding")]
|
||||||
wedding: Option<RequestDate>,
|
wedding: Option<RequestDate>,
|
||||||
@ -37,9 +37,14 @@ pub struct CoupleRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CoupleRequest {
|
impl CoupleRequest {
|
||||||
pub async fn to_couple(self, couple: &mut Couple) -> anyhow::Result<()> {
|
pub async fn to_couple(
|
||||||
|
self,
|
||||||
|
couple: &mut Couple,
|
||||||
|
check_members_existence: bool,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
if let Some(wife) = self.wife {
|
if let Some(wife) = self.wife {
|
||||||
if !members_service::exists(couple.family_id(), wife).await? {
|
if check_members_existence && !members_service::exists(couple.family_id(), wife).await?
|
||||||
|
{
|
||||||
return Err(CoupleControllerErr::WifeNotExisting.into());
|
return Err(CoupleControllerErr::WifeNotExisting.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +54,9 @@ impl CoupleRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(husband) = self.husband {
|
if let Some(husband) = self.husband {
|
||||||
if !members_service::exists(couple.family_id(), husband).await? {
|
if check_members_existence
|
||||||
|
&& !members_service::exists(couple.family_id(), husband).await?
|
||||||
|
{
|
||||||
return Err(CoupleControllerErr::HusbandNotExisting.into());
|
return Err(CoupleControllerErr::HusbandNotExisting.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,7 +109,7 @@ impl CoupleAPI {
|
|||||||
pub async fn create(m: FamilyInPath, req: web::Json<CoupleRequest>) -> HttpResult {
|
pub async fn create(m: FamilyInPath, req: web::Json<CoupleRequest>) -> HttpResult {
|
||||||
let mut couple = couples_service::create(m.family_id()).await?;
|
let mut couple = couples_service::create(m.family_id()).await?;
|
||||||
|
|
||||||
if let Err(e) = req.0.to_couple(&mut couple).await {
|
if let Err(e) = req.0.to_couple(&mut couple, true).await {
|
||||||
log::error!("Failed to apply couple information! {e}");
|
log::error!("Failed to apply couple information! {e}");
|
||||||
couples_service::delete(&mut couple).await?;
|
couples_service::delete(&mut couple).await?;
|
||||||
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
||||||
@ -137,7 +144,7 @@ pub async fn get_single(m: FamilyAndCoupleInPath) -> HttpResult {
|
|||||||
pub async fn update(m: FamilyAndCoupleInPath, req: web::Json<CoupleRequest>) -> HttpResult {
|
pub async fn update(m: FamilyAndCoupleInPath, req: web::Json<CoupleRequest>) -> HttpResult {
|
||||||
let mut couple = m.to_couple();
|
let mut couple = m.to_couple();
|
||||||
|
|
||||||
if let Err(e) = req.0.to_couple(&mut couple).await {
|
if let Err(e) = req.0.to_couple(&mut couple, true).await {
|
||||||
log::error!("Failed to parse couple information {e}!");
|
log::error!("Failed to parse couple information {e}!");
|
||||||
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,39 @@
|
|||||||
use crate::connections::s3_connection;
|
use crate::connections::s3_connection;
|
||||||
|
use crate::constants;
|
||||||
|
use crate::controllers::couples_controller::CoupleRequest;
|
||||||
|
use crate::controllers::members_controller::MemberRequest;
|
||||||
use crate::controllers::HttpResult;
|
use crate::controllers::HttpResult;
|
||||||
use crate::extractors::family_extractor::FamilyInPath;
|
use crate::extractors::family_extractor::{FamilyInPath, FamilyInPathWithAdminMembership};
|
||||||
|
use crate::models::{CoupleID, MemberID, PhotoID};
|
||||||
use crate::services::{couples_service, members_service, photos_service};
|
use crate::services::{couples_service, members_service, photos_service};
|
||||||
|
use actix_multipart::form::tempfile::TempFile;
|
||||||
|
use actix_multipart::form::MultipartForm;
|
||||||
use actix_web::HttpResponse;
|
use actix_web::HttpResponse;
|
||||||
use std::io::{Cursor, Write};
|
use std::collections::HashMap;
|
||||||
|
use std::io;
|
||||||
|
use std::io::{Cursor, Read, Write};
|
||||||
use zip::write::FileOptions;
|
use zip::write::FileOptions;
|
||||||
use zip::CompressionMethod;
|
use zip::{CompressionMethod, ZipArchive};
|
||||||
|
|
||||||
const MEMBERS_FILE: &str = "members.json";
|
const MEMBERS_FILE: &str = "members.json";
|
||||||
const COUPLES_FILE: &str = "couples.json";
|
const COUPLES_FILE: &str = "couples.json";
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct ImportMemberRequest {
|
||||||
|
id: MemberID,
|
||||||
|
photo_id: Option<PhotoID>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
data: MemberRequest,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct ImportCoupleRequest {
|
||||||
|
id: CoupleID,
|
||||||
|
photo_id: Option<PhotoID>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
data: CoupleRequest,
|
||||||
|
}
|
||||||
|
|
||||||
/// Export whole family data
|
/// Export whole family data
|
||||||
pub async fn export_family(f: FamilyInPath) -> HttpResult {
|
pub async fn export_family(f: FamilyInPath) -> HttpResult {
|
||||||
let files_opt = FileOptions::default().compression_method(CompressionMethod::Bzip2);
|
let files_opt = FileOptions::default().compression_method(CompressionMethod::Bzip2);
|
||||||
@ -56,3 +80,120 @@ pub async fn export_family(f: FamilyInPath) -> HttpResult {
|
|||||||
.content_type("application/zip")
|
.content_type("application/zip")
|
||||||
.body(buff))
|
.body(buff))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, MultipartForm)]
|
||||||
|
pub struct UploadFamilyDataForm {
|
||||||
|
#[multipart(rename = "archive")]
|
||||||
|
archive: TempFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Import whole family data
|
||||||
|
pub async fn import_family(
|
||||||
|
f: FamilyInPathWithAdminMembership,
|
||||||
|
MultipartForm(form): MultipartForm<UploadFamilyDataForm>,
|
||||||
|
) -> HttpResult {
|
||||||
|
let mut zip = ZipArchive::new(form.archive.file)?;
|
||||||
|
|
||||||
|
// Parse general information
|
||||||
|
let members_list = serde_json::from_slice::<Vec<ImportMemberRequest>>(&read_zip_file(
|
||||||
|
&mut zip,
|
||||||
|
MEMBERS_FILE,
|
||||||
|
)?)?;
|
||||||
|
let mut couples_list = serde_json::from_slice::<Vec<ImportCoupleRequest>>(&read_zip_file(
|
||||||
|
&mut zip,
|
||||||
|
COUPLES_FILE,
|
||||||
|
)?)?;
|
||||||
|
|
||||||
|
// Delete all existing members
|
||||||
|
members_service::delete_all_family(f.family_id()).await?;
|
||||||
|
couples_service::delete_all_family(f.family_id()).await?;
|
||||||
|
|
||||||
|
// Create empty members set
|
||||||
|
let mut mapped_req_members = HashMap::new();
|
||||||
|
|
||||||
|
let mut members_id_mapping = HashMap::new();
|
||||||
|
let mut rev_members_id_mapping = HashMap::new();
|
||||||
|
let mut new_members = Vec::with_capacity(members_list.len());
|
||||||
|
|
||||||
|
for req_m in members_list {
|
||||||
|
// Create member entry in database
|
||||||
|
let new_m = members_service::create(f.family_id()).await?;
|
||||||
|
|
||||||
|
// Map new member ID with request id
|
||||||
|
members_id_mapping.insert(req_m.id, new_m.id());
|
||||||
|
rev_members_id_mapping.insert(new_m.id(), req_m.id);
|
||||||
|
|
||||||
|
// Save new member structure
|
||||||
|
new_members.push(new_m);
|
||||||
|
|
||||||
|
// Save request member information, mapped with its old id
|
||||||
|
mapped_req_members.insert(req_m.id, req_m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set member information, checking for eventual loops
|
||||||
|
for member in &mut new_members {
|
||||||
|
let db_id = member.id();
|
||||||
|
let req_id = *rev_members_id_mapping.get(&db_id).unwrap();
|
||||||
|
|
||||||
|
let req_member = mapped_req_members.get(&req_id).unwrap();
|
||||||
|
|
||||||
|
// Map mother and father id and extract member information
|
||||||
|
let mut req_member_data = req_member.data.clone();
|
||||||
|
if let Some(i) = req_member_data.father {
|
||||||
|
req_member_data.father = members_id_mapping.get(&i).copied();
|
||||||
|
}
|
||||||
|
if let Some(i) = req_member_data.mother {
|
||||||
|
req_member_data.mother = members_id_mapping.get(&i).copied();
|
||||||
|
}
|
||||||
|
|
||||||
|
req_member_data.to_member(member, true).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for loops
|
||||||
|
let members_slice = new_members.iter().collect::<Vec<_>>();
|
||||||
|
if members_service::loop_detection::detect_loop(&members_slice) {
|
||||||
|
return Ok(HttpResponse::BadRequest().body("Loop detected in members relationships!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save member information
|
||||||
|
for member in &mut new_members {
|
||||||
|
members_service::update(member).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract and insert couples information
|
||||||
|
let mut couple_mapping = HashMap::new();
|
||||||
|
for c in &mut couples_list {
|
||||||
|
// Map wife and husband
|
||||||
|
if let Some(i) = c.data.wife {
|
||||||
|
c.data.wife = members_id_mapping.get(&i).copied();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(i) = c.data.husband {
|
||||||
|
c.data.husband = members_id_mapping.get(&i).copied();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut db_couple = couples_service::create(f.family_id()).await?;
|
||||||
|
couple_mapping.insert(c.id, db_couple.id());
|
||||||
|
|
||||||
|
c.data.clone().to_couple(&mut db_couple, true).await?;
|
||||||
|
|
||||||
|
couples_service::update(&mut db_couple).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert photos
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().body("go on"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_zip_file<R: Read + io::Seek>(
|
||||||
|
archive: &mut ZipArchive<R>,
|
||||||
|
file: &str,
|
||||||
|
) -> anyhow::Result<Vec<u8>> {
|
||||||
|
let mut entry = archive.by_name(file)?;
|
||||||
|
assert!(entry.size() < constants::PHOTOS_MAX_SIZE as u64);
|
||||||
|
let mut buff = Vec::with_capacity(entry.size() as usize);
|
||||||
|
entry.read_to_end(&mut buff)?;
|
||||||
|
|
||||||
|
Ok(buff)
|
||||||
|
}
|
||||||
|
@ -12,7 +12,7 @@ use actix_web::{web, HttpResponse};
|
|||||||
serde_with::with_prefix!(prefix_birth "birth_");
|
serde_with::with_prefix!(prefix_birth "birth_");
|
||||||
serde_with::with_prefix!(prefix_death "death_");
|
serde_with::with_prefix!(prefix_death "death_");
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, Clone)]
|
||||||
pub struct RequestDate {
|
pub struct RequestDate {
|
||||||
pub year: Option<i16>,
|
pub year: Option<i16>,
|
||||||
pub month: Option<i16>,
|
pub month: Option<i16>,
|
||||||
@ -29,7 +29,7 @@ impl RequestDate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, Clone)]
|
||||||
pub struct MemberRequest {
|
pub struct MemberRequest {
|
||||||
first_name: Option<String>,
|
first_name: Option<String>,
|
||||||
last_name: Option<String>,
|
last_name: Option<String>,
|
||||||
@ -41,8 +41,8 @@ pub struct MemberRequest {
|
|||||||
postal_code: Option<String>,
|
postal_code: Option<String>,
|
||||||
country: Option<String>,
|
country: Option<String>,
|
||||||
sex: Option<Sex>,
|
sex: Option<Sex>,
|
||||||
mother: Option<MemberID>,
|
pub mother: Option<MemberID>,
|
||||||
father: Option<MemberID>,
|
pub father: Option<MemberID>,
|
||||||
#[serde(flatten, with = "prefix_birth")]
|
#[serde(flatten, with = "prefix_birth")]
|
||||||
birth: Option<RequestDate>,
|
birth: Option<RequestDate>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@ -104,7 +104,11 @@ fn check_opt_str_val(
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MemberRequest {
|
impl MemberRequest {
|
||||||
pub async fn to_member(self, member: &mut Member) -> anyhow::Result<()> {
|
pub async fn to_member(
|
||||||
|
self,
|
||||||
|
member: &mut Member,
|
||||||
|
check_members_existence: bool, /* TODO: remove this field */
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let c = StaticConstraints::default();
|
let c = StaticConstraints::default();
|
||||||
check_opt_str_val(
|
check_opt_str_val(
|
||||||
&self.first_name,
|
&self.first_name,
|
||||||
@ -191,7 +195,9 @@ impl MemberRequest {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(mother) = self.mother {
|
if let Some(mother) = self.mother {
|
||||||
if !members_service::exists(member.family_id(), mother).await? {
|
if check_members_existence
|
||||||
|
&& !members_service::exists(member.family_id(), mother).await?
|
||||||
|
{
|
||||||
return Err(MemberControllerErr::MotherNotExisting.into());
|
return Err(MemberControllerErr::MotherNotExisting.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +207,9 @@ impl MemberRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(father) = self.father {
|
if let Some(father) = self.father {
|
||||||
if !members_service::exists(member.family_id(), father).await? {
|
if check_members_existence
|
||||||
|
&& !members_service::exists(member.family_id(), father).await?
|
||||||
|
{
|
||||||
return Err(MemberControllerErr::FatherNotExisting.into());
|
return Err(MemberControllerErr::FatherNotExisting.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -254,7 +262,7 @@ impl MemberAPI {
|
|||||||
pub async fn create(f: FamilyInPath, req: web::Json<MemberRequest>) -> HttpResult {
|
pub async fn create(f: FamilyInPath, req: web::Json<MemberRequest>) -> HttpResult {
|
||||||
let mut member = members_service::create(f.family_id()).await?;
|
let mut member = members_service::create(f.family_id()).await?;
|
||||||
|
|
||||||
if let Err(e) = req.0.to_member(&mut member).await {
|
if let Err(e) = req.0.to_member(&mut member, true).await {
|
||||||
log::error!("Failed to apply member information! {e}");
|
log::error!("Failed to apply member information! {e}");
|
||||||
members_service::delete(&mut member).await?;
|
members_service::delete(&mut member).await?;
|
||||||
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
||||||
@ -284,7 +292,7 @@ pub async fn get_single(m: FamilyAndMemberInPath) -> HttpResult {
|
|||||||
pub async fn update(m: FamilyAndMemberInPath, req: web::Json<MemberRequest>) -> HttpResult {
|
pub async fn update(m: FamilyAndMemberInPath, req: web::Json<MemberRequest>) -> HttpResult {
|
||||||
let mut member = m.to_member();
|
let mut member = m.to_member();
|
||||||
|
|
||||||
if let Err(e) = req.0.to_member(&mut member).await {
|
if let Err(e) = req.0.to_member(&mut member, true).await {
|
||||||
log::error!("Failed to parse member information {e}!");
|
log::error!("Failed to parse member information {e}!");
|
||||||
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
||||||
}
|
}
|
||||||
|
@ -196,6 +196,10 @@ async fn main() -> std::io::Result<()> {
|
|||||||
"/family/{id}/data/export",
|
"/family/{id}/data/export",
|
||||||
web::get().to(data_controller::export_family),
|
web::get().to(data_controller::export_family),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/family/{id}/data/import",
|
||||||
|
web::put().to(data_controller::import_family),
|
||||||
|
)
|
||||||
// Photos controller
|
// Photos controller
|
||||||
.route(
|
.route(
|
||||||
"/photo/{id}",
|
"/photo/{id}",
|
||||||
|
@ -202,7 +202,7 @@ impl NewPhoto {
|
|||||||
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
|
||||||
pub struct MemberID(pub i32);
|
pub struct MemberID(pub i32);
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(serde::Serialize, serde::Deserialize, Copy, Clone)]
|
||||||
pub enum Sex {
|
pub enum Sex {
|
||||||
#[serde(rename = "M")]
|
#[serde(rename = "M")]
|
||||||
Male,
|
Male,
|
||||||
@ -310,7 +310,7 @@ pub struct NewMember {
|
|||||||
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
|
||||||
pub struct CoupleID(pub i32);
|
pub struct CoupleID(pub i32);
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(serde::Serialize, serde::Deserialize, Copy, Clone)]
|
||||||
pub enum CoupleState {
|
pub enum CoupleState {
|
||||||
#[serde(rename = "N")]
|
#[serde(rename = "N")]
|
||||||
None,
|
None,
|
||||||
|
Loading…
Reference in New Issue
Block a user