[Add] Structure

This commit is contained in:
Alex Emmet 2026-07-20 22:21:43 +02:00
commit 826fb9ce50
44 changed files with 2210 additions and 2721 deletions

View file

@ -0,0 +1,43 @@
use super::super::omikron_connection::{OmikronConnection, OmikronResult};
use crate::{
db::{iota_repo, user_repo},
models::{IotaId, UserId},
};
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
use std::sync::Arc;
async fn delete(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
result: impl std::future::Future<Output = crate::error::Result<()>>,
) -> OmikronResult<()> {
let response = match result.await {
Ok(()) => CommunicationValue::new(CommunicationType::Success),
Err(error) => CommunicationValue::new(CommunicationType::ErrorInternal)
.add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())),
};
connection.send(&response.with_id(value.get_id())).await
}
pub async fn user(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
) -> OmikronResult<()> {
delete(
connection,
value.clone(),
user_repo::delete_user(UserId::from(value.get_sender() as i64)),
)
.await
}
pub async fn iota(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
) -> OmikronResult<()> {
delete(
connection,
value.clone(),
iota_repo::delete_iota(IotaId::from(value.get_sender() as i64)),
)
.await
}

View file

@ -0,0 +1 @@
/* Call and WebRTC commands are reserved for the transport extensions that define those protocol values. */

View file

@ -0,0 +1,21 @@
use super::super::omikron_connection::{OmikronConnection, OmikronResult};
use crate::server::short_link::add_short_link;
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
use std::sync::Arc;
pub async fn shorten(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
) -> OmikronResult<()> {
let link = value
.get_data(DataType::Link)
.as_str()
.ok_or(crate::error::OmegaError::InvalidResponse)?;
let short = add_short_link(link)
.await
.map_err(|_| crate::error::OmegaError::Transport("short link error".to_string()))?;
let response = CommunicationValue::new(CommunicationType::ShortenLink)
.with_id(value.get_id())
.add_typed_default(DataType::Link, DataValue::Str(short));
connection.send(&response).await
}

View file

@ -0,0 +1 @@
/* Message delivery remains in the connection dispatcher until the protocol exposes a separate message handler contract. */

View file

@ -0,0 +1,9 @@
pub mod account;
pub mod calls;
pub mod links;
pub mod messaging;
pub mod notifications;
pub mod presence;
pub mod register;
pub mod states;
pub mod user_data;

View file

@ -0,0 +1,116 @@
use super::super::omikron_connection::{OmikronConnection, OmikronResult};
use crate::{db::notification_repo, log, models::UserId};
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
use mtp::type_map::TypeMap;
use std::sync::Arc;
pub async fn get(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
) -> OmikronResult<()> {
let notifications =
match notification_repo::get_notifications(UserId::from(value.get_sender() as i64)).await {
Ok(items) => items
.into_iter()
.map(|item| {
let tm = TypeMap::latest();
let Some(sender) = DataType::SenderId.try_to_id(&tm) else {
return DataValue::Container(Vec::new());
};
let Some(amount) = DataType::Amount.try_to_id(&tm) else {
return DataValue::Container(Vec::new());
};
DataValue::Container(vec![
(sender, DataValue::SignedNumber(item.sender_id.0.into())),
(amount, DataValue::SignedNumber(item.amount.into())),
])
})
.collect(),
Err(error) => {
log!(
crate::util::logger::PrintType::General,
"SQL get_notifications error: {}",
error
);
Vec::new()
}
};
let response = CommunicationValue::new(CommunicationType::GetNotifications)
.with_id(value.get_id())
.add_typed_default(DataType::Notifications, DataValue::Array(notifications));
connection.send(&response).await
}
pub async fn read(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
) -> OmikronResult<()> {
let receiver = match value.get_sender() {
sender if sender > 0 => sender as i64,
_ => match value.get_data(DataType::ReceiverId).as_number() {
Some(id) => id as i64,
None => return Ok(()),
},
};
let Some(other) = value
.get_data(DataType::SenderId)
.as_number()
.map(|id| id as i64)
else {
return Ok(());
};
if let Err(error) =
notification_repo::read_notification(UserId::from(receiver), UserId::from(other)).await
{
log!(
crate::util::logger::PrintType::General,
"SQL read_notification error: {}",
error
);
} else {
let response =
CommunicationValue::new(CommunicationType::ReadNotification).with_id(value.get_id());
let _ = connection.send(&response).await;
let sync = CommunicationValue::new(CommunicationType::ReadNotification)
.with_receiver(receiver as u64)
.add_typed_default(DataType::SenderId, DataValue::SignedNumber(other.into()));
crate::transport::omikron_manager::send_to_user(receiver, &sync).await;
}
Ok(())
}
pub async fn push(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
) -> OmikronResult<()> {
let receiver = match value.get_receiver() {
receiver if receiver > 0 => receiver as i64,
_ => match value.get_data(DataType::ReceiverId).as_number() {
Some(id) => id as i64,
None => return Ok(()),
},
};
let sender = value
.get_data(DataType::SenderId)
.as_number()
.map(|id| id as i64)
.unwrap_or(value.get_sender() as i64);
if let Err(error) =
notification_repo::add_notification(UserId::from(receiver), UserId::from(sender)).await
{
log!(
crate::util::logger::PrintType::General,
"SQL add_notification error: {}",
error
);
} else {
let response =
CommunicationValue::new(CommunicationType::PushNotification).with_id(value.get_id());
let _ = connection.send(&response).await;
let push = CommunicationValue::new(CommunicationType::PushNotification)
.with_receiver(receiver as u64)
.add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender.into()));
crate::transport::omikron_manager::send_to_user(receiver, &push).await;
}
Ok(())
}

