From c9f198494d471985868becc4167de76b0b0c5b88 Mon Sep 17 00:00:00 2001 From: Jonatan Ziegler Date: Mon, 17 Jun 2024 22:30:42 +0200 Subject: [PATCH] added new mail template to rust; almost complete --- backend/Cargo.lock | 21 ++--- backend/Cargo.toml | 4 +- backend/src/interface/admin_notification.rs | 17 +++- .../src/interface/persistent_data/model.rs | 2 + backend/src/layer/data/database/command.rs | 4 +- backend/src/layer/data/database/request.rs | 4 +- backend/src/layer/data/mail/mail_sender.rs | 82 +++++++++++-------- .../src/layer/data/mail/template/output.css | 2 +- .../layer/data/mail/template/template.html | 61 +++++++------- .../src/layer/data/mail/template/template.txt | 12 --- .../logic/api_command/command_handler.rs | 8 +- backend/src/layer/logic/api_command/mocks.rs | 1 + backend/src/layer/trigger/api/mock.rs | 3 + backend/src/util.rs | 3 +- 14 files changed, 122 insertions(+), 102 deletions(-) delete mode 100644 backend/src/layer/data/mail/template/template.txt diff --git a/backend/Cargo.lock b/backend/Cargo.lock index ac9ae8c8..6aee6dd4 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -464,6 +464,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.52.3", ] @@ -1695,6 +1696,7 @@ dependencies = [ "lazy_static", "lettre", "mime", + "minijinja", "multer 3.0.0", "rand", "regex", @@ -1706,7 +1708,6 @@ dependencies = [ "serial_test", "sha2", "sqlx", - "string_template", "tempfile", "thiserror", "time", @@ -1736,6 +1737,15 @@ dependencies = [ "unicase", ] +[[package]] +name = "minijinja" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e136ef580d7955019ab0a407b68d77c292a9976907e217900f3f76bc8f6dc1a4" +dependencies = [ + "serde", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3132,15 +3142,6 @@ dependencies = [ "quote", ] -[[package]] -name = "string_template" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6f2c6b2c3fa950895c9aeb0c3cb9271d7eb580662af9af2b711b593f446320" -dependencies = [ - "regex", -] - [[package]] name = "stringprep" version = "0.1.4" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 87226578..03dda9c1 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -17,7 +17,7 @@ license = "MIT" [dependencies] async-trait = "0.1.68" -chrono = "0.4.26" +chrono = { version = "0.4.26", features = ["serde"] } thiserror = "1.0.40" uuid = "1.4.0" axum = { version = "0.6.18", features = [ @@ -55,7 +55,6 @@ sqlx = { version = "0.7", features = [ "macros", ] } lettre = "0.11.1" -string_template = "0.2.1" lazy_static = "1.4.0" colored = "2.0.4" image = "0.24.0" @@ -67,6 +66,7 @@ hyper = { version = "0.14" } mime = "0.3.17" hmac = "0.12.1" multer = { version = "3.0.0", features = ["tokio-io"] } +minijinja = "2.0.2" [dev-dependencies] serial_test = "3.0.0" diff --git a/backend/src/interface/admin_notification.rs b/backend/src/interface/admin_notification.rs index 372d8501..89c9cc21 100644 --- a/backend/src/interface/admin_notification.rs +++ b/backend/src/interface/admin_notification.rs @@ -1,8 +1,9 @@ //! This interface allows administrators to be notified of reporting requests. use async_trait::async_trait; +use serde::Serialize; -use crate::util::{ReportReason, Uuid}; +use crate::util::{Date, ReportReason, Uuid}; /// Interface for notification of administrators. #[async_trait] @@ -11,7 +12,7 @@ pub trait AdminNotification: Sync + Send { async fn notify_admin_image_report(&self, info: ImageReportInfo); } -#[derive(Debug)] +#[derive(Debug, Serialize)] /// Structure containing all information about the reporting of an image. pub struct ImageReportInfo { /// Reason for the report. @@ -29,11 +30,19 @@ pub struct ImageReportInfo { /// Number of negative ratings for this image. pub negative_rating_count: u32, /// Image rank after which the images are sorted when shown to the user. - pub get_image_rank: f32, + pub image_rank: f32, /// Number of times this image would have to be reported to automatically get hidden (at the current date). pub report_barrier: u32, /// User that reported the image. pub client_id: Uuid, - /// The age of the image in days + /// The age of the image in days. pub image_age: i64, + /// Name of the meal this image belongs to. + pub meal_name: String, + /// Id of the meal this image belongs to. + pub meal_id: Uuid, + /// Date and time this image got reported. + pub report_date: Date, + /// list of urls of other images of the same meal. + pub other_image_urls: Vec, } diff --git a/backend/src/interface/persistent_data/model.rs b/backend/src/interface/persistent_data/model.rs index 738725f1..4f1fbe7b 100644 --- a/backend/src/interface/persistent_data/model.rs +++ b/backend/src/interface/persistent_data/model.rs @@ -92,6 +92,8 @@ pub struct Image { pub upload_date: Date, /// Amount of open report request related to that image. pub report_count: u32, + /// Id of the meal this image belongs to. + pub meal_id: Uuid, } /// This struct contains all environmental information. co2 in grams, water in litres diff --git a/backend/src/layer/data/database/command.rs b/backend/src/layer/data/database/command.rs index a5eba3ee..332e2c3a 100644 --- a/backend/src/layer/data/database/command.rs +++ b/backend/src/layer/data/database/command.rs @@ -21,7 +21,7 @@ impl CommandDataAccess for PersistentCommandData { let record = sqlx::query!( r#" SELECT approved, link_date as upload_date, report_count, - upvotes, downvotes, image_id, rank + upvotes, downvotes, image_id, rank, food_id FROM image_detail WHERE image_id = $1 ORDER BY image_id @@ -39,6 +39,7 @@ impl CommandDataAccess for PersistentCommandData { downvotes: u32::try_from(null_error!(record.downvotes))?, upvotes: u32::try_from(null_error!(record.upvotes))?, id: null_error!(record.image_id), + meal_id: null_error!(record.food_id), }) } @@ -190,6 +191,7 @@ mod test { approved: false, upload_date: Local::now().date_naive(), report_count: 0, + meal_id: Uuid::default(), } } diff --git a/backend/src/layer/data/database/request.rs b/backend/src/layer/data/database/request.rs index 1ee095ff..4687680a 100644 --- a/backend/src/layer/data/database/request.rs +++ b/backend/src/layer/data/database/request.rs @@ -200,7 +200,7 @@ impl RequestDataAccess for PersistentRequestData { sqlx::query!( " SELECT image_id, rank, id as hoster_id, url, upvotes, downvotes, - approved, report_count, link_date + approved, report_count, link_date, food_id FROM ( --- not reported by user SELECT image_id @@ -226,6 +226,7 @@ impl RequestDataAccess for PersistentRequestData { approved: null_error!(r.approved), report_count: u32::try_from(null_error!(r.report_count))?, upload_date: null_error!(r.link_date), + meal_id: null_error!(r.food_id), }) }) .collect::>>() @@ -568,6 +569,7 @@ mod tests { upvotes: 0, upload_date: Local::now().date_naive(), report_count: 0, + meal_id: Uuid::default(), }; let image2 = Image { id: Uuid::parse_str("76b904fe-d0f1-4122-8832-d0e21acab86d").unwrap(), diff --git a/backend/src/layer/data/mail/mail_sender.rs b/backend/src/layer/data/mail/mail_sender.rs index 965f63c0..2fa7a1fc 100644 --- a/backend/src/layer/data/mail/mail_sender.rs +++ b/backend/src/layer/data/mail/mail_sender.rs @@ -2,11 +2,14 @@ use std::fmt::Debug; use async_trait::async_trait; +use minijinja::{context, Environment, Value}; use thiserror::Error; use lettre::{ - address::AddressError, message::Mailbox, transport::smtp::authentication::Credentials, Address, - Message, SmtpTransport, Transport, + address::AddressError, + message::{Mailbox, MaybeString, SinglePart}, + transport::smtp::authentication::Credentials, + Address, Message, SmtpTransport, Transport, }; use crate::{ @@ -14,13 +17,13 @@ use crate::{ layer::data::mail::mail_info::MailInfo, }; -use string_template::Template; use tracing::{error, info}; /// Result returned when sending emails, potentially containing a [`MailError`]. pub type MailResult = std::result::Result; -const REPORT_TEMPLATE: &str = include_str!("./template.txt"); +const REPORT_TEMPLATE: &str = include_str!("./template/template.html"); +const REPORT_CSS: &str = include_str!("./template/output.css"); const SENDER_NAME: &str = "MensaKa"; const RECEIVER_NAME: &str = "Administrator"; const MAIL_SUBJECT: &str = "An image was reported and requires your review"; @@ -77,7 +80,7 @@ impl MailSender { .from(sender) .to(reciever) .subject(MAIL_SUBJECT) - .body(report)?; + .singlepart(SinglePart::html(MaybeString::String(report)))?; self.mailer.send(&email)?; info!( ?info, @@ -97,39 +100,33 @@ impl MailSender { } fn get_report(info: &ImageReportInfo) -> String { - let info_array_map = [ - ("image_link", &info.image_link as &dyn ToString), - ("image_id", &info.image_id), - ("report_count", &info.report_count), - ("reason", &info.reason), - ("image_got_hidden", &info.image_got_hidden), - ("positive_rating_count", &info.positive_rating_count), - ("negative_rating_count", &info.negative_rating_count), - ("get_image_rank", &info.get_image_rank), - ("report_barrier", &info.report_barrier), - ("client_id", &info.client_id), - ("image_age", &info.image_age), - ]; - - let info_map = info_array_map - .into_iter() - .map(|(k, v)| (k, v.to_string())) - .collect::>(); - let info_map = info_map.iter().map(|(k, v)| (*k, v.as_str())).collect(); - - Template::new(REPORT_TEMPLATE).render(&info_map) + let env = Environment::new(); + let template = env + .template_from_str(REPORT_TEMPLATE) + .expect("template always preset"); + + template + .render(context!( + css => REPORT_CSS, + delete_url => "#", // todo + verify_url => "#", // todo + ..Value::from_serialize(info), + )) + .expect("all arguments provided at compile time") } } #[cfg(test)] mod test { #![allow(clippy::unwrap_used)] + use super::REPORT_CSS; use crate::{ interface::admin_notification::{AdminNotification, ImageReportInfo}, layer::data::mail::mail_info::MailInfo, layer::data::mail::mail_sender::MailSender, util::Uuid, }; + use chrono::Local; use dotenvy; use std::env::{self, VarError}; use tracing_test::traced_test; @@ -140,16 +137,24 @@ mod test { const SMTP_PASSWORD_ENV_NAME: &str = "SMTP_PASSWORD"; const ADMIN_EMAIL_ENV_NAME: &str = "ADMIN_EMAIL"; + #[tokio::test] + #[ignore] + async fn test_print_report() { + let info = get_report_info(); + let report = MailSender::get_report(&info); + println!("{report}"); + } + #[tokio::test] async fn test_get_report() { let info = get_report_info(); let report = MailSender::get_report(&info).replace("\r\n", "\n"); assert!( - !report.contains("{{"), + !report.contains("{{ "), "the template must not contain any formatting" ); assert!( - !report.contains("}}"), + !report.contains(" }}"), "the template must not contain any formatting" ); assert!( @@ -164,10 +169,10 @@ mod test { report.contains(info.report_count.to_string().as_str()), "the template must contain all of the information from the report info" ); - assert!( - report.contains(info.image_got_hidden.to_string().as_str()), - "the template must contain all of the information from the report info" - ); + // assert!( + // report.contains(info.image_got_hidden.to_string().as_str()), + // "the template must contain all of the information from the report info" + // ); assert!( report.contains(info.positive_rating_count.to_string().as_str()), "the template must contain all of the information from the report info" @@ -177,7 +182,7 @@ mod test { "the template must contain all of the information from the report info" ); assert!( - report.contains(info.get_image_rank.to_string().as_str()), + report.contains(info.image_rank.to_string().as_str()), "the template must contain all of the information from the report info" ); assert!( @@ -192,6 +197,9 @@ mod test { report.contains(info.image_age.to_string().as_str()), "the template must contain all of the information from the report info" ); + assert!( + report.contains(REPORT_CSS), "Report css must be included. maybe auto-formatting destroyed the braces in template.html?" + ); } #[tokio::test] @@ -227,14 +235,18 @@ mod test { reason: crate::util::ReportReason::Advert, image_got_hidden: true, image_id: Uuid::default(), - image_link: String::from("www.test.com"), + image_link: String::from("https://picsum.photos/200/300"), report_count: 1, positive_rating_count: 10, negative_rating_count: 20, - get_image_rank: 1.0, + image_rank: 1.0, report_barrier: 1, client_id: Uuid::default(), image_age: 1, + meal_id: Uuid::default(), + meal_name: "Happy Meal".into(), + report_date: Local::now().date_naive(), + other_image_urls: vec!["https://picsum.photos/200/300".into()], } } diff --git a/backend/src/layer/data/mail/template/output.css b/backend/src/layer/data/mail/template/output.css index 30594a64..101aed60 100644 --- a/backend/src/layer/data/mail/template/output.css +++ b/backend/src/layer/data/mail/template/output.css @@ -1 +1 @@ -/*! tailwindcss v3.4.4 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Roboto,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.m-0{margin:0}.m-2{margin:.5rem}.m-auto{margin:auto}.mx-auto{margin-left:auto;margin-right:auto}.mb-2{margin-bottom:.5rem}.mr-4{margin-right:1rem}.inline{display:inline}.flex{display:flex}.table{display:table}.table-cell{display:table-cell}.table-row-group{display:table-row-group}.table-row{display:table-row}.grid{display:grid}.aspect-video{aspect-ratio:16/9}.size-10{width:2.5rem;height:2.5rem}.size-5{width:1.25rem;height:1.25rem}.size-full{width:100%;height:100%}.h-40{height:10rem}.h-auto{height:auto}.h-full{height:100%}.w-1\/2{width:50%}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.max-w-2xl{max-width:42rem}.flex-none{flex:none}.table-fixed{table-layout:fixed}.border-separate{border-collapse:initial}.border-spacing-y-1{--tw-border-spacing-y:0.25rem;border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.snap-x{scroll-snap-type:x var(--tw-scroll-snap-strictness)}.snap-mandatory{--tw-scroll-snap-strictness:mandatory}.snap-center{scroll-snap-align:center}.snap-always{scroll-snap-stop:always}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-8{gap:2rem}.overflow-x-auto{overflow-x:auto}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-xl{border-radius:.75rem}.bg-dark-grey{--tw-bg-opacity:1;background-color:rgb(30 30 30/var(--tw-bg-opacity))}.bg-green{--tw-bg-opacity:1;background-color:rgb(122 172 43/var(--tw-bg-opacity))}.bg-light-grey{--tw-bg-opacity:1;background-color:rgb(51 51 51/var(--tw-bg-opacity))}.bg-red{--tw-bg-opacity:1;background-color:rgb(211 47 47/var(--tw-bg-opacity))}.bg-\[url\(\'https\:\/\/api\.mensa-ka\.de\/image\/8a5be6a6-4825-4348-a53d-a44dd11e15c0\.jpg\'\)\]{background-image:url(https://api.mensa-ka.de/image/8a5be6a6-4825-4348-a53d-a44dd11e15c0.jpg)}.bg-cover{background-size:cover}.bg-center{background-position:50%}.fill-green{fill:#7aac2b}.fill-red{fill:#d32f2f}.fill-white{fill:#fff}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-10{padding:2.5rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-8{padding-left:2rem;padding-right:2rem}.align-middle{vertical-align:middle}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-5xl{font-size:3rem;line-height:1}.text-\[0\.6em\]{font-size:.6em}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.leading-none{line-height:1}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.drop-shadow{--tw-drop-shadow:drop-shadow(0 1px 2px #0000001a) drop-shadow(0 1px 1px #0000000f)}.drop-shadow,.drop-shadow-md{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow-md{--tw-drop-shadow:drop-shadow(0 4px 3px #00000012) drop-shadow(0 2px 2px #0000000f)}.backdrop-blur-xl{--tw-backdrop-blur:blur(24px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}*{scrollbar-color:gray #0000;scrollbar-width:thick}.active\:invisible:active{visibility:hidden} \ No newline at end of file +/*! tailwindcss v3.4.4 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Roboto,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.m-0{margin:0}.m-2{margin:.5rem}.m-auto{margin:auto}.mx-auto{margin-left:auto;margin-right:auto}.mb-2{margin-bottom:.5rem}.mr-4{margin-right:1rem}.inline{display:inline}.flex{display:flex}.table{display:table}.table-cell{display:table-cell}.table-row-group{display:table-row-group}.table-row{display:table-row}.grid{display:grid}.hidden{display:none}.aspect-video{aspect-ratio:16/9}.size-10{width:2.5rem;height:2.5rem}.size-5{width:1.25rem;height:1.25rem}.size-full{width:100%;height:100%}.h-40{height:10rem}.h-auto{height:auto}.h-full{height:100%}.w-1\/2{width:50%}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.max-w-2xl{max-width:42rem}.flex-none{flex:none}.table-fixed{table-layout:fixed}.border-separate{border-collapse:initial}.border-spacing-y-1{--tw-border-spacing-y:0.25rem;border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.snap-x{scroll-snap-type:x var(--tw-scroll-snap-strictness)}.snap-mandatory{--tw-scroll-snap-strictness:mandatory}.snap-center{scroll-snap-align:center}.snap-always{scroll-snap-stop:always}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-8{gap:2rem}.overflow-x-auto{overflow-x:auto}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-xl{border-radius:.75rem}.bg-dark-grey{--tw-bg-opacity:1;background-color:rgb(30 30 30/var(--tw-bg-opacity))}.bg-green{--tw-bg-opacity:1;background-color:rgb(122 172 43/var(--tw-bg-opacity))}.bg-light-grey{--tw-bg-opacity:1;background-color:rgb(51 51 51/var(--tw-bg-opacity))}.bg-red{--tw-bg-opacity:1;background-color:rgb(211 47 47/var(--tw-bg-opacity))}.bg-\[url\(\'https\:\/\/api\.mensa-ka\.de\/image\/8a5be6a6-4825-4348-a53d-a44dd11e15c0\.jpg\'\)\]{background-image:url(https://api.mensa-ka.de/image/8a5be6a6-4825-4348-a53d-a44dd11e15c0.jpg)}.bg-\[url\(\'\{\{https\:\/\/api\.mensa-ka\.de\/image\/8a5be6a6-4825-4348-a53d-a44dd11e15c0\.jpg\}\}\'\)\]{background-image:url({{https:/api.mensa-ka.de/image/8a5be6a6-4825-4348-a53d-a44dd11e15c0.jpg}})}.bg-\[url\(\'\{\{image\}\}\'\)\]{background-image:url({{image}})}.bg-\[url\(\'\{\{image-url\}\}\'\)\]{background-image:url({{image-url}})}.bg-\[url\(\'\{\{image_url\}\}\'\)\]{background-image:url({{image_url}})}.bg-cover{background-size:cover}.bg-center{background-position:50%}.fill-green{fill:#7aac2b}.fill-red{fill:#d32f2f}.fill-white{fill:#fff}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-10{padding:2.5rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-8{padding-left:2rem;padding-right:2rem}.align-middle{vertical-align:middle}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-5xl{font-size:3rem;line-height:1}.text-\[0\.6em\]{font-size:.6em}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.leading-none{line-height:1}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.drop-shadow{--tw-drop-shadow:drop-shadow(0 1px 2px #0000001a) drop-shadow(0 1px 1px #0000000f)}.drop-shadow,.drop-shadow-md{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow-md{--tw-drop-shadow:drop-shadow(0 4px 3px #00000012) drop-shadow(0 2px 2px #0000000f)}.backdrop-blur-xl{--tw-backdrop-blur:blur(24px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}*{scrollbar-color:gray #0000;scrollbar-width:thick}.active\:invisible:active{visibility:hidden} \ No newline at end of file diff --git a/backend/src/layer/data/mail/template/template.html b/backend/src/layer/data/mail/template/template.html index 129c2db3..d22dfac0 100644 --- a/backend/src/layer/data/mail/template/template.html +++ b/backend/src/layer/data/mail/template/template.html @@ -7,7 +7,10 @@ Mensa KA Image Report - + + {{ "" }} @@ -36,8 +39,7 @@

-
+
@@ -47,7 +49,7 @@

d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM144 256a144 144 0 1 1 288 0 144 144 0 1 1 -288 0zm144-64c0 35.3-28.7 64-64 64c-7.1 0-13.9-1.2-20.3-3.3c-5.5-1.8-11.9 1.6-11.7 7.4c.3 6.9 1.3 13.8 3.2 20.7c13.7 51.2 66.4 81.6 117.6 67.9s81.6-66.4 67.9-117.6c-11.1-41.5-47.8-69.4-88.6-71.1c-5.8-.2-9.2 6.1-7.4 11.7c2.1 6.4 3.3 13.2 3.3 20.3z" />

-
Image ID: ajkhdklsajd
+
Image ID: {{ image_id }}
@@ -58,27 +60,31 @@

Meal - {meal}
fe31278d... + {{ meal_name}}
{{ meal_id }} + Reason - {reason} + {{ reason }} Hidden + {% if hidden %} + {% else %} - (a / b) + {% endif %} + ({{ report_count }} / {{ report_barrier }}) @@ -87,8 +93,10 @@

@@ -98,58 +106,45 @@

More Information

Report Date
-
{date}
+
{{ report_date }}
Reported By
-
{reporter}
+
{{ client_id }}
Number of Reports
-
{reporter}
+
{{ report_count }}
Image Age
-
{reporter}
+
{{ image_age }} days
Rating
-
u / d
+
+{{ positive_rating_count }} -{{ negative_rating_count }}
Rank
-
{rank}
+
{{ image_rank }}
+ {% if other_images %}

Other Images

- - - - - - - - - - + {% for image_url in other_image_urls %} + + {% endfor %}
+ {% endif %} diff --git a/backend/src/layer/data/mail/template/template.txt b/backend/src/layer/data/mail/template/template.txt deleted file mode 100644 index ae3871ed..00000000 --- a/backend/src/layer/data/mail/template/template.txt +++ /dev/null @@ -1,12 +0,0 @@ -The image at the url {{image_link}} -with the id {{image_id}} -was reported {{report_count}} times. -Last reported by: {{client_id}} -Reason: {{reason}} -Image automatically hidden: {{image_got_hidden}} ({{report_barrier}} reports needed) - -Additional Data: -Positive ratings: {{positive_rating_count}} -Negative ratings: {{negative_rating_count}} -Rank: {{get_image_rank}} -Image age: {{image_age}} \ No newline at end of file diff --git a/backend/src/layer/logic/api_command/command_handler.rs b/backend/src/layer/logic/api_command/command_handler.rs index 0f27a6ba..7af427f9 100644 --- a/backend/src/layer/logic/api_command/command_handler.rs +++ b/backend/src/layer/logic/api_command/command_handler.rs @@ -65,7 +65,7 @@ where fn will_be_hidden(image: &Image) -> bool { Self::days_since(image.upload_date) <= 30 - && image.report_count > Self::get_report_barrier(image.upload_date) + && image.report_count >= Self::get_report_barrier(image.upload_date) } fn days_since(date: Date) -> i64 { @@ -119,10 +119,14 @@ where report_count: info.report_count, positive_rating_count: info.upvotes, negative_rating_count: info.downvotes, - get_image_rank: info.rank, + image_rank: info.rank, report_barrier: Self::get_report_barrier(info.upload_date), client_id, image_age: Self::days_since(info.upload_date), + report_date: Local::now().date_naive(), + meal_id: info.meal_id, + meal_name: "Happy Meal".into(), // todo + other_image_urls: vec![], // todo }; self.admin_notification diff --git a/backend/src/layer/logic/api_command/mocks.rs b/backend/src/layer/logic/api_command/mocks.rs index 732ecf24..9c823fc8 100644 --- a/backend/src/layer/logic/api_command/mocks.rs +++ b/backend/src/layer/logic/api_command/mocks.rs @@ -32,6 +32,7 @@ impl CommandDataAccess for CommandDatabaseMock { downvotes: 2000, rank: 0.1, id: Uuid::default(), + meal_id: Uuid::default(), }; Ok(info) } diff --git a/backend/src/layer/trigger/api/mock.rs b/backend/src/layer/trigger/api/mock.rs index eb9de928..ac0b0e1a 100644 --- a/backend/src/layer/trigger/api/mock.rs +++ b/backend/src/layer/trigger/api/mock.rs @@ -217,6 +217,7 @@ impl RequestDataAccess for RequestDatabaseMock { report_count: 0, approved: false, upload_date: Date::default(), + meal_id: Uuid::default(), }; let d2 = Image { id: Uuid::parse_str("e4e1c2f5-881c-4e1f-8618-ca8f6f3bf1d2").expect(INVALID_UUID), @@ -226,6 +227,7 @@ impl RequestDataAccess for RequestDatabaseMock { report_count: 0, approved: false, upload_date: Date::default(), + meal_id: Uuid::default(), }; let d3 = Image { id: Uuid::parse_str("9f0a4fb0-c233-4a16-8f3a-2bbbf735ef07").expect(INVALID_UUID), @@ -235,6 +237,7 @@ impl RequestDataAccess for RequestDatabaseMock { report_count: 0, approved: false, upload_date: Date::default(), + meal_id: Uuid::default(), }; Ok(vec![d1, d2, d3]) } diff --git a/backend/src/util.rs b/backend/src/util.rs index aeb25952..3c4d31f0 100644 --- a/backend/src/util.rs +++ b/backend/src/util.rs @@ -7,6 +7,7 @@ use std::fmt::Display; use async_graphql::Enum; use image::DynamicImage; use lazy_static::lazy_static; +use serde::Serialize; /// Date type used in multiple places. pub type Date = chrono::NaiveDate; @@ -146,7 +147,7 @@ pub enum FoodType { } /// This enum lists all the predetermined reasons a image can be reported for. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Enum, sqlx::Type)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Enum, sqlx::Type, Serialize)] #[sqlx(type_name = "report_reason", rename_all = "SCREAMING_SNAKE_CASE")] pub enum ReportReason { /// This picture shows offensive content.