captcha stats

This commit is contained in:
realaravinth 2021-05-27 14:47:29 +05:30
parent df89938f2a
commit fcdbe66b26
No known key found for this signature in database
GPG key ID: AD9F0F08E855ED88
28 changed files with 412 additions and 122 deletions

View file

@ -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()
);

View file

@ -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": {

View file

@ -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<Password>,
@ -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
// );
}

View file

@ -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<AccountCheckPayload>,
data: web::Data<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<Email>,
@ -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
// );
}

View file

@ -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<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<Data>,

View file

@ -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<AccountCheckPayload>,
data: web::Data<Data>,

View file

@ -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<Register>,
data: web::Data<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<Login>,
@ -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();

View file

@ -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<UpdateDuration>,
data: web::Data<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<MCaptchaDetails>,
data: web::Data<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)]

View file

@ -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<AddLevels>,
data: web::Data<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<UpdateLevels>,
data: web::Data<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<UpdateLevels>,
data: web::Data<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<MCaptchaDetails>,
data: web::Data<Data>,

View file

@ -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<MCaptchaDetails>,
data: web::Data<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<MCaptchaDetails>,
data: web::Data<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<MCaptchaDetails>,
data: web::Data<Data>,

View file

@ -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;

View file

@ -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<Data>) -> impl Responder {
use sqlx::Connection;

View file

@ -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<AddNotification>,
data: web::Data<Data>,

View file

@ -52,7 +52,10 @@ impl From<Notification> 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<Data>,
id: Identity,

View file

@ -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<Data>,
payload: web::Json<MarkReadReq>,
@ -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<NotificationResp> =
test::read_body_json(get_notifications_resp).await;
assert!(notifications.pop().is_none());
}
}

View file

@ -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<GetConfigPayload>,
data: web::Data<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.
///

View file

@ -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()

View file

@ -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<Work>,
data: web::Data<Data>,

View file

@ -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<VerifyCaptchaResult>,
data: web::Data<Data>,

View file

@ -194,6 +194,9 @@ pub type ServiceResult<V> = std::result::Result<V, ServiceError>;
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<sqlx::Error> for PageError {
}
}
#[cfg(not(tarpaulin_include))]
impl From<ServiceError> 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(),
}
}
}

View file

@ -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;

View file

@ -15,11 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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")

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */
use actix_web::{web, HttpResponse, Responder};
use lazy_static::lazy_static;

View file

@ -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()

152
src/stats/fetch.rs Normal file
View file

@ -0,0 +1,152 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* 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 <https://www.gnu.org/licenses/>.
*/
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use crate::errors::*;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Stats {
pub config_fetches: Vec<i64>,
pub solves: Vec<i64>,
pub confirms: Vec<i64>,
}
impl Stats {
pub async fn new(key: &str, db: &PgPool) -> ServiceResult<Self> {
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<Vec<i64>> {
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<i64> = 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<Vec<i64>> {
// "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<i64> = 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<Vec<i64>> {
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<i64> = 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);
}
}

19
src/stats/mod.rs Normal file
View file

@ -0,0 +1,19 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* 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 <https://www.gnu.org/licenses/>.
*/
pub mod fetch;
pub mod record;

View file

@ -31,7 +31,6 @@
li.help-text__instructions::marker {
background-color: $violet;
color: $light-text;
width: 30px;
height: 30px;
border-radius: 50%;