Skip to content

Commit

Permalink
Allow course material viewers to view closed chapters
Browse files Browse the repository at this point in the history
  • Loading branch information
nygrenh committed Dec 4, 2023
1 parent 906e2ee commit 467a818
Show file tree
Hide file tree
Showing 18 changed files with 204 additions and 145 deletions.
2 changes: 1 addition & 1 deletion bin/cargo-clippy
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ RELATIVE_PATH=$(realpath --relative-to="$(pwd)" "$FOLDER_PATH")

run_command cd "$RELATIVE_PATH" || exit

run_command cargo clippy "$@"
run_command env SQLX_OFFLINE=1 cargo clippy "$@"
2 changes: 1 addition & 1 deletion bin/cargo-clippy-fix
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ echo -e "${BLUE}Note: please git add all your changes before running this."
echo -e "That way you can easily see what this command changed with git add -p"
echo -e "$RESET_EVERYTHING"

run_command cargo clippy --fix --allow-staged "$@"
run_command env SQLX_OFFLINE=1 cargo clippy --fix --allow-staged "$@"
2 changes: 1 addition & 1 deletion bin/cargo-doc-open
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ RELATIVE_PATH=$(realpath --relative-to="$(pwd)" "$FOLDER_PATH")

run_command cd "$RELATIVE_PATH" || exit

