2023-08-07 09:07:24 +00:00
|
|
|
use crate::connections::{db_connection, s3_connection};
|
|
|
|
use crate::constants::{ALLOWED_PHOTOS_MIMETYPES, PHOTOS_MAX_SIZE, THUMB_HEIGHT, THUMB_WIDTH};
|
|
|
|
use crate::models::{NewPhoto, Photo, PhotoID};
|
|
|
|
use crate::schema::photos;
|
|
|
|
use crate::utils::crypt_utils::sha512;
|
|
|
|
use crate::utils::time_utils::time;
|
|
|
|
use actix_multipart::form::tempfile::TempFile;
|
|
|
|
use diesel::prelude::*;
|
|
|
|
use image::imageops::FilterType;
|
|
|
|
use image::ImageOutputFormat;
|
|
|
|
use std::io::{Cursor, Read};
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
|
|
enum PhotoServiceError {
|
|
|
|
#[error("Uploaded file is too large ({0} bytes)!")]
|
|
|
|
FileToLarge(usize),
|
|
|
|
|
|
|
|
#[error("Mime type not specified in request!")]
|
|
|
|
MissingMimeType,
|
|
|
|
|
|
|
|
#[error("Mimetype forbidden ({0})!")]
|
|
|
|
MimeTypeForbidden(String),
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Finalize upload of a photo
|
|
|
|
pub async fn finalize_upload(mut file: TempFile) -> anyhow::Result<Photo> {
|
|
|
|
// Prerequisite checks
|
|
|
|
if file.size > PHOTOS_MAX_SIZE {
|
|
|
|
return Err(PhotoServiceError::FileToLarge(file.size).into());
|
|
|
|
}
|
|
|
|
|
|
|
|
let mime_type = file
|
|
|
|
.content_type
|
|
|
|
.ok_or(PhotoServiceError::MissingMimeType)?;
|
|
|
|
|
|
|
|
if !ALLOWED_PHOTOS_MIMETYPES.contains(&mime_type.as_ref()) {
|
|
|
|
return Err(PhotoServiceError::MimeTypeForbidden(mime_type.to_string()).into());
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut photo_img = Vec::with_capacity(file.size);
|
|
|
|
file.file.read_to_end(&mut photo_img)?;
|
|
|
|
|
|
|
|
let thumbnail_image = image::load_from_memory(&photo_img)?.resize(
|
|
|
|
THUMB_WIDTH,
|
|
|
|
THUMB_HEIGHT,
|
|
|
|
FilterType::Triangle,
|
|
|
|
);
|
|
|
|
|
|
|
|
let mut thumb_cursor = Cursor::new(vec![]);
|
|
|
|
thumbnail_image.write_to(&mut thumb_cursor, ImageOutputFormat::Png)?;
|
|
|
|
let thumb_img = thumb_cursor.into_inner();
|
|
|
|
|
|
|
|
let photo = NewPhoto {
|
|
|
|
file_id: Uuid::new_v4().to_string(),
|
|
|
|
time_create: time() as i64,
|
|
|
|
mime_type: mime_type.to_string(),
|
|
|
|
sha512: sha512(&photo_img),
|
|
|
|
file_size: photo_img.len() as i32,
|
|
|
|
thumb_sha512: sha512(&thumb_img),
|
|
|
|
};
|
|
|
|
|
|
|
|
s3_connection::upload_file(&photo.photo_path(), &photo_img).await?;
|
|
|
|
s3_connection::upload_file(&photo.thumbnail_path(), &thumb_img).await?;
|
|
|
|
|
|
|
|
db_connection::execute(|conn| {
|
|
|
|
let res: Photo = diesel::insert_into(photos::table)
|
|
|
|
.values(&photo)
|
|
|
|
.get_result(conn)?;
|
|
|
|
|
|
|
|
Ok(res)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a photo by its ID
|
|
|
|
pub async fn get_by_id(id: PhotoID) -> anyhow::Result<Photo> {
|
|
|
|
db_connection::execute(|conn| photos::table.filter(photos::dsl::id.eq(id.0)).first(conn))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Delete a photo
|
|
|
|
pub async fn delete(id: PhotoID) -> anyhow::Result<()> {
|
|
|
|
let photo = get_by_id(id).await?;
|
|
|
|
|
|
|
|
s3_connection::delete_file_if_exists(&photo.photo_path()).await?;
|
|
|
|
s3_connection::delete_file_if_exists(&photo.thumbnail_path()).await?;
|
|
|
|
|
|
|
|
db_connection::execute(|conn| {
|
|
|
|
diesel::delete(photos::dsl::photos.filter(photos::dsl::id.eq(photo.id().0))).execute(conn)
|
|
|
|
})?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|