[Fix] Split

This commit is contained in:
Alex Emmet 2026-05-04 17:04:58 +02:00
commit e5360088eb
21 changed files with 197 additions and 18 deletions

61
communities/Cargo.toml Normal file
View file

@ -0,0 +1,61 @@
[package]
name = "communities"
version = "0.1.0"
edition = "2024"
[dependencies]
ttp-core = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-core" }
ttp-native = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-native" }
iota-logger = { path = "../iota-logger" }
iota-util = { path = "../iota-util" }
iota-storage = { path = "../iota-storage" }
iota-state = { path = "../iota-state" }
iota-auth = { path = "../iota-auth" }
actix-web = { version = "4", features = ["rustls-0_23"] }
actix-web-actors = "4"
aes-gcm = "0.10.3"
async-trait = "0.1.89"
base64 = "0.22.1"
chrono = "0.4.43"
crossterm = "*"
dashmap = "6.1.0"
futures = "*"
futures-util = "*"
hex = "*"
hkdf = "0.12.4"
hyper = { version = "1.8.1", features = [
"capi",
"client",
"full",
"http1",
"http2",
"nightly",
"server",
] }
hyper-util = { version = "*" }
json = "*"
lazy_static = "1.5.0"
once_cell = "1.21.3"
open = "5.3.3"
pnet = "0.35.0"
rand = "0.8"
rand_core = { version = "0.6", features = ["getrandom", "std"] }
ratatui = "0.30.0"
reqwest = "0.13.2"
rusqlite = "0.39.0"
rustls = { version = "0.23.37", features = ["aws-lc-rs"] }
rustls-pemfile = "2.2.0"
serde_json = "1.0.149"
sha2 = "0.10.9"
strum = "0.27.2"
strum_macros = "0.27.2"
sysinfo = "0.38.3"
tokio = { version = "1.50.0", features = ["full"] }
tokio-tungstenite = { version = "*", features = ["native-tls"] }
tungstenite = "*"
uuid = { version = "*", features = ["v4"] }
walkdir = "2.5.0"
warp = "*"
x448 = { version = "*" }
zip = "6.0.0"

View file

