瀏覽代碼

add user_id in revision

appflowy 3 年之前
父節點
當前提交
45d9a0918f

+ 14 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/lib-ot/model.pb.dart

@@ -22,6 +22,7 @@ class Revision extends $pb.GeneratedMessage {
     ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'md5')
     ..aOS(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'docId')
     ..e<RevType>(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'ty', $pb.PbFieldType.OE, defaultOrMaker: RevType.Local, valueOf: RevType.valueOf, enumValues: RevType.values)
+    ..aOS(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'userId')
     ..hasRequiredFields = false
   ;
 
@@ -33,6 +34,7 @@ class Revision extends $pb.GeneratedMessage {
     $core.String? md5,
     $core.String? docId,
     RevType? ty,
+    $core.String? userId,
   }) {
     final _result = create();
     if (baseRevId != null) {
@@ -53,6 +55,9 @@ class Revision extends $pb.GeneratedMessage {
     if (ty != null) {
       _result.ty = ty;
     }
+    if (userId != null) {
+      _result.userId = userId;
+    }
     return _result;
   }
   factory Revision.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -129,6 +134,15 @@ class Revision extends $pb.GeneratedMessage {
   $core.bool hasTy() => $_has(5);
   @$pb.TagNumber(6)
   void clearTy() => clearField(6);
+
+  @$pb.TagNumber(7)
+  $core.String get userId => $_getSZ(6);
+  @$pb.TagNumber(7)
+  set userId($core.String v) { $_setString(6, v); }
+  @$pb.TagNumber(7)
+  $core.bool hasUserId() => $_has(6);
+  @$pb.TagNumber(7)
+  void clearUserId() => clearField(7);
 }
 
 class RevId extends $pb.GeneratedMessage {

+ 2 - 1
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/lib-ot/model.pbjson.dart

@@ -29,11 +29,12 @@ const Revision$json = const {
     const {'1': 'md5', '3': 4, '4': 1, '5': 9, '10': 'md5'},
     const {'1': 'doc_id', '3': 5, '4': 1, '5': 9, '10': 'docId'},
     const {'1': 'ty', '3': 6, '4': 1, '5': 14, '6': '.RevType', '10': 'ty'},
+    const {'1': 'user_id', '3': 7, '4': 1, '5': 9, '10': 'userId'},
   ],
 };
 
 /// Descriptor for `Revision`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List revisionDescriptor = $convert.base64Decode('CghSZXZpc2lvbhIeCgtiYXNlX3Jldl9pZBgBIAEoA1IJYmFzZVJldklkEhUKBnJldl9pZBgCIAEoA1IFcmV2SWQSHQoKZGVsdGFfZGF0YRgDIAEoDFIJZGVsdGFEYXRhEhAKA21kNRgEIAEoCVIDbWQ1EhUKBmRvY19pZBgFIAEoCVIFZG9jSWQSGAoCdHkYBiABKA4yCC5SZXZUeXBlUgJ0eQ==');
+final $typed_data.Uint8List revisionDescriptor = $convert.base64Decode('CghSZXZpc2lvbhIeCgtiYXNlX3Jldl9pZBgBIAEoA1IJYmFzZVJldklkEhUKBnJldl9pZBgCIAEoA1IFcmV2SWQSHQoKZGVsdGFfZGF0YRgDIAEoDFIJZGVsdGFEYXRhEhAKA21kNRgEIAEoCVIDbWQ1EhUKBmRvY19pZBgFIAEoCVIFZG9jSWQSGAoCdHkYBiABKA4yCC5SZXZUeXBlUgJ0eRIXCgd1c2VyX2lkGAcgASgJUgZ1c2VySWQ=');
 @$core.Deprecated('Use revIdDescriptor instead')
 const RevId$json = const {
   '1': 'RevId',

+ 3 - 2
frontend/rust-lib/flowy-document/src/services/doc/controller.rs

@@ -112,12 +112,13 @@ impl DocController {
         // let doc = self.read_doc(doc_id, pool.clone()).await?;
         let ws_sender = self.ws_manager.ws();
         let token = self.user.token()?;
+        let user_id = self.user.user_id()?;
         let server = Arc::new(RevisionServerImpl {
             token,
             server: self.server.clone(),
         });
-        let cache = Arc::new(RevisionCache::new(doc_id, pool, server));
-        Ok(RevisionManager::new(doc_id, cache, ws_sender))
+        let cache = Arc::new(RevisionCache::new(&user_id, doc_id, pool, server));
+        Ok(RevisionManager::new(&user_id, doc_id, cache, ws_sender))
     }
 }
 

+ 6 - 1
frontend/rust-lib/flowy-document/src/services/doc/edit/editor.rs

@@ -163,7 +163,8 @@ impl ClientDocEditor {
         let delta_data = delta.to_bytes();
         let (base_rev_id, rev_id) = self.rev_manager.next_rev_id();
         let delta_data = delta_data.to_vec();
-        let revision = Revision::new(base_rev_id, rev_id, delta_data, &self.doc_id, RevType::Local);
+        let user_id = self.user.user_id()?;
+        let revision = Revision::new(base_rev_id, rev_id, delta_data, &self.doc_id, RevType::Local, user_id);
         let _ = self.rev_manager.add_revision(&revision).await?;
         Ok(rev_id.into())
     }
@@ -236,22 +237,26 @@ impl ClientDocEditor {
         let (local_base_rev_id, local_rev_id) = self.rev_manager.next_rev_id();
 
         // save the revision
+        let user_id = self.user.user_id()?;
         let revision = Revision::new(
             local_base_rev_id,
             local_rev_id,
             client_prime.to_bytes().to_vec(),
             &self.doc_id,
             RevType::Remote,
+            user_id,
         );
         let _ = self.rev_manager.add_revision(&revision).await?;
 
         // send the server_prime delta
+        let user_id = self.user.user_id()?;
         let revision = Revision::new(
             local_base_rev_id,
             local_rev_id,
             server_prime.to_bytes().to_vec(),
             &self.doc_id,
             RevType::Remote,
+            user_id,
         );
         let _ = self.ws_sender.send(revision.into());
         Ok(())

+ 20 - 6
frontend/rust-lib/flowy-document/src/services/doc/revision/cache.rs

@@ -24,6 +24,7 @@ pub trait RevisionIterator: Send + Sync {
 type DocRevisionDeskCache = dyn RevisionDiskCache<Error = DocError>;
 
 pub struct RevisionCache {
+    user_id: String,
     doc_id: String,
     dish_cache: Arc<DocRevisionDeskCache>,
     memory_cache: Arc<RevisionMemoryCache>,
@@ -32,11 +33,17 @@ pub struct RevisionCache {
 }
 
 impl RevisionCache {
-    pub fn new(doc_id: &str, pool: Arc<ConnectionPool>, server: Arc<dyn RevisionServer>) -> RevisionCache {
+    pub fn new(
+        user_id: &str,
+        doc_id: &str,
+        pool: Arc<ConnectionPool>,
+        server: Arc<dyn RevisionServer>,
+    ) -> RevisionCache {
         let doc_id = doc_id.to_owned();
-        let dish_cache = Arc::new(Persistence::new(pool));
+        let dish_cache = Arc::new(Persistence::new(user_id, pool));
         let memory_cache = Arc::new(RevisionMemoryCache::new());
         Self {
+            user_id: user_id.to_owned(),
             doc_id,
             dish_cache,
             memory_cache,
@@ -117,6 +124,7 @@ impl RevisionCache {
             delta_data.to_owned(),
             &doc.id,
             RevType::Remote,
+            self.user_id.clone(),
         );
         let record = RevisionRecord {
             revision,
@@ -215,6 +223,7 @@ fn correct_delta_if_need(delta: &mut RichTextDelta) {
 }
 
 pub(crate) struct Persistence {
+    user_id: String,
     pub(crate) pool: Arc<ConnectionPool>,
 }
 
@@ -231,25 +240,30 @@ impl RevisionDiskCache for Persistence {
 
     fn revisions_in_range(&self, doc_id: &str, range: &RevisionRange) -> Result<Vec<Revision>, Self::Error> {
         let conn = &*self.pool.get().map_err(internal_error).unwrap();
-        let revisions = RevTableSql::read_rev_tables_with_range(doc_id, range.clone(), conn)?;
+        let revisions = RevTableSql::read_rev_tables_with_range(&self.user_id, doc_id, range.clone(), conn)?;
         Ok(revisions)
     }
 
     fn read_revision(&self, doc_id: &str, rev_id: i64) -> Result<Option<Revision>, Self::Error> {
         let conn = self.pool.get().map_err(internal_error)?;
-        let some = RevTableSql::read_rev_table(doc_id, &rev_id, &*conn)?;
+        let some = RevTableSql::read_rev_table(&self.user_id, doc_id, &rev_id, &*conn)?;
         Ok(some)
     }
 
     fn read_revisions(&self, doc_id: &str) -> Result<Vec<Revision>, Self::Error> {
         let conn = self.pool.get().map_err(internal_error)?;
-        let some = RevTableSql::read_rev_tables(doc_id, &*conn)?;
+        let some = RevTableSql::read_rev_tables(&self.user_id, doc_id, &*conn)?;
         Ok(some)
     }
 }
 
 impl Persistence {
-    pub(crate) fn new(pool: Arc<ConnectionPool>) -> Self { Self { pool } }
+    pub(crate) fn new(user_id: &str, pool: Arc<ConnectionPool>) -> Self {
+        Self {
+            user_id: user_id.to_owned(),
+            pool,
+        }
+    }
 }
 
 #[cfg(feature = "flowy_unit_test")]

+ 4 - 1
frontend/rust-lib/flowy-document/src/services/doc/revision/manager.rs

@@ -20,16 +20,18 @@ pub trait RevisionServer: Send + Sync {
 
 pub struct RevisionManager {
     doc_id: String,
+    user_id: String,
     rev_id_counter: RevIdCounter,
     cache: Arc<RevisionCache>,
     ws_sender: Arc<dyn DocumentWebSocket>,
 }
 
 impl RevisionManager {
-    pub fn new(doc_id: &str, cache: Arc<RevisionCache>, ws_sender: Arc<dyn DocumentWebSocket>) -> Self {
+    pub fn new(user_id: &str, doc_id: &str, cache: Arc<RevisionCache>, ws_sender: Arc<dyn DocumentWebSocket>) -> Self {
         let rev_id_counter = RevIdCounter::new(0);
         Self {
             doc_id: doc_id.to_string(),
+            user_id: user_id.to_owned(),
             rev_id_counter,
             cache,
             ws_sender,
@@ -83,6 +85,7 @@ impl RevisionManager {
             delta_data.to_vec(),
             &self.doc_id,
             RevType::Remote,
+            self.user_id.clone(),
         );
 
         Ok(revision)

+ 13 - 7
frontend/rust-lib/flowy-document/src/sql_tables/doc/rev_sql.rs

@@ -1,6 +1,6 @@
 use crate::{
     errors::DocError,
-    sql_tables::{doc::RevTable, RevChangeset, RevTableState, RevTableType},
+    sql_tables::{doc::RevTable, mk_revision_from_table, RevChangeset, RevTableState, RevTableType},
 };
 use diesel::update;
 use flowy_database::{insert_or_ignore_into, prelude::*, schema::rev_table::dsl, SqliteConnection};
@@ -41,7 +41,11 @@ impl RevTableSql {
         Ok(())
     }
 
-    pub(crate) fn read_rev_tables(doc_id: &str, conn: &SqliteConnection) -> Result<Vec<Revision>, DocError> {
+    pub(crate) fn read_rev_tables(
+        user_id: &str,
+        doc_id: &str,
+        conn: &SqliteConnection,
+    ) -> Result<Vec<Revision>, DocError> {
         let filter = dsl::rev_table
             .filter(dsl::doc_id.eq(doc_id))
             .order(dsl::rev_id.asc())
@@ -49,12 +53,13 @@ impl RevTableSql {
         let rev_tables = filter.load::<RevTable>(conn)?;
         let revisions = rev_tables
             .into_iter()
-            .map(|table| table.into())
+            .map(|table| mk_revision_from_table(user_id, table))
             .collect::<Vec<Revision>>();
         Ok(revisions)
     }
 
     pub(crate) fn read_rev_table(
+        user_id: &str,
         doc_id: &str,
         revision_id: &i64,
         conn: &SqliteConnection,
@@ -67,25 +72,26 @@ impl RevTableSql {
         if Err(diesel::NotFound) == result {
             Ok(None)
         } else {
-            Ok(Some(result?.into()))
+            Ok(Some(mk_revision_from_table(user_id, result?)))
         }
     }
 
     pub(crate) fn read_rev_tables_with_range(
-        doc_id_s: &str,
+        user_id: &str,
+        doc_id: &str,
         range: RevisionRange,
         conn: &SqliteConnection,
     ) -> Result<Vec<Revision>, DocError> {
         let rev_tables = dsl::rev_table
             .filter(dsl::rev_id.ge(range.start))
             .filter(dsl::rev_id.le(range.end))
-            .filter(dsl::doc_id.eq(doc_id_s))
+            .filter(dsl::doc_id.eq(doc_id))
             .order(dsl::rev_id.asc())
             .load::<RevTable>(conn)?;
 
         let revisions = rev_tables
             .into_iter()
-            .map(|table| table.into())
+            .map(|table| mk_revision_from_table(user_id, table))
             .collect::<Vec<Revision>>();
         Ok(revisions)
     }

+ 11 - 11
frontend/rust-lib/flowy-document/src/sql_tables/doc/rev_table.rs

@@ -1,5 +1,6 @@
 use diesel::sql_types::Integer;
 use flowy_database::schema::rev_table;
+
 use flowy_document_infra::util::md5;
 use lib_ot::revision::{RevId, RevState, RevType, Revision};
 
@@ -63,17 +64,16 @@ impl std::convert::From<RevState> for RevTableState {
     }
 }
 
-impl std::convert::From<RevTable> for Revision {
-    fn from(table: RevTable) -> Self {
-        let md5 = md5(&table.data);
-        Revision {
-            base_rev_id: table.base_rev_id,
-            rev_id: table.rev_id,
-            delta_data: table.data,
-            md5,
-            doc_id: table.doc_id,
-            ty: table.ty.into(),
-        }
+pub(crate) fn mk_revision_from_table(user_id: &str, table: RevTable) -> Revision {
+    let md5 = md5(&table.data);
+    Revision {
+        base_rev_id: table.base_rev_id,
+        rev_id: table.rev_id,
+        delta_data: table.data,
+        md5,
+        doc_id: table.doc_id,
+        ty: table.ty.into(),
+        user_id: user_id.to_owned(),
     }
 }
 

+ 1 - 1
frontend/rust-lib/flowy-document/tests/editor/revision_test.rs

@@ -1,5 +1,5 @@
 use flowy_test::editor::{EditorScript::*, *};
-use lib_ot::{core::DeltaBuilder, revision::RevState, rich_text::RichTextDeltaBuilder};
+use lib_ot::{revision::RevState, rich_text::RichTextDeltaBuilder};
 
 #[tokio::test]
 async fn doc_rev_state_test1() {

+ 4 - 1
frontend/rust-lib/flowy-test/src/editor.rs

@@ -9,7 +9,7 @@ use lib_ot::{
     revision::{RevState, RevType, Revision, RevisionRange},
     rich_text::RichTextDelta,
 };
-use std::{str::FromStr, sync::Arc};
+use std::sync::Arc;
 use tokio::time::{sleep, Duration};
 
 pub enum EditorScript {
@@ -56,10 +56,12 @@ impl EditorTest {
         let _memory_cache = cache.memory_cache();
         let _disk_cache = cache.dish_cache();
         let doc_id = self.editor.doc_id.clone();
+        let user_id = self.sdk.user_session.user_id().unwrap();
 
         match script {
             EditorScript::InsertText(s, offset) => {
                 self.editor.insert(offset, s).await.unwrap();
+                sleep(Duration::from_millis(200)).await;
             },
             EditorScript::Delete(interval) => {
                 self.editor.delete(interval).await.unwrap();
@@ -98,6 +100,7 @@ impl EditorTest {
                     delta.to_bytes().to_vec(),
                     &doc_id,
                     RevType::Remote,
+                    user_id,
                 );
                 let data = WsDocumentDataBuilder::build_push_rev_message(&doc_id, revision);
                 self.send_ws_message(data).await;

+ 2 - 0
frontend/rust-lib/flowy-user/src/services/user/user_session.rs

@@ -184,6 +184,8 @@ impl UserSession {
 
     pub fn user_id(&self) -> Result<String, UserError> { Ok(self.get_session()?.user_id) }
 
+    pub fn user_name(&self) -> Result<String, UserError> { Ok(self.get_session()?.name) }
+
     pub fn token(&self) -> Result<String, UserError> { Ok(self.get_session()?.token) }
 
     pub fn add_ws_handler(&self, handler: Arc<dyn WsMessageHandler>) { let _ = self.ws_manager.add_handler(handler); }

+ 7 - 1
shared-lib/flowy-derive/src/proto_buf/deserialize.rs

@@ -47,7 +47,13 @@ pub fn make_de_token_steam(ctxt: &Ctxt, ast: &ASTContainer) -> Option<TokenStrea
 fn token_stream_for_one_of(ctxt: &Ctxt, field: &ASTField) -> Option<TokenStream> {
     let member = &field.member;
     let ident = get_member_ident(ctxt, member)?;
-    let ty_info = parse_ty(ctxt, &field.ty)?;
+    let ty_info = match parse_ty(ctxt, &field.ty) {
+        Ok(ty_info) => ty_info,
+        Err(e) => {
+            eprintln!("token_stream_for_one_of failed: {:?} with error: {}", member, e);
+            panic!();
+        }
+    }?;
     let bracketed_ty_info = ty_info.bracket_ty_info.as_ref().as_ref();
 
     let has_func = format_ident!("has_{}", ident.to_string());

+ 1 - 1
shared-lib/flowy-document-infra/src/core/document.rs

@@ -109,7 +109,7 @@ impl Document {
 
         let text = data.to_string();
         let delta = self.view.insert(&self.delta, &text, interval)?;
-        tracing::trace!("👉 receive change: {}", delta);
+        tracing::debug!("👉 receive change: {}", delta);
         self.compose_delta(delta.clone())?;
         Ok(delta)
     }

+ 1 - 1
shared-lib/flowy-document-infra/src/core/view.rs

@@ -32,7 +32,7 @@ impl View {
         for ext in &self.insert_exts {
             if let Some(mut delta) = ext.apply(delta, interval.size(), text, interval.start) {
                 trim(&mut delta);
-                tracing::trace!("[{}]: applied, delta: {}", ext.ext_name(), delta);
+                tracing::debug!("[{}]: applied, delta: {}", ext.ext_name(), delta);
                 new_delta = Some(delta);
                 break;
             }

+ 0 - 11
shared-lib/flowy-document-infra/src/entities/ws/ws.rs

@@ -29,15 +29,6 @@ impl std::default::Default for WsDataType {
     fn default() -> Self { WsDataType::Acked }
 }
 
-// #[derive(ProtoBuf, Default, Debug, Clone)]
-// pub struct WsDocumentUser {
-//     #[pb(index = 1)]
-//     pub user_id: String,
-//
-//     #[pb(index = 2)]
-//     pub name: String,
-// }
-
 #[derive(ProtoBuf, Default, Debug, Clone)]
 pub struct WsDocumentData {
     #[pb(index = 1)]
@@ -48,8 +39,6 @@ pub struct WsDocumentData {
 
     #[pb(index = 3)]
     pub data: Vec<u8>,
-    /* #[pb(index = 4)]
-     * pub user: WsDocumentUser, */
 }
 
 impl std::convert::From<Revision> for WsDocumentData {

+ 2 - 2
shared-lib/flowy-user-infra/src/entities/auth.rs

@@ -28,7 +28,7 @@ pub struct SignInParams {
     pub name: String,
 }
 
-#[derive(Debug, Default, ProtoBuf)]
+#[derive(Debug, Default, ProtoBuf, Clone)]
 pub struct SignInResponse {
     #[pb(index = 1)]
     pub user_id: String,
@@ -97,7 +97,7 @@ pub struct SignUpParams {
     pub password: String,
 }
 
-#[derive(ProtoBuf, Debug, Default)]
+#[derive(ProtoBuf, Debug, Default, Clone)]
 pub struct SignUpResponse {
     #[pb(index = 1)]
     pub user_id: String,

+ 89 - 43
shared-lib/lib-ot/src/protobuf/model/model.rs

@@ -32,6 +32,7 @@ pub struct Revision {
     pub md5: ::std::string::String,
     pub doc_id: ::std::string::String,
     pub ty: RevType,
+    pub user_id: ::std::string::String,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -170,6 +171,32 @@ impl Revision {
     pub fn set_ty(&mut self, v: RevType) {
         self.ty = v;
     }
+
+    // string user_id = 7;
+
+
+    pub fn get_user_id(&self) -> &str {
+        &self.user_id
+    }
+    pub fn clear_user_id(&mut self) {
+        self.user_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_user_id(&mut self, v: ::std::string::String) {
+        self.user_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_user_id(&mut self) -> &mut ::std::string::String {
+        &mut self.user_id
+    }
+
+    // Take field
+    pub fn take_user_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.user_id, ::std::string::String::new())
+    }
 }
 
 impl ::protobuf::Message for Revision {
@@ -207,6 +234,9 @@ impl ::protobuf::Message for Revision {
                 6 => {
                     ::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.ty, 6, &mut self.unknown_fields)?
                 },
+                7 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.user_id)?;
+                },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
                 },
@@ -237,6 +267,9 @@ impl ::protobuf::Message for Revision {
         if self.ty != RevType::Local {
             my_size += ::protobuf::rt::enum_size(6, self.ty);
         }
+        if !self.user_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(7, &self.user_id);
+        }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
         my_size
@@ -261,6 +294,9 @@ impl ::protobuf::Message for Revision {
         if self.ty != RevType::Local {
             os.write_enum(6, ::protobuf::ProtobufEnum::value(&self.ty))?;
         }
+        if !self.user_id.is_empty() {
+            os.write_string(7, &self.user_id)?;
+        }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
     }
@@ -329,6 +365,11 @@ impl ::protobuf::Message for Revision {
                 |m: &Revision| { &m.ty },
                 |m: &mut Revision| { &mut m.ty },
             ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "user_id",
+                |m: &Revision| { &m.user_id },
+                |m: &mut Revision| { &mut m.user_id },
+            ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<Revision>(
                 "Revision",
                 fields,
@@ -351,6 +392,7 @@ impl ::protobuf::Clear for Revision {
         self.md5.clear();
         self.doc_id.clear();
         self.ty = RevType::Local;
+        self.user_id.clear();
         self.unknown_fields.clear();
     }
 }
@@ -799,52 +841,56 @@ impl ::protobuf::reflect::ProtobufValue for RevType {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0bmodel.proto\"\xa3\x01\n\x08Revision\x12\x1e\n\x0bbase_rev_id\x18\
+    \n\x0bmodel.proto\"\xbc\x01\n\x08Revision\x12\x1e\n\x0bbase_rev_id\x18\
     \x01\x20\x01(\x03R\tbaseRevId\x12\x15\n\x06rev_id\x18\x02\x20\x01(\x03R\
     \x05revId\x12\x1d\n\ndelta_data\x18\x03\x20\x01(\x0cR\tdeltaData\x12\x10\
     \n\x03md5\x18\x04\x20\x01(\tR\x03md5\x12\x15\n\x06doc_id\x18\x05\x20\x01\
-    (\tR\x05docId\x12\x18\n\x02ty\x18\x06\x20\x01(\x0e2\x08.RevTypeR\x02ty\"\
-    \x1d\n\x05RevId\x12\x14\n\x05value\x18\x01\x20\x01(\x03R\x05value\"N\n\r\
-    RevisionRange\x12\x15\n\x06doc_id\x18\x01\x20\x01(\tR\x05docId\x12\x14\n\
-    \x05start\x18\x02\x20\x01(\x03R\x05start\x12\x10\n\x03end\x18\x03\x20\
-    \x01(\x03R\x03end*\x20\n\x07RevType\x12\t\n\x05Local\x10\0\x12\n\n\x06Re\
-    mote\x10\x01J\xea\x05\n\x06\x12\x04\0\0\x15\x01\n\x08\n\x01\x0c\x12\x03\
-    \0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\t\x01\n\n\n\x03\x04\0\x01\x12\x03\
-    \x02\x08\x10\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x1a\n\x0c\n\x05\x04\
-    \0\x02\0\x05\x12\x03\x03\x04\t\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\n\
-    \x15\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x18\x19\n\x0b\n\x04\x04\0\
-    \x02\x01\x12\x03\x04\x04\x15\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\
-    \x04\t\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\n\x10\n\x0c\n\x05\x04\0\
-    \x02\x01\x03\x12\x03\x04\x13\x14\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\
-    \x04\x19\n\x0c\n\x05\x04\0\x02\x02\x05\x12\x03\x05\x04\t\n\x0c\n\x05\x04\
-    \0\x02\x02\x01\x12\x03\x05\n\x14\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\
-    \x05\x17\x18\n\x0b\n\x04\x04\0\x02\x03\x12\x03\x06\x04\x13\n\x0c\n\x05\
-    \x04\0\x02\x03\x05\x12\x03\x06\x04\n\n\x0c\n\x05\x04\0\x02\x03\x01\x12\
-    \x03\x06\x0b\x0e\n\x0c\n\x05\x04\0\x02\x03\x03\x12\x03\x06\x11\x12\n\x0b\
-    \n\x04\x04\0\x02\x04\x12\x03\x07\x04\x16\n\x0c\n\x05\x04\0\x02\x04\x05\
-    \x12\x03\x07\x04\n\n\x0c\n\x05\x04\0\x02\x04\x01\x12\x03\x07\x0b\x11\n\
-    \x0c\n\x05\x04\0\x02\x04\x03\x12\x03\x07\x14\x15\n\x0b\n\x04\x04\0\x02\
-    \x05\x12\x03\x08\x04\x13\n\x0c\n\x05\x04\0\x02\x05\x06\x12\x03\x08\x04\
-    \x0b\n\x0c\n\x05\x04\0\x02\x05\x01\x12\x03\x08\x0c\x0e\n\x0c\n\x05\x04\0\
-    \x02\x05\x03\x12\x03\x08\x11\x12\n\n\n\x02\x04\x01\x12\x04\n\0\x0c\x01\n\
-    \n\n\x03\x04\x01\x01\x12\x03\n\x08\r\n\x0b\n\x04\x04\x01\x02\0\x12\x03\
-    \x0b\x04\x14\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x0b\x04\t\n\x0c\n\x05\
-    \x04\x01\x02\0\x01\x12\x03\x0b\n\x0f\n\x0c\n\x05\x04\x01\x02\0\x03\x12\
-    \x03\x0b\x12\x13\n\n\n\x02\x04\x02\x12\x04\r\0\x11\x01\n\n\n\x03\x04\x02\
-    \x01\x12\x03\r\x08\x15\n\x0b\n\x04\x04\x02\x02\0\x12\x03\x0e\x04\x16\n\
-    \x0c\n\x05\x04\x02\x02\0\x05\x12\x03\x0e\x04\n\n\x0c\n\x05\x04\x02\x02\0\
-    \x01\x12\x03\x0e\x0b\x11\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x0e\x14\
-    \x15\n\x0b\n\x04\x04\x02\x02\x01\x12\x03\x0f\x04\x14\n\x0c\n\x05\x04\x02\
-    \x02\x01\x05\x12\x03\x0f\x04\t\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\
-    \x0f\n\x0f\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03\x0f\x12\x13\n\x0b\n\
-    \x04\x04\x02\x02\x02\x12\x03\x10\x04\x12\n\x0c\n\x05\x04\x02\x02\x02\x05\
-    \x12\x03\x10\x04\t\n\x0c\n\x05\x04\x02\x02\x02\x01\x12\x03\x10\n\r\n\x0c\
-    \n\x05\x04\x02\x02\x02\x03\x12\x03\x10\x10\x11\n\n\n\x02\x05\0\x12\x04\
-    \x12\0\x15\x01\n\n\n\x03\x05\0\x01\x12\x03\x12\x05\x0c\n\x0b\n\x04\x05\0\
-    \x02\0\x12\x03\x13\x04\x0e\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x13\x04\t\
-    \n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x13\x0c\r\n\x0b\n\x04\x05\0\x02\x01\
-    \x12\x03\x14\x04\x0f\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x14\x04\n\n\
-    \x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x14\r\x0eb\x06proto3\
+    (\tR\x05docId\x12\x18\n\x02ty\x18\x06\x20\x01(\x0e2\x08.RevTypeR\x02ty\
+    \x12\x17\n\x07user_id\x18\x07\x20\x01(\tR\x06userId\"\x1d\n\x05RevId\x12\
+    \x14\n\x05value\x18\x01\x20\x01(\x03R\x05value\"N\n\rRevisionRange\x12\
+    \x15\n\x06doc_id\x18\x01\x20\x01(\tR\x05docId\x12\x14\n\x05start\x18\x02\
+    \x20\x01(\x03R\x05start\x12\x10\n\x03end\x18\x03\x20\x01(\x03R\x03end*\
+    \x20\n\x07RevType\x12\t\n\x05Local\x10\0\x12\n\n\x06Remote\x10\x01J\xa1\
+    \x06\n\x06\x12\x04\0\0\x16\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\
+    \x04\0\x12\x04\x02\0\n\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x10\n\x0b\
+    \n\x04\x04\0\x02\0\x12\x03\x03\x04\x1a\n\x0c\n\x05\x04\0\x02\0\x05\x12\
+    \x03\x03\x04\t\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\n\x15\n\x0c\n\x05\
+    \x04\0\x02\0\x03\x12\x03\x03\x18\x19\n\x0b\n\x04\x04\0\x02\x01\x12\x03\
+    \x04\x04\x15\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\t\n\x0c\n\x05\
+    \x04\0\x02\x01\x01\x12\x03\x04\n\x10\n\x0c\n\x05\x04\0\x02\x01\x03\x12\
+    \x03\x04\x13\x14\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\x04\x19\n\x0c\n\
+    \x05\x04\0\x02\x02\x05\x12\x03\x05\x04\t\n\x0c\n\x05\x04\0\x02\x02\x01\
+    \x12\x03\x05\n\x14\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x05\x17\x18\n\
+    \x0b\n\x04\x04\0\x02\x03\x12\x03\x06\x04\x13\n\x0c\n\x05\x04\0\x02\x03\
+    \x05\x12\x03\x06\x04\n\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06\x0b\x0e\
+    \n\x0c\n\x05\x04\0\x02\x03\x03\x12\x03\x06\x11\x12\n\x0b\n\x04\x04\0\x02\
+    \x04\x12\x03\x07\x04\x16\n\x0c\n\x05\x04\0\x02\x04\x05\x12\x03\x07\x04\n\
+    \n\x0c\n\x05\x04\0\x02\x04\x01\x12\x03\x07\x0b\x11\n\x0c\n\x05\x04\0\x02\
+    \x04\x03\x12\x03\x07\x14\x15\n\x0b\n\x04\x04\0\x02\x05\x12\x03\x08\x04\
+    \x13\n\x0c\n\x05\x04\0\x02\x05\x06\x12\x03\x08\x04\x0b\n\x0c\n\x05\x04\0\
+    \x02\x05\x01\x12\x03\x08\x0c\x0e\n\x0c\n\x05\x04\0\x02\x05\x03\x12\x03\
+    \x08\x11\x12\n\x0b\n\x04\x04\0\x02\x06\x12\x03\t\x04\x17\n\x0c\n\x05\x04\
+    \0\x02\x06\x05\x12\x03\t\x04\n\n\x0c\n\x05\x04\0\x02\x06\x01\x12\x03\t\
+    \x0b\x12\n\x0c\n\x05\x04\0\x02\x06\x03\x12\x03\t\x15\x16\n\n\n\x02\x04\
+    \x01\x12\x04\x0b\0\r\x01\n\n\n\x03\x04\x01\x01\x12\x03\x0b\x08\r\n\x0b\n\
+    \x04\x04\x01\x02\0\x12\x03\x0c\x04\x14\n\x0c\n\x05\x04\x01\x02\0\x05\x12\
+    \x03\x0c\x04\t\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x0c\n\x0f\n\x0c\n\
+    \x05\x04\x01\x02\0\x03\x12\x03\x0c\x12\x13\n\n\n\x02\x04\x02\x12\x04\x0e\
+    \0\x12\x01\n\n\n\x03\x04\x02\x01\x12\x03\x0e\x08\x15\n\x0b\n\x04\x04\x02\
+    \x02\0\x12\x03\x0f\x04\x16\n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\x0f\x04\
+    \n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\x0f\x0b\x11\n\x0c\n\x05\x04\x02\
+    \x02\0\x03\x12\x03\x0f\x14\x15\n\x0b\n\x04\x04\x02\x02\x01\x12\x03\x10\
+    \x04\x14\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\x10\x04\t\n\x0c\n\x05\
+    \x04\x02\x02\x01\x01\x12\x03\x10\n\x0f\n\x0c\n\x05\x04\x02\x02\x01\x03\
+    \x12\x03\x10\x12\x13\n\x0b\n\x04\x04\x02\x02\x02\x12\x03\x11\x04\x12\n\
+    \x0c\n\x05\x04\x02\x02\x02\x05\x12\x03\x11\x04\t\n\x0c\n\x05\x04\x02\x02\
+    \x02\x01\x12\x03\x11\n\r\n\x0c\n\x05\x04\x02\x02\x02\x03\x12\x03\x11\x10\
+    \x11\n\n\n\x02\x05\0\x12\x04\x13\0\x16\x01\n\n\n\x03\x05\0\x01\x12\x03\
+    \x13\x05\x0c\n\x0b\n\x04\x05\0\x02\0\x12\x03\x14\x04\x0e\n\x0c\n\x05\x05\
+    \0\x02\0\x01\x12\x03\x14\x04\t\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x14\
+    \x0c\r\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x15\x04\x0f\n\x0c\n\x05\x05\0\
+    \x02\x01\x01\x12\x03\x15\x04\n\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x15\
+    \r\x0eb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 1 - 0
shared-lib/lib-ot/src/protobuf/proto/model.proto

@@ -7,6 +7,7 @@ message Revision {
     string md5 = 4;
     string doc_id = 5;
     RevType ty = 6;
+    string user_id = 7;
 }
 message RevId {
     int64 value = 1;

+ 5 - 1
shared-lib/lib-ot/src/revision/model.rs

@@ -21,6 +21,9 @@ pub struct Revision {
 
     #[pb(index = 6)]
     pub ty: RevType,
+
+    #[pb(index = 7)]
+    pub user_id: String,
 }
 
 impl Revision {
@@ -47,7 +50,7 @@ impl std::fmt::Debug for Revision {
 }
 
 impl Revision {
-    pub fn new<T1, T2, D>(base_rev_id: T1, rev_id: T2, delta: D, doc_id: &str, ty: RevType) -> Revision
+    pub fn new<T1, T2, D>(base_rev_id: T1, rev_id: T2, delta: D, doc_id: &str, ty: RevType, user_id: String) -> Revision
     where
         T1: Into<i64>,
         T2: Into<i64>,
@@ -70,6 +73,7 @@ impl Revision {
             md5,
             doc_id,
             ty,
+            user_id,
         }
     }
 }