use bytes::Bytes;
use chrono::Utc;
use uuid::Uuid;
use crate::error::Error;
pub const ALLOWED_IMAGE_TYPES: &[&str] = &["image/jpeg", "image/png", "image/gif", "image/webp"];
pub const MAX_PRESIGN_EXPIRY_SECS: u64 = 604_800;
pub const PROFILE_IMAGE_SIZE: u32 = 200;
pub const THUMBNAIL_SIZE: u32 = 50;
#[derive(Debug, Clone, Default)]
pub struct PutOptions {
pub content_disposition: Option<String>,
pub cache_control: Option<String>,
}
#[derive(Debug, Clone)]
pub struct RawDownloadResult {
pub data: Bytes,
pub content_type: String,
pub content_length: Option<i64>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct UploadResult {
pub url: String,
pub thumbnail_url: String,
pub key: String,
pub thumbnail_key: String,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FileUploadResult {
pub url: String,
pub key: String,
pub content_type: String,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DownloadResult {
#[cfg_attr(feature = "serde", serde(skip))]
pub data: Bytes,
pub content_type: String,
pub content_length: Option<i64>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ImageUploadConfig {
pub width: u32,
pub height: u32,
pub thumbnail_width: u32,
pub thumbnail_height: u32,
pub folder: String,
pub maintain_aspect_ratio: bool,
}
impl Default for ImageUploadConfig {
fn default() -> Self {
Self {
width: PROFILE_IMAGE_SIZE,
height: PROFILE_IMAGE_SIZE,
thumbnail_width: THUMBNAIL_SIZE,
thumbnail_height: THUMBNAIL_SIZE,
folder: "profiles".to_string(),
maintain_aspect_ratio: false,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PresignedUploadResult {
pub presigned_url: String,
pub key: String,
pub public_url: String,
pub expires_at: i64,
}
pub fn validate_content_type(content_type: &str) -> Result<(), Error> {
if !ALLOWED_IMAGE_TYPES.contains(&content_type) {
return Err(Error::InvalidFileType(
content_type.to_string(),
ALLOWED_IMAGE_TYPES.join(", "),
));
}
Ok(())
}
pub fn validate_file_size(size: usize, max: usize) -> Result<(), Error> {
if size > max {
return Err(Error::FileTooLarge(size, max));
}
Ok(())
}
pub fn validate_expiry(expires_in_secs: u64) -> Result<(), Error> {
if expires_in_secs == 0 || expires_in_secs > MAX_PRESIGN_EXPIRY_SECS {
return Err(Error::InvalidExpiry(
expires_in_secs,
MAX_PRESIGN_EXPIRY_SECS,
));
}
Ok(())
}
pub fn generate_storage_key(folder: &str, extension: &str) -> String {
let date = Utc::now().format("%Y/%m/%d");
let uuid = Uuid::new_v4();
format!("{folder}/{date}/{uuid}.{extension}")
}
pub fn generate_thumbnail_key(key: &str) -> String {
if let Some(pos) = key.rfind('.') {
format!("{}_thumb{}", &key[..pos], &key[pos..])
} else {
format!("{key}_thumb")
}
}