diff --git a/migrations/20210509135154_mcaptcha_pow_confirmed_stats.sql b/migrations/20210509135154_mcaptcha_pow_confirmed_stats.sql index 87c28aba..b41d6149 100644 --- a/migrations/20210509135154_mcaptcha_pow_confirmed_stats.sql +++ b/migrations/20210509135154_mcaptcha_pow_confirmed_stats.sql @@ -1,4 +1,4 @@ CREATE TABLE IF NOT EXISTS mcaptcha_pow_confirmed_stats ( config_id INTEGER references mcaptcha_config(config_id) ON DELETE CASCADE, - confirm_ed timestamptz NOT NULL DEFAULT now() + confirmed_at timestamptz NOT NULL DEFAULT now() ); diff --git a/sqlx-data.json b/sqlx-data.json index 6f679419..df82d6c3 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -14,32 +14,6 @@ "nullable": [] } }, - "0612bd9e6b89ff4bf09be0936bf2262cde3967a54fa103ff09a591f7539d063d": { - "query": "SELECT difficulty_factor, visitor_threshold from mcaptcha_levels WHERE config_id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "difficulty_factor", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "visitor_threshold", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Int4" - ] - }, - "nullable": [ - false, - false - ] - } - }, "1be6274d5cc6d16f38285b8a62c9f66e8c3014cd403bc599598e911023bfeedb": { "query": "INSERT INTO mcaptcha_pow_fetched_stats \n (config_id) VALUES ((SELECT config_id FROM mcaptcha_config WHERE key = $1))", "describe": { @@ -173,6 +147,26 @@ ] } }, + "4dc1b6d8ae3b92ebff45f683951c087244f9614ed0e95b75578f0d1346887224": { + "query": "SELECT fetched_at FROM mcaptcha_pow_fetched_stats WHERE config_id = \n (SELECT config_id FROM mcaptcha_config where key = $1)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "fetched_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + } + }, "507bea10c7f8417c5b1430211d0137299cd561333bf47f7b4887d0ef801d1ea4": { "query": "UPDATE mcaptcha_config SET key = $1 \n WHERE key = $2 AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = $3)", "describe": { @@ -355,6 +349,32 @@ "nullable": [] } }, + "a1f1e5693dad5c04b85f97d1de9c68b584a1ca99436e61f7c93f2a5acf8fb55f": { + "query": "SELECT \n difficulty_factor, visitor_threshold \n FROM \n mcaptcha_levels \n WHERE config_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "difficulty_factor", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "visitor_threshold", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false, + false + ] + } + }, "aa9a21fd88c106fe6c4b75a724b202b7bdda66eb9c5fd91780113e2c3ea82719": { "query": "SELECT difficulty_factor, visitor_threshold FROM mcaptcha_levels WHERE\n config_id = (\n SELECT config_id FROM mcaptcha_config WHERE key = ($1)\n AND user_id = (SELECT ID from mcaptcha_users WHERE name = $2)\n );", "describe": { @@ -501,6 +521,26 @@ ] } }, + "daebbef26cf04fdc46226304d028528e121a9847c07139d7d3a56a0e7c165879": { + "query": "SELECT solved_at FROM mcaptcha_pow_solved_stats WHERE config_id = \n (SELECT config_id FROM mcaptcha_config where key = $1)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "solved_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + } + }, "dcf0d4f9d803dcb1d6f775899f79595f9c78d46633e0ec822303284430df7a3d": { "query": "-- gets all unread notifications a user has\nSELECT \n mcaptcha_notifications.id,\n mcaptcha_notifications.heading,\n mcaptcha_notifications.message,\n mcaptcha_notifications.received,\n mcaptcha_users.name\nFROM\n mcaptcha_notifications \nINNER JOIN \n mcaptcha_users \nON \n mcaptcha_notifications.tx = mcaptcha_users.id\nWHERE \n mcaptcha_notifications.rx = (\n SELECT \n id \n FROM \n mcaptcha_users\n WHERE\n name = $1\n )\nAND \n mcaptcha_notifications.read IS NULL;\n", "describe": { @@ -580,6 +620,26 @@ "nullable": [] } }, + "fb15883459af48b0f6f1eee5d47641db8fa0875f7f21665dbc40800869e5e353": { + "query": "SELECT confirmed_at FROM mcaptcha_pow_confirmed_stats WHERE config_id = (\n SELECT config_id FROM mcaptcha_config where key = $1)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "confirmed_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + } + }, "fb19fbff4265cc59450d64a8d945f0ae2ad337b97e6192837881e8b6b4c397ee": { "query": "DELETE FROM mcaptcha_levels WHERE \n config_id = (\n SELECT config_id FROM mcaptcha_config WHERE key = $1 AND\n user_id = (SELECT ID from mcaptcha_users WHERE name = $3)\n ) AND difficulty_factor = ($2);", "describe": { diff --git a/src/api/v1/account/delete.rs b/src/api/v1/account/delete.rs index c5686170..0f8e1791 100644 --- a/src/api/v1/account/delete.rs +++ b/src/api/v1/account/delete.rs @@ -22,7 +22,10 @@ use super::auth::Password; use crate::errors::*; use crate::Data; -#[my_codegen::post(path="crate::V1_API_ROUTES.account.delete", wrap="crate::CheckLogin")] +#[my_codegen::post( + path = "crate::V1_API_ROUTES.account.delete", + wrap = "crate::CheckLogin" +)] async fn delete_account( id: Identity, payload: web::Json, @@ -61,13 +64,13 @@ async fn delete_account( pub fn services(cfg: &mut actix_web::web::ServiceConfig) { cfg.service(delete_account); -// use crate::define_resource; -// use crate::V1_API_ROUTES; -// -// define_resource!( -// cfg, -// V1_API_ROUTES.account.delete, -// Methods::ProtectPost, -// delete_account -// ); + // use crate::define_resource; + // use crate::V1_API_ROUTES; + // + // define_resource!( + // cfg, + // V1_API_ROUTES.account.delete, + // Methods::ProtectPost, + // delete_account + // ); } diff --git a/src/api/v1/account/email.rs b/src/api/v1/account/email.rs index ee4d64b9..a019f96d 100644 --- a/src/api/v1/account/email.rs +++ b/src/api/v1/account/email.rs @@ -29,7 +29,7 @@ pub struct Email { pub email: String, } -#[my_codegen::post(path="crate::V1_API_ROUTES.account.email_exists")] +#[my_codegen::post(path = "crate::V1_API_ROUTES.account.email_exists")] pub async fn email_exists( payload: web::Json, data: web::Data, @@ -53,7 +53,10 @@ pub async fn email_exists( } /// update email -#[my_codegen::post(path="crate::V1_API_ROUTES.account.update_email", wrap="crate::CheckLogin")] +#[my_codegen::post( + path = "crate::V1_API_ROUTES.account.update_email", + wrap = "crate::CheckLogin" +)] async fn set_email( id: Identity, payload: web::Json, @@ -88,20 +91,20 @@ async fn set_email( pub fn services(cfg: &mut actix_web::web::ServiceConfig) { cfg.service(email_exists); cfg.service(set_email); -// use crate::define_resource; -// use crate::V1_API_ROUTES; -// -// define_resource!( -// cfg, -// V1_API_ROUTES.account.email_exists, -// Methods::Post, -// email_exists -// ); -// -// define_resource!( -// cfg, -// V1_API_ROUTES.account.update_email, -// Methods::Post, -// set_email -// ); + // use crate::define_resource; + // use crate::V1_API_ROUTES; + // + // define_resource!( + // cfg, + // V1_API_ROUTES.account.email_exists, + // Methods::Post, + // email_exists + // ); + // + // define_resource!( + // cfg, + // V1_API_ROUTES.account.update_email, + // Methods::Post, + // set_email + // ); } diff --git a/src/api/v1/account/secret.rs b/src/api/v1/account/secret.rs index 51631dd2..4f53f439 100644 --- a/src/api/v1/account/secret.rs +++ b/src/api/v1/account/secret.rs @@ -29,7 +29,10 @@ pub struct Secret { pub secret: String, } -#[my_codegen::get(path="crate::V1_API_ROUTES.account.get_secret", wrap="crate::CheckLogin")] +#[my_codegen::get( + path = "crate::V1_API_ROUTES.account.get_secret", + wrap = "crate::CheckLogin" +)] async fn get_secret( id: Identity, data: web::Data, @@ -47,7 +50,10 @@ async fn get_secret( Ok(HttpResponse::Ok().json(secret)) } -#[my_codegen::post(path="crate::V1_API_ROUTES.account.update_secret", wrap="crate::CheckLogin")] +#[my_codegen::post( + path = "crate::V1_API_ROUTES.account.update_secret", + wrap = "crate::CheckLogin" +)] async fn update_user_secret( id: Identity, data: web::Data, diff --git a/src/api/v1/account/username.rs b/src/api/v1/account/username.rs index 8f925ee0..74cafb72 100644 --- a/src/api/v1/account/username.rs +++ b/src/api/v1/account/username.rs @@ -20,7 +20,7 @@ use super::{AccountCheckPayload, AccountCheckResp}; use crate::errors::*; use crate::Data; -#[my_codegen::post(path="crate::V1_API_ROUTES.account.username_exists")] +#[my_codegen::post(path = "crate::V1_API_ROUTES.account.username_exists")] async fn username_exists( payload: web::Json, data: web::Data, diff --git a/src/api/v1/auth.rs b/src/api/v1/auth.rs index 0d4560d5..cb2d332f 100644 --- a/src/api/v1/auth.rs +++ b/src/api/v1/auth.rs @@ -48,16 +48,15 @@ pub mod routes { } pub fn services(cfg: &mut web::ServiceConfig) { - - // protect_get!(cfg, V1_API_ROUTES.auth.logout, signout); + // protect_get!(cfg, V1_API_ROUTES.auth.logout, signout); cfg.service(signup); cfg.service(signin); cfg.service(signout); -// define_resource!(cfg, V1_API_ROUTES.auth.register, Methods::Post, signup); -// define_resource!(cfg, V1_API_ROUTES.auth.logout, Methods::ProtectGet, signout); -// define_resource!(cfg, V1_API_ROUTES.auth.login, Methods::Post, signin); + // define_resource!(cfg, V1_API_ROUTES.auth.register, Methods::Post, signup); + // define_resource!(cfg, V1_API_ROUTES.auth.logout, Methods::ProtectGet, signout); + // define_resource!(cfg, V1_API_ROUTES.auth.login, Methods::Post, signin); //post!(cfg, V1_API_ROUTES.auth.login, signin); } @@ -80,7 +79,7 @@ pub struct Password { pub password: String, } -#[my_codegen::post(path="crate::V1_API_ROUTES.auth.register")] +#[my_codegen::post(path = "crate::V1_API_ROUTES.auth.register")] async fn signup( payload: web::Json, data: web::Data, @@ -126,7 +125,8 @@ async fn signup( .execute(&data.db) .await; } - if res.is_ok() { break; + if res.is_ok() { + break; } else { if let Err(sqlx::Error::Database(err)) = res { if err.code() == Some(Cow::from("23505")) { @@ -147,7 +147,7 @@ async fn signup( Ok(HttpResponse::Ok()) } -#[my_codegen::post(path="crate::V1_API_ROUTES.auth.login")] +#[my_codegen::post(path = "crate::V1_API_ROUTES.auth.login")] async fn signin( id: Identity, payload: web::Json, @@ -179,7 +179,7 @@ async fn signin( } } -#[my_codegen::get(path="crate::V1_API_ROUTES.auth.logout", wrap="crate::CheckLogin")] +#[my_codegen::get(path = "crate::V1_API_ROUTES.auth.logout", wrap = "crate::CheckLogin")] async fn signout(id: Identity) -> impl Responder { if let Some(_) = id.identity() { id.forget(); diff --git a/src/api/v1/mcaptcha/duration.rs b/src/api/v1/mcaptcha/duration.rs index 6ad208ca..7e71a690 100644 --- a/src/api/v1/mcaptcha/duration.rs +++ b/src/api/v1/mcaptcha/duration.rs @@ -44,7 +44,10 @@ pub struct UpdateDuration { pub duration: i32, } -#[my_codegen::post(path="crate::V1_API_ROUTES.duration.update", wrap="crate::CheckLogin")] +#[my_codegen::post( + path = "crate::V1_API_ROUTES.duration.update", + wrap = "crate::CheckLogin" +)] async fn update_duration( payload: web::Json, data: web::Data, @@ -82,7 +85,10 @@ pub struct GetDuration { pub token: String, } -#[my_codegen::post(path="crate::V1_API_ROUTES.duration.get", wrap="crate::CheckLogin")] +#[my_codegen::post( + path = "crate::V1_API_ROUTES.duration.get", + wrap = "crate::CheckLogin" +)] async fn get_duration( payload: web::Json, data: web::Data, @@ -105,21 +111,21 @@ async fn get_duration( pub fn services(cfg: &mut web::ServiceConfig) { cfg.service(get_duration); cfg.service(update_duration); -// use crate::define_resource; -// use crate::V1_API_ROUTES; -// -// define_resource!( -// cfg, -// V1_API_ROUTES.duration.get, -// Methods::ProtectPost, -// get_duration -// ); -// define_resource!( -// cfg, -// V1_API_ROUTES.duration.update, -// Methods::ProtectPost, -// update_duration -// ); + // use crate::define_resource; + // use crate::V1_API_ROUTES; + // + // define_resource!( + // cfg, + // V1_API_ROUTES.duration.get, + // Methods::ProtectPost, + // get_duration + // ); + // define_resource!( + // cfg, + // V1_API_ROUTES.duration.update, + // Methods::ProtectPost, + // update_duration + // ); } #[cfg(test)] diff --git a/src/api/v1/mcaptcha/levels.rs b/src/api/v1/mcaptcha/levels.rs index d62fd8c4..b931e85d 100644 --- a/src/api/v1/mcaptcha/levels.rs +++ b/src/api/v1/mcaptcha/levels.rs @@ -67,7 +67,7 @@ pub fn services(cfg: &mut web::ServiceConfig) { // TODO redo mcaptcha table to include levels as json field // so that the whole thing can be added/udpaed in a single stroke -#[my_codegen::post(path="crate::V1_API_ROUTES.levels.add", wrap="crate::CheckLogin")] +#[my_codegen::post(path = "crate::V1_API_ROUTES.levels.add", wrap = "crate::CheckLogin")] async fn add_levels( payload: web::Json, data: web::Data, @@ -120,7 +120,10 @@ pub struct UpdateLevels { pub key: String, } -#[my_codegen::post(path="crate::V1_API_ROUTES.levels.update", wrap="crate::CheckLogin")] +#[my_codegen::post( + path = "crate::V1_API_ROUTES.levels.update", + wrap = "crate::CheckLogin" +)] async fn update_levels( payload: web::Json, data: web::Data, @@ -178,7 +181,10 @@ async fn update_levels( Ok(HttpResponse::Ok()) } -#[my_codegen::post(path="crate::V1_API_ROUTES.levels.delete", wrap="crate::CheckLogin")] +#[my_codegen::post( + path = "crate::V1_API_ROUTES.levels.delete", + wrap = "crate::CheckLogin" +)] async fn delete_levels( payload: web::Json, data: web::Data, @@ -205,7 +211,7 @@ async fn delete_levels( Ok(HttpResponse::Ok()) } -#[my_codegen::post(path="crate::V1_API_ROUTES.levels.get", wrap="crate::CheckLogin")] +#[my_codegen::post(path = "crate::V1_API_ROUTES.levels.get", wrap = "crate::CheckLogin")] async fn get_levels( payload: web::Json, data: web::Data, diff --git a/src/api/v1/mcaptcha/mcaptcha.rs b/src/api/v1/mcaptcha/mcaptcha.rs index 03622f76..3e73045c 100644 --- a/src/api/v1/mcaptcha/mcaptcha.rs +++ b/src/api/v1/mcaptcha/mcaptcha.rs @@ -111,7 +111,10 @@ pub async fn add_mcaptcha_util( Ok(resp) } -#[my_codegen::post(path="crate::V1_API_ROUTES.mcaptcha.update_key", wrap="crate::CheckLogin")] +#[my_codegen::post( + path = "crate::V1_API_ROUTES.mcaptcha.update_key", + wrap = "crate::CheckLogin" +)] async fn update_token( payload: web::Json, data: web::Data, @@ -162,7 +165,10 @@ async fn update_token_helper( Ok(()) } -#[my_codegen::post(path="crate::V1_API_ROUTES.mcaptcha.get_token", wrap="crate::CheckLogin")] +#[my_codegen::post( + path = "crate::V1_API_ROUTES.mcaptcha.get_token", + wrap = "crate::CheckLogin" +)] async fn get_token( payload: web::Json, data: web::Data, @@ -190,7 +196,10 @@ async fn get_token( Ok(HttpResponse::Ok().json(res)) } -#[my_codegen::post(path="crate::V1_API_ROUTES.mcaptcha.delete", wrap="crate::CheckLogin")] +#[my_codegen::post( + path = "crate::V1_API_ROUTES.mcaptcha.delete", + wrap = "crate::CheckLogin" +)] async fn delete_mcaptcha( payload: web::Json, data: web::Data, diff --git a/src/api/v1/mcaptcha/mod.rs b/src/api/v1/mcaptcha/mod.rs index 1923c69b..28b1642b 100644 --- a/src/api/v1/mcaptcha/mod.rs +++ b/src/api/v1/mcaptcha/mod.rs @@ -18,7 +18,6 @@ pub mod duration; pub mod levels; pub mod mcaptcha; -pub mod stats; pub fn get_random(len: usize) -> String { use std::iter; diff --git a/src/api/v1/meta.rs b/src/api/v1/meta.rs index 5a759eab..15bf6ed6 100644 --- a/src/api/v1/meta.rs +++ b/src/api/v1/meta.rs @@ -45,7 +45,7 @@ pub mod routes { } /// emmits build details of the bninary -#[my_codegen::get(path="crate::V1_API_ROUTES.meta.build_details")] +#[my_codegen::get(path = "crate::V1_API_ROUTES.meta.build_details")] async fn build_details() -> impl Responder { let build = BuildDetails { version: VERSION, @@ -61,7 +61,7 @@ pub struct Health { } /// checks all components of the system -#[my_codegen::get(path="crate::V1_API_ROUTES.meta.health")] +#[my_codegen::get(path = "crate::V1_API_ROUTES.meta.health")] async fn health(data: web::Data) -> impl Responder { use sqlx::Connection; diff --git a/src/api/v1/notifications/add.rs b/src/api/v1/notifications/add.rs index aa3ed37f..2c01e4cd 100644 --- a/src/api/v1/notifications/add.rs +++ b/src/api/v1/notifications/add.rs @@ -30,7 +30,10 @@ pub struct AddNotification { } /// route handler that adds a notification message -#[my_codegen::post(path="crate::V1_API_ROUTES.notifications.add", wrap="crate::CheckLogin")] +#[my_codegen::post( + path = "crate::V1_API_ROUTES.notifications.add", + wrap = "crate::CheckLogin" +)] pub async fn add_notification( payload: web::Json, data: web::Data, diff --git a/src/api/v1/notifications/get.rs b/src/api/v1/notifications/get.rs index d0d7e96d..ff54b234 100644 --- a/src/api/v1/notifications/get.rs +++ b/src/api/v1/notifications/get.rs @@ -52,7 +52,10 @@ impl From for NotificationResp { } } /// route handler that gets all unread notifications -#[my_codegen::get(path="crate::V1_API_ROUTES.notifications.get", wrap="crate::CheckLogin")] +#[my_codegen::get( + path = "crate::V1_API_ROUTES.notifications.get", + wrap = "crate::CheckLogin" +)] pub async fn get_notification( data: web::Data, id: Identity, diff --git a/src/api/v1/notifications/mark_read.rs b/src/api/v1/notifications/mark_read.rs index 5a5287af..f5a7f62e 100644 --- a/src/api/v1/notifications/mark_read.rs +++ b/src/api/v1/notifications/mark_read.rs @@ -37,7 +37,10 @@ pub struct NotificationResp { } /// route handler that marks a notification read -#[my_codegen::post(path="crate::V1_API_ROUTES.notifications.mark_read", wrap="crate::CheckLogin")] +#[my_codegen::post( + path = "crate::V1_API_ROUTES.notifications.mark_read", + wrap = "crate::CheckLogin" +)] pub async fn mark_read( data: web::Data, payload: web::Json, @@ -68,7 +71,6 @@ mod tests { use crate::tests::*; use crate::*; - #[actix_rt::test] async fn notification_mark_read_works() { const NAME1: &str = "notifuser122"; @@ -125,7 +127,6 @@ mod tests { assert_eq!(notification.message, MESSAGE); assert_eq!(notification.heading, HEADING); - let mark_read_payload = MarkReadReq { id: notification.id.clone(), }; @@ -138,7 +139,6 @@ mod tests { .await; assert_eq!(mark_read_resp.status(), StatusCode::OK); - let get_notifications_resp = test::call_service( &mut app, test::TestRequest::get() @@ -151,6 +151,5 @@ mod tests { let mut notifications: Vec = test::read_body_json(get_notifications_resp).await; assert!(notifications.pop().is_none()); - } } diff --git a/src/api/v1/pow/get_config.rs b/src/api/v1/pow/get_config.rs index d35a3453..5d1c9164 100644 --- a/src/api/v1/pow/get_config.rs +++ b/src/api/v1/pow/get_config.rs @@ -22,10 +22,10 @@ use m_captcha::{ }; use serde::{Deserialize, Serialize}; -use super::record_fetch; use super::GetDurationResp; use super::I32Levels; use crate::errors::*; +use crate::stats::record::record_fetch; use crate::Data; use crate::V1_API_ROUTES; @@ -43,7 +43,9 @@ pub struct GetConfigPayload { // API keys are mcaptcha actor names /// get PoW configuration for an mcaptcha key -#[my_codegen::post(path = "V1_API_ROUTES.pow.get_config.strip_prefix(V1_API_ROUTES.pow.scope).unwrap()")] +#[my_codegen::post( + path = "V1_API_ROUTES.pow.get_config.strip_prefix(V1_API_ROUTES.pow.scope).unwrap()" +)] pub async fn get_config( payload: web::Json, data: web::Data, @@ -82,7 +84,6 @@ pub async fn get_config( Some(false) => Err(ServiceError::TokenNotFound), None => Err(ServiceError::TokenNotFound), } - } /// Call this when [MCaptcha][m_captcha::MCaptcha] is not in master. /// diff --git a/src/api/v1/pow/mod.rs b/src/api/v1/pow/mod.rs index 888e38bc..662c25a9 100644 --- a/src/api/v1/pow/mod.rs +++ b/src/api/v1/pow/mod.rs @@ -24,7 +24,6 @@ pub mod verify_token; pub use super::mcaptcha::duration::GetDurationResp; pub use super::mcaptcha::levels::I32Levels; -use crate::api::v1::mcaptcha::stats::*; pub fn services(cfg: &mut web::ServiceConfig) { let cors = actix_cors::Cors::default() diff --git a/src/api/v1/pow/verify_pow.rs b/src/api/v1/pow/verify_pow.rs index 3fe07152..88d24be0 100644 --- a/src/api/v1/pow/verify_pow.rs +++ b/src/api/v1/pow/verify_pow.rs @@ -20,8 +20,8 @@ use actix_web::{web, HttpResponse, Responder}; use m_captcha::pow::Work; use serde::{Deserialize, Serialize}; -use super::record_solve; use crate::errors::*; +use crate::stats::record::record_solve; use crate::Data; use crate::V1_API_ROUTES; @@ -36,7 +36,9 @@ pub struct ValidationToken { /// route handler that verifies PoW and issues a solution token /// if verification is successful -#[my_codegen::post(path = "V1_API_ROUTES.pow.verify_pow.strip_prefix(V1_API_ROUTES.pow.scope).unwrap()")] +#[my_codegen::post( + path = "V1_API_ROUTES.pow.verify_pow.strip_prefix(V1_API_ROUTES.pow.scope).unwrap()" +)] pub async fn verify_pow( payload: web::Json, data: web::Data, diff --git a/src/api/v1/pow/verify_token.rs b/src/api/v1/pow/verify_token.rs index 99861988..4ecb0e1c 100644 --- a/src/api/v1/pow/verify_token.rs +++ b/src/api/v1/pow/verify_token.rs @@ -20,8 +20,8 @@ use actix_web::{web, HttpResponse, Responder}; use m_captcha::cache::messages::VerifyCaptchaResult; use serde::{Deserialize, Serialize}; -use super::record_confirm; use crate::errors::*; +use crate::stats::record::record_confirm; use crate::Data; use crate::V1_API_ROUTES; @@ -33,7 +33,9 @@ pub struct CaptchaValidateResp { // API keys are mcaptcha actor names /// route hander that validates a PoW solution token -#[my_codegen::post(path = "V1_API_ROUTES.pow.validate_captcha_token.strip_prefix(V1_API_ROUTES.pow.scope).unwrap()")] +#[my_codegen::post( + path = "V1_API_ROUTES.pow.validate_captcha_token.strip_prefix(V1_API_ROUTES.pow.scope).unwrap()" +)] pub async fn validate_captcha_token( payload: web::Json, data: web::Data, diff --git a/src/errors.rs b/src/errors.rs index 531ff839..504222eb 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -194,6 +194,9 @@ pub type ServiceResult = std::result::Result; pub enum PageError { #[display(fmt = "Something weng wrong: Internal server error")] InternalServerError, + + #[display(fmt = "{}", _0)] + ServiceError(ServiceError), } #[cfg(not(tarpaulin_include))] @@ -204,6 +207,14 @@ impl From for PageError { } } +#[cfg(not(tarpaulin_include))] +impl From for PageError { + #[cfg(not(tarpaulin_include))] + fn from(e: ServiceError) -> Self { + PageError::ServiceError(e) + } +} + impl ResponseError for PageError { fn error_response(&self) -> HttpResponse { use crate::PAGES; @@ -223,6 +234,7 @@ impl ResponseError for PageError { fn status_code(&self) -> StatusCode { match self { PageError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR, + PageError::ServiceError(e) => e.status_code(), } } } diff --git a/src/main.rs b/src/main.rs index c0f55008..cf6c1a81 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,15 +28,16 @@ mod api; mod data; mod docs; mod errors; +mod middleware; mod pages; -mod settings; -mod static_assets; #[macro_use] mod routes; +mod settings; +mod static_assets; +mod stats; #[cfg(test)] #[macro_use] mod tests; -mod middleware; pub use api::v1::ROUTES as V1_API_ROUTES; pub use data::Data; diff --git a/src/pages/auth/login.rs b/src/pages/auth/login.rs index 55ca4f42..4c49c8f5 100644 --- a/src/pages/auth/login.rs +++ b/src/pages/auth/login.rs @@ -15,11 +15,11 @@ * along with this program. If not, see . */ +use crate::PAGES; use actix_web::{HttpResponse, Responder}; use lazy_static::lazy_static; -use sailfish::TemplateOnce; use my_codegen::get; -use crate::PAGES; +use sailfish::TemplateOnce; #[derive(Clone, TemplateOnce)] #[template(path = "auth/login/index.html")] @@ -37,7 +37,7 @@ lazy_static! { static ref INDEX: String = IndexPage::default().render_once().unwrap(); } -#[get(path="PAGES.auth.login")] +#[get(path = "PAGES.auth.login")] pub async fn login() -> impl Responder { HttpResponse::Ok() .content_type("text/html; charset=utf-8") diff --git a/src/pages/errors.rs b/src/pages/errors.rs index 1926ea31..9495c473 100644 --- a/src/pages/errors.rs +++ b/src/pages/errors.rs @@ -11,9 +11,7 @@ * 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 . -*/ +* You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ use actix_web::{web, HttpResponse, Responder}; use lazy_static::lazy_static; diff --git a/src/pages/panel/sitekey/view.rs b/src/pages/panel/sitekey/view.rs index e34703b3..2a5a9e66 100644 --- a/src/pages/panel/sitekey/view.rs +++ b/src/pages/panel/sitekey/view.rs @@ -17,9 +17,11 @@ use actix_identity::Identity; use actix_web::{web, HttpResponse, Responder}; +use futures::{future::TryFutureExt, try_join}; use sailfish::TemplateOnce; use crate::errors::*; +use crate::stats::fetch::Stats; use crate::Data; const PAGE: &str = "SiteKeys"; @@ -75,13 +77,19 @@ pub async fn view_sitekey( .fetch_one(&data.db) .await?; - let levels = sqlx::query_as!( + let levels_fut = sqlx::query_as!( Level, - "SELECT difficulty_factor, visitor_threshold from mcaptcha_levels WHERE config_id = $1", + "SELECT + difficulty_factor, visitor_threshold + FROM + mcaptcha_levels + WHERE config_id = $1", &config.config_id ) .fetch_all(&data.db) - .await?; + .err_into(); + + let (stats, levels) = try_join!(Stats::new(&key, &data.db), levels_fut)?; let body = IndexPage::new(config, levels).render_once().unwrap(); Ok(HttpResponse::Ok() diff --git a/src/stats/fetch.rs b/src/stats/fetch.rs new file mode 100644 index 00000000..9a90cbfe --- /dev/null +++ b/src/stats/fetch.rs @@ -0,0 +1,152 @@ +/* +* Copyright (C) 2021 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::errors::*; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Stats { + pub config_fetches: Vec, + pub solves: Vec, + pub confirms: Vec, +} + +impl Stats { + pub async fn new(key: &str, db: &PgPool) -> ServiceResult { + let config_fetches_fut = Self::fetch_config_fetched(key, db); + let solves_fut = Self::fetch_solve(key, db); + let confirms_fut = Self::fetch_confirm(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) + } + + /// featch PoWConfig fetches + #[inline] + pub async fn fetch_config_fetched( + key: &str, + db: &PgPool, + ) -> ServiceResult> { + let records = sqlx::query!( + "SELECT fetched_at FROM mcaptcha_pow_fetched_stats WHERE config_id = + (SELECT config_id FROM mcaptcha_config where key = $1)", + &key, + ) + .fetch_all(db) + .await?; + + let mut res: Vec = Vec::with_capacity(records.len()); + + records + .iter() + .for_each(|record| res.push(record.fetched_at.unix_timestamp())); + + Ok(res) + } + + /// featch PoWConfig solves + #[inline] + pub async fn fetch_solve(key: &str, db: &PgPool) -> ServiceResult> { + // "SELECT solved_at FROM mcaptcha_pow_solved_stats WHERE config_id = + // (SELECT config_id FROM mcaptcha_config where key = $1)" + let records = sqlx::query!( + "SELECT solved_at FROM mcaptcha_pow_solved_stats WHERE config_id = + (SELECT config_id FROM mcaptcha_config where key = $1)", + &key, + ) + .fetch_all(db) + .await?; + + let mut res: Vec = Vec::with_capacity(records.len()); + + records + .iter() + .for_each(|record| res.push(record.solved_at.unix_timestamp())); + + Ok(res) + } + + /// featch PoWConfig confirms + #[inline] + pub async fn fetch_confirm(key: &str, db: &PgPool) -> ServiceResult> { + let records = sqlx::query!( + "SELECT confirmed_at FROM mcaptcha_pow_confirmed_stats WHERE config_id = ( + SELECT config_id FROM mcaptcha_config where key = $1)", + &key + ) + .fetch_all(db) + .await?; + + let mut res: Vec = Vec::with_capacity(records.len()); + + records + .iter() + .for_each(|record| res.push(record.confirmed_at.unix_timestamp())); + + Ok(res) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::stats::record::*; + use crate::tests::*; + use crate::*; + + #[actix_rt::test] + async fn stats_works() { + const NAME: &str = "statsuser"; + const PASSWORD: &str = "testingpas"; + const EMAIL: &str = "statsuser@a.com"; + + let data = Data::new().await; + delete_user(NAME, &data).await; + + register_and_signin(NAME, EMAIL, PASSWORD).await; + let (_, _, _, token_key) = add_levels_util(NAME, PASSWORD).await; + let key = token_key.key.clone(); + + let stats = Stats::new(&key, &data.db).await.unwrap(); + + assert_eq!(stats.config_fetches.len(), 0); + assert_eq!(stats.solves.len(), 0); + assert_eq!(stats.confirms.len(), 0); + + futures::join!( + record_fetch(&key, &data.db), + record_solve(&key, &data.db), + record_confirm(&key, &data.db) + ); + + let stats = Stats::new(&key, &data.db).await.unwrap(); + + assert_eq!(stats.config_fetches.len(), 1); + assert_eq!(stats.solves.len(), 1); + assert_eq!(stats.confirms.len(), 1); + } +} diff --git a/src/stats/mod.rs b/src/stats/mod.rs new file mode 100644 index 00000000..868df0b1 --- /dev/null +++ b/src/stats/mod.rs @@ -0,0 +1,19 @@ +/* +* Copyright (C) 2021 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 . +*/ + +pub mod fetch; +pub mod record; diff --git a/src/api/v1/mcaptcha/stats.rs b/src/stats/record.rs similarity index 100% rename from src/api/v1/mcaptcha/stats.rs rename to src/stats/record.rs diff --git a/templates/panel/help-banner/main.scss b/templates/panel/help-banner/main.scss index db282fbd..4841bfd8 100644 --- a/templates/panel/help-banner/main.scss +++ b/templates/panel/help-banner/main.scss @@ -31,7 +31,6 @@ li.help-text__instructions::marker { background-color: $violet; - color: $light-text; width: 30px; height: 30px; border-radius: 50%;