use crate::util::{validate_not_empty_payload, validate_not_empty_str}; use appflowy_ai_client::dto::AIModel; use bytes::Bytes; use chrono::{DateTime, Utc}; use collab_entity::CollabType; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::cmp::Ordering; use std::collections::HashMap; use std::fmt::Display; use std::ops::{Deref, DerefMut}; use std::str::FromStr; use tracing::error; use uuid::Uuid; use validator::Validate; #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct CreateCollabParams { #[validate(custom = "validate_not_empty_str")] pub workspace_id: String, #[validate(custom = "validate_not_empty_str")] pub object_id: String, #[validate(custom = "validate_not_empty_payload")] pub encoded_collab_v1: Vec, pub collab_type: CollabType, } impl From<(String, CollabParams)> for CreateCollabParams { fn from((workspace_id, collab_params): (String, CollabParams)) -> Self { Self { workspace_id, object_id: collab_params.object_id, encoded_collab_v1: collab_params.encoded_collab_v1.to_vec(), collab_type: collab_params.collab_type, } } } impl CreateCollabParams { pub fn split(self) -> (CollabParams, String) { ( CollabParams { object_id: self.object_id, encoded_collab_v1: Bytes::from(self.encoded_collab_v1), collab_type: self.collab_type, embeddings: None, }, self.workspace_id, ) } pub fn to_bytes(&self) -> Result, bincode::Error> { bincode::serialize(self) } pub fn from_bytes(bytes: &[u8]) -> Result { bincode::deserialize(bytes) } } pub struct CollabIndexParams {} #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct CollabParams { #[validate(custom = "validate_not_empty_str")] pub object_id: String, #[validate(custom = "validate_not_empty_payload")] pub encoded_collab_v1: Bytes, pub collab_type: CollabType, #[serde(default)] pub embeddings: Option, } impl CollabParams { pub fn new( object_id: T, collab_type: CollabType, encoded_collab_v1: Vec, ) -> Self { let object_id = object_id.to_string(); Self { object_id, collab_type, encoded_collab_v1: Bytes::from(encoded_collab_v1), embeddings: None, } } pub fn to_bytes(&self) -> Result, bincode::Error> { bincode::serialize(self) } pub fn from_bytes(bytes: &[u8]) -> Result { match bincode::deserialize(bytes) { Ok(value) => Ok(value), Err(_) => { // fallback to deserialize into older version let old: CollabParamsV0 = bincode::deserialize(bytes)?; Ok(Self { object_id: old.object_id, encoded_collab_v1: old.encoded_collab_v1.into(), collab_type: old.collab_type, embeddings: None, }) }, } } } #[derive(Serialize, Deserialize)] struct CollabParamsV0 { object_id: String, encoded_collab_v1: Vec, collab_type: CollabType, } #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct BatchCreateCollabParams { #[validate(custom = "validate_not_empty_str")] pub workspace_id: String, pub params_list: Vec, } impl BatchCreateCollabParams { pub fn to_bytes(&self) -> Result, bincode::Error> { bincode::serialize(self) } pub fn from_bytes(bytes: &[u8]) -> Result { bincode::deserialize(bytes) } } #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct DeleteCollabParams { #[validate(custom = "validate_not_empty_str")] pub object_id: String, #[validate(custom = "validate_not_empty_str")] pub workspace_id: String, } #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct InsertSnapshotParams { #[validate(custom = "validate_not_empty_str")] pub object_id: String, #[validate(custom = "validate_not_empty_payload")] pub encoded_collab_v1: Vec, #[validate(custom = "validate_not_empty_str")] pub workspace_id: String, pub collab_type: CollabType, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SnapshotData { pub object_id: String, pub encoded_collab_v1: Vec, pub workspace_id: String, } #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct QuerySnapshotParams { pub snapshot_id: i64, } #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct QueryCollabParams { #[validate(custom = "validate_not_empty_str")] pub workspace_id: String, #[serde(flatten)] #[validate] pub inner: QueryCollab, } impl Display for QueryCollabParams { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "workspace_id: {}, object_id: {}, collab_type: {:?}", self.workspace_id, self.object_id, self.collab_type ) } } impl QueryCollabParams { pub fn new( object_id: T1, collab_type: CollabType, workspace_id: T2, ) -> Self { let workspace_id = workspace_id.to_string(); let object_id = object_id.to_string(); let inner = QueryCollab { object_id, collab_type, }; Self { workspace_id, inner, } } } impl Deref for QueryCollabParams { type Target = QueryCollab; fn deref(&self) -> &Self::Target { &self.inner } } #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct QueryCollab { #[validate(custom = "validate_not_empty_str")] pub object_id: String, pub collab_type: CollabType, } impl QueryCollab { pub fn new(object_id: T, collab_type: CollabType) -> Self { let object_id = object_id.to_string(); Self { object_id, collab_type, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BatchQueryCollabParams(pub Vec); impl Deref for BatchQueryCollabParams { type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for BatchQueryCollabParams { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AFSnapshotMeta { pub snapshot_id: i64, pub object_id: String, pub created_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AFSnapshotMetas(pub Vec); #[derive(Debug, Clone, Deserialize)] pub struct QueryObjectSnapshotParams { pub object_id: String, } #[derive(Serialize, Deserialize, Debug)] pub struct AFBlobRecord { pub file_id: String, } impl AFBlobRecord { pub fn new(file_id: String) -> Self { Self { file_id } } } #[derive(Serialize, Deserialize, Debug, PartialEq)] pub enum QueryCollabResult { Success { encode_collab_v1: Vec }, Failed { error: String }, } #[derive(Serialize, Deserialize)] pub struct BatchQueryCollabResult(pub HashMap); #[derive(Serialize, Deserialize)] pub struct WorkspaceUsage { pub total_document_size: i64, } #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct InsertCollabMemberParams { pub uid: i64, #[validate(custom = "validate_not_empty_str")] pub workspace_id: String, #[validate(custom = "validate_not_empty_str")] pub object_id: String, pub access_level: AFAccessLevel, } pub type UpdateCollabMemberParams = InsertCollabMemberParams; #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct CollabMemberIdentify { pub uid: i64, #[validate(custom = "validate_not_empty_str")] pub workspace_id: String, #[validate(custom = "validate_not_empty_str")] pub object_id: String, } #[derive(Serialize, Deserialize)] pub struct UpdatePublishNamespace { pub new_namespace: String, } #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct QueryCollabMembers { #[validate(custom = "validate_not_empty_str")] pub workspace_id: String, #[validate(custom = "validate_not_empty_str")] pub object_id: String, } #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct QueryWorkspaceMember { #[validate(custom = "validate_not_empty_str")] pub workspace_id: String, pub uid: i64, } #[derive(Serialize, Deserialize)] pub struct AFCollabMember { pub uid: i64, pub oid: String, pub permission: AFPermission, } #[derive(Serialize, Deserialize)] pub struct PublishInfo { pub namespace: Option, pub publish_name: String, pub view_id: Uuid, } #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Hash)] #[repr(i32)] pub enum AFRole { Owner = 1, Member = 2, Guest = 3, } impl AFRole { /// The user can create a [Collab] if the user is [AFRole::Owner] or [AFRole::Member] of the workspace. pub fn can_create_collab(&self) -> bool { matches!(self, AFRole::Owner | AFRole::Member) } } impl From for AFRole { fn from(value: i32) -> Self { // Can't modify the value of the enum match value { 1 => AFRole::Owner, 2 => AFRole::Member, 3 => AFRole::Guest, _ => { error!("Invalid role id: {}", value); AFRole::Guest }, } } } impl From<&str> for AFRole { fn from(value: &str) -> Self { match i32::from_str(value) { Ok(value) => value.into(), Err(_) => AFRole::Guest, } } } impl From for i32 { fn from(role: AFRole) -> Self { role as i32 } } impl From<&AFRole> for i32 { fn from(role: &AFRole) -> Self { role.clone() as i32 } } impl PartialOrd for AFRole { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for AFRole { fn cmp(&self, other: &Self) -> Ordering { let left = i32::from(self); let right = i32::from(other); // lower value has higher priority left.cmp(&right).reverse() } } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct AFPermission { /// The permission id pub id: i32, pub name: String, pub access_level: AFAccessLevel, pub description: String, } #[derive(Deserialize_repr, Serialize_repr, Eq, PartialEq, Debug, Clone, Copy)] #[repr(i32)] pub enum AFAccessLevel { // Can't modify the value of the enum ReadOnly = 10, ReadAndComment = 20, ReadAndWrite = 30, FullAccess = 50, } impl AFAccessLevel { pub fn can_write(&self) -> bool { match self { AFAccessLevel::ReadOnly | AFAccessLevel::ReadAndComment => false, AFAccessLevel::ReadAndWrite | AFAccessLevel::FullAccess => true, } } pub fn can_delete(&self) -> bool { match self { AFAccessLevel::ReadOnly | AFAccessLevel::ReadAndComment | AFAccessLevel::ReadAndWrite => { false }, AFAccessLevel::FullAccess => true, } } } impl From<&AFRole> for AFAccessLevel { fn from(value: &AFRole) -> Self { match value { AFRole::Owner => AFAccessLevel::FullAccess, AFRole::Member => AFAccessLevel::ReadAndWrite, AFRole::Guest => AFAccessLevel::ReadOnly, } } } impl From for AFAccessLevel { fn from(value: i32) -> Self { // Can't modify the value of the enum match value { 10 => AFAccessLevel::ReadOnly, 20 => AFAccessLevel::ReadAndComment, 30 => AFAccessLevel::ReadAndWrite, 50 => AFAccessLevel::FullAccess, _ => { error!("Invalid access level: {}", value); AFAccessLevel::ReadOnly }, } } } impl From<&str> for AFAccessLevel { fn from(value: &str) -> Self { match i32::from_str(value) { Ok(value) => AFAccessLevel::from(value), Err(_) => AFAccessLevel::ReadOnly, } } } impl From for i32 { fn from(level: AFAccessLevel) -> Self { level as i32 } } impl From<&AFAccessLevel> for i32 { fn from(level: &AFAccessLevel) -> Self { *level as i32 } } impl PartialOrd for AFAccessLevel { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for AFAccessLevel { fn cmp(&self, other: &Self) -> Ordering { let left = i32::from(self); let right = i32::from(other); left.cmp(&right) } } #[derive(Serialize, Deserialize)] pub struct AFCollabMembers(pub Vec); pub type RawData = Vec; #[derive(Serialize, Deserialize)] pub struct AFUserProfile { pub uid: i64, pub uuid: Uuid, pub email: Option, pub password: Option, pub name: Option, pub metadata: Option, pub encryption_sign: Option, pub latest_workspace_id: Uuid, pub updated_at: i64, } #[derive(Debug, Serialize, Deserialize)] pub struct AFWorkspace { pub workspace_id: Uuid, pub database_storage_id: Uuid, pub owner_uid: i64, pub owner_name: String, pub workspace_type: i32, pub workspace_name: String, pub created_at: DateTime, pub icon: String, pub member_count: Option, } #[derive(Serialize, Deserialize)] pub struct AFWorkspaceSettings { #[serde(default)] pub disable_search_indexing: bool, #[serde(default)] pub ai_model: String, } impl Default for AFWorkspaceSettings { fn default() -> Self { Self { disable_search_indexing: false, ai_model: "".to_string(), } } } #[derive(Default, Serialize, Deserialize, Debug)] pub struct AFWorkspaceSettingsChange { #[serde(skip_serializing_if = "Option::is_none")] pub disable_search_indexing: Option, #[serde(skip_serializing_if = "Option::is_none")] pub ai_model: Option, } impl AFWorkspaceSettingsChange { pub fn new() -> Self { Self { disable_search_indexing: None, ai_model: None, } } pub fn disable_search_indexing(mut self, disable_search_indexing: bool) -> Self { self.disable_search_indexing = Some(disable_search_indexing); self } pub fn ai_model(mut self, ai_model: String) -> Self { self.ai_model = Some(ai_model); self } } #[derive(Serialize, Deserialize)] pub struct AFUserWorkspaceInfo { pub user_profile: AFUserProfile, pub visiting_workspace: AFWorkspace, pub workspaces: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] pub struct AFWorkspaceMember { pub name: String, pub email: String, pub role: AFRole, pub avatar_url: Option, } #[derive(Deserialize, Serialize, Debug)] pub struct AFWorkspaceInvitation { pub invite_id: Uuid, pub workspace_id: Uuid, pub workspace_name: Option, pub inviter_email: Option, pub inviter_name: Option, pub status: AFWorkspaceInvitationStatus, pub updated_at: DateTime, } #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] #[repr(i16)] pub enum AFWorkspaceInvitationStatus { Pending = 0, Accepted = 1, Rejected = 2, } impl From for AFWorkspaceInvitationStatus { fn from(value: i16) -> Self { match value { 0 => AFWorkspaceInvitationStatus::Pending, 1 => AFWorkspaceInvitationStatus::Accepted, 2 => AFWorkspaceInvitationStatus::Rejected, _ => { error!("Invalid role id: {}", value); AFWorkspaceInvitationStatus::Pending }, } } } #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct CreateChatParams { #[validate(custom = "validate_not_empty_str")] pub chat_id: String, pub name: String, pub rag_ids: Vec, } #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct UpdateChatParams { #[validate(custom = "validate_not_empty_str")] pub chat_id: String, #[validate(custom = "validate_not_empty_str")] pub name: Option, pub metadata: Option, } #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct CreateChatMessageParams { #[validate(custom = "validate_not_empty_str")] pub content: String, pub message_type: ChatMessageType, /// metadata is json array object #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, } /// [ChatMessageMetadata] is used when creating a new question message. /// All the properties of [ChatMessageMetadata] except [ChatMetadataData] will be stored as a /// metadata for specific [ChatMessage] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChatMessageMetadata { pub data: ChatMetadataData, /// The id for the metadata. It can be a file_id, view_id pub id: String, /// The name for the metadata. For example, @xxx, @xx.txt pub name: String, pub source: String, #[serde(skip_serializing_if = "Option::is_none")] pub extract: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChatMetadataData { /// The textual content of the metadata. This field can contain raw text data from a specific /// document or any other text content that is indexable. This content is typically used for /// search and indexing purposes within the chat context. pub content: String, /// The type of content represented by this metadata. This could indicate the format or /// nature of the content (e.g., text, markdown, PDF). The `content_type` helps in /// processing or rendering the content appropriately. pub content_type: ChatMetadataContentType, /// The size of the content in bytes. pub size: i64, } impl ChatMetadataData { /// Validates the `ChatMetadataData` instance. /// /// This method checks the validity of the data based on the content type and the presence of content or URL. /// - If `content` is empty, the method checks if `url` is provided. If `url` is also empty, the data is invalid. /// - For `Text` and `Markdown`, it ensures that the content length matches the specified size if content is present. /// - For `Unknown` and `PDF`, it currently returns `false` as these types are either unsupported or /// require additional validation logic. /// /// Returns `true` if the data is valid according to its content type and the presence of content or URL, otherwise `false`. pub fn validate(&self) -> Result<(), anyhow::Error> { match self.content_type { ChatMetadataContentType::Text | ChatMetadataContentType::Markdown => { if self.content.len() != self.size as usize { return Err(anyhow::anyhow!( "Invalid content size: content size: {}, expected size: {}", self.content.len(), self.size )); } }, ChatMetadataContentType::PDF => { if self.content.is_empty() { return Err(anyhow::anyhow!("Invalid content: content is empty")); } }, ChatMetadataContentType::Unknown => { return Err(anyhow::anyhow!( "Unsupported content type: {:?}", self.content_type )); }, } Ok(()) } } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum ChatMetadataContentType { Unknown, Text, Markdown, PDF, } impl Display for ChatMetadataContentType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ChatMetadataContentType::Unknown => write!(f, "unknown"), ChatMetadataContentType::Text => write!(f, "text"), ChatMetadataContentType::Markdown => write!(f, "markdown"), ChatMetadataContentType::PDF => write!(f, "pdf"), } } } impl ChatMetadataData { pub fn new_text(content: String) -> Self { let size = content.len(); Self { content, content_type: ChatMetadataContentType::Text, size: size as i64, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpdateChatMessageMetaParams { pub message_id: i64, pub meta_data: HashMap, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpdateChatMessageContentParams { pub chat_id: String, pub message_id: i64, pub content: String, #[serde(default)] pub model: AIModel, } #[derive(Debug, Clone, Default, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum ChatMessageType { System = 0, #[default] User = 1, } impl CreateChatMessageParams { pub fn new_system(content: T) -> Self { Self { content: content.to_string(), message_type: ChatMessageType::System, metadata: None, } } pub fn new_user(content: T) -> Self { Self { content: content.to_string(), message_type: ChatMessageType::User, metadata: None, } } pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self { self.metadata = Some(metadata); self } } #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct GetChatMessageParams { pub cursor: MessageCursor, pub limit: u64, } impl GetChatMessageParams { pub fn offset(offset: u64, limit: u64) -> Self { Self { cursor: MessageCursor::Offset(offset), limit, } } pub fn after_message_id(after_message_id: i64, limit: u64) -> Self { Self { cursor: MessageCursor::AfterMessageId(after_message_id), limit, } } pub fn before_message_id(before_message_id: i64, limit: u64) -> Self { Self { cursor: MessageCursor::BeforeMessageId(before_message_id), limit, } } pub fn next_back(limit: u64) -> Self { Self { cursor: MessageCursor::NextBack, limit, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum MessageCursor { Offset(u64), AfterMessageId(i64), BeforeMessageId(i64), NextBack, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChatMessage { pub author: ChatAuthor, pub message_id: i64, pub content: String, pub created_at: DateTime, pub meta_data: serde_json::Value, pub reply_message_id: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct QAChatMessage { pub question: ChatMessage, pub answer: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RepeatedChatMessage { pub messages: Vec, pub has_more: bool, pub total: i64, } #[derive(Debug, Default, Clone, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum ChatAuthorType { Unknown = 0, Human = 1, #[default] System = 2, AI = 3, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChatAuthor { pub author_id: i64, #[serde(default)] pub author_type: ChatAuthorType, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub meta: Option, } impl ChatAuthor { pub fn new(author_id: i64, author_type: ChatAuthorType) -> Self { Self { author_id, author_type, meta: None, } } pub fn ai() -> Self { Self { author_id: 0, author_type: ChatAuthorType::AI, meta: None, } } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct AFCollabEmbeddingParams { pub fragment_id: String, pub object_id: String, pub collab_type: CollabType, pub content_type: EmbeddingContentType, pub content: String, pub embedding: Option>, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct AFCollabEmbeddings { pub tokens_consumed: u32, pub params: Vec, } /// Type of content stored by the embedding. /// Currently only plain text of the document is supported. /// In the future, we might support other kinds like i.e. PDF, images or image-extracted text. #[repr(i32)] #[derive(Debug, Copy, Clone, Serialize_repr, Deserialize_repr, Eq, PartialEq)] pub enum EmbeddingContentType { /// The plain text representation of the document. PlainText = 0, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpdateChatMessageResponse { pub answer: Option, } #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct CreateAnswerMessageParams { #[validate(custom = "validate_not_empty_str")] pub content: String, #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, pub question_message_id: i64, } #[derive(Serialize, Deserialize, Debug)] pub struct PublishCollabMetadata { pub view_id: uuid::Uuid, pub publish_name: String, pub metadata: Metadata, } #[derive(Debug)] pub struct PublishCollabItem { pub meta: PublishCollabMetadata, pub data: Data, } #[derive(Serialize, Deserialize, Debug)] pub struct GlobalComments { pub comments: Vec, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct AFWebUser { pub uuid: Uuid, pub name: String, pub avatar_url: Option, } #[derive(Serialize, Deserialize, Debug)] pub struct GlobalComment { pub user: Option, pub created_at: DateTime, pub last_updated_at: DateTime, pub content: String, pub reply_comment_id: Option, pub comment_id: Uuid, pub is_deleted: bool, pub can_be_deleted: bool, } #[derive(Serialize, Deserialize, Debug)] pub struct CreateGlobalCommentParams { pub content: String, pub reply_comment_id: Option, } #[derive(Serialize, Deserialize, Debug)] pub struct DeleteGlobalCommentParams { pub comment_id: Uuid, } #[derive(Serialize, Deserialize, Debug)] pub struct Reactions { pub reactions: Vec, } #[derive(Serialize, Deserialize, Debug)] pub struct Reaction { pub reaction_type: String, pub react_users: Vec, pub comment_id: Uuid, } #[derive(Serialize, Deserialize, Debug)] pub struct GetReactionQueryParams { pub comment_id: Option, } #[derive(Serialize, Deserialize, Debug)] pub struct CreateReactionParams { pub reaction_type: String, pub comment_id: Uuid, } #[derive(Serialize, Deserialize, Debug)] pub struct DeleteReactionParams { pub reaction_type: String, pub comment_id: Uuid, } /// Indexing status of a document. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum IndexingStatus { /// Indexing is disabled for that document. Disabled, /// Indexing is enabled, but the document has never been indexed. NotIndexed, /// Indexing is enabled and the document has been indexed. Indexed, } #[derive(Serialize, Deserialize, Debug)] pub struct TemplateCategories { pub categories: Vec, } #[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug, Copy, Clone)] #[repr(i32)] pub enum TemplateCategoryType { UseCase = 0, Feature = 1, } #[derive(Serialize, Deserialize, Debug)] pub struct TemplateCategory { pub id: Uuid, pub name: String, pub icon: String, pub bg_color: String, pub description: String, pub category_type: TemplateCategoryType, pub priority: i32, } #[derive(Serialize, Deserialize, Debug)] pub struct TemplateCategoryMinimal { pub id: Uuid, pub name: String, pub icon: String, pub bg_color: String, } #[derive(Serialize, Deserialize, Debug)] pub struct CreateTemplateCategoryParams { pub name: String, pub icon: String, pub bg_color: String, pub description: String, pub category_type: TemplateCategoryType, pub priority: i32, } #[derive(Serialize, Deserialize, Debug)] pub struct GetTemplateCategoriesQueryParams { pub name_contains: Option, pub category_type: Option, } #[derive(Serialize, Deserialize, Debug)] pub struct UpdateTemplateCategoryParams { pub name: String, pub icon: String, pub bg_color: String, pub description: String, pub category_type: TemplateCategoryType, pub priority: i32, } #[derive(Serialize, Deserialize, Debug)] pub struct TemplateCreators { pub creators: Vec, } #[derive(Serialize, Deserialize, Debug)] pub struct AccountLink { pub link_type: String, pub url: String, } #[derive(Serialize, Deserialize, Debug)] pub struct TemplateCreator { pub id: Uuid, pub name: String, pub avatar_url: String, pub account_links: Vec, pub number_of_templates: i32, } #[derive(Serialize, Deserialize, Debug)] pub struct TemplateCreatorMinimal { pub id: Uuid, pub name: String, pub avatar_url: String, } #[derive(Serialize, Deserialize, Debug)] pub struct CreateTemplateCreatorParams { pub name: String, pub avatar_url: String, pub account_links: Vec, } #[derive(Serialize, Deserialize, Debug)] pub struct UpdateTemplateCreatorParams { pub name: String, pub avatar_url: String, pub account_links: Vec, } #[derive(Serialize, Deserialize, Debug)] pub struct GetTemplateCreatorsQueryParams { pub name_contains: Option, } #[derive(Serialize, Deserialize, Debug)] pub struct Template { pub view_id: Uuid, pub created_at: DateTime, pub last_updated_at: DateTime, pub name: String, pub description: String, pub about: String, pub view_url: String, pub categories: Vec, pub creator: TemplateCreator, pub is_new_template: bool, pub is_featured: bool, pub related_templates: Vec, } #[derive(Serialize, Deserialize, Debug)] pub struct TemplateMinimal { pub view_id: Uuid, pub created_at: DateTime, pub last_updated_at: DateTime, pub name: String, pub description: String, pub view_url: String, pub creator: TemplateCreatorMinimal, pub categories: Vec, pub is_new_template: bool, pub is_featured: bool, } #[derive(Serialize, Deserialize, Debug)] pub struct Templates { pub templates: Vec, } #[derive(Serialize, Deserialize, Debug)] pub struct CreateTemplateParams { pub view_id: Uuid, pub name: String, pub description: String, pub about: String, pub view_url: String, pub category_ids: Vec, pub creator_id: Uuid, pub is_new_template: bool, pub is_featured: bool, pub related_view_ids: Vec, } #[derive(Serialize, Deserialize, Debug)] pub struct UpdateTemplateParams { pub name: String, pub description: String, pub about: String, pub view_url: String, pub category_ids: Vec, pub creator_id: Uuid, pub is_new_template: bool, pub is_featured: bool, pub related_view_ids: Vec, } #[derive(Serialize, Deserialize, Debug)] pub struct GetTemplatesQueryParams { pub category_id: Option, pub is_featured: Option, pub is_new_template: Option, pub name_contains: Option, } #[derive(Serialize, Deserialize, Debug)] pub struct TemplateGroup { pub category: TemplateCategoryMinimal, pub templates: Vec, } #[derive(Serialize, Deserialize, Debug)] pub struct TemplateHomePage { pub featured_templates: Vec, pub new_templates: Vec, pub template_groups: Vec, } #[derive(Serialize, Deserialize, Debug)] pub struct TemplateHomePageQueryParams { pub per_count: Option, } #[derive(Serialize, Deserialize, Debug)] pub struct AvatarImageSource { pub file_id: String, } #[cfg(test)] mod test { use crate::dto::{CollabParams, CollabParamsV0}; use collab_entity::CollabType; use uuid::Uuid; #[test] fn collab_params_serialization_from_old_format() { let v0 = CollabParamsV0 { object_id: Uuid::new_v4().to_string(), collab_type: CollabType::Document, encoded_collab_v1: vec![ 7, 0, 0, 0, 0, 0, 0, 0, 1, 209, 196, 206, 243, 15, 1, 26, 4, 0, 0, 0, 0, 0, 0, 1, 1, 209, 196, 206, 243, 15, 0, 40, 1, 4, 100, 97, 116, 97, 5, 116, 105, 116, 108, 101, 1, 119, 128, 8, 120, 118, 88, 114, 83, 79, 105, 70, 69, 84, 70, 97, 66, 79, 57, 53, 111, 122, 87, 110, 54, 106, 71, 87, 66, 104, 120, 114, 79, 70, 74, 111, 109, 119, 68, 119, 114, 114, 89, 66, 103, 79, 72, 114, 102, 51, 87, 55, 79, 76, 110, 85, 120, 69, 113, 121, 104, 121, 107, 82, 117, 82, 113, 73, 104, 89, 72, 84, 114, 105, 122, 56, 122, 72, 90, 67, 110, 97, 120, 83, 114, 85, 113, 71, 98, 52, 50, 110, 87, 116, 105, 55, 107, 82, 103, 72, 68, 101, 89, 118, 82, 112, 114, 73, 122, 87, 76, 68, 120, 101, 57, 55, 87, 68, 77, 113, 107, 120, 104, 65, 71, 103, 48, 51, 49, 110, 119, 111, 106, 70, 108, 114, 102, 83, 99, 89, 110, 73, 50, 98, 118, 54, 68, 88, 111, 118, 74, 107, 121, 119, 103, 102, 98, 107, 51, 78, 99, 103, 51, 73, 78, 106, 67, 85, 97, 54, 114, 50, 103, 83, 55, 70, 57, 122, 106, 115, 103, 121, 88, 68, 97, 101, 50, 68, 84, 70, 84, 72, 87, 112, 105, 102, 71, 108, 52, 48, 114, 106, 113, 56, 119, 110, 86, 71, 54, 65, 99, 99, 109, 85, 107, 105, 89, 86, 100, 77, 75, 69, 73, 54, 107, 122, 48, 80, 54, 50, 99, 80, 100, 115, 78, 90, 49, 70, 90, 106, 117, 106, 98, 111, 88, 65, 105, 83, 108, 82, 105, 90, 73, 73, 90, 102, 116, 77, 117, 79, 81, 79, 85, 53, 71, 72, 65, 49, 119, 118, 88, 97, 98, 122, 52, 122, 77, 70, 85, 112, 100, 115, 67, 89, 107, 114, 88, 87, 101, 65, 79, 86, 102, 77, 102, 106, 100, 117, 74, 57, 89, 82, 65, 103, 72, 100, 120, 89, 75, 54, 103, 70, 89, 122, 75, 122, 53, 119, 78, 76, 83, 68, 90, 101, 115, 109, 116, 117, 65, 54, 53, 48, 97, 52, 85, 51, 57, 111, 74, 90, 73, 77, 117, 105, 80, 116, 57, 70, 84, 118, 76, 122, 111, 82, 116, 68, 51, 83, 71, 108, 78, 77, 71, 102, 68, 84, 110, 114, 80, 106, 79, 65, 75, 114, 118, 116, 98, 57, 72, 84, 108, 101, 76, 109, 48, 110, 54, 102, 97, 83, 89, 77, 66, 88, 69, 119, 78, 71, 89, 75, 53, 114, 80, 56, 72, 55, 122, 112, 116, 82, 52, 117, 121, 113, 67, 53, 72, 48, 101, 83, 81, 76, 76, 87, 110, 53, 81, 106, 67, 100, 103, 55, 85, 109, 115, 103, 55, 110, 121, 87, 121, 117, 98, 72, 121, 85, 106, 57, 66, 90, 89, 54, 50, 122, 84, 69, 103, 57, 52, 67, 102, 50, 82, 114, 74, 84, 115, 87, 97, 87, 113, 109, 88, 105, 113, 97, 100, 113, 57, 87, 114, 70, 57, 120, 108, 80, 122, 52, 113, 119, 53, 48, 69, 73, 78, 90, 55, 120, 65, 67, 122, 89, 111, 84, 57, 88, 79, 112, 105, 76, 76, 51, 77, 52, 119, 84, 98, 67, 54, 101, 105, 89, 72, 80, 99, 119, 90, 109, 56, 105, 49, 68, 57, 102, 111, 75, 65, 68, 74, 87, 106, 69, 65, 85, 71, 104, 85, 75, 117, 66, 70, 105, 72, 100, 75, 121, 86, 74, 81, 56, 49, 73, 82, 85, 98, 120, 122, 74, 88, 107, 116, 110, 73, 101, 75, 87, 57, 76, 53, 120, 100, 117, 112, 99, 72, 49, 105, 122, 115, 113, 103, 109, 85, 122, 113, 50, 70, 76, 118, 76, 121, 88, 55, 110, 84, 78, 120, 99, 78, 70, 122, 117, 66, 98, 65, 75, 112, 50, 116, 112, 84, 73, 113, 106, 77, 106, 68, 114, 99, 76, 78, 53, 109, 117, 66, 88, 100, 68, 76, 77, 113, 67, 101, 108, 120, 49, 117, 50, 56, 89, 118, 88, 82, 74, 55, 99, 111, 78, 77, 77, 87, 111, 121, 50, 52, 73, 116, 120, 73, 81, 72, 53, 107, 70, 50, 111, 67, 97, 122, 88, 103, 114, 76, 97, 105, 109, 113, 105, 89, 77, 79, 70, 74, 90, 70, 122, 117, 100, 105, 113, 121, 55, 55, 89, 49, 85, 51, 103, 120, 98, 79, 80, 121, 66, 57, 73, 98, 114, 70, 100, 113, 83, 101, 106, 65, 82, 121, 49, 83, 73, 122, 84, 80, 103, 121, 84, 110, 117, 74, 117, 70, 90, 116, 104, 56, 57, 109, 82, 71, 100, 68, 106, 70, 75, 80, 83, 77, 110, 113, 120, 102, 68, 119, 118, 109, 84, 113, 77, 79, 119, 112, 114, 107, 118, 85, 78, 65, 104, 106, 98, 53, 81, 98, 80, 71, 122, 107, 52, 83, 67, 121, 68, 78, 108, 107, 55, 109, 121, 105, 51, 120, 100, 110, 51, 108, 122, 65, 117, 77, 83, 78, 55, 121, 86, 76, 109, 78, 103, 88, 68, 75, 48, 81, 101, 120, 112, 69, 78, 86, 88, 116, 78, 48, 97, 105, 55, 116, 79, 104, 118, 86, 89, 101, 74, 112, 82, 87, 82, 115, 84, 54, 55, 97, 111, 76, 56, 109, 120, 53, 73, 55, 85, 114, 82, 49, 90, 66, 76, 76, 99, 81, 72, 49, 105, 71, 79, 109, 110, 87, 90, 68, 111, 75, 108, 116, 86, 81, 103, 109, 112, 120, 100, 52, 70, 79, 111, 121, 57, 111, 76, 65, 111, 83, 67, 56, 48, 120, 53, 87, 56, 72, 83, 83, 51, 103, 114, 49, 88, 85, 53, 111, 51, 90, 86, 121, 121, 86, 74, 116, 112, 104, 116, 120, 84, 113, 85, 71, 90, 75, 85, 86, 119, 122, 75, 49, 122, 78, 101, 118, 82, 54, 90, 55, 72, 114, 53, 84, 72, 115, 102, 104, 48, 103, 48, 99, 65, 101, 65, 111, 122, 89, 106, 89, 117, 49, 110, 79, 115, 78, 80, 71, 53, 107, 112, 54, 48, 121, 55, 119, 116, 119, 76, 73, 105, 110, 102, 79, 101, 72, 82, 105, 80, 117, 80, 88, 70, 84, 51, 53, 98, 108, 74, 84, 121, 112, 76, 76, 98, 119, 102, 80, 122, 51, 120, 53, 54, 113, 68, 0, 0, ], }; let data = bincode::serialize(&v0).unwrap(); let collab_params = CollabParams::from_bytes(&data).unwrap(); assert_eq!(collab_params.object_id, v0.object_id); assert_eq!(collab_params.collab_type, v0.collab_type); assert_eq!(collab_params.encoded_collab_v1, v0.encoded_collab_v1); } }