View file

@ -0,0 +1,128 @@
use super::super::omikron_connection::{OmikronConnection, OmikronResult};
use crate::{
db::user_repo,
log_in,
models::IotaId,
sql::{connection_status::UserStatus, user_online_tracker},
};
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
use std::sync::Arc;
pub async fn user_connected(
_connection: Arc<OmikronConnection>,
value: CommunicationValue,
omikron_id: i64,
) -> OmikronResult<()> {
log_in!(crate::util::logger::PrintType::Omega, "User connected");
if let Some(user_id) = value.get_data(DataType::UserId).as_number() {
let status = value
.get_data(DataType::UserState)
.as_str()
.and_then(UserStatus::from_str)
.unwrap_or(UserStatus::user_online);
if let Ok(user_id) = i64::try_from(user_id) {
user_online_tracker::track_user_status(user_id, status, omikron_id);
}
}
Ok(())
}
pub async fn user_disconnected(
_: Arc<OmikronConnection>,
value: CommunicationValue,
omikron_id: i64,
) -> OmikronResult<()> {
log_in!(crate::util::logger::PrintType::Omega, "User disconnected");
if let Some(user_id) = value.get_data(DataType::UserId).as_number() {
user_online_tracker::untrack_user_status(user_id as i64, omikron_id);
}
Ok(())
}
pub async fn iota_connected(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
omikron_id: i64,
) -> OmikronResult<()> {
log_in!(crate::util::logger::PrintType::Omega, "IOTA connected");
let Some(iota_id) = value
.get_data(DataType::IotaId)
.as_number()
.map(|id| id as i64)
else {
return Ok(());
};
user_online_tracker::track_iota_connection(iota_id, omikron_id, true);
let mut user_ids = Vec::new();
match user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await {
Ok(users) => {
for user in users {
user_ids.push(DataValue::SignedNumber(user.id.0.into()));
user_online_tracker::track_user_status(
user.id.0,
UserStatus::user_offline,
omikron_id,
);
}
}
Err(_) => log_in!(
crate::util::logger::PrintType::General,
"SQL error loading users for IOTA"
),
}
let response = CommunicationValue::new(CommunicationType::IotaUserData)
.with_id(value.get_id())
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into()))
.add_typed_default(DataType::UserIds, DataValue::Array(user_ids));
let _ = connection.send(&response).await;
Ok(())
}
pub async fn iota_disconnected(
_: Arc<OmikronConnection>,
value: CommunicationValue,
omikron_id: i64,
) -> OmikronResult<()> {
log_in!(crate::util::logger::PrintType::Omega, "IOTA disconnected");
let Some(iota_id) = value
.get_data(DataType::IotaId)
.as_number()
.map(|id| id as i64)
else {
return Ok(());
};
if user_online_tracker::untrack_iota_connection(iota_id, omikron_id) {
if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await {
user_online_tracker::untrack_many_users(
&users.iter().map(|user| user.id.0).collect::<Vec<_>>(),
);
}
}
Ok(())
}
pub async fn sync_status(
_: Arc<OmikronConnection>,
value: CommunicationValue,
omikron_id: i64,
) -> OmikronResult<()> {
if let DataValue::Array(ids) = value.get_data(DataType::UserIds) {
for id in ids {
if let DataValue::SignedNumber(id) = id {
user_online_tracker::track_user_status(
*id as i64,
UserStatus::user_offline,
omikron_id,
);
}
}
}
if let DataValue::Array(ids) = value.get_data(DataType::IotaIds) {
for id in ids {
if let DataValue::SignedNumber(id) = id {
user_online_tracker::track_iota_connection(*id as i64, omikron_id, true);
}
}
}
Ok(())
}

