/* * 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 std::cell::RefCell; use actix_identity::Identity; use actix_web::http::header::ContentType; use libforms::{FormSubmissionResp, Table}; use serde::{Deserialize, Serialize}; use serde_json::Value; use tera::Context; use time::{format_description, OffsetDateTime}; use super::get_auth_middleware; use crate::pages::errors::*; use crate::settings::Settings; use crate::AppCtx; pub use super::*; pub const DASH_SITE_FORM_VIEW: TemplateFile = TemplateFile::new("dash_site_form_view", "pages/dash/sites/forms/view.html"); pub struct View { ctx: RefCell, } impl CtxError for View { fn with_error(&self, e: &ReadableError) -> String { self.ctx.borrow_mut().insert(ERROR_KEY, e); self.render() } } impl View { pub fn new(settings: &Settings, payload: Option) -> Self { let ctx = RefCell::new(context(settings)); if let Some(payload) = payload { ctx.borrow_mut().insert(PAYLOAD_KEY, &payload); } Self { ctx } } pub fn render(&self) -> String { TEMPLATES .render(DASH_SITE_FORM_VIEW.name, &self.ctx.borrow()) .unwrap() } } #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] pub struct TemplateForms { pub submissions: Vec, pub next_page: String, } #[derive(Serialize, Deserialize, Clone, Debug, Default, Eq, PartialEq)] pub struct TemplateSubmission { pub value: TemplateFormData, pub delete: String, pub time: String, pub id: usize, } #[derive(Serialize, Deserialize, Clone, Debug, Default, Eq, PartialEq)] pub struct TemplateFormData { pub titles: Vec, pub rows: Vec>, } impl From for TemplateFormData { fn from(json: Value) -> Self { let mut titles = Vec::default(); let mut rows: Vec> = Vec::default(); match json { Value::Object(m) => { let mut row: Vec = Vec::default(); for (k, v) in m.iter() { titles.push(k.clone()); let value = match v { Value::String(s) => s.clone(), Value::Null => "-".into(), Value::Bool(b) => b.to_string(), Value::Number(n) => n.to_string(), Value::Array(a) => format!("{:?}", a), Value::Object(_) => unimplemented!(), }; row.push(value); } rows.push(row); } _ => unimplemented!(), } Self { titles, rows } } } impl TemplateSubmission { fn from_form_submission_resp(f: FormSubmissionResp, host: &str, path: &str) -> Self { let format = format_description::parse( "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \ sign:mandatory]:[offset_minute]:[offset_second]", ) .unwrap(); let time = OffsetDateTime::from_unix_timestamp(f.time) .unwrap() .format(&format) .unwrap(); Self { value: f.value.unwrap().into(), id: f.id, time, delete: PAGES.dash.site.forms.get_delete(f.id, host, path), } } } impl TemplateForms { pub fn new( current_page: usize, mut form_submissions: Vec, host: &str, path: &str, ) -> Self { let mut submissions = Vec::with_capacity(form_submissions.len()); for sub in form_submissions.drain(0..) { let sub = TemplateSubmission::from_form_submission_resp(sub, host, path); submissions.push(sub); } let next_page = PAGES.dash.site.forms.get_view(current_page + 1, host, path); Self { next_page, submissions, } } } #[derive(Serialize, Deserialize, Clone, Debug, Default)] pub struct FormPage { page: usize, host: String, path: String, } impl FormPage { pub fn to_table(self) -> (Table, usize) { ( Table { host: self.host, path: self.path, }, self.page, ) } } #[actix_web_codegen_const_routes::get( path = "PAGES.dash.site.forms.view", wrap = "get_auth_middleware()" )] #[tracing::instrument(name = "Dashboard view site form submissions webpage", skip(ctx, id))] pub async fn get_form_submissions( ctx: AppCtx, id: Identity, payload: web::Query, ) -> PageResult { let owner = id.identity().unwrap(); let payload = payload.into_inner(); let (table, page) = payload.to_table(); let resp = ctx .get_all_form_submission(&owner, page, &table) .await .unwrap(); // .map_err(|e| PageError::new(View::new(&ctx.settings, None), e))?; let payload = TemplateForms::new(page, resp, &table.host, &table.path); let page = View::new(&ctx.settings, Some(payload)); let body = page.render(); let html = ContentType::html(); Ok(HttpResponse::Ok().content_type(html).body(body)) } pub fn services(cfg: &mut web::ServiceConfig) { cfg.service(get_form_submissions); } #[cfg(test)] mod tests { use actix_web::http::StatusCode; use actix_web::test; use serde::{Deserialize, Serialize}; use crate::ctx::api::v1::auth::Password; use crate::ctx::ArcCtx; use crate::errors::ServiceError; use crate::pages::dash::sites::add::TemplateAddSite; use crate::tests; use crate::*; use super::Table; use super::PAGES; #[actix_rt::test] async fn postgres_dashboard_form_works() { let (_, ctx) = tests::get_ctx().await; dashboard_site_form_works(ctx.clone()).await; } async fn dashboard_site_form_works(ctx: ArcCtx) { const NAME: &str = "testdashsiteformuser"; const EMAIL: &str = "testdashsiteformuser@foo.com"; const PASSWORD: &str = "longpassword"; let _ = ctx.delete_user(NAME, PASSWORD).await; let (_, signin_resp) = ctx.register_and_signin(NAME, EMAIL, PASSWORD).await; let cookies = get_cookie!(signin_resp); let app = get_app!(ctx.clone()).await; let page = ctx.add_test_site(NAME.into()).await; let site_info = Table { host: page.domain.clone(), path: format!("/foo/{NAME}"), }; #[derive(Serialize, Deserialize)] struct Foo { foo: String, } if let Ok(subs) = ctx.get_all_form_submission(NAME, 0, &site_info).await { for s in subs.iter() { let _ = ctx.delete_form_submission(NAME, s.id, &site_info).await; } } let foo = Foo { foo: "barvalue".into(), }; ctx.add_form_submission(NAME, &site_info, &serde_json::to_value(&foo).unwrap()) .await .unwrap(); // list forms for host let resp = get_request!( &app, &PAGES.dash.site.forms.get_list_host(&site_info.host), cookies.clone() ); assert_eq!(resp.status(), StatusCode::OK); let res = String::from_utf8(test::read_body(resp).await.to_vec()).unwrap(); assert!(res.contains(&site_info.path)); // view form submission let resp = get_request!( &app, &PAGES .dash .site .forms .get_view(0, &site_info.host, &site_info.path), cookies.clone() ); assert_eq!(resp.status(), StatusCode::OK); let res = String::from_utf8(test::read_body(resp).await.to_vec()).unwrap(); assert!(res.contains("foo")); assert!(res.contains("barvalue")); } }