Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Research consent form #1164

Merged
merged 15 commits into from
Jul 26, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,21 @@ const UserNavigationControls: React.FC<React.PropsWithChildren<UserNavigationCon
{t("settings")}
</Button>
</li>

<li>
<a href={"/user-settings"}>
<Button
className={css`
color: ${baseTheme.colors.green[600]}!important;
`}
size="medium"
variant="primary"
>
{t("user-settings")}
</Button>
</a>
</li>

<li className={cx(styles)}>
<Button
className={css`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- Add down migration script here
DROP TABLE user_research_consents;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- Add up migration script here
CREATE TABLE user_research_consents (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID NOT NULL REFERENCES users,
research_consent BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
deleted_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT user_id_when_not_deleted UNIQUE NULLS NOT DISTINCT(user_id, deleted_at)
);
CREATE TRIGGER set_timestamp BEFORE
UPDATE ON user_research_consents FOR EACH ROW EXECUTE PROCEDURE trigger_set_timestamp();
COMMENT ON TABLE user_research_consents IS 'Stores information whether a student has consented to participate on research done on courses';
COMMENT ON COLUMN user_research_consents.user_id IS 'The user for which the consent belongs to';
COMMENT ON COLUMN user_research_consents.research_consent IS 'Whether or not the student has given a consent to research';
COMMENT ON COLUMN user_research_consents.created_at IS 'Timestamp when the record was created.';
COMMENT ON COLUMN user_research_consents.updated_at IS 'Timestamp when the record was last updated. The field is updated automatically by the set_timestamp trigger.';
COMMENT ON COLUMN user_research_consents.deleted_at IS 'Timestamp when the record was deleted. If null, the record is not deleted.';
82 changes: 82 additions & 0 deletions services/headless-lms/models/sqlx-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -5444,6 +5444,47 @@
},
"query": "\nSELECT user_id,\n to_jsonb(array_agg(to_jsonb(uue) - 'email' - 'user_id')) AS points_for_each_chapter\nFROM (\n SELECT ud.email,\n u.id AS user_id,\n c.chapter_number,\n COALESCE(SUM(ues.score_given), 0) AS points_for_chapter\n FROM user_exercise_states ues\n JOIN users u ON u.id = ues.user_id\n JOIN user_details ud ON ud.user_id = u.id\n JOIN exercises e ON e.id = ues.exercise_id\n JOIN chapters c on e.chapter_id = c.id\n WHERE ues.course_instance_id = $1\n AND ues.deleted_at IS NULL\n AND c.deleted_at IS NULL\n AND u.deleted_at IS NULL\n AND e.deleted_at IS NULL\n GROUP BY ud.email,\n u.id,\n c.chapter_number\n ) as uue\nGROUP BY user_id\n\n"
},
"49dafb59d714ea09cb8693d014aadcb7c60ce112d3a883dc739b551e7b7ea96e": {
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Uuid"
},
{
"name": "user_id",
"ordinal": 1,
"type_info": "Uuid"
},
{
"name": "research_consent",
"ordinal": 2,
"type_info": "Bool"
},
{
"name": "created_at",
"ordinal": 3,
"type_info": "Timestamptz"
},
{
"name": "updated_at",
"ordinal": 4,
"type_info": "Timestamptz"
},
{
"name": "deleted_at",
"ordinal": 5,
"type_info": "Timestamptz"
}
],
"nullable": [false, false, false, false, false, true],
"parameters": {
"Left": ["Uuid"]
}
},
"query": "\nSELECT *\nFROM user_research_consents\nWHERE user_id = $1\nAND deleted_at IS NULL\n "
},
"49fb36de200703e85a0cc8687d51bbfaa8d3c0303b9c09d0e5011b20926f9f24": {
"describe": {
"columns": [
Expand Down Expand Up @@ -15055,6 +15096,47 @@
},
"query": "\nSELECT id,\ncreated_at,\nupdated_at,\nexercise_task_submission_id,\ncourse_id,\nexam_id,\nexercise_id,\nexercise_task_id,\ngrading_priority,\nscore_given,\ngrading_progress as \"grading_progress: _\",\nunscaled_score_given,\nunscaled_score_maximum,\ngrading_started_at,\ngrading_completed_at,\nfeedback_json,\nfeedback_text,\ndeleted_at\nFROM exercise_task_gradings\nWHERE deleted_at IS NULL\n AND exercise_task_submission_id IN (\n SELECT id\n FROM exercise_task_submissions\n WHERE exercise_slide_submission_id = $1\n )\n"
},
"a8ea36034543bcc580413790b2bf159402da39f943469c0fe24efc63597b07e1": {
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Uuid"
},
{
"name": "user_id",
"ordinal": 1,
"type_info": "Uuid"
},
{
"name": "research_consent",
"ordinal": 2,
"type_info": "Bool"
},
{
"name": "created_at",
"ordinal": 3,
"type_info": "Timestamptz"
},
{
"name": "updated_at",
"ordinal": 4,
"type_info": "Timestamptz"
},
{
"name": "deleted_at",
"ordinal": 5,
"type_info": "Timestamptz"
}
],
"nullable": [false, false, false, false, false, true],
"parameters": {
"Left": ["Uuid", "Uuid", "Bool"]
}
},
"query": "\nINSERT INTO user_research_consents (\n id,\n user_id,\n research_consent\n)\nVALUES ($1, $2, $3) ON CONFLICT (user_id, deleted_at)\nDO UPDATE SET research_consent = $3\nRETURNING *;\n "
},
"a963c734bc1f9c48be192fa5c8537d461c9ded7412268b17b64085e4f44b174a": {
"describe": {
"columns": [
Expand Down
1 change: 1 addition & 0 deletions services/headless-lms/models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ pub mod user_details;
pub mod user_exercise_slide_states;
pub mod user_exercise_states;
pub mod user_exercise_task_states;
pub mod user_research_consents;
pub mod users;

pub mod error;
Expand Down
58 changes: 58 additions & 0 deletions services/headless-lms/models/src/user_research_consents.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use crate::prelude::*;

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "ts_rs", derive(TS))]
pub struct UserResearchConsent {
pub id: Uuid,
pub user_id: Uuid,
pub research_consent: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub deleted_at: Option<DateTime<Utc>>,
}

pub async fn upsert(
conn: &mut PgConnection,
pkey_policy: PKeyPolicy<Uuid>,
user_id: Uuid,
research_consent: bool,
) -> ModelResult<UserResearchConsent> {
let res = sqlx::query_as!(
UserResearchConsent,
"
INSERT INTO user_research_consents (
id,
user_id,
research_consent
)
VALUES ($1, $2, $3) ON CONFLICT (user_id, deleted_at)
DO UPDATE SET research_consent = $3
RETURNING *;
",
pkey_policy.into_uuid(),
user_id,
research_consent,
)
.fetch_one(conn)
.await?;
Ok(res)
}

pub async fn get_research_consent_by_user_id(
conn: &mut PgConnection,
user_id: Uuid,
) -> ModelResult<UserResearchConsent> {
let res = sqlx::query_as!(
UserResearchConsent,
"
SELECT *
FROM user_research_consents
WHERE user_id = $1
nygrenh marked this conversation as resolved.
Show resolved Hide resolved
AND deleted_at IS NULL
",
user_id,
)
.fetch_one(conn)
.await?;
Ok(res)
}
4 changes: 4 additions & 0 deletions services/headless-lms/models/src/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ pub async fn authenticate_test_user(
&& password == "teaching-and-learning-services"
{
crate::users::get_by_email(conn, "[email protected]").await?
} else if email == "[email protected]"
&& password == "student-without-research-consent"
{
crate::users::get_by_email(conn, "[email protected]").await?
} else {
return Err(ModelError::new(
ModelErrorType::InvalidRequest,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use models::{course_instance_enrollments::CourseInstanceEnrollmentsInfo, users::User};

use crate::prelude::*;
use models::{
course_instance_enrollments::CourseInstanceEnrollmentsInfo,
user_research_consents::UserResearchConsent, users::User,
};

/**
GET `/api/v0/main-frontend/users/:id`
Expand Down Expand Up @@ -48,9 +50,65 @@ pub async fn get_course_instance_enrollments_for_user(
token.authorized_ok(web::Json(res))
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "ts_rs", derive(TS))]
pub struct ConsentData {
pub consent: bool,
}

/**
POST `/api/v0/main-frontend/users/user-research-consents` - Adds a research consent for a student.
*/
#[generated_doc]
#[instrument(skip(pool))]
pub async fn post_user_consents(
payload: web::Json<ConsentData>,
user: AuthUser,
pool: web::Data<PgPool>,
) -> ControllerResult<web::Json<UserResearchConsent>> {
let mut conn = pool.acquire().await?;
let token = skip_authorize();

let res = models::user_research_consents::upsert(
&mut conn,
PKeyPolicy::Generate,
user.id,
payload.consent,
)
.await?;
token.authorized_ok(web::Json(res))
}

/**
GET `/api/v0/main-frontend/users/get-user-research-consent` - Gets users research consent.
*/
#[generated_doc]
#[instrument(skip(pool))]
pub async fn get_research_consent_by_user_id(
user: AuthUser,
pool: web::Data<PgPool>,
) -> ControllerResult<web::Json<UserResearchConsent>> {
let mut conn = pool.acquire().await?;
let token = skip_authorize();

let res =
models::user_research_consents::get_research_consent_by_user_id(&mut conn, user.id).await?;

token.authorized_ok(web::Json(res))
}

pub fn _add_routes(cfg: &mut ServiceConfig) {
cfg.route("/{user_id}", web::get().to(get_user)).route(
cfg.route(
"/get-user-research-consent",
web::get().to(get_research_consent_by_user_id),
)
.route("/{user_id}", web::get().to(get_user))
.route(
"/{user_id}/course-instance-enrollments",
web::get().to(get_course_instance_enrollments_for_user),
)
.route(
"/user-research-consents",
web::post().to(post_user_consents),
);
}
2 changes: 2 additions & 0 deletions services/headless-lms/server/src/programs/seed/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod seed_helpers;
pub mod seed_organizations;
pub mod seed_playground_examples;
pub mod seed_roles;
mod seed_user_research_consents;
pub mod seed_users;

use std::{env, process::Command, sync::Arc, time::Duration};
Expand Down Expand Up @@ -49,6 +50,7 @@ pub async fn main() -> anyhow::Result<()> {
)?;

seed_roles::seed_roles(&db_pool, &seed_users_result, &uh_cs_organization_result).await?;
seed_user_research_consents::seed_user_research_consents(&db_pool).await?;

seed_file_storage::seed_file_storage().await?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub async fn seed_organization_uh_cs(
student_user_id,
example_normal_user_ids,
teaching_and_learning_services_user_id: _,
student_without_research_consent: _,
} = seed_users_result;

let mut conn = db_pool.acquire().await?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub async fn seed_organization_uh_mathstat(
student_user_id,
example_normal_user_ids,
teaching_and_learning_services_user_id: _,
student_without_research_consent: _,
} = seed_users_result;

let mut conn = db_pool.acquire().await?;
Expand Down
Loading
Loading