From b0c6410f4596328f66d8b9402a4f976d24616e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Tue, 5 Mar 2024 10:21:11 +0100 Subject: [PATCH 1/7] add db to the api state, organize the files --- server/Cargo.toml | 2 + server/src/bin/nightly-connect-server.rs | 2 +- server/src/http/mod.rs | 10 +-- .../src/http/{ => relay}/connect_session.rs | 0 server/src/http/{ => relay}/drop_sessions.rs | 0 .../http/{ => relay}/get_pending_request.rs | 0 .../http/{ => relay}/get_pending_requests.rs | 0 .../src/http/{ => relay}/get_session_info.rs | 0 server/src/http/{ => relay}/get_sessions.rs | 0 .../http/{ => relay}/get_wallets_metadata.rs | 0 server/src/http/relay/mod.rs | 8 ++ .../src/http/{ => relay}/resolve_request.rs | 0 server/src/http/statistics/connect_session.rs | 87 +++++++++++++++++++ server/src/http/statistics/mod.rs | 0 server/src/lib.rs | 2 +- server/src/routes/mod.rs | 2 + server/src/{ => routes}/router.rs | 4 +- server/src/routes/stats_router.rs | 6 ++ server/src/state.rs | 2 + 19 files changed, 114 insertions(+), 11 deletions(-) rename server/src/http/{ => relay}/connect_session.rs (100%) rename server/src/http/{ => relay}/drop_sessions.rs (100%) rename server/src/http/{ => relay}/get_pending_request.rs (100%) rename server/src/http/{ => relay}/get_pending_requests.rs (100%) rename server/src/http/{ => relay}/get_session_info.rs (100%) rename server/src/http/{ => relay}/get_sessions.rs (100%) rename server/src/http/{ => relay}/get_wallets_metadata.rs (100%) create mode 100644 server/src/http/relay/mod.rs rename server/src/http/{ => relay}/resolve_request.rs (100%) create mode 100644 server/src/http/statistics/connect_session.rs create mode 100644 server/src/http/statistics/mod.rs create mode 100644 server/src/routes/mod.rs rename server/src/{ => routes}/router.rs (96%) create mode 100644 server/src/routes/stats_router.rs diff --git a/server/Cargo.toml b/server/Cargo.toml index e65dc9c3..a981f471 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +database = { path = "../database" } + serde = { workspace = true } tokio = { workspace = true } serde_json = { workspace = true } diff --git a/server/src/bin/nightly-connect-server.rs b/server/src/bin/nightly-connect-server.rs index 1add9f03..d3260108 100644 --- a/server/src/bin/nightly-connect-server.rs +++ b/server/src/bin/nightly-connect-server.rs @@ -1,4 +1,4 @@ -use server::router::get_router; +use server::routes::router::get_router; use std::net::SocketAddr; use std::sync::mpsc::channel; diff --git a/server/src/http/mod.rs b/server/src/http/mod.rs index 6cef9d5f..bf528574 100644 --- a/server/src/http/mod.rs +++ b/server/src/http/mod.rs @@ -1,8 +1,2 @@ -pub mod connect_session; -pub mod drop_sessions; -pub mod get_pending_request; -pub mod get_pending_requests; -pub mod get_session_info; -pub mod get_sessions; -pub mod get_wallets_metadata; -pub mod resolve_request; +pub mod relay; +pub mod statistics; diff --git a/server/src/http/connect_session.rs b/server/src/http/relay/connect_session.rs similarity index 100% rename from server/src/http/connect_session.rs rename to server/src/http/relay/connect_session.rs diff --git a/server/src/http/drop_sessions.rs b/server/src/http/relay/drop_sessions.rs similarity index 100% rename from server/src/http/drop_sessions.rs rename to server/src/http/relay/drop_sessions.rs diff --git a/server/src/http/get_pending_request.rs b/server/src/http/relay/get_pending_request.rs similarity index 100% rename from server/src/http/get_pending_request.rs rename to server/src/http/relay/get_pending_request.rs diff --git a/server/src/http/get_pending_requests.rs b/server/src/http/relay/get_pending_requests.rs similarity index 100% rename from server/src/http/get_pending_requests.rs rename to server/src/http/relay/get_pending_requests.rs diff --git a/server/src/http/get_session_info.rs b/server/src/http/relay/get_session_info.rs similarity index 100% rename from server/src/http/get_session_info.rs rename to server/src/http/relay/get_session_info.rs diff --git a/server/src/http/get_sessions.rs b/server/src/http/relay/get_sessions.rs similarity index 100% rename from server/src/http/get_sessions.rs rename to server/src/http/relay/get_sessions.rs diff --git a/server/src/http/get_wallets_metadata.rs b/server/src/http/relay/get_wallets_metadata.rs similarity index 100% rename from server/src/http/get_wallets_metadata.rs rename to server/src/http/relay/get_wallets_metadata.rs diff --git a/server/src/http/relay/mod.rs b/server/src/http/relay/mod.rs new file mode 100644 index 00000000..6cef9d5f --- /dev/null +++ b/server/src/http/relay/mod.rs @@ -0,0 +1,8 @@ +pub mod connect_session; +pub mod drop_sessions; +pub mod get_pending_request; +pub mod get_pending_requests; +pub mod get_session_info; +pub mod get_sessions; +pub mod get_wallets_metadata; +pub mod resolve_request; diff --git a/server/src/http/resolve_request.rs b/server/src/http/relay/resolve_request.rs similarity index 100% rename from server/src/http/resolve_request.rs rename to server/src/http/relay/resolve_request.rs diff --git a/server/src/http/statistics/connect_session.rs b/server/src/http/statistics/connect_session.rs new file mode 100644 index 00000000..7e7c8c75 --- /dev/null +++ b/server/src/http/statistics/connect_session.rs @@ -0,0 +1,87 @@ +use crate::{ + errors::NightlyError, + state::{ClientToSessions, ModifySession, SessionToApp, SessionToAppMap, Sessions}, + structs::common::{Device, Notification}, +}; +use axum::{extract::State, http::StatusCode, Json}; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct HttpConnectSessionRequest { + pub client_id: String, + pub public_keys: Vec, + pub session_id: String, + #[ts(optional)] + pub notification: Option, + #[ts(optional)] + pub device: Option, + #[ts(optional)] + pub metadata: Option, +} +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +pub struct HttpConnectSessionResponse {} +pub async fn connect_session( + State(sessions): State, + State(client_to_sessions): State, + State(session_to_app_map): State, + Json(request): Json, +) -> Result, (StatusCode, String)> { + let app_id = match session_to_app_map.get_app_id(&request.session_id).await { + Some(app_id) => app_id, + None => { + return Err(( + StatusCode::BAD_REQUEST, + NightlyError::UnhandledInternalError.to_string(), + )) + } + }; + + let sessions_read = sessions.read().await; + let app_sessions_read = match sessions_read.get(&app_id) { + Some(session) => session.read().await, + None => { + return Err(( + StatusCode::BAD_REQUEST, + NightlyError::SessionDoesNotExist.to_string(), + )) + } + }; + + let mut session_write = match app_sessions_read.get(&request.session_id) { + Some(session) => session.write().await, + None => { + return Err(( + StatusCode::BAD_REQUEST, + NightlyError::SessionDoesNotExist.to_string(), + )) + } + }; + + // Insert user socket + session_write + .connect_user( + &request.device, + &request.public_keys, + &request.metadata, + &request.client_id, + &request.notification, + ) + .await + .map_err(|_| { + return ( + StatusCode::BAD_REQUEST, + NightlyError::AppDisconnected.to_string(), + ); + })?; + + // Insert new session id into client_to_sessions + client_to_sessions + .add_session(request.client_id.clone(), request.session_id.clone()) + .await; + + return Ok(Json(HttpConnectSessionResponse {})); +} diff --git a/server/src/http/statistics/mod.rs b/server/src/http/statistics/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/server/src/lib.rs b/server/src/lib.rs index f149a6eb..a1c27d01 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -1,7 +1,7 @@ pub mod errors; pub mod handle_error; pub mod http; -pub mod router; +pub mod routes; mod sesssion_cleaner; pub mod state; pub mod structs; diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs new file mode 100644 index 00000000..7d97a9b8 --- /dev/null +++ b/server/src/routes/mod.rs @@ -0,0 +1,2 @@ +pub mod router; +pub mod stats_router; diff --git a/server/src/router.rs b/server/src/routes/router.rs similarity index 96% rename from server/src/router.rs rename to server/src/routes/router.rs index ce62f986..33b127ca 100644 --- a/server/src/router.rs +++ b/server/src/routes/router.rs @@ -1,6 +1,6 @@ use crate::{ handle_error::handle_error, - http::{ + http::relay::{ connect_session::connect_session, drop_sessions::drop_sessions, get_pending_request::get_pending_request, get_pending_requests::get_pending_requests, get_session_info::get_session_info, get_sessions::get_sessions, @@ -20,6 +20,7 @@ use axum::{ routing::{get, post}, Router, }; +use database::db::Db; use std::{sync::Arc, time::Duration}; use tower::ServiceBuilder; use tower_http::trace::TraceLayer; @@ -32,6 +33,7 @@ pub async fn get_router() -> Router { client_to_sockets: Default::default(), wallets_metadata: Arc::new(get_wallets_metadata_vec()), session_to_app_map: Default::default(), + db: Arc::new(Db::connect_to_the_pool().await), }; // Start cleaning outdated sessions start_cleaning_sessions( diff --git a/server/src/routes/stats_router.rs b/server/src/routes/stats_router.rs new file mode 100644 index 00000000..b98c4bac --- /dev/null +++ b/server/src/routes/stats_router.rs @@ -0,0 +1,6 @@ +use crate::state::ServerState; +use axum::Router; + +pub fn stats_router(state: ServerState) -> Router { + Router::new().with_state(state) +} diff --git a/server/src/state.rs b/server/src/state.rs index a4e674b0..88b7d5e6 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -8,6 +8,7 @@ use axum::extract::{ ws::{Message, WebSocket}, FromRef, }; +use database::db::Db; use futures::{stream::SplitSink, SinkExt}; use log::info; use std::{ @@ -30,6 +31,7 @@ pub struct ServerState { pub client_to_sessions: ClientToSessions, pub wallets_metadata: Arc>, pub session_to_app_map: SessionToAppMap, + pub db: Arc, } #[async_trait] From aabaf182e9ebe5e6a69c186c484bb195488f57af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Tue, 5 Mar 2024 10:46:37 +0100 Subject: [PATCH 2/7] rename struct in db --- .../requests_stats.rs | 4 +- database/src/structs/subscription.rs | 3 +- database/src/tables/registered_app/select.rs | 6 +- .../src/tables/registered_app/table_struct.rs | 6 +- database/src/tables/registered_app/update.rs | 6 +- database/src/tables/team/select.rs | 21 ++++- database/src/tables/team/update.rs | 12 +-- database/src/tables/test_utils.rs | 7 +- server/src/http/statistics/connect_session.rs | 87 ------------------- server/src/http/statistics/mod.rs | 1 + 10 files changed, 44 insertions(+), 109 deletions(-) delete mode 100644 server/src/http/statistics/connect_session.rs diff --git a/database/src/aggregated_views_queries/requests_stats.rs b/database/src/aggregated_views_queries/requests_stats.rs index 5a8c5eff..8ea2b867 100644 --- a/database/src/aggregated_views_queries/requests_stats.rs +++ b/database/src/aggregated_views_queries/requests_stats.rs @@ -60,7 +60,7 @@ mod test { consts::DAY_IN_SECONDS, request_status::RequestStatus, session_type::SessionType, }, tables::{ - registered_app::table_struct::RegisteredApp, requests::table_struct::Request, + registered_app::table_struct::DbRegisteredApp, requests::table_struct::Request, sessions::table_struct::DbNcSession, }, }; @@ -311,7 +311,7 @@ mod test { // Test missing success due to all requests having pending status // Add new app to have a "clean" state let second_app_id = "test_app_id2".to_string(); - let app = RegisteredApp { + let app = DbRegisteredApp { team_id: team_id.clone(), app_id: second_app_id.to_string(), app_name: "test_app_name".to_string(), diff --git a/database/src/structs/subscription.rs b/database/src/structs/subscription.rs index 5bbf4d56..a8ee913d 100644 --- a/database/src/structs/subscription.rs +++ b/database/src/structs/subscription.rs @@ -1,6 +1,7 @@ +use serde::{Deserialize, Serialize}; use sqlx::Type; -#[derive(Clone, Debug, Eq, PartialEq, Type)] +#[derive(Clone, Debug, Eq, PartialEq, Type, Serialize, Deserialize)] #[sqlx(type_name = "subscription")] pub struct Subscription { pub subscription_type: String, diff --git a/database/src/tables/registered_app/select.rs b/database/src/tables/registered_app/select.rs index 4006c045..e3b76859 100644 --- a/database/src/tables/registered_app/select.rs +++ b/database/src/tables/registered_app/select.rs @@ -1,4 +1,4 @@ -use super::table_struct::{RegisteredApp, REGISTERED_APPS_TABLE_NAME}; +use super::table_struct::{DbRegisteredApp, REGISTERED_APPS_TABLE_NAME}; use crate::tables::requests::table_struct::REQUESTS_TABLE_NAME; use crate::{db::Db, tables::requests::table_struct::Request}; use sqlx::query_as; @@ -7,9 +7,9 @@ impl Db { pub async fn get_registered_app_by_app_id( &self, app_id: &String, - ) -> Result { + ) -> Result { let query = format!("SELECT * FROM {REGISTERED_APPS_TABLE_NAME} WHERE app_id = $1"); - let typed_query = query_as::<_, RegisteredApp>(&query); + let typed_query = query_as::<_, DbRegisteredApp>(&query); return typed_query .bind(&app_id) diff --git a/database/src/tables/registered_app/table_struct.rs b/database/src/tables/registered_app/table_struct.rs index 1942f9cd..ea8aaa79 100644 --- a/database/src/tables/registered_app/table_struct.rs +++ b/database/src/tables/registered_app/table_struct.rs @@ -5,7 +5,7 @@ pub const REGISTERED_APPS_TABLE_NAME: &str = "registered_apps"; pub const REGISTERED_APPS_KEYS: &str = "team_id, app_id, app_name, whitelisted_domains, ack_public_keys, email, registration_timestamp, pass_hash"; #[derive(Clone, Debug, Eq, PartialEq)] -pub struct RegisteredApp { +pub struct DbRegisteredApp { pub team_id: String, pub app_id: String, pub app_name: String, @@ -17,10 +17,10 @@ pub struct RegisteredApp { pub pass_hash: Option, } -impl FromRow<'_, PgRow> for RegisteredApp { +impl FromRow<'_, PgRow> for DbRegisteredApp { fn from_row(row: &sqlx::postgres::PgRow) -> std::result::Result { let registration_timestamp: i64 = row.get("registration_timestamp"); - Ok(RegisteredApp { + Ok(DbRegisteredApp { team_id: row.get("team_id"), app_id: row.get("app_id"), app_name: row.get("app_name"), diff --git a/database/src/tables/registered_app/update.rs b/database/src/tables/registered_app/update.rs index 47987aea..4a301b95 100644 --- a/database/src/tables/registered_app/update.rs +++ b/database/src/tables/registered_app/update.rs @@ -1,9 +1,9 @@ -use super::table_struct::{RegisteredApp, REGISTERED_APPS_KEYS, REGISTERED_APPS_TABLE_NAME}; +use super::table_struct::{DbRegisteredApp, REGISTERED_APPS_KEYS, REGISTERED_APPS_TABLE_NAME}; use crate::db::Db; use sqlx::{query, Transaction}; impl Db { - pub async fn register_new_app(&self, app: &RegisteredApp) -> Result<(), sqlx::Error> { + pub async fn register_new_app(&self, app: &DbRegisteredApp) -> Result<(), sqlx::Error> { let query_body = format!( "INSERT INTO {REGISTERED_APPS_TABLE_NAME} ({REGISTERED_APPS_KEYS}) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)" ); @@ -29,7 +29,7 @@ impl Db { pub async fn register_new_app_within_tx( &self, tx: &mut Transaction<'_, sqlx::Postgres>, - app: &RegisteredApp, + app: &DbRegisteredApp, ) -> Result<(), sqlx::Error> { let query_body = format!( "INSERT INTO {REGISTERED_APPS_TABLE_NAME} ({}) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", diff --git a/database/src/tables/team/select.rs b/database/src/tables/team/select.rs index f663447a..4dc7ba67 100644 --- a/database/src/tables/team/select.rs +++ b/database/src/tables/team/select.rs @@ -1,6 +1,7 @@ use super::table_struct::Team; -use crate::db::Db; +use crate::tables::registered_app::table_struct::REGISTERED_APPS_TABLE_NAME; use crate::tables::team::table_struct::TEAM_TABLE_NAME; +use crate::{db::Db, tables::registered_app::table_struct::DbRegisteredApp}; use sqlx::{query_as, Transaction}; impl Db { @@ -22,4 +23,22 @@ impl Db { } } } + + pub async fn get_registered_apps_by_team_id( + &self, + team_id: &String, + ) -> Result, sqlx::Error> { + let query = format!( + "SELECT r.* FROM {REGISTERED_APPS_TABLE_NAME} r + INNER JOIN team t ON r.team_id = t.team_id + WHERE t.team_id = $1 + ORDER BY t.registration_timestamp DESC" + ); + let typed_query = query_as::<_, DbRegisteredApp>(&query); + + return typed_query + .bind(&team_id) + .fetch_all(&self.connection_pool) + .await; + } } diff --git a/database/src/tables/team/update.rs b/database/src/tables/team/update.rs index 18014057..dca4846f 100644 --- a/database/src/tables/team/update.rs +++ b/database/src/tables/team/update.rs @@ -3,7 +3,7 @@ use crate::{ db::Db, structs::subscription::Subscription, tables::{ - registered_app::table_struct::RegisteredApp, + registered_app::table_struct::DbRegisteredApp, team::table_struct::{Team, TEAM_TABLE_NAME}, user_app_privileges::table_struct::UserAppPrivilege, }, @@ -55,7 +55,7 @@ impl Db { pub async fn setup_team( &self, team: &Team, - app: &RegisteredApp, + app: &DbRegisteredApp, admin: &UserAppPrivilege, ) -> Result<(), sqlx::Error> { // Start a transaction @@ -96,9 +96,9 @@ mod tests { use crate::{ structs::privelage_level::PrivilegeLevel, tables::{ - grafana_users::table_struct::GrafanaUser, registered_app::table_struct::RegisteredApp, - team::table_struct::Team, user_app_privileges::table_struct::UserAppPrivilege, - utils::to_microsecond_precision, + grafana_users::table_struct::GrafanaUser, + registered_app::table_struct::DbRegisteredApp, team::table_struct::Team, + user_app_privileges::table_struct::UserAppPrivilege, utils::to_microsecond_precision, }, }; use sqlx::types::chrono::Utc; @@ -126,7 +126,7 @@ mod tests { registration_timestamp: to_microsecond_precision(&Utc::now()), }; - let app = RegisteredApp { + let app = DbRegisteredApp { app_id: "test_app_id".to_string(), team_id: "test_team_id".to_string(), app_name: "test_app_name".to_string(), diff --git a/database/src/tables/test_utils.rs b/database/src/tables/test_utils.rs index 28bacd89..a9efcd89 100644 --- a/database/src/tables/test_utils.rs +++ b/database/src/tables/test_utils.rs @@ -4,8 +4,9 @@ pub mod test_utils { db::Db, structs::privelage_level::PrivilegeLevel, tables::{ - grafana_users::table_struct::GrafanaUser, registered_app::table_struct::RegisteredApp, - team::table_struct::Team, user_app_privileges::table_struct::UserAppPrivilege, + grafana_users::table_struct::GrafanaUser, + registered_app::table_struct::DbRegisteredApp, team::table_struct::Team, + user_app_privileges::table_struct::UserAppPrivilege, }, }; use sqlx::{ @@ -96,7 +97,7 @@ pub mod test_utils { registration_timestamp: registration_timestamp, }; - let registered_app = RegisteredApp { + let registered_app = DbRegisteredApp { team_id: team_id.clone(), app_id: app_id.clone(), app_name: "test_app".to_string(), diff --git a/server/src/http/statistics/connect_session.rs b/server/src/http/statistics/connect_session.rs deleted file mode 100644 index 7e7c8c75..00000000 --- a/server/src/http/statistics/connect_session.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::{ - errors::NightlyError, - state::{ClientToSessions, ModifySession, SessionToApp, SessionToAppMap, Sessions}, - structs::common::{Device, Notification}, -}; -use axum::{extract::State, http::StatusCode, Json}; -use serde::{Deserialize, Serialize}; -use ts_rs::TS; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] -#[ts(export)] -#[serde(rename_all = "camelCase")] -pub struct HttpConnectSessionRequest { - pub client_id: String, - pub public_keys: Vec, - pub session_id: String, - #[ts(optional)] - pub notification: Option, - #[ts(optional)] - pub device: Option, - #[ts(optional)] - pub metadata: Option, -} -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] -#[ts(export)] -pub struct HttpConnectSessionResponse {} -pub async fn connect_session( - State(sessions): State, - State(client_to_sessions): State, - State(session_to_app_map): State, - Json(request): Json, -) -> Result, (StatusCode, String)> { - let app_id = match session_to_app_map.get_app_id(&request.session_id).await { - Some(app_id) => app_id, - None => { - return Err(( - StatusCode::BAD_REQUEST, - NightlyError::UnhandledInternalError.to_string(), - )) - } - }; - - let sessions_read = sessions.read().await; - let app_sessions_read = match sessions_read.get(&app_id) { - Some(session) => session.read().await, - None => { - return Err(( - StatusCode::BAD_REQUEST, - NightlyError::SessionDoesNotExist.to_string(), - )) - } - }; - - let mut session_write = match app_sessions_read.get(&request.session_id) { - Some(session) => session.write().await, - None => { - return Err(( - StatusCode::BAD_REQUEST, - NightlyError::SessionDoesNotExist.to_string(), - )) - } - }; - - // Insert user socket - session_write - .connect_user( - &request.device, - &request.public_keys, - &request.metadata, - &request.client_id, - &request.notification, - ) - .await - .map_err(|_| { - return ( - StatusCode::BAD_REQUEST, - NightlyError::AppDisconnected.to_string(), - ); - })?; - - // Insert new session id into client_to_sessions - client_to_sessions - .add_session(request.client_id.clone(), request.session_id.clone()) - .await; - - return Ok(Json(HttpConnectSessionResponse {})); -} diff --git a/server/src/http/statistics/mod.rs b/server/src/http/statistics/mod.rs index e69de29b..ebc2d06c 100644 --- a/server/src/http/statistics/mod.rs +++ b/server/src/http/statistics/mod.rs @@ -0,0 +1 @@ +pub mod get_registered_apps; From 2d4888d42a012b8ac4e0acba41158498ec82ef5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Tue, 5 Mar 2024 10:59:34 +0100 Subject: [PATCH 3/7] get_registered_apps --- database/Cargo.toml | 1 + database/src/structs/subscription.rs | 4 +- .../http/statistics/get_registered_apps.rs | 48 +++++++++++++++++++ server/src/structs/mod.rs | 1 + .../structs/requests_structs_filters/mod.rs | 1 + .../registered_app.rs | 32 +++++++++++++ 6 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 server/src/http/statistics/get_registered_apps.rs create mode 100644 server/src/structs/requests_structs_filters/mod.rs create mode 100644 server/src/structs/requests_structs_filters/registered_app.rs diff --git a/database/Cargo.toml b/database/Cargo.toml index d0fb9afb..0ecc7f9f 100644 --- a/database/Cargo.toml +++ b/database/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] sqlx = { workspace = true } serde = { workspace = true } +ts-rs = { workspace = true } tokio = { workspace = true } dotenvy = { workspace = true } anyhow = { workspace = true } diff --git a/database/src/structs/subscription.rs b/database/src/structs/subscription.rs index a8ee913d..a5bb6593 100644 --- a/database/src/structs/subscription.rs +++ b/database/src/structs/subscription.rs @@ -1,7 +1,9 @@ use serde::{Deserialize, Serialize}; use sqlx::Type; +use ts_rs::TS; -#[derive(Clone, Debug, Eq, PartialEq, Type, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Type)] +#[ts(export)] #[sqlx(type_name = "subscription")] pub struct Subscription { pub subscription_type: String, diff --git a/server/src/http/statistics/get_registered_apps.rs b/server/src/http/statistics/get_registered_apps.rs new file mode 100644 index 00000000..8ef746d9 --- /dev/null +++ b/server/src/http/statistics/get_registered_apps.rs @@ -0,0 +1,48 @@ +use crate::structs::requests_structs_filters::registered_app::RegisteredApp; +use axum::{extract::State, http::StatusCode, Json}; +use database::db::Db; +use log::error; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct HttpGetDbRegisteredAppsRequest { + pub team_id: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +pub struct HttpGetDbRegisteredAppsResponse { + pub apps: Vec, +} + +pub async fn get_registered_apps( + State(db): State, + Json(request): Json, +) -> Result, (StatusCode, String)> { + let _ = db + .get_team_by_team_id(None, &request.team_id) + .await + .map_err(|e| { + error!("Failed to get team: {:?}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to get team".to_string(), + ) + })?; + + let apps = match db.get_registered_apps_by_team_id(&request.team_id).await { + Ok(apps) => apps.into_iter().map(|app| app.into()).collect(), + Err(e) => { + error!("Failed to get registered apps: {:?}", e); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to get registered apps".to_string(), + )); + } + }; + + return Ok(Json(HttpGetDbRegisteredAppsResponse { apps })); +} diff --git a/server/src/structs/mod.rs b/server/src/structs/mod.rs index e2247efe..1e54ece7 100644 --- a/server/src/structs/mod.rs +++ b/server/src/structs/mod.rs @@ -3,6 +3,7 @@ pub mod client_messages; pub mod common; pub mod http_endpoints; pub mod notification_msg; +pub mod requests_structs_filters; pub mod session; pub mod wallet_metadata; pub mod wallet_type; diff --git a/server/src/structs/requests_structs_filters/mod.rs b/server/src/structs/requests_structs_filters/mod.rs new file mode 100644 index 00000000..056653ba --- /dev/null +++ b/server/src/structs/requests_structs_filters/mod.rs @@ -0,0 +1 @@ +pub mod registered_app; diff --git a/server/src/structs/requests_structs_filters/registered_app.rs b/server/src/structs/requests_structs_filters/registered_app.rs new file mode 100644 index 00000000..b8f28c69 --- /dev/null +++ b/server/src/structs/requests_structs_filters/registered_app.rs @@ -0,0 +1,32 @@ +use database::{ + structs::subscription::Subscription, tables::registered_app::table_struct::DbRegisteredApp, +}; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct RegisteredApp { + pub app_id: String, + pub app_name: String, + pub whitelisted_domains: Vec, + pub subscription: Option, + pub ack_public_keys: Vec, + pub email: Option, + pub registration_timestamp: u64, +} + +impl From for RegisteredApp { + fn from(db_registered_app: DbRegisteredApp) -> Self { + RegisteredApp { + app_id: db_registered_app.app_id, + app_name: db_registered_app.app_name, + whitelisted_domains: db_registered_app.whitelisted_domains, + subscription: db_registered_app.subscription, + ack_public_keys: db_registered_app.ack_public_keys, + email: db_registered_app.email, + registration_timestamp: db_registered_app.registration_timestamp, + } + } +} From dd9310527df50d60db62136a1ac6f8a379d4be21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Tue, 5 Mar 2024 11:28:19 +0100 Subject: [PATCH 4/7] add register new user --- database/src/tables/grafana_users/select.rs | 10 ++++ database/src/tables/utils.rs | 4 ++ .../http/statistics/get_registered_apps.rs | 10 ++-- server/src/http/statistics/mod.rs | 1 + .../src/http/statistics/register_new_user.rs | 56 +++++++++++++++++++ 5 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 server/src/http/statistics/register_new_user.rs diff --git a/database/src/tables/grafana_users/select.rs b/database/src/tables/grafana_users/select.rs index 89a78bcd..9fa8d26f 100644 --- a/database/src/tables/grafana_users/select.rs +++ b/database/src/tables/grafana_users/select.rs @@ -13,4 +13,14 @@ impl Db { .fetch_one(&self.connection_pool) .await; } + + pub async fn get_user_by_email(&self, email: &String) -> Result { + let query = format!("SELECT * FROM {GRAFANA_USERS_TABLE_NAME} WHERE email = $1"); + let typed_query = query_as::<_, GrafanaUser>(&query); + + return typed_query + .bind(&email) + .fetch_one(&self.connection_pool) + .await; + } } diff --git a/database/src/tables/utils.rs b/database/src/tables/utils.rs index 65867d35..a9e90545 100644 --- a/database/src/tables/utils.rs +++ b/database/src/tables/utils.rs @@ -11,6 +11,10 @@ pub fn get_date_time(timestamp: u64) -> Option> { Utc.timestamp_millis_opt(timestamp as i64).single() } +pub fn get_current_datetime() -> DateTime { + Utc::now() +} + pub fn to_microsecond_precision(datetime: &DateTime) -> DateTime { // Should never fail as we are converting from a valid DateTime Utc.timestamp_micros(datetime.timestamp_micros()).unwrap() diff --git a/server/src/http/statistics/get_registered_apps.rs b/server/src/http/statistics/get_registered_apps.rs index 8ef746d9..0e45f2b5 100644 --- a/server/src/http/statistics/get_registered_apps.rs +++ b/server/src/http/statistics/get_registered_apps.rs @@ -8,20 +8,20 @@ use ts_rs::TS; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] #[ts(export)] #[serde(rename_all = "camelCase")] -pub struct HttpGetDbRegisteredAppsRequest { +pub struct HttpGetRegisteredAppsRequest { pub team_id: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] #[ts(export)] -pub struct HttpGetDbRegisteredAppsResponse { +pub struct HttpGetRegisteredAppsResponse { pub apps: Vec, } pub async fn get_registered_apps( State(db): State, - Json(request): Json, -) -> Result, (StatusCode, String)> { + Json(request): Json, +) -> Result, (StatusCode, String)> { let _ = db .get_team_by_team_id(None, &request.team_id) .await @@ -44,5 +44,5 @@ pub async fn get_registered_apps( } }; - return Ok(Json(HttpGetDbRegisteredAppsResponse { apps })); + return Ok(Json(HttpGetRegisteredAppsResponse { apps })); } diff --git a/server/src/http/statistics/mod.rs b/server/src/http/statistics/mod.rs index ebc2d06c..2c572ce5 100644 --- a/server/src/http/statistics/mod.rs +++ b/server/src/http/statistics/mod.rs @@ -1 +1,2 @@ pub mod get_registered_apps; +pub mod register_new_user; diff --git a/server/src/http/statistics/register_new_user.rs b/server/src/http/statistics/register_new_user.rs new file mode 100644 index 00000000..2751d989 --- /dev/null +++ b/server/src/http/statistics/register_new_user.rs @@ -0,0 +1,56 @@ +use axum::{extract::State, http::StatusCode, Json}; +use database::{ + db::Db, + tables::{grafana_users::table_struct::GrafanaUser, utils::get_current_datetime}, +}; +use log::error; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; +use uuid7::uuid7; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct HttpRegisterNewUserRequest { + pub email: String, + pub password: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +pub struct HttpRegisterNewUserResponse { + pub user_id: String, +} + +pub async fn register_new_user( + State(db): State, + Json(request): Json, +) -> Result, (StatusCode, String)> { + // Check if user already exists + if let Ok(_) = db.get_user_by_email(&request.email).await { + return Err(( + StatusCode::BAD_REQUEST, + "User with this email already exists".to_string(), + )); + } + + // Create new user + let user_id = uuid7().to_string(); + let grafana_user = GrafanaUser { + user_id: user_id.clone(), + email: request.email.clone(), + // TODO: hash password + password_hash: request.password.clone(), + creation_timestamp: get_current_datetime(), + }; + + if let Err(err) = db.add_new_user(&grafana_user).await { + error!("Failed to create user: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to create user".to_string(), + )); + } + + return Ok(Json(HttpRegisterNewUserResponse { user_id })); +} From 2d9f0b1f7ad9a7045c63f410aef21e5d8aa4511e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Tue, 5 Mar 2024 13:59:55 +0100 Subject: [PATCH 5/7] add register app method for endpoint --- Cargo.toml | 6 +- database/migrations/0004_registered_apps.sql | 2 +- .../requests_stats.rs | 2 +- database/src/tables/registered_app/select.rs | 13 ++ .../src/tables/registered_app/table_struct.rs | 11 +- database/src/tables/registered_app/update.rs | 4 +- .../src/tables/session_public_keys/update.rs | 2 +- database/src/tables/team/select.rs | 6 +- database/src/tables/team/update.rs | 4 +- database/src/tables/test_utils.rs | 2 +- server/Cargo.toml | 3 +- server/src/http/statistics/mod.rs | 1 + .../src/http/statistics/register_new_app.rs | 162 ++++++++++++++++++ .../registered_app.rs | 3 +- 14 files changed, 202 insertions(+), 19 deletions(-) create mode 100644 server/src/http/statistics/register_new_app.rs diff --git a/Cargo.toml b/Cargo.toml index cebd79bc..b9c64811 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ anyhow = "1.0.79" serde_json = "1.0.113" strum = { version = "0.26.1", features = ["derive"] } dotenvy = "0.15.7" -ts-rs = "6.2.1" +ts-rs = { version = "6.2.1", features = ["serde-compat", "chrono-impl"] } uuid7 = { version = "0.7.2" } log = "0.4.20" @@ -28,4 +28,6 @@ reqwest = "0.11.24" tokio = { version = "1.35.1", features = ["full"] } async-trait = "0.1.77" -sqlx = { version = "0.7.3", features = [ "runtime-tokio", "tls-rustls", "macros", "postgres", "chrono"] } \ No newline at end of file +# If you're updating sqlx, make sure that chrono version below is the same as the one in sqlx +sqlx = { version = "0.7.3", features = [ "runtime-tokio", "tls-rustls", "macros", "postgres", "chrono"] } +chrono = { version = "0.4.22", features = ["serde"] } diff --git a/database/migrations/0004_registered_apps.sql b/database/migrations/0004_registered_apps.sql index 3e74c890..d06a0121 100644 --- a/database/migrations/0004_registered_apps.sql +++ b/database/migrations/0004_registered_apps.sql @@ -1,7 +1,7 @@ CREATE TABLE registered_apps( team_id TEXT NOT NULL REFERENCES team(team_id) ON DELETE CASCADE, app_id TEXT NOT NULL UNIQUE, - app_name TEXT NOT NULL, + app_name TEXT NOT NULL UNIQUE, whitelisted_domains TEXT [] NOT NULL, ack_public_keys TEXT [] NOT NULL, email TEXT, diff --git a/database/src/aggregated_views_queries/requests_stats.rs b/database/src/aggregated_views_queries/requests_stats.rs index 8ea2b867..3cc495d5 100644 --- a/database/src/aggregated_views_queries/requests_stats.rs +++ b/database/src/aggregated_views_queries/requests_stats.rs @@ -319,7 +319,7 @@ mod test { subscription: None, ack_public_keys: vec!["test_key".to_string()], email: None, - registration_timestamp: 0, + registration_timestamp: Utc::now(), pass_hash: None, }; db_arc.register_new_app(&app).await.unwrap(); diff --git a/database/src/tables/registered_app/select.rs b/database/src/tables/registered_app/select.rs index e3b76859..079c8237 100644 --- a/database/src/tables/registered_app/select.rs +++ b/database/src/tables/registered_app/select.rs @@ -34,4 +34,17 @@ impl Db { .fetch_all(&self.connection_pool) .await; } + + pub async fn get_registered_app_by_app_name( + &self, + app_name: &String, + ) -> Result { + let query = format!("SELECT * FROM {REGISTERED_APPS_TABLE_NAME} WHERE app_name = $1"); + let typed_query = query_as::<_, DbRegisteredApp>(&query); + + return typed_query + .bind(&app_name) + .fetch_one(&self.connection_pool) + .await; + } } diff --git a/database/src/tables/registered_app/table_struct.rs b/database/src/tables/registered_app/table_struct.rs index ea8aaa79..5f9cfe7e 100644 --- a/database/src/tables/registered_app/table_struct.rs +++ b/database/src/tables/registered_app/table_struct.rs @@ -1,5 +1,9 @@ use crate::structs::subscription::Subscription; -use sqlx::{postgres::PgRow, FromRow, Row}; +use sqlx::{ + postgres::PgRow, + types::chrono::{DateTime, Utc}, + FromRow, Row, +}; pub const REGISTERED_APPS_TABLE_NAME: &str = "registered_apps"; pub const REGISTERED_APPS_KEYS: &str = "team_id, app_id, app_name, whitelisted_domains, ack_public_keys, email, registration_timestamp, pass_hash"; @@ -13,13 +17,12 @@ pub struct DbRegisteredApp { pub subscription: Option, pub ack_public_keys: Vec, pub email: Option, - pub registration_timestamp: u64, + pub registration_timestamp: DateTime, pub pass_hash: Option, } impl FromRow<'_, PgRow> for DbRegisteredApp { fn from_row(row: &sqlx::postgres::PgRow) -> std::result::Result { - let registration_timestamp: i64 = row.get("registration_timestamp"); Ok(DbRegisteredApp { team_id: row.get("team_id"), app_id: row.get("app_id"), @@ -29,7 +32,7 @@ impl FromRow<'_, PgRow> for DbRegisteredApp { subscription: None, ack_public_keys: row.get("ack_public_keys"), email: row.get("email"), - registration_timestamp: registration_timestamp as u64, + registration_timestamp: row.get("registration_timestamp"), pass_hash: row.get("pass_hash"), }) } diff --git a/database/src/tables/registered_app/update.rs b/database/src/tables/registered_app/update.rs index 4a301b95..af51a6f3 100644 --- a/database/src/tables/registered_app/update.rs +++ b/database/src/tables/registered_app/update.rs @@ -15,7 +15,7 @@ impl Db { .bind(&app.whitelisted_domains) .bind(&app.ack_public_keys) .bind(&app.email) - .bind(&(app.registration_timestamp as i64)) + .bind(&app.registration_timestamp) .bind(&app.pass_hash) .execute(&self.connection_pool) .await; @@ -43,7 +43,7 @@ impl Db { .bind(&app.whitelisted_domains) .bind(&app.ack_public_keys) .bind(&app.email) - .bind(&(app.registration_timestamp as i64)) + .bind(&app.registration_timestamp) .bind(&app.pass_hash) .execute(&mut **tx) .await; diff --git a/database/src/tables/session_public_keys/update.rs b/database/src/tables/session_public_keys/update.rs index 2c6c7c70..93de6c92 100644 --- a/database/src/tables/session_public_keys/update.rs +++ b/database/src/tables/session_public_keys/update.rs @@ -47,7 +47,7 @@ mod tests { // Create Public key let mut tx = db.connection_pool.begin().await.unwrap(); - let (client_profile_id, public_key) = db + let (client_profile_id, _public_key) = db .handle_public_keys_entries(&mut tx, &vec![public_key_str.clone()]) .await .unwrap(); diff --git a/database/src/tables/team/select.rs b/database/src/tables/team/select.rs index 4dc7ba67..00b94f04 100644 --- a/database/src/tables/team/select.rs +++ b/database/src/tables/team/select.rs @@ -9,16 +9,16 @@ impl Db { &self, tx: Option<&mut Transaction<'_, sqlx::Postgres>>, team_id: &String, - ) -> Result { + ) -> Result, sqlx::Error> { let query = format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_id = $1"); let typed_query = query_as::<_, Team>(&query); match tx { - Some(tx) => return typed_query.bind(&team_id).fetch_one(&mut **tx).await, + Some(tx) => return typed_query.bind(&team_id).fetch_optional(&mut **tx).await, None => { return typed_query .bind(&team_id) - .fetch_one(&self.connection_pool) + .fetch_optional(&self.connection_pool) .await } } diff --git a/database/src/tables/team/update.rs b/database/src/tables/team/update.rs index dca4846f..eb7ce6b7 100644 --- a/database/src/tables/team/update.rs +++ b/database/src/tables/team/update.rs @@ -134,7 +134,7 @@ mod tests { whitelisted_domains: vec!["test_whitelisted_domain".to_string()], email: None, pass_hash: None, - registration_timestamp: 0, + registration_timestamp: to_microsecond_precision(&Utc::now()), subscription: None, }; @@ -148,7 +148,7 @@ mod tests { db.setup_team(&team, &app, &admin_privilege).await.unwrap(); let team_result = db.get_team_by_team_id(None, &team.team_id).await.unwrap(); - assert_eq!(team_result, team); + assert_eq!(team_result, Some(team)); let admin_result = db.get_user_by_user_id(&admin.user_id).await.unwrap(); assert_eq!(admin_result, admin); diff --git a/database/src/tables/test_utils.rs b/database/src/tables/test_utils.rs index a9efcd89..314fd8e0 100644 --- a/database/src/tables/test_utils.rs +++ b/database/src/tables/test_utils.rs @@ -106,7 +106,7 @@ pub mod test_utils { ack_public_keys: vec!["key".to_string()], email: None, pass_hash: None, - registration_timestamp: registration_timestamp.timestamp() as u64, + registration_timestamp: registration_timestamp, }; let admin_privilege = UserAppPrivilege { diff --git a/server/Cargo.toml b/server/Cargo.toml index a981f471..9a383221 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -25,4 +25,5 @@ axum = { workspace = true } tower-http = { workspace = true } tracing-subscriber = { workspace = true } async-trait = { workspace = true } -once_cell = { workspace = true } \ No newline at end of file +once_cell = { workspace = true } +chrono = { workspace = true } \ No newline at end of file diff --git a/server/src/http/statistics/mod.rs b/server/src/http/statistics/mod.rs index 2c572ce5..37691a49 100644 --- a/server/src/http/statistics/mod.rs +++ b/server/src/http/statistics/mod.rs @@ -1,2 +1,3 @@ pub mod get_registered_apps; +pub mod register_new_app; pub mod register_new_user; diff --git a/server/src/http/statistics/register_new_app.rs b/server/src/http/statistics/register_new_app.rs new file mode 100644 index 00000000..a4928d6b --- /dev/null +++ b/server/src/http/statistics/register_new_app.rs @@ -0,0 +1,162 @@ +use axum::{extract::State, http::StatusCode, Json}; +use database::{ + db::Db, + structs::privelage_level::PrivilegeLevel, + tables::{ + registered_app::table_struct::DbRegisteredApp, team::table_struct::Team, + user_app_privileges::table_struct::UserAppPrivilege, utils::get_current_datetime, + }, +}; +use log::error; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; +use uuid7::uuid7; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct HttpRegisterNewAppRequest { + pub team_id: Option, + pub user_id: String, + pub app_name: String, + pub whitelisted_domains: Vec, + pub ack_public_keys: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +pub struct HttpRegisterNewAppResponse { + pub app_id: String, +} + +pub async fn register_new_app( + State(db): State, + Json(request): Json, +) -> Result, (StatusCode, String)> { + // Check if app is already registered + if let Ok(_) = db.get_registered_app_by_app_name(&request.app_name).await { + return Err(( + StatusCode::BAD_REQUEST, + "App with this name already exists".to_string(), + )); + } + + // Check if we are creating a new team or not + match request.team_id { + Some(team_id) => { + // Start a transaction + let mut tx = db.connection_pool.begin().await.unwrap(); + + let team = db.get_team_by_team_id(Some(&mut tx), &team_id).await; + + match team { + Ok(Some(team)) => { + // If team_id is provided check if user is a admin of the team + if team.team_admin_id != request.user_id { + return Err(( + StatusCode::BAD_REQUEST, + "User does not have permissions to create a new app for this team" + .to_string(), + )); + } + + // Register a new app + let app_id = uuid7().to_string(); + let db_registered_app = + database::tables::registered_app::table_struct::DbRegisteredApp { + app_id: app_id.clone(), + team_id: team_id.clone(), + app_name: request.app_name.clone(), + ack_public_keys: request.ack_public_keys.clone(), + whitelisted_domains: request.whitelisted_domains.clone(), + email: None, + pass_hash: None, + registration_timestamp: get_current_datetime(), + subscription: None, + }; + + if let Err(err) = db + .register_new_app_within_tx(&mut tx, &db_registered_app) + .await + { + tx.rollback().await.unwrap(); + error!("Failed to create app: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to create app".to_string(), + )); + } + + tx.commit().await.unwrap(); + return Ok(Json(HttpRegisterNewAppResponse { app_id })); + } + Ok(None) => { + return Err((StatusCode::BAD_REQUEST, "Team does not exist".to_string())); + } + Err(err) => { + tx.rollback().await.unwrap(); + error!("Failed to create app: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to create app".to_string(), + )); + } + } + } + None => { + // Check if user is already a admin of a team + if let Ok(privileges) = db.get_privileges_by_user_id(&request.user_id).await { + let is_already_admin = privileges.iter().any(|privilege| { + if let PrivilegeLevel::Admin = privilege.privilege_level { + return true; + } + return false; + }); + + if is_already_admin { + return Err(( + StatusCode::BAD_REQUEST, + "User is already an admin of a team".to_string(), + )); + } + } + + // Register a new app under a new team + let team_id = uuid7().to_string(); + let app_id = uuid7().to_string(); + let time = get_current_datetime(); + + let team = Team { + team_id: team_id.clone(), + subscription: None, + team_admin_id: request.user_id.clone(), + registration_timestamp: time.clone(), + }; + + let db_registered_app = DbRegisteredApp { + app_id: app_id.clone(), + team_id: team_id.clone(), + app_name: request.app_name.clone(), + ack_public_keys: request.ack_public_keys.clone(), + whitelisted_domains: request.whitelisted_domains.clone(), + email: None, + pass_hash: None, + registration_timestamp: time.clone(), + subscription: None, + }; + + let user_app_privilege = UserAppPrivilege { + app_id: app_id.clone(), + creation_timestamp: time, + privilege_level: PrivilegeLevel::Admin, + user_id: request.user_id.clone(), + }; + + db.setup_team(&team, &db_registered_app, &user_app_privilege) + .await + .unwrap(); + + return Ok(Json(HttpRegisterNewAppResponse { app_id })); + } + } +} diff --git a/server/src/structs/requests_structs_filters/registered_app.rs b/server/src/structs/requests_structs_filters/registered_app.rs index b8f28c69..d5a178cc 100644 --- a/server/src/structs/requests_structs_filters/registered_app.rs +++ b/server/src/structs/requests_structs_filters/registered_app.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use database::{ structs::subscription::Subscription, tables::registered_app::table_struct::DbRegisteredApp, }; @@ -14,7 +15,7 @@ pub struct RegisteredApp { pub subscription: Option, pub ack_public_keys: Vec, pub email: Option, - pub registration_timestamp: u64, + pub registration_timestamp: DateTime, } impl From for RegisteredApp { From acc775a506d6ec4953bf95c1ab8cb60a6df3d44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Tue, 5 Mar 2024 14:38:32 +0100 Subject: [PATCH 6/7] add nested router --- .../http/statistics/get_registered_apps.rs | 4 ++- .../src/http/statistics/register_new_app.rs | 4 ++- .../src/http/statistics/register_new_user.rs | 4 ++- server/src/routes/router.rs | 3 ++ server/src/routes/stats_router.rs | 29 +++++++++++++++++-- server/src/structs/mod.rs | 1 + server/src/structs/stats_http_endpoints.rs | 23 +++++++++++++++ 7 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 server/src/structs/stats_http_endpoints.rs diff --git a/server/src/http/statistics/get_registered_apps.rs b/server/src/http/statistics/get_registered_apps.rs index 0e45f2b5..1b12dc1c 100644 --- a/server/src/http/statistics/get_registered_apps.rs +++ b/server/src/http/statistics/get_registered_apps.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use crate::structs::requests_structs_filters::registered_app::RegisteredApp; use axum::{extract::State, http::StatusCode, Json}; use database::db::Db; @@ -19,7 +21,7 @@ pub struct HttpGetRegisteredAppsResponse { } pub async fn get_registered_apps( - State(db): State, + State(db): State>, Json(request): Json, ) -> Result, (StatusCode, String)> { let _ = db diff --git a/server/src/http/statistics/register_new_app.rs b/server/src/http/statistics/register_new_app.rs index a4928d6b..66557e83 100644 --- a/server/src/http/statistics/register_new_app.rs +++ b/server/src/http/statistics/register_new_app.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use axum::{extract::State, http::StatusCode, Json}; use database::{ db::Db, @@ -30,7 +32,7 @@ pub struct HttpRegisterNewAppResponse { } pub async fn register_new_app( - State(db): State, + State(db): State>, Json(request): Json, ) -> Result, (StatusCode, String)> { // Check if app is already registered diff --git a/server/src/http/statistics/register_new_user.rs b/server/src/http/statistics/register_new_user.rs index 2751d989..d0d70b59 100644 --- a/server/src/http/statistics/register_new_user.rs +++ b/server/src/http/statistics/register_new_user.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use axum::{extract::State, http::StatusCode, Json}; use database::{ db::Db, @@ -23,7 +25,7 @@ pub struct HttpRegisterNewUserResponse { } pub async fn register_new_user( - State(db): State, + State(db): State>, Json(request): Json, ) -> Result, (StatusCode, String)> { // Check if user already exists diff --git a/server/src/routes/router.rs b/server/src/routes/router.rs index 33b127ca..f692c11d 100644 --- a/server/src/routes/router.rs +++ b/server/src/routes/router.rs @@ -26,6 +26,8 @@ use tower::ServiceBuilder; use tower_http::trace::TraceLayer; use tracing_subscriber::EnvFilter; +use super::stats_router::stats_router; + pub async fn get_router() -> Router { let state = ServerState { sessions: Default::default(), @@ -78,6 +80,7 @@ pub async fn get_router() -> Router { &HttpEndpoint::GetWalletsMetadata.to_string(), get(get_wallets_metadata), ) + .nest("/stats", stats_router(state.clone())) .with_state(state) .layer(TraceLayer::new_for_http()) .layer( diff --git a/server/src/routes/stats_router.rs b/server/src/routes/stats_router.rs index b98c4bac..3dc17173 100644 --- a/server/src/routes/stats_router.rs +++ b/server/src/routes/stats_router.rs @@ -1,6 +1,29 @@ -use crate::state::ServerState; -use axum::Router; +use crate::{ + http::statistics::{ + get_registered_apps::get_registered_apps, register_new_app::register_new_app, + register_new_user::register_new_user, + }, + state::ServerState, + structs::stats_http_endpoints::HttpStatsEndpoint, +}; +use axum::{ + routing::{get, post}, + Router, +}; pub fn stats_router(state: ServerState) -> Router { - Router::new().with_state(state) + Router::new() + .route( + &HttpStatsEndpoint::RegisterNewApp.to_string(), + post(register_new_app), + ) + .route( + &HttpStatsEndpoint::RegisterNewUser.to_string(), + post(register_new_user), + ) + .route( + &HttpStatsEndpoint::GetRegisteredApps.to_string(), + get(get_registered_apps), + ) + .with_state(state) } diff --git a/server/src/structs/mod.rs b/server/src/structs/mod.rs index 1e54ece7..ea135a44 100644 --- a/server/src/structs/mod.rs +++ b/server/src/structs/mod.rs @@ -5,6 +5,7 @@ pub mod http_endpoints; pub mod notification_msg; pub mod requests_structs_filters; pub mod session; +pub mod stats_http_endpoints; pub mod wallet_metadata; pub mod wallet_type; pub mod wallets; diff --git a/server/src/structs/stats_http_endpoints.rs b/server/src/structs/stats_http_endpoints.rs new file mode 100644 index 00000000..d9678638 --- /dev/null +++ b/server/src/structs/stats_http_endpoints.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +pub enum HttpStatsEndpoint { + #[serde(rename = "/get_registered_apps")] + GetRegisteredApps, + #[serde(rename = "/register_new_app")] + RegisterNewApp, + #[serde(rename = "/register_new_user")] + RegisterNewUser, +} + +impl HttpStatsEndpoint { + pub fn to_string(&self) -> String { + match self { + HttpStatsEndpoint::GetRegisteredApps => "/get_registered_apps".to_string(), + HttpStatsEndpoint::RegisterNewApp => "/register_new_app".to_string(), + HttpStatsEndpoint::RegisterNewUser => "/register_new_user".to_string(), + } + } +} From fa4094d5b47fd15c2c8344ab626189fe4311df0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Wed, 6 Mar 2024 08:33:10 +0100 Subject: [PATCH 7/7] fix type --- database/bindings/Subscription.ts | 3 +++ database/migrations/0004_registered_apps.sql | 2 +- .../src/aggregated_views_queries/requests_stats.rs | 12 ++++++------ 3 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 database/bindings/Subscription.ts diff --git a/database/bindings/Subscription.ts b/database/bindings/Subscription.ts new file mode 100644 index 00000000..92218529 --- /dev/null +++ b/database/bindings/Subscription.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface Subscription { subscription_type: string, valid_from: bigint, valid_till: bigint, } \ No newline at end of file diff --git a/database/migrations/0004_registered_apps.sql b/database/migrations/0004_registered_apps.sql index d06a0121..1d841a0e 100644 --- a/database/migrations/0004_registered_apps.sql +++ b/database/migrations/0004_registered_apps.sql @@ -5,7 +5,7 @@ CREATE TABLE registered_apps( whitelisted_domains TEXT [] NOT NULL, ack_public_keys TEXT [] NOT NULL, email TEXT, - registration_timestamp BIGINT NOT NULL, + registration_timestamp TIMESTAMPTZ NOT NULL, pass_hash TEXT ); diff --git a/database/src/aggregated_views_queries/requests_stats.rs b/database/src/aggregated_views_queries/requests_stats.rs index 3cc495d5..183c1c80 100644 --- a/database/src/aggregated_views_queries/requests_stats.rs +++ b/database/src/aggregated_views_queries/requests_stats.rs @@ -61,7 +61,7 @@ mod test { }, tables::{ registered_app::table_struct::DbRegisteredApp, requests::table_struct::Request, - sessions::table_struct::DbNcSession, + sessions::table_struct::DbNcSession, utils::to_microsecond_precision, }, }; use sqlx::types::chrono::{DateTime, Utc}; @@ -199,7 +199,7 @@ mod test { network: "test_network".to_string(), client_profile_id: None, client: None, - session_open_timestamp: DateTime::from(Utc::now()), + session_open_timestamp: to_microsecond_precision(&Utc::now()), session_close_timestamp: None, }; @@ -236,7 +236,7 @@ mod test { app_id: app_id.to_string(), session_id: "test_session_id".to_string(), network: "test_network".to_string(), - creation_timestamp: creation_time, + creation_timestamp: to_microsecond_precision(&creation_time), request_status: status, request_type: "test_request_type".to_string(), }; @@ -319,7 +319,7 @@ mod test { subscription: None, ack_public_keys: vec!["test_key".to_string()], email: None, - registration_timestamp: Utc::now(), + registration_timestamp: to_microsecond_precision(&Utc::now()), pass_hash: None, }; db_arc.register_new_app(&app).await.unwrap(); @@ -341,7 +341,7 @@ mod test { network: "test_network".to_string(), client_profile_id: None, client: None, - session_open_timestamp: DateTime::from(Utc::now()), + session_open_timestamp: to_microsecond_precision(&Utc::now()), session_close_timestamp: None, }; @@ -365,7 +365,7 @@ mod test { app_id: app_id.to_string(), session_id: "test_session_id".to_string(), network: "test_network".to_string(), - creation_timestamp: creation_time, + creation_timestamp: to_microsecond_precision(&creation_time), request_status: RequestStatus::Pending, request_type: "test_request_type".to_string(), };