From cd3d4ffc9f7ab15b803962069ada7b9d69e64949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 20 Sep 2023 13:20:19 +0200 Subject: [PATCH 1/5] Remove unused test-and-earn from nym-connect --- nym-connect/desktop/src-tauri/src/error.rs | 5 - nym-connect/desktop/src-tauri/src/main.rs | 7 - .../src-tauri/src/operations/export.rs | 1 + .../src/operations/growth/api_client.rs | 270 ------------------ .../src-tauri/src/operations/growth/assets.rs | 57 ---- .../src-tauri/src/operations/growth/mod.rs | 3 - .../src/operations/growth/test_and_earn.rs | 156 ---------- .../desktop/src-tauri/src/operations/mod.rs | 1 - 8 files changed, 1 insertion(+), 499 deletions(-) delete mode 100644 nym-connect/desktop/src-tauri/src/operations/growth/api_client.rs delete mode 100644 nym-connect/desktop/src-tauri/src/operations/growth/assets.rs delete mode 100644 nym-connect/desktop/src-tauri/src/operations/growth/mod.rs delete mode 100644 nym-connect/desktop/src-tauri/src/operations/growth/test_and_earn.rs diff --git a/nym-connect/desktop/src-tauri/src/error.rs b/nym-connect/desktop/src-tauri/src/error.rs index 5b324a8850..da7dbd65c3 100644 --- a/nym-connect/desktop/src-tauri/src/error.rs +++ b/nym-connect/desktop/src-tauri/src/error.rs @@ -41,11 +41,6 @@ pub enum BackendError { source: ClientCoreError, }, #[error("{source}")] - ApiClientError { - #[from] - source: crate::operations::growth::api_client::ApiClientError, - }, - #[error("{source}")] EnvError { #[from] source: std::env::VarError, diff --git a/nym-connect/desktop/src-tauri/src/main.rs b/nym-connect/desktop/src-tauri/src/main.rs index 684c641446..d9a6040466 100644 --- a/nym-connect/desktop/src-tauri/src/main.rs +++ b/nym-connect/desktop/src-tauri/src/main.rs @@ -91,13 +91,6 @@ fn main() { crate::operations::directory::services::get_services, crate::operations::export::export_keys, crate::operations::window::hide_window, - crate::operations::growth::test_and_earn::growth_tne_get_client_id, - crate::operations::growth::test_and_earn::growth_tne_take_part, - crate::operations::growth::test_and_earn::growth_tne_get_draws, - crate::operations::growth::test_and_earn::growth_tne_ping, - crate::operations::growth::test_and_earn::growth_tne_submit_wallet_address, - crate::operations::growth::test_and_earn::growth_tne_enter_draw, - crate::operations::growth::test_and_earn::growth_tne_toggle_window, crate::operations::help::log::help_log_toggle_window, ]) .on_menu_event(|event| { diff --git a/nym-connect/desktop/src-tauri/src/operations/export.rs b/nym-connect/desktop/src-tauri/src/operations/export.rs index 3b11891e9c..10a9ef73e3 100644 --- a/nym-connect/desktop/src-tauri/src/operations/export.rs +++ b/nym-connect/desktop/src-tauri/src/operations/export.rs @@ -10,6 +10,7 @@ use nym_client_core::client::key_manager::persistence::OnDiskKeys; use nym_client_core::client::key_manager::KeyManager; use nym_crypto::asymmetric::identity; +#[allow(unused)] pub async fn get_identity_key( state: &tauri::State<'_, Arc>>, ) -> Result> { diff --git a/nym-connect/desktop/src-tauri/src/operations/growth/api_client.rs b/nym-connect/desktop/src-tauri/src/operations/growth/api_client.rs deleted file mode 100644 index c069cb6a9d..0000000000 --- a/nym-connect/desktop/src-tauri/src/operations/growth/api_client.rs +++ /dev/null @@ -1,270 +0,0 @@ -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -#[allow(unused)] -#[derive(Error, Debug)] -pub enum ApiClientError { - #[error("{source}")] - Reqwest { - #[from] - source: reqwest::Error, - }, - #[error("{source}")] - SerdeJson { - #[from] - source: serde_json::Error, - }, - #[error("{0}")] - Status(String), -} - -const API_BASE_URL: &str = "https://growth-api.nymtech.net"; - -// For development mode, switch to this -// const API_BASE_URL: &str = "http://localhost:8000"; - -#[derive(Debug, Clone)] -pub struct GrowthApiClient { - base_url: String, -} - -impl GrowthApiClient { - pub fn new(resource_base: &str) -> Self { - let base_url = std::env::var("API_BASE_URL").unwrap_or_else(|_| API_BASE_URL.to_string()); - GrowthApiClient { - base_url: format!("{base_url}{resource_base}"), - } - } - - pub fn registrations() -> Registrations { - Registrations::new(GrowthApiClient::new("/v1/tne")) - } - - pub fn daily_draws() -> DailyDraws { - DailyDraws::new(GrowthApiClient::new("/v1/tne/daily_draw")) - } - - pub(crate) async fn get(&self, url: &str) -> Result { - log::info!(">>> GET {}", url); - let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?; - let client = reqwest::Client::builder() - .proxy(proxy) - .timeout(std::time::Duration::from_secs(10)) - .build()?; - - match client.get(format!("{}{}", self.base_url, url)).send().await { - Ok(res) => { - if res.status().is_client_error() || res.status().is_server_error() { - log::error!("<<< {}", res.status()); - return Err(ApiClientError::Status(res.status().to_string())); - } - match res.text().await { - Ok(response_body) => { - log::info!("<<< {}", response_body); - match serde_json::from_str(&response_body) { - Ok(res) => Ok(res), - Err(e) => { - log::error!("<<< JSON parsing error: {}", e); - Err(e.into()) - } - } - } - Err(e) => { - log::error!("<<< Request error: {}", e); - Err(e.into()) - } - } - } - Err(e) => { - log::error!("<<< Response parsing error: {}", e); - Err(e.into()) - } - } - } - - // TODO: use the method in `operations::http` instead - pub(crate) async fn post( - &self, - url: &str, - body: &REQ, - ) -> Result { - log::info!(">>> POST {}", url); - let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?; - let client = reqwest::Client::builder() - .proxy(proxy) - .timeout(std::time::Duration::from_secs(10)) - .build()?; - - match client - .post(format!("{}{}", self.base_url, url)) - .json(body) - .send() - .await - { - Ok(res) => { - if res.status().is_client_error() || res.status().is_server_error() { - log::error!("<<< {}", res.status()); - return Err(ApiClientError::Status(res.status().to_string())); - } - match res.text().await { - Ok(response_body) => { - log::info!("<<< {}", response_body); - match serde_json::from_str(&response_body) { - Ok(res) => Ok(res), - Err(e) => { - log::error!("<<< JSON parsing error: {}", e); - Err(e.into()) - } - } - } - Err(e) => { - log::error!("<<< Request error: {}", e); - Err(e.into()) - } - } - } - Err(e) => { - log::error!("<<< Response parsing error: {}", e); - Err(e.into()) - } - } - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct ClientIdPartial { - pub client_id: String, - pub client_id_signature: String, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Registration { - pub id: String, - pub client_id: String, - pub timestamp: String, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Ping { - pub client_id: String, - pub timestamp: String, -} - -pub struct Registrations { - client: GrowthApiClient, -} - -impl Registrations { - pub fn new(client: GrowthApiClient) -> Self { - Registrations { client } - } - - pub async fn register( - &self, - registration: &ClientIdPartial, - ) -> Result { - self.client.post("/register", ®istration).await - } - - #[allow(dead_code)] - pub async fn unregister(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> { - self.client.post("/unregister", ®istration).await - } - - #[allow(dead_code)] - pub async fn status(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> { - self.client.post("/status", ®istration).await - } - - pub async fn ping(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> { - self.client.post("/ping", ®istration).await - } - - #[allow(dead_code)] - pub async fn health(&self) -> Result<(), ApiClientError> { - self.client.get("/health").await - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct DrawEntryPartial { - pub draw_id: String, - pub client_id: String, - pub client_id_signature: String, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct DrawEntry { - pub id: String, - pub draw_id: String, - pub timestamp: String, - pub status: Option, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct DrawWithWordOfTheDay { - pub id: String, - pub start_utc: String, - pub end_utc: String, - pub word_of_the_day: Option, - pub last_modified: String, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct ClaimPartial { - pub draw_id: String, - pub registration_id: String, - pub client_id: String, - pub client_id_signature: String, - pub wallet_address: String, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Winner { - pub id: String, - pub client_id: String, - pub draw_id: String, - pub timestamp: String, - pub winner_reg_id: String, - pub winner_wallet_address: Option, - pub winner_claim_timestamp: Option, -} - -pub struct DailyDraws { - client: GrowthApiClient, -} - -impl DailyDraws { - pub fn new(client: GrowthApiClient) -> Self { - DailyDraws { client } - } - - pub async fn current(&self) -> Result { - self.client.get("/current").await - } - - pub async fn next(&self) -> Result { - self.client.get("/next").await - } - - #[allow(dead_code)] - pub async fn status(&self, draw_id: &str) -> Result { - self.client.get(format!("/status/{draw_id}").as_str()).await - } - - pub async fn enter(&self, entry: &DrawEntryPartial) -> Result { - self.client.post("/enter", entry).await - } - - pub async fn entries( - &self, - client_id: &ClientIdPartial, - ) -> Result, ApiClientError> { - self.client.post("/entries", client_id).await - } - - pub async fn claim(&self, claim: &ClaimPartial) -> Result { - self.client.post("/claim", claim).await - } -} diff --git a/nym-connect/desktop/src-tauri/src/operations/growth/assets.rs b/nym-connect/desktop/src-tauri/src/operations/growth/assets.rs deleted file mode 100644 index 752b221916..0000000000 --- a/nym-connect/desktop/src-tauri/src/operations/growth/assets.rs +++ /dev/null @@ -1,57 +0,0 @@ -use rust_embed::RustEmbed; -extern crate yaml_rust; -use yaml_rust::YamlLoader; - -#[derive(RustEmbed)] -#[folder = "../src/components/Growth/content/"] -#[include = "*.yaml"] -#[exclude = "*.mdx"] -struct Asset; - -#[derive(Debug)] -pub struct NotificationContent { - pub title: String, - pub body: String, -} - -#[derive(Debug)] -pub struct Notifications { - pub you_are_in_draw: NotificationContent, - pub take_part: NotificationContent, -} - -pub struct Content {} - -const RESOURCE_ERROR: &str = "❌ RESOURCE ERROR"; - -fn get_as_string_or_error_message(value: &yaml_rust::Yaml) -> String { - value.as_str().unwrap_or(RESOURCE_ERROR).to_string() -} - -impl Content { - pub fn get_notifications() -> Notifications { - let content = Asset::get("en.yaml").unwrap(); - let s = std::str::from_utf8(content.data.as_ref()).unwrap(); - let content = YamlLoader::load_from_str(s).unwrap(); - let content = &content[0]; - - Notifications { - you_are_in_draw: NotificationContent { - title: get_as_string_or_error_message( - &content["testAndEarn"]["notifications"]["youAreInDraw"]["title"], - ), - body: get_as_string_or_error_message( - &content["testAndEarn"]["notifications"]["youAreInDraw"]["body"], - ), - }, - take_part: NotificationContent { - title: get_as_string_or_error_message( - &content["testAndEarn"]["notifications"]["takePart"]["title"], - ), - body: get_as_string_or_error_message( - &content["testAndEarn"]["notifications"]["takePart"]["body"], - ), - }, - } - } -} diff --git a/nym-connect/desktop/src-tauri/src/operations/growth/mod.rs b/nym-connect/desktop/src-tauri/src/operations/growth/mod.rs deleted file mode 100644 index 449c1bcee2..0000000000 --- a/nym-connect/desktop/src-tauri/src/operations/growth/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod api_client; -pub mod assets; -pub mod test_and_earn; diff --git a/nym-connect/desktop/src-tauri/src/operations/growth/test_and_earn.rs b/nym-connect/desktop/src-tauri/src/operations/growth/test_and_earn.rs deleted file mode 100644 index c2216ce245..0000000000 --- a/nym-connect/desktop/src-tauri/src/operations/growth/test_and_earn.rs +++ /dev/null @@ -1,156 +0,0 @@ -use crate::error::BackendError; -use crate::operations::export::get_identity_key; -use crate::operations::growth::api_client::{ - ClaimPartial, ClientIdPartial, DrawEntry, DrawEntryPartial, DrawWithWordOfTheDay, - GrowthApiClient, Registration, Winner, -}; -use crate::State; -use serde::{Deserialize, Serialize}; -use std::sync::Arc; -use tauri::api::notification::Notification; -use tauri::Manager; -use tokio::sync::RwLock; - -async fn get_client_id( - state: &tauri::State<'_, Arc>>, -) -> Result { - let keypair = get_identity_key(state).await?; - let client_id = keypair.public_key().to_base58_string(); - let client_id_signature = keypair - .private_key() - .sign(client_id.as_bytes()) - .to_base58_string(); - Ok(ClientIdPartial { - client_id, - client_id_signature, - }) -} - -#[tauri::command] -pub async fn growth_tne_get_client_id( - state: tauri::State<'_, Arc>>, -) -> Result { - get_client_id(&state).await -} - -#[tauri::command] -pub async fn growth_tne_take_part( - app_handle: tauri::AppHandle, - state: tauri::State<'_, Arc>>, -) -> Result { - let notifications = super::assets::Content::get_notifications(); - - let client_id = get_client_id(&state).await?; - let registration = GrowthApiClient::registrations() - .register(&client_id) - .await?; - - log::info!("<<< Test&Earn: registration details: {:?}", registration); - - if let Err(e) = Notification::new(&app_handle.config().tauri.bundle.identifier) - .title(notifications.take_part.title) - .body(notifications.take_part.body) - .show() - { - log::error!("Could not show notification. Error = {:?}", e); - } - - Ok(registration) -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Draws { - pub current: Option, - pub next: Option, - pub draws: Vec, -} - -#[tauri::command] -pub async fn growth_tne_get_draws(client_details: ClientIdPartial) -> Result { - let draws_api = GrowthApiClient::daily_draws(); - - let current = draws_api.current().await.ok(); - let next = draws_api.next().await.ok(); - let draws = draws_api.entries(&client_details).await?; - - Ok(Draws { - current, - next, - draws, - }) -} - -#[tauri::command] -pub async fn growth_tne_enter_draw( - client_details: ClientIdPartial, - draw_id: String, -) -> Result { - Ok(GrowthApiClient::daily_draws() - .enter(&DrawEntryPartial { - draw_id, - client_id: client_details.client_id, - client_id_signature: client_details.client_id_signature, - }) - .await?) -} - -#[tauri::command] -pub async fn growth_tne_submit_wallet_address( - client_details: ClientIdPartial, - draw_id: String, - wallet_address: String, - registration_id: String, -) -> Result { - Ok(GrowthApiClient::daily_draws() - .claim(&ClaimPartial { - draw_id, - client_id: client_details.client_id, - client_id_signature: client_details.client_id_signature, - wallet_address, - registration_id, - }) - .await?) -} - -#[tauri::command] -pub async fn growth_tne_ping(client_details: ClientIdPartial) -> Result<(), BackendError> { - log::info!("Test&Earn is sending a ping..."); - Ok(GrowthApiClient::registrations() - .ping(&client_details) - .await?) -} - -#[tauri::command] -pub async fn growth_tne_toggle_window( - app_handle: tauri::AppHandle, - window_title: Option, -) -> Result<(), BackendError> { - if let Some(window) = app_handle.windows().get("growth") { - log::info!("Closing growth window..."); - if let Err(e) = window.close() { - log::error!("Unable to close growth window: {:?}", e); - } - return Ok(()); - } - - log::info!("Creating growth window..."); - match tauri::WindowBuilder::new( - &app_handle, - "growth", - tauri::WindowUrl::App("growth.html".into()), - ) - .title(window_title.unwrap_or_else(|| "NymConnect Test&Earn".to_string())) - .build() - { - Ok(window) => { - if let Err(e) = window.set_focus() { - log::error!("Unable to focus growth window: {:?}", e); - } - Ok(()) - } - Err(e) => { - log::error!("Unable to create growth window: {:?}", e); - Err(BackendError::NewWindowError) - } - } -} diff --git a/nym-connect/desktop/src-tauri/src/operations/mod.rs b/nym-connect/desktop/src-tauri/src/operations/mod.rs index 977dcc5110..f20c82770d 100644 --- a/nym-connect/desktop/src-tauri/src/operations/mod.rs +++ b/nym-connect/desktop/src-tauri/src/operations/mod.rs @@ -3,7 +3,6 @@ pub mod config; pub mod connection; pub mod directory; pub mod export; -pub mod growth; pub mod help; pub mod http; pub mod window; From 1c2e3c754eef28d29232150558aa7ccbe96eaac5 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 21 Sep 2023 12:33:17 +0100 Subject: [PATCH 2/5] Revert "Remove unused test-and-earn from nym-connect" This reverts commit cd3d4ffc9f7ab15b803962069ada7b9d69e64949. --- nym-connect/desktop/src-tauri/src/error.rs | 5 + nym-connect/desktop/src-tauri/src/main.rs | 7 + .../src-tauri/src/operations/export.rs | 1 - .../src/operations/growth/api_client.rs | 270 ++++++++++++++++++ .../src-tauri/src/operations/growth/assets.rs | 57 ++++ .../src-tauri/src/operations/growth/mod.rs | 3 + .../src/operations/growth/test_and_earn.rs | 156 ++++++++++ .../desktop/src-tauri/src/operations/mod.rs | 1 + 8 files changed, 499 insertions(+), 1 deletion(-) create mode 100644 nym-connect/desktop/src-tauri/src/operations/growth/api_client.rs create mode 100644 nym-connect/desktop/src-tauri/src/operations/growth/assets.rs create mode 100644 nym-connect/desktop/src-tauri/src/operations/growth/mod.rs create mode 100644 nym-connect/desktop/src-tauri/src/operations/growth/test_and_earn.rs diff --git a/nym-connect/desktop/src-tauri/src/error.rs b/nym-connect/desktop/src-tauri/src/error.rs index da7dbd65c3..5b324a8850 100644 --- a/nym-connect/desktop/src-tauri/src/error.rs +++ b/nym-connect/desktop/src-tauri/src/error.rs @@ -41,6 +41,11 @@ pub enum BackendError { source: ClientCoreError, }, #[error("{source}")] + ApiClientError { + #[from] + source: crate::operations::growth::api_client::ApiClientError, + }, + #[error("{source}")] EnvError { #[from] source: std::env::VarError, diff --git a/nym-connect/desktop/src-tauri/src/main.rs b/nym-connect/desktop/src-tauri/src/main.rs index d9a6040466..684c641446 100644 --- a/nym-connect/desktop/src-tauri/src/main.rs +++ b/nym-connect/desktop/src-tauri/src/main.rs @@ -91,6 +91,13 @@ fn main() { crate::operations::directory::services::get_services, crate::operations::export::export_keys, crate::operations::window::hide_window, + crate::operations::growth::test_and_earn::growth_tne_get_client_id, + crate::operations::growth::test_and_earn::growth_tne_take_part, + crate::operations::growth::test_and_earn::growth_tne_get_draws, + crate::operations::growth::test_and_earn::growth_tne_ping, + crate::operations::growth::test_and_earn::growth_tne_submit_wallet_address, + crate::operations::growth::test_and_earn::growth_tne_enter_draw, + crate::operations::growth::test_and_earn::growth_tne_toggle_window, crate::operations::help::log::help_log_toggle_window, ]) .on_menu_event(|event| { diff --git a/nym-connect/desktop/src-tauri/src/operations/export.rs b/nym-connect/desktop/src-tauri/src/operations/export.rs index 10a9ef73e3..3b11891e9c 100644 --- a/nym-connect/desktop/src-tauri/src/operations/export.rs +++ b/nym-connect/desktop/src-tauri/src/operations/export.rs @@ -10,7 +10,6 @@ use nym_client_core::client::key_manager::persistence::OnDiskKeys; use nym_client_core::client::key_manager::KeyManager; use nym_crypto::asymmetric::identity; -#[allow(unused)] pub async fn get_identity_key( state: &tauri::State<'_, Arc>>, ) -> Result> { diff --git a/nym-connect/desktop/src-tauri/src/operations/growth/api_client.rs b/nym-connect/desktop/src-tauri/src/operations/growth/api_client.rs new file mode 100644 index 0000000000..c069cb6a9d --- /dev/null +++ b/nym-connect/desktop/src-tauri/src/operations/growth/api_client.rs @@ -0,0 +1,270 @@ +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[allow(unused)] +#[derive(Error, Debug)] +pub enum ApiClientError { + #[error("{source}")] + Reqwest { + #[from] + source: reqwest::Error, + }, + #[error("{source}")] + SerdeJson { + #[from] + source: serde_json::Error, + }, + #[error("{0}")] + Status(String), +} + +const API_BASE_URL: &str = "https://growth-api.nymtech.net"; + +// For development mode, switch to this +// const API_BASE_URL: &str = "http://localhost:8000"; + +#[derive(Debug, Clone)] +pub struct GrowthApiClient { + base_url: String, +} + +impl GrowthApiClient { + pub fn new(resource_base: &str) -> Self { + let base_url = std::env::var("API_BASE_URL").unwrap_or_else(|_| API_BASE_URL.to_string()); + GrowthApiClient { + base_url: format!("{base_url}{resource_base}"), + } + } + + pub fn registrations() -> Registrations { + Registrations::new(GrowthApiClient::new("/v1/tne")) + } + + pub fn daily_draws() -> DailyDraws { + DailyDraws::new(GrowthApiClient::new("/v1/tne/daily_draw")) + } + + pub(crate) async fn get(&self, url: &str) -> Result { + log::info!(">>> GET {}", url); + let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?; + let client = reqwest::Client::builder() + .proxy(proxy) + .timeout(std::time::Duration::from_secs(10)) + .build()?; + + match client.get(format!("{}{}", self.base_url, url)).send().await { + Ok(res) => { + if res.status().is_client_error() || res.status().is_server_error() { + log::error!("<<< {}", res.status()); + return Err(ApiClientError::Status(res.status().to_string())); + } + match res.text().await { + Ok(response_body) => { + log::info!("<<< {}", response_body); + match serde_json::from_str(&response_body) { + Ok(res) => Ok(res), + Err(e) => { + log::error!("<<< JSON parsing error: {}", e); + Err(e.into()) + } + } + } + Err(e) => { + log::error!("<<< Request error: {}", e); + Err(e.into()) + } + } + } + Err(e) => { + log::error!("<<< Response parsing error: {}", e); + Err(e.into()) + } + } + } + + // TODO: use the method in `operations::http` instead + pub(crate) async fn post( + &self, + url: &str, + body: &REQ, + ) -> Result { + log::info!(">>> POST {}", url); + let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?; + let client = reqwest::Client::builder() + .proxy(proxy) + .timeout(std::time::Duration::from_secs(10)) + .build()?; + + match client + .post(format!("{}{}", self.base_url, url)) + .json(body) + .send() + .await + { + Ok(res) => { + if res.status().is_client_error() || res.status().is_server_error() { + log::error!("<<< {}", res.status()); + return Err(ApiClientError::Status(res.status().to_string())); + } + match res.text().await { + Ok(response_body) => { + log::info!("<<< {}", response_body); + match serde_json::from_str(&response_body) { + Ok(res) => Ok(res), + Err(e) => { + log::error!("<<< JSON parsing error: {}", e); + Err(e.into()) + } + } + } + Err(e) => { + log::error!("<<< Request error: {}", e); + Err(e.into()) + } + } + } + Err(e) => { + log::error!("<<< Response parsing error: {}", e); + Err(e.into()) + } + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ClientIdPartial { + pub client_id: String, + pub client_id_signature: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Registration { + pub id: String, + pub client_id: String, + pub timestamp: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Ping { + pub client_id: String, + pub timestamp: String, +} + +pub struct Registrations { + client: GrowthApiClient, +} + +impl Registrations { + pub fn new(client: GrowthApiClient) -> Self { + Registrations { client } + } + + pub async fn register( + &self, + registration: &ClientIdPartial, + ) -> Result { + self.client.post("/register", ®istration).await + } + + #[allow(dead_code)] + pub async fn unregister(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> { + self.client.post("/unregister", ®istration).await + } + + #[allow(dead_code)] + pub async fn status(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> { + self.client.post("/status", ®istration).await + } + + pub async fn ping(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> { + self.client.post("/ping", ®istration).await + } + + #[allow(dead_code)] + pub async fn health(&self) -> Result<(), ApiClientError> { + self.client.get("/health").await + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct DrawEntryPartial { + pub draw_id: String, + pub client_id: String, + pub client_id_signature: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct DrawEntry { + pub id: String, + pub draw_id: String, + pub timestamp: String, + pub status: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct DrawWithWordOfTheDay { + pub id: String, + pub start_utc: String, + pub end_utc: String, + pub word_of_the_day: Option, + pub last_modified: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ClaimPartial { + pub draw_id: String, + pub registration_id: String, + pub client_id: String, + pub client_id_signature: String, + pub wallet_address: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Winner { + pub id: String, + pub client_id: String, + pub draw_id: String, + pub timestamp: String, + pub winner_reg_id: String, + pub winner_wallet_address: Option, + pub winner_claim_timestamp: Option, +} + +pub struct DailyDraws { + client: GrowthApiClient, +} + +impl DailyDraws { + pub fn new(client: GrowthApiClient) -> Self { + DailyDraws { client } + } + + pub async fn current(&self) -> Result { + self.client.get("/current").await + } + + pub async fn next(&self) -> Result { + self.client.get("/next").await + } + + #[allow(dead_code)] + pub async fn status(&self, draw_id: &str) -> Result { + self.client.get(format!("/status/{draw_id}").as_str()).await + } + + pub async fn enter(&self, entry: &DrawEntryPartial) -> Result { + self.client.post("/enter", entry).await + } + + pub async fn entries( + &self, + client_id: &ClientIdPartial, + ) -> Result, ApiClientError> { + self.client.post("/entries", client_id).await + } + + pub async fn claim(&self, claim: &ClaimPartial) -> Result { + self.client.post("/claim", claim).await + } +} diff --git a/nym-connect/desktop/src-tauri/src/operations/growth/assets.rs b/nym-connect/desktop/src-tauri/src/operations/growth/assets.rs new file mode 100644 index 0000000000..752b221916 --- /dev/null +++ b/nym-connect/desktop/src-tauri/src/operations/growth/assets.rs @@ -0,0 +1,57 @@ +use rust_embed::RustEmbed; +extern crate yaml_rust; +use yaml_rust::YamlLoader; + +#[derive(RustEmbed)] +#[folder = "../src/components/Growth/content/"] +#[include = "*.yaml"] +#[exclude = "*.mdx"] +struct Asset; + +#[derive(Debug)] +pub struct NotificationContent { + pub title: String, + pub body: String, +} + +#[derive(Debug)] +pub struct Notifications { + pub you_are_in_draw: NotificationContent, + pub take_part: NotificationContent, +} + +pub struct Content {} + +const RESOURCE_ERROR: &str = "❌ RESOURCE ERROR"; + +fn get_as_string_or_error_message(value: &yaml_rust::Yaml) -> String { + value.as_str().unwrap_or(RESOURCE_ERROR).to_string() +} + +impl Content { + pub fn get_notifications() -> Notifications { + let content = Asset::get("en.yaml").unwrap(); + let s = std::str::from_utf8(content.data.as_ref()).unwrap(); + let content = YamlLoader::load_from_str(s).unwrap(); + let content = &content[0]; + + Notifications { + you_are_in_draw: NotificationContent { + title: get_as_string_or_error_message( + &content["testAndEarn"]["notifications"]["youAreInDraw"]["title"], + ), + body: get_as_string_or_error_message( + &content["testAndEarn"]["notifications"]["youAreInDraw"]["body"], + ), + }, + take_part: NotificationContent { + title: get_as_string_or_error_message( + &content["testAndEarn"]["notifications"]["takePart"]["title"], + ), + body: get_as_string_or_error_message( + &content["testAndEarn"]["notifications"]["takePart"]["body"], + ), + }, + } + } +} diff --git a/nym-connect/desktop/src-tauri/src/operations/growth/mod.rs b/nym-connect/desktop/src-tauri/src/operations/growth/mod.rs new file mode 100644 index 0000000000..449c1bcee2 --- /dev/null +++ b/nym-connect/desktop/src-tauri/src/operations/growth/mod.rs @@ -0,0 +1,3 @@ +pub mod api_client; +pub mod assets; +pub mod test_and_earn; diff --git a/nym-connect/desktop/src-tauri/src/operations/growth/test_and_earn.rs b/nym-connect/desktop/src-tauri/src/operations/growth/test_and_earn.rs new file mode 100644 index 0000000000..c2216ce245 --- /dev/null +++ b/nym-connect/desktop/src-tauri/src/operations/growth/test_and_earn.rs @@ -0,0 +1,156 @@ +use crate::error::BackendError; +use crate::operations::export::get_identity_key; +use crate::operations::growth::api_client::{ + ClaimPartial, ClientIdPartial, DrawEntry, DrawEntryPartial, DrawWithWordOfTheDay, + GrowthApiClient, Registration, Winner, +}; +use crate::State; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use tauri::api::notification::Notification; +use tauri::Manager; +use tokio::sync::RwLock; + +async fn get_client_id( + state: &tauri::State<'_, Arc>>, +) -> Result { + let keypair = get_identity_key(state).await?; + let client_id = keypair.public_key().to_base58_string(); + let client_id_signature = keypair + .private_key() + .sign(client_id.as_bytes()) + .to_base58_string(); + Ok(ClientIdPartial { + client_id, + client_id_signature, + }) +} + +#[tauri::command] +pub async fn growth_tne_get_client_id( + state: tauri::State<'_, Arc>>, +) -> Result { + get_client_id(&state).await +} + +#[tauri::command] +pub async fn growth_tne_take_part( + app_handle: tauri::AppHandle, + state: tauri::State<'_, Arc>>, +) -> Result { + let notifications = super::assets::Content::get_notifications(); + + let client_id = get_client_id(&state).await?; + let registration = GrowthApiClient::registrations() + .register(&client_id) + .await?; + + log::info!("<<< Test&Earn: registration details: {:?}", registration); + + if let Err(e) = Notification::new(&app_handle.config().tauri.bundle.identifier) + .title(notifications.take_part.title) + .body(notifications.take_part.body) + .show() + { + log::error!("Could not show notification. Error = {:?}", e); + } + + Ok(registration) +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Draws { + pub current: Option, + pub next: Option, + pub draws: Vec, +} + +#[tauri::command] +pub async fn growth_tne_get_draws(client_details: ClientIdPartial) -> Result { + let draws_api = GrowthApiClient::daily_draws(); + + let current = draws_api.current().await.ok(); + let next = draws_api.next().await.ok(); + let draws = draws_api.entries(&client_details).await?; + + Ok(Draws { + current, + next, + draws, + }) +} + +#[tauri::command] +pub async fn growth_tne_enter_draw( + client_details: ClientIdPartial, + draw_id: String, +) -> Result { + Ok(GrowthApiClient::daily_draws() + .enter(&DrawEntryPartial { + draw_id, + client_id: client_details.client_id, + client_id_signature: client_details.client_id_signature, + }) + .await?) +} + +#[tauri::command] +pub async fn growth_tne_submit_wallet_address( + client_details: ClientIdPartial, + draw_id: String, + wallet_address: String, + registration_id: String, +) -> Result { + Ok(GrowthApiClient::daily_draws() + .claim(&ClaimPartial { + draw_id, + client_id: client_details.client_id, + client_id_signature: client_details.client_id_signature, + wallet_address, + registration_id, + }) + .await?) +} + +#[tauri::command] +pub async fn growth_tne_ping(client_details: ClientIdPartial) -> Result<(), BackendError> { + log::info!("Test&Earn is sending a ping..."); + Ok(GrowthApiClient::registrations() + .ping(&client_details) + .await?) +} + +#[tauri::command] +pub async fn growth_tne_toggle_window( + app_handle: tauri::AppHandle, + window_title: Option, +) -> Result<(), BackendError> { + if let Some(window) = app_handle.windows().get("growth") { + log::info!("Closing growth window..."); + if let Err(e) = window.close() { + log::error!("Unable to close growth window: {:?}", e); + } + return Ok(()); + } + + log::info!("Creating growth window..."); + match tauri::WindowBuilder::new( + &app_handle, + "growth", + tauri::WindowUrl::App("growth.html".into()), + ) + .title(window_title.unwrap_or_else(|| "NymConnect Test&Earn".to_string())) + .build() + { + Ok(window) => { + if let Err(e) = window.set_focus() { + log::error!("Unable to focus growth window: {:?}", e); + } + Ok(()) + } + Err(e) => { + log::error!("Unable to create growth window: {:?}", e); + Err(BackendError::NewWindowError) + } + } +} diff --git a/nym-connect/desktop/src-tauri/src/operations/mod.rs b/nym-connect/desktop/src-tauri/src/operations/mod.rs index f20c82770d..977dcc5110 100644 --- a/nym-connect/desktop/src-tauri/src/operations/mod.rs +++ b/nym-connect/desktop/src-tauri/src/operations/mod.rs @@ -3,6 +3,7 @@ pub mod config; pub mod connection; pub mod directory; pub mod export; +pub mod growth; pub mod help; pub mod http; pub mod window; From 33838eae8cef5f6fc7a47c56b7c2a9c9832042c8 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 21 Sep 2023 12:35:33 +0100 Subject: [PATCH 3/5] remove test and earn context provider --- nym-connect/desktop/src/index.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/nym-connect/desktop/src/index.tsx b/nym-connect/desktop/src/index.tsx index 4399c91cf2..e9411cf05b 100644 --- a/nym-connect/desktop/src/index.tsx +++ b/nym-connect/desktop/src/index.tsx @@ -18,14 +18,12 @@ if (elem) { - - - - - - - - + + + + + + , From e85c7b6f5f071cba4eae2ab41c97ab7f3d892fd9 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 21 Sep 2023 12:48:42 +0100 Subject: [PATCH 4/5] remove test and earn from bundle --- nym-connect/desktop/webpack.common.js | 2 -- nym-connect/desktop/webpack.prod.js | 1 - 2 files changed, 3 deletions(-) diff --git a/nym-connect/desktop/webpack.common.js b/nym-connect/desktop/webpack.common.js index 38423b5e6b..dfd23f7c96 100644 --- a/nym-connect/desktop/webpack.common.js +++ b/nym-connect/desktop/webpack.common.js @@ -4,7 +4,6 @@ const { webpackCommon } = require('@nymproject/webpack'); const entry = { app: path.resolve(__dirname, 'src/index.tsx'), - growth: path.resolve(__dirname, 'src/growth.tsx'), log: path.resolve(__dirname, 'src/log.tsx'), }; @@ -19,7 +18,6 @@ module.exports = mergeWithRules({ webpackCommon(__dirname, [ { filename: 'index.html', chunks: ['app'], template: path.resolve(__dirname, 'public/index.html') }, { filename: 'log.html', chunks: ['log'], template: path.resolve(__dirname, 'public/log.html') }, - { filename: 'growth.html', chunks: ['growth'], template: path.resolve(__dirname, 'public/growth.html') }, ]), { module: { diff --git a/nym-connect/desktop/webpack.prod.js b/nym-connect/desktop/webpack.prod.js index 0e9061e25a..cb3789f7a6 100644 --- a/nym-connect/desktop/webpack.prod.js +++ b/nym-connect/desktop/webpack.prod.js @@ -4,7 +4,6 @@ const common = require('./webpack.common'); const entry = { app: path.resolve(__dirname, 'src/index.tsx'), - growth: path.resolve(__dirname, 'src/growth.tsx'), log: path.resolve(__dirname, 'src/log.tsx'), }; From 81a0729f52e0409b52c794b3b86277b22b656497 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 21 Sep 2023 13:30:15 +0100 Subject: [PATCH 5/5] fix linting --- nym-connect/desktop/src/growth.tsx | 30 ++++++++++++++++-------------- nym-connect/desktop/src/index.tsx | 1 - nym-connect/desktop/src/log.tsx | 9 ++++++--- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/nym-connect/desktop/src/growth.tsx b/nym-connect/desktop/src/growth.tsx index d90a8bb462..683b49c2e1 100644 --- a/nym-connect/desktop/src/growth.tsx +++ b/nym-connect/desktop/src/growth.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { ErrorBoundary } from 'react-error-boundary'; import { ClientContextProvider } from './context/main'; import { ErrorFallback } from './components/Error'; @@ -7,17 +7,19 @@ import { NymShipyardTheme } from './theme'; import { TestAndEarnPopup } from './components/Growth/TestAndEarnPopup'; import { TestAndEarnContextProvider } from './components/Growth/context/TestAndEarnContext'; -const root = document.getElementById('root-growth'); +const elem = document.getElementById('root-growth'); -ReactDOM.render( - - - - - - - - - , - root, -); +if (elem) { + const root = createRoot(elem); + root.render( + + + + + + + + + , + ); +} diff --git a/nym-connect/desktop/src/index.tsx b/nym-connect/desktop/src/index.tsx index e9411cf05b..996825445d 100644 --- a/nym-connect/desktop/src/index.tsx +++ b/nym-connect/desktop/src/index.tsx @@ -7,7 +7,6 @@ import { ClientContextProvider } from './context/main'; import { ErrorFallback } from './components/Error'; import { NymMixnetTheme } from './theme'; import { AppWindowFrame } from './components/AppWindowFrame'; -import { TestAndEarnContextProvider } from './components/Growth/context/TestAndEarnContext'; import { AppRoutes } from './routes'; const elem = document.getElementById('root'); diff --git a/nym-connect/desktop/src/log.tsx b/nym-connect/desktop/src/log.tsx index cd17271368..ffce19c1d9 100644 --- a/nym-connect/desktop/src/log.tsx +++ b/nym-connect/desktop/src/log.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { ErrorBoundary } from 'react-error-boundary'; import { LogViewer } from './components/LogViewer'; import { ErrorFallback } from './components/ErrorFallback'; @@ -13,6 +13,9 @@ const Log = () => ( ); -const root = document.getElementById('root-log'); +const elem = document.getElementById('root-log'); -ReactDOM.render(, root); +if (elem) { + const root = createRoot(elem); + root.render(); +}