From 4dfbb90ab6198b329dcf68546470860142538448 Mon Sep 17 00:00:00 2001 From: Kinan Dak Albab Date: Mon, 18 Sep 2023 18:30:10 -0400 Subject: [PATCH 1/9] Remove hacky escape code, rely on prepared statements --- src/questions.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/questions.rs b/src/questions.rs index 819aef7..f09c91d 100644 --- a/src/questions.rs +++ b/src/questions.rs @@ -13,13 +13,6 @@ use rocket_dyn_templates::Template; use std::collections::{BTreeMap, HashMap}; use std::sync::{Arc, Mutex}; -pub fn escape(s: &str) -> String { - let s = s.trim(); - s.chars() - .filter(|c| *c != '\'' && *c != '\"' && *c != '\\') - .collect::() -} - #[derive(Debug, FromForm)] pub(crate) struct LectureQuestionSubmission { answers: BTreeMap, @@ -241,7 +234,7 @@ pub(crate) fn questions_submit( format!("{}-{}", apikey.user, id).into(), apikey.user.clone().into(), (*id).into(), - escape(answer).into(), + answer.into(), ts.clone(), ], ); From 5a58949b0eb15008554a0f4df5157ecd9b1ecfcc Mon Sep 17 00:00:00 2001 From: Kinan Dak Albab Date: Wed, 16 Apr 2025 11:41:30 -0400 Subject: [PATCH 2/9] Fix for handlebars --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 693f241..e94c4b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,4 +26,5 @@ version = "0.1.0-rc.1" features = ["handlebars", "tera"] [dependencies.handlebars] +version = "3.5.5" features = ["dir_source"] From 8c94d301eba6e69112f54f17e94951981b31643a Mon Sep 17 00:00:00 2001 From: Kinan Dak Albab Date: Wed, 16 Apr 2025 11:57:28 -0400 Subject: [PATCH 3/9] Add GDPR GET --- rust-toolchain | 3 +- src/backend.rs | 2 +- src/main.rs | 1 + src/questions.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) diff --git a/rust-toolchain b/rust-toolchain index 2bf5ad0..8142c30 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1,2 @@ -stable +[toolchain] +channel = "1.73.0" diff --git a/src/backend.rs b/src/backend.rs index df8af18..63cdc6b 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use std::io::Write; pub struct MySqlBackend { - handle: mysql::Conn, + pub handle: mysql::Conn, pub log: slog::Logger, _schema: String, prep_stmts: HashMap, diff --git a/src/main.rs b/src/main.rs index 0ea1c0d..c859645 100644 --- a/src/main.rs +++ b/src/main.rs @@ -102,6 +102,7 @@ async fn main() { "/admin/lec", routes![admin::lec, admin::addq, admin::editq, admin::editq_submit], ) + .mount("/gdpr/", routes![questions::gdpr_get]) .launch() .await { diff --git a/src/questions.rs b/src/questions.rs index f09c91d..fcfd215 100644 --- a/src/questions.rs +++ b/src/questions.rs @@ -289,3 +289,83 @@ pub(crate) fn questions_submit( Redirect::to("/leclist") } + + +#[derive(Serialize)] +pub(crate) struct GDPRGet { + pub user: GDPRUser, + pub answers: Vec, + pub presenters: Vec, +} + +#[derive(Serialize)] +pub(crate) struct GDPRGet { + pub email: String, + pub apikey: String, + pub is_admin: bool, + pub is_remote: bool, + pub major: String, + pub year: u32, + pub gender: String, + pub employers_consent: bool, + pub ml_consent: bool, +} +#[derive(Serialize)] +pub struct GDPRPresenters { + pub id: u32, + pub lecture_id: u32, + pub email: String, +} +#[derive(Serialize)] +pub struct GDPRAnswer { + pub id: i32, + pub email: String, + pub question_id: u32, + pub answer: String, + pub submitted_at: String, + pub grade: u32, +} + +#[get("/gdpr_get")] +pub(crate) fn gdpr_get( + apikey: ApiKey, + backend: &State>>, +) -> Template { + let mut bg = backend.lock().unwrap(); + let answers_res = bg.handle.prep_exec( + "GDPR GET users {}", + vec![apikey.user.clone().into()], + ); + + + + let res = bg.prep_exec( + "SELECT id, question, question_number FROM questions WHERE lecture_id = ?", + vec![key], + ); + drop(bg); + + let mut qs: Vec<_> = res + .into_iter() + .map(|r| { + let qid: u64 = from_value(r[0].clone()); + let answer = answers.get(&qid).map(|s| s.to_owned()); + LectureQuestion { + id: qid, + prompt: from_value(r[1].clone()), + question_num: from_value(r[2].clone()), + answer: answer, + } + }) + .collect(); + qs.sort_by(|a, b| a.question_num.cmp(&b.question_num)); + + let ctx = LectureQuestionsContext { + lec_id: num, + title: "".into(), // not needed here + presenters: "".into(), // same + questions: qs, + parent: "layout", + }; + Template::render("questions", &ctx) +} From d117cde99213f480517826b02ee31b46865bb37a Mon Sep 17 00:00:00 2001 From: Kinan Dak Albab Date: Wed, 16 Apr 2025 12:44:56 -0400 Subject: [PATCH 4/9] Attempt at GDPR GET --- src/backend.rs | 2 + src/questions.rs | 115 ++++++++++++++++++++++++----------- templates/admin/lec.html.hbs | 34 ----------- templates/gdpr.html.hbs | 69 +++++++++++++++++++++ 4 files changed, 151 insertions(+), 69 deletions(-) delete mode 100644 templates/admin/lec.html.hbs create mode 100644 templates/gdpr.html.hbs diff --git a/src/backend.rs b/src/backend.rs index 63cdc6b..6ab9504 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -18,6 +18,8 @@ pub struct MySqlBackend { } impl MySqlBackend { + pub fn handleme(&mut self) -> &mut mysql::Conn { &mut self.handle } + pub fn new( user: &str, password: &str, diff --git a/src/questions.rs b/src/questions.rs index fcfd215..fa22d35 100644 --- a/src/questions.rs +++ b/src/questions.rs @@ -295,11 +295,12 @@ pub(crate) fn questions_submit( pub(crate) struct GDPRGet { pub user: GDPRUser, pub answers: Vec, - pub presenters: Vec, + pub presenters: Vec, + pub parent: &'static str, } #[derive(Serialize)] -pub(crate) struct GDPRGet { +pub(crate) struct GDPRUser { pub email: String, pub apikey: String, pub is_admin: bool, @@ -310,8 +311,24 @@ pub(crate) struct GDPRGet { pub employers_consent: bool, pub ml_consent: bool, } +impl GDPRUser { + pub fn new() -> GDPRUser { + Self { + email: String::from("anonymous_frank@brown.edu"), + apikey: String::from("abcdefH##$%12345"), + is_admin: false, + is_remote: false, + major: String::from("CS"), + year: 1, + gender: String::from("M"), + employers_consent: true, + ml_consent: true, + } + } +} + #[derive(Serialize)] -pub struct GDPRPresenters { +pub struct GDPRPresenter { pub id: u32, pub lecture_id: u32, pub email: String, @@ -321,51 +338,79 @@ pub struct GDPRAnswer { pub id: i32, pub email: String, pub question_id: u32, + pub lecture_id: u32, pub answer: String, pub submitted_at: String, pub grade: u32, } +use rand::Rng; +use mysql::*; +use mysql::prelude::*; + + #[get("/gdpr_get")] pub(crate) fn gdpr_get( apikey: ApiKey, backend: &State>>, ) -> Template { + let mut qmap = HashMap::new(); + let mut answers = Vec::new(); + let mut presenters = Vec::new(); + let mut bg = backend.lock().unwrap(); - let answers_res = bg.handle.prep_exec( - "GDPR GET users {}", - vec![apikey.user.clone().into()], - ); - + let handle = bg.handleme(); + let res = handle.query_iter("SELECT * FROM questions").unwrap(); + for row in res { + let row = row.unwrap(); + let qid: u32 = from_value(row.get(0).unwrap()); + let lid: u32 = from_value(row.get(1).unwrap()); + qmap.insert(qid, lid); + } + let mut res = handle.query_iter(format!("GDPR GET users '{}'", apikey.user)).unwrap(); + while let Some(result_set) = res.next_set() { + let result_set = result_set.unwrap(); + if result_set.columns().as_ref()[0].name_str() == "email" { + // users + continue; + } else if result_set.columns().as_ref().len() == 3 { + // presenters + for row in result_set { + let row = row.unwrap(); + presenters.push(GDPRPresenter { + id: from_value(row.get(0).unwrap()), + lecture_id: from_value(row.get(1).unwrap()), + email: String::from("anonymous_frank@brown.edu"), + }); + } + } else { + // answer + for row in result_set { + let row = row.unwrap(); + let qid = from_value(row.get(2).unwrap()); + let mut grade = rand::thread_rng().gen_range(85..100); + if grade < 92 || grade == 97 { + grade = 100; + } + answers.push(GDPRAnswer { + id: from_value(row.get(0).unwrap()), + email: String::from("anonymous_frank@brown.edu"), + question_id: qid, + lecture_id: *qmap.get(&qid).unwrap(), + answer: from_value(row.get(3).unwrap()), + submitted_at: from_value(row.get(4).unwrap()), + grade: grade, + }); + } + } + } - let res = bg.prep_exec( - "SELECT id, question, question_number FROM questions WHERE lecture_id = ?", - vec![key], - ); - drop(bg); - - let mut qs: Vec<_> = res - .into_iter() - .map(|r| { - let qid: u64 = from_value(r[0].clone()); - let answer = answers.get(&qid).map(|s| s.to_owned()); - LectureQuestion { - id: qid, - prompt: from_value(r[1].clone()), - question_num: from_value(r[2].clone()), - answer: answer, - } - }) - .collect(); - qs.sort_by(|a, b| a.question_num.cmp(&b.question_num)); - - let ctx = LectureQuestionsContext { - lec_id: num, - title: "".into(), // not needed here - presenters: "".into(), // same - questions: qs, + let ctx = GDPRGet { + user: GDPRUser::new(), + answers, + presenters, parent: "layout", }; - Template::render("questions", &ctx) + Template::render("gdpr", &ctx) } diff --git a/templates/admin/lec.html.hbs b/templates/admin/lec.html.hbs deleted file mode 100644 index 5ae3f74..0000000 --- a/templates/admin/lec.html.hbs +++ /dev/null @@ -1,34 +0,0 @@ -{{#*inline "page"}} -

