chore: move client folders (#948)

This commit is contained in:
DarkSky
2023-02-10 20:41:01 +08:00
committed by GitHub
parent cb118149f3
commit 8a7393a961
235 changed files with 114 additions and 215 deletions

View File

@@ -0,0 +1,25 @@
pub mod blob;
pub mod workspace;
pub mod document;
pub mod user;
use blob::*;
use workspace::*;
use document::*;
use user::*;
pub fn invoke_handler() -> impl Fn(tauri::Invoke) + Send + Sync + 'static {
tauri::generate_handler![
update_y_document,
create_workspace,
update_workspace,
get_workspaces,
get_workspace,
create_user,
get_user,
create_doc,
get_doc,
put_blob,
get_blob
]
}

View File

@@ -0,0 +1,68 @@
use bytes::Bytes;
use futures::{
stream::{self},
StreamExt,
};
use ipc_types::blob::{GetBlob, PutBlob};
use jwst::BlobStorage;
use crate::state::AppState;
#[tauri::command]
pub async fn put_blob<'s>(
state: tauri::State<'s, AppState>,
parameters: PutBlob,
) -> Result<String, String> {
let blob_storage = &state.0.lock().await.blob_storage;
if let Ok(path) = blob_storage
.put_blob(
// TODO: ask octobase to accept blob directly or wrap/await tauri command to create a real stream, so we don't need to construct stream manually
parameters.workspace_id,
stream::iter::<Vec<Bytes>>(vec![Bytes::from(parameters.blob)]),
)
.await
{
Ok(path)
} else {
Err("Failed to create".to_string())
}
}
#[tauri::command]
pub async fn get_blob<'s>(
state: tauri::State<'s, AppState>,
parameters: GetBlob,
) -> Result<Vec<u8>, String> {
let GetBlob { workspace_id, id } = parameters;
// TODO: check user permission? Or just assume there will only be one user
let blob_storage = &state.0.lock().await.blob_storage;
if let Ok(mut file_stream) = blob_storage.get_blob(workspace_id.clone(), id.clone()).await {
// Read all of the chunks into a vector.
let mut stream_contents = Vec::new();
let mut error_message = "".to_string();
while let Some(chunk) = file_stream.next().await {
match chunk {
Ok(chunk_bytes) => stream_contents.extend_from_slice(&chunk_bytes),
Err(err) => {
error_message = format!(
"Failed to read blob file {}/{} from stream, error: {}",
workspace_id.clone().unwrap_or_default().to_string(),
id,
err
);
}
}
}
if error_message.len() > 0 {
return Err(error_message);
}
Ok(stream_contents)
} else {
Err(format!(
"Failed to read blob file {}/{} ",
workspace_id.unwrap_or_default().to_string(),
id
))
}
}

View File

@@ -0,0 +1,81 @@
use ipc_types::document::{
CreateDocumentParameter, GetDocumentParameter, GetDocumentResponse, YDocumentUpdate,
};
use jwst::DocStorage;
use jwst::Workspace as OctoBaseWorkspace;
use lib0::any::Any;
use crate::state::AppState;
#[tauri::command]
/// get yDoc created by create_workspace, using same id
pub async fn create_doc<'s>(
state: tauri::State<'s, AppState>,
parameters: CreateDocumentParameter,
) -> Result<(), String> {
let workspace_doc = OctoBaseWorkspace::new(parameters.workspace_id.clone());
workspace_doc.with_trx(|mut workspace_doc_transaction| {
workspace_doc_transaction.set_metadata(
"name",
Any::String(parameters.workspace_name.clone().into_boxed_str()),
);
});
if let Err(error_message) = &state
.0
.lock()
.await
.doc_db
.write_doc(parameters.workspace_id.clone(), workspace_doc.doc())
.await
{
Err(format!(
"Failed to write_doc during create_workspace with error {}",
error_message.to_string()
))
} else {
Ok(())
}
}
#[tauri::command]
/// get yDoc created by create_workspace, using same id
pub async fn get_doc<'s>(
state: tauri::State<'s, AppState>,
parameters: GetDocumentParameter,
) -> Result<GetDocumentResponse, String> {
// TODO: check user permission
let state = &state.0.lock().await;
let doc_db = &state.doc_db;
if let Ok(all_updates_of_workspace) = doc_db.all(&parameters.id).await {
let all_updates = all_updates_of_workspace
.iter()
.map(|model| model.blob.clone())
.collect::<Vec<Vec<u8>>>();
Ok(GetDocumentResponse {
updates: all_updates,
})
} else {
Err(format!(
"Failed to get yDoc from workspace {}",
parameters.id
))
}
}
#[tauri::command]
pub async fn update_y_document<'s>(
state: tauri::State<'s, AppState>,
parameters: YDocumentUpdate,
) -> Result<bool, String> {
let state = &state.0.lock().await;
let doc_db = &state.doc_db;
doc_db
.replace_with(&parameters.id.clone(), parameters.update)
.await
.ok();
Ok(true)
}

