feat: Chat request implementation #14
11 changed files with 147 additions and 106 deletions
|
@ -20,7 +20,7 @@ use oxidetalis_core::types::PublicKey;
|
|||
use oxidetalis_entities::prelude::*;
|
||||
use sea_orm::DatabaseConnection;
|
||||
|
||||
use crate::errors::ApiResult;
|
||||
use crate::errors::ServerResult;
|
||||
|
||||
/// Extension trait for the `DatabaseConnection` to work with the blacklist
|
||||
/// table
|
||||
|
@ -31,7 +31,7 @@ pub trait BlackListExt {
|
|||
&self,
|
||||
blacklister: &UserModel,
|
||||
awiteb marked this conversation as resolved
|
||||
target_public_key: &PublicKey,
|
||||
) -> ApiResult<bool>;
|
||||
) -> ServerResult<bool>;
|
||||
}
|
||||
|
||||
impl BlackListExt for DatabaseConnection {
|
||||
|
@ -40,7 +40,7 @@ impl BlackListExt for DatabaseConnection {
|
|||
&self,
|
||||
blacklister: &UserModel,
|
||||
target_public_key: &PublicKey,
|
||||
) -> ApiResult<bool> {
|
||||
) -> ServerResult<bool> {
|
||||
blacklister
|
||||
.find_related(BlacklistEntity)
|
||||
.filter(BlacklistColumn::Target.eq(target_public_key.to_string()))
|
||||
|
|
|
@ -21,7 +21,7 @@ use oxidetalis_core::types::PublicKey;
|
|||
use oxidetalis_entities::prelude::*;
|
||||
use sea_orm::DatabaseConnection;
|
||||
|
||||
use crate::websocket::errors::{WsError, WsResult};
|
||||
use crate::errors::ServerResult;
|
||||
|
||||
/// Extension trait for the `in_chat_requests` table.
|
||||
pub trait InChatRequestsExt {
|
||||
|
@ -30,7 +30,7 @@ pub trait InChatRequestsExt {
|
|||
&self,
|
||||
requester: &PublicKey,
|
||||
recipient: &UserModel,
|
||||
) -> WsResult<()>;
|
||||
) -> ServerResult<()>;
|
||||
}
|
||||
|
||||
impl InChatRequestsExt for DatabaseConnection {
|
||||
|
@ -39,16 +39,12 @@ impl InChatRequestsExt for DatabaseConnection {
|
|||
&self,
|
||||
sender: &PublicKey,
|
||||
recipient: &UserModel,
|
||||
) -> WsResult<()> {
|
||||
if sender.to_string() == recipient.public_key {
|
||||
return Err(WsError::CannotSendChatRequestToSelf);
|
||||
}
|
||||
) -> ServerResult<()> {
|
||||
if recipient
|
||||
.find_related(InChatRequestsEntity)
|
||||
.filter(InChatRequestsColumn::Sender.eq(sender.to_string()))
|
||||
.one(self)
|
||||
.await
|
||||
.map_err(|_| WsError::InternalServerError)?
|
||||
.await?
|
||||
.is_none()
|
||||
awiteb marked this conversation as resolved
Amjad50
commented
since we are not dealing with the result. maybe a one statement approach to do
here i'm updating since we are not dealing with the result. maybe a one statement approach to do
```rust
InChatRequestsEntity::insert(InChatRequestsActiveModel {
recipient_id: Set(recipient.id),
sender: Set(sender.to_string()),
in_on: Set(Utc::now()),
..Default::default()
})
.on_conflict(
OnConflict::columns([
InChatRequestsColumn::RecipientId,
InChatRequestsColumn::Sender,
])
.update_column(InChatRequestsColumn::InOn)
.to_owned(),
)
.exec(self)
```
here i'm updating `InOn` if needed, but can also be changed to `do_nothing()`
|
||||
{
|
||||
InChatRequestsActiveModel {
|
||||
|
@ -58,8 +54,7 @@ impl InChatRequestsExt for DatabaseConnection {
|
|||
..Default::default()
|
||||
}
|
||||
.save(self)
|
||||
.await
|
||||
.map_err(|_| WsError::InternalServerError)?;
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -21,10 +21,7 @@ use oxidetalis_core::types::PublicKey;
|
|||
use oxidetalis_entities::prelude::*;
|
||||
use sea_orm::DatabaseConnection;
|
||||
|
||||
use crate::{
|
||||
errors::ApiResult,
|
||||
websocket::errors::{WsError, WsResult},
|
||||
};
|
||||
use crate::{errors::ServerResult, websocket::errors::WsError};
|
||||
|
||||
/// Extension trait for the `out_chat_requests` table.
|
||||
pub trait OutChatRequestsExt {
|
||||
|
@ -33,14 +30,14 @@ pub trait OutChatRequestsExt {
|
|||
&self,
|
||||
requester: &UserModel,
|
||||
recipient: &PublicKey,
|
||||
) -> ApiResult<bool>;
|
||||
) -> ServerResult<bool>;
|
||||
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
|
||||
|
||||
/// Save the chat request in the requester table
|
||||
async fn save_out_chat_request(
|
||||
&self,
|
||||
requester: &UserModel,
|
||||
recipient: &PublicKey,
|
||||
) -> WsResult<()>;
|
||||
) -> ServerResult<()>;
|
||||
}
|
||||
|
||||
impl OutChatRequestsExt for DatabaseConnection {
|
||||
|
@ -49,7 +46,7 @@ impl OutChatRequestsExt for DatabaseConnection {
|
|||
&self,
|
||||
requester: &UserModel,
|
||||
recipient: &PublicKey,
|
||||
) -> ApiResult<bool> {
|
||||
) -> ServerResult<bool> {
|
||||
requester
|
||||
.find_related(OutChatRequestsEntity)
|
||||
.filter(OutChatRequestsColumn::Recipient.eq(recipient.to_string()))
|
||||
|
@ -64,13 +61,9 @@ impl OutChatRequestsExt for DatabaseConnection {
|
|||
&self,
|
||||
requester: &UserModel,
|
||||
recipient: &PublicKey,
|
||||
) -> WsResult<()> {
|
||||
if self
|
||||
.have_chat_request_to(requester, recipient)
|
||||
.await
|
||||
.map_err(|_| WsError::InternalServerError)?
|
||||
{
|
||||
return Err(WsError::AlreadySendChatRequest);
|
||||
) -> ServerResult<()> {
|
||||
if self.have_chat_request_to(requester, recipient).await? {
|
||||
return Err(WsError::AlreadySendChatRequest.into());
|
||||
}
|
||||
OutChatRequestsActiveModel {
|
||||
sender_id: Set(requester.id),
|
||||
|
@ -79,8 +72,7 @@ impl OutChatRequestsExt for DatabaseConnection {
|
|||
..Default::default()
|
||||
}
|
||||
.save(self)
|
||||
.await
|
||||
.map_err(|_| WsError::InternalServerError)?;
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,29 +21,29 @@ use oxidetalis_core::types::PublicKey;
|
|||
use oxidetalis_entities::prelude::*;
|
||||
use sea_orm::DatabaseConnection;
|
||||
|
||||
use crate::errors::{ApiError, ApiResult};
|
||||
use crate::{errors::ServerResult, routes::ApiError};
|
||||
|
||||
pub trait UserTableExt {
|
||||
/// 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
|
||||
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) -> ApiResult<Option<UserModel>>;
|
||||
async fn get_user_by_pubk(&self, public_key: &PublicKey) -> ServerResult<Option<UserModel>>;
|
||||
}
|
||||
|
||||
impl UserTableExt for DatabaseConnection {
|
||||
#[logcall]
|
||||
async fn users_exists_in_database(&self) -> ApiResult<bool> {
|
||||
async fn users_exists_in_database(&self) -> ServerResult<bool> {
|
||||
UserEntity::find()
|
||||
.one(self)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.map(|u| u.is_some())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[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 {
|
||||
public_key: Set(public_key.to_string()),
|
||||
is_admin: Set(is_admin),
|
||||
|
@ -53,7 +53,7 @@ impl UserTableExt for DatabaseConnection {
|
|||
.await
|
||||
{
|
||||
if let Some(SqlErr::UniqueConstraintViolation(_)) = err.sql_err() {
|
||||
return Err(ApiError::AlreadyRegistered);
|
||||
return Err(ApiError::AlreadyRegistered.into());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,11 +61,11 @@ impl UserTableExt for DatabaseConnection {
|
|||
}
|
||||
|
||||
#[logcall]
|
||||
async fn get_user_by_pubk(&self, public_key: &PublicKey) -> ApiResult<Option<UserModel>> {
|
||||
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(ApiError::SeaOrm)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,10 +21,7 @@ use oxidetalis_core::types::PublicKey;
|
|||
use oxidetalis_entities::prelude::*;
|
||||
use sea_orm::DatabaseConnection;
|
||||
|
||||
use crate::{
|
||||
errors::ApiResult,
|
||||
websocket::errors::{WsError, WsResult},
|
||||
};
|
||||
use crate::{errors::ServerResult, websocket::errors::WsError};
|
||||
|
||||
/// Extension trait for the `DatabaseConnection` to work with the whitelist
|
||||
/// table
|
||||
|
@ -35,14 +32,14 @@ pub trait WhiteListExt {
|
|||
&self,
|
||||
awiteb marked this conversation as resolved
Amjad50
commented
ture ture
The whitelister has whitelisted
|
||||
whitelister: &UserModel,
|
||||
target_public_key: &PublicKey,
|
||||
) -> ApiResult<bool>;
|
||||
) -> ServerResult<bool>;
|
||||
|
||||
/// Add the `target_public_key` to the whitelist of the `whitelister`
|
||||
async fn add_to_whitelist(
|
||||
&self,
|
||||
whitelister: &UserModel,
|
||||
target_public_key: &PublicKey,
|
||||
) -> WsResult<()>;
|
||||
) -> ServerResult<()>;
|
||||
}
|
||||
|
||||
impl WhiteListExt for DatabaseConnection {
|
||||
|
@ -50,7 +47,7 @@ impl WhiteListExt for DatabaseConnection {
|
|||
&self,
|
||||
whitelister: &UserModel,
|
||||
target_public_key: &PublicKey,
|
||||
) -> ApiResult<bool> {
|
||||
) -> ServerResult<bool> {
|
||||
whitelister
|
||||
.find_related(WhitelistEntity)
|
||||
.filter(WhitelistColumn::Target.eq(target_public_key.to_string()))
|
||||
|
@ -64,16 +61,12 @@ impl WhiteListExt for DatabaseConnection {
|
|||
&self,
|
||||
whitelister: &UserModel,
|
||||
target_public_key: &PublicKey,
|
||||
) -> WsResult<()> {
|
||||
if self
|
||||
.is_whitelisted(whitelister, target_public_key)
|
||||
.await
|
||||
.map_err(|_| WsError::InternalServerError)?
|
||||
{
|
||||
return Err(WsError::AlreadyOnTheWhitelist);
|
||||
) -> ServerResult<()> {
|
||||
if self.is_whitelisted(whitelister, target_public_key).await? {
|
||||
return Err(WsError::AlreadyOnTheWhitelist.into());
|
||||
}
|
||||
if whitelister.public_key == target_public_key.to_string() {
|
||||
return Err(WsError::CannotAddSelfToWhitelist);
|
||||
return Err(WsError::CannotAddSelfToWhitelist.into());
|
||||
}
|
||||
WhitelistActiveModel {
|
||||
user_id: Set(whitelister.id),
|
||||
|
@ -82,8 +75,7 @@ impl WhiteListExt for DatabaseConnection {
|
|||
..Default::default()
|
||||
}
|
||||
.save(self)
|
||||
.await
|
||||
.map_err(|_| WsError::InternalServerError)?;
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,24 +14,16 @@
|
|||
// 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 sea_orm::DbErr;
|
||||
|
||||
use crate::{routes::write_json_body, schemas::MessageSchema};
|
||||
use crate::{routes::ApiError, websocket::errors::WsError};
|
||||
|
||||
/// Result type of the homeserver
|
||||
#[allow(clippy::absolute_paths)]
|
||||
pub(crate) type Result<T> = std::result::Result<T, Error>;
|
||||
#[allow(clippy::absolute_paths)]
|
||||
pub type ApiResult<T> = std::result::Result<T, ApiError>;
|
||||
pub(crate) type ServerResult<T> = Result<T, ServerError>;
|
||||
|
||||
/// The homeserver errors
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub(crate) enum Error {
|
||||
pub enum InternalError {
|
||||
#[error("Database Error: {0}")]
|
||||
Database(#[from] sea_orm::DbErr),
|
||||
#[error("{0}")]
|
||||
|
@ -39,43 +31,39 @@ pub(crate) enum Error {
|
|||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ApiError {
|
||||
/// Error from the database (500 Internal Server Error)
|
||||
#[error("Internal server error")]
|
||||
SeaOrm(#[from] sea_orm::DbErr),
|
||||
/// 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,
|
||||
/// The homeserver errors
|
||||
pub enum ServerError {
|
||||
/// Internal server error, should not be exposed to the user
|
||||
#[error("{0}")]
|
||||
Internal(#[from] InternalError),
|
||||
/// API error, errors happening in the API
|
||||
#[error("{0}")]
|
||||
Api(#[from] ApiError),
|
||||
/// WebSocket error, errors happening in the WebSocket
|
||||
#[error("{0}")]
|
||||
Ws(#[from] WsError),
|
||||
}
|
||||
|
||||
impl ApiError {
|
||||
/// Status code of the error
|
||||
pub const fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
Self::SeaOrm(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Self::RegistrationClosed => StatusCode::FORBIDDEN,
|
||||
Self::AlreadyRegistered | Self::TwoDifferentKeys => StatusCode::BAD_REQUEST,
|
||||
impl From<DbErr> for ServerError {
|
||||
fn from(err: DbErr) -> Self {
|
||||
Self::Internal(err.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ServerError> for WsError {
|
||||
fn from(err: ServerError) -> Self {
|
||||
match err {
|
||||
ServerError::Internal(_) | ServerError::Api(_) => WsError::InternalServerError,
|
||||
ServerError::Ws(err) => err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()));
|
||||
impl From<ServerError> for ApiError {
|
||||
fn from(err: ServerError) -> Self {
|
||||
match err {
|
||||
ServerError::Internal(_) | ServerError::Ws(_) => ApiError::Internal,
|
||||
ServerError::Api(err) => err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,9 +39,9 @@ macro_rules! try_ws {
|
|||
match $result_expr {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
log::error!("Error in try_ws macro: {err:?}");
|
||||
log::error!("{err}");
|
||||
return $crate::websocket::ServerEvent::<$crate::websocket::Unsigned>::from(
|
||||
$crate::websocket::errors::WsError::InternalServerError,
|
||||
$crate::websocket::errors::WsError::from(err),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -60,11 +60,12 @@ macro_rules! try_ws {
|
|||
#[macro_export]
|
||||
macro_rules! ws_errors {
|
||||
($($name:ident = $reason:tt),+ $(,)?) => {
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[doc = "Websocket errors, returned in the websocket communication"]
|
||||
pub enum WsError {
|
||||
$(
|
||||
#[doc = $reason]
|
||||
#[error($reason)]
|
||||
$name
|
||||
),+
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
use std::process::ExitCode;
|
||||
|
||||
use errors::ServerError;
|
||||
use oxidetalis_config::{CliArgs, Parser};
|
||||
use oxidetalis_migrations::MigratorTrait;
|
||||
use salvo::{conn::TcpListener, Listener, Server};
|
||||
|
@ -34,11 +35,12 @@ mod schemas;
|
|||
mod utils;
|
||||
mod websocket;
|
||||
|
||||
async fn try_main() -> errors::Result<()> {
|
||||
async fn try_main() -> errors::ServerResult<()> {
|
||||
pretty_env_logger::init_timed();
|
||||
|
||||
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!("Connecting to the database");
|
||||
let connection = sea_orm::Database::connect(utils::postgres_url(&config.postgresdb)).await?;
|
||||
|
|
68
crates/oxidetalis/src/routes/errors.rs
Normal file
68
crates/oxidetalis/src/routes/errors.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
// 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,
|
||||
}
|
||||
|
||||
impl ApiError {
|
||||
/// Status code of the error
|
||||
pub const fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
Self::Internal => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Self::RegistrationClosed => StatusCode::FORBIDDEN,
|
||||
Self::AlreadyRegistered | Self::TwoDifferentKeys => 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::{middlewares, websocket};
|
||||
|
||||
mod errors;
|
||||
mod user;
|
||||
|
||||
pub use errors::*;
|
||||
|
||||
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"))
|
||||
.ok();
|
||||
|
|
|
@ -26,9 +26,9 @@ use salvo::{
|
|||
Writer,
|
||||
};
|
||||
|
||||
use super::{ApiError, ApiResult};
|
||||
use crate::{
|
||||
database::UserTableExt,
|
||||
errors::{ApiError, ApiResult},
|
||||
extensions::DepotExt,
|
||||
middlewares,
|
||||
schemas::{EmptySchema, MessageSchema, RegisterUserBody},
|
||||
|
|
Loading…
Reference in a new issue
ture
->true
(use find and replace all, there are multiple of these)blacklister has blacklisted