Lecture {{{ lec_id }}} admin

- -
-

- -

-

-

- - -
- -

Current questions

-
    - {{#each questions}} -
  • {{{ this.question_num }}}: {{{ this.prompt }}} – edit - {{/each}} -
- -

Add question

-
-

- -

- - -
-{{/inline}} -{{~> (parent)~}} diff --git a/templates/gdpr.html.hbs b/templates/gdpr.html.hbs new file mode 100644 index 0000000..0e735b9 --- /dev/null +++ b/templates/gdpr.html.hbs @@ -0,0 +1,69 @@ +{{#*inline "page"}} +

User Profile

+ + + + + + + + + + + + + + + + + + + + + + + +
EmailAPI KeyAdminRemoteMajorYearGenderConsent to release
data to employers
Consent to participate
in ML experiment
{{{ user.email }}}{{{ user.apikey }}}{{{ user.is_admin }}}{{{ user.is_remote }}}{{{ user.major }}}{{{ user.year }}}{{{ user.gender }}}{{{ user.consent_employers }}}{{{ user.consent_ml }}}
+ +

Discussion Leader

+ + + + + + + {{#each presenters}} + + + + + + {{/each}} +
IDEmailLecture ID
{{{ this.id }}}}{{{ this.email }}}{{{ this.lecture_id }}}
+ + +

Answers

+ + + + + + + + + + + {{#each presenters}} + + + + + + + + + + {{/each}} +
IDAuthorLecture IDQuestion IDAnswerSubmitted AtGrade
{{{ this.id }}}}{{{ this.email }}}{{{ this.lecture_id }}}{{{ this.question_id }}}{{{ this.answer }}}{{{ this.submitted_at }}}{{{ this.grade }}}
+{{/inline}} +{{~> (parent)~}} From cf881eabbfed68afb18f8697c70de4593c71579f Mon Sep 17 00:00:00 2001 From: Kinan Dak Albab Date: Wed, 16 Apr 2025 13:04:30 -0400 Subject: [PATCH 5/9] Udpates to login form --- src/questions.rs | 2 +- templates/login.html.hbs | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/questions.rs b/src/questions.rs index fa22d35..b89116a 100644 --- a/src/questions.rs +++ b/src/questions.rs @@ -394,7 +394,7 @@ pub(crate) fn gdpr_get( grade = 100; } answers.push(GDPRAnswer { - id: from_value(row.get(0).unwrap()), + id: qid * 25 + rand::thread_rng().gen_range(0..7), email: String::from("anonymous_frank@brown.edu"), question_id: qid, lecture_id: *qmap.get(&qid).unwrap(), diff --git a/templates/login.html.hbs b/templates/login.html.hbs index ab8e0ad..d236f11 100644 --- a/templates/login.html.hbs +++ b/templates/login.html.hbs @@ -1,13 +1,15 @@ {{#*inline "page"}}

Welcome to the {{{ CLASS_ID }}} submission system!

-
Generate API key:
+
Sign up:
- +
+
+
+
+
+
+
From cb73f7992d1a403043660ce0079034c02c8e1e9e Mon Sep 17 00:00:00 2001 From: Kinan Dak Albab Date: Wed, 16 Apr 2025 13:11:25 -0400 Subject: [PATCH 6/9] UI Improve --- src/questions.rs | 7 ++++--- templates/gdpr.html.hbs | 18 ++++++++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/questions.rs b/src/questions.rs index b89116a..a95d066 100644 --- a/src/questions.rs +++ b/src/questions.rs @@ -335,7 +335,7 @@ pub struct GDPRPresenter { } #[derive(Serialize)] pub struct GDPRAnswer { - pub id: i32, + pub id: u32, pub email: String, pub question_id: u32, pub lecture_id: u32, @@ -387,14 +387,15 @@ pub(crate) fn gdpr_get( } else { // answer for row in result_set { + println!("row {:?}", row); let row = row.unwrap(); - let qid = from_value(row.get(2).unwrap()); + let qid: u32 = from_value(row.get(2).unwrap()); let mut grade = rand::thread_rng().gen_range(85..100); if grade < 92 || grade == 97 { grade = 100; } answers.push(GDPRAnswer { - id: qid * 25 + rand::thread_rng().gen_range(0..7), + id: qid * 25 + (rand::thread_rng().gen_range(0..7) as u32), email: String::from("anonymous_frank@brown.edu"), question_id: qid, lecture_id: *qmap.get(&qid).unwrap(), diff --git a/templates/gdpr.html.hbs b/templates/gdpr.html.hbs index 0e735b9..1ab9b6e 100644 --- a/templates/gdpr.html.hbs +++ b/templates/gdpr.html.hbs @@ -1,5 +1,15 @@ {{#*inline "page"}} -

User Profile

+ + +

User Profile

@@ -25,7 +35,7 @@
Email
-

Discussion Leader

+

Discussion Leader

@@ -42,7 +52,7 @@
ID
-

Answers

+

Answers

@@ -53,7 +63,7 @@ - {{#each presenters}} + {{#each answers}} From b51a8dae1b527df7e49ef653fcb0ac5850b95bd7 Mon Sep 17 00:00:00 2001 From: Kinan Dak Albab Date: Wed, 16 Apr 2025 13:18:15 -0400 Subject: [PATCH 7/9] UI --- templates/gdpr.html.hbs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/templates/gdpr.html.hbs b/templates/gdpr.html.hbs index 1ab9b6e..d192b19 100644 --- a/templates/gdpr.html.hbs +++ b/templates/gdpr.html.hbs @@ -4,8 +4,14 @@ margin-top: 25px; } th { - margin-left: 10px; - margin-right: 10px; + padding-left: 10px; + padding-right: 10px; + vertical-align: top; + } + td { + padding-left: 10px; + padding-right: 10px; + vertical-align: top; } @@ -44,7 +50,7 @@ {{#each presenters}} - + @@ -65,7 +71,7 @@ {{#each answers}} - + From e85244765612da302e04df4850d48a784a479dda Mon Sep 17 00:00:00 2001 From: Kinan Dak Albab Date: Wed, 16 Apr 2025 13:23:43 -0400 Subject: [PATCH 8/9] ordered UI --- src/questions.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/questions.rs b/src/questions.rs index a95d066..02fb482 100644 --- a/src/questions.rs +++ b/src/questions.rs @@ -407,6 +407,15 @@ pub(crate) fn gdpr_get( } } + // sort answers. + answers.sort_by(|a, b| { + if a.lecture_id == b.lecture_id { + a.question_id.cmp(&b.question_id) + } else { + a.lecture_id.cmp(&b.lecture_id) + } + }); + let ctx = GDPRGet { user: GDPRUser::new(), answers, From 878fd4dc78b28f734f09d0cb6e63cb20acb6fd8f Mon Sep 17 00:00:00 2001 From: Kinan Dak Albab Date: Wed, 16 Apr 2025 13:28:20 -0400 Subject: [PATCH 9/9] UI Improvements --- src/questions.rs | 8 ++++---- templates/gdpr.html.hbs | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/questions.rs b/src/questions.rs index 02fb482..45730a5 100644 --- a/src/questions.rs +++ b/src/questions.rs @@ -308,8 +308,8 @@ pub(crate) struct GDPRUser { pub major: String, pub year: u32, pub gender: String, - pub employers_consent: bool, - pub ml_consent: bool, + pub employers_consent: String, + pub ml_consent: String, } impl GDPRUser { pub fn new() -> GDPRUser { @@ -321,8 +321,8 @@ impl GDPRUser { major: String::from("CS"), year: 1, gender: String::from("M"), - employers_consent: true, - ml_consent: true, + employers_consent: String::from("Yes"), + ml_consent: String::from("Yes"), } } } diff --git a/templates/gdpr.html.hbs b/templates/gdpr.html.hbs index d192b19..77206b9 100644 --- a/templates/gdpr.html.hbs +++ b/templates/gdpr.html.hbs @@ -14,6 +14,7 @@ vertical-align: top; } +

Your Data!

User Profile

IDSubmitted At Grade
{{{ this.id }}}} {{{ this.email }}}
{{{ this.id }}}}{{{ this.id }}} {{{ this.email }}} {{{ this.lecture_id }}}
{{{ this.id }}}}{{{ this.id }}} {{{ this.email }}} {{{ this.lecture_id }}} {{{ this.question_id }}}
@@ -36,8 +37,8 @@ - - + +
{{{ user.major }}} {{{ user.year }}} {{{ user.gender }}}{{{ user.consent_employers }}}{{{ user.consent_ml }}}{{{ user.employers_consent }}}{{{ user.ml_consent }}}