View File

@@ -0,0 +1,53 @@
use cloud_database::{CreateUser, User};
use ipc_types::{document::CreateDocumentParameter, user::GetUserParameters};
use crate::state::AppState;
use super::document::create_doc;
#[tauri::command]
/// create new user and a private workspace
pub async fn create_user<'s>(
state: tauri::State<'s, AppState>,
parameters: CreateUser,
) -> Result<User, String> {
let new_user_result = &state
.0
.lock()
.await
.metadata_db
.create_user(parameters.clone())
.await;
match new_user_result {
Ok(new_user_option) => match new_user_option {
Some((new_user, new_workspace)) => {
// a new private workspace is created, we have to create a yDoc for it
create_doc(
state,
CreateDocumentParameter {
workspace_id: new_workspace.id.clone(),
workspace_name: parameters.name.clone(),
},
)
.await
.ok();
Ok(new_user.clone())
}
None => Err("User creation failed".to_string()),
},
Err(error_message) => Err(error_message.to_string()),
}
}
#[tauri::command]
/// get the only one user in local sqlite
pub async fn get_user<'s>(
state: tauri::State<'s, AppState>,
parameters: GetUserParameters,
) -> Result<User, String> {
let db = &state.0.lock().await.metadata_db;
match db.get_user_by_email(&parameters.email).await.ok().unwrap() {
Some(user) => Ok(user),
None => Err("User not found".to_string()),
}
}

View File

@@ -0,0 +1,102 @@
use ipc_types::{
document::CreateDocumentParameter,
workspace::{
CreateWorkspace, CreateWorkspaceResult, GetWorkspace, GetWorkspaceResult, GetWorkspaces,
GetWorkspacesResult, UpdateWorkspace,
},
};
use crate::state::AppState;
use super::document::create_doc;
#[tauri::command]
/// create yDoc for a workspace
pub async fn get_workspaces<'s>(
state: tauri::State<'s, AppState>,
parameters: GetWorkspaces,
) -> Result<GetWorkspacesResult, String> {
match &state
.0
.lock()
.await
.metadata_db
.get_user_workspaces(parameters.user_id.to_string())
.await
{
Ok(user_workspaces) => Ok(GetWorkspacesResult {
workspaces: user_workspaces.clone(),
}),
Err(error_message) => Err(error_message.to_string()),
}
}
#[tauri::command]
/// create yDoc for a workspace
pub async fn get_workspace<'s>(
state: tauri::State<'s, AppState>,
parameters: GetWorkspace,
) -> Result<GetWorkspaceResult, String> {
match &state
.0
.lock()
.await
.metadata_db
.get_workspace_by_id(parameters.id)
.await
{
Ok(user_workspace_option) => match user_workspace_option {
Some(user_workspace) => Ok(GetWorkspaceResult {
workspace: user_workspace.clone(),
}),
None => Err("Get workspace has no result".to_string()),
},
Err(error_message) => Err(error_message.to_string()),
}
}
#[tauri::command]
/// create yDoc for a workspace
pub async fn create_workspace<'s>(
state: tauri::State<'s, AppState>,
parameters: CreateWorkspace,
) -> Result<CreateWorkspaceResult, String> {
let new_workspace_result = &state
.0
.lock()
.await
.metadata_db
.create_normal_workspace(parameters.user_id.to_string())
.await;
match new_workspace_result {
Ok(new_workspace) => {
create_doc(
state,
CreateDocumentParameter {
workspace_id: new_workspace.id.clone(),
workspace_name: parameters.name.clone(),
},
)
.await
.ok();
Ok(CreateWorkspaceResult {
id: new_workspace.id.clone(),
name: parameters.name,
})
}
Err(error_message) => Err(format!(
"Failed to create_workspace with error {}",
error_message.to_string()
)),
}
}
#[tauri::command]
pub async fn update_workspace<'s>(
state: tauri::State<'s, AppState>,
parameters: UpdateWorkspace,
) -> Result<bool, String> {
// TODO: check user permission
// No thing to update now. The avatar is update in YDoc using websocket or yrs.update
Ok(true)
}