run_command cargo doc -p headless-lms-server --open --document-private-items "$@"
run_command env SQLX_OFFLINE=1 cargo doc -p headless-lms-server --open --document-private-items "$@"
14 changes: 0 additions & 14 deletions services/headless-lms/models/src/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,6 @@ pub async fn get_page_with_user_data_by_path(
user_id: Option<Uuid>,
course_data: &CourseContextData,
url_path: &str,
can_view_not_open_chapters: bool,
) -> ModelResult<CoursePageWithUserData> {
let page_option = get_page_by_path(conn, course_data.id, url_path).await?;

Expand All @@ -678,7 +677,6 @@ pub async fn get_page_with_user_data_by_path(
page,
false,
course_data.is_test_mode,
can_view_not_open_chapters,
)
.await;
} else {
Expand All @@ -691,7 +689,6 @@ pub async fn get_page_with_user_data_by_path(
redirected_page,
true,
course_data.is_test_mode,
can_view_not_open_chapters,
)
.await;
}
Expand Down Expand Up @@ -747,18 +744,7 @@ pub async fn get_course_page_with_user_data_from_selected_page(
page: Page,
was_redirected: bool,
is_test_mode: bool,
can_view_not_open_chapters: bool,
) -> ModelResult<CoursePageWithUserData> {
if let Some(chapter_id) = page.chapter_id {
if !can_view_not_open_chapters && !crate::chapters::is_open(conn, chapter_id).await? {
return Err(ModelError::new(
ModelErrorType::PreconditionFailed,
"Chapter is not open yet.".to_string(),
None,
));
}
}

if let Some(course_id) = page.course_id {
if let Some(user_id) = user_id {
let instance =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use models::{

use crate::{
domain::authorization::{
authorize_access_to_course_material, can_user_view_not_open_chapter, skip_authorize,
authorize_access_to_course_material, can_user_view_chapter, skip_authorize,
},
prelude::*,
};
Expand Down Expand Up @@ -82,18 +82,25 @@ async fn get_course_page_by_path(
};
let user_id = user.map(|u| u.id);
let course_data = get_nondeleted_course_id_by_slug(&mut conn, &course_slug).await?;
let page_with_user_data =
models::pages::get_page_with_user_data_by_path(&mut conn, user_id, &course_data, &path)
.await?;

let can_view_not_open_chapters =
can_user_view_not_open_chapter(&mut conn, user_id, course_data.id).await?;

let page_with_user_data = models::pages::get_page_with_user_data_by_path(
// Chapters may be closed
if !can_user_view_chapter(
&mut conn,
user_id,
&course_data,
&path,
can_view_not_open_chapters,
page_with_user_data.page.course_id,
page_with_user_data.page.chapter_id,
)
.await?;
.await?
{
return Err(ControllerError::new(
ControllerErrorType::Unauthorized,
"Chapter is not open yet.".to_string(),
None,
));
}

let token = authorize_access_to_course_material(
&mut conn,
Expand Down
32 changes: 23 additions & 9 deletions services/headless-lms/server/src/domain/authorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,17 +331,29 @@ pub async fn authorize_access_to_tmc_server(
))
}

/** Can be used to check whether user is allowed to view some course material */
pub async fn can_user_view_not_open_chapter(
/** Can be used to check whether user is allowed to view some course material. Chapters can be closed and and limited to certain people only. */
pub async fn can_user_view_chapter(
conn: &mut PgConnection,
user_id: Option<Uuid>,
course_id: Uuid,
course_id: Option<Uuid>,
chapter_id: Option<Uuid>,
) -> Result<bool, ControllerError> {
if user_id.is_none() {
return Ok(false);
if let Some(course_id) = course_id {
if let Some(chapter_id) = chapter_id {
if !models::chapters::is_open(&mut *conn, chapter_id).await? {
if user_id.is_none() {
return Ok(false);
}
// If the user has been granted access to view the material, then they can see the unopened chapters too
// This is important because sometimes teachers wish to test unopened chapters with real students
let permission =
authorize(conn, Act::ViewMaterial, user_id, Res::Course(course_id)).await;

return Ok(permission.is_ok());
}
}
}
let permission = authorize(conn, Act::Edit, user_id, Res::Course(course_id)).await;
Ok(permission.is_ok())
Ok(true)
}

/**
Expand Down Expand Up @@ -404,10 +416,10 @@ pub async fn authorize_with_fetched_list_of_roles(
// for some resources, we need to get more information from the database
match resource {
Resource::Chapter(id) => {
// if trying to View a chapter that is not open, check for permission to Teach
// if trying to View a chapter that is not open, check for permission to view the material
let action =
if matches!(action, Action::View) && !models::chapters::is_open(conn, id).await? {
Action::Teach
Action::ViewMaterial
} else {
action
};
Expand Down Expand Up @@ -869,6 +881,8 @@ pub async fn authenticate_test_user(
models::users::get_by_email(conn, "[email protected]").await?
} else if email == "[email protected]" && password == "language.teacher" {
models::users::get_by_email(conn, "[email protected]").await?
} else if email == "[email protected]" && password == "material.viewer" {
models::users::get_by_email(conn, "[email protected]").await?
} else if email == "[email protected]" && password == "user" {
models::users::get_by_email(conn, "[email protected]").await?
} else if email == "[email protected]" && password == "assistant" {
Expand Down
2 changes: 1 addition & 1 deletion services/headless-lms/server/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub use headless_lms_utils::{
file_store::FileStore, pagination::Pagination, ApplicationConfiguration,
};
pub use serde::{Deserialize, Serialize};
pub use sqlx::{Connection, FromRow, PgConnection, PgPool, Type};
pub use sqlx::{Connection, FromRow, PgConnection, PgPool, Pool, Postgres, Type};
#[cfg(feature = "ts_rs")]
pub use ts_rs::TS;
pub use uuid::Uuid;
23 changes: 18 additions & 5 deletions services/headless-lms/server/src/programs/seed/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,42 @@ pub async fn main() -> anyhow::Result<()> {
)),
)?;

seed_file_storage::seed_file_storage().await?;
// Not run parallely because waits another future that is not send.
let seed_file_storage_result = seed_file_storage::seed_file_storage().await?;

let (uh_cs_organization_result, _uh_mathstat_organization_id) = try_join!(
run_parallelly(seed_organizations::uh_cs::seed_organization_uh_cs(
db_pool.clone(),
seed_users_result.clone(),
base_url.clone(),
Arc::clone(&jwt_key),
seed_file_storage_result.clone()
)),
run_parallelly(
seed_organizations::uh_mathstat::seed_organization_uh_mathstat(
db_pool.clone(),
seed_users_result.clone(),
base_url.clone(),
Arc::clone(&jwt_key),
seed_file_storage_result.clone()
)
)
)?;

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_certificate_fonts::seed_certificate_fonts(&db_pool).await?;
try_join!(
run_parallelly(seed_roles::seed_roles(
db_pool.clone(),
seed_users_result.clone(),
uh_cs_organization_result
)),
run_parallelly(seed_user_research_consents::seed_user_research_consents(
db_pool.clone(),
seed_users_result.clone()
)),
run_parallelly(seed_certificate_fonts::seed_certificate_fonts(
db_pool.clone()
))
)?;

Ok(())
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::prelude::*;
use models::certificate_fonts::NewCertificateFont;

pub async fn seed_certificate_fonts(db_pool: &PgPool) -> anyhow::Result<()> {
pub async fn seed_certificate_fonts(db_pool: PgPool) -> anyhow::Result<()> {
let mut conn = db_pool.acquire().await?;

let file_upload_id = models::file_uploads::insert(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ const FONT_LATO_REGULAR: &[u8] = include_bytes!("./data/Lato-Regular.ttf");
const CERTIFICATE_BACKGROUND: &[u8] = include_bytes!("./data/certificate-background.svg");
const AUTHOR_IMAGE: &[u8] = include_bytes!("./data/lilo-and-stitch.jpg");

pub async fn seed_file_storage() -> anyhow::Result<()> {
#[derive(Clone)]
pub struct SeedFileStorageResult {}

pub async fn seed_file_storage() -> anyhow::Result<SeedFileStorageResult> {
info!("seeding file storage");

let file_storage = LocalFileStore::new(
Expand Down Expand Up @@ -51,5 +54,5 @@ pub async fn seed_file_storage() -> anyhow::Result<()> {
"application/octet-stream",
)
.await?;
Ok(())
Ok(SeedFileStorageResult {})
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use crate::{
create_glossary_course, seed_cs_course_material,
seed_peer_review_course_without_submissions, seed_sample_course, CommonCourseData,
},
seed_file_storage::SeedFileStorageResult,
seed_helpers::create_exam,
},
};
Expand All @@ -43,19 +44,27 @@ pub async fn seed_organization_uh_cs(
seed_users_result: SeedUsersResult,
base_url: String,
jwt_key: Arc<JwtKey>,
// Passed to this function to ensure the seed file storage has been ran before this. This function will not work is seed file storage has not been ran
seed_file_storage_result: SeedFileStorageResult,
) -> anyhow::Result<SeedOrganizationUhCsResult> {
info!("inserting organization uh-cs");
let SeedUsersResult {
admin_user_id,
teacher_user_id,
language_teacher_user_id,
material_viewer_user_id,
assistant_user_id: _,
course_or_exam_creator_user_id,
student_user_id,
example_normal_user_ids,
teaching_and_learning_services_user_id: _,
student_without_research_consent: _,
user_user_id: _,
student_1_user_id: _,
student_2_user_id: _,
student_3_user_id,
langs_user_id: _,
} = seed_users_result;
let _ = seed_file_storage_result;

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

Expand All @@ -68,14 +77,22 @@ pub async fn seed_organization_uh_cs(
)
.await?;

roles::insert(
&mut conn,
material_viewer_user_id,
UserRole::MaterialViewer,
RoleDomain::Organization(uh_cs_organization_id),
)
.await?;

info!("inserting uh-cs courses");

// Seed courses in groups to improve performance. We cannot create a new task for each course because it is causing stack overflows in headless-lms entrypoint in seemingly unrelated code.
let cs_data = CommonCourseData {
db_pool: db_pool.clone(),
organization_id: uh_cs_organization_id,
admin_user_id,
student_user_id,
student_user_id: student_3_user_id,
example_normal_user_ids: Arc::new(example_normal_user_ids.clone()),
jwt_key: Arc::clone(&jwt_key),
base_url: base_url.clone(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ use sqlx::{Pool, Postgres};

use crate::{
domain::models_requests::{self, JwtKey},
programs::seed::seed_courses::{seed_sample_course, CommonCourseData},
programs::seed::{
seed_courses::{seed_sample_course, CommonCourseData},
seed_file_storage::SeedFileStorageResult,
},
};

use super::super::seed_users::SeedUsersResult;
Expand All @@ -26,20 +29,28 @@ pub async fn seed_organization_uh_mathstat(
seed_users_result: SeedUsersResult,
base_url: String,
jwt_key: Arc<JwtKey>,
// Passed to this function to ensure the seed file storage has been ran before this. This function will not work is seed file storage has not been ran
seed_file_storage_result: SeedFileStorageResult,
) -> anyhow::Result<Uuid> {
info!("seeding organization uh-mathstat");

let SeedUsersResult {
admin_user_id,
teacher_user_id,
language_teacher_user_id: _,
material_viewer_user_id,
assistant_user_id: _,
course_or_exam_creator_user_id: _,
student_user_id,
example_normal_user_ids,
teaching_and_learning_services_user_id: _,
student_without_research_consent: _,
user_user_id: _,
student_1_user_id: _,
student_2_user_id: _,
student_3_user_id,
langs_user_id: _,
} = seed_users_result;
let _ = seed_file_storage_result;

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

Expand All @@ -51,6 +62,15 @@ pub async fn seed_organization_uh_mathstat(
"Organization for Mathematics and Statistics courses. This organization creates courses that do require prior experience in mathematics, such as integration and induction.",
)
.await?;

roles::insert(
&mut conn,
material_viewer_user_id,
UserRole::MaterialViewer,
RoleDomain::Organization(uh_mathstat_id),
)
.await?;

let new_course = NewCourse {
name: "Introduction to Statistics".to_string(),
slug: "introduction-to-statistics".to_string(),
Expand Down Expand Up @@ -125,7 +145,7 @@ pub async fn seed_organization_uh_mathstat(
db_pool: db_pool.clone(),
organization_id: uh_mathstat_id,
admin_user_id,
student_user_id,
student_user_id: student_3_user_id,
example_normal_user_ids: Arc::new(example_normal_user_ids.clone()),
jwt_key: Arc::clone(&jwt_key),
base_url,
Expand Down
6 changes: 3 additions & 3 deletions services/headless-lms/server/src/programs/seed/seed_roles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use sqlx::{Pool, Postgres};
use super::{seed_organizations::uh_cs::SeedOrganizationUhCsResult, seed_users::SeedUsersResult};

pub async fn seed_roles(
db_pool: &Pool<Postgres>,
seed_users_result: &SeedUsersResult,
uh_cs_organization_result: &SeedOrganizationUhCsResult,
db_pool: Pool<Postgres>,
seed_users_result: SeedUsersResult,
uh_cs_organization_result: SeedOrganizationUhCsResult,
) -> anyhow::Result<()> {
// roles
info!("inserting roles");
Expand Down
Loading

0 comments on commit 467a818

Please sign in to comment.