AppFlowy-Cloud/src/api/file_storage.rs

82 lines
2.3 KiB
Rust

use std::pin::Pin;
use actix_http::body::BoxBody;
use actix_web::http::header::ContentType;
use actix_web::web::Payload;
use actix_web::{
web::{self, Data},
Scope,
};
use actix_web::{HttpResponse, Result};
use shared_entity::data::{AppResponse, JsonAppResponse};
use shared_entity::error_code::ErrorCode;
use tokio::io::AsyncRead;
use tokio_stream::StreamExt;
use tokio_util::io::StreamReader;
use crate::biz::file_storage;
use crate::{component::auth::jwt::UserUuid, state::AppState};
pub fn file_storage_scope() -> Scope {
web::scope("/api/file_storage").service(
web::resource("/{path}")
.route(web::get().to(get_handler))
.route(web::put().to(put_handler))
.route(web::delete().to(delete_handler)),
)
}
async fn put_handler(
user_uuid: UserUuid,
state: Data<AppState>,
path: web::Path<String>,
payload: Payload,
content_type: web::Header<ContentType>,
) -> Result<JsonAppResponse<()>> {
let file_path = path.into_inner();
let mime = content_type.into_inner().0;
let mut async_read = payload_to_async_read(payload);
file_storage::put_object(
&state.pg_pool,
&state.s3_bucket,
&user_uuid,
&file_path,
mime,
&mut async_read,
)
.await?;
Ok(AppResponse::Ok().into())
}
async fn delete_handler(
user_uuid: UserUuid,
state: Data<AppState>,
path: web::Path<String>,
) -> Result<JsonAppResponse<()>> {
let file_path = path.into_inner();
file_storage::delete_object(&state.pg_pool, &state.s3_bucket, &user_uuid, &file_path).await?;
Ok(AppResponse::Ok().into())
}
async fn get_handler(
user_uuid: UserUuid,
state: Data<AppState>,
path: web::Path<String>,
) -> Result<HttpResponse<BoxBody>> {
let file_path = path.into_inner();
match file_storage::get_object(&state.pg_pool, &state.s3_bucket, &user_uuid, &file_path).await {
Ok(async_read) => Ok(HttpResponse::Ok().streaming(async_read)),
Err(e) => match e.code {
ErrorCode::FileNotFound => Err(actix_web::error::ErrorNotFound(e)),
_ => Err(actix_web::error::ErrorInternalServerError(e)),
},
}
}
fn payload_to_async_read(payload: actix_web::web::Payload) -> Pin<Box<dyn AsyncRead>> {
let mapped =
payload.map(|chunk| chunk.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)));
let reader = StreamReader::new(mapped);
Box::pin(reader)
}