Compare commits
3 Commits
3b5efb46cd
...
5fa3d79b4c
| Author | SHA1 | Date | |
|---|---|---|---|
| 5fa3d79b4c | |||
| 9e94cfc298 | |||
| 57d919ee63 |
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"
|
||||||
@@ -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>,
|
||||||
@@ -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?;
|
||||||
|
|||||||
@@ -1,14 +1,40 @@
|
|||||||
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::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::MultipartForm;
|
||||||
use actix_web::HttpResponse;
|
use actix_web::HttpResponse;
|
||||||
use std::io::{Cursor, Write};
|
use mime_guess::Mime;
|
||||||
|
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";
|
||||||
|
const PHOTOS_DIR: &str = "photos/";
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct ImportMemberRequest {
|
||||||
|
id: MemberID,
|
||||||
|
photo_id: Option<PhotoID>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
data: MemberRequest,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct ImportCoupleRequest {
|
||||||
|
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 {
|
||||||
@@ -46,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)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,3 +82,204 @@ 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Photo {
|
||||||
|
path: String,
|
||||||
|
mimetype: Mime,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PhotoTarget {
|
||||||
|
Member(MemberID),
|
||||||
|
Couple(CoupleID),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PhotoToProcess {
|
||||||
|
id: PhotoID,
|
||||||
|
target: PhotoTarget,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Import whole family data
|
||||||
|
pub async fn import_family(
|
||||||
|
f: FamilyInPathWithAdminMembership,
|
||||||
|
MultipartForm(form): MultipartForm<UploadFamilyDataForm>,
|
||||||
|
) -> HttpResult {
|
||||||
|
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
|
||||||
|
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());
|
||||||
|
|
||||||
|
let mut photos_to_insert = Vec::with_capacity(photos.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).await?;
|
||||||
|
|
||||||
|
if let Some(id) = req_member.photo_id {
|
||||||
|
photos_to_insert.push(PhotoToProcess {
|
||||||
|
id,
|
||||||
|
target: PhotoTarget::Member(db_id),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 new_couples = HashMap::new();
|
||||||
|
for req_couple in &mut couples_list {
|
||||||
|
// Map wife and husband
|
||||||
|
if let Some(i) = req_couple.data.wife {
|
||||||
|
req_couple.data.wife = members_id_mapping.get(&i).copied();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(i) = req_couple.data.husband {
|
||||||
|
req_couple.data.husband = members_id_mapping.get(&i).copied();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut db_couple = couples_service::create(f.family_id()).await?;
|
||||||
|
|
||||||
|
req_couple.data.clone().to_couple(&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 member photos
|
||||||
|
for photo_to_process in photos_to_insert {
|
||||||
|
let photo = match photos.get(&photo_to_process.id) {
|
||||||
|
None => continue,
|
||||||
|
Some(photo) => photo,
|
||||||
|
};
|
||||||
|
|
||||||
|
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>(
|
||||||
|
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)]
|
||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -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,
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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