Skip to content

Commit

Permalink
Merge pull request #273 from AppFlowy-IO/create_workspace_with_template
Browse files Browse the repository at this point in the history
refactor: template
  • Loading branch information
speed2exe authored Jan 24, 2024
2 parents a318d2f + bb10290 commit 2a7b0ad
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 95 deletions.
55 changes: 55 additions & 0 deletions libs/workspace-template/src/document/get_started.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use crate::document::parser::JsonToDocumentParser;
use crate::hierarchy_builder::WorkspaceViewBuilder;
use crate::{TemplateData, WorkspaceTemplate};
use async_trait::async_trait;
use collab::core::collab::MutexCollab;
use collab::core::origin::CollabOrigin;
use collab_document::document::Document;
use collab_entity::CollabType;
use collab_folder::ViewLayout;
use std::sync::Arc;
use tokio::sync::RwLock;

/// This template generates a document containing a 'read me' guide.
/// It ensures that at least one view is created for the document.
pub struct GetStartedDocumentTemplate;

#[async_trait]
impl WorkspaceTemplate for GetStartedDocumentTemplate {
fn layout(&self) -> ViewLayout {
ViewLayout::Document
}

async fn create_workspace_view(
&self,
_uid: i64,
workspace_view_builder: Arc<RwLock<WorkspaceViewBuilder>>,
) -> anyhow::Result<TemplateData> {
let view_id = workspace_view_builder
.write()
.await
.with_view_builder(|view_builder| async {
view_builder
.with_name("Getting started")
.with_icon("⭐️")
.build()
})
.await;

// create a empty document
let data = tokio::task::spawn_blocking(|| {
let json_str = include_str!("../../assets/read_me.json");
let document_data = JsonToDocumentParser::json_str_to_document(json_str).unwrap();
let collab = Arc::new(MutexCollab::new(CollabOrigin::Empty, &view_id, vec![]));
let document = Document::create_with_data(collab, document_data)?;
let data = document.get_collab().encode_collab_v1();
Ok::<_, anyhow::Error>(TemplateData {
object_id: view_id,
object_type: CollabType::Document,
object_data: data,
})
})
.await??;
Ok(data)
}
}
54 changes: 1 addition & 53 deletions libs/workspace-template/src/document/mod.rs
Original file line number Diff line number Diff line change
@@ -1,54 +1,2 @@
pub mod get_started;
mod parser;

use crate::document::parser::JsonToDocumentParser;
use crate::hierarchy_builder::WorkspaceViewBuilder;
use crate::{TemplateData, WorkspaceTemplate};
use async_trait::async_trait;
use collab::core::collab::MutexCollab;
use collab::core::origin::CollabOrigin;
use collab_document::document::Document;
use collab_entity::CollabType;
use std::sync::Arc;
use tokio::sync::RwLock;

/// A default template for creating documents.
///
/// This template generates a document containing a 'read me' guide.
/// It ensures that at least one view is created for the document.
pub struct DocumentTemplate;

#[async_trait]
impl WorkspaceTemplate for DocumentTemplate {
async fn create_workspace_view(
&self,
_uid: i64,
workspace_view_builder: Arc<RwLock<WorkspaceViewBuilder>>,
) -> anyhow::Result<TemplateData> {
let view_id = workspace_view_builder
.write()
.await
.with_view_builder(|view_builder| async {
view_builder
.with_name("Getting started")
.with_icon("⭐️")
.build()
})
.await;

// create a empty document
let data = tokio::task::spawn_blocking(|| {
let json_str = include_str!("../../assets/read_me.json");
let document_data = JsonToDocumentParser::json_str_to_document(json_str).unwrap();
let collab = Arc::new(MutexCollab::new(CollabOrigin::Empty, &view_id, vec![]));
let document = Document::create_with_data(collab, document_data)?;
let data = document.get_collab().encode_collab_v1();
Ok::<_, anyhow::Error>(TemplateData {
object_id: view_id,
object_type: CollabType::Document,
object_data: data,
})
})
.await??;
Ok(data)
}
}
30 changes: 25 additions & 5 deletions libs/workspace-template/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mod document;
pub mod document;
mod hierarchy_builder;

use crate::hierarchy_builder::{FlattedViews, WorkspaceViewBuilder};
Expand All @@ -17,6 +17,8 @@ use tokio::sync::RwLock;

