read belongings

This commit is contained in:
appflowy 2021-08-26 10:19:50 +08:00
parent c8664c8a28
commit 0734e53f07
28 changed files with 193 additions and 121 deletions

View File

@ -1,7 +1,6 @@
use crate::{
config::{
env::{domain, secret, use_https},
get_configuration,
DatabaseSettings,
Settings,
},
@ -16,7 +15,7 @@ use actix::Actor;
use actix_identity::{CookieIdentityPolicy, IdentityService};
use actix_web::{dev::Server, middleware, web, web::Data, App, HttpServer, Scope};
use sqlx::{postgres::PgPoolOptions, PgPool};
use std::{net::TcpListener, sync::Arc};
use std::net::TcpListener;
pub struct Application {
port: u16,

View File

@ -2,7 +2,6 @@ use crate::ws_service::WSServer;
use actix::Addr;
use sqlx::PgPool;
use std::sync::Arc;
pub struct AppContext {
pub ws_server: Addr<WSServer>,

View File

@ -4,7 +4,7 @@ use crate::{
};
use chrono::{Duration, Local};
use derive_more::{From, Into};
use flowy_net::errors::{ErrorCode, ServerError};
use flowy_net::errors::ServerError;
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,8 @@
use chrono::Utc;
use flowy_workspace::entities::{
app::App,
view::{RepeatedView, View, ViewType},
};
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct WorkspaceTable {
@ -24,6 +28,19 @@ pub struct AppTable {
pub(crate) is_trash: bool,
}
impl std::convert::Into<App> for AppTable {
fn into(self) -> App {
App {
id: self.id.to_string(),
workspace_id: self.workspace_id,
name: self.name,
desc: self.description,
belongings: RepeatedView::default(),
version: 0,
}
}
}
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct ViewTable {
pub(crate) id: uuid::Uuid,
@ -36,3 +53,17 @@ pub struct ViewTable {
pub(crate) view_type: i32,
pub(crate) is_trash: bool,
}
impl std::convert::Into<View> for ViewTable {
fn into(self) -> View {
View {
id: self.id.to_string(),
belong_to_id: self.belong_to_id,
name: self.name,
desc: self.description,
view_type: ViewType::from(self.view_type),
version: 0,
belongings: RepeatedView::default(),
}
}
}

View File

@ -1,5 +1,4 @@
use backend::{application::Application, config::get_configuration};
use std::net::TcpListener;
#[actix_web::main]
async fn main() -> std::io::Result<()> {

View File

@ -1,9 +1,6 @@
use crate::config::MAX_PAYLOAD_SIZE;
use actix_web::web;
use flowy_net::{
errors::{ErrorCode, ServerError},
response::*,
};
use flowy_net::errors::{ErrorCode, ServerError};
use futures::StreamExt;
use protobuf::{Message, ProtobufResult};

View File

@ -1,6 +1,6 @@
use flowy_net::errors::ServerError;
use sql_builder::SqlBuilder as InnerBuilder;
use sqlx::{any::AnyArguments, postgres::PgArguments, Arguments, Encode, PgPool, Postgres, Type};
use sqlx::{postgres::PgArguments, Arguments, Encode, PgPool, Postgres, Type};
enum BuilderType {
Create,

View File

@ -14,8 +14,7 @@ use flowy_user::{
prelude::parser::{UserEmail, UserPassword},
protobuf::{SignInParams, SignUpParams},
};
use sqlx::{Error, PgPool, Postgres, Transaction};
use std::sync::Arc;
use sqlx::{PgPool, Postgres, Transaction};
pub async fn sign_in(
pool: &PgPool,

View File

@ -1,18 +1,16 @@
use crate::routers::utils::parse_from_payload;
use actix_web::{
web::{Data, Payload},
Error,
HttpRequest,
HttpResponse,
};
use flowy_net::response::*;
use flowy_user::protobuf::{SignInParams, SignUpParams};
use crate::user_service::{register_user, sign_in};
use actix_identity::Identity;
use flowy_net::errors::ServerError;
use sqlx::PgPool;
use std::sync::Arc;
pub async fn sign_in_handler(
payload: Payload,
@ -30,15 +28,14 @@ pub async fn sign_out_handler(id: Identity) -> Result<HttpResponse, ServerError>
}
pub async fn user_profile(
request: HttpRequest,
payload: Payload,
pool: Data<PgPool>,
_request: HttpRequest,
_payload: Payload,
_pool: Data<PgPool>,
) -> Result<HttpResponse, ServerError> {
unimplemented!()
}
pub async fn register_user_handler(
_request: HttpRequest,
payload: Payload,
pool: Data<PgPool>,
) -> Result<HttpResponse, ServerError> {
@ -49,9 +46,9 @@ pub async fn register_user_handler(
}
pub async fn change_password(
request: HttpRequest,
payload: Payload,
pool: Data<PgPool>,
_request: HttpRequest,
_payload: Payload,
_pool: Data<PgPool>,
) -> Result<HttpResponse, ServerError> {
unimplemented!()
}

View File

@ -1,6 +1,5 @@
use bcrypt::{hash, verify, BcryptError, DEFAULT_COST};
use bcrypt::{hash, verify, DEFAULT_COST};
use flowy_net::errors::{ErrorCode, ServerError};
use jsonwebtoken::Algorithm;
pub fn uuid() -> String { uuid::Uuid::new_v4().to_string() }

View File

@ -3,6 +3,7 @@ use flowy_net::{errors::ServerError, response::FlowyResponse};
use crate::{
entities::workspace::AppTable,
sqlx_ext::{map_sqlx_error, SqlBuilder},
workspace_service::view::read_views_belong_to_id,
};
use anyhow::Context;
use chrono::Utc;
@ -11,8 +12,9 @@ use flowy_user::entities::parser::UserId;
use flowy_workspace::{
entities::{
app::{
parser::{AppColorStyle, AppDesc, AppId, AppName},
parser::{AppDesc, AppId, AppName},
App,
RepeatedApp,
},
view::RepeatedView,
workspace::parser::WorkspaceId,
@ -20,7 +22,7 @@ use flowy_workspace::{
protobuf::{CreateAppParams, QueryAppParams, UpdateAppParams},
};
use protobuf::Message;
use sqlx::{postgres::PgArguments, PgPool, Postgres};
use sqlx::{postgres::PgArguments, PgPool, Postgres, Transaction};
use uuid::Uuid;
pub(crate) async fn create_app(
@ -95,23 +97,18 @@ pub(crate) async fn read_app(
.await
.map_err(map_sqlx_error)?;
let mut views = RepeatedView::default();
if params.read_belongings {
views.items = read_views_belong_to_id(&mut transaction, &table.id.to_string()).await?;
}
transaction
.commit()
.await
.context("Failed to commit SQL transaction to read app.")?;
let mut app = App {
id: table.id.to_string(),
workspace_id: table.workspace_id,
name: table.name,
desc: table.description,
belongings: RepeatedView::default(),
version: 0,
};
if params.read_belongings {
// TODO: read belongings
}
let mut app: App = table.into();
app.belongings = views;
FlowyResponse::success().data(app)
}
@ -208,6 +205,30 @@ pub(crate) async fn delete_app(pool: &PgPool, app_id: &str) -> Result<FlowyRespo
Ok(FlowyResponse::success())
}
// transaction must be commit from caller
pub(crate) async fn read_apps_belong_to_workspace<'c>(
transaction: &mut Transaction<'c, Postgres>,
workspace_id: &str,
) -> Result<Vec<App>, ServerError> {
let workspace_id = WorkspaceId::parse(workspace_id.to_owned()).map_err(invalid_params)?;
let (sql, args) = SqlBuilder::select("app_table")
.add_field("*")
.and_where_eq("workspace_id", workspace_id.0)
.build()?;
let tables = sqlx::query_as_with::<Postgres, AppTable, PgArguments>(&sql, args)
.fetch_all(transaction)
.await
.map_err(map_sqlx_error)?;
let apps = tables
.into_iter()
.map(|table| table.into())
.collect::<Vec<App>>();
Ok(apps)
}
fn check_app_id(id: String) -> Result<Uuid, ServerError> {
let app_id = AppId::parse(id).map_err(invalid_params)?;
let app_id = Uuid::parse_str(app_id.as_ref())?;

View File

@ -2,11 +2,9 @@ use crate::{
routers::utils::parse_from_payload,
workspace_service::app::app::{create_app, delete_app, read_app, update_app},
};
use actix_identity::Identity;
use actix_web::{
web::{Data, Payload},
Error,
HttpRequest,
HttpResponse,
};
use flowy_net::errors::ServerError;

View File

@ -2,11 +2,9 @@ use crate::{
routers::utils::parse_from_payload,
workspace_service::view::{create_view, delete_view, read_view, update_view},
};
use actix_identity::Identity;
use actix_web::{
web::{Data, Payload},
Error,
HttpRequest,
HttpResponse,
};
use flowy_net::errors::ServerError;

View File

@ -15,13 +15,12 @@ use flowy_workspace::{
parser::{ViewDesc, ViewId, ViewName, ViewThumbnail},
RepeatedView,
View,
ViewType,
},
},
protobuf::{CreateViewParams, QueryViewParams, UpdateViewParams},
};
use protobuf::ProtobufEnum;
use sqlx::{postgres::PgArguments, PgPool, Postgres};
use sqlx::{postgres::PgArguments, PgPool, Postgres, Transaction};
use uuid::Uuid;
pub(crate) async fn create_view(
@ -62,12 +61,6 @@ pub(crate) async fn create_view(
.await
.context("Failed to commit SQL transaction to create view.")?;
// a little confused here, different type with the same name in different crate
// let view_type = match params.view_type {
// flowy_workspace::protobuf::ViewType::Blank => ViewType::Doc,
// flowy_workspace::protobuf::ViewType::Doc => ViewType::Doc,
// };
let view = View {
id: uuid.to_string(),
belong_to_id: belong_to_id.as_ref().to_owned(),
@ -101,24 +94,19 @@ pub(crate) async fn read_view(
.await
.map_err(map_sqlx_error)?;
let mut views = RepeatedView::default();
if params.read_belongings {
views.items = read_views_belong_to_id(&mut transaction, &table.id.to_string()).await?;
}
transaction
.commit()
.await
.context("Failed to commit SQL transaction to read view.")?;
let view = View {
id: table.id.to_string(),
belong_to_id: table.belong_to_id,
name: table.name,
desc: table.description,
view_type: ViewType::from(table.view_type),
version: 0,
belongings: RepeatedView::default(),
};
let mut view: View = table.into();
view.belongings = views;
if params.read_belongings {
// TODO: read belongings
}
FlowyResponse::success().data(view)
}
@ -127,20 +115,6 @@ pub(crate) async fn update_view(
params: UpdateViewParams,
) -> Result<FlowyResponse, ServerError> {
let view_id = check_view_id(params.view_id.clone())?;
// #[pb(index = 1)]
// pub view_id: String,
//
// #[pb(index = 2, one_of)]
// pub name: Option<String>,
//
// #[pb(index = 3, one_of)]
// pub desc: Option<String>,
//
// #[pb(index = 4, one_of)]
// pub thumbnail: Option<String>,
//
// #[pb(index = 5, one_of)]
// pub is_trash: Option<bool>,
let name = match params.has_name() {
false => None,
@ -223,6 +197,30 @@ pub(crate) async fn delete_view(
Ok(FlowyResponse::success())
}
// transaction must be commit from caller
pub(crate) async fn read_views_belong_to_id<'c>(
transaction: &mut Transaction<'c, Postgres>,
id: &str,
) -> Result<Vec<View>, ServerError> {
// TODO: add index for app_table
let (sql, args) = SqlBuilder::select("view_table")
.add_field("*")
.and_where_eq("belong_to_id", id)
.build()?;
let tables = sqlx::query_as_with::<Postgres, ViewTable, PgArguments>(&sql, args)
.fetch_all(transaction)
.await
.map_err(map_sqlx_error)?;
let views = tables
.into_iter()
.map(|table| table.into())
.collect::<Vec<View>>();
Ok(views)
}
fn check_view_id(id: String) -> Result<Uuid, ServerError> {
let view_id = ViewId::parse(id).map_err(invalid_params)?;
let view_id = Uuid::parse_str(view_id.as_ref())?;

View File

@ -10,8 +10,6 @@ use crate::{
use actix_identity::Identity;
use actix_web::{
web::{Data, Payload},
Error,
HttpRequest,
HttpResponse,
};
use flowy_net::errors::ServerError;
@ -52,7 +50,7 @@ pub async fn delete_handler(
pub async fn update_handler(
payload: Payload,
id: Identity,
_id: Identity,
pool: Data<PgPool>,
) -> Result<HttpResponse, ServerError> {
let params: UpdateWorkspaceParams = parse_from_payload(payload).await?;

View File

@ -1,4 +1,8 @@
use crate::{entities::workspace::WorkspaceTable, sqlx_ext::*};
use crate::{
entities::workspace::WorkspaceTable,
sqlx_ext::*,
workspace_service::app::app::read_apps_belong_to_workspace,
};
use anyhow::Context;
use chrono::Utc;
use flowy_net::{
@ -21,7 +25,7 @@ use flowy_workspace::{
UpdateWorkspaceParams,
},
};
use sqlx::{postgres::PgArguments, Arguments, PgPool, Postgres};
use sqlx::{postgres::PgArguments, PgPool, Postgres, Transaction};
use uuid::Uuid;
pub(crate) async fn create_workspace(
@ -90,15 +94,18 @@ pub(crate) async fn read_workspace(
.await
.map_err(map_sqlx_error)?;
let mut apps = RepeatedApp { items: vec![] };
if params.read_apps {
apps.items = read_apps_belong_to_workspace(&mut transaction, &table.id.to_string()).await?;
}
transaction
.commit()
.await
.context("Failed to commit SQL transaction to read workspace.")?;
let mut workspace = Workspace::new(table.id.to_string(), table.name, table.description);
if params.get_read_apps() {
workspace.apps = RepeatedApp { items: vec![] }
}
workspace.apps = apps;
FlowyResponse::success().data(workspace)
}

View File

@ -11,7 +11,6 @@ use actix::{
fut,
Actor,
ActorContext,
ActorFuture,
ActorFutureExt,
Addr,
AsyncContext,

View File

@ -46,7 +46,7 @@ impl Handler<Disconnect> for WSServer {
impl Handler<ClientMessage> for WSServer {
type Result = ();
fn handle(&mut self, msg: ClientMessage, _ctx: &mut Context<Self>) -> Self::Result {}
fn handle(&mut self, _msg: ClientMessage, _ctx: &mut Context<Self>) -> Self::Result {}
}
impl actix::Supervised for WSServer {

View File

@ -1,5 +1,5 @@
use crate::helper::{spawn_app, TestApp};
use flowy_user::entities::{SignInParams, SignInResponse, SignUpParams, SignUpResponse};
use flowy_user::entities::{SignInParams, SignUpParams, SignUpResponse};
#[actix_rt::test]
async fn user_register() {

View File

@ -2,7 +2,7 @@ use backend::{
application::{get_connection_pool, Application},
config::{get_configuration, DatabaseSettings},
};
use flowy_net::request::HttpRequestBuilder;
use flowy_user::prelude::*;
use flowy_workspace::prelude::*;
use sqlx::{Connection, Executor, PgConnection, PgPool};

View File

@ -31,6 +31,23 @@ async fn workspace_read() {
log::info!("{:?}", app.read_workspace(read_params).await.unwrap());
}
#[actix_rt::test]
async fn workspace_read_with_belongs() {
let application = spawn_app().await;
let (workspace, user_id) = create_test_workspace(&application).await;
let _ = create_test_app(&application, &workspace.id, &user_id).await;
let _ = create_test_app(&application, &workspace.id, &user_id).await;
let _ = create_test_app(&application, &workspace.id, &user_id).await;
let read_params = QueryWorkspaceParams {
workspace_id: workspace.id.clone(),
read_apps: true,
};
let workspace = application.read_workspace(read_params).await.unwrap();
assert_eq!(workspace.apps.len(), 3);
}
#[actix_rt::test]
async fn workspace_update() {
let app = spawn_app().await;
@ -81,14 +98,16 @@ async fn create_test_workspace(app: &TestApp) -> (Workspace, String) {
#[actix_rt::test]
async fn app_create() {
let application = spawn_app().await;
let app = create_test_app(&application).await;
let (workspace, user_id) = create_test_workspace(&application).await;
let app = create_test_app(&application, &workspace.id, &user_id).await;
log::info!("{:?}", app);
}
#[actix_rt::test]
async fn app_read() {
let application = spawn_app().await;
let app = create_test_app(&application).await;
let (workspace, user_id) = create_test_workspace(&application).await;
let app = create_test_app(&application, &workspace.id, &user_id).await;
let read_params = QueryAppParams {
app_id: app.id,
@ -99,10 +118,30 @@ async fn app_read() {
log::info!("{:?}", application.read_app(read_params).await.unwrap());
}
#[actix_rt::test]
async fn app_read_with_belongs() {
let application = spawn_app().await;
let (workspace, user_id) = create_test_workspace(&application).await;
let app = create_test_app(&application, &workspace.id, &user_id).await;
let _ = create_test_view(&application, &app.id).await;
let _ = create_test_view(&application, &app.id).await;
let read_params = QueryAppParams {
app_id: app.id,
read_belongings: true,
is_trash: false,
};
let app = application.read_app(read_params).await.unwrap();
assert_eq!(app.belongings.len(), 2);
}
#[actix_rt::test]
async fn app_update() {
let application = spawn_app().await;
let app = create_test_app(&application).await;
let (workspace, user_id) = create_test_workspace(&application).await;
let app = create_test_app(&application, &workspace.id, &user_id).await;
let update_params = UpdateAppParams {
app_id: app.id.clone(),
@ -127,7 +166,8 @@ async fn app_update() {
#[actix_rt::test]
async fn app_delete() {
let application = spawn_app().await;
let app = create_test_app(&application).await;
let (workspace, user_id) = create_test_workspace(&application).await;
let app = create_test_app(&application, &workspace.id, &user_id).await;
let delete_params = DeleteAppParams {
app_id: app.id.clone(),
@ -143,15 +183,13 @@ async fn app_delete() {
assert_eq!(application.read_app(read_params).await.is_none(), true);
}
async fn create_test_app(app: &TestApp) -> App {
let (workspace, user_id) = create_test_workspace(&app).await;
async fn create_test_app(app: &TestApp, workspace_id: &str, user_id: &str) -> App {
let params = CreateAppParams {
workspace_id: workspace.id,
workspace_id: workspace_id.to_owned(),
name: "My first app".to_string(),
desc: "This is my first app".to_string(),
color_style: ColorStyle::default(),
user_id,
user_id: user_id.to_string(),
};
let app = app.create_app(params).await;
@ -161,14 +199,19 @@ async fn create_test_app(app: &TestApp) -> App {
#[actix_rt::test]
async fn view_create() {
let application = spawn_app().await;
let view = create_test_view(&application).await;
let (workspace, user_id) = create_test_workspace(&application).await;
let app = create_test_app(&application, &workspace.id, &user_id).await;
let view = create_test_view(&application, &app.id).await;
log::info!("{:?}", view);
}
#[actix_rt::test]
async fn view_update() {
let application = spawn_app().await;
let view = create_test_view(&application).await;
let (workspace, user_id) = create_test_workspace(&application).await;
let app = create_test_app(&application, &workspace.id, &user_id).await;
let view = create_test_view(&application, &app.id).await;
// update
let update_params = UpdateViewParams {
@ -193,7 +236,9 @@ async fn view_update() {
#[actix_rt::test]
async fn view_delete() {
let application = spawn_app().await;
let view = create_test_view(&application).await;
let (workspace, user_id) = create_test_workspace(&application).await;
let app = create_test_app(&application, &workspace.id, &user_id).await;
let view = create_test_view(&application, &app.id).await;
// delete
let delete_params = DeleteViewParams {
@ -210,10 +255,9 @@ async fn view_delete() {
assert_eq!(application.read_view(read_params).await.is_none(), true);
}
async fn create_test_view(application: &TestApp) -> View {
let app = create_test_app(&application).await;
async fn create_test_view(application: &TestApp, app_id: &str) -> View {
let params = CreateViewParams {
belong_to_id: app.id.clone(),
belong_to_id: app_id.to_string(),
name: "My first view".to_string(),
desc: "This is my first view".to_string(),
thumbnail: "http://1.png".to_string(),

View File

@ -2,8 +2,7 @@ use flowy_dispatch::prelude::*;
use std::sync::Once;
#[allow(dead_code)]
pub fn setup_env() {
static INIT: Once = Once::new();
= Once::new();
INIT.call_once(|| {
std::env::set_var("RUST_LOG", "flowy_dispatch=debug,debug");
env_logger::init();

View File

@ -4,7 +4,6 @@ use serde_repr::*;
use std::{fmt, fmt::Debug};
use crate::response::FlowyResponse;
use uuid::Error;
#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone)]
pub struct ServerError {

View File

@ -1,7 +1,4 @@
use crate::{
errors::{ErrorCode, ServerError},
response::FlowyResponse,
};
use crate::{errors::ServerError, response::FlowyResponse};
use bytes::Bytes;
use hyper::http;
use protobuf::ProtobufError;

View File

@ -3,11 +3,7 @@ use crate::{
errors::{ErrorBuilder, UserErrCode, UserError},
};
use flowy_net::{
config::*,
future::ResultFuture,
request::{http_post, HttpRequestBuilder},
};
use flowy_net::{config::*, future::ResultFuture, request::HttpRequestBuilder};
use std::sync::Arc;
pub trait UserServer {

View File

@ -7,7 +7,7 @@ use crate::{
errors::*,
impl_def_and_def_mut,
};
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use std::convert::TryInto;

View File

@ -2,7 +2,6 @@ use crate::{
entities::{app::parser::AppId, view::parser::*},
errors::{ErrorBuilder, WorkspaceError, WsErrCode},
impl_def_and_def_mut,
sql_tables::view::ViewTableType,
};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use std::convert::TryInto;

View File

@ -1,6 +1,5 @@
mod config;
mod dart_event;
mod proto;
mod util;
use clap::{App, Arg};