From c46b3f4f4ceee0e3825feab48be07622d06b05d2 Mon Sep 17 00:00:00 2001 From: realaravinth Date: Sat, 18 Dec 2021 21:01:19 +0530 Subject: [PATCH] Implement easy edit view When user tries to visit this view without an easy configuration available, i.e, user had created the CAPTCHA using advance view and no TrafficPattern is available in database, the user will be automatically redirected to the advance edit page. But the default edit link everywhere is to the easy edit view. --- src/api/v1/mcaptcha/easy.rs | 29 ++++ src/pages/panel/sitekey/edit.rs | 131 +++++++++++++++++- src/pages/panel/sitekey/mod.rs | 1 + templates/index.ts | 6 +- templates/panel/sitekey/add/novice/ts/form.ts | 19 ++- templates/panel/sitekey/edit/easy/form.html | 67 +++++++++ templates/panel/sitekey/edit/easy/form.ts | 51 +++++++ templates/panel/sitekey/edit/easy/index.html | 15 ++ templates/panel/sitekey/edit/easy/index.ts | 22 +++ 9 files changed, 332 insertions(+), 9 deletions(-) create mode 100644 templates/panel/sitekey/edit/easy/form.html create mode 100644 templates/panel/sitekey/edit/easy/form.ts create mode 100644 templates/panel/sitekey/edit/easy/index.html create mode 100644 templates/panel/sitekey/edit/easy/index.ts diff --git a/src/api/v1/mcaptcha/easy.rs b/src/api/v1/mcaptcha/easy.rs index 9ce99a2b..b226a47e 100644 --- a/src/api/v1/mcaptcha/easy.rs +++ b/src/api/v1/mcaptcha/easy.rs @@ -219,6 +219,7 @@ async fn update( mod tests { use actix_web::http::StatusCode; use actix_web::test; + use actix_web::web::Bytes; use super::*; use crate::api::v1::mcaptcha::create::MCaptchaDetails; @@ -392,5 +393,33 @@ mod tests { assert_ne!(res_levels, default_levels); assert_eq!(res_levels, updated_default_values); // END update_easy + + // test easy edit page + let easy_url = PAGES.panel.sitekey.get_edit_easy(&token_key.key); + + let easy_edit_page = test::call_service( + &app, + test::TestRequest::get() + .uri(&easy_url) + .cookie(cookies.clone()) + .to_request(), + ) + .await; + assert_eq!(easy_edit_page.status(), StatusCode::OK); + + let body: Bytes = test::read_body(easy_edit_page).await; + let body = String::from_utf8(body.to_vec()).unwrap(); + assert!(body.contains(&token_key.name)); + + assert!(body.contains( + &payload + .pattern + .broke_my_site_traffic + .as_ref() + .unwrap() + .to_string() + )); + assert!(body.contains(&payload.pattern.avg_traffic.to_string())); + assert!(body.contains(&payload.pattern.peak_sustainable_traffic.to_string())); } } diff --git a/src/pages/panel/sitekey/edit.rs b/src/pages/panel/sitekey/edit.rs index 7cea8c2b..1356e7e0 100644 --- a/src/pages/panel/sitekey/edit.rs +++ b/src/pages/panel/sitekey/edit.rs @@ -15,13 +15,15 @@ * along with this program. If not, see . */ use actix_identity::Identity; -use actix_web::{web, HttpResponse, Responder}; +use actix_web::{http, web, HttpResponse, Responder}; use sailfish::TemplateOnce; +use sqlx::Error::RowNotFound; +use crate::api::v1::mcaptcha::easy::TrafficPattern; use crate::errors::*; use crate::AppData; -const PAGE: &str = "SiteKeys"; +const PAGE: &str = "Edit Sitekey"; #[derive(Clone)] struct McaptchaConfig { @@ -100,9 +102,118 @@ pub async fn advance( .body(body)) } +#[derive(TemplateOnce, Clone)] +#[template(path = "panel/sitekey/edit/easy/index.html")] +pub struct EasyEditPage<'a> { + pub form_title: &'a str, + pub pattern: TrafficPattern, + pub key: String, +} + +impl<'a> EasyEditPage<'a> { + pub fn new(key: String, pattern: TrafficPattern) -> Self { + Self { + form_title: PAGE, + pattern, + key, + } + } +} + +/// route handler that renders individual views for sitekeys +#[my_codegen::get( + path = "crate::PAGES.panel.sitekey.edit_easy", + wrap = "crate::CheckLogin" +)] +pub async fn easy( + path: web::Path, + data: AppData, + id: Identity, +) -> PageResult { + let username = id.identity().unwrap(); + let key = path.into_inner(); + + struct Traffic { + peak_sustainable_traffic: i32, + avg_traffic: i32, + broke_my_site_traffic: Option, + } + + match sqlx::query_as!( + Traffic, + "SELECT + avg_traffic, + peak_sustainable_traffic, + broke_my_site_traffic + FROM + mcaptcha_sitekey_user_provided_avg_traffic + WHERE + config_id = ( + SELECT + config_id + FROM + mcaptcha_config + WHERE + KEY = $1 + AND user_id = ( + SELECT + id + FROM + mcaptcha_users + WHERE + NAME = $2 + ) + ) + ", + &key, + &username + ) + .fetch_one(&data.db) + .await + { + Ok(c) => { + struct Description { + name: String, + } + let description = sqlx::query_as!( + Description, + "SELECT name FROM mcaptcha_config + WHERE key = $1 + AND user_id = ( + SELECT user_id FROM mcaptcha_users WHERE NAME = $2)", + &key, + &username + ) + .fetch_one(&data.db) + .await?; + + let pattern = TrafficPattern { + peak_sustainable_traffic: c.peak_sustainable_traffic as u32, + avg_traffic: c.avg_traffic as u32, + broke_my_site_traffic: c.broke_my_site_traffic.map(|n| n as u32), + description: description.name, + }; + + let page = EasyEditPage::new(key, pattern).render_once().unwrap(); + return Ok(HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(page)); + } + Err(RowNotFound) => { + return Ok(HttpResponse::Found() + .insert_header(( + http::header::LOCATION, + crate::PAGES.panel.sitekey.get_edit_advance(&key), + )) + .finish()); + } + Err(e) => Err(e.into()), + } +} + #[cfg(test)] mod test { - use actix_web::http::StatusCode; + use actix_web::http::{header, StatusCode}; use actix_web::test; use actix_web::web::Bytes; @@ -148,5 +259,19 @@ mod test { assert!(body.contains(&L1.difficulty_factor.to_string())); assert!(body.contains(&L2.difficulty_factor.to_string())); assert!(body.contains(&L2.visitor_threshold.to_string())); + + let easy_url = PAGES.panel.sitekey.get_edit_easy(&key.key); + + let redirect_resp = test::call_service( + &app, + test::TestRequest::get() + .uri(&easy_url) + .cookie(cookies.clone()) + .to_request(), + ) + .await; + assert_eq!(redirect_resp.status(), StatusCode::FOUND); + let headers = redirect_resp.headers(); + assert_eq!(headers.get(header::LOCATION).unwrap(), &url); } } diff --git a/src/pages/panel/sitekey/mod.rs b/src/pages/panel/sitekey/mod.rs index 6aa3224a..1d1cb654 100644 --- a/src/pages/panel/sitekey/mod.rs +++ b/src/pages/panel/sitekey/mod.rs @@ -73,6 +73,7 @@ pub fn services(cfg: &mut actix_web::web::ServiceConfig) { cfg.service(list::list_sitekeys); cfg.service(view::view_sitekey); cfg.service(edit::advance); + cfg.service(edit::easy); cfg.service(delete::delete_sitekey); } diff --git a/templates/index.ts b/templates/index.ts index 0c2d6814..a4b0fc6b 100644 --- a/templates/index.ts +++ b/templates/index.ts @@ -25,7 +25,8 @@ import * as deleteAccount from "./panel/settings/account/delete"; import * as updateSecret from "./panel/settings/secret/update"; import * as addSiteKeyAdvance from "./panel/sitekey/add/advance/ts"; import * as addSiteKeyEasy from "./panel/sitekey/add/novice/ts"; -import * as editSitekey from "./panel/sitekey/edit/"; +import * as editSitekeyAdvance from "./panel/sitekey/edit/"; +import * as editSitekeyEasy from "./panel/sitekey/edit/easy/"; import * as deleteSitekey from "./panel/sitekey/delete/"; import * as listSitekeys from "./panel/sitekey/list/ts"; import * as notidications from "./panel/notifications/ts"; @@ -48,7 +49,8 @@ router.register(VIEWS.notifications, notidications.index); router.register(VIEWS.listSitekey, listSitekeys.index); router.register(VIEWS.addSiteKeyAdvance,addSiteKeyAdvance.index); router.register(VIEWS.addSiteKeyEasy, addSiteKeyEasy.index); -router.register(VIEWS.editSitekeyAdvance("[A-Z),a-z,0-9]+"), editSitekey.index); +router.register(VIEWS.editSitekeyAdvance("[A-Z),a-z,0-9]+"), editSitekeyAdvance.index); +router.register(VIEWS.editSitekeyEasy("[A-Z),a-z,0-9]+"), editSitekeyEasy.index); router.register(VIEWS.deleteSitekey("[A-Z),a-z,0-9]+"), deleteSitekey.index); try { diff --git a/templates/panel/sitekey/add/novice/ts/form.ts b/templates/panel/sitekey/add/novice/ts/form.ts index 2f86759d..087e7f92 100644 --- a/templates/panel/sitekey/add/novice/ts/form.ts +++ b/templates/panel/sitekey/add/novice/ts/form.ts @@ -45,9 +45,14 @@ export const break_my_site_name = "traffic that broke your website"; export const avg_traffic_name = "average"; export const peak_traffic_name = "maximum traffic your website can handle"; -const submit = async (e: Event) => { - e.preventDefault(); +type TrafficPattern = { + avg_traffic: number; + peak_sustainable_traffic: number; + broke_my_site_traffic?: number; + description: string; +}; +export const validate = (e: Event): TrafficPattern => { const description = validateDescription(e); let broke_is_set = false; @@ -89,8 +94,6 @@ const submit = async (e: Event) => { } } - const formUrl = getFormUrl(FORM); - const payload = { avg_traffic, peak_sustainable_traffic, @@ -98,6 +101,14 @@ const submit = async (e: Event) => { description, }; + return payload; +}; + +const submit = async (e: Event) => { + e.preventDefault(); + + const formUrl = getFormUrl(FORM); + const payload = validate(e); console.debug(`[form submition] json payload: ${JSON.stringify(payload)}`); const res = await fetch(formUrl, genJsonPayload(payload)); diff --git a/templates/panel/sitekey/edit/easy/form.html b/templates/panel/sitekey/edit/easy/form.html new file mode 100644 index 00000000..0f82e433 --- /dev/null +++ b/templates/panel/sitekey/edit/easy/form.html @@ -0,0 +1,67 @@ +
+
+

+ <.= form_title .> +

+ + Advance Options + +
+ + + + + + + + + + + +
diff --git a/templates/panel/sitekey/edit/easy/form.ts b/templates/panel/sitekey/edit/easy/form.ts new file mode 100644 index 00000000..76a01e33 --- /dev/null +++ b/templates/panel/sitekey/edit/easy/form.ts @@ -0,0 +1,51 @@ +/* + * 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 . + */ +import getFormUrl from "../../../../utils/getFormUrl"; +import genJsonPayload from "../../../../utils/genJsonPayload"; +import createError from "../../../../components/error"; + +import VIEWS from "../../../../views/v1/routes"; + +import { validate, FORM } from "../../add/novice/ts/form"; + +const SUBMIT_BTN = ( + document.querySelector(".sitekey-form__submit") +); +const key = SUBMIT_BTN.dataset.sitekey; +const submit = async (e: Event) => { + e.preventDefault(); + + const formUrl = getFormUrl(FORM); + const payload = { + pattern: validate(e), + key, + }; + console.debug(`[form submition] json payload: ${JSON.stringify(payload)}`); + + const res = await fetch(formUrl, genJsonPayload(payload)); + if (res.ok) { + window.location.assign(VIEWS.viewSitekey(key)); + } else { + const err = await res.json(); + createError(err.error); + } +}; + +const addSubmitEventListener = (): void => + FORM.addEventListener("submit", submit, true); + +export default addSubmitEventListener; diff --git a/templates/panel/sitekey/edit/easy/index.html b/templates/panel/sitekey/edit/easy/index.html new file mode 100644 index 00000000..0a0ee36f --- /dev/null +++ b/templates/panel/sitekey/edit/easy/index.html @@ -0,0 +1,15 @@ +<. include!("../../../../components/headers/index.html"); .> +<. include!("../../../navbar/index.html"); .> +
+<. include!("../../../header/index.html"); .> + +
+ <. include!("../../../help-banner/index.html"); .> + +
+ + + <. include!("./form.html"); .> +
+ +<. include!("../../../../components/footers.html"); .> diff --git a/templates/panel/sitekey/edit/easy/index.ts b/templates/panel/sitekey/edit/easy/index.ts new file mode 100644 index 00000000..808180c5 --- /dev/null +++ b/templates/panel/sitekey/edit/easy/index.ts @@ -0,0 +1,22 @@ +/* + * 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 . + */ + +import addSubmitEventListener from "./form"; + +export const index = (): void => { + addSubmitEventListener(); +};