use crate::crdt::EntityType;
use crate::invite::InviteStatus;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Command {
Initialize {
four_words: String,
display_name: String,
device_name: String,
storage_dir: String,
},
UpdateDisplayName { display_name: String },
StartNetworking { preferred_port: Option<u16> },
StopNetworking,
ConnectToPeer { peer_four_words: String },
RequestExternalAddress,
CreateEntity {
name: String,
entity_type: EntityType,
description: Option<String>,
initial_members: Vec<String>,
},
CreateLocalEntity {
name: String,
entity_type: EntityType,
description: Option<String>,
},
LinkEntityToNetwork {
entity_id: String,
four_words: String,
},
MarkEntitySynced { entity_id: String },
SetParentOrganization {
entity_id: String,
parent_org_id: String,
},
UpdateEntity {
entity_type: EntityType,
entity_id: String,
name: Option<String>,
description: Option<Option<String>>,
},
DeleteEntity {
entity_type: EntityType,
entity_id: String,
},
AddMember {
entity_type: EntityType,
entity_id: String,
member_id: String,
role: String,
},
RemoveMember {
entity_type: EntityType,
entity_id: String,
member_id: String,
},
RemoveOrganizationMember { org_id: String, member_id: String },
SetMemberRole {
entity_type: EntityType,
entity_id: String,
member_id: String,
new_role: String,
},
SetPermissionOverride {
entity_type: EntityType,
entity_id: String,
member_id: String,
resource_type: String,
access_level: String,
},
RemovePermissionOverride {
entity_type: EntityType,
entity_id: String,
member_id: String,
resource_type: String,
},
SendMessage {
entity_id: String,
entity_type: EntityType,
text: String,
author: String,
reply_to_id: Option<String>,
attachments: Option<Vec<String>>,
},
SendDirectMessage {
recipients: Vec<String>,
text: String,
author: String,
},
DeleteMessage {
entity_id: String,
entity_type: EntityType,
message_id: String,
},
EditMessage {
entity_id: String,
entity_type: EntityType,
message_id: String,
new_text: String,
},
AddReaction {
entity_id: String,
entity_type: EntityType,
message_id: String,
emoji: String,
},
RemoveReaction {
entity_id: String,
entity_type: EntityType,
message_id: String,
emoji: String,
},
CreateInvite {
recipient_id: String,
entity_type: EntityType,
entity_id: String,
role: String,
message: Option<String>,
expires_in_hours: Option<u32>,
},
AcceptInvite { invite_id: String },
RejectInvite { invite_id: String },
RevokeInvite { invite_id: String },
WriteFile {
entity_id: String,
disk_type: DiskTypeArg,
path: String,
data: Vec<u8>,
},
DeleteFile {
entity_id: String,
disk_type: DiskTypeArg,
path: String,
},
CreateDirectory {
entity_id: String,
disk_type: DiskTypeArg,
path: String,
},
CreateKanbanBoard {
entity_id: String,
board_name: String,
description: Option<String>,
},
CreateKanbanColumn {
board_id: String,
column_name: String,
position: Option<u32>,
},
CreateKanbanCard {
board_id: String,
column_id: String,
title: String,
description: Option<String>,
assignee: Option<String>,
},
MoveKanbanCard {
board_id: String,
card_id: String,
target_column_id: String,
position: Option<u32>,
},
UpdateKanbanCard {
board_id: String,
card_id: String,
title: Option<String>,
description: Option<String>,
assignee: Option<String>,
},
DeleteKanbanCard { board_id: String, card_id: String },
UpdateKanbanBoard {
board_id: String,
name: Option<String>,
description: Option<Option<String>>,
},
DeleteKanbanBoard { board_id: String },
StartCall {
entity_id: String,
video_enabled: bool,
},
JoinCall { call_id: String },
LeaveCall { call_id: String },
ToggleVideo { call_id: String, enabled: bool },
ToggleAudio { call_id: String, enabled: bool },
StartScreenShare { call_id: String },
StopScreenShare { call_id: String },
CreateContact {
display_name: String,
four_words: Option<String>,
is_favourite: bool,
},
UpdateContact {
contact_id: String,
display_name: Option<String>,
is_favourite: Option<bool>,
},
DeleteContact { contact_id: String },
LinkContact {
contact_id: String,
four_words: String,
},
SetFavouriteContact { four_words: String },
RemoveFavouriteContact { four_words: String },
CreateWebsite {
entity_id: String,
html: String,
css: Option<String>,
js: Option<String>,
metadata: Option<String>,
},
UpdateWebsite {
entity_id: String,
html: Option<String>,
css: Option<String>,
js: Option<String>,
metadata: Option<String>,
},
DeleteWebsite { entity_id: String },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DiskTypeArg {
Private,
Public,
Shared,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Event {
Initialized {
four_words: String,
display_name: String,
device_name: String,
},
DisplayNameUpdated { old_name: String, new_name: String },
NetworkingStarted {
listen_address: String,
connection_identity: String,
},
NetworkingStopped,
PeerConnected { peer_four_words: String },
ExternalAddressDiscovered { address: String },
ConnectionFailed {
peer_four_words: String,
reason: String,
},
EntityCreated {
entity_id: String,
name: String,
entity_type: EntityType,
created_by: String,
},
EntityLinkedToNetwork {
entity_id: String,
four_words: String,
},
EntitySynced { entity_id: String },
ParentOrganizationSet {
entity_id: String,
parent_org_id: String,
},
EntityUpdated {
entity_id: String,
entity_type: EntityType,
name: Option<String>,
},
EntityDeleted {
entity_id: String,
entity_type: EntityType,
},
MemberAdded {
entity_type: EntityType,
entity_id: String,
member_id: String,
role: String,
},
MemberRemoved {
entity_type: EntityType,
entity_id: String,
member_id: String,
},
MemberRoleChanged {
entity_type: EntityType,
entity_id: String,
member_id: String,
old_role: String,
new_role: String,
},
OrganizationMemberRemoved {
org_id: String,
member_id: String,
removed_from: Vec<(EntityType, String)>,
},
PermissionOverrideSet {
entity_type: EntityType,
entity_id: String,
member_id: String,
resource_type: String,
access_level: String,
},
PermissionOverrideRemoved {
entity_type: EntityType,
entity_id: String,
member_id: String,
resource_type: String,
},
MessageSent {
message_id: String,
entity_id: String,
entity_type: EntityType,
author: String,
text: String,
},
MessageReceived {
message_id: String,
entity_id: String,
entity_type: EntityType,
author: String,
text: String,
},
DirectMessageSent {
message_ids: Vec<String>,
recipients: Vec<String>,
},
MessageDeleted {
message_id: String,
entity_id: String,
entity_type: EntityType,
},
MessageEdited {
message_id: String,
entity_id: String,
entity_type: EntityType,
new_text: String,
edited_at: u64,
},
ReactionAdded {
message_id: String,
entity_id: String,
entity_type: EntityType,
emoji: String,
reactor_id: String,
},
ReactionRemoved {
message_id: String,
entity_id: String,
entity_type: EntityType,
emoji: String,
reactor_id: String,
},
InviteCreated {
invite_id: String,
recipient_id: String,
entity_type: EntityType,
entity_id: String,
role: String,
},
InviteAccepted {
invite_id: String,
recipient_id: String,
entity_id: String,
},
InviteRejected { invite_id: String },
InviteRevoked { invite_id: String },
FileWritten {
entity_id: String,
disk_type: DiskTypeArg,
path: String,
size_bytes: u64,
},
FileDeleted {
entity_id: String,
disk_type: DiskTypeArg,
path: String,
},
DirectoryCreated {
entity_id: String,
disk_type: DiskTypeArg,
path: String,
},
KanbanBoardCreated {
board_id: String,
entity_id: String,
board_name: String,
},
KanbanColumnCreated {
column_id: String,
board_id: String,
column_name: String,
},
KanbanCardCreated {
card_id: String,
column_id: String,
title: String,
},
KanbanCardMoved {
card_id: String,
from_column_id: String,
to_column_id: String,
},
KanbanCardUpdated { card_id: String },
KanbanCardDeleted { card_id: String },
KanbanBoardUpdated {
board_id: String,
name: Option<String>,
},
KanbanBoardDeleted { board_id: String },
CallStarted { call_id: String, entity_id: String },
CallJoined { call_id: String },
CallLeft { call_id: String },
VideoToggled { call_id: String, enabled: bool },
AudioToggled { call_id: String, enabled: bool },
ScreenShareStarted { call_id: String },
ScreenShareStopped { call_id: String },
ContactCreated {
contact_id: String,
display_name: String,
four_words: Option<String>,
},
ContactUpdated {
contact_id: String,
display_name: Option<String>,
is_favourite: Option<bool>,
},
ContactDeleted { contact_id: String },
ContactLinked {
contact_id: String,
four_words: String,
},
ContactFavouriteSet { four_words: String },
ContactFavouriteRemoved { four_words: String },
WebsiteCreated {
entity_id: String,
website_root_hash: String,
published_at: i64,
size_bytes: usize,
},
WebsiteUpdated {
entity_id: String,
website_root_hash: String,
updated_at: i64,
size_bytes: usize,
},
WebsiteDeleted { entity_id: String },
CommandFailed { command_type: String, error: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Query {
GetProfile,
IsNetworkingActive,
GetConnectionIdentity,
GetExternalAddress,
GetEntity { entity_id: String },
ListEntities,
ListEntitiesByType { entity_type: EntityType },
ListChildEntities { org_id: String },
ListMembers {
entity_type: EntityType,
entity_id: String,
},
GetMemberRole {
entity_type: EntityType,
entity_id: String,
member_id: String,
},
GetPermissionOverrides {
entity_type: EntityType,
entity_id: String,
member_id: String,
},
GetMessage {
entity_id: String,
message_id: String,
},
GetEntityMessages { entity_id: String },
GetThreadMessages {
entity_id: String,
parent_message_id: String,
},
GetDirectMessages { other_peer_id: String },
GetEntitySyncState {
entity_id: String,
entity_type: EntityType,
},
GetInvite { invite_id: String },
ListPendingInvites,
ListSentInvites,
ReadFile {
entity_id: String,
disk_type: DiskTypeArg,
path: String,
},
ListFiles {
entity_id: String,
disk_type: DiskTypeArg,
path: String,
},
GetDiskStats {
entity_id: String,
disk_type: DiskTypeArg,
},
GetKanbanBoard { board_id: String },
ListKanbanBoards { entity_id: String },
GetKanbanCard { board_id: String, card_id: String },
ListKanbanCards {
board_id: String,
column_id: Option<String>,
state: Option<String>,
assignee_id: Option<String>,
tag_id: Option<String>,
},
GetPresence { peer_id: String },
ListOnlinePeers,
ListActiveCalls,
GetCallParticipants { call_id: String },
GetContact { contact_id: String },
ListContacts,
ListFavouriteContacts,
SearchContacts { query: String },
GetWebsite { entity_id: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum QueryResponse {
Profile {
four_words: String,
display_name: String,
device_name: String,
device_type: String,
},
Bool(bool),
OptionalString(Option<String>),
Entity(EntityResponse),
EntityList(Vec<EntityResponse>),
MemberList(Vec<MemberResponse>),
MemberRole(String),
PermissionOverrides(Vec<(String, String)>),
Message(MessageResponse),
Messages(Vec<MessageResponse>),
SyncState(SyncStateResponse),
Invite(InviteResponse),
InviteList(Vec<InviteResponse>),
FileContents(Vec<u8>),
FileList(Vec<FileInfoResponse>),
DiskStats(DiskStatsResponse),
KanbanBoard(KanbanBoardResponse),
KanbanBoardList(Vec<KanbanBoardResponse>),
KanbanCard(KanbanCardResponse),
KanbanCards(Vec<KanbanCardResponse>),
Presence(PresenceResponse),
PeerList(Vec<String>),
CallList(Vec<CallResponse>),
CallParticipants(Vec<String>),
Contact(ContactResponse),
ContactList(Vec<ContactResponse>),
Website(WebsiteResponse),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntityResponse {
pub id: String,
pub name: String,
pub entity_type: EntityType,
pub description: Option<String>,
pub created_by: String,
pub created_at: i64,
pub member_count: usize,
pub parent_org_id: Option<String>,
pub network_four_words: Option<String>,
pub is_local_only: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemberResponse {
pub member_id: String,
pub role: String,
pub joined_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageResponse {
pub id: String,
pub entity_id: String,
pub author: String,
pub text: String,
pub timestamp: i64,
pub reply_to_id: Option<String>,
#[serde(default)]
pub reactions: Vec<ReactionResponse>,
pub edited_at: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReactionResponse {
pub emoji: String,
pub count: u32,
pub user_reacted: bool,
pub peer_ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncStateResponse {
pub entity_id: String,
pub entity_type: EntityType,
pub message_count: usize,
pub last_sync_time: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InviteResponse {
pub id: String,
pub sender_id: String,
pub recipient_id: String,
pub entity_type: EntityType,
pub entity_id: String,
pub role: String,
pub status: InviteStatus,
pub message: Option<String>,
pub created_at: i64,
pub expires_at: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileInfoResponse {
pub path: String,
pub name: String,
pub is_directory: bool,
pub size_bytes: u64,
pub modified_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiskStatsResponse {
pub entity_id: String,
pub disk_type: DiskTypeArg,
pub used_bytes: u64,
pub file_count: u32,
pub dir_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KanbanBoardResponse {
pub id: String,
pub entity_id: String,
pub name: String,
pub description: Option<String>,
pub column_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KanbanCardResponse {
pub id: String,
pub column_id: String,
pub title: String,
pub description: Option<String>,
pub assignee: Option<String>,
pub position: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PresenceResponse {
pub peer_id: String,
pub status: String,
pub last_seen: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallResponse {
pub id: String,
pub entity_id: String,
pub participant_count: usize,
pub started_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContactResponse {
pub id: String,
pub display_name: String,
pub four_words: Option<String>,
pub is_favourite: bool,
pub is_online: bool,
pub created_at: i64,
pub last_seen: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebsiteResponse {
pub entity_id: String,
pub html: String,
pub css: String,
pub js: String,
pub website_root_hash: String,
pub published_at: i64,
pub size_bytes: usize,
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum Subscription {
AllEvents,
EntityEvents { entity_id: String },
MessageEvents,
EntityMessages { entity_id: String },
PresenceUpdates,
InviteEvents,
NetworkingEvents,
KanbanEvents { entity_id: String },
CallEvents,
}
pub type CommandResult = Result<Vec<Event>, CommandError>;
pub type QueryResult = Result<QueryResponse, QueryError>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandError {
pub command_type: String,
pub message: String,
pub code: String,
}
impl std::fmt::Display for CommandError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {} ({})", self.command_type, self.message, self.code)
}
}
impl std::error::Error for CommandError {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryError {
pub query_type: String,
pub message: String,
pub code: String,
}
impl std::fmt::Display for QueryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {} ({})", self.query_type, self.message, self.code)
}
}
impl std::error::Error for QueryError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_command_serialization() {
let cmd = Command::CreateEntity {
name: "Test Org".to_string(),
entity_type: EntityType::Organisation,
description: Some("A test organization".to_string()),
initial_members: vec!["alice-bob-charlie-dave".to_string()],
};
let json = serde_json::to_string(&cmd).unwrap();
let deserialized: Command = serde_json::from_str(&json).unwrap();
match deserialized {
Command::CreateEntity {
name, entity_type, ..
} => {
assert_eq!(name, "Test Org");
assert_eq!(entity_type, EntityType::Organisation);
}
_ => panic!("Wrong command type"),
}
}
#[test]
fn test_event_serialization() {
let event = Event::EntityCreated {
entity_id: "abc123".to_string(),
name: "Test Org".to_string(),
entity_type: EntityType::Organisation,
created_by: "alice-bob-charlie-dave".to_string(),
};
let json = serde_json::to_string(&event).unwrap();
let deserialized: Event = serde_json::from_str(&json).unwrap();
match deserialized {
Event::EntityCreated {
entity_id, name, ..
} => {
assert_eq!(entity_id, "abc123");
assert_eq!(name, "Test Org");
}
_ => panic!("Wrong event type"),
}
}
#[test]
fn test_query_serialization() {
let query = Query::GetEntity {
entity_id: "abc123".to_string(),
};
let json = serde_json::to_string(&query).unwrap();
let deserialized: Query = serde_json::from_str(&json).unwrap();
match deserialized {
Query::GetEntity { entity_id } => {
assert_eq!(entity_id, "abc123");
}
_ => panic!("Wrong query type"),
}
}
#[test]
fn test_subscription_serialization() {
let sub = Subscription::EntityMessages {
entity_id: "abc123".to_string(),
};
let json = serde_json::to_string(&sub).unwrap();
let deserialized: Subscription = serde_json::from_str(&json).unwrap();
match deserialized {
Subscription::EntityMessages { entity_id } => {
assert_eq!(entity_id, "abc123");
}
_ => panic!("Wrong subscription type"),
}
}
}