Files
quoter/src/s3_utils.rs
Untone 0506f5abe9
Some checks failed
Deploy quoter / deploy (push) Has been cancelled
fix: remove double extension in uploaded files (filename.png.png → filename.png)
2025-10-05 12:36:19 +03:00

137 lines
4.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use actix_web::error::ErrorInternalServerError;
use aws_sdk_s3::{Client as S3Client, error::SdkError, primitives::ByteStream};
use infer::get;
use mime_guess::mime;
use std::str::FromStr;
/// Загружает файл в S3 хранилище.
pub async fn upload_to_s3(
storj_client: &S3Client,
bucket: &str,
key: &str,
body: Vec<u8>,
content_type: &str,
) -> Result<String, actix_web::Error> {
let body_stream = ByteStream::from(body); // Преобразуем тело файла в поток байтов
storj_client
.put_object()
.bucket(bucket)
.key(key)
.body(body_stream)
.content_type(content_type)
.send()
.await
.map_err(|_| ErrorInternalServerError("Failed to upload file to S3"))?; // Загрузка файла в S3
Ok(key.to_string()) // Возвращаем ключ файла
}
/// Проверяет, существует ли файл в S3.
pub async fn check_file_exists(
s3_client: &S3Client,
bucket: &str,
filepath: &str,
) -> Result<bool, actix_web::Error> {
match s3_client
.head_object()
.bucket(bucket)
.key(filepath)
.send()
.await
{
Ok(_) => Ok(true), // Файл найден
Err(SdkError::ServiceError(service_error)) if service_error.err().is_not_found() => {
Ok(false) // Файл не найден
}
Err(e) => Err(ErrorInternalServerError(e.to_string())), // Ошибка при проверке
}
}
/// Загружает файл из S3.
pub async fn load_file_from_s3(
s3_client: &S3Client,
bucket: &str,
key: &str,
) -> Result<Vec<u8>, actix_web::Error> {
let get_object_output = s3_client
.get_object()
.bucket(bucket)
.key(key)
.send()
.await
.map_err(|_| ErrorInternalServerError("Failed to get object from S3"))?;
let data: aws_sdk_s3::primitives::AggregatedBytes = get_object_output
.body
.collect()
.await
.map_err(|_| ErrorInternalServerError("Failed to read object body"))?;
Ok(data.to_vec())
}
/// Генерирует ключ с правильным расширением на основе MIME-типа.
#[allow(clippy::collapsible_if)]
pub fn generate_key_with_extension(base_key: String, mime_type: String) -> String {
// Убираем существующее расширение из base_key (если есть)
let key_without_ext = if let Some(dot_pos) = base_key.rfind('.') {
&base_key[..dot_pos]
} else {
&base_key
};
let mime: mime::Mime =
mime::Mime::from_str(&mime_type).unwrap_or(mime::APPLICATION_OCTET_STREAM);
if let Some(extensions) = mime_guess::get_mime_extensions_str(mime.as_ref()) {
if let Some(extension) = extensions.first() {
return format!("{}.{}", key_without_ext, extension.to_lowercase());
}
}
key_without_ext.to_string()
}
/// список файлов из S3
pub async fn get_s3_filelist(client: &S3Client, bucket: &str) -> Vec<[std::string::String; 2]> {
let mut filenames = Vec::new();
// Запрашиваем список файлов из S3
let list_objects_v2 = client.list_objects_v2();
let list_response = list_objects_v2
.bucket(bucket)
.send()
.await
.expect("Failed to list files from Storj");
if let Some(objects) = list_response.contents {
for object in objects.iter() {
if let Some(s3_filepath) = &object.key {
let filepath = match s3_filepath.ends_with("/webp") {
true => &s3_filepath.replace("/webp", ""),
false => s3_filepath,
};
let mut parts = filepath.split('/').collect::<Vec<&str>>(); // Explicit type annotation
if let Some(filename) = parts.pop() {
filenames.push([filename.to_string(), s3_filepath.to_string()]);
}
}
}
}
filenames
}
pub fn detect_mime_type(bytes: &[u8]) -> Option<String> {
let kind = get(bytes)?;
Some(kind.mime_type().to_string())
}
pub fn get_extension_from_mime(mime_type: &str) -> Option<&str> {
match mime_type {
"image/jpeg" => Some("jpg"),
"image/png" => Some("png"),
"image/gif" => Some("gif"),
"image/webp" => Some("webp"),
"image/heic" => Some("heic"),
"image/tiff" => Some("tiff"),
_ => None,
}
}