#[async_trait]
pub trait WorkspaceTemplate {
fn layout(&self) -> ViewLayout;

async fn create_workspace_view(
&self,
uid: i64,
Expand All @@ -32,6 +34,8 @@ pub struct TemplateData {

pub type WorkspaceTemplateHandlers = HashMap<ViewLayout, Arc<dyn WorkspaceTemplate + Send + Sync>>;

/// A builder for creating a workspace template.
/// workspace template is a set of views that are created when a workspace is created.
pub struct WorkspaceTemplateBuilder {
pub uid: i64,
pub workspace_id: String,
Expand All @@ -40,17 +44,33 @@ pub struct WorkspaceTemplateBuilder {

impl WorkspaceTemplateBuilder {
pub fn new(uid: i64, workspace_id: &str) -> Self {
let mut handlers = WorkspaceTemplateHandlers::default();
// register the document template handler
handlers.insert(ViewLayout::Document, Arc::new(document::DocumentTemplate));
let handlers = WorkspaceTemplateHandlers::default();
Self {
uid,
workspace_id: workspace_id.to_string(),
handlers,
}
}

pub async fn default_workspace(&self) -> Result<Vec<TemplateData>> {
pub fn with_template<T>(mut self, template: T) -> Self
where
T: WorkspaceTemplate + Send + Sync + 'static,
{
self.handlers.insert(template.layout(), Arc::new(template));
self
}

pub fn with_templates<T>(mut self, templates: Vec<T>) -> Self
where
T: WorkspaceTemplate + Send + Sync + 'static,
{
for template in templates {
self.handlers.insert(template.layout(), Arc::new(template));
}
self
}

pub async fn build(&self) -> Result<Vec<TemplateData>> {
let workspace_view_builder = Arc::new(RwLock::new(WorkspaceViewBuilder::new(
self.workspace_id.clone(),
self.uid,
Expand Down
100 changes: 63 additions & 37 deletions src/biz/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ use database::user::{create_user, is_user_exist};
use realtime::entities::RealtimeUser;
use shared_entity::dto::auth_dto::UpdateUserParams;
use snowflake::Snowflake;
use sqlx::{types::uuid, PgPool};
use sqlx::{types::uuid, PgPool, Transaction};
use tokio::sync::RwLock;
use tracing::{debug, event, instrument};
use workspace_template::WorkspaceTemplateBuilder;
use workspace_template::document::get_started::GetStartedDocumentTemplate;
use workspace_template::{WorkspaceTemplate, WorkspaceTemplateBuilder};

/// Verify the token from the gotrue server and create the user if it is a new user
/// Return true if the user is a new user
Expand Down Expand Up @@ -86,41 +87,15 @@ where
)
.await?;

// Create the default workspace for the user. A default workspace might contain multiple
// templates, e.g. a document template, a database template, etc.
let templates = WorkspaceTemplateBuilder::new(new_uid, &workspace_id)
.default_workspace()
.await?;

debug!("create {} templates for user:{}", templates.len(), new_uid);
for template in templates {
let object_id = template.object_id;
let encoded_collab_v1 = template
.object_data
.encode_to_bytes()
.map_err(|err| AppError::Internal(anyhow::Error::from(err)))?;

collab_access_control
.cache_collab_access_level(
realtime::collaborate::CollabUserId::UserId(&new_uid),
&object_id,
AFAccessLevel::FullAccess,
)
.await?;

insert_into_af_collab(
&mut txn,
&new_uid,
&workspace_id,
&CollabParams {
object_id,
encoded_collab_v1,
collab_type: template.object_type,
override_if_exist: false,
},
)
.await?;
}
// Create a workspace with the GetStarted template
create_workspace_for_user(
new_uid,
&workspace_id,
collab_access_control,
&mut txn,
vec![GetStartedDocumentTemplate],
)
.await?;
}
txn
.commit()
Expand All @@ -129,6 +104,57 @@ where
Ok(is_new)
}

/// Create a workspace for a user.
/// This function generates a workspace along with its templates and stores them in the database.
/// Each template is stored as an individual collaborative object.
async fn create_workspace_for_user<C, T>(
new_uid: i64,
workspace_id: &str,
collab_access_control: &C,
txn: &mut Transaction<'_, sqlx::Postgres>,
templates: Vec<T>,
) -> Result<(), AppError>
where
C: CollabAccessControl,
T: WorkspaceTemplate + Send + Sync + 'static,
{
let templates = WorkspaceTemplateBuilder::new(new_uid, workspace_id)
.with_templates(templates)
.build()
.await?;

debug!("create {} templates for user:{}", templates.len(), new_uid);
for template in templates {
let object_id = template.object_id;
let encoded_collab_v1 = template
.object_data
.encode_to_bytes()
.map_err(|err| AppError::Internal(anyhow::Error::from(err)))?;

collab_access_control
.cache_collab_access_level(
realtime::collaborate::CollabUserId::UserId(&new_uid),
&object_id,
AFAccessLevel::FullAccess,
)
.await?;

insert_into_af_collab(
txn,
&new_uid,
workspace_id,
&CollabParams {
object_id,
encoded_collab_v1,
collab_type: template.object_type,
override_if_exist: false,
},
)
.await?;
}
Ok(())
}

pub async fn get_profile(pg_pool: &PgPool, uuid: &Uuid) -> Result<AFUserProfile, AppError> {
let row = select_user_profile(pg_pool, uuid)
.await?
Expand Down

0 comments on commit 2a7b0ad

Please sign in to comment.