View file

@ -0,0 +1,151 @@
use super::super::omikron_connection::{OmikronConnection, OmikronResult};
use crate::{
db::{iota_repo, user_repo},
models::{IotaId, UserId},
};
use mtp::{
codec::{CommunicationType, CommunicationValue, DataType, DataValue},
crypto::PublicKeyBundle,
};
use std::sync::Arc;
pub async fn get_register(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
) -> OmikronResult<()> {
let register_id = user_repo::get_register_id().await?;
let response = CommunicationValue::new(CommunicationType::GetRegister)
.with_id(value.get_id())
.add_typed_default(
DataType::UserId,
DataValue::SignedNumber(register_id.0.into()),
);
connection.send(&response).await
}
pub async fn complete_iota(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
) -> OmikronResult<()> {
let iota_id = value
.get_data(DataType::IotaId)
.as_number()
.map(|id| id as i64);
let public_key = value
.get_data(DataType::PublicKey)
.as_str()
.and_then(|key| PublicKeyBundle::from_base64(key).ok());
let Some(public_key) = public_key else {
return connection
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
.await;
};
match iota_id {
Some(iota_id) => {
match iota_repo::register_complete_iota(IotaId::from(iota_id), public_key).await {
Ok(()) => {
connection
.send(
&CommunicationValue::new(CommunicationType::Success)
.with_id(value.get_id()),
)
.await
}
Err(error) => {
connection
.send(
&CommunicationValue::new(CommunicationType::ErrorInternal)
.with_id(value.get_id())
.add_typed_default(
DataType::ErrorType,
DataValue::Str(error.to_string()),
),
)
.await
}
}
}
None => match iota_repo::create_new_iota(public_key).await {
Ok(id) => {
connection
.send(
&CommunicationValue::new(CommunicationType::CompleteRegisterIota)
.with_id(value.get_id())
.add_typed_default(
DataType::IotaId,
DataValue::SignedNumber(id.0.into()),
),
)
.await
}
Err(error) => {
connection
.send(
&CommunicationValue::new(CommunicationType::ErrorInternal)
.with_id(value.get_id())
.add_typed_default(
DataType::ErrorType,
DataValue::Str(error.to_string()),
),
)
.await
}
},
}
}
pub async fn complete_user(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
) -> OmikronResult<()> {
let user_id = value
.get_data(DataType::UserId)
.as_number()
.map(|id| id as i64);
let username = value
.get_data(DataType::Username)
.as_str()
.map(str::to_owned);
let public_key = value
.get_data(DataType::PublicKey)
.as_str()
.and_then(|key| PublicKeyBundle::from_base64(key).ok());
let reset_token = value
.get_data(DataType::ResetToken)
.as_str()
.map(str::to_owned);
let Some((user_id, username, public_key, reset_token)) = user_id
.zip(username)
.zip(public_key)
.zip(reset_token)
.map(|(((id, name), key), token)| (id, name, key, token))
else {
return connection
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
.await;
};
match user_repo::register_complete_user(
UserId::from(user_id),
username,
public_key,
IotaId::from(value.get_sender() as i64),
reset_token,
)
.await
{
Ok(()) => {
connection
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()))
.await
}
Err(error) => {
connection
.send(
&CommunicationValue::new(CommunicationType::ErrorInternal)
.with_id(value.get_id())
.add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())),
)
.await
}
}
}

View file

