feat: Chat request implementation #14
1
Cargo.lock
generated
|
@ -1965,6 +1965,7 @@ dependencies = [
|
||||||
name = "oxidetalis_entities"
|
name = "oxidetalis_entities"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
61
crates/oxidetalis/src/database/in_chat_requests.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// OxideTalis Messaging Protocol homeserver implementation
|
||||||
|
// Copyright (C) 2024 OxideTalis Developers <otmp@4rs.nl>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://gnu.org/licenses/agpl-3.0>.
|
||||||
|
|
||||||
|
//! Database extension for the `in_chat_requests` table.
|
||||||
|
|
||||||
|
use chrono::Utc;
|
||||||
|
use oxidetalis_core::types::PublicKey;
|
||||||
|
use oxidetalis_entities::prelude::*;
|
||||||
|
use sea_orm::{sea_query::OnConflict, DatabaseConnection};
|
||||||
|
|
||||||
|
use crate::errors::ServerResult;
|
||||||
|
|
||||||
|
/// Extension trait for the `in_chat_requests` table.
|
||||||
|
pub trait InChatRequestsExt {
|
||||||
|
/// Save the chat request in the recipient table
|
||||||
|
async fn save_in_chat_request(
|
||||||
|
&self,
|
||||||
|
requester: &PublicKey,
|
||||||
|
recipient: &UserModel,
|
||||||
|
) -> ServerResult<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InChatRequestsExt for DatabaseConnection {
|
||||||
|
#[logcall::logcall]
|
||||||
|
async fn save_in_chat_request(
|
||||||
|
&self,
|
||||||
|
sender: &PublicKey,
|
||||||
|
recipient: &UserModel,
|
||||||
|
) -> ServerResult<()> {
|
||||||
|
InChatRequestsEntity::insert(InChatRequestsActiveModel {
|
||||||
|
recipient_id: Set(recipient.id),
|
||||||
|
sender: Set(sender.to_string()),
|
||||||
|
in_on: Set(Utc::now()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
awiteb marked this conversation as resolved
|
|||||||
|
.on_conflict(
|
||||||
|
OnConflict::columns([
|
||||||
|
InChatRequestsColumn::RecipientId,
|
||||||
|
InChatRequestsColumn::Sender,
|
||||||
|
])
|
||||||
|
.do_nothing()
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.exec(self)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,12 @@
|
||||||
|
|
||||||
//! Database utilities for the OxideTalis homeserver.
|
//! Database utilities for the OxideTalis homeserver.
|
||||||
|
|
||||||
|
mod in_chat_requests;
|
||||||
|
mod out_chat_requests;
|
||||||
mod user;
|
mod user;
|
||||||
|
mod user_status;
|
||||||
|
|
||||||
|
pub use in_chat_requests::*;
|
||||||
|
pub use out_chat_requests::*;
|
||||||
pub use user::*;
|
pub use user::*;
|
||||||
|
pub use user_status::*;
|
||||||
|
|
102
crates/oxidetalis/src/database/out_chat_requests.rs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
// OxideTalis Messaging Protocol homeserver implementation
|
||||||
|
// Copyright (C) 2024 OxideTalis Developers <otmp@4rs.nl>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://gnu.org/licenses/agpl-3.0>.
|
||||||
|
|
||||||
|
//! Database extension for the `out_chat_requests` table.
|
||||||
|
|
||||||
|
use chrono::Utc;
|
||||||
|
use oxidetalis_core::types::PublicKey;
|
||||||
|
use oxidetalis_entities::prelude::*;
|
||||||
|
use sea_orm::DatabaseConnection;
|
||||||
|
|
||||||
|
use crate::{errors::ServerResult, websocket::errors::WsError};
|
||||||
|
|
||||||
|
/// Extension trait for the `out_chat_requests` table.
|
||||||
|
pub trait OutChatRequestsExt {
|
||||||
|
/// Returns the outgoing chat request if the `user` have a sent chat request
|
||||||
|
/// to the `recipient`
|
||||||
|
async fn get_chat_request_to(
|
||||||
|
&self,
|
||||||
|
requester: &UserModel,
|
||||||
|
recipient: &PublicKey,
|
||||||
awiteb marked this conversation as resolved
Amjad50
commented
should return should return `true` based on the name and description of the func
Amjad50
commented
small nitpick (also applies to small nitpick (also applies to `have_chat_request_to` below)
I would name it `get_chat_request_to`, as `have` seems that it would be boolean
|
|||||||
|
) -> ServerResult<Option<OutChatRequestsModel>>;
|
||||||
|
|
||||||
|
/// Save the chat request in the requester table
|
||||||
|
async fn save_out_chat_request(
|
||||||
|
&self,
|
||||||
|
requester: &UserModel,
|
||||||
|
recipient: &PublicKey,
|
||||||
|
) -> ServerResult<()>;
|
||||||
|
|
||||||
|
/// Remove the chat request from requester table
|
||||||
|
async fn remove_out_chat_request(
|
||||||
|
&self,
|
||||||
|
requester: &UserModel,
|
||||||
|
recipient: &PublicKey,
|
||||||
|
) -> ServerResult<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutChatRequestsExt for DatabaseConnection {
|
||||||
|
#[logcall::logcall]
|
||||||
|
async fn get_chat_request_to(
|
||||||
|
&self,
|
||||||
|
requester: &UserModel,
|
||||||
|
recipient: &PublicKey,
|
||||||
|
) -> ServerResult<Option<OutChatRequestsModel>> {
|
||||||
|
requester
|
||||||
|
.find_related(OutChatRequestsEntity)
|
||||||
|
.filter(OutChatRequestsColumn::Recipient.eq(recipient.to_string()))
|
||||||
|
.one(self)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[logcall::logcall]
|
||||||
|
async fn save_out_chat_request(
|
||||||
|
&self,
|
||||||
|
requester: &UserModel,
|
||||||
|
recipient: &PublicKey,
|
||||||
|
) -> ServerResult<()> {
|
||||||
|
if let Err(err) = (OutChatRequestsActiveModel {
|
||||||
|
sender_id: Set(requester.id),
|
||||||
|
recipient: Set(recipient.to_string()),
|
||||||
|
out_on: Set(Utc::now()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.save(self)
|
||||||
|
.await)
|
||||||
|
{
|
||||||
|
match err.sql_err() {
|
||||||
|
Some(SqlErr::UniqueConstraintViolation(_)) => {
|
||||||
|
return Err(WsError::AlreadySendChatRequest.into());
|
||||||
|
}
|
||||||
|
_ => return Err(err.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn remove_out_chat_request(
|
||||||
|
&self,
|
||||||
|
requester: &UserModel,
|
||||||
|
recipient: &PublicKey,
|
||||||
|
) -> ServerResult<()> {
|
||||||
|
if let Some(out_model) = self.get_chat_request_to(requester, recipient).await? {
|
||||||
|
out_model.delete(self).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,27 +21,29 @@ use oxidetalis_core::types::PublicKey;
|
||||||
use oxidetalis_entities::prelude::*;
|
use oxidetalis_entities::prelude::*;
|
||||||
use sea_orm::DatabaseConnection;
|
use sea_orm::DatabaseConnection;
|
||||||
|
|
||||||
use crate::errors::{ApiError, ApiResult};
|
use crate::{errors::ServerResult, routes::ApiError};
|
||||||
|
|
||||||
pub trait UserTableExt {
|
pub trait UserTableExt {
|
||||||
/// Returns true if there is users in the database
|
/// Returns true if there is users in the database
|
||||||
async fn users_exists_in_database(&self) -> ApiResult<bool>;
|
async fn users_exists_in_database(&self) -> ServerResult<bool>;
|
||||||
/// Register new user
|
/// Register new user
|
||||||
async fn register_user(&self, public_key: &PublicKey, is_admin: bool) -> ApiResult<()>;
|
async fn register_user(&self, public_key: &PublicKey, is_admin: bool) -> ServerResult<()>;
|
||||||
|
/// Returns user by its public key
|
||||||
|
async fn get_user_by_pubk(&self, public_key: &PublicKey) -> ServerResult<Option<UserModel>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserTableExt for DatabaseConnection {
|
impl UserTableExt for DatabaseConnection {
|
||||||
#[logcall]
|
#[logcall]
|
||||||
async fn users_exists_in_database(&self) -> ApiResult<bool> {
|
async fn users_exists_in_database(&self) -> ServerResult<bool> {
|
||||||
UserEntity::find()
|
UserEntity::find()
|
||||||
.one(self)
|
.one(self)
|
||||||
.await
|
.await
|
||||||
.map_err(Into::into)
|
|
||||||
.map(|u| u.is_some())
|
.map(|u| u.is_some())
|
||||||
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[logcall]
|
#[logcall]
|
||||||
async fn register_user(&self, public_key: &PublicKey, is_admin: bool) -> ApiResult<()> {
|
async fn register_user(&self, public_key: &PublicKey, is_admin: bool) -> ServerResult<()> {
|
||||||
if let Err(err) = (UserActiveModel {
|
if let Err(err) = (UserActiveModel {
|
||||||
public_key: Set(public_key.to_string()),
|
public_key: Set(public_key.to_string()),
|
||||||
is_admin: Set(is_admin),
|
is_admin: Set(is_admin),
|
||||||
|
@ -51,10 +53,19 @@ impl UserTableExt for DatabaseConnection {
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
if let Some(SqlErr::UniqueConstraintViolation(_)) = err.sql_err() {
|
if let Some(SqlErr::UniqueConstraintViolation(_)) = err.sql_err() {
|
||||||
return Err(ApiError::DuplicatedUser);
|
return Err(ApiError::AlreadyRegistered.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[logcall]
|
||||||
|
async fn get_user_by_pubk(&self, public_key: &PublicKey) -> ServerResult<Option<UserModel>> {
|
||||||
|
UserEntity::find()
|
||||||
|
.filter(UserColumn::PublicKey.eq(public_key.to_string()))
|
||||||
|
.one(self)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
306
crates/oxidetalis/src/database/user_status.rs
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
// OxideTalis Messaging Protocol homeserver implementation
|
||||||
|
// Copyright (C) 2024 OxideTalis Developers <otmp@4rs.nl>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://gnu.org/licenses/agpl-3.0>.
|
||||||
|
|
||||||
|
//! Database extension to work with the whitelist table
|
||||||
|
|
||||||
|
use std::num::{NonZeroU32, NonZeroU8};
|
||||||
|
|
||||||
|
use chrono::Utc;
|
||||||
|
use oxidetalis_core::types::PublicKey;
|
||||||
|
use oxidetalis_entities::prelude::*;
|
||||||
|
use sea_orm::DatabaseConnection;
|
||||||
|
|
||||||
|
use crate::{errors::ServerResult, websocket::errors::WsError};
|
||||||
|
|
||||||
|
/// Extension trait for the `DatabaseConnection` to work with the whitelist
|
||||||
|
/// table
|
||||||
|
pub trait UsersStatusExt {
|
||||||
|
/// Returns true if the `whitelister` has whitelisted the
|
||||||
|
/// `target_public_key`
|
||||||
|
async fn is_whitelisted(
|
||||||
|
&self,
|
||||||
|
whitelister: &UserModel,
|
||||||
|
target_public_key: &PublicKey,
|
||||||
|
) -> ServerResult<bool>;
|
||||||
|
|
||||||
|
/// Returns true if the `blacklister` has blacklisted the
|
||||||
|
/// `target_public_key`
|
||||||
|
async fn is_blacklisted(
|
||||||
|
&self,
|
||||||
|
blacklister: &UserModel,
|
||||||
|
target_public_key: &PublicKey,
|
||||||
|
) -> ServerResult<bool>;
|
||||||
|
|
||||||
|
/// Add the `target_public_key` to the whitelist of the `whitelister` and
|
||||||
|
/// remove it from the blacklist table (if it's there)
|
||||||
|
async fn add_to_whitelist(
|
||||||
|
&self,
|
||||||
|
whitelister: &UserModel,
|
||||||
|
target_public_key: &PublicKey,
|
||||||
|
) -> ServerResult<()>;
|
||||||
|
|
||||||
|
/// Add the `target_public_key` to the blacklist of the `blacklister` and
|
||||||
|
/// remove it from the whitelist table (if it's there)
|
||||||
|
async fn add_to_blacklist(
|
||||||
|
&self,
|
||||||
|
blacklister: &UserModel,
|
||||||
|
target_public_key: &PublicKey,
|
||||||
|
) -> ServerResult<()>;
|
||||||
|
|
||||||
|
/// Remove the target from whitelist table
|
||||||
|
// FIXME(awiteb): This method will be used when I work on decentralization, So, I'm keeping it
|
||||||
|
// for now
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn remove_from_whitelist(
|
||||||
|
&self,
|
||||||
|
whitelister: &UserModel,
|
||||||
|
target_public_key: &PublicKey,
|
||||||
|
) -> ServerResult<()>;
|
||||||
|
|
||||||
|
/// Remove the target from blacklist table
|
||||||
|
// FIXME(awiteb): This method will be used when I work on decentralization, So, I'm keeping it
|
||||||
|
// for now
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn remove_from_blacklist(
|
||||||
|
&self,
|
||||||
|
blacklister: &UserModel,
|
||||||
|
target_public_key: &PublicKey,
|
||||||
|
) -> ServerResult<()>;
|
||||||
|
|
||||||
|
/// Returns the whitelist of the user
|
||||||
|
async fn user_whitelist(
|
||||||
|
&self,
|
||||||
|
whitelister: &UserModel,
|
||||||
|
page: NonZeroU32,
|
||||||
|
page_size: NonZeroU8,
|
||||||
|
) -> ServerResult<Vec<UsersStatusModel>>;
|
||||||
|
|
||||||
|
/// Returns the blacklist of the user
|
||||||
|
async fn user_blacklist(
|
||||||
|
&self,
|
||||||
|
blacklister: &UserModel,
|
||||||
|
page: NonZeroU32,
|
||||||
|
page_size: NonZeroU8,
|
||||||
|
) -> ServerResult<Vec<UsersStatusModel>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsersStatusExt for DatabaseConnection {
|
||||||
|
#[logcall::logcall]
|
||||||
|
async fn is_whitelisted(
|
||||||
|
&self,
|
||||||
|
whitelister: &UserModel,
|
||||||
|
target_public_key: &PublicKey,
|
||||||
|
) -> ServerResult<bool> {
|
||||||
awiteb marked this conversation as resolved
Amjad50
commented
you can use you can use `get_user_status` here and also `is_blacklisted`. also in `remove...` functions
|
|||||||
|
get_user_status(
|
||||||
|
self,
|
||||||
|
whitelister,
|
||||||
|
target_public_key,
|
||||||
|
AccessStatus::Whitelisted,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|u| u.is_some())
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[logcall::logcall]
|
||||||
|
async fn is_blacklisted(
|
||||||
|
&self,
|
||||||
|
blacklister: &UserModel,
|
||||||
|
target_public_key: &PublicKey,
|
||||||
|
) -> ServerResult<bool> {
|
||||||
|
get_user_status(
|
||||||
|
self,
|
||||||
|
blacklister,
|
||||||
|
target_public_key,
|
||||||
|
AccessStatus::Blacklisted,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|u| u.is_some())
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[logcall::logcall]
|
||||||
|
async fn add_to_whitelist(
|
||||||
|
&self,
|
||||||
|
whitelister: &UserModel,
|
||||||
|
target_public_key: &PublicKey,
|
||||||
|
) -> ServerResult<()> {
|
||||||
|
if whitelister.public_key == target_public_key.to_string() {
|
||||||
|
return Err(WsError::CannotAddSelfToWhitelist.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(mut user) = get_user_status(
|
||||||
|
self,
|
||||||
|
whitelister,
|
||||||
|
target_public_key,
|
||||||
|
AccessStatus::Blacklisted,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.map(IntoActiveModel::into_active_model)
|
||||||
|
{
|
||||||
|
user.status = Set(AccessStatus::Whitelisted);
|
||||||
|
user.updated_at = Set(Utc::now());
|
||||||
|
user.update(self).await?;
|
||||||
|
} else if let Err(err) = (UsersStatusActiveModel {
|
||||||
|
user_id: Set(whitelister.id),
|
||||||
|
target: Set(target_public_key.to_string()),
|
||||||
|
status: Set(AccessStatus::Whitelisted),
|
||||||
|
updated_at: Set(Utc::now()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.save(self)
|
||||||
|
.await)
|
||||||
|
{
|
||||||
|
match err.sql_err() {
|
||||||
|
Some(SqlErr::UniqueConstraintViolation(_)) => {
|
||||||
|
return Err(WsError::AlreadyOnTheWhitelist.into());
|
||||||
|
}
|
||||||
|
_ => return Err(err.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[logcall::logcall]
|
||||||
|
async fn add_to_blacklist(
|
||||||
|
&self,
|
||||||
|
blacklister: &UserModel,
|
||||||
|
target_public_key: &PublicKey,
|
||||||
|
) -> ServerResult<()> {
|
||||||
|
if blacklister.public_key == target_public_key.to_string() {
|
||||||
|
return Err(WsError::CannotAddSelfToBlacklist.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(mut user) = get_user_status(
|
||||||
|
self,
|
||||||
|
blacklister,
|
||||||
|
target_public_key,
|
||||||
|
AccessStatus::Whitelisted,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.map(IntoActiveModel::into_active_model)
|
||||||
|
{
|
||||||
|
user.status = Set(AccessStatus::Blacklisted);
|
||||||
|
user.updated_at = Set(Utc::now());
|
||||||
|
user.update(self).await?;
|
||||||
|
} else if let Err(err) = (UsersStatusActiveModel {
|
||||||
|
user_id: Set(blacklister.id),
|
||||||
|
target: Set(target_public_key.to_string()),
|
||||||
|
status: Set(AccessStatus::Blacklisted),
|
||||||
|
updated_at: Set(Utc::now()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.save(self)
|
||||||
|
.await)
|
||||||
|
{
|
||||||
|
match err.sql_err() {
|
||||||
|
Some(SqlErr::UniqueConstraintViolation(_)) => {
|
||||||
|
return Err(WsError::AlreadyOnTheBlacklist.into());
|
||||||
|
}
|
||||||
|
_ => return Err(err.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[logcall::logcall]
|
||||||
|
async fn remove_from_whitelist(
|
||||||
|
&self,
|
||||||
|
whitelister: &UserModel,
|
||||||
|
target_public_key: &PublicKey,
|
||||||
|
) -> ServerResult<()> {
|
||||||
|
if let Some(target_user) = get_user_status(
|
||||||
|
self,
|
||||||
|
whitelister,
|
||||||
|
target_public_key,
|
||||||
|
AccessStatus::Whitelisted,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
target_user.delete(self).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[logcall::logcall]
|
||||||
|
async fn remove_from_blacklist(
|
||||||
|
&self,
|
||||||
|
blacklister: &UserModel,
|
||||||
|
target_public_key: &PublicKey,
|
||||||
|
) -> ServerResult<()> {
|
||||||
|
if let Some(target_user) = get_user_status(
|
||||||
|
self,
|
||||||
|
blacklister,
|
||||||
|
target_public_key,
|
||||||
|
AccessStatus::Blacklisted,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
target_user.delete(self).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn user_whitelist(
|
||||||
|
&self,
|
||||||
|
whitelister: &UserModel,
|
||||||
|
page: NonZeroU32,
|
||||||
|
page_size: NonZeroU8,
|
||||||
|
) -> ServerResult<Vec<UsersStatusModel>> {
|
||||||
|
whitelister
|
||||||
|
.find_related(UsersStatusEntity)
|
||||||
|
.filter(UsersStatusColumn::Status.eq(AccessStatus::Whitelisted))
|
||||||
|
.paginate(self, u64::from(page_size.get()))
|
||||||
|
.fetch_page(u64::from(page.get() - 1))
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn user_blacklist(
|
||||||
|
&self,
|
||||||
|
blacklister: &UserModel,
|
||||||
|
page: NonZeroU32,
|
||||||
|
page_size: NonZeroU8,
|
||||||
|
) -> ServerResult<Vec<UsersStatusModel>> {
|
||||||
|
blacklister
|
||||||
|
.find_related(UsersStatusEntity)
|
||||||
|
.filter(UsersStatusColumn::Status.eq(AccessStatus::Blacklisted))
|
||||||
|
.paginate(self, u64::from(page_size.get()))
|
||||||
|
.fetch_page(u64::from(page.get() - 1))
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns user from user_status table by the entered and target public key
|
||||||
|
async fn get_user_status(
|
||||||
|
conn: &DatabaseConnection,
|
||||||
|
user: &UserModel,
|
||||||
|
target_public_key: &PublicKey,
|
||||||
|
status: AccessStatus,
|
||||||
|
) -> ServerResult<Option<UsersStatusModel>> {
|
||||||
|
user.find_related(UsersStatusEntity)
|
||||||
|
.filter(
|
||||||
|
UsersStatusColumn::Target
|
||||||
|
.eq(target_public_key.to_string())
|
||||||
|
.and(UsersStatusColumn::Status.eq(status)),
|
||||||
|
)
|
||||||
|
.one(conn)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
|
@ -14,24 +14,16 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://gnu.org/licenses/agpl-3.0>.
|
// along with this program. If not, see <https://gnu.org/licenses/agpl-3.0>.
|
||||||
|
|
||||||
use salvo::{
|
use sea_orm::DbErr;
|
||||||
http::StatusCode,
|
|
||||||
oapi::{Components as OapiComponents, EndpointOutRegister, Operation as OapiOperation},
|
|
||||||
Response,
|
|
||||||
Scribe,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{routes::write_json_body, schemas::MessageSchema};
|
use crate::{routes::ApiError, websocket::errors::WsError};
|
||||||
|
|
||||||
/// Result type of the homeserver
|
/// Result type of the homeserver
|
||||||
#[allow(clippy::absolute_paths)]
|
pub(crate) type ServerResult<T> = Result<T, ServerError>;
|
||||||
pub(crate) type Result<T> = std::result::Result<T, Error>;
|
|
||||||
#[allow(clippy::absolute_paths)]
|
|
||||||
pub type ApiResult<T> = std::result::Result<T, ApiError>;
|
|
||||||
|
|
||||||
/// The homeserver errors
|
/// The homeserver errors
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub(crate) enum Error {
|
pub enum InternalError {
|
||||||
#[error("Database Error: {0}")]
|
#[error("Database Error: {0}")]
|
||||||
Database(#[from] sea_orm::DbErr),
|
Database(#[from] sea_orm::DbErr),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
|
@ -39,43 +31,41 @@ pub(crate) enum Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum ApiError {
|
/// The homeserver errors
|
||||||
/// Error from the database (500 Internal Server Error)
|
pub enum ServerError {
|
||||||
#[error("Internal server error")]
|
/// Internal server error, should not be exposed to the user
|
||||||
SeaOrm(#[from] sea_orm::DbErr),
|
#[error("{0}")]
|
||||||
/// The server registration is closed (403 Forbidden)
|
Internal(#[from] InternalError),
|
||||||
#[error("Server registration is closed")]
|
/// API error, errors happening in the API
|
||||||
RegistrationClosed,
|
#[error("{0}")]
|
||||||
/// The entered public key is already registered (400 Bad Request)
|
Api(#[from] ApiError),
|
||||||
#[error("The entered public key is already registered")]
|
/// WebSocket error, errors happening in the WebSocket
|
||||||
DuplicatedUser,
|
#[error("{0}")]
|
||||||
/// The user entered two different public keys
|
Ws(#[from] WsError),
|
||||||
/// one in the header and other in the request body
|
|
||||||
/// (400 Bad Request)
|
|
||||||
#[error("You entered two different public keys")]
|
|
||||||
TwoDifferentKeys,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApiError {
|
impl From<DbErr> for ServerError {
|
||||||
/// Status code of the error
|
fn from(err: DbErr) -> Self {
|
||||||
pub const fn status_code(&self) -> StatusCode {
|
Self::Internal(err.into())
|
||||||
match self {
|
}
|
||||||
Self::SeaOrm(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
}
|
||||||
Self::RegistrationClosed => StatusCode::FORBIDDEN,
|
|
||||||
Self::DuplicatedUser | Self::TwoDifferentKeys => StatusCode::BAD_REQUEST,
|
impl From<ServerError> for WsError {
|
||||||
|
fn from(err: ServerError) -> Self {
|
||||||
|
match err {
|
||||||
|
ServerError::Api(ApiError::NotRegisteredUser) => WsError::RegistredUserEvent,
|
||||||
|
ServerError::Internal(_) | ServerError::Api(_) => WsError::InternalServerError,
|
||||||
|
ServerError::Ws(err) => err,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EndpointOutRegister for ApiError {
|
impl From<ServerError> for ApiError {
|
||||||
fn register(_: &mut OapiComponents, _: &mut OapiOperation) {}
|
fn from(err: ServerError) -> Self {
|
||||||
}
|
match err {
|
||||||
|
ServerError::Ws(WsError::RegistredUserEvent) => ApiError::NotRegisteredUser,
|
||||||
impl Scribe for ApiError {
|
ServerError::Internal(_) | ServerError::Ws(_) => ApiError::Internal,
|
||||||
fn render(self, res: &mut Response) {
|
ServerError::Api(err) => err,
|
||||||
log::error!("Error: {self}");
|
}
|
||||||
|
|
||||||
res.status_code(self.status_code());
|
|
||||||
write_json_body(res, MessageSchema::new(self.to_string()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,20 +18,21 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use oxidetalis_config::Config;
|
use oxidetalis_config::Config;
|
||||||
|
use oxidetalis_core::types::PublicKey;
|
||||||
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
|
||||||
use salvo::{websocket::Message, Depot};
|
use salvo::Depot;
|
||||||
use sea_orm::DatabaseConnection;
|
use sea_orm::DatabaseConnection;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
nonce::NonceCache,
|
nonce::NonceCache,
|
||||||
websocket::{OnlineUsers, ServerEvent, SocketUserData},
|
websocket::{OnlineUsers, ServerEvent, SocketUserData, Unsigned},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Extension trait for the Depot.
|
/// Extension trait for the Depot.
|
||||||
pub trait DepotExt {
|
pub trait DepotExt {
|
||||||
/// Returns the database connection
|
/// Returns the database connection
|
||||||
fn db_conn(&self) -> &DatabaseConnection;
|
fn db_conn(&self) -> Arc<DatabaseConnection>;
|
||||||
/// Returns the server configuration
|
/// Returns the server configuration
|
||||||
fn config(&self) -> &Config;
|
fn config(&self) -> &Config;
|
||||||
/// Retutns the nonce cache
|
/// Retutns the nonce cache
|
||||||
|
@ -54,12 +55,20 @@ pub trait OnlineUsersExt {
|
||||||
|
|
||||||
/// Disconnect inactive users (who not respond for the ping event)
|
/// Disconnect inactive users (who not respond for the ping event)
|
||||||
async fn disconnect_inactive_users(&self);
|
async fn disconnect_inactive_users(&self);
|
||||||
|
|
||||||
|
/// Returns the connection id of the user, if it is online
|
||||||
|
async fn is_online(&self, public_key: &PublicKey) -> Option<Uuid>;
|
||||||
|
|
||||||
|
/// Send an event to user by connection id
|
||||||
|
async fn send(&self, conn_id: &Uuid, event: ServerEvent<Unsigned>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DepotExt for Depot {
|
impl DepotExt for Depot {
|
||||||
fn db_conn(&self) -> &DatabaseConnection {
|
fn db_conn(&self) -> Arc<DatabaseConnection> {
|
||||||
|
Arc::clone(
|
||||||
self.obtain::<Arc<DatabaseConnection>>()
|
self.obtain::<Arc<DatabaseConnection>>()
|
||||||
.expect("Database connection not found")
|
.expect("Database connection not found"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config(&self) -> &Config {
|
fn config(&self) -> &Config {
|
||||||
|
@ -87,9 +96,10 @@ impl OnlineUsersExt for OnlineUsers {
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
self.write().await.par_iter_mut().for_each(|(_, u)| {
|
self.write().await.par_iter_mut().for_each(|(_, u)| {
|
||||||
u.pinged_at = now;
|
u.pinged_at = now;
|
||||||
let _ = u.sender.unbounded_send(Ok(Message::from(
|
let _ = u.sender.unbounded_send(Ok(ServerEvent::ping()
|
||||||
&ServerEvent::ping().sign(&u.shared_secret),
|
.sign(&u.shared_secret)
|
||||||
)));
|
.as_ref()
|
||||||
|
.into()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,4 +120,20 @@ impl OnlineUsersExt for OnlineUsers {
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn is_online(&self, public_key: &PublicKey) -> Option<Uuid> {
|
||||||
|
self.read()
|
||||||
|
.await
|
||||||
|
.iter()
|
||||||
|
.find(|(_, u)| &u.public_key == public_key)
|
||||||
|
.map(|(c, _)| *c)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send(&self, conn_id: &Uuid, event: ServerEvent<Unsigned>) {
|
||||||
|
if let Some(user) = self.read().await.get(conn_id) {
|
||||||
awiteb marked this conversation as resolved
Amjad50
commented
use
use `get`
```rust
if let Some(user) = self.read().await.get(conn_id) {
// ...
}
```
|
|||||||
|
let _ = user
|
||||||
|
.sender
|
||||||
|
.unbounded_send(Ok(event.sign(&user.shared_secret).as_ref().into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
93
crates/oxidetalis/src/macros.rs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// OxideTalis Messaging Protocol homeserver implementation
|
||||||
|
// Copyright (C) 2024 OxideTalis Developers <otmp@4rs.nl>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://gnu.org/licenses/agpl-3.0>.
|
||||||
|
|
||||||
|
//! OxideTalis server macros, to make the code more readable and easier to
|
||||||
|
//! write.
|
||||||
|
|
||||||
|
/// Macro to return a [`ServerEvent`] with a [`WsError::InternalServerError`] if
|
||||||
|
/// the result of an expression is an [`Err`].
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// fn example() -> ServerEvent {
|
||||||
|
/// // some_function() returns a Result, if it's an Err, return an
|
||||||
|
/// // ServerEvent::InternalServerError
|
||||||
|
/// let result = try_ws!(some_function());
|
||||||
|
/// ServerEvent::from(result)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`ServerEvent`]: crate::websocket::ServerEvent
|
||||||
|
/// [`WsError::InternalServerError`]: crate::websocket::errors::WsError::InternalServerError
|
||||||
|
/// [`Err`]: std::result::Result::Err
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! try_ws {
|
||||||
|
(Some $result_expr:expr) => {
|
||||||
|
match $result_expr {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("{err}");
|
||||||
|
return Some(
|
||||||
|
$crate::websocket::ServerEvent::<$crate::websocket::Unsigned>::from(
|
||||||
|
$crate::websocket::errors::WsError::from(err),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Macro to create the `WsError` enum with the given error names and reasons.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// ws_errors! {
|
||||||
|
/// FirstError = "This is the first error",
|
||||||
|
/// SecondError = "This is the second error",
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! ws_errors {
|
||||||
|
($($name:ident = $reason:tt),+ $(,)?) => {
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[doc = "Websocket errors, returned in the websocket communication"]
|
||||||
|
pub enum WsError {
|
||||||
|
$(
|
||||||
|
#[doc = $reason]
|
||||||
|
#[error($reason)]
|
||||||
|
$name
|
||||||
|
),+
|
||||||
|
}
|
||||||
|
impl WsError {
|
||||||
|
#[doc = "Returns error name"]
|
||||||
|
pub const fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
$(
|
||||||
|
WsError::$name => stringify!($name)
|
||||||
|
),+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[doc = "Returns the error reason"]
|
||||||
|
pub const fn reason(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
$(
|
||||||
|
WsError::$name => $reason
|
||||||
|
),+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
|
|
||||||
|
use errors::ServerError;
|
||||||
use oxidetalis_config::{CliArgs, Parser};
|
use oxidetalis_config::{CliArgs, Parser};
|
||||||
use oxidetalis_migrations::MigratorTrait;
|
use oxidetalis_migrations::MigratorTrait;
|
||||||
use salvo::{conn::TcpListener, Listener, Server};
|
use salvo::{conn::TcpListener, Listener, Server};
|
||||||
|
@ -26,18 +27,21 @@ use salvo::{conn::TcpListener, Listener, Server};
|
||||||
mod database;
|
mod database;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod extensions;
|
mod extensions;
|
||||||
|
mod macros;
|
||||||
mod middlewares;
|
mod middlewares;
|
||||||
mod nonce;
|
mod nonce;
|
||||||
|
mod parameters;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod schemas;
|
mod schemas;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod websocket;
|
mod websocket;
|
||||||
|
|
||||||
async fn try_main() -> errors::Result<()> {
|
async fn try_main() -> errors::ServerResult<()> {
|
||||||
pretty_env_logger::init_timed();
|
pretty_env_logger::init_timed();
|
||||||
|
|
||||||
log::info!("Parsing configuration");
|
log::info!("Parsing configuration");
|
||||||
let config = oxidetalis_config::Config::load(CliArgs::parse())?;
|
let config = oxidetalis_config::Config::load(CliArgs::parse())
|
||||||
|
.map_err(|err| ServerError::Internal(err.into()))?;
|
||||||
log::info!("Configuration parsed successfully");
|
log::info!("Configuration parsed successfully");
|
||||||
log::info!("Connecting to the database");
|
log::info!("Connecting to the database");
|
||||||
let connection = sea_orm::Database::connect(utils::postgres_url(&config.postgresdb)).await?;
|
let connection = sea_orm::Database::connect(utils::postgres_url(&config.postgresdb)).await?;
|
||||||
|
|
21
crates/oxidetalis/src/parameters/mod.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// OxideTalis Messaging Protocol homeserver implementation
|
||||||
|
// Copyright (C) 2024 OxideTalis Developers <otmp@4rs.nl>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://gnu.org/licenses/agpl-3.0>.
|
||||||
|
|
||||||
|
//! Set of route parameters for the API
|
||||||
|
|
||||||
|
mod pagination;
|
||||||
|
|
||||||
|
pub use pagination::*;
|
131
crates/oxidetalis/src/parameters/pagination.rs
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
// OxideTalis Messaging Protocol homeserver implementation
|
||||||
|
// Copyright (C) 2024 OxideTalis Developers <otmp@4rs.nl>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://gnu.org/licenses/agpl-3.0>.
|
||||||
|
|
||||||
|
//! Pagination parameters for the API
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
num::{NonZeroU32, NonZeroU8},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use salvo::{
|
||||||
|
extract::Metadata as ExtractMetadata,
|
||||||
|
oapi::{
|
||||||
|
Components as OapiComponents,
|
||||||
|
EndpointArgRegister,
|
||||||
|
Object,
|
||||||
|
Operation as OapiOperation,
|
||||||
|
Parameter,
|
||||||
|
ParameterIn,
|
||||||
|
Parameters,
|
||||||
|
SchemaType,
|
||||||
|
ToParameters,
|
||||||
|
},
|
||||||
|
Extractible,
|
||||||
|
Request,
|
||||||
|
};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::routes::{ApiError, ApiResult};
|
||||||
|
|
||||||
awiteb marked this conversation as resolved
Amjad50
commented
We can add
If we go with this, we can also use We can add `EndpointArgRegister` implementation and thus we can use it in the arg directly
```rust
async fn user_whitelist(
req: &mut Request,
depot: &mut Depot,
pagination: Pagination,
) -> ApiResult<Json<Vec<WhiteListedUser>>>
```
```rust
impl EndpointArgRegister for Pagination {
fn register(components: &mut OapiComponents, operation: &mut Operation, _arg: &str) {
for parameter in Self::to_parameters(components) {
operation.parameters.insert(parameter);
}
}
}
```
If we go with this, we can also use `ToParameters` macro to generate the implementation.
The issue with it is that it doesn't use `ApiError`, but if we are using it as a paremeter, `salvo` will handle the error reporting
awiteb
commented
I'll see it, the reason why I don't put it as an argument, is because As I see, Salvo want it as one parameter, but the pagination I'll see it, the reason why I don't put it as an argument, is because
Salvo ask me to implement `ToSchema` to it.
As I see, Salvo want it as one parameter, but the pagination
is two parameters. I'll see
awiteb
commented
Ok, I get it. It ask me to implement
So I can just implement the Thank you @Amjad50 Ok, I get it. It ask me to implement `ToSchema` because of this
```rust
impl<T, const R: bool> EndpointArgRegister for QueryParam<T, R>
where
T: ToSchema, // <----
{
fn register(components: &mut Components, operation: &mut Operation, arg: &str) {
let parameter = Parameter::new(arg)
.parameter_in(ParameterIn::Query)
.description(format!("Get parameter `{arg}` from request url query."))
.schema(T::to_schema(components))
.required(R);
operation.parameters.insert(parameter);
}
}
```
So I can just implement the `EndpointArgRegister` trait and not using `QueryParam`.
Thank you @Amjad50
|
|||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Pagination {
|
||||||
|
/// The page number of the result
|
||||||
|
pub page: NonZeroU32,
|
||||||
|
/// The page size
|
||||||
|
pub page_size: NonZeroU8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ex> Extractible<'ex> for Pagination {
|
||||||
|
fn metadata() -> &'ex ExtractMetadata {
|
||||||
|
static METADATA: ExtractMetadata = ExtractMetadata::new("");
|
||||||
|
&METADATA
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(refining_impl_trait)]
|
||||||
|
async fn extract(req: &'ex mut Request) -> ApiResult<Self> {
|
||||||
|
let page = extract_query(req, "page", NonZeroU32::new(1).expect("is non-zero"))?;
|
||||||
|
let page_size = extract_query(req, "page_size", NonZeroU8::new(10).expect("is non-zero"))?;
|
||||||
|
|
||||||
|
Ok(Self { page, page_size })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(refining_impl_trait)]
|
||||||
|
async fn extract_with_arg(req: &'ex mut Request, _arg: &str) -> ApiResult<Self> {
|
||||||
|
Self::extract(req).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToParameters<'_> for Pagination {
|
||||||
|
fn to_parameters(_components: &mut OapiComponents) -> Parameters {
|
||||||
|
Parameters::new()
|
||||||
|
.parameter(create_parameter(
|
||||||
|
"page",
|
||||||
|
"Page number, starting from 1",
|
||||||
|
1,
|
||||||
|
f64::from(u32::MAX),
|
||||||
|
))
|
||||||
|
.parameter(create_parameter(
|
||||||
|
"page_size",
|
||||||
|
"How many items per page, starting from 1",
|
||||||
|
10,
|
||||||
|
f64::from(u8::MAX),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EndpointArgRegister for Pagination {
|
||||||
|
fn register(components: &mut OapiComponents, operation: &mut OapiOperation, _arg: &str) {
|
||||||
|
for parameter in Self::to_parameters(components) {
|
||||||
|
operation.parameters.insert(parameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract a query parameter from the request
|
||||||
|
fn extract_query<T: FromStr>(req: &Request, name: &str, default_value: T) -> ApiResult<T>
|
||||||
|
where
|
||||||
|
<T as FromStr>::Err: fmt::Display,
|
||||||
|
{
|
||||||
|
Ok(req
|
||||||
|
.queries()
|
||||||
|
.get(name)
|
||||||
|
.map(|p| p.parse::<T>())
|
||||||
|
.transpose()
|
||||||
|
.map_err(|err| ApiError::Querys(format!("Invalid value for `{name}` query ({err})")))?
|
||||||
|
.unwrap_or(default_value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a parameter for the pagination
|
||||||
|
fn create_parameter(name: &str, description: &str, default: usize, max: f64) -> Parameter {
|
||||||
|
Parameter::new(name)
|
||||||
|
.parameter_in(ParameterIn::Query)
|
||||||
|
.required(false)
|
||||||
|
.description(description)
|
||||||
|
.example(json!(default))
|
||||||
|
.schema(
|
||||||
|
Object::new()
|
||||||
|
.name(name)
|
||||||
|
.description(description)
|
||||||
|
.schema_type(SchemaType::Integer)
|
||||||
|
.default_value(json!(default))
|
||||||
|
.example(json!(default))
|
||||||
|
.maximum(max)
|
||||||
|
.minimum(1.0)
|
||||||
|
.read_only(true),
|
||||||
|
)
|
||||||
|
}
|
77
crates/oxidetalis/src/routes/errors.rs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
// OxideTalis Messaging Protocol homeserver implementation
|
||||||
|
// Copyright (C) 2024 OxideTalis Developers <otmp@4rs.nl>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://gnu.org/licenses/agpl-3.0>.
|
||||||
|
|
||||||
|
use salvo::{
|
||||||
|
http::StatusCode,
|
||||||
|
oapi::{Components as OapiComponents, EndpointOutRegister, Operation as OapiOperation},
|
||||||
|
Response,
|
||||||
|
Scribe,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{routes::write_json_body, schemas::MessageSchema};
|
||||||
|
|
||||||
|
/// Result type of the API
|
||||||
|
pub type ApiResult<T> = Result<T, ApiError>;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum ApiError {
|
||||||
|
#[error("Internal server error")]
|
||||||
|
Internal,
|
||||||
|
/// The server registration is closed (403 Forbidden)
|
||||||
|
#[error("Server registration is closed")]
|
||||||
|
RegistrationClosed,
|
||||||
|
/// The entered public key is already registered (400 Bad Request)
|
||||||
|
#[error("The entered public key is already registered")]
|
||||||
|
AlreadyRegistered,
|
||||||
|
/// The user entered two different public keys
|
||||||
|
/// one in the header and other in the request body
|
||||||
|
/// (400 Bad Request)
|
||||||
|
#[error("You entered two different public keys")]
|
||||||
|
TwoDifferentKeys,
|
||||||
|
/// Error in the query parameters (400 Bad Request)
|
||||||
|
#[error("{0}")]
|
||||||
|
Querys(String),
|
||||||
|
/// Non registered user tried to access to registered user only endpoint
|
||||||
|
/// (403 Forbidden)
|
||||||
|
#[error("You are not a registered user, please register first")]
|
||||||
|
NotRegisteredUser,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApiError {
|
||||||
|
/// Status code of the error
|
||||||
|
pub const fn status_code(&self) -> StatusCode {
|
||||||
|
match self {
|
||||||
|
Self::Internal => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Self::RegistrationClosed | Self::NotRegisteredUser => StatusCode::FORBIDDEN,
|
||||||
|
Self::AlreadyRegistered | Self::TwoDifferentKeys | Self::Querys(_) => {
|
||||||
|
StatusCode::BAD_REQUEST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EndpointOutRegister for ApiError {
|
||||||
|
fn register(_: &mut OapiComponents, _: &mut OapiOperation) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scribe for ApiError {
|
||||||
|
fn render(self, res: &mut Response) {
|
||||||
|
log::error!("Error: {self}");
|
||||||
|
|
||||||
|
res.status_code(self.status_code());
|
||||||
|
write_json_body(res, MessageSchema::new(self.to_string()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,8 +27,11 @@ use crate::nonce::NonceCache;
|
||||||
use crate::schemas::MessageSchema;
|
use crate::schemas::MessageSchema;
|
||||||
use crate::{middlewares, websocket};
|
use crate::{middlewares, websocket};
|
||||||
|
|
||||||
|
mod errors;
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
|
pub use errors::*;
|
||||||
|
|
||||||
pub fn write_json_body(res: &mut Response, json_body: impl serde::Serialize) {
|
pub fn write_json_body(res: &mut Response, json_body: impl serde::Serialize) {
|
||||||
res.write_body(serde_json::to_string(&json_body).expect("Json serialization can't be fail"))
|
res.write_body(serde_json::to_string(&json_body).expect("Json serialization can't be fail"))
|
||||||
.ok();
|
.ok();
|
||||||
|
|
|
@ -20,18 +20,20 @@ use oxidetalis_core::types::{PublicKey, Signature};
|
||||||
use salvo::{
|
use salvo::{
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
oapi::{endpoint, extract::JsonBody},
|
oapi::{endpoint, extract::JsonBody},
|
||||||
|
writing::Json,
|
||||||
Depot,
|
Depot,
|
||||||
Request,
|
Request,
|
||||||
Router,
|
Router,
|
||||||
Writer,
|
Writer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::{ApiError, ApiResult};
|
||||||
use crate::{
|
use crate::{
|
||||||
database::UserTableExt,
|
database::{UserTableExt, UsersStatusExt},
|
||||||
errors::{ApiError, ApiResult},
|
|
||||||
extensions::DepotExt,
|
extensions::DepotExt,
|
||||||
middlewares,
|
middlewares,
|
||||||
schemas::{EmptySchema, MessageSchema, RegisterUserBody},
|
parameters::Pagination,
|
||||||
|
schemas::{BlackListedUser, EmptySchema, MessageSchema, RegisterUserBody, WhiteListedUser},
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -79,10 +81,90 @@ pub async fn register(
|
||||||
Ok(EmptySchema::new(StatusCode::CREATED))
|
Ok(EmptySchema::new(StatusCode::CREATED))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// (🔐) Get whitelisted users
|
||||||
|
#[endpoint(
|
||||||
|
operation_id = "whitelist",
|
||||||
|
tags("User"),
|
||||||
|
responses(
|
||||||
|
(status_code = 200, description = "Returns whitelisted users", content_type = "application/json", body = Vec<WhiteListedUser>),
|
||||||
|
(status_code = 400, description = "Wrong query parameter", content_type = "application/json", body = MessageSchema),
|
||||||
|
(status_code = 401, description = "The entered signature or public key is invalid", content_type = "application/json", body = MessageSchema),
|
||||||
|
(status_code = 403, description = "Not registered user, must register first", content_type = "application/json", body = MessageSchema),
|
||||||
|
(status_code = 429, description = "Too many requests", content_type = "application/json", body = MessageSchema),
|
||||||
|
(status_code = 500, description = "Internal server error", content_type = "application/json", body = MessageSchema),
|
||||||
|
),
|
||||||
|
parameters(
|
||||||
|
("X-OTMP-PUBLIC" = PublicKey, Header, description = "Public key of the sender"),
|
||||||
|
("X-OTMP-SIGNATURE" = Signature, Header, description = "Signature of the request"),
|
||||||
|
),
|
||||||
|
)]
|
||||||
|
async fn user_whitelist(
|
||||||
|
req: &mut Request,
|
||||||
|
depot: &mut Depot,
|
||||||
|
pagination: Pagination,
|
||||||
|
) -> ApiResult<Json<Vec<WhiteListedUser>>> {
|
||||||
|
let conn = depot.db_conn();
|
||||||
|
let user = conn
|
||||||
|
.get_user_by_pubk(
|
||||||
|
&utils::extract_public_key(req)
|
||||||
|
.expect("Public key should be checked in the middleware"),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.ok_or(ApiError::NotRegisteredUser)?;
|
||||||
|
Ok(Json(
|
||||||
|
conn.user_whitelist(&user, pagination.page, pagination.page_size)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (🔐) Get blacklisted users
|
||||||
|
#[endpoint(
|
||||||
|
operation_id = "blacklist",
|
||||||
|
tags("User"),
|
||||||
|
responses(
|
||||||
|
(status_code = 200, description = "Returns blacklisted users", content_type = "application/json", body = Vec<BlackListedUser>),
|
||||||
|
(status_code = 400, description = "Wrong query parameter", content_type = "application/json", body = MessageSchema),
|
||||||
|
(status_code = 401, description = "The entered signature or public key is invalid", content_type = "application/json", body = MessageSchema),
|
||||||
|
(status_code = 403, description = "Not registered user, must register first", content_type = "application/json", body = MessageSchema),
|
||||||
|
(status_code = 429, description = "Too many requests", content_type = "application/json", body = MessageSchema),
|
||||||
|
(status_code = 500, description = "Internal server error", content_type = "application/json", body = MessageSchema),
|
||||||
|
),
|
||||||
|
parameters(
|
||||||
|
("X-OTMP-PUBLIC" = PublicKey, Header, description = "Public key of the sender"),
|
||||||
|
("X-OTMP-SIGNATURE" = Signature, Header, description = "Signature of the request"),
|
||||||
|
),
|
||||||
|
)]
|
||||||
|
async fn user_blacklist(
|
||||||
|
req: &mut Request,
|
||||||
|
depot: &mut Depot,
|
||||||
|
pagination: Pagination,
|
||||||
|
) -> ApiResult<Json<Vec<BlackListedUser>>> {
|
||||||
|
let conn = depot.db_conn();
|
||||||
|
let user = conn
|
||||||
|
.get_user_by_pubk(
|
||||||
|
&utils::extract_public_key(req)
|
||||||
|
.expect("Public key should be checked in the middleware"),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.ok_or(ApiError::NotRegisteredUser)?;
|
||||||
|
Ok(Json(
|
||||||
|
conn.user_blacklist(&user, pagination.page, pagination.page_size)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// The route of the endpoints of this module
|
/// The route of the endpoints of this module
|
||||||
pub fn route() -> Router {
|
pub fn route() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.push(Router::with_path("register").post(register))
|
.push(Router::with_path("register").post(register))
|
||||||
|
.push(Router::with_path("whitelist").get(user_whitelist))
|
||||||
|
.push(Router::with_path("blacklist").get(user_blacklist))
|
||||||
.hoop(middlewares::public_key_check)
|
.hoop(middlewares::public_key_check)
|
||||||
.hoop(middlewares::signature_check)
|
.hoop(middlewares::signature_check)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,11 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://gnu.org/licenses/agpl-3.0>.
|
// along with this program. If not, see <https://gnu.org/licenses/agpl-3.0>.
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
use oxidetalis_core::{cipher::K256Secret, types::PublicKey};
|
use oxidetalis_core::{cipher::K256Secret, types::PublicKey};
|
||||||
|
use oxidetalis_entities::prelude::*;
|
||||||
use salvo::oapi::ToSchema;
|
use salvo::oapi::ToSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -25,3 +29,57 @@ pub struct RegisterUserBody {
|
||||||
/// The public key of the user
|
/// The public key of the user
|
||||||
pub public_key: PublicKey,
|
pub public_key: PublicKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, ToSchema, derive_new::new)]
|
||||||
|
#[salvo(schema(name = WhiteListedUser, example = json!(WhiteListedUser::default())))]
|
||||||
|
pub struct WhiteListedUser {
|
||||||
|
/// User's public key
|
||||||
|
pub public_key: PublicKey,
|
||||||
|
/// When the user was whitelisted
|
||||||
|
pub whitelisted_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, ToSchema, derive_new::new)]
|
||||||
|
#[salvo(schema(name = BlackListedUser, example = json!(BlackListedUser::default())))]
|
||||||
|
pub struct BlackListedUser {
|
||||||
|
/// User's public key
|
||||||
|
pub public_key: PublicKey,
|
||||||
|
/// When the user was blacklisted
|
||||||
|
pub blacklisted_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WhiteListedUser {
|
||||||
|
fn default() -> Self {
|
||||||
|
WhiteListedUser::new(
|
||||||
|
PublicKey::from_str("bYhbrm61ov8GLZfskUYbsCLJTfaacMsuTBYgBABEH9dy").expect("is valid"),
|
||||||
|
Utc::now(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UsersStatusModel> for WhiteListedUser {
|
||||||
|
fn from(user: UsersStatusModel) -> Self {
|
||||||
|
Self {
|
||||||
|
public_key: PublicKey::from_str(&user.target).expect("Is valid public key"),
|
||||||
|
whitelisted_at: user.updated_at,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BlackListedUser {
|
||||||
|
fn default() -> Self {
|
||||||
|
BlackListedUser::new(
|
||||||
|
PublicKey::from_str("bYhbrm61ov8GLZfskUYbsCLJTfaacMsuTBYgBABEH9dy").expect("is valid"),
|
||||||
|
Utc::now(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UsersStatusModel> for BlackListedUser {
|
||||||
|
fn from(user: UsersStatusModel) -> Self {
|
||||||
|
Self {
|
||||||
|
public_key: PublicKey::from_str(&user.target).expect("Is valid public key"),
|
||||||
|
blacklisted_at: user.updated_at,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,42 +16,27 @@
|
||||||
|
|
||||||
//! Websocket errors
|
//! Websocket errors
|
||||||
|
|
||||||
|
use crate::ws_errors;
|
||||||
|
|
||||||
/// Result type of websocket
|
/// Result type of websocket
|
||||||
pub type WsResult<T> = Result<T, WsError>;
|
pub type WsResult<T> = Result<T, WsError>;
|
||||||
|
|
||||||
/// Websocket errors, returned in the websocket communication
|
ws_errors! {
|
||||||
#[derive(Debug)]
|
InternalServerError = "Internal server error",
|
||||||
pub enum WsError {
|
InvalidSignature = "Invalid event signature",
|
||||||
/// The signature is invalid
|
NotTextMessage = "The websocket message must be text message",
|
||||||
InvalidSignature,
|
InvalidJsonData = "Received invalid json data, the text must be valid json",
|
||||||
/// Message type must be text
|
UnknownClientEvent = "Unknown client event, the event is not recognized by the server",
|
||||||
NotTextMessage,
|
RegistredUserEvent = "The event is only for registred users",
|
||||||
/// Invalid json data
|
UserNotFound = "The user is not registered in the server",
|
||||||
InvalidJsonData,
|
AlreadyOnTheWhitelist = "The user is already on your whitelist",
|
||||||
/// Unknown client event
|
CannotAddSelfToWhitelist = "You cannot add yourself to the whitelist",
|
||||||
UnknownClientEvent,
|
AlreadyOnTheBlacklist = "The user is already on your blacklist",
|
||||||
}
|
CannotAddSelfToBlacklist = "You cannot add yourself to the blacklist",
|
||||||
|
AlreadySendChatRequest = "You have already sent a chat request to this user",
|
||||||
impl WsError {
|
CannotSendChatRequestToSelf = "You cannot send a chat request to yourself",
|
||||||
/// Returns error name
|
CannotRespondToOwnChatRequest = "You cannot respond to your own chat request",
|
||||||
pub const fn name(&self) -> &'static str {
|
NoChatRequestFromRecipient = "You do not have a chat request from the recipient",
|
||||||
match self {
|
RecipientBlacklist = "You cannot send a chat request because you are on the recipient's blacklist.",
|
||||||
WsError::InvalidSignature => "InvalidSignature",
|
AlreadyInRecipientWhitelist = "You are already on the recipient's whitelist and can chat with them."
|
||||||
WsError::NotTextMessage => "NotTextMessage",
|
|
||||||
WsError::InvalidJsonData => "InvalidJsonData",
|
|
||||||
WsError::UnknownClientEvent => "UnknownClientEvent",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the error reason
|
|
||||||
pub const fn reason(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
WsError::InvalidSignature => "Invalid event signature",
|
|
||||||
WsError::NotTextMessage => "The websocket message must be text message",
|
|
||||||
WsError::InvalidJsonData => "Received invalid json data, the text must be valid json",
|
|
||||||
WsError::UnknownClientEvent => {
|
|
||||||
"Unknown client event, the event is not recognized by the server"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
//! Events that the client send it
|
//! Events that the client send it
|
||||||
|
|
||||||
use oxidetalis_core::types::Signature;
|
use oxidetalis_core::types::{PublicKey, Signature};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{nonce::NonceCache, utils};
|
use crate::{nonce::NonceCache, utils};
|
||||||
|
@ -24,10 +24,14 @@ use crate::{nonce::NonceCache, utils};
|
||||||
/// Client websocket event
|
/// Client websocket event
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
pub struct ClientEvent {
|
pub struct ClientEvent {
|
||||||
|
#[serde(flatten)]
|
||||||
pub event: ClientEventType,
|
pub event: ClientEventType,
|
||||||
signature: Signature,
|
signature: Signature,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ## Important for contuributors
|
||||||
|
// Please make sure to order the event data alphabetically.
|
||||||
|
|
||||||
/// Client websocket event type
|
/// Client websocket event type
|
||||||
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
|
||||||
#[serde(rename_all = "PascalCase", tag = "event", content = "data")]
|
#[serde(rename_all = "PascalCase", tag = "event", content = "data")]
|
||||||
|
@ -36,6 +40,10 @@ pub enum ClientEventType {
|
||||||
Ping { timestamp: u64 },
|
Ping { timestamp: u64 },
|
||||||
/// Pong event
|
/// Pong event
|
||||||
Pong { timestamp: u64 },
|
Pong { timestamp: u64 },
|
||||||
|
/// Request to chat with a user
|
||||||
|
ChatRequest { to: PublicKey },
|
||||||
|
/// Response to a chat request
|
||||||
|
ChatRequestResponse { accepted: bool, to: PublicKey },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientEventType {
|
impl ClientEventType {
|
||||||
|
|
|
@ -19,7 +19,10 @@
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use oxidetalis_core::{cipher::K256Secret, types::Signature};
|
use oxidetalis_core::{
|
||||||
|
cipher::K256Secret,
|
||||||
|
types::{PublicKey, Signature},
|
||||||
|
};
|
||||||
use salvo::websocket::Message;
|
use salvo::websocket::Message;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
@ -28,6 +31,7 @@ use crate::websocket::errors::WsError;
|
||||||
/// Signed marker, used to indicate that the event is signed
|
/// Signed marker, used to indicate that the event is signed
|
||||||
pub struct Signed;
|
pub struct Signed;
|
||||||
/// Unsigned marker, used to indicate that the event is unsigned
|
/// Unsigned marker, used to indicate that the event is unsigned
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Unsigned;
|
pub struct Unsigned;
|
||||||
|
|
||||||
/// Server websocket event
|
/// Server websocket event
|
||||||
|
@ -42,12 +46,16 @@ pub struct ServerEvent<T> {
|
||||||
|
|
||||||
/// server websocket event type
|
/// server websocket event type
|
||||||
#[derive(Serialize, Clone, Eq, PartialEq, Debug)]
|
#[derive(Serialize, Clone, Eq, PartialEq, Debug)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase", tag = "event", content = "data")]
|
||||||
pub enum ServerEventType {
|
pub enum ServerEventType {
|
||||||
/// Ping event
|
/// Ping event
|
||||||
Ping { timestamp: u64 },
|
Ping { timestamp: u64 },
|
||||||
/// Pong event
|
/// Pong event
|
||||||
Pong { timestamp: u64 },
|
Pong { timestamp: u64 },
|
||||||
|
/// New chat request from someone
|
||||||
|
ChatRequest { from: PublicKey },
|
||||||
|
/// New chat request response from someone
|
||||||
|
ChatRequestResponse { accepted: bool, from: PublicKey },
|
||||||
/// Error event
|
/// Error event
|
||||||
awiteb marked this conversation as resolved
Amjad50
commented
in the client, you mentioned to make it alphabetical, should it be the same here? in the client, you mentioned to make it alphabetical, should it be the same here?
awiteb
commented
Yes, I forgot it Yes, I forgot it
|
|||||||
Error {
|
Error {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
|
@ -88,6 +96,16 @@ impl ServerEvent<Unsigned> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create chat request event
|
||||||
|
pub fn chat_request(from: &PublicKey) -> Self {
|
||||||
|
Self::new(ServerEventType::ChatRequest { from: *from })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create chat request response event
|
||||||
|
pub fn chat_request_response(from: PublicKey, accepted: bool) -> Self {
|
||||||
|
Self::new(ServerEventType::ChatRequestResponse { from, accepted })
|
||||||
|
}
|
||||||
|
|
||||||
/// Sign the event
|
/// Sign the event
|
||||||
pub fn sign(self, shared_secret: &[u8; 32]) -> ServerEvent<Signed> {
|
pub fn sign(self, shared_secret: &[u8; 32]) -> ServerEvent<Signed> {
|
||||||
ServerEvent::<Signed> {
|
ServerEvent::<Signed> {
|
||||||
|
@ -101,6 +119,12 @@ impl ServerEvent<Unsigned> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S> AsRef<Self> for ServerEvent<S> {
|
||||||
|
fn as_ref(&self) -> &Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&ServerEvent<Signed>> for Message {
|
impl From<&ServerEvent<Signed>> for Message {
|
||||||
fn from(value: &ServerEvent<Signed>) -> Self {
|
fn from(value: &ServerEvent<Signed>) -> Self {
|
||||||
Message::text(serde_json::to_string(value).expect("This can't fail"))
|
Message::text(serde_json::to_string(value).expect("This can't fail"))
|
||||||
|
|
138
crates/oxidetalis/src/websocket/handlers/chat_request.rs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
// OxideTalis Messaging Protocol homeserver implementation
|
||||||
|
// Copyright (C) 2024 OxideTalis Developers <otmp@4rs.nl>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://gnu.org/licenses/agpl-3.0>.
|
||||||
|
|
||||||
|
//! Handler for incoming and outgoing chat requests.
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use oxidetalis_core::types::PublicKey;
|
||||||
|
use oxidetalis_entities::prelude::*;
|
||||||
|
use sea_orm::DatabaseConnection;
|
||||||
|
|
||||||
|
use crate::database::InChatRequestsExt;
|
||||||
|
use crate::errors::ServerError;
|
||||||
|
use crate::extensions::OnlineUsersExt;
|
||||||
|
use crate::{
|
||||||
|
database::{OutChatRequestsExt, UserTableExt, UsersStatusExt},
|
||||||
|
try_ws,
|
||||||
|
websocket::{errors::WsError, ServerEvent, Unsigned, ONLINE_USERS},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Handle a chat request from a user.
|
||||||
|
#[logcall::logcall]
|
||||||
|
pub async fn handle_chat_request(
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
from: Option<&UserModel>,
|
||||||
|
to_public_key: &PublicKey,
|
||||||
|
) -> Option<ServerEvent<Unsigned>> {
|
||||||
|
let Some(from_user) = from else {
|
||||||
|
return Some(WsError::RegistredUserEvent.into());
|
||||||
|
};
|
||||||
|
let Some(to_user) = try_ws!(Some db.get_user_by_pubk(to_public_key).await) else {
|
||||||
|
return Some(WsError::UserNotFound.into());
|
||||||
|
};
|
||||||
|
if from_user.id == to_user.id {
|
||||||
|
return Some(WsError::CannotSendChatRequestToSelf.into());
|
||||||
|
}
|
||||||
|
// FIXME: When change the entity public key to a PublicKey type, change this
|
||||||
|
let from_public_key = PublicKey::from_str(&from_user.public_key).expect("Is valid public key");
|
||||||
|
|
||||||
|
if try_ws!(Some db.get_chat_request_to(from_user, to_public_key).await).is_some() {
|
||||||
awiteb marked this conversation as resolved
Amjad50
commented
Maybe would be better to move this to Maybe would be better to move this to `WsError` easier to manage errors
there are also others below this
awiteb
commented
Right, this should be an error, not a message. I don't know how is this happened Right, this should be an error, not a message. I don't know how is this happened
awiteb
commented
I'll remove I'll remove `Message` event it is actually useless. I don't know why I thought it was good.
awiteb
commented
Well, I remembered when I added it, it was a pain to add a new error (before > I don't know why I thought it was good.
Well, I remembered when I added it, it was a pain to add a new error (before `ws_error` macro) so I thought it was good for simple messages.
|
|||||||
|
return Some(WsError::AlreadySendChatRequest.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if try_ws!(Some db.is_blacklisted(&to_user, &from_public_key).await) {
|
||||||
|
return Some(WsError::RecipientBlacklist.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// To ignore the error if the requester added the recipient to the whitelist
|
||||||
|
// table before send a request to them
|
||||||
|
if let Err(ServerError::Internal(_)) = db.add_to_whitelist(from_user, to_public_key).await {
|
||||||
|
return Some(WsError::InternalServerError.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if try_ws!(Some db.is_whitelisted(&to_user, &from_public_key).await) {
|
||||||
|
return Some(WsError::AlreadyInRecipientWhitelist.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
try_ws!(Some db.save_out_chat_request(from_user, to_public_key).await);
|
||||||
|
if let Some(conn_id) = ONLINE_USERS.is_online(to_public_key).await {
|
||||||
|
ONLINE_USERS
|
||||||
|
.send(&conn_id, ServerEvent::chat_request(&from_public_key))
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
try_ws!(Some db.save_in_chat_request(&from_public_key, &to_user).await);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[logcall::logcall]
|
||||||
|
pub async fn handle_chat_response(
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
recipient: Option<&UserModel>,
|
||||||
|
sender_public_key: &PublicKey,
|
||||||
|
accepted: bool,
|
||||||
|
) -> Option<ServerEvent<Unsigned>> {
|
||||||
|
let Some(recipient_user) = recipient else {
|
||||||
|
return Some(WsError::RegistredUserEvent.into());
|
||||||
|
};
|
||||||
|
let Some(sender_user) = try_ws!(Some db.get_user_by_pubk(sender_public_key).await) else {
|
||||||
|
return Some(WsError::UserNotFound.into());
|
||||||
|
};
|
||||||
|
if recipient_user.id == sender_user.id {
|
||||||
|
return Some(WsError::CannotRespondToOwnChatRequest.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: When change the entity public key to a PublicKey type, change this
|
||||||
|
let recipient_public_key =
|
||||||
|
PublicKey::from_str(&recipient_user.public_key).expect("Is valid public key");
|
||||||
|
|
||||||
|
if try_ws!(Some
|
||||||
|
db.get_chat_request_to(&sender_user, &recipient_public_key)
|
||||||
|
.await
|
||||||
|
)
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
return Some(WsError::NoChatRequestFromRecipient.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't need to handle the case where the sender is blacklisted or
|
||||||
|
// whitelisted already, just add it if it is not already there
|
||||||
|
let _ = if accepted {
|
||||||
|
db.add_to_whitelist(recipient_user, sender_public_key).await
|
||||||
|
} else {
|
||||||
|
db.add_to_blacklist(recipient_user, sender_public_key).await
|
||||||
|
};
|
||||||
|
|
||||||
|
try_ws!(Some
|
||||||
|
db.remove_out_chat_request(&sender_user, &recipient_public_key)
|
||||||
|
.await
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(conn_id) = ONLINE_USERS.is_online(sender_public_key).await {
|
||||||
|
ONLINE_USERS
|
||||||
|
.send(
|
||||||
|
&conn_id,
|
||||||
|
ServerEvent::chat_request_response(recipient_public_key, accepted),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
// TODO: Create a table for chat request responses, and send them when
|
||||||
|
// the user logs in
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
21
crates/oxidetalis/src/websocket/handlers/mod.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// OxideTalis Messaging Protocol homeserver implementation
|
||||||
|
// Copyright (C) 2024 OxideTalis Developers <otmp@4rs.nl>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://gnu.org/licenses/agpl-3.0>.
|
||||||
|
|
||||||
|
//! Websocket event handlers.
|
||||||
|
|
||||||
|
mod chat_request;
|
||||||
|
|
||||||
|
pub use chat_request::*;
|
|
@ -21,6 +21,7 @@ use errors::{WsError, WsResult};
|
||||||
use futures::{channel::mpsc, FutureExt, StreamExt, TryStreamExt};
|
use futures::{channel::mpsc, FutureExt, StreamExt, TryStreamExt};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use oxidetalis_core::{cipher::K256Secret, types::PublicKey};
|
use oxidetalis_core::{cipher::K256Secret, types::PublicKey};
|
||||||
|
use oxidetalis_entities::prelude::*;
|
||||||
use salvo::{
|
use salvo::{
|
||||||
handler,
|
handler,
|
||||||
http::StatusError,
|
http::StatusError,
|
||||||
|
@ -30,15 +31,18 @@ use salvo::{
|
||||||
Response,
|
Response,
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
|
use sea_orm::DatabaseConnection;
|
||||||
use tokio::{sync::RwLock, task::spawn as tokio_spawn, time::sleep as tokio_sleep};
|
use tokio::{sync::RwLock, task::spawn as tokio_spawn, time::sleep as tokio_sleep};
|
||||||
|
|
||||||
mod errors;
|
pub mod errors;
|
||||||
mod events;
|
mod events;
|
||||||
|
mod handlers;
|
||||||
|
|
||||||
pub use events::*;
|
pub use events::*;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
database::UserTableExt,
|
||||||
extensions::{DepotExt, OnlineUsersExt},
|
extensions::{DepotExt, OnlineUsersExt},
|
||||||
middlewares,
|
middlewares,
|
||||||
nonce::NonceCache,
|
nonce::NonceCache,
|
||||||
|
@ -92,6 +96,7 @@ pub async fn user_connected(
|
||||||
depot: &Depot,
|
depot: &Depot,
|
||||||
) -> Result<(), StatusError> {
|
) -> Result<(), StatusError> {
|
||||||
let nonce_cache = depot.nonce_cache();
|
let nonce_cache = depot.nonce_cache();
|
||||||
|
let db_conn = depot.db_conn();
|
||||||
let public_key =
|
let public_key =
|
||||||
utils::extract_public_key(req).expect("The public key was checked in the middleware");
|
utils::extract_public_key(req).expect("The public key was checked in the middleware");
|
||||||
// FIXME: The config should hold `K256Secret` not `PrivateKey`
|
// FIXME: The config should hold `K256Secret` not `PrivateKey`
|
||||||
|
@ -100,7 +105,7 @@ pub async fn user_connected(
|
||||||
|
|
||||||
WebSocketUpgrade::new()
|
WebSocketUpgrade::new()
|
||||||
.upgrade(req, res, move |ws| {
|
.upgrade(req, res, move |ws| {
|
||||||
handle_socket(ws, nonce_cache, public_key, shared_secret)
|
handle_socket(ws, db_conn, nonce_cache, public_key, shared_secret)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -108,6 +113,7 @@ pub async fn user_connected(
|
||||||
/// Handle the websocket connection
|
/// Handle the websocket connection
|
||||||
async fn handle_socket(
|
async fn handle_socket(
|
||||||
ws: WebSocket,
|
ws: WebSocket,
|
||||||
|
db_conn: Arc<DatabaseConnection>,
|
||||||
nonce_cache: Arc<NonceCache>,
|
nonce_cache: Arc<NonceCache>,
|
||||||
user_public_key: PublicKey,
|
user_public_key: PublicKey,
|
||||||
user_shared_secret: [u8; 32],
|
user_shared_secret: [u8; 32],
|
||||||
|
@ -123,27 +129,47 @@ async fn handle_socket(
|
||||||
});
|
});
|
||||||
tokio_spawn(fut);
|
tokio_spawn(fut);
|
||||||
let conn_id = Uuid::new_v4();
|
let conn_id = Uuid::new_v4();
|
||||||
let user = SocketUserData::new(user_public_key, user_shared_secret, sender.clone());
|
let Ok(user) = db_conn.get_user_by_pubk(&user_public_key).await else {
|
||||||
ONLINE_USERS.add_user(&conn_id, user).await;
|
let _ = sender.unbounded_send(Ok(ServerEvent::from(WsError::InternalServerError)
|
||||||
|
.sign(&user_shared_secret)
|
||||||
|
.as_ref()
|
||||||
|
.into()));
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
ONLINE_USERS
|
||||||
|
.add_user(
|
||||||
|
&conn_id,
|
||||||
|
SocketUserData::new(user_public_key, user_shared_secret, sender.clone()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
log::info!("New user connected: ConnId(={conn_id}) PublicKey(={user_public_key})");
|
log::info!("New user connected: ConnId(={conn_id}) PublicKey(={user_public_key})");
|
||||||
|
|
||||||
|
// TODO: Send the incoming chat request to the user, while they are offline.
|
||||||
|
// This after adding last_login col to the user table
|
||||||
|
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
while let Some(Ok(msg)) = user_ws_receiver.next().await {
|
while let Some(Ok(msg)) = user_ws_receiver.next().await {
|
||||||
match handle_ws_msg(msg, &nonce_cache, &user_shared_secret).await {
|
match handle_ws_msg(msg, &nonce_cache, &user_shared_secret).await {
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
if let Some(server_event) = handle_events(event, &conn_id).await {
|
if let Some(server_event) =
|
||||||
if let Err(err) = sender.unbounded_send(Ok(Message::from(
|
handle_events(event, &db_conn, &conn_id, user.as_ref()).await
|
||||||
&server_event.sign(&user_shared_secret),
|
{
|
||||||
))) {
|
if let Err(err) = sender.unbounded_send(Ok(server_event
|
||||||
|
.sign(&user_shared_secret)
|
||||||
|
.as_ref()
|
||||||
|
.into()))
|
||||||
|
{
|
||||||
log::error!("Websocket Error: {err}");
|
log::error!("Websocket Error: {err}");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if let Err(err) = sender.unbounded_send(Ok(Message::from(
|
if let Err(err) = sender.unbounded_send(Ok(ServerEvent::from(err)
|
||||||
&ServerEvent::from(err).sign(&user_shared_secret),
|
.sign(&user_shared_secret)
|
||||||
))) {
|
.as_ref()
|
||||||
|
.into()))
|
||||||
|
{
|
||||||
log::error!("Websocket Error: {err}");
|
log::error!("Websocket Error: {err}");
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
@ -178,13 +204,22 @@ async fn handle_ws_msg(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle user events, and return the server event if needed
|
/// Handle user events, and return the server event if needed
|
||||||
async fn handle_events(event: ClientEvent, conn_id: &Uuid) -> Option<ServerEvent<Unsigned>> {
|
async fn handle_events(
|
||||||
|
event: ClientEvent,
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
conn_id: &Uuid,
|
||||||
|
user: Option<&UserModel>,
|
||||||
|
) -> Option<ServerEvent<Unsigned>> {
|
||||||
match &event.event {
|
match &event.event {
|
||||||
ClientEventType::Ping { .. } => Some(ServerEvent::pong()),
|
ClientEventType::Ping { .. } => Some(ServerEvent::pong()),
|
||||||
ClientEventType::Pong { .. } => {
|
ClientEventType::Pong { .. } => {
|
||||||
ONLINE_USERS.update_pong(conn_id).await;
|
ONLINE_USERS.update_pong(conn_id).await;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
ClientEventType::ChatRequest { to } => handlers::handle_chat_request(db, user, to).await,
|
||||||
|
ClientEventType::ChatRequestResponse { to, accepted } => {
|
||||||
|
handlers::handle_chat_response(db, user, to, *accepted).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,8 @@ rust-version.workspace = true
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sea-orm = {workspace = true }
|
sea-orm = { workspace = true }
|
||||||
|
chrono = { workspace = true }
|
||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
unsafe_code = "deny"
|
unsafe_code = "deny"
|
||||||
|
|
57
crates/oxidetalis_entities/src/incoming_chat_requests.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// OxideTalis Messaging Protocol homeserver core implementation
|
||||||
|
// Copyright (c) 2024 OxideTalis Developers <otmp@4rs.nl>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
use chrono::Utc;
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "in_chat_requests")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: UserId,
|
||||||
|
pub recipient_id: UserId,
|
||||||
|
/// Public key of the sender
|
||||||
|
pub sender: String,
|
||||||
|
/// The timestamp of the request, when it was received
|
||||||
|
pub in_on: chrono::DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "UserEntity",
|
||||||
|
from = "Column::RecipientId",
|
||||||
|
to = "super::users::Column::Id"
|
||||||
|
on_update = "NoAction",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
RecipientId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<UserEntity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::RecipientId.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -19,5 +19,8 @@
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
|
|
||||||
|
pub mod incoming_chat_requests;
|
||||||
|
pub mod outgoing_chat_requests;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
pub mod users_status;
|
||||||
|
|
57
crates/oxidetalis_entities/src/outgoing_chat_requests.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// OxideTalis Messaging Protocol homeserver core implementation
|
||||||
|
// Copyright (c) 2024 OxideTalis Developers <otmp@4rs.nl>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
use chrono::Utc;
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "out_chat_requests")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: UserId,
|
||||||
|
pub sender_id: UserId,
|
||||||
|
/// Public key of the recipient
|
||||||
|
pub recipient: String,
|
||||||
|
/// The timestamp of the request, when it was sent
|
||||||
|
pub out_on: chrono::DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "UserEntity",
|
||||||
|
from = "Column::SenderId",
|
||||||
|
to = "super::users::Column::Id"
|
||||||
|
on_update = "NoAction",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
SenderId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<UserEntity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::SenderId.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -22,8 +22,10 @@
|
||||||
pub use sea_orm::{
|
pub use sea_orm::{
|
||||||
ActiveModelTrait,
|
ActiveModelTrait,
|
||||||
ColumnTrait,
|
ColumnTrait,
|
||||||
|
EntityOrSelect,
|
||||||
EntityTrait,
|
EntityTrait,
|
||||||
IntoActiveModel,
|
IntoActiveModel,
|
||||||
|
ModelTrait,
|
||||||
Order,
|
Order,
|
||||||
PaginatorTrait,
|
PaginatorTrait,
|
||||||
QueryFilter,
|
QueryFilter,
|
||||||
|
@ -33,9 +35,31 @@ pub use sea_orm::{
|
||||||
SqlErr,
|
SqlErr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// User ID type
|
||||||
|
pub type UserId = i64;
|
||||||
|
|
||||||
|
pub use super::incoming_chat_requests::{
|
||||||
|
ActiveModel as InChatRequestsActiveModel,
|
||||||
|
Column as InChatRequestsColumn,
|
||||||
|
Entity as InChatRequestsEntity,
|
||||||
|
Model as InChatRequestsModel,
|
||||||
|
};
|
||||||
|
pub use super::outgoing_chat_requests::{
|
||||||
|
ActiveModel as OutChatRequestsActiveModel,
|
||||||
|
Column as OutChatRequestsColumn,
|
||||||
|
Entity as OutChatRequestsEntity,
|
||||||
|
Model as OutChatRequestsModel,
|
||||||
|
};
|
||||||
pub use super::users::{
|
pub use super::users::{
|
||||||
ActiveModel as UserActiveModel,
|
ActiveModel as UserActiveModel,
|
||||||
Column as UserColumn,
|
Column as UserColumn,
|
||||||
Entity as UserEntity,
|
Entity as UserEntity,
|
||||||
Model as UserModel,
|
Model as UserModel,
|
||||||
};
|
};
|
||||||
|
pub use super::users_status::{
|
||||||
|
AccessStatus,
|
||||||
|
ActiveModel as UsersStatusActiveModel,
|
||||||
|
Column as UsersStatusColumn,
|
||||||
|
Entity as UsersStatusEntity,
|
||||||
|
Model as UsersStatusModel,
|
||||||
|
};
|
||||||
|
|
|
@ -21,16 +21,43 @@
|
||||||
|
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
#[sea_orm(table_name = "users")]
|
#[sea_orm(table_name = "users")]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key)]
|
#[sea_orm(primary_key)]
|
||||||
pub id: i32,
|
pub id: UserId,
|
||||||
pub public_key: String,
|
pub public_key: String,
|
||||||
pub is_admin: bool,
|
pub is_admin: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
pub enum Relation {}
|
pub enum Relation {
|
||||||
|
#[sea_orm(has_many = "InChatRequestsEntity")]
|
||||||
|
InChatRequests,
|
||||||
|
#[sea_orm(has_many = "OutChatRequestsEntity")]
|
||||||
|
OutChatRequests,
|
||||||
|
#[sea_orm(has_many = "UsersStatusEntity")]
|
||||||
|
UsersStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<InChatRequestsEntity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::InChatRequests.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<OutChatRequestsEntity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::OutChatRequests.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<UsersStatusEntity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::UsersStatus.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
66
crates/oxidetalis_entities/src/users_status.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// OxideTalis Messaging Protocol homeserver core implementation
|
||||||
|
// Copyright (c) 2024 OxideTalis Developers <otmp@4rs.nl>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
use chrono::Utc;
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, EnumIter, DeriveActiveEnum)]
|
||||||
|
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "access_status")]
|
||||||
|
pub enum AccessStatus {
|
||||||
|
#[sea_orm(string_value = "whitelisted")]
|
||||||
|
Whitelisted,
|
||||||
|
#[sea_orm(string_value = "blacklisted")]
|
||||||
|
Blacklisted,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "users_status")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: UserId,
|
||||||
|
pub user_id: UserId,
|
||||||
|
/// Public key of the target
|
||||||
|
pub target: String,
|
||||||
|
pub status: AccessStatus,
|
||||||
|
pub updated_at: chrono::DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "UserEntity",
|
||||||
|
from = "Column::UserId",
|
||||||
|
to = "super::users::Column::Id"
|
||||||
|
on_update = "NoAction",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
UserId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<UserEntity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::UserId.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -0,0 +1,89 @@
|
||||||
|
// OxideTalis Messaging Protocol homeserver core implementation
|
||||||
|
// Copyright (c) 2024 OxideTalis Developers <otmp@4rs.nl>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
use crate::create_users_table::Users;
|
||||||
|
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.create_table(
|
||||||
|
Table::create()
|
||||||
|
.table(InChatRequests::Table)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(InChatRequests::Id)
|
||||||
|
.big_integer()
|
||||||
|
.not_null()
|
||||||
|
.auto_increment()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(InChatRequests::RecipientId)
|
||||||
|
.big_integer()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.foreign_key(
|
||||||
|
ForeignKey::create()
|
||||||
|
.name("fk-in_chat_requests-users")
|
||||||
|
.from(InChatRequests::Table, InChatRequests::RecipientId)
|
||||||
|
.to(Users::Table, Users::Id)
|
||||||
|
.on_update(ForeignKeyAction::NoAction)
|
||||||
|
.on_delete(ForeignKeyAction::Cascade),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(InChatRequests::Sender).string().not_null())
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(InChatRequests::InOn)
|
||||||
|
.timestamp_with_time_zone()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
manager
|
||||||
|
.create_index(
|
||||||
|
Index::create()
|
||||||
|
.if_not_exists()
|
||||||
|
.name("sep_request")
|
||||||
|
.table(InChatRequests::Table)
|
||||||
|
.col(InChatRequests::RecipientId)
|
||||||
|
.col(InChatRequests::Sender)
|
||||||
|
.unique()
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(DeriveIden)]
|
||||||
|
enum InChatRequests {
|
||||||
|
Table,
|
||||||
|
Id,
|
||||||
|
RecipientId,
|
||||||
|
/// Public key of the sender
|
||||||
|
Sender,
|
||||||
|
InOn,
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
// OxideTalis Messaging Protocol homeserver core implementation
|
||||||
|
// Copyright (c) 2024 OxideTalis Developers <otmp@4rs.nl>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
use crate::create_users_table::Users;
|
||||||
|
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.create_table(
|
||||||
|
Table::create()
|
||||||
|
.table(OutChatRequests::Table)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(OutChatRequests::Id)
|
||||||
|
.big_integer()
|
||||||
|
.not_null()
|
||||||
|
.auto_increment()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(OutChatRequests::SenderId)
|
||||||
|
.big_integer()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.foreign_key(
|
||||||
|
ForeignKey::create()
|
||||||
|
.name("fk-out_chat_requests-users")
|
||||||
|
.from(OutChatRequests::Table, OutChatRequests::SenderId)
|
||||||
|
.to(Users::Table, Users::Id)
|
||||||
|
.on_update(ForeignKeyAction::NoAction)
|
||||||
|
.on_delete(ForeignKeyAction::Cascade),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(OutChatRequests::Recipient)
|
||||||
|
.string()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(OutChatRequests::OutOn)
|
||||||
|
.timestamp_with_time_zone()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.create_index(
|
||||||
|
Index::create()
|
||||||
|
.if_not_exists()
|
||||||
|
.name("sep_request")
|
||||||
|
.table(OutChatRequests::Table)
|
||||||
|
.col(OutChatRequests::SenderId)
|
||||||
|
.col(OutChatRequests::Recipient)
|
||||||
|
.unique()
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(DeriveIden)]
|
||||||
|
enum OutChatRequests {
|
||||||
|
Table,
|
||||||
|
Id,
|
||||||
|
SenderId,
|
||||||
|
/// Public key of the recipient
|
||||||
|
Recipient,
|
||||||
|
OutOn,
|
||||||
|
}
|
128
crates/oxidetalis_migrations/src/create_users_status.rs
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
// OxideTalis Messaging Protocol homeserver core implementation
|
||||||
|
// Copyright (c) 2024 OxideTalis Developers <otmp@4rs.nl>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use sea_orm::sea_query::extension::postgres::Type;
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
use super::create_users_table::Users;
|
||||||
|
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.create_type(
|
||||||
|
Type::create()
|
||||||
|
.as_enum(AccessStatus::Name)
|
||||||
|
.values(vec![AccessStatus::Whitelisted, AccessStatus::Blacklisted])
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.create_table(
|
||||||
|
Table::create()
|
||||||
|
.table(UsersStatus::Table)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(UsersStatus::Id)
|
||||||
|
.big_integer()
|
||||||
|
.not_null()
|
||||||
|
.auto_increment()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(UsersStatus::UserId).big_integer().not_null())
|
||||||
|
.foreign_key(
|
||||||
|
ForeignKey::create()
|
||||||
|
.name("fk-users_status-users")
|
||||||
|
.from(UsersStatus::Table, UsersStatus::UserId)
|
||||||
|
.to(Users::Table, Users::Id)
|
||||||
|
.on_update(ForeignKeyAction::NoAction)
|
||||||
|
.on_delete(ForeignKeyAction::Cascade),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(UsersStatus::Target).string().not_null())
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(UsersStatus::Status)
|
||||||
|
.enumeration(
|
||||||
|
AccessStatus::Name,
|
||||||
|
[AccessStatus::Whitelisted, AccessStatus::Blacklisted],
|
||||||
|
)
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(UsersStatus::UpdatedAt)
|
||||||
|
.timestamp_with_time_zone()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.create_index(
|
||||||
|
Index::create()
|
||||||
|
.if_not_exists()
|
||||||
|
.name("sep_status")
|
||||||
|
.table(UsersStatus::Table)
|
||||||
|
.col(UsersStatus::UserId)
|
||||||
|
.col(UsersStatus::Target)
|
||||||
|
.unique()
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AccessStatus {
|
||||||
|
Name,
|
||||||
|
Whitelisted,
|
||||||
|
Blacklisted,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(DeriveIden)]
|
||||||
|
enum UsersStatus {
|
||||||
|
Table,
|
||||||
|
Id,
|
||||||
|
UserId,
|
||||||
|
/// Public key of the target
|
||||||
|
Target,
|
||||||
|
Status,
|
||||||
|
UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iden for AccessStatus {
|
||||||
|
fn unquoted(&self, s: &mut dyn fmt::Write) {
|
||||||
|
write!(
|
||||||
|
s,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
Self::Name => "access_status",
|
||||||
|
Self::Whitelisted => "whitelisted",
|
||||||
|
Self::Blacklisted => "blacklisted",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.expect("is a string")
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,7 +34,7 @@ impl MigrationTrait for Migration {
|
||||||
.if_not_exists()
|
.if_not_exists()
|
||||||
.col(
|
.col(
|
||||||
ColumnDef::new(Users::Id)
|
ColumnDef::new(Users::Id)
|
||||||
.integer()
|
.big_integer()
|
||||||
.not_null()
|
.not_null()
|
||||||
.auto_increment()
|
.auto_increment()
|
||||||
.primary_key(),
|
.primary_key(),
|
||||||
|
@ -58,7 +58,7 @@ impl MigrationTrait for Migration {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(DeriveIden)]
|
#[derive(DeriveIden)]
|
||||||
enum Users {
|
pub enum Users {
|
||||||
Table,
|
Table,
|
||||||
Id,
|
Id,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
|
|
|
@ -19,8 +19,12 @@
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
|
|
||||||
pub use sea_orm_migration::prelude::*;
|
use sea_orm_migration::prelude::*;
|
||||||
|
pub use sea_orm_migration::MigratorTrait;
|
||||||
|
|
||||||
|
mod create_incoming_chat_requests_table;
|
||||||
|
mod create_outgoing_chat_requests_table;
|
||||||
|
mod create_users_status;
|
||||||
mod create_users_table;
|
mod create_users_table;
|
||||||
|
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
@ -28,6 +32,11 @@ pub struct Migrator;
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl MigratorTrait for Migrator {
|
impl MigratorTrait for Migrator {
|
||||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||||
vec![Box::new(create_users_table::Migration)]
|
vec![
|
||||||
|
Box::new(create_users_table::Migration),
|
||||||
|
Box::new(create_incoming_chat_requests_table::Migration),
|
||||||
|
Box::new(create_outgoing_chat_requests_table::Migration),
|
||||||
|
Box::new(create_users_status::Migration),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
since we are not dealing with the result. maybe a one statement approach to do
here i'm updating
InOn
if needed, but can also be changed todo_nothing()