diff --git a/src/api/v1/mcaptcha/stats.rs b/src/api/v1/mcaptcha/stats.rs index b3a911d8..9db9084d 100644 --- a/src/api/v1/mcaptcha/stats.rs +++ b/src/api/v1/mcaptcha/stats.rs @@ -19,7 +19,6 @@ use actix_web::{web, HttpResponse, Responder}; use serde::{Deserialize, Serialize}; use crate::errors::*; -use crate::stats::fetch::{Stats, StatsUnixTimestamp}; use crate::AppData; pub mod routes { @@ -50,7 +49,6 @@ pub async fn get( id: Identity, ) -> ServiceResult { let username = id.identity().unwrap(); - let stats = Stats::new(&username, &payload.key, &data.db).await?; - let stats = StatsUnixTimestamp::from_stats(&stats); + let stats = data.stats.fetch(&data, &username, &payload.key).await?; Ok(HttpResponse::Ok().json(&stats)) } diff --git a/src/api/v1/pow/get_config.rs b/src/api/v1/pow/get_config.rs index c4be496b..2486bff6 100644 --- a/src/api/v1/pow/get_config.rs +++ b/src/api/v1/pow/get_config.rs @@ -49,7 +49,7 @@ pub async fn get_config( match data.captcha.get_pow(payload.key.clone()).await { Ok(Some(config)) => { - data.stats.record_fetch(&data, &payload.key).await; + data.stats.record_fetch(&data, &payload.key).await?; Ok(HttpResponse::Ok().json(config)) } Ok(None) => { @@ -61,7 +61,7 @@ pub async fn get_config( .expect("mcaptcha should be initialized and ready to go"); // background it. would require data::Data to be static // to satidfy lifetime - data.stats.record_fetch(&data, &payload.key).await; + data.stats.record_fetch(&data, &payload.key).await?; Ok(HttpResponse::Ok().json(config)) } Err(e) => Err(e.into()), diff --git a/src/api/v1/pow/verify_pow.rs b/src/api/v1/pow/verify_pow.rs index 40a45eb4..5bd82cc7 100644 --- a/src/api/v1/pow/verify_pow.rs +++ b/src/api/v1/pow/verify_pow.rs @@ -42,7 +42,7 @@ pub async fn verify_pow( ) -> ServiceResult { let key = payload.key.clone(); let res = data.captcha.verify_pow(payload.into_inner()).await?; - data.stats.record_solve(&data, &key).await; + data.stats.record_solve(&data, &key).await?; let payload = ValidationToken { token: res }; Ok(HttpResponse::Ok().json(payload)) } diff --git a/src/api/v1/pow/verify_token.rs b/src/api/v1/pow/verify_token.rs index 4e37e685..76666118 100644 --- a/src/api/v1/pow/verify_token.rs +++ b/src/api/v1/pow/verify_token.rs @@ -43,7 +43,7 @@ pub async fn validate_captcha_token( .validate_verification_tokens(payload.into_inner()) .await?; let payload = CaptchaValidateResp { valid: res }; - data.stats.record_confirm(&data, &key).await; + data.stats.record_confirm(&data, &key).await?; //println!("{:?}", &payload); Ok(HttpResponse::Ok().json(payload)) } diff --git a/src/date.rs b/src/date.rs index d98b4cef..2d3a389e 100644 --- a/src/date.rs +++ b/src/date.rs @@ -65,6 +65,12 @@ impl Date { pub fn date(&self) -> String { self.time.format("%F %r %z") } + + pub fn new(unix: i64) -> Self { + Self { + time: OffsetDateTime::from_unix_timestamp(unix), + } + } } #[cfg(test)] diff --git a/src/pages/panel/sitekey/view.rs b/src/pages/panel/sitekey/view.rs index 9b67158b..d83c0dcf 100644 --- a/src/pages/panel/sitekey/view.rs +++ b/src/pages/panel/sitekey/view.rs @@ -17,13 +17,12 @@ use actix_identity::Identity; use actix_web::{web, HttpResponse, Responder}; -use futures::{future::TryFutureExt, try_join}; use sailfish::TemplateOnce; use libmcaptcha::defense::Level; use crate::errors::*; -use crate::stats::fetch::Stats; +use crate::stats::CaptchaStats; use crate::AppData; const PAGE: &str = "SiteKeys"; @@ -42,12 +41,12 @@ struct IndexPage { name: String, key: String, levels: Vec, - stats: Stats, + stats: CaptchaStats, } impl IndexPage { fn new( - stats: Stats, + stats: CaptchaStats, config: McaptchaConfig, levels: Vec, key: String, @@ -87,8 +86,7 @@ pub async fn view_sitekey( .await?; let levels = data.dblib.get_captcha_levels(Some(&username), &key).await?; - - let stats = Stats::new(&username, &key, &data.db).await?; + let stats = data.stats.fetch(&data, &username, &key).await?; let body = IndexPage::new(stats, config, levels, key) .render_once() diff --git a/src/stats/mod.rs b/src/stats.rs similarity index 72% rename from src/stats/mod.rs rename to src/stats.rs index 3f5af9a7..b3012316 100644 --- a/src/stats/mod.rs +++ b/src/stats.rs @@ -14,14 +14,9 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - -pub mod fetch; -//pub mod record; - -pub use fetch::StatsUnixTimestamp; - use async_trait::async_trait; use db_core::errors::DBResult; +use serde::{Deserialize, Serialize}; use crate::data::Data; @@ -35,6 +30,9 @@ pub trait Stats: std::marker::Send + std::marker::Sync + CloneStats { /// record PoWConfig confirms async fn record_confirm(&self, d: &Data, key: &str) -> DBResult<()>; + + /// fetch stats + async fn fetch(&self, d: &Data, user: &str, key: &str) -> DBResult; } /// Trait to clone MCDatabase @@ -59,6 +57,13 @@ impl Clone for Box { } } +#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] +pub struct CaptchaStats { + pub config_fetches: Vec, + pub solves: Vec, + pub confirms: Vec, +} + #[derive(Clone, Default, PartialEq, Debug)] pub struct Real; @@ -78,6 +83,24 @@ impl Stats for Real { async fn record_confirm(&self, d: &Data, key: &str) -> DBResult<()> { d.dblib.record_confirm(key).await } + + /// fetch stats + async fn fetch(&self, d: &Data, user: &str, key: &str) -> DBResult { + let config_fetches_fut = d.dblib.fetch_config_fetched(user, key); + let solves_fut = d.dblib.fetch_solve(user, key); + let confirms_fut = d.dblib.fetch_confirm(user, key); + + let (config_fetches, solves, confirms) = + futures::try_join!(config_fetches_fut, solves_fut, confirms_fut)?; + + let res = CaptchaStats { + config_fetches, + solves, + confirms, + }; + + Ok(res) + } } #[derive(Clone, Default, PartialEq, Debug)] @@ -99,4 +122,9 @@ impl Stats for Dummy { async fn record_confirm(&self, _: &Data, _: &str) -> DBResult<()> { Ok(()) } + + /// fetch stats + async fn fetch(&self, _: &Data, _: &str, _: &str) -> DBResult { + Ok(CaptchaStats::default()) + } } diff --git a/src/stats/fetch.rs b/src/stats/fetch.rs deleted file mode 100644 index 8cf56714..00000000 --- a/src/stats/fetch.rs +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) 2022 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 serde::{Deserialize, Serialize}; -use sqlx::PgPool; - -use crate::date::Date; -use crate::errors::*; - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct StatsUnixTimestamp { - pub config_fetches: Vec, - pub solves: Vec, - pub confirms: Vec, -} - -#[derive(Debug, Clone)] -pub struct Stats { - pub config_fetches: Vec, - pub solves: Vec, - pub confirms: Vec, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct StatsPayload { - pub key: String, -} - -impl Stats { - pub async fn new(user: &str, key: &str, db: &PgPool) -> ServiceResult { - let config_fetches_fut = runners::fetch_config_fetched(user, key, db); - let solves_fut = runners::fetch_solve(user, key, db); - let confirms_fut = runners::fetch_confirm(user, key, db); - - let (config_fetches, solves, confirms) = - futures::try_join!(config_fetches_fut, solves_fut, confirms_fut)?; - - let res = Self { - config_fetches, - solves, - confirms, - }; - - Ok(res) - } -} - -impl StatsUnixTimestamp { - pub fn from_stats(stats: &Stats) -> Self { - let config_fetches = Self::unix_timestamp(&stats.config_fetches); - let solves = Self::unix_timestamp(&stats.solves); - let confirms = Self::unix_timestamp(&stats.confirms); - Self { - config_fetches, - solves, - confirms, - } - } - - /// featch PoWConfig confirms - #[inline] - fn unix_timestamp(dates: &[Date]) -> Vec { - let mut res: Vec = Vec::with_capacity(dates.len()); - - dates - .iter() - .for_each(|record| res.push(record.time.unix_timestamp())); - - res - } -} - -pub mod runners { - use super::*; - /// featch PoWConfig fetches - #[inline] - pub async fn fetch_config_fetched( - user: &str, - key: &str, - db: &PgPool, - ) -> ServiceResult> { - let records = sqlx::query_as!( - Date, - "SELECT time FROM mcaptcha_pow_fetched_stats - WHERE - config_id = ( - SELECT - config_id FROM mcaptcha_config - WHERE - key = $1 - AND - user_id = ( - SELECT - ID FROM mcaptcha_users WHERE name = $2)) - ORDER BY time DESC", - &key, - &user, - ) - .fetch_all(db) - .await?; - - Ok(records) - } - - /// featch PoWConfig solves - #[inline] - pub async fn fetch_solve( - user: &str, - key: &str, - db: &PgPool, - ) -> ServiceResult> { - let records = sqlx::query_as!( - Date, - "SELECT time FROM mcaptcha_pow_solved_stats - WHERE config_id = ( - SELECT config_id FROM mcaptcha_config - WHERE - key = $1 - AND - user_id = ( - SELECT - ID FROM mcaptcha_users WHERE name = $2)) - ORDER BY time DESC", - &key, - &user - ) - .fetch_all(db) - .await?; - - Ok(records) - } - - /// featch PoWConfig confirms - #[inline] - pub async fn fetch_confirm( - user: &str, - key: &str, - db: &PgPool, - ) -> ServiceResult> { - let records = sqlx::query_as!( - Date, - "SELECT time FROM mcaptcha_pow_confirmed_stats - WHERE - config_id = ( - SELECT config_id FROM mcaptcha_config - WHERE - key = $1 - AND - user_id = ( - SELECT - ID FROM mcaptcha_users WHERE name = $2)) - ORDER BY time DESC", - &key, - &user - ) - .fetch_all(db) - .await?; - - Ok(records) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::*; - - #[actix_rt::test] - async fn stats_works() { - const NAME: &str = "statsuser"; - const PASSWORD: &str = "testingpas"; - const EMAIL: &str = "statsuser@a.com"; - - let data = get_data().await; - let data = &data; - delete_user(data, NAME).await; - - register_and_signin(data, NAME, EMAIL, PASSWORD).await; - let (_, _, token_key) = add_levels_util(data, NAME, PASSWORD).await; - let key = token_key.key.clone(); - - let stats = Stats::new(NAME, &key, &data.db).await.unwrap(); - - assert_eq!(stats.config_fetches.len(), 0); - assert_eq!(stats.solves.len(), 0); - assert_eq!(stats.confirms.len(), 0); - - let _ = futures::join!( - data.stats.record_fetch(&data, &key,), - data.stats.record_solve(&data, &key,), - data.stats.record_confirm(&data, &key,), - ); - - let stats = Stats::new(NAME, &key, &data.db).await.unwrap(); - - assert_eq!(stats.config_fetches.len(), 1); - assert_eq!(stats.solves.len(), 1); - assert_eq!(stats.confirms.len(), 1); - - let ustats = StatsUnixTimestamp::from_stats(&stats); - assert_eq!(ustats.config_fetches.len(), 1); - assert_eq!(ustats.solves.len(), 1); - assert_eq!(ustats.confirms.len(), 1); - } -} diff --git a/src/stats/record.rs b/src/stats/record.rs deleted file mode 100644 index bc03514e..00000000 --- a/src/stats/record.rs +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2022 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 sqlx::types::time::OffsetDateTime; -use sqlx::PgPool; - -/// record PoWConfig fetches -#[inline] -pub async fn record_fetch(key: &str, db: &PgPool) { - let now = OffsetDateTime::now_utc(); - let _ = sqlx::query!( - "INSERT INTO mcaptcha_pow_fetched_stats - (config_id, time) VALUES ((SELECT config_id FROM mcaptcha_config WHERE key = $1), $2)", - &key, - &now, - ) - .execute(db) - .await; -} - -/// record PoWConfig solves -#[inline] -pub async fn record_solve(key: &str, db: &PgPool) { - let now = OffsetDateTime::now_utc(); - let _ = sqlx::query!( - "INSERT INTO mcaptcha_pow_solved_stats - (config_id, time) VALUES ((SELECT config_id FROM mcaptcha_config WHERE key = $1), $2)", - &key, - &now, - ) - .execute(db) - .await; -} - -/// record PoWConfig confirms -#[inline] -pub async fn record_confirm(key: &str, db: &PgPool) { - let now = OffsetDateTime::now_utc(); - let _ = sqlx::query!( - "INSERT INTO mcaptcha_pow_confirmed_stats - (config_id, time) VALUES ((SELECT config_id FROM mcaptcha_config WHERE key = $1), $2)", - &key, - &now - ) - .execute(db) - .await; -} diff --git a/templates/panel/sitekey/view/stats.html b/templates/panel/sitekey/view/stats.html index e95a5f1c..d6110ec7 100644 --- a/templates/panel/sitekey/view/stats.html +++ b/templates/panel/sitekey/view/stats.html @@ -16,7 +16,7 @@ -

<.= val.date() .>

+

<.= crate::date::Date::new(*val).date() .>

<. } .>