@ -0,0 +1,356 @@
use crate::communities::interactables::category::Category;
use crate::communities::interactables::registry;
use crate::communities::perms::permission::Permission;
use crate::communities::{
community_connection::CommunityConnection, interactables::interactable::Interactable,
};
use crate::util::file_util;
use base64::{Engine as _, engine::general_purpose::STANDARD};
use json::JsonValue;
use json::number::Number;
use json::object::Object;
use rand::RngCore;
use rand_core::OsRng;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use x448::{PublicKey, Secret};
/// Permissions
// uuid -> interactable/path/like/this/interactable_name:permission
// -> role
/// Roles
// rolename -> interactable/path/like/this/interactable_name:permission
// -> other/path/like/this/interactable_name:permission
pub struct Community {
name: String,
owner_id: Arc<RwLock<i64>>,
members: Vec<i64>,
permissions: HashMap<i64, Vec<Permission>>,
roles: HashMap<String, Vec<Permission>>,
private_key: Secret,
public_key: PublicKey,
pub interactables: Arc<RwLock<Vec<Arc<Box<dyn Interactable>>>>>,
pub connections: Arc<RwLock<HashMap<i64, Vec<Arc<CommunityConnection>>>>>,
}
impl Community {
pub fn new() -> Self {
let mut buf = [0u8; 56];
let mut rng = OsRng;
rng.fill_bytes(&mut buf);
let private_key = Secret::from_bytes(&buf).unwrap();
let public_key = PublicKey::from(&private_key);
Community {
name: String::new(),
owner_id: Arc::new(RwLock::new(0)),
members: Vec::new(),
permissions: HashMap::new(),
roles: HashMap::new(),
private_key,
public_key,
interactables: Arc::new(RwLock::new(Vec::new())),
connections: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn create(name: String, owner_id: i64) -> Self {
let mut buf = [0u8; 56];
let mut rng = OsRng;
rng.fill_bytes(&mut buf);
let private_key = Secret::from_bytes(&buf).unwrap();
let public_key = PublicKey::from(&private_key);
let c = Community {
name,
owner_id: Arc::new(RwLock::new(owner_id)),
members: Vec::new(),
permissions: HashMap::new(),
roles: HashMap::new(),
private_key,
public_key,
interactables: Arc::new(RwLock::new(Vec::new())),
connections: Arc::new(RwLock::new(HashMap::new())),
};
c.save().await;
c
}
pub async fn to_json(&self) -> JsonValue {
let mut json = JsonValue::new_object();
json["name"] = self.name.clone().into();
json["owner_id"] = (*self.owner_id.read().await as i64).into();
json["members"] = self
.members
.clone()
.iter()
.map(|f| f.to_string())
.collect::<Vec<String>>()
.into();
json["private_key"] = self.private_key.as_bytes().to_vec().into();
json["public_key"] = self.public_key.as_bytes().to_vec().into();
json["connections"] = self.connections.read().await.clone().len().into();
json
}
pub async fn frontend(&self) -> JsonValue {
let mut json = JsonValue::new_object();
json["name"] = self.name.clone().into();
json["owner_id"] = (*self.owner_id.read().await as i64).into();
json["members"] = self
.members
.clone()
.iter()
.map(|f| f.to_string())
.collect::<Vec<String>>()
.into();
json["public_key"] = self.public_key.as_bytes().to_vec().into();
json["connections"] = self.connections.read().await.clone().len().into();
json
}
pub fn add_member(&mut self, member_id: i64) {
self.members.push(member_id);
}
pub fn remove_member(&mut self, member_id: i64) {
self.members.retain(|id| *id != member_id);
}
pub fn get_name(&self) -> &str {
&self.name
}
pub async fn get_owner_id(&self) -> i64 {
*self.owner_id.read().await
}
pub fn get_members(&self) -> Vec<i64> {
self.members.clone()
}
pub fn get_private_key(&self) -> Secret {
Secret::from_bytes(self.private_key.as_bytes()).unwrap()
}
pub fn get_public_key(&self) -> &PublicKey {
&self.public_key
}
pub async fn set_owner(self: Arc<Community>, owner_id: i64) {
*self.owner_id.write().await = owner_id;
}
pub async fn add_connection(self: &Arc<Self>, other: Arc<CommunityConnection>) {
let mut vec = self
.connections
.read()
.await
.get(&other.get_user_id().await)
.cloned()
.unwrap_or_default();
vec.push(other.clone());
self.connections
.write()
.await
.insert(other.get_user_id().await, vec);
}
pub async fn remove_connection(self: &Arc<Self>, other: Arc<CommunityConnection>) {
let mut vec = self
.connections
.read()
.await
.get(&other.get_user_id().await)
.cloned()
.unwrap_or_default();
vec.retain(|conn| !Arc::ptr_eq(conn, &other));
self.connections
.write()
.await
.insert(other.get_user_id().await, vec);
}
pub async fn get_connections(&self) -> HashMap<i64, Vec<Arc<CommunityConnection>>> {
self.connections.read().await.clone()
}
pub async fn get_connections_for_user(&self, user_id: i64) -> Vec<Arc<CommunityConnection>> {
self.connections
.read()
.await
.get(&user_id)
.cloned()
.unwrap_or_default()
}
pub async fn get_interactables(
&self,
_user_id: i64,
) -> Vec<Arc<Box<dyn Interactable + 'static>>> {
self.interactables.read().await.clone()
}
pub async fn add_interactable(self: &mut Arc<Self>, interactable: Arc<Box<dyn Interactable>>) {
self.interactables.write().await.push(interactable);
}
pub async fn remove_interactable(
self: &mut Arc<Self>,
interactable: Arc<Box<dyn Interactable>>,
) {
self.interactables
.write()
.await
.retain(|i| !Arc::ptr_eq(i, &interactable));
}
pub async fn broadcast_message(&self, message: &CommunicationValue) {
for (_, conns) in self.connections.read().await.clone().iter() {
for user in conns.iter() {
user.send_message(message).await;
}
}
}
pub async fn run_function(
self: &mut Arc<Self>,
_user_id: i64,
name: &str,
path: &str,
_function: &str,
cv: &CommunicationValue,
) -> CommunicationValue {
if path.is_empty() {
let target_interactables = &self.interactables.read().await.clone();
for interactable in target_interactables.iter() {
if interactable.get_name() == name {
if interactable.get_codec() == "category" {
return CommunicationValue::new(CommunicationType::error);
} else {
// cannot move a value of type dyn Interactable the size of dyn Interactable cannot be statically determined (rustc E0161)
return interactable.run_function(cv.clone()).await;
}
}
}
} else {
let target_interactables = &self.interactables.read().await.clone();
for interactable in target_interactables.iter() {
if interactable.get_name() == name {
if interactable.get_codec() == "category" {
let category: &Category =
interactable.as_any().downcast_ref::<Category>().unwrap();
// cannot move a value of type dyn Interactable the size of dyn Interactable cannot be statically determined (rustc E0161)
return category
.get_child(path.to_string(), name.to_string())
.unwrap()
.run_function(cv.clone())
.await;
} else {
return CommunicationValue::new(CommunicationType::error);
}
}
}
}
CommunicationValue::new(CommunicationType::add_conversation)
}
pub async fn save(&self) {
let mut json = Object::new();
json.insert("name", JsonValue::String(self.name.clone()));
json.insert(
"owner_id",
JsonValue::Number(Number::from(*self.owner_id.read().await as i64)),
);
json.insert(
"private_key",
JsonValue::String(STANDARD.encode(&self.private_key.as_bytes())),
);
json.insert(
"public_key",
JsonValue::String(STANDARD.encode(&self.public_key.as_bytes())),
);
file_util::save_file(
&format!("communities/{}/", self.name),
"config.json",
&json.dump(),
);
let mut user_data = Object::new();
for user in self.members.iter() {
let mut data = JsonValue::new_object();
let mut permissions = JsonValue::new_array();
for perm in self.permissions.get(user).unwrap() {
if let Ok(_) = permissions.push(perm.to_string()) {}
}
if let Ok(_) = data.insert("permissions", permissions) {}
user_data.insert(&user.to_string(), data);
}
file_util::save_file(
&format!("communities/{}/", self.name),
"users.json",
&user_data.dump(),
);
for interactable in self.interactables.read().await.clone().iter() {
registry::save(interactable).await;
}
}
}
pub async fn load(name: &String) -> Option<Arc<Community>> {
let file_contents = file_util::load_file(&format!("communities/{}/", name), "config.json");
let json_content = json::parse(&file_contents).unwrap();
let user_data = file_util::load_file(&format!("communities/{}/", name), "users.json");
let user_json: JsonValue = json::parse(&user_data).unwrap();
let mut users = Vec::new();
let mut permissions: HashMap<i64, Vec<Permission>> = HashMap::new();
for user in user_json.entries() {
let (id, json): (&str, &JsonValue) = user;
let perms_j = &json["permissions"];
let perms = Vec::new();
for _ in perms_j.entries() {
// let perm_j = i.as_str().unwrap();
// perms.push(perm_j.to_string());
}
let user_id: i64 = id.parse().unwrap_or(0);
users.push(user_id);
permissions.insert(user_id, perms);
}
let role_data = file_util::load_file(&format!("communities/{}/", name), "roles.json");
let roles: HashMap<String, Vec<Permission>> = HashMap::new();
if let Ok(_) = json::parse(&role_data) {
// Fill roles
} else {
return None;
};
let community = Community {
name: json_content["name"].as_str().unwrap().to_string(),
owner_id: Arc::new(RwLock::new(json_content["owner_id"].as_i64().unwrap_or(0))),
members: users,
roles,
permissions,
private_key: Secret::from_bytes(
&STANDARD
.decode(json_content["private_key"].as_str().unwrap())
.unwrap(),
)
.unwrap(),
public_key: PublicKey::from(
&Secret::from_bytes(
&STANDARD
.decode(json_content["private_key"].as_str().unwrap())
.unwrap(),
)
.unwrap(),
),
interactables: Arc::new(RwLock::new(Vec::new())),
connections: Arc::new(RwLock::new(HashMap::new())),
};
let mut comarc = Arc::new(community);
let interactable_files: Vec<String> =
file_util::get_children(&format!("communities/{}/interactables/", name));
for file in interactable_files {
if file.contains(".json") {
let name = file.split('.').next().unwrap().to_string();
let interactable: Box<dyn Interactable> =
registry::load(comarc.clone(), String::new(), name).await;
comarc.add_interactable(Arc::new(interactable)).await;
}
}
Some(comarc)
}

View file

@ -0,0 +1,396 @@
use crate::auth::auth_user::AuthUser;
use crate::communities::community::Community;
use crate::communities::interactables::interactable::Interactable;
use crate::users::user_manager::get_user;
use aes_gcm::{Aes256Gcm, KeyInit, Nonce, aead::Aead};
use base64::{Engine as _, engine::general_purpose::STANDARD};
use futures::SinkExt;
use futures::stream::SplitSink;
use futures::stream::SplitStream;
use hkdf::Hkdf;
use hyper::upgrade::Upgraded;
use hyper_util::rt::TokioIo;
use json::JsonValue;
use json::number::Number;
use rand::{Rng, distributions::Alphanumeric};
use sha2::Sha256;
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio_tungstenite::WebSocketStream;
use tungstenite::Message;
use tungstenite::Utf8Bytes;
use uuid::Uuid;
use x448::PublicKey;
pub struct CommunityConnection {
pub sender: Arc<RwLock<SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>>>,
pub receiver: Arc<RwLock<SplitStream<WebSocketStream<TokioIo<Upgraded>>>>>,
pub user_id: Arc<RwLock<i64>>,
pub community: Arc<RwLock<Option<Arc<Community>>>>,
identified: Arc<RwLock<bool>>,
challenged: Arc<RwLock<bool>>,
challenge: Arc<RwLock<String>>,
auth: Arc<RwLock<Option<AuthUser>>>,
pub ping: Arc<RwLock<i64>>,
}
impl CommunityConnection {
pub fn new(
sender: SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>,
receiver: SplitStream<WebSocketStream<TokioIo<Upgraded>>>,
community: Arc<Community>,
) -> Arc<Self> {
Arc::new(Self {
sender: Arc::new(RwLock::new(sender)),
receiver: Arc::new(RwLock::new(receiver)),
user_id: Arc::new(RwLock::new(0)),
community: Arc::new(RwLock::new(Some(community))),
identified: Arc::new(RwLock::new(false)),
challenged: Arc::new(RwLock::new(false)),
challenge: Arc::new(RwLock::new(String::new())),
auth: Arc::new(RwLock::new(None)),
ping: Arc::new(RwLock::new(-1)),
})
}
pub async fn send_message(&self, message: &CommunicationValue) {
let mut sender = self.sender.write().await; // Access the SplitSink
let message_text = Message::Text(Utf8Bytes::from(message.to_json().to_string()));
sender.send(message_text).await.unwrap(); // Send the message via the SplitSink
}
pub async fn get_community(&self) -> Option<Arc<Community>> {
self.community.read().await.clone()
}
pub async fn get_user_id(&self) -> i64 {
*self.user_id.read().await
}
pub async fn is_identified(&self) -> bool {
*self.identified.read().await && *self.challenged.read().await
}
pub async fn handle_message(self: Arc<Self>, message: String) {
let mut cv = CommunicationValue::from_json(&message);
let user_id = self.get_user_id().await;
cv = cv.with_sender(user_id);
if cv.is_type(CommunicationType::identification) && !self.is_identified().await {
self.handle_identification(cv).await;
return;
}
if cv.is_type(CommunicationType::challenge_response) && !self.is_identified().await {
self.handle_challenge_response(cv).await;
return;
}
if !self.is_identified().await {
return;
}
if cv.is_type(CommunicationType::ping) {
self.handle_ping(cv).await;
return;
}
if cv.is_type(CommunicationType::client_changed) {
//self.handle_client_changed(cv).await;
return;
}
if cv.is_type(CommunicationType::function) {
self.handle_function(cv).await;
return;
}
}
async fn handle_function(&self, cv: CommunicationValue) {
let name = cv.get_data(DataTypes::name).unwrap().as_str().unwrap();
let path = cv.get_data(DataTypes::path).unwrap().as_str().unwrap();
let function = cv.get_data(DataTypes::function).unwrap().as_str().unwrap();
let result = self
.get_community()
.await
.unwrap()
.run_function(self.get_user_id().await, name, path, function, &cv)
.await;
self.send_message(&result).await;
}
async fn handle_identification(&self, cv: CommunicationValue) {
let user_id = cv
.get_data(DataTypes::user_id)
.unwrap_or(&JsonValue::Number(Number::from(0)))
.as_i64()
.unwrap_or(0);
let Some(user) = get_user(user_id) else {
self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id)
.await;
return;
};
{
let mut auth_guard = self.auth.write().await;
//*auth_guard = Some(user.clone());
let mut user_id_guard = self.user_id.write().await;
*user_id_guard = user_id;
let mut identified_guard = self.identified.write().await;
*identified_guard = true;
}
let challenge_str: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(32)
.map(char::from)
.collect();
{
let mut challenge_guard = self.challenge.write().await;
*challenge_guard = challenge_str.clone();
}
let user_public_key_bytes = match STANDARD.decode(&user.public_key) {
Ok(bytes) => bytes,
Err(_) => {
self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id)
.await;
return;
}
};
let user_pub_key: PublicKey = match PublicKey::from_bytes(&user_public_key_bytes) {
Some(key) => key,
__ => {
self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id)
.await;
return;
}
};
let Some(community) = self.community.read().await.clone() else {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
};
let community_private_key = community.get_private_key();
let community_public_key = community.get_public_key();
let shared_secret = match community_private_key.to_diffie_hellman(&user_pub_key) {
Some(secret) => secret,
_ => {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
};
let aes_key = {
let hk = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
let mut key_bytes = [0u8; 32];
hk.expand(b"challenge", &mut key_bytes)
.expect("HKDF failure");
key_bytes
};
let cipher = Aes256Gcm::new_from_slice(&aes_key).expect("AES init failed");
let nonce_bytes: [u8; 12] = rand::random();
let nonce = Nonce::from_slice(&nonce_bytes);
let encrypted_challenge = match cipher.encrypt(nonce, challenge_str.as_bytes()) {
Ok(data) => data,
Err(_) => {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
};
let mut encrypted_out = nonce_bytes.to_vec();
encrypted_out.extend(encrypted_challenge);
let response = CommunicationValue::new(CommunicationType::challenge)
.add_data_str(
DataTypes::public_key,
STANDARD.encode(community_public_key.as_bytes()),
)
.add_data_str(DataTypes::challenge, STANDARD.encode(&encrypted_out))
.with_id(cv.get_id());
self.send_message(&response).await;
}
async fn handle_challenge_response(self: Arc<Self>, cv: CommunicationValue) {
let client_challenge_response_b64 = match cv.get_data(DataTypes::challenge) {
Some(data) => data.to_string(),
_ => {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
};
let challenge_response_bytes = match STANDARD.decode(&client_challenge_response_b64) {
Ok(bytes) => bytes,
Err(_) => {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
};
if challenge_response_bytes.len() < 12 {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
let Some(user) = self.auth.read().await.clone() else {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
};
let Some(user_pub_bytes) = STANDARD.decode(&user.public_key).ok() else {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
};
let Some(user_pub_key) = PublicKey::from_bytes(&user_pub_bytes) else {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
};
let Some(community) = self.community.read().await.clone() else {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
};
let community_private_key = community.get_private_key();
let shared_secret = match community_private_key.to_diffie_hellman(&user_pub_key) {
Some(secret) => secret,
_ => {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
};
let aes_key = {
use hkdf::Hkdf;
use sha2::Sha256;
let hk = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
let mut key_bytes = [0u8; 32];
hk.expand(b"challenge", &mut key_bytes)
.expect("HKDF failure");
key_bytes
};
let (nonce_bytes, ciphertext) = challenge_response_bytes.split_at(12);
let nonce = Nonce::from_slice(nonce_bytes);
let cipher = Aes256Gcm::new_from_slice(&aes_key).expect("AES init failed");
let decrypted_bytes = match cipher.decrypt(nonce, ciphertext) {
Ok(pt) => pt,
Err(_) => {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
};
let client_response = match String::from_utf8(decrypted_bytes) {
Ok(str) => str,
Err(_) => {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
};
let expected_challenge = self.challenge.read().await.clone();
if client_response != expected_challenge {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
self.close().await;
return;
}
{
let mut authenticated_guard = self.challenged.write().await;
*authenticated_guard = true;
}
let Some(community) = self.community.read().await.clone() else {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
};
let arc = Arc::new(community);
let user_id = self.get_user_id().await;
if user_id == 0 {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
arc.add_connection(self.clone()).await;
let response = CommunicationValue::new(CommunicationType::identification_response)
.add_data(DataTypes::interactables, {
let a: Vec<Arc<Box<dyn Interactable>>> = arc.get_interactables(user_id).await;
let mut c: JsonValue = JsonValue::new_object();
for b in a {
let mut subject = JsonValue::new_object();
subject["codec"] = JsonValue::String(b.get_codec());
subject["data"] = b.get_data();
c[b.get_name()] = subject;
}
c
})
.with_id(cv.get_id());
self.send_message(&response).await;
}
async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) {
let error = CommunicationValue::new(error_type).with_id(*message_id);
self.send_message(&error).await;
}
pub async fn close(&self) {
let mut sender = self.sender.write().await;
let _ = sender.close().await;
}
pub async fn handle_close(self: Arc<Self>) {
if self.is_identified().await {
if self.get_user_id().await != 0 {
self.community
.read()
.await
.as_ref()
.unwrap()
.remove_connection(self.clone())
.await;
}
}
}
async fn handle_ping(&self, cv: CommunicationValue) {
if let Some(last_ping) = cv.get_data(DataTypes::last_ping) {
if let Ok(ping_val) = last_ping.to_string().parse::<i64>() {
let mut ping_guard = self.ping.write().await;
*ping_guard = ping_val;
}
}
let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id());
self.send_message(&response).await;
}
}

View file

@ -0,0 +1,59 @@
use crate::communities::community::{self, Community};
use crate::log;
use crate::util::file_util;
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
pub static COMMUNITY_REGISTRY: Lazy<Arc<Mutex<HashMap<String, Arc<Community>>>>> =
Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
pub async fn add_community(community: Arc<Community>) {
COMMUNITY_REGISTRY
.lock()
.await
.insert(community.get_name().to_string(), community);
}
pub async fn remove_community(name: &str) {
COMMUNITY_REGISTRY.lock().await.remove(name);
}
pub async fn clear() {
COMMUNITY_REGISTRY.lock().await.clear();
}
pub async fn get_community(name: &str) -> Option<Arc<Community>> {
if let Some(c) = COMMUNITY_REGISTRY.lock().await.get(name) {
Some(c.clone())
} else {
None
}
}
pub async fn load_communities() {
let community_names = file_util::get_children("communities");
for name in community_names {
if let Some(community) = community::load(&name).await {
add_community(community).await;
} else {
log!("failed to load the {} community", &name);
}
}
}
pub async fn save_communities() {
for community in COMMUNITY_REGISTRY.lock().await.values() {
community.save().await;
}
}
pub async fn get_communities() -> Vec<Arc<Community>> {
COMMUNITY_REGISTRY.lock().await.values().cloned().collect()
}
pub async fn rename_community(old_name: &str, new_name: &str) {
if let Some(community) = COMMUNITY_REGISTRY.lock().await.remove(old_name) {
COMMUNITY_REGISTRY
.lock()
.await
.insert(new_name.to_string(), community);
}
}

View file

@ -0,0 +1,121 @@
use crate::communities::{community::Community, interactables::interactable::Interactable};
use async_trait::async_trait;
use json::JsonValue;
use std::any::Any;
use std::sync::Arc;
use ttp_core::CommunicationValue;
use uuid::Uuid;
pub struct Category {
id: Uuid,
name: String,
path: String,
community: Arc<Community>,
children: Vec<Arc<Box<dyn Interactable>>>,
}
impl Category {
pub fn new() -> Category {
Category {
id: Uuid::new_v4(),
name: String::new(),
path: String::new(),
community: Arc::new(Community::new()),
children: Vec::new(),
}
}
pub fn get_child(&self, path: String, name: String) -> Option<Arc<Box<dyn Interactable>>> {
if path.is_empty() {
self.children
.iter()
.find(|child| child.get_name() == &name)
.cloned()
} else {
let sub_module = path.split("/").next().unwrap();
let next = self
.children
.iter()
.find(|child| child.get_name() == sub_module)
.unwrap();
if next.get_codec() == "category" {
let next_cat = next.as_any().downcast_ref::<Category>().unwrap();
next_cat.get_child(path, name)
} else {
Some(next.clone())
}
}
}
pub fn get_children(&self) -> Vec<Arc<Box<dyn Interactable>>> {
self.children.iter().map(|child| child.clone()).collect()
}
}
#[async_trait]
impl Interactable for Category {
fn get_id(&self) -> &Uuid {
&self.id
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn get_codec(&self) -> String {
"category".to_string()
}
fn set_name(&mut self, name: String) {
self.name = name;
}
fn set_path(&mut self, path: String) {
self.path = path;
}
fn get_community(&self) -> &Arc<Community> {
&self.community
}
fn set_community(&mut self, community: Arc<Community>) {
self.community = community;
}
fn get_name(&self) -> &String {
&self.name
}
fn get_path(&self) -> &String {
&self.path
}
fn get_total_path(&self) -> String {
String::new() + &self.path + "/" + &self.name
}
fn get_data(&self) -> JsonValue {
let mut v = JsonValue::new_object();
for child in &self.children {
let mut subject = JsonValue::new_object();
subject["codec"] = JsonValue::String(child.get_codec());
subject["data"] = child.get_data();
v[child.get_name()] = subject;
}
v
}
async fn run_function(&self, _cv: CommunicationValue) -> CommunicationValue {
CommunicationValue::new(CommunicationType::error)
}
fn to_json(&self) -> JsonValue {
let mut v = JsonValue::new_object();
v["children"] = JsonValue::new_array();
for child in &self.children {
let _ = v["children"].push(child.to_json());
}
v
}
fn load(
&mut self,
community: Arc<Community>,
id: Uuid,
path: String,
name: String,
_json: &JsonValue,
) {
self.community = community;
self.id = id;
self.name = name;
self.path = path;
}
}

View file

@ -0,0 +1,35 @@
use crate::communities::community::Community;
use async_trait::async_trait;
use json::JsonValue;
use std::any::Any;
use std::sync::Arc;
use ttp_core::CommunicationValue;
use uuid::Uuid;
pub type InteractableFactory = fn() -> Box<dyn Interactable>;
#[async_trait]
pub trait Interactable: Send + Sync + Any {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn get_codec(&self) -> String;
fn get_name(&self) -> &String;
fn get_path(&self) -> &String;
fn get_total_path(&self) -> String;
fn set_name(&mut self, name: String);
fn set_path(&mut self, path: String);
fn get_community(&self) -> &Arc<Community>;
fn set_community(&mut self, community: Arc<Community>);
async fn run_function(&self, cv: CommunicationValue) -> CommunicationValue;
fn get_data(&self) -> JsonValue;
fn get_id(&self) -> &Uuid;
fn to_json(&self) -> JsonValue;
fn load(
&mut self,
community: Arc<Community>,
id: Uuid,
path: String,
name: String,
json: &JsonValue,
);
}

View file

@ -0,0 +1,75 @@
use crate::communities::community::Community;
use crate::communities::interactables::category::Category;
use crate::communities::interactables::interactable::{Interactable, InteractableFactory};
use crate::communities::interactables::text_chat::TextChat;
use crate::communities::interactables::voice_chat::VoiceChat;
use crate::util::file_util;
use json::JsonValue;
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
use uuid::Uuid;
pub static INTERACTABLE_REGISTRY: Lazy<Arc<Mutex<HashMap<String, InteractableFactory>>>> =
Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
pub async fn load_interactables() {
INTERACTABLE_REGISTRY
.lock()
.await
.insert(TextChat::new().get_codec(), || {
Box::new(TextChat::new()) as Box<dyn Interactable>
});
INTERACTABLE_REGISTRY
.lock()
.await
.insert(VoiceChat::new().get_codec(), || {
Box::new(VoiceChat::new()) as Box<dyn Interactable>
});
INTERACTABLE_REGISTRY
.lock()
.await
.insert(Category::new().get_codec(), || {
Box::new(Category::new()) as Box<dyn Interactable>
});
}
pub async fn register_interactable(name: String, interactable: InteractableFactory) {
INTERACTABLE_REGISTRY
.lock()
.await
.insert(name.to_string(), interactable)
.unwrap();
}
pub async fn get_interactable(name: &str) -> Box<dyn Interactable> {
INTERACTABLE_REGISTRY.lock().await.get(name).unwrap()()
}
pub async fn save(interactable: &Arc<Box<dyn Interactable>>) {
let mut json_object: JsonValue = interactable.to_json().clone();
json_object["codec"] = JsonValue::String(interactable.get_codec());
json_object["id"] = JsonValue::String(interactable.get_id().to_string());
file_util::save_file(
&format!(
"communities/{}/interactables/{}",
interactable.get_community().get_name(),
interactable.get_path()
),
&format!("{}.json", interactable.get_name()),
&json_object.to_string(),
);
}
pub async fn load(
c: Arc<Community>,
path: String,
name: String,
) -> Box<dyn Interactable + 'static> {
let s = file_util::load_file(
&format!("communities/{}/interactables/{}", c.get_name(), path),
&format!("{}.json", name),
);
let json_object: JsonValue = json::parse(&s).unwrap();
let codec: String = json_object["codec"].as_str().unwrap().to_string();
let id: String = json_object["id"].as_str().unwrap().to_string();
let mut interactable = get_interactable(&codec).await;
interactable.load(c, Uuid::parse_str(&id).unwrap(), path, name, &json_object);
interactable
}

View file

@ -0,0 +1,261 @@
use crate::{
communities::{
community::Community, community_connection::CommunityConnection,
interactables::interactable::Interactable,
},
log,
util::file_util::{get_children, load_file, save_file},
};
use async_trait::async_trait;
use json::{JsonValue, array, object};
use std::fs;
use std::sync::Arc;
use std::{any::Any, collections::HashMap};
use ttp_core::{CommunicationType, CommunicationValue, DataTypes};
use uuid::Uuid;
pub struct TextChat {
id: Uuid,
name: String,
path: String,
community: Arc<Community>,
}
impl TextChat {
pub fn new() -> TextChat {
TextChat {
id: Uuid::new_v4(),
name: String::new(),
path: String::new(),
community: Arc::new(Community::new()),
}
}
pub fn add_message(&self, send_time: u128, sender: i64, message: &str) {
let user_dir = &format!(
"communities/{}/interactables/{}/{}",
self.get_community().get_name(),
self.get_path(),
self.get_name()
);
if let Err(e) = fs::create_dir_all(user_dir) {
log!("Failed to create chat directory: {}", e);
return;
}
let mut chunk_index = 0;
let mut message_chunk = array![];
// find latest chunk not full (max 800 msgs)
loop {
let file_name = format!("msgs_{}.json", chunk_index);
let file_content = load_file(&user_dir, &file_name);
if !file_content.is_empty() {
if let Ok(current_chunk) = json::parse(&file_content) {
if current_chunk.is_array() && current_chunk.len() < 800 {
message_chunk = current_chunk;
break;
}
} else {
log!("Failed to parse existing JSON file: {}", file_name);
}
} else {
break;
}
chunk_index += 1;
if chunk_index > 1000 {
log!("Too many message chunks. Aborting add.");
return;
}
}
let json_obj = object! {
"timestamp" => send_time as i64,
"content" => message,
"sender" => sender.to_string(),
};
if let Err(e) = message_chunk.push(json_obj) {
log!("Failed to push new message into JSON array: {}", e);
return;
}
let file_name = format!("msgs_{}.json", chunk_index);
log!("Saving message to {}/{}", user_dir, file_name);
save_file(&user_dir, &file_name, &message_chunk.dump());
}
pub fn get_messages(&self, loaded_messages: i64, amount: i64) -> JsonValue {
let mut messages = array![];
let mut latest_chunk_index: i32 = -1;
let files = get_children(&format!(
"communities/{}/interactables/{}/{}",
self.get_community().get_name(),
self.get_path(),
self.get_name()
));
for entry in files {
if let Some(num) = {
entry
.strip_prefix("msgs_")
.and_then(|s| s.strip_suffix(".json"))
} {
if let Ok(index) = num.parse::<i32>() {
if index > latest_chunk_index {
latest_chunk_index = index;
}
}
}
}
if latest_chunk_index == -1 {
return messages;
}
let mut to_skip = loaded_messages;
let mut needed = amount;
for chunk_index in (0..=latest_chunk_index).rev() {
if needed == 0 {
break;
}
let file_name = format!("msgs_{}.json", chunk_index);
let file_content = load_file(
&format!(
"communities/{}/interactables/{}/{}",
self.get_community().get_name(),
self.get_path(),
self.get_name()
),
&file_name,
);
if file_content.is_empty() {
continue;
}
if let Ok(chunk) = json::parse(&file_content) {
for i in (0..chunk.len()).rev() {
if needed == 0 {
break;
}
if to_skip > 0 {
to_skip -= 1;
continue;
}
messages.push(chunk[i].clone()).unwrap();
needed -= 1;
}
}
}
messages
}
}
#[async_trait]
impl Interactable for TextChat {
fn get_id(&self) -> &Uuid {
&self.id
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn get_codec(&self) -> String {
"text".to_string()
}
fn set_name(&mut self, name: String) {
self.name = name;
}
fn set_path(&mut self, path: String) {
self.path = path;
}
fn get_community(&self) -> &Arc<Community> {
&self.community
}
fn set_community(&mut self, community: Arc<Community>) {
self.community = community;
}
fn get_name(&self) -> &String {
&self.name
}
fn get_path(&self) -> &String {
&self.path
}
fn get_total_path(&self) -> String {
String::new() + &self.path + "/" + &self.name
}
fn get_data(&self) -> JsonValue {
JsonValue::new_object()
}
async fn run_function(&self, cv: CommunicationValue) -> CommunicationValue {
let payload = cv.get_data(DataTypes::payload).as_container().unwrap();
if cv.get_data(DataTypes::function).as_str().unwrap() == "get_messages" {
let amount = payload.get(DataTypes::amount).as_i64().unwrap();
let loaded_messages = payload["loaded_messages"].as_i64().unwrap();
let messages = self.get_messages(loaded_messages, amount).clone();
let mut payload = JsonValue::new_object();
payload["messages"] = messages;
return CommunicationValue::new(CommunicationType::function)
.with_id(cv.get_id())
.add_data_str(DataTypes::name, self.name.clone())
.add_data_str(DataTypes::path, self.path.clone())
.add_data_str(DataTypes::result, "message_chunk".to_string())
.add_data(DataTypes::payload, payload);
}
if cv.get_data(DataTypes::function).unwrap().as_str().unwrap() == "send_message" {
let message = payload["message"].as_str().unwrap();
let milliseconds_timestamp: u128 = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis();
self.add_message(milliseconds_timestamp, cv.get_sender(), message);
let mut distribution_payload = JsonValue::new_object();
distribution_payload["message"] = JsonValue::String(message.to_string());
distribution_payload["sender_id"] = JsonValue::String(cv.get_sender().to_string());
distribution_payload["send_time"] =
JsonValue::String(milliseconds_timestamp.to_string());
let distribution = CommunicationValue::new(CommunicationType::update)
.with_id(cv.get_id())
.add_data_str(DataTypes::name, self.name.clone())
.add_data_str(DataTypes::path, self.path.clone())
.add_data_str(DataTypes::result, "message_live".to_string())
.add_data(DataTypes::payload, distribution_payload);
let connections: HashMap<i64, Vec<Arc<CommunityConnection>>> =
self.get_community().get_connections().await.clone();
for con in connections.values() {
for c in con {
let cd: &Arc<CommunityConnection> = c;
cd.send_message(&distribution).await;
}
}
return CommunicationValue::new(CommunicationType::function)
.with_id(cv.get_id())
.add_data_str(DataTypes::name, self.name.clone())
.add_data_str(DataTypes::path, self.path.clone())
.add_data_str(DataTypes::result, "message_received".to_string())
.add_data(DataTypes::payload, JsonValue::new_object());
}
CommunicationValue::new(CommunicationType::error).with_id(cv.get_id())
}
fn to_json(&self) -> JsonValue {
JsonValue::new_object()
}
fn load(
&mut self,
community: Arc<Community>,
id: Uuid,
path: String,
name: String,
_json: &JsonValue,
) {
self.community = community;
self.id = id;
self.name = name;
self.path = path;
}
}

View file

@ -0,0 +1,187 @@
use crate::communities::{community::Community, interactables::interactable::Interactable};
use async_trait::async_trait;
use json::JsonValue;
use std::sync::Arc;
use std::{any::Any, sync::RwLock};
use uuid::Uuid;
pub enum CallUserState {
Active,
Muted,
Deafed,
}
impl CallUserState {
pub fn parse(state: &str) -> CallUserState {
match state {
"active" => CallUserState::Active,
"muted" => CallUserState::Muted,
"deafed" => CallUserState::Deafed,
_ => CallUserState::Active,
}
}
pub fn to_string(&self) -> String {
match self {
CallUserState::Active => "active".to_string(),
CallUserState::Muted => "muted".to_string(),
CallUserState::Deafed => "deafed".to_string(),
}
}
}
pub struct CallUser {
pub user_id: Uuid,
pub user_state: CallUserState,
pub streaming: bool,
}
pub struct VoiceChat {
id: Uuid,
name: String,
path: String,
community: Arc<Community>,
users: RwLock<Vec<CallUser>>,
}
impl VoiceChat {
pub fn new() -> VoiceChat {
VoiceChat {
id: Uuid::new_v4(),
name: String::new(),
path: String::new(),
community: Arc::new(Community::new()),
users: RwLock::new(Vec::new()),
}
}
pub fn update_user_state(
self: Arc<Self>,
user_id: Uuid,
state: CallUserState,
streaming: bool,
) {
if let Some(user) = self
.users
.write()
.unwrap()
.iter_mut()
.find(|u| u.user_id == user_id)
{
user.user_state = state;
user.streaming = streaming;
}
}
}
#[async_trait]
impl Interactable for VoiceChat {
fn get_id(&self) -> &Uuid {
&self.id
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn get_codec(&self) -> String {
"voice".to_string()
}
fn set_name(&mut self, name: String) {
self.name = name;
}
fn set_path(&mut self, path: String) {
self.path = path;
}
fn get_community(&self) -> &Arc<Community> {
&self.community
}
fn set_community(&mut self, community: Arc<Community>) {
self.community = community;
}
fn get_name(&self) -> &String {
&self.name
}
fn get_path(&self) -> &String {
&self.path
}
fn get_total_path(&self) -> String {
String::new() + &self.path + "/" + &self.name
}
fn get_data(&self) -> JsonValue {
let mut data = JsonValue::new_object();
let mut active_users = JsonValue::new_object();
for user in self.users.read().unwrap().iter() {
let mut user_data = JsonValue::new_object();
let _ = user_data.insert("state", JsonValue::String(user.user_state.to_string()));
let _ = user_data.insert("streaming", JsonValue::Boolean(user.streaming));
let _ = active_users.insert(&user.user_id.to_string(), user_data);
}
let _ = data.insert("active_users", active_users);
data
}
async fn run_function(&self, cv: CommunicationValue) -> CommunicationValue {
let payload = cv.get_data(DataTypes::payload).unwrap();
let function = cv.get_data(DataTypes::function).unwrap().as_str().unwrap();
if function == "get_call" {
let sender_id = payload["sender_id"].as_str().unwrap();
let message_id = payload["message"].as_str().unwrap();
let send_time = payload["send_time"].as_str().unwrap();
let mut response_payload = JsonValue::new_object();
response_payload["sender_id"] = JsonValue::String(sender_id.to_string());
response_payload["message"] = JsonValue::String(message_id.to_string());
response_payload["send_time"] = JsonValue::String(send_time.to_string());
return CommunicationValue::new(CommunicationType::function)
.with_id(cv.get_id())
.add_data_str(DataTypes::name, self.name.clone())
.add_data_str(DataTypes::path, self.path.clone())
.add_data_str(DataTypes::result, "getting_call".to_string())
.add_data(DataTypes::payload, response_payload);
}
if function == "update_user_state" {
let user_id = payload["user_id"].as_str().unwrap();
let state = payload["state"].as_str().unwrap();
let streaming = payload["streaming"].as_bool().unwrap();
if let Some(user) = self
.users
.write()
.unwrap()
.iter_mut()
.find(|u| u.user_id == Uuid::parse_str(user_id).unwrap())
{
user.user_state = CallUserState::parse(state);
user.streaming = streaming;
}
let mut response_payload = JsonValue::new_object();
response_payload["user_id"] = JsonValue::String(user_id.to_string());
response_payload["state"] = JsonValue::String(state.to_string());
response_payload["streaming"] = JsonValue::Boolean(streaming);
return CommunicationValue::new(CommunicationType::update)
.with_id(cv.get_id())
.add_data_str(DataTypes::name, self.name.clone())
.add_data_str(DataTypes::path, self.path.clone())
.add_data_str(DataTypes::result, "user_changed".to_string())
.add_data(DataTypes::payload, response_payload);
}
CommunicationValue::new(CommunicationType::error).with_id(cv.get_id())
}
fn to_json(&self) -> JsonValue {
let v = JsonValue::new_object();
v
}
fn load(
&mut self,
community: Arc<Community>,
id: Uuid,
path: String,
name: String,
_json: &JsonValue,
) {
self.community = community;
self.id = id;
self.name = name;
self.path = path;
}
}

13
communities/src/lib.rs Normal file
View file

@ -0,0 +1,13 @@
pub mod community_manager;
pub mod interactables {
pub mod category;
pub mod interactable;
pub mod registry;
pub mod text_chat;
pub mod voice_chat;
}
pub mod community;
pub mod community_connection;
pub mod perms {
pub mod permission;
}

View file

@ -0,0 +1,27 @@
use json::JsonValue;
use uuid::Uuid;
pub struct Permission {
pub id: Uuid,
pub name: String,
}
impl Permission {
pub fn new(id: Uuid, name: String) -> Self {
Permission { id, name }
}
pub fn to_json(&self) -> JsonValue {
json::object! {
"id" => self.id.to_string(),
"name" => self.name.clone()
}
}
pub fn from_json(json: JsonValue) -> Self {
Permission {
id: Uuid::parse_str(json["id"].as_str().unwrap()).unwrap(),
name: json["name"].as_str().unwrap().to_string(),
}
}
pub fn to_string(&self) -> String {
self.to_json().as_str().unwrap().to_string()
}
}