@ -0,0 +1,46 @@
use super::super::omikron_connection::{OmikronConnection, OmikronResult};
use crate::sql::{connection_status::UserStatus, user_online_tracker};
use mtp::{
codec::{CommunicationType, CommunicationValue, DataType, DataValue},
type_map::TypeMap,
};
use std::sync::Arc;
pub async fn get(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
) -> OmikronResult<()> {
let DataValue::Array(ids) = value.get_data(DataType::UserIds) else {
return Ok(());
};
let tm = TypeMap::latest();
let states = ids
.iter()
.filter_map(|id| {
let DataValue::SignedNumber(id) = id else {
return None;
};
let status = user_online_tracker::get_user_status(*id as i64)
.map(|status| {
if status.connection_type == UserStatus::user_invisible {
UserStatus::user_offline.to_string()
} else {
status.connection_type.to_string()
}
})
.unwrap_or_else(|| UserStatus::iota_offline.to_string());
let mut map = Vec::new();
if let Some(kind) = DataType::UserId.try_to_id(&tm) {
map.push((kind, DataValue::SignedNumber((*id as i64).into())));
}
if let Some(kind) = DataType::UserState.try_to_id(&tm) {
map.push((kind, DataValue::Str(status)));
}
Some(DataValue::Container(map))
})
.collect();
let response = CommunicationValue::new(CommunicationType::GetStates)
.with_id(value.get_id())
.add_typed_default(DataType::UserStates, DataValue::Array(states));
connection.send(&response).await
}

View file

