Can import photos
This commit is contained in:
		
							
								
								
									
										53
									
								
								geneit_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										53
									
								
								geneit_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -1129,6 +1129,12 @@ dependencies = [
 | 
				
			|||||||
 "instant",
 | 
					 "instant",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "fastrand"
 | 
				
			||||||
 | 
					version = "2.0.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "fdeflate"
 | 
					name = "fdeflate"
 | 
				
			||||||
version = "0.3.0"
 | 
					version = "0.3.0"
 | 
				
			||||||
@@ -1310,6 +1316,7 @@ dependencies = [
 | 
				
			|||||||
 "serde_json",
 | 
					 "serde_json",
 | 
				
			||||||
 "serde_with",
 | 
					 "serde_with",
 | 
				
			||||||
 "sha2",
 | 
					 "sha2",
 | 
				
			||||||
 | 
					 "tempfile",
 | 
				
			||||||
 "thiserror",
 | 
					 "thiserror",
 | 
				
			||||||
 "uuid",
 | 
					 "uuid",
 | 
				
			||||||
 "zip",
 | 
					 "zip",
 | 
				
			||||||
@@ -1603,17 +1610,6 @@ dependencies = [
 | 
				
			|||||||
 "cfg-if",
 | 
					 "cfg-if",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "io-lifetimes"
 | 
					 | 
				
			||||||
version = "1.0.11"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
 | 
					 | 
				
			||||||
dependencies = [
 | 
					 | 
				
			||||||
 "hermit-abi",
 | 
					 | 
				
			||||||
 "libc",
 | 
					 | 
				
			||||||
 "windows-sys",
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "ipnet"
 | 
					name = "ipnet"
 | 
				
			||||||
version = "2.8.0"
 | 
					version = "2.8.0"
 | 
				
			||||||
@@ -1627,7 +1623,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			|||||||
checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb"
 | 
					checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "hermit-abi",
 | 
					 "hermit-abi",
 | 
				
			||||||
 "rustix 0.38.2",
 | 
					 "rustix",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1691,7 +1687,7 @@ dependencies = [
 | 
				
			|||||||
 "base64 0.21.2",
 | 
					 "base64 0.21.2",
 | 
				
			||||||
 "email-encoding",
 | 
					 "email-encoding",
 | 
				
			||||||
 "email_address",
 | 
					 "email_address",
 | 
				
			||||||
 "fastrand",
 | 
					 "fastrand 1.9.0",
 | 
				
			||||||
 "futures-util",
 | 
					 "futures-util",
 | 
				
			||||||
 "hostname",
 | 
					 "hostname",
 | 
				
			||||||
 "httpdate",
 | 
					 "httpdate",
 | 
				
			||||||
@@ -1725,12 +1721,6 @@ dependencies = [
 | 
				
			|||||||
 "urlencoding",
 | 
					 "urlencoding",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "linux-raw-sys"
 | 
					 | 
				
			||||||
version = "0.3.8"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "linux-raw-sys"
 | 
					name = "linux-raw-sys"
 | 
				
			||||||
version = "0.4.3"
 | 
					version = "0.4.3"
 | 
				
			||||||
@@ -2468,20 +2458,6 @@ dependencies = [
 | 
				
			|||||||
 "semver",
 | 
					 "semver",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "rustix"
 | 
					 | 
				
			||||||
version = "0.37.22"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "8818fa822adcc98b18fedbb3632a6a33213c070556b5aa7c4c8cc21cff565c4c"
 | 
					 | 
				
			||||||
dependencies = [
 | 
					 | 
				
			||||||
 "bitflags 1.3.2",
 | 
					 | 
				
			||||||
 "errno",
 | 
					 | 
				
			||||||
 "io-lifetimes",
 | 
					 | 
				
			||||||
 "libc",
 | 
					 | 
				
			||||||
 "linux-raw-sys 0.3.8",
 | 
					 | 
				
			||||||
 "windows-sys",
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "rustix"
 | 
					name = "rustix"
 | 
				
			||||||
version = "0.38.2"
 | 
					version = "0.38.2"
 | 
				
			||||||
@@ -2491,7 +2467,7 @@ dependencies = [
 | 
				
			|||||||
 "bitflags 2.3.3",
 | 
					 "bitflags 2.3.3",
 | 
				
			||||||
 "errno",
 | 
					 "errno",
 | 
				
			||||||
 "libc",
 | 
					 "libc",
 | 
				
			||||||
 "linux-raw-sys 0.4.3",
 | 
					 "linux-raw-sys",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2784,15 +2760,14 @@ dependencies = [
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "tempfile"
 | 
					name = "tempfile"
 | 
				
			||||||
version = "3.6.0"
 | 
					version = "3.7.1"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
 | 
					checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "autocfg",
 | 
					 | 
				
			||||||
 "cfg-if",
 | 
					 "cfg-if",
 | 
				
			||||||
 "fastrand",
 | 
					 "fastrand 2.0.0",
 | 
				
			||||||
 "redox_syscall 0.3.5",
 | 
					 "redox_syscall 0.3.5",
 | 
				
			||||||
 "rustix 0.37.22",
 | 
					 "rustix",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,4 +34,5 @@ image = "0.24.6"
 | 
				
			|||||||
uuid = { version = "1.4.1", features = ["v4"] }
 | 
					uuid = { version = "1.4.1", features = ["v4"] }
 | 
				
			||||||
httpdate = "1.0.2"
 | 
					httpdate = "1.0.2"
 | 
				
			||||||
zip = "0.6.6"
 | 
					zip = "0.6.6"
 | 
				
			||||||
mime_guess = "2.0.4"
 | 
					mime_guess = "2.0.4"
 | 
				
			||||||
 | 
					tempfile = "3.7.1"
 | 
				
			||||||
@@ -164,7 +164,7 @@ pub async fn set_photo(
 | 
				
			|||||||
    m: FamilyAndCoupleInPath,
 | 
					    m: FamilyAndCoupleInPath,
 | 
				
			||||||
    MultipartForm(form): MultipartForm<UploadPhotoForm>,
 | 
					    MultipartForm(form): MultipartForm<UploadPhotoForm>,
 | 
				
			||||||
) -> HttpResult {
 | 
					) -> HttpResult {
 | 
				
			||||||
    let photo = photos_service::finalize_upload(form.photo).await?;
 | 
					    let photo = photos_service::finalize_upload(form.photo.into()).await?;
 | 
				
			||||||
    let mut couple = m.to_couple();
 | 
					    let mut couple = m.to_couple();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    couples_service::remove_photo(&mut couple).await?;
 | 
					    couples_service::remove_photo(&mut couple).await?;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,10 +5,12 @@ use crate::controllers::members_controller::MemberRequest;
 | 
				
			|||||||
use crate::controllers::HttpResult;
 | 
					use crate::controllers::HttpResult;
 | 
				
			||||||
use crate::extractors::family_extractor::{FamilyInPath, FamilyInPathWithAdminMembership};
 | 
					use crate::extractors::family_extractor::{FamilyInPath, FamilyInPathWithAdminMembership};
 | 
				
			||||||
use crate::models::{CoupleID, MemberID, PhotoID};
 | 
					use crate::models::{CoupleID, MemberID, PhotoID};
 | 
				
			||||||
 | 
					use crate::services::photos_service::UploadedFile;
 | 
				
			||||||
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::tempfile::TempFile;
 | 
				
			||||||
use actix_multipart::form::MultipartForm;
 | 
					use actix_multipart::form::MultipartForm;
 | 
				
			||||||
use actix_web::HttpResponse;
 | 
					use actix_web::HttpResponse;
 | 
				
			||||||
 | 
					use mime_guess::Mime;
 | 
				
			||||||
use std::collections::HashMap;
 | 
					use std::collections::HashMap;
 | 
				
			||||||
use std::io;
 | 
					use std::io;
 | 
				
			||||||
use std::io::{Cursor, Read, Write};
 | 
					use std::io::{Cursor, Read, Write};
 | 
				
			||||||
@@ -17,6 +19,7 @@ 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";
 | 
				
			||||||
 | 
					const PHOTOS_DIR: &str = "photos/";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Deserialize)]
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
struct ImportMemberRequest {
 | 
					struct ImportMemberRequest {
 | 
				
			||||||
@@ -28,7 +31,6 @@ struct ImportMemberRequest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Deserialize)]
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
struct ImportCoupleRequest {
 | 
					struct ImportCoupleRequest {
 | 
				
			||||||
    id: CoupleID,
 | 
					 | 
				
			||||||
    photo_id: Option<PhotoID>,
 | 
					    photo_id: Option<PhotoID>,
 | 
				
			||||||
    #[serde(flatten)]
 | 
					    #[serde(flatten)]
 | 
				
			||||||
    data: CoupleRequest,
 | 
					    data: CoupleRequest,
 | 
				
			||||||
@@ -70,7 +72,7 @@ pub async fn export_family(f: FamilyInPath) -> HttpResult {
 | 
				
			|||||||
        let ext = photo.mime_extension().unwrap_or("bad");
 | 
					        let ext = photo.mime_extension().unwrap_or("bad");
 | 
				
			||||||
        let file = s3_connection::get_file(&photo.photo_path()).await?;
 | 
					        let file = s3_connection::get_file(&photo.photo_path()).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        zip_file.start_file(format!("photos/{}.{ext}", id.0), files_opt)?;
 | 
					        zip_file.start_file(format!("{PHOTOS_DIR}{}.{ext}", id.0), files_opt)?;
 | 
				
			||||||
        zip_file.write_all(&file)?;
 | 
					        zip_file.write_all(&file)?;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -87,6 +89,21 @@ pub struct UploadFamilyDataForm {
 | 
				
			|||||||
    archive: TempFile,
 | 
					    archive: TempFile,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Photo {
 | 
				
			||||||
 | 
					    path: String,
 | 
				
			||||||
 | 
					    mimetype: Mime,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum PhotoTarget {
 | 
				
			||||||
 | 
					    Member(MemberID),
 | 
				
			||||||
 | 
					    Couple(CoupleID),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct PhotoToProcess {
 | 
				
			||||||
 | 
					    id: PhotoID,
 | 
				
			||||||
 | 
					    target: PhotoTarget,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Import whole family data
 | 
					/// Import whole family data
 | 
				
			||||||
pub async fn import_family(
 | 
					pub async fn import_family(
 | 
				
			||||||
    f: FamilyInPathWithAdminMembership,
 | 
					    f: FamilyInPathWithAdminMembership,
 | 
				
			||||||
@@ -94,6 +111,23 @@ pub async fn import_family(
 | 
				
			|||||||
) -> HttpResult {
 | 
					) -> HttpResult {
 | 
				
			||||||
    let mut zip = ZipArchive::new(form.archive.file)?;
 | 
					    let mut zip = ZipArchive::new(form.archive.file)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Pre-process photos list
 | 
				
			||||||
 | 
					    let mut photos = HashMap::new();
 | 
				
			||||||
 | 
					    for file in zip.file_names() {
 | 
				
			||||||
 | 
					        let (id, ext) = match file.strip_prefix(PHOTOS_DIR).map(|f| f.split_once('.')) {
 | 
				
			||||||
 | 
					            Some(Some((id, ext))) => (id, ext),
 | 
				
			||||||
 | 
					            _ => continue,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        photos.insert(
 | 
				
			||||||
 | 
					            PhotoID(id.parse()?),
 | 
				
			||||||
 | 
					            Photo {
 | 
				
			||||||
 | 
					                path: file.to_string(),
 | 
				
			||||||
 | 
					                mimetype: mime_guess::from_ext(ext).first_or_octet_stream(),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Parse general information
 | 
					    // Parse general information
 | 
				
			||||||
    let members_list = serde_json::from_slice::<Vec<ImportMemberRequest>>(&read_zip_file(
 | 
					    let members_list = serde_json::from_slice::<Vec<ImportMemberRequest>>(&read_zip_file(
 | 
				
			||||||
        &mut zip,
 | 
					        &mut zip,
 | 
				
			||||||
@@ -115,6 +149,8 @@ pub async fn import_family(
 | 
				
			|||||||
    let mut rev_members_id_mapping = HashMap::new();
 | 
					    let mut rev_members_id_mapping = HashMap::new();
 | 
				
			||||||
    let mut new_members = Vec::with_capacity(members_list.len());
 | 
					    let mut new_members = Vec::with_capacity(members_list.len());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut photos_to_insert = Vec::with_capacity(photos.len());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for req_m in members_list {
 | 
					    for req_m in members_list {
 | 
				
			||||||
        // Create member entry in database
 | 
					        // Create member entry in database
 | 
				
			||||||
        let new_m = members_service::create(f.family_id()).await?;
 | 
					        let new_m = members_service::create(f.family_id()).await?;
 | 
				
			||||||
@@ -146,7 +182,14 @@ pub async fn import_family(
 | 
				
			|||||||
            req_member_data.mother = members_id_mapping.get(&i).copied();
 | 
					            req_member_data.mother = members_id_mapping.get(&i).copied();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        req_member_data.to_member(member, true).await?;
 | 
					        req_member_data.to_member(member).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if let Some(id) = req_member.photo_id {
 | 
				
			||||||
 | 
					            photos_to_insert.push(PhotoToProcess {
 | 
				
			||||||
 | 
					                id,
 | 
				
			||||||
 | 
					                target: PhotoTarget::Member(db_id),
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Check for loops
 | 
					    // Check for loops
 | 
				
			||||||
@@ -161,29 +204,72 @@ pub async fn import_family(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Extract and insert couples information
 | 
					    // Extract and insert couples information
 | 
				
			||||||
    let mut couple_mapping = HashMap::new();
 | 
					    let mut new_couples = HashMap::new();
 | 
				
			||||||
    for c in &mut couples_list {
 | 
					    for req_couple in &mut couples_list {
 | 
				
			||||||
        // Map wife and husband
 | 
					        // Map wife and husband
 | 
				
			||||||
        if let Some(i) = c.data.wife {
 | 
					        if let Some(i) = req_couple.data.wife {
 | 
				
			||||||
            c.data.wife = members_id_mapping.get(&i).copied();
 | 
					            req_couple.data.wife = members_id_mapping.get(&i).copied();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Some(i) = c.data.husband {
 | 
					        if let Some(i) = req_couple.data.husband {
 | 
				
			||||||
            c.data.husband = members_id_mapping.get(&i).copied();
 | 
					            req_couple.data.husband = members_id_mapping.get(&i).copied();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut db_couple = couples_service::create(f.family_id()).await?;
 | 
					        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?;
 | 
					        req_couple.data.clone().to_couple(&mut db_couple).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        couples_service::update(&mut db_couple).await?;
 | 
					        couples_service::update(&mut db_couple).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if let Some(id) = req_couple.photo_id {
 | 
				
			||||||
 | 
					            photos_to_insert.push(PhotoToProcess {
 | 
				
			||||||
 | 
					                id,
 | 
				
			||||||
 | 
					                target: PhotoTarget::Couple(db_couple.id()),
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        new_couples.insert(db_couple.id(), db_couple);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Insert photos
 | 
					    // Insert member photos
 | 
				
			||||||
    // TODO
 | 
					    for photo_to_process in photos_to_insert {
 | 
				
			||||||
 | 
					        let photo = match photos.get(&photo_to_process.id) {
 | 
				
			||||||
 | 
					            None => continue,
 | 
				
			||||||
 | 
					            Some(photo) => photo,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(HttpResponse::Ok().body("go on"))
 | 
					        let mut file = zip.by_name(&photo.path)?;
 | 
				
			||||||
 | 
					        if file.size() > constants::PHOTOS_MAX_SIZE as u64 {
 | 
				
			||||||
 | 
					            return Ok(HttpResponse::BadRequest().body("File is too large!"));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let mut buff = Vec::with_capacity(file.size() as usize);
 | 
				
			||||||
 | 
					        file.read_to_end(&mut buff)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let photo = photos_service::finalize_upload(UploadedFile::from_memory(
 | 
				
			||||||
 | 
					            &buff,
 | 
				
			||||||
 | 
					            Some(photo.mimetype.clone()),
 | 
				
			||||||
 | 
					        )?)
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Update appropriate database entry
 | 
				
			||||||
 | 
					        match photo_to_process.target {
 | 
				
			||||||
 | 
					            PhotoTarget::Member(member_id) => {
 | 
				
			||||||
 | 
					                let member = new_members
 | 
				
			||||||
 | 
					                    .iter_mut()
 | 
				
			||||||
 | 
					                    .find(|m| m.id().eq(&member_id))
 | 
				
			||||||
 | 
					                    .unwrap();
 | 
				
			||||||
 | 
					                member.set_photo_id(Some(photo.id()));
 | 
				
			||||||
 | 
					                members_service::update(member).await?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            PhotoTarget::Couple(couple_id) => {
 | 
				
			||||||
 | 
					                let couple = new_couples.get_mut(&couple_id).unwrap();
 | 
				
			||||||
 | 
					                couple.set_photo_id(Some(photo.id()));
 | 
				
			||||||
 | 
					                couples_service::update(couple).await?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Accepted().finish())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn read_zip_file<R: Read + io::Seek>(
 | 
					fn read_zip_file<R: Read + io::Seek>(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -325,7 +325,7 @@ pub async fn set_photo(
 | 
				
			|||||||
    m: FamilyAndMemberInPath,
 | 
					    m: FamilyAndMemberInPath,
 | 
				
			||||||
    MultipartForm(form): MultipartForm<UploadPhotoForm>,
 | 
					    MultipartForm(form): MultipartForm<UploadPhotoForm>,
 | 
				
			||||||
) -> HttpResult {
 | 
					) -> HttpResult {
 | 
				
			||||||
    let photo = photos_service::finalize_upload(form.photo).await?;
 | 
					    let photo = photos_service::finalize_upload(form.photo.into()).await?;
 | 
				
			||||||
    let mut member = m.to_member();
 | 
					    let mut member = m.to_member();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    members_service::remove_photo(&mut member).await?;
 | 
					    members_service::remove_photo(&mut member).await?;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,4 +57,10 @@ impl From<std::io::Error> for HttpErr {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<std::num::ParseIntError> for HttpErr {
 | 
				
			||||||
 | 
					    fn from(value: std::num::ParseIntError) -> Self {
 | 
				
			||||||
 | 
					        HttpErr { err: value.into() }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub type HttpResult = Result<HttpResponse, HttpErr>;
 | 
					pub type HttpResult = Result<HttpResponse, HttpErr>;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -227,7 +227,7 @@ impl Sex {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Queryable, Debug, serde::Serialize)]
 | 
					#[derive(Queryable, Debug, serde::Serialize, Clone)]
 | 
				
			||||||
pub struct Member {
 | 
					pub struct Member {
 | 
				
			||||||
    id: i32,
 | 
					    id: i32,
 | 
				
			||||||
    family_id: i32,
 | 
					    family_id: i32,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,9 @@ use actix_multipart::form::tempfile::TempFile;
 | 
				
			|||||||
use diesel::prelude::*;
 | 
					use diesel::prelude::*;
 | 
				
			||||||
use image::imageops::FilterType;
 | 
					use image::imageops::FilterType;
 | 
				
			||||||
use image::ImageOutputFormat;
 | 
					use image::ImageOutputFormat;
 | 
				
			||||||
use std::io::{Cursor, Read};
 | 
					use mime_guess::Mime;
 | 
				
			||||||
 | 
					use std::fs::File;
 | 
				
			||||||
 | 
					use std::io::{Cursor, Read, Seek, Write};
 | 
				
			||||||
use uuid::Uuid;
 | 
					use uuid::Uuid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(thiserror::Error, Debug)]
 | 
					#[derive(thiserror::Error, Debug)]
 | 
				
			||||||
@@ -23,8 +25,38 @@ enum PhotoServiceError {
 | 
				
			|||||||
    MimeTypeForbidden(String),
 | 
					    MimeTypeForbidden(String),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct UploadedFile {
 | 
				
			||||||
 | 
					    pub size: usize,
 | 
				
			||||||
 | 
					    pub content_type: Option<Mime>,
 | 
				
			||||||
 | 
					    pub file: File,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<TempFile> for UploadedFile {
 | 
				
			||||||
 | 
					    fn from(value: TempFile) -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            size: value.size,
 | 
				
			||||||
 | 
					            content_type: value.content_type,
 | 
				
			||||||
 | 
					            file: value.file.into_file(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl UploadedFile {
 | 
				
			||||||
 | 
					    pub fn from_memory(buff: &[u8], content_type: Option<Mime>) -> anyhow::Result<Self> {
 | 
				
			||||||
 | 
					        let mut file = tempfile::tempfile()?;
 | 
				
			||||||
 | 
					        file.write_all(buff)?;
 | 
				
			||||||
 | 
					        file.rewind()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(Self {
 | 
				
			||||||
 | 
					            size: buff.len(),
 | 
				
			||||||
 | 
					            content_type,
 | 
				
			||||||
 | 
					            file,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Finalize upload of a photo
 | 
					/// Finalize upload of a photo
 | 
				
			||||||
pub async fn finalize_upload(mut file: TempFile) -> anyhow::Result<Photo> {
 | 
					pub async fn finalize_upload(mut file: UploadedFile) -> anyhow::Result<Photo> {
 | 
				
			||||||
    // Prerequisite checks
 | 
					    // Prerequisite checks
 | 
				
			||||||
    if file.size > PHOTOS_MAX_SIZE {
 | 
					    if file.size > PHOTOS_MAX_SIZE {
 | 
				
			||||||
        return Err(PhotoServiceError::FileToLarge(file.size).into());
 | 
					        return Err(PhotoServiceError::FileToLarge(file.size).into());
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user