feat: implement file storage using appflowy cloud (#3675)

* feat: implement file storage using appflowy cloud

* chore: clippy
This commit is contained in:
Nathan.fooo 2023-10-12 09:54:45 +08:00 committed by GitHub
parent 8e10cba8e5
commit 058eeec932
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 79 additions and 56 deletions

View File

@ -454,7 +454,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
dependencies = [ dependencies = [
"borsh-derive", "borsh-derive",
"hashbrown 0.12.3", "hashbrown 0.13.2",
] ]
[[package]] [[package]]
@ -762,7 +762,7 @@ dependencies = [
[[package]] [[package]]
name = "client-api" name = "client-api"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6faefb0#6faefb083fe0a127045ec57431f126e499f4a687" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a231fda#5a231fdadba514d891e914e95c90bfcca7e042b7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -1291,7 +1291,7 @@ dependencies = [
"cssparser-macros", "cssparser-macros",
"dtoa-short", "dtoa-short",
"itoa 1.0.6", "itoa 1.0.6",
"phf 0.11.2", "phf 0.8.0",
"smallvec", "smallvec",
] ]
@ -1437,7 +1437,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]] [[package]]
name = "database-entity" name = "database-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6faefb0#6faefb083fe0a127045ec57431f126e499f4a687" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a231fda#5a231fdadba514d891e914e95c90bfcca7e042b7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -1976,7 +1976,7 @@ dependencies = [
"collab-integrate", "collab-integrate",
"csv", "csv",
"dashmap", "dashmap",
"fancy-regex 0.10.0", "fancy-regex 0.11.0",
"flowy-codegen", "flowy-codegen",
"flowy-database-deps", "flowy-database-deps",
"flowy-derive", "flowy-derive",
@ -2778,7 +2778,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue" name = "gotrue"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6faefb0#6faefb083fe0a127045ec57431f126e499f4a687" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a231fda#5a231fdadba514d891e914e95c90bfcca7e042b7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"futures-util", "futures-util",
@ -2794,7 +2794,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue-entity" name = "gotrue-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6faefb0#6faefb083fe0a127045ec57431f126e499f4a687" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a231fda#5a231fdadba514d891e914e95c90bfcca7e042b7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"reqwest", "reqwest",
@ -3227,7 +3227,7 @@ dependencies = [
[[package]] [[package]]
name = "infra" name = "infra"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6faefb0#6faefb083fe0a127045ec57431f126e499f4a687" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a231fda#5a231fdadba514d891e914e95c90bfcca7e042b7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"reqwest", "reqwest",
@ -4268,7 +4268,6 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [ dependencies = [
"phf_macros 0.11.2",
"phf_shared 0.11.2", "phf_shared 0.11.2",
] ]
@ -4360,19 +4359,6 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "phf_macros"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
dependencies = [
"phf_generator 0.11.2",
"phf_shared 0.11.2",
"proc-macro2",
"quote",
"syn 2.0.29",
]
[[package]] [[package]]
name = "phf_shared" name = "phf_shared"
version = "0.8.0" version = "0.8.0"
@ -4876,7 +4862,7 @@ dependencies = [
[[package]] [[package]]
name = "realtime-entity" name = "realtime-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6faefb0#6faefb083fe0a127045ec57431f126e499f4a687" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a231fda#5a231fdadba514d891e914e95c90bfcca7e042b7"
dependencies = [ dependencies = [
"bytes", "bytes",
"collab", "collab",
@ -5598,7 +5584,7 @@ dependencies = [
[[package]] [[package]]
name = "shared_entity" name = "shared_entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6faefb0#6faefb083fe0a127045ec57431f126e499f4a687" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a231fda#5a231fdadba514d891e914e95c90bfcca7e042b7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"database-entity", "database-entity",

View File

@ -38,7 +38,7 @@ custom-protocol = ["tauri/custom-protocol"]
# Run the script: # Run the script:
# scripts/tool/update_client_api_rev.sh new_rev_id # scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6faefb0" } client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "5a231fda" }
# Please use the following script to update collab. # Please use the following script to update collab.
# Working directory: frontend # Working directory: frontend
# #

View File

@ -660,7 +660,7 @@ dependencies = [
[[package]] [[package]]
name = "client-api" name = "client-api"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6faefb0#6faefb083fe0a127045ec57431f126e499f4a687" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a231fda#5a231fdadba514d891e914e95c90bfcca7e042b7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -1264,7 +1264,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]] [[package]]
name = "database-entity" name = "database-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6faefb0#6faefb083fe0a127045ec57431f126e499f4a687" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a231fda#5a231fdadba514d891e914e95c90bfcca7e042b7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -2438,7 +2438,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue" name = "gotrue"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6faefb0#6faefb083fe0a127045ec57431f126e499f4a687" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a231fda#5a231fdadba514d891e914e95c90bfcca7e042b7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"futures-util", "futures-util",
@ -2454,7 +2454,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue-entity" name = "gotrue-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6faefb0#6faefb083fe0a127045ec57431f126e499f4a687" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a231fda#5a231fdadba514d891e914e95c90bfcca7e042b7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"reqwest", "reqwest",
@ -2812,7 +2812,7 @@ dependencies = [
[[package]] [[package]]
name = "infra" name = "infra"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6faefb0#6faefb083fe0a127045ec57431f126e499f4a687" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a231fda#5a231fdadba514d891e914e95c90bfcca7e042b7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"reqwest", "reqwest",
@ -4203,7 +4203,7 @@ dependencies = [
[[package]] [[package]]
name = "realtime-entity" name = "realtime-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6faefb0#6faefb083fe0a127045ec57431f126e499f4a687" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a231fda#5a231fdadba514d891e914e95c90bfcca7e042b7"
dependencies = [ dependencies = [
"bytes", "bytes",
"collab", "collab",
@ -4824,7 +4824,7 @@ dependencies = [
[[package]] [[package]]
name = "shared_entity" name = "shared_entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6faefb0#6faefb083fe0a127045ec57431f126e499f4a687" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a231fda#5a231fdadba514d891e914e95c90bfcca7e042b7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"database-entity", "database-entity",

View File

@ -82,7 +82,7 @@ incremental = false
# Run the script: # Run the script:
# scripts/tool/update_client_api_rev.sh new_rev_id # scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6faefb0" } client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "5a231fda" }
# Please use the following script to update collab. # Please use the following script to update collab.
# Working directory: frontend # Working directory: frontend
# #

View File

@ -12,6 +12,7 @@ use crate::document::supabase_test::helper::FlowySupabaseDocumentTest;
#[tokio::test] #[tokio::test]
async fn supabase_document_upload_text_file_test() { async fn supabase_document_upload_text_file_test() {
if let Some(test) = FlowySupabaseDocumentTest::new().await { if let Some(test) = FlowySupabaseDocumentTest::new().await {
let workspace_id = test.get_current_workspace().await.workspace.id;
let storage_service = test let storage_service = test
.document_manager .document_manager
.get_file_storage_service() .get_file_storage_service()
@ -19,6 +20,7 @@ async fn supabase_document_upload_text_file_test() {
.unwrap(); .unwrap();
let object = StorageObject::from_bytes( let object = StorageObject::from_bytes(
&workspace_id,
&Uuid::new_v4().to_string(), &Uuid::new_v4().to_string(),
"hello world".as_bytes(), "hello world".as_bytes(),
"text/plain".to_string(), "text/plain".to_string(),
@ -41,6 +43,7 @@ async fn supabase_document_upload_text_file_test() {
#[tokio::test] #[tokio::test]
async fn supabase_document_upload_zip_file_test() { async fn supabase_document_upload_zip_file_test() {
if let Some(test) = FlowySupabaseDocumentTest::new().await { if let Some(test) = FlowySupabaseDocumentTest::new().await {
let workspace_id = test.get_current_workspace().await.workspace.id;
let storage_service = test let storage_service = test
.document_manager .document_manager
.get_file_storage_service() .get_file_storage_service()
@ -48,8 +51,11 @@ async fn supabase_document_upload_zip_file_test() {
.unwrap(); .unwrap();
// Upload zip file // Upload zip file
let object = let object = StorageObject::from_file(
StorageObject::from_file(&Uuid::new_v4().to_string(), "./tests/asset/test.txt.zip"); &workspace_id,
&Uuid::new_v4().to_string(),
"./tests/asset/test.txt.zip",
);
let url = storage_service.create_object(object).await.unwrap(); let url = storage_service.create_object(object).await.unwrap();
// Read zip file // Read zip file
@ -79,6 +85,7 @@ async fn supabase_document_upload_zip_file_test() {
#[tokio::test] #[tokio::test]
async fn supabase_document_upload_image_test() { async fn supabase_document_upload_image_test() {
if let Some(test) = FlowySupabaseDocumentTest::new().await { if let Some(test) = FlowySupabaseDocumentTest::new().await {
let workspace_id = test.get_current_workspace().await.workspace.id;
let storage_service = test let storage_service = test
.document_manager .document_manager
.get_file_storage_service() .get_file_storage_service()
@ -86,7 +93,11 @@ async fn supabase_document_upload_image_test() {
.unwrap(); .unwrap();
// Upload zip file // Upload zip file
let object = StorageObject::from_file(&Uuid::new_v4().to_string(), "./tests/asset/logo.png"); let object = StorageObject::from_file(
&workspace_id,
&Uuid::new_v4().to_string(),
"./tests/asset/logo.png",
);
let url = storage_service.create_object(object).await.unwrap(); let url = storage_service.create_object(object).await.unwrap();
let image_data = storage_service let image_data = storage_service

View File

@ -8,7 +8,6 @@ impl From<AppError> for FlowyError {
client_api::error::ErrorCode::Ok => ErrorCode::Internal, client_api::error::ErrorCode::Ok => ErrorCode::Internal,
client_api::error::ErrorCode::Unhandled => ErrorCode::Internal, client_api::error::ErrorCode::Unhandled => ErrorCode::Internal,
client_api::error::ErrorCode::RecordNotFound => ErrorCode::RecordNotFound, client_api::error::ErrorCode::RecordNotFound => ErrorCode::RecordNotFound,
client_api::error::ErrorCode::FileNotFound => ErrorCode::RecordNotFound,
client_api::error::ErrorCode::RecordAlreadyExists => ErrorCode::RecordAlreadyExists, client_api::error::ErrorCode::RecordAlreadyExists => ErrorCode::RecordAlreadyExists,
client_api::error::ErrorCode::InvalidEmail => ErrorCode::EmailFormatInvalid, client_api::error::ErrorCode::InvalidEmail => ErrorCode::EmailFormatInvalid,
client_api::error::ErrorCode::InvalidPassword => ErrorCode::PasswordFormatInvalid, client_api::error::ErrorCode::InvalidPassword => ErrorCode::PasswordFormatInvalid,

View File

@ -1,18 +1,18 @@
use bytes::Bytes; use bytes::Bytes;
use tokio::fs::File;
use tokio::io::AsyncReadExt;
use flowy_error::FlowyError; use flowy_error::FlowyError;
use flowy_storage::{FileStorageService, StorageObject}; use flowy_storage::{FileStorageService, ObjectValue, StorageObject};
use lib_infra::future::FutureResult; use lib_infra::future::FutureResult;
use crate::af_cloud::AFServer; use crate::af_cloud::AFServer;
pub struct AFCloudFileStorageServiceImpl<T> { pub struct AFCloudFileStorageServiceImpl<T>(pub T);
#[allow(dead_code)]
client: T,
}
impl<T> AFCloudFileStorageServiceImpl<T> { impl<T> AFCloudFileStorageServiceImpl<T> {
pub fn new(client: T) -> Self { pub fn new(client: T) -> Self {
Self { client } Self(client)
} }
} }
@ -20,24 +20,43 @@ impl<T> FileStorageService for AFCloudFileStorageServiceImpl<T>
where where
T: AFServer, T: AFServer,
{ {
fn create_object(&self, _object: StorageObject) -> FutureResult<String, FlowyError> { fn create_object(&self, object: StorageObject) -> FutureResult<String, FlowyError> {
let try_get_client = self.0.try_get_client();
FutureResult::new(async move { FutureResult::new(async move {
// TODO let client = try_get_client?;
Ok("".to_owned())
match object.value {
ObjectValue::File { file_path } => {
let mut file = File::open(&file_path).await?;
let mime = mime_guess::from_path(file_path)
.first_or_octet_stream()
.to_string();
let mut buffer = Vec::new();
file.read_to_end(&mut buffer).await?;
Ok(client.put_file(&object.workspace_id, buffer, mime).await?)
},
ObjectValue::Bytes { bytes, mime } => {
Ok(client.put_file(&object.workspace_id, bytes, mime).await?)
},
}
}) })
} }
fn delete_object_by_url(&self, _object_url: String) -> FutureResult<(), FlowyError> { fn delete_object_by_url(&self, object_url: String) -> FutureResult<(), FlowyError> {
let try_get_client = self.0.try_get_client();
FutureResult::new(async move { FutureResult::new(async move {
// TODO let client = try_get_client?;
client.delete_file(&object_url).await?;
Ok(()) Ok(())
}) })
} }
fn get_object_by_url(&self, _object_url: String) -> FutureResult<Bytes, FlowyError> { fn get_object_by_url(&self, object_url: String) -> FutureResult<Bytes, FlowyError> {
let try_get_client = self.0.try_get_client();
FutureResult::new(async move { FutureResult::new(async move {
// TODO let client = try_get_client?;
Ok(Bytes::new()) let bytes = client.get_file(&object_url).await?;
Ok(bytes)
}) })
} }
} }

View File

@ -13,7 +13,7 @@ async fn supabase_get_object_test() {
let service = file_storage_service(); let service = file_storage_service();
let file_name = format!("test-{}.txt", Uuid::new_v4()); let file_name = format!("test-{}.txt", Uuid::new_v4());
let object = StorageObject::from_file(&file_name, "tests/test.txt"); let object = StorageObject::from_file("1", &file_name, "tests/test.txt");
// Upload a file // Upload a file
let url = service let url = service
@ -42,7 +42,7 @@ async fn supabase_upload_image_test() {
let service = file_storage_service(); let service = file_storage_service();
let file_name = format!("image-{}.png", Uuid::new_v4()); let file_name = format!("image-{}.png", Uuid::new_v4());
let object = StorageObject::from_file(&file_name, "tests/logo.png"); let object = StorageObject::from_file("1", &file_name, "tests/logo.png");
// Upload a file // Upload a file
let url = service let url = service
@ -65,7 +65,7 @@ async fn supabase_delete_object_test() {
let service = file_storage_service(); let service = file_storage_service();
let file_name = format!("test-{}.txt", Uuid::new_v4()); let file_name = format!("test-{}.txt", Uuid::new_v4());
let object = StorageObject::from_file(&file_name, "tests/test.txt"); let object = StorageObject::from_file("1", &file_name, "tests/test.txt");
let url = service.create_object(object).await.unwrap(); let url = service.create_object(object).await.unwrap();
let result = service.get_object_by_url(url.clone()).await; let result = service.get_object_by_url(url.clone()).await;

View File

@ -4,6 +4,7 @@ use flowy_error::FlowyError;
use lib_infra::future::FutureResult; use lib_infra::future::FutureResult;
pub struct StorageObject { pub struct StorageObject {
pub workspace_id: String,
pub file_name: String, pub file_name: String,
pub value: ObjectValue, pub value: ObjectValue,
} }
@ -16,8 +17,9 @@ impl StorageObject {
/// * `name`: The name of the storage object. /// * `name`: The name of the storage object.
/// * `file_path`: The file path to the storage object's data. /// * `file_path`: The file path to the storage object's data.
/// ///
pub fn from_file<T: ToString>(file_name: &str, file_path: T) -> Self { pub fn from_file<T: ToString>(workspace_id: &str, file_name: &str, file_path: T) -> Self {
Self { Self {
workspace_id: workspace_id.to_string(),
file_name: file_name.to_string(), file_name: file_name.to_string(),
value: ObjectValue::File { value: ObjectValue::File {
file_path: file_path.to_string(), file_path: file_path.to_string(),
@ -33,9 +35,15 @@ impl StorageObject {
/// * `bytes`: The byte data of the storage object. /// * `bytes`: The byte data of the storage object.
/// * `mime`: The MIME type of the storage object. /// * `mime`: The MIME type of the storage object.
/// ///
pub fn from_bytes<B: Into<Bytes>>(file_name: &str, bytes: B, mime: String) -> Self { pub fn from_bytes<B: Into<Bytes>>(
workspace_id: &str,
file_name: &str,
bytes: B,
mime: String,
) -> Self {
let bytes = bytes.into(); let bytes = bytes.into();
Self { Self {
workspace_id: workspace_id.to_string(),
file_name: file_name.to_string(), file_name: file_name.to_string(),
value: ObjectValue::Bytes { bytes, mime }, value: ObjectValue::Bytes { bytes, mime },
} }