@ -0,0 +1,271 @@
use super::super::omikron_connection::{OmikronConnection, OmikronResult};
use crate::{
db::{iota_repo, user_repo},
models::{IotaId, UserId},
sql::{connection_status::UserStatus, user_online_tracker},
};
use base64::{Engine as _, engine::general_purpose::STANDARD};
use mtp::{
codec::{CommunicationType, CommunicationValue, DataType, DataValue},
crypto::PublicKeyBundle,
};
use std::sync::Arc;
fn connections(iota_id: i64) -> DataValue {
DataValue::Array(
user_online_tracker::get_iota_omikron_connections(iota_id)
.unwrap_or_default()
.into_iter()
.map(|id| DataValue::SignedNumber(id.into()))
.collect(),
)
}
pub async fn get_user(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
) -> OmikronResult<()> {
let user = if let Some(id) = value.get_data(DataType::UserId).as_number() {
user_repo::get_by_user_id(UserId::from(id as i64))
.await
.ok()
} else if let Some(name) = value.get_data(DataType::Username).as_str() {
user_repo::get_by_username(name).await.ok()
} else {
None
};
let Some(user) = user else {
return connection
.send_error_response(value.get_id(), CommunicationType::ErrorNotFound)
.await;
};
let id = user.id.0;
let iota_id = user.iota_id.0;
let username = user.username.clone();
let display = user
.display
.filter(|name| !name.is_empty())
.unwrap_or_else(|| username.clone());
let mut response = CommunicationValue::new(CommunicationType::GetUserData)
.with_id(value.get_id())
.add_typed_default(DataType::Username, DataValue::Str(username))
.add_typed_default(
DataType::PublicKey,
DataValue::Str(user.public_key.to_base64()),
)
.add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into()))
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into()))
.add_typed_default(DataType::Display, DataValue::Str(display))
.add_typed_default(
DataType::SubLevel,
DataValue::SignedNumber(user.sub_level as i128),
)
.add_typed_default(
DataType::SubEnd,
DataValue::SignedNumber(user.sub_end.into()),
);
if let Some(status) = user.status.filter(|value| !value.is_empty()) {
response = response.add_typed_default(DataType::Status, DataValue::Str(status));
}
if let Some(about) = user.about.filter(|value| !value.is_empty()) {
response = response.add_typed_default(DataType::About, DataValue::Str(about));
}
if let Some(avatar) = user.avatar {
response =
response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(avatar)));
}
let online = user_online_tracker::get_user_status(id);
response = response
.add_typed_default(
DataType::OnlineStatus,
DataValue::Str(
online
.as_ref()
.map(|status| {
if status.connection_type == UserStatus::user_invisible {
UserStatus::user_offline.to_string()
} else {
status.connection_type.to_string()
}
})
.unwrap_or_else(|| UserStatus::iota_offline.to_string()),
),
)
.add_typed_default(DataType::OmikronConnections, connections(iota_id));
if let Some(status) = online {
response = response.add_typed_default(
DataType::OmikronId,
DataValue::SignedNumber(status.omikron_id.into()),
);
}
connection.send(&response).await
}
pub async fn get_iota(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
) -> OmikronResult<()> {
let found = if let Some(id) = value.get_data(DataType::IotaId).as_number() {
iota_repo::get_iota_by_id(IotaId::from(id as i64))
.await
.ok()
.map(|iota| (iota.id.0, iota.public_key, None, None))
} else if let Some(id) = value.get_data(DataType::UserId).as_number() {
if let Ok(user) = user_repo::get_by_user_id(UserId::from(id as i64)).await {
iota_repo::get_iota_by_id(user.iota_id)
.await
.ok()
.map(|iota| (iota.id.0, iota.public_key, Some(user.id.0), None))
} else {
None
}
} else if let Some(name) = value.get_data(DataType::Username).as_str() {
if let Ok(user) = user_repo::get_by_username(name).await {
iota_repo::get_iota_by_id(user.iota_id)
.await
.ok()
.map(|iota| {
(
iota.id.0,
iota.public_key,
Some(user.id.0),
Some(name.to_owned()),
)
})
} else {
None
}
} else {
None
};
let Some((id, key, user_id, username)) = found else {
return connection
.send_error_response(value.get_id(), CommunicationType::ErrorNotFound)
.await;
};
let mut response = CommunicationValue::new(CommunicationType::GetIotaData)
.with_id(value.get_id())
.add_typed_default(DataType::PublicKey, DataValue::Str(key.to_base64()))
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.into()))
.add_typed_default(DataType::OmikronConnections, connections(id));
if let Some(user_id) = user_id {
response =
response.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into()));
}
if let Some(username) = username {
response = response.add_typed_default(DataType::Username, DataValue::Str(username));
}
connection.send(&response).await
}
async fn update_user(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
) -> OmikronResult<()> {
let id = UserId::from(value.get_sender() as i64);
let mut error = None;
if let Some(name) = value.get_data(DataType::Username).as_str() {
error = user_repo::change_username(id, name.to_owned())
.await
.err()
.map(|e| e.to_string());
}
if error.is_none() {
if let Some(name) = value.get_data(DataType::Display).as_str() {
error = user_repo::change_display_name(id, name.to_owned())
.await
.err()
.map(|e| e.to_string());
}
}
if error.is_none() {
if let Some(avatar) = value.get_data(DataType::Avatar).as_str() {
error = user_repo::change_avatar(id, avatar.to_owned())
.await
.err()
.map(|e| e.to_string());
}
}
if error.is_none() {
if let Some(about) = value.get_data(DataType::About).as_str() {
error = user_repo::change_about(id, about.to_owned())
.await
.err()
.map(|e| e.to_string());
}
}
if error.is_none() {
if let Some(status) = value.get_data(DataType::Status).as_str() {
error = user_repo::change_status(id, status.to_owned())
.await
.err()
.map(|e| e.to_string());
}
}
if error.is_none() {
if let Some(key) = value
.get_data(DataType::PublicKey)
.as_str()
.and_then(|key| PublicKeyBundle::from_base64(key).ok())
{
error = user_repo::change_keys(id, key)
.await
.err()
.map(|e| e.to_string());
}
}
let response = match error {
None => CommunicationValue::new(CommunicationType::Success),
Some(error) => CommunicationValue::new(CommunicationType::ErrorInternal)
.add_typed_default(DataType::ErrorType, DataValue::Str(error)),
};
connection.send(&response.with_id(value.get_id())).await
}
pub async fn change_user(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
) -> OmikronResult<()> {
update_user(connection, value).await
}
pub async fn change_iota(
connection: Arc<OmikronConnection>,
value: CommunicationValue,
) -> OmikronResult<()> {
let Some(reset) = value.get_data(DataType::ResetToken).as_str() else {
return connection
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
.await;
};
let Some(new_token) = value.get_data(DataType::NewToken).as_str() else {
return connection
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
.await;
};
let user_id = UserId::from(value.get_sender() as i64);
let user = match user_repo::get_by_user_id(user_id).await {
Ok(user) => user,
Err(_) => {
return connection
.send_error_response(value.get_id(), CommunicationType::ErrorNotFound)
.await;
}
};
if user.token != reset {
return connection
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge)
.await;
}
let result =
match user_repo::change_iota_id(user_id, IotaId::from(value.get_sender() as i64)).await {
Ok(()) => user_repo::change_token(user_id, new_token.to_owned()).await,
Err(error) => Err(error),
};
let response = match result {
Ok(()) => CommunicationValue::new(CommunicationType::Success),
Err(error) => CommunicationValue::new(CommunicationType::ErrorInternal)
.add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())),
};
connection.send(&response.with_id(value.get_id())).await
}