View File

@@ -0,0 +1,46 @@
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
mod commands;
mod state;
use dotenvy::dotenv;
use state::AppState;
use std::env;
use tauri::TitleBarStyle;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
tauri::async_runtime::set(tokio::runtime::Handle::current());
dotenv().ok();
let preload = include_str!("../../public/preload/index.js");
let is_dev = env::var("NODE_ENV").unwrap_or_default() == "development";
let initial_path = if is_dev {
"index.html"
} else {
"affine-out/index.html"
};
tauri::Builder::default()
.manage(AppState(Mutex::new(
state::AppStateRaw::new().await.unwrap(),
)))
// manually create window here, instead of in the tauri.conf.json, to add `initialization_script` here
.setup(move |app| {
let _window =
tauri::WindowBuilder::new(app, "label", tauri::WindowUrl::App(initial_path.into()))
.title("AFFiNE")
.inner_size(1000.0, 800.0)
.title_bar_style(TitleBarStyle::Overlay)
.hidden_title(true)
.initialization_script(&preload)
.build()?;
#[cfg(debug_assertions)]
_window.open_devtools();
Ok(())
})
.invoke_handler(commands::invoke_handler())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@@ -0,0 +1,48 @@
use cloud_database::SqliteDBContext;
use jwst::Workspace;
use jwst_storage::{BlobAutoStorage, DocAutoStorage};
use std::{fs, path::Path};
use tauri::api::path::document_dir;
use tokio::sync::Mutex;
pub struct AppStateRaw {
pub doc_db: DocAutoStorage,
pub blob_storage: BlobAutoStorage,
pub metadata_db: SqliteDBContext,
}
impl AppStateRaw {
pub async fn new() -> Option<AppStateRaw> {
let affine_document_path = Path::new(&document_dir()?.into_os_string()).join("affine");
let metadata_db_env = format!(
"sqlite://{}?mode=rwc",
affine_document_path
.join("metadata")
.with_extension("db")
.display()
);
let blob_db_env = format!(
"sqlite://{}?mode=rwc",
affine_document_path
.join("blob")
.with_extension("db")
.display()
);
let doc_db_env = format!(
"sqlite://{}?mode=rwc",
affine_document_path
.join("doc")
.with_extension("db")
.display()
);
fs::create_dir_all(affine_document_path.clone()).unwrap();
Some(Self {
doc_db: DocAutoStorage::init_pool(&doc_db_env).await.unwrap(),
blob_storage: BlobAutoStorage::init_pool(&blob_db_env).await.unwrap(),
metadata_db: SqliteDBContext::new(metadata_db_env).await,
})
}
}
pub struct AppState(pub Mutex<AppStateRaw>); // need pub, otherwise will be "field `0` of struct `types::state::AppState` is private"