/* * Copyright (C) 2023 Aravinth Manivannan * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use async_trait::async_trait; use reqwest::Client; use serde::{Deserialize, Serialize}; use url::Url; use crate::errors::*; /* TODO: * 1. Define traits to interact with mCaptcha * 2. Implement trait with request 3. Implement mocking for testing * 4. Load to crate::data::Data */ #[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] /// Proof-of-Work CAPTCHA performance analytics pub struct PerformanceAnalytics { /// log ID pub id: usize, /// time taken to generate proof pub time: u32, /// difficulty factor for which the proof was generated pub difficulty_factor: u32, /// worker/client type: wasm, javascript, python, etc. pub worker_type: String, } #[async_trait] pub trait MCaptchaClient: std::marker::Send + std::marker::Sync + CloneMCaptchaClient { async fn share_secret( &self, mut mcaptcha: Url, secret: String, auth_token: String, ) -> ServiceResult<()>; async fn download_benchmarks( &self, mut mcaptcha: Url, campaign_id: &str, page: usize, ) -> ServiceResult>; } /// Trait to clone MCaptchaClient pub trait CloneMCaptchaClient { /// clone client fn clone_client(&self) -> Box; } impl CloneMCaptchaClient for T where T: MCaptchaClient + Clone + 'static, { fn clone_client(&self) -> Box { Box::new(self.clone()) } } impl Clone for Box { fn clone(&self) -> Self { (**self).clone_client() } } #[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] pub struct Secret { pub secret: String, } #[derive(Clone)] pub struct MCaptchaClientReqwest { client: Client, } impl Default for MCaptchaClientReqwest { fn default() -> Self { Self { client: Client::new(), } } } #[async_trait] impl MCaptchaClient for MCaptchaClientReqwest { async fn share_secret( &self, mut mcaptcha: Url, secret: String, auth_token: String, ) -> ServiceResult<()> { #[derive(Serialize)] struct S { secret: String, auth_token: String, } let msg = S { secret, auth_token, }; mcaptcha.set_path("/api/v1/survey/secret"); self.client .post(mcaptcha) .json(&msg) .send() .await .unwrap(); Ok(()) } async fn download_benchmarks( &self, mut mcaptcha: Url, campaign_id: &str, page: usize, ) -> ServiceResult> { mcaptcha.set_path(&format!("/api/v1/survey/{campaign_id}/get?page={page}")); let res = self .client .get(mcaptcha) .send() .await .unwrap() .json() .await .unwrap(); Ok(res) } } #[cfg(test)] pub mod tests { use super::*; use std::collections::HashMap; use std::sync::{Arc, RwLock}; use lazy_static::lazy_static; lazy_static! { pub static ref BENCHMARK: Vec = vec![ PerformanceAnalytics { id: 1, time: 2, difficulty_factor: 3, worker_type: "foo".to_string(), }, PerformanceAnalytics { id: 4, time: 5, difficulty_factor: 6, worker_type: "bar".to_string(), }, ]; } #[derive(Clone)] pub struct TestClient { pub client: Arc>>, } impl Default for TestClient { fn default() -> Self { Self { client: Arc::new(RwLock::new(HashMap::default())), } } } #[async_trait] impl MCaptchaClient for TestClient { async fn share_secret( &self, mut mcaptcha: Url, secret: String, auth_token: String, ) -> ServiceResult<()> { mcaptcha.set_path("/api/v1/survey/secret"); let mut x = self.client.write().unwrap(); x.insert(mcaptcha.to_string(), secret.secret.to_owned()); drop(x); Ok(()) } async fn download_benchmarks( &self, mcaptcha: Url, campaign_id: &str, page: usize, ) -> ServiceResult> { println!( "mcaptcha_url {}, campaign_id {}, page: {page}", mcaptcha.to_string(), campaign_id ); let res = BENCHMARK.clone(); Ok(res) } } }