use anyhow::anyhow; use axum::extract::{Path, Query, State}; use axum::Json; use diesel::PgConnection; use htycommons::common::{ current_local_datetime, get_some_from_query_params, string_to_date, string_to_datetime, HtyErr, HtyErrCode, HtyResponse, }; use htycommons::db::*; use htycommons::jwt::jwt_decode_token; use htycommons::uuid; use htycommons::web::{ wrap_json_anyhow_err, wrap_json_ok_resp, AuthorizationHeader, HtyHostHeader, HtySudoerTokenHeader, }; use htykc_models::models::{ Clazz, ClazzAuditLog, ClazzRepeat, NewClazzAuditLog, ReqClazz, ReqClazzRepeat, ReqClazzWithRepeat, }; use std::collections::HashMap; use std::ops::DerefMut; use std::sync::Arc; use tracing::{debug, error}; fn current_org_id_from_auth(token: &AuthorizationHeader) -> Option { jwt_decode_token(&(*token).clone()) .ok() .and_then(|decoded| decoded.current_org_id) } fn hty_id_from_auth(token: &AuthorizationHeader) -> Option { jwt_decode_token(&(*token).clone()) .ok() .and_then(|decoded| decoded.hty_id) } fn write_clazz_audit_log( db_pool: &Arc, clazz_id: &str, action: &str, operator_hty_id: &str, changes: Option, org_id: Option, ) { if let Ok(conn) = fetch_db_conn(db_pool) { let _ = ClazzAuditLog::insert( &NewClazzAuditLog { id: uuid(), clazz_id: clazz_id.to_string(), action: action.to_string(), operator_hty_id: operator_hty_id.to_string(), operator_name: None, changes, created_at: current_local_datetime(), org_id, }, extract_conn(conn).deref_mut(), ); } } pub async fn find_all_non_repeatable_within_date_range( sudoer: HtySudoerTokenHeader, host: HtyHostHeader, auth: AuthorizationHeader, State(db_pool): State>, Query(params): Query>, ) -> Json>> { debug!( "find_all_non_repeatable_within_date_range -> starts, params: {:?}", params ); match raw_find_all_non_repeatable_within_date_range( auth, sudoer, host, db_pool, ¶ms, ) { Ok(ok) => wrap_json_ok_resp(ok), Err(e) => { error!( "find_all_non_repeatable_within_date_range -> failed to find kecheng, e: {}", e ); wrap_json_anyhow_err(e) } } } fn raw_find_all_non_repeatable_within_date_range( token: AuthorizationHeader, _sudoer: HtySudoerTokenHeader, _host: HtyHostHeader, db_pool: Arc, params: &HashMap, ) -> anyhow::Result> { let start_from = string_to_date(&get_some_from_query_params::("start_from", ¶ms))?; let end_by = string_to_date(&get_some_from_query_params::("end_by", ¶ms))?; debug!( "raw_find_all_non_repeatable_within_date_range -> start_from: {:?}", start_from ); debug!( "raw_find_all_non_repeatable_within_date_range -> end_by: {:?}", end_by ); if start_from.is_none() || end_by.is_none() { return Err(anyhow!(HtyErr { code: HtyErrCode::WebErr, reason: Some("start_from or end_by is none".into()), })); } let some_kechengs = Clazz::find_all_non_repeatable_by_date_range( start_from .as_ref() .ok_or_else(|| anyhow!("start_from is required"))?, end_by .as_ref() .ok_or_else(|| anyhow!("end_by is required"))?, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; debug!( "raw_find_all_non_repeatable_within_date_range -> some_kechengs: {:?}", some_kechengs ); if let Some(kechengs) = some_kechengs { let current_org_id = current_org_id_from_auth(&token); let res: Vec = kechengs .iter() .filter(|clazz| { if let Some(org_id) = ¤t_org_id { clazz.org_id.as_ref() == Some(org_id) } else { true } }) .map(|kc| kc.to_req()) .collect(); Ok(res) } else { Ok(vec![]) } } pub async fn find_all_non_repeatable_within_date_range_by_hty_id( sudoer: HtySudoerTokenHeader, host: HtyHostHeader, auth: AuthorizationHeader, State(db_pool): State>, Query(params): Query>, ) -> Json>> { debug!( "find_all_non_repeatable_within_date_range_by_hty_id -> starts, params: {:?}", params ); match raw_find_all_non_repeatable_within_date_range_by_hty_id( auth, sudoer, host, db_pool, ¶ms, ) { Ok(ok) => wrap_json_ok_resp(ok), Err(e) => { error!( "find_all_non_repeatable_within_date_range_by_hty_id -> failed to find kecheng, e: {}", e ); wrap_json_anyhow_err(e) } } } fn raw_find_all_non_repeatable_within_date_range_by_hty_id( token: AuthorizationHeader, _sudoer: HtySudoerTokenHeader, _host: HtyHostHeader, db_pool: Arc, params: &HashMap, ) -> anyhow::Result> { let start_from = string_to_date(&get_some_from_query_params::("start_from", ¶ms))?; let end_by = string_to_date(&get_some_from_query_params::("end_by", ¶ms))?; debug!( "raw_find_all_non_repeatable_within_date_range_by_hty_id -> start_from: {:?}", start_from ); debug!( "raw_find_all_non_repeatable_within_date_range_by_hty_id -> end_by: {:?}", end_by ); let id_user = get_some_from_query_params::("hty_id", ¶ms); if start_from.is_none() || end_by.is_none() { return Err(anyhow!(HtyErr { code: HtyErrCode::WebErr, reason: Some("start_from or end_by is none".into()), })); } if id_user.is_none() { return Err(anyhow!(HtyErr { code: HtyErrCode::WebErr, reason: Some("hty_id is none".into()), })); } let some_kechengs = Clazz::find_all_non_repeatable_by_date_range_and_user_id( id_user .as_ref() .ok_or_else(|| anyhow!("id_user is required"))?, start_from .as_ref() .ok_or_else(|| anyhow!("start_from is required"))?, end_by .as_ref() .ok_or_else(|| anyhow!("end_by is required"))?, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; debug!( "raw_find_all_non_repeatable_within_date_range_by_hty_id -> {:?}", some_kechengs ); if let Some(kechengs) = some_kechengs { let current_org_id = current_org_id_from_auth(&token); let res: Vec = kechengs .iter() .filter(|clazz| { if let Some(org_id) = ¤t_org_id { clazz.org_id.as_ref() == Some(org_id) } else { true } }) .map(|kc| kc.to_req()) .collect(); Ok(res) } else { Ok(vec![]) } } pub async fn find_all_repeatable_within_date_range( sudoer: HtySudoerTokenHeader, host: HtyHostHeader, auth: AuthorizationHeader, State(db_pool): State>, Query(params): Query>, ) -> Json>> { debug!( "find_all_repeatable_within_date_range -> starts, params: {:?}", params ); match raw_find_all_repeatable_within_date_range(auth, sudoer, host, db_pool, ¶ms) { Ok(kechengs) => wrap_json_ok_resp(kechengs), Err(e) => { error!( "find_all_repeatable_within_date_range -> failed to find kecheng, e: {}", e ); wrap_json_anyhow_err(e) } } } fn raw_find_all_repeatable_within_date_range( token: AuthorizationHeader, _sudoer: HtySudoerTokenHeader, _host: HtyHostHeader, db_pool: Arc, params: &HashMap, ) -> anyhow::Result> { let start_from = string_to_date(&get_some_from_query_params::("start_from", ¶ms))?; let end_by = string_to_date(&get_some_from_query_params::("end_by", ¶ms))?; debug!( "raw_find_all_repeatable_within_date_range -> start_from: {:?}", start_from ); debug!( "raw_find_all_repeatable_within_date_range -> end_by: {:?}", end_by ); if start_from.is_none() || end_by.is_none() { return Err(anyhow!(HtyErr { code: HtyErrCode::WebErr, reason: Some("start_from or end_by is none".into()), })); } let some_kechengs = Clazz::find_all_repeatable_by_date_range( start_from .as_ref() .ok_or_else(|| anyhow!("start_from is required"))?, end_by .as_ref() .ok_or_else(|| anyhow!("end_by is required"))?, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; debug!( "raw_find_all_repeatable_within_date_range -> some_kechengs: {:?}", some_kechengs ); if let Some(kechengs) = some_kechengs { let current_org_id = current_org_id_from_auth(&token); let filtered_kechengs: Vec = kechengs .into_iter() .filter(|clazz| { if let Some(org_id) = ¤t_org_id { clazz.clz_org_id.as_ref() == Some(org_id) } else { true } }) .collect(); debug!( "raw_find_all_repeatable_within_date_range -> kechengs.len(): {:?}", filtered_kechengs.len() ); Ok(filtered_kechengs) } else { Ok(vec![]) } } pub async fn find_all_repeatable_within_date_range_by_hty_id( sudoer: HtySudoerTokenHeader, host: HtyHostHeader, auth: AuthorizationHeader, State(db_pool): State>, Query(params): Query>, ) -> Json>> { debug!( "find_all_repeatable_within_date_range_by_hty_id -> starts, params: {:?}", params ); match raw_find_all_repeatable_within_date_range_by_hty_id( auth, sudoer, host, db_pool, ¶ms, ) { Ok(ok) => wrap_json_ok_resp(ok), Err(e) => { error!( "find_all_repeatable_within_date_range_by_hty_id -> failed to find kecheng, e: {}", e ); wrap_json_anyhow_err(e) } } } /* * 找到前端给定时间范围内的所有kecheng数据 * 本周的重复课程如果存在,返回重复课程对应的sudoer课程 * 1. hty_user_id / 哪个用的课程 * 2. start_from / end_by * 3. 实体kecheng数据 * 4. 需要显示在本周的重复课程的sudoer课程 (kecheng_repeat_status == `OPEN` && is_repeat == True && repeat_end 在本周范围内 or 为空) * htykc_moicen=# select kecheng.is_repeat from kecheng join kecheng_repeat on kecheng.id = kecheng_repeat.clazz_id where kecheng.is_repeat is not null; * * a. 返回的kecheng里,如果`id == root_id`那么此条数据就是sudoer数据. * b. 如果此条kecheng的`start_from`和`end_by`落在本周,则说明是本周数据 */ fn raw_find_all_repeatable_within_date_range_by_hty_id( token: AuthorizationHeader, _sudoer: HtySudoerTokenHeader, _host: HtyHostHeader, db_pool: Arc, params: &HashMap, ) -> anyhow::Result> { let start_from = string_to_date(&get_some_from_query_params::("start_from", ¶ms))?; let end_by = string_to_date(&get_some_from_query_params::("end_by", ¶ms))?; debug!( "raw_find_all_repeatable_within_date_range_by_hty_id -> start_from: {:?}", start_from ); debug!( "raw_find_all_repeatable_within_date_range_by_hty_id -> end_by: {:?}", end_by ); let id_user = get_some_from_query_params::("hty_id", ¶ms); if start_from.is_none() || end_by.is_none() { return Err(anyhow!(HtyErr { code: HtyErrCode::WebErr, reason: Some("start_from or end_by is none".into()), })); } if id_user.is_none() { return Err(anyhow!(HtyErr { code: HtyErrCode::WebErr, reason: Some("hty_id is none".into()), })); } let some_kechengs = Clazz::find_all_repeatable_by_date_range_and_user_id( id_user .as_ref() .ok_or_else(|| anyhow!("id_user is required"))?, start_from .as_ref() .ok_or_else(|| anyhow!("start_from is required"))?, end_by .as_ref() .ok_or_else(|| anyhow!("end_by is required"))?, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; debug!( "raw_find_all_repeatable_within_date_range_by_hty_id -> some_kechengs: {:?}", some_kechengs ); if let Some(kechengs) = some_kechengs { let current_org_id = current_org_id_from_auth(&token); let filtered_kechengs: Vec = kechengs .into_iter() .filter(|clazz| { if let Some(org_id) = ¤t_org_id { clazz.clz_org_id.as_ref() == Some(org_id) } else { true } }) .collect(); debug!( "raw_find_all_repeatable_within_date_range_by_hty_id -> kechengs.len(): {:?}", filtered_kechengs.len() ); Ok(filtered_kechengs) } else { Ok(vec![]) } } pub async fn find_by_daka_ids( sudoer: HtySudoerTokenHeader, host: HtyHostHeader, auth: AuthorizationHeader, State(db_pool): State>, daka_ids: Json>, ) -> Json>> { debug!("find_by_daka_ids -> starts: {:?}", daka_ids); match raw_find_by_daka_ids(auth, sudoer, host, db_pool, &daka_ids) { Ok(ok) => wrap_json_ok_resp(ok), Err(e) => { error!( "find_by_daka_ids -> failed to find kecheng, e: {}", e ); wrap_json_anyhow_err(e) } } } fn raw_find_by_daka_ids( _auth: AuthorizationHeader, _sudoer: HtySudoerTokenHeader, _host: HtyHostHeader, db_pool: Arc, daka_ids: &Vec, ) -> anyhow::Result> { let db_kechengs = Clazz::find_by_daka_ids( daka_ids, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; debug!( "raw_find_by_daka_ids -> db_kechengs: {:?}", db_kechengs ); let req_kechengs = db_kechengs.into_iter().map(|item| item.to_req()).collect(); debug!( "raw_find_by_daka_ids -> req_kechengs: {:?}", req_kechengs ); Ok(req_kechengs) } pub async fn find_clazz_repeat_by_id( sudoer: HtySudoerTokenHeader, host: HtyHostHeader, auth: AuthorizationHeader, State(db_pool): State>, Path(id): Path, ) -> Json> { debug!("find_clazz_repeat_by_id -> starts: {:?}", id); match raw_find_clazz_repeat_by_id(auth, sudoer, host, db_pool, &id) { Ok(ok) => wrap_json_ok_resp(ok), Err(e) => { error!( "find_clazz_repeat_by_id -> failed to find kecheng repeat, e: {}", e ); wrap_json_anyhow_err(e) } } } fn raw_find_clazz_repeat_by_id( _auth: AuthorizationHeader, _sudoer: HtySudoerTokenHeader, _host: HtyHostHeader, db_pool: Arc, kecheng_repeat_id: &String, ) -> anyhow::Result { let res = ClazzRepeat::find_by_id( kecheng_repeat_id, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; if let Some(kecheng_repeat) = res { return Ok(kecheng_repeat.to_req()); } else { return Err(anyhow!(HtyErr { code: HtyErrCode::NullErr, reason: Some(format!( "raw_find_clazz_repeat_by_id -> failed to find record according to id :{:?}.", kecheng_repeat_id )), })); } } pub async fn find_clazz_by_hty_id( sudoer: HtySudoerTokenHeader, host: HtyHostHeader, auth: AuthorizationHeader, State(db_pool): State>, Query(params): Query>, ) -> Json>> { debug!("find_clazz_by_hty_id -> starts: {:?}", params); match raw_find_clazz_by_hty_id(auth, sudoer, host, db_pool, ¶ms) { Ok(ok) => wrap_json_ok_resp(ok), Err(e) => { error!( "find_clazz_by_hty_id -> failed to find kechengs, e: {}", e ); wrap_json_anyhow_err(e) } } } fn raw_find_clazz_by_hty_id( auth: AuthorizationHeader, _sudoer: HtySudoerTokenHeader, _host: HtyHostHeader, db_pool: Arc, params: &HashMap, ) -> anyhow::Result> { let start_date = string_to_datetime(&get_some_from_query_params::("start_date", ¶ms))?; let end_date = string_to_datetime(&get_some_from_query_params::("end_date", ¶ms))?; debug!( "raw_find_clazz_by_hty_id -> start_date: {:?}", start_date ); debug!("raw_find_clazz_by_hty_id -> end_date: {:?}", end_date); if start_date.is_none() || end_date.is_none() { return Err(anyhow!(HtyErr { code: HtyErrCode::WebErr, reason: Some("start_date or end_date is none".into()), })); } let id_user = get_some_from_query_params::("hty_id", ¶ms); if id_user.is_none() { return Err(anyhow!(HtyErr { code: HtyErrCode::WebErr, reason: Some("hty_id is none".into()), })); } let current_org_id = current_org_id_from_auth(&auth); let kechengs = Clazz::find_by_user_id( id_user .as_ref() .ok_or_else(|| anyhow!("id_user is required"))?, &start_date, &end_date, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; let res = kechengs .into_iter() .filter(|clazz| { if let Some(org_id) = ¤t_org_id { clazz.org_id.as_ref() == Some(org_id) } else { true } }) .map(|item| item.to_req()) .collect(); debug!("raw_find_clazz_by_hty_id -> {:?}", res); Ok(res) } pub async fn update_clazz( sudoer: HtySudoerTokenHeader, host: HtyHostHeader, auth: AuthorizationHeader, State(db_pool): State>, Json(in_kecheng): Json, ) -> Json> { debug!("update_clazz -> in_kecheng: {:?}", in_kecheng); match raw_update_clazz(auth, sudoer, host, db_pool, &in_kecheng).await { Ok(ok) => wrap_json_ok_resp(ok), Err(e) => { error!("update_clazz -> failed to update clazz, e: {}", e); wrap_json_anyhow_err(e) } } } async fn raw_update_clazz( token: AuthorizationHeader, _sudoer: HtySudoerTokenHeader, _host: HtyHostHeader, db_pool: Arc, in_kecheng: &ReqClazz, ) -> anyhow::Result<()> { if in_kecheng.id.is_none() { return Err(anyhow!(HtyErr { code: HtyErrCode::WebErr, reason: Some("clazz_id is none".into()), })); } let id_kecheng = in_kecheng .id .clone() .ok_or_else(|| anyhow!("id is required"))?; let mut db_kecheng = Clazz::find_by_id( &id_kecheng, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; let current_org_id = current_org_id_from_auth(&token); if let Some(org_id) = ¤t_org_id { if db_kecheng.org_id.as_ref() != Some(org_id) { return Err(anyhow!(HtyErr { code: HtyErrCode::AuthenticationFailed, reason: Some("clazz does not belong to current organization".to_string()), })); } } if let Some(clazz_name) = &in_kecheng.clazz_name { db_kecheng.clazz_name = clazz_name.clone(); } if let Some(clazz_status) = &in_kecheng.clazz_status { db_kecheng.clazz_status = clazz_status.clone(); } if let Some(start_from) = &in_kecheng.start_from { db_kecheng.start_from = start_from.clone(); } if let Some(end_by) = &in_kecheng.end_by { db_kecheng.end_by = end_by.clone(); } if let Some(root_id) = &in_kecheng.root_id { db_kecheng.root_id = root_id.clone(); } if let Some(parent_id) = &in_kecheng.parent_id { db_kecheng.parent_id = parent_id.clone(); } db_kecheng.clazz_desc = in_kecheng.clazz_desc.clone(); db_kecheng.duration = in_kecheng.duration.clone(); db_kecheng.clazz_type = in_kecheng.clazz_type.clone(); db_kecheng.students = in_kecheng.students.clone(); db_kecheng.teachers = in_kecheng.teachers.clone(); db_kecheng.jihuas = in_kecheng.jihuas.clone(); db_kecheng.dakas = in_kecheng.dakas.clone(); db_kecheng.is_delete = in_kecheng.is_delete.clone(); db_kecheng.is_repeat = in_kecheng.is_repeat.clone(); db_kecheng.course_sections = in_kecheng.course_sections.clone(); db_kecheng.is_notified = in_kecheng.is_notified.clone(); debug!("raw_update_clazz -> {:?}", db_kecheng); let _ = Clazz::update( &db_kecheng, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; // Audit log let operator_id = hty_id_from_auth(&token).unwrap_or_default(); let action = if in_kecheng.is_delete == Some(true) { "DELETE" } else { "UPDATE" }; write_clazz_audit_log( &db_pool, &id_kecheng, action, &operator_id, Some(serde_json::json!({ "is_delete": in_kecheng.is_delete, "clazz_name": in_kecheng.clazz_name, "start_from": in_kecheng.start_from, "end_by": in_kecheng.end_by, })), current_org_id, ); Ok(()) } /*async fn find_group_users_by_group_id(_p0: &String, _p1: &HtyHostHeader, _p2: &HtySudoerTokenHeader) -> anyhow::Result>> { unimplemented!() } */ fn raw_create_clazz_with_repeat_tx( params: HashMap)>, db_pool: Arc, ) -> anyhow::Result { let task = move |in_params: Option)>>, conn: &mut PgConnection| -> anyhow::Result { let the_params = in_params.ok_or_else(|| anyhow!("params is required"))?; let (to_create_kecheng, to_create_kecheng_repeat) = the_params .clone() .get("params") .ok_or_else(|| anyhow!("params key not found"))? .clone(); let created_kecheng = Clazz::create(&to_create_kecheng, conn)?; debug!( "raw_create_clazz_with_repeat_tx -> created_kecheng: {:?}", created_kecheng ); let mut created_kecheng_repeat = None; if let Some(kecheng_repeat) = &to_create_kecheng_repeat { created_kecheng_repeat = Some(ClazzRepeat::create(kecheng_repeat, conn)?); debug!( "raw_create_clazz_with_repeat_tx -> created_kecheng_repeat: {:?}", created_kecheng_repeat ); } let out = ReqClazz::from_clazz_and_repeat(&Some(created_kecheng), &created_kecheng_repeat); debug!("raw_create_clazz_with_repeat_tx -> out: {:?}", out); Ok(out) }; exec_read_write_task( Box::new(task), Some(params), extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), ) } pub async fn create_clazz_with_repeat( sudoer: HtySudoerTokenHeader, host: HtyHostHeader, auth: AuthorizationHeader, State(db_pool): State>, Json(in_kecheng): Json, ) -> Json> { debug!( "create_clazz_with_repeat -> in_kecheng: {:?}", in_kecheng ); match raw_create_clazz_with_repeat(auth, sudoer, host, db_pool, &in_kecheng).await { Ok(out) => wrap_json_ok_resp(out), Err(e) => { error!( "create_clazz_with_repeat -> failed to create clazz, e: {}", e ); wrap_json_anyhow_err(e) } } } async fn raw_create_clazz_with_repeat( token: AuthorizationHeader, _sudoer: HtySudoerTokenHeader, _host: HtyHostHeader, db_pool: Arc, in_kecheng: &ReqClazz, ) -> anyhow::Result { if in_kecheng.clazz_name.is_none() || in_kecheng.start_from.is_none() || in_kecheng.end_by.is_none() || in_kecheng.duration.is_none() { return Err(anyhow!(HtyErr { code: HtyErrCode::WebErr, reason: Some( "clazz_name or start_from or end_by or duration or users is none".into(), ), })); } let id_user = jwt_decode_token(&(*token).clone())? .hty_id .ok_or_else(|| anyhow!("hty_id is required"))?; let current_org_id = jwt_decode_token(&(*token).clone())?.current_org_id; debug!( "raw_create_clazz_with_repeat -> id_user: {:?}", id_user ); let new_clazz_id = uuid(); let id_sudoer = in_kecheng .root_id .clone() .unwrap_or_else(|| new_clazz_id.clone()); let id_parent = in_kecheng .parent_id .clone() .unwrap_or_else(|| new_clazz_id.clone()); let mut to_create_kecheng = Clazz { id: new_clazz_id.clone(), clazz_name: in_kecheng .clazz_name .clone() .ok_or_else(|| anyhow!("clazz_name is required"))?, clazz_status: in_kecheng .clazz_status .clone() .ok_or_else(|| anyhow!("clazz_status is required"))?, clazz_desc: in_kecheng.clazz_desc.clone(), start_from: in_kecheng .start_from .clone() .ok_or_else(|| anyhow!("start_from is required"))?, end_by: in_kecheng .end_by .clone() .ok_or_else(|| anyhow!("end_by is required"))?, duration: in_kecheng.duration.clone(), root_id: id_sudoer.clone(), clazz_type: in_kecheng.clazz_type.clone(), parent_id: id_parent.clone(), created_by: in_kecheng.created_by.clone(), created_at: Some(current_local_datetime()), students: in_kecheng.students.clone(), teachers: in_kecheng.teachers.clone(), jihuas: in_kecheng.jihuas.clone(), dakas: in_kecheng.dakas.clone(), is_delete: in_kecheng.is_delete.clone(), is_repeat: in_kecheng.is_repeat.clone(), course_sections: in_kecheng.course_sections.clone(), is_notified: in_kecheng.is_notified.clone(), completed_at: None, org_id: in_kecheng.org_id.clone(), }; if to_create_kecheng.org_id.is_none() { to_create_kecheng.org_id = current_org_id.clone(); } debug!( "raw_create_clazz_with_repeat -> to_create_kecheng: {:?}", to_create_kecheng ); let mut to_create_kecheng_repeat: Option = None; if let Some(kecheng_repeat) = &in_kecheng.clazz_repeat { let kecheng_repeat_copy = kecheng_repeat.clone(); to_create_kecheng_repeat = Some(ClazzRepeat { id: uuid(), clazz_id: Some(new_clazz_id.clone()), start_from: kecheng_repeat_copy.start_from.clone(), end_by: kecheng_repeat_copy.end_by.clone(), repeat_start: kecheng_repeat_copy.repeat_start.clone(), repeat_cycle_days: kecheng_repeat_copy.repeat_cycle_days.clone(), repeat_end: kecheng_repeat_copy.repeat_end.clone(), repeat_status: kecheng_repeat_copy.repeat_status.clone(), latest_clazz_created_at: kecheng_repeat_copy.latest_clazz_created_at.clone(), org_id: kecheng_repeat_copy.org_id.clone(), }) } let mut params = HashMap::new(); params.insert( "params".to_string(), (to_create_kecheng, to_create_kecheng_repeat), ); let out_result = raw_create_clazz_with_repeat_tx(params, db_pool.clone()); match out_result { Ok(out) => { debug!("created_kecheng: {:?}", out); write_clazz_audit_log( &db_pool, &new_clazz_id, "CREATE", &id_user, Some(serde_json::json!({ "clazz_name": in_kecheng.clazz_name, "start_from": in_kecheng.start_from, "end_by": in_kecheng.end_by, "is_repeat": in_kecheng.is_repeat, })), current_org_id.clone(), ); Ok(out) } Err(e) => Err(anyhow!(HtyErr { code: HtyErrCode::WebErr, reason: Some("fail to create clazz e: ".to_string() + &e.to_string()), })), } } pub async fn list_clazz_audit_log( _sudoer: HtySudoerTokenHeader, _host: HtyHostHeader, _auth: AuthorizationHeader, State(db_pool): State>, Query(params): Query>, ) -> Json>> { let result = (|| -> anyhow::Result> { let clazz_id = get_some_from_query_params::("clazz_id", ¶ms) .ok_or_else(|| anyhow!("clazz_id is required"))?; ClazzAuditLog::list_by_clazz_id( &clazz_id, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), ) })(); match result { Ok(ok) => wrap_json_ok_resp(ok), Err(e) => { error!("list_clazz_audit_log -> failed, e: {}", e); wrap_json_anyhow_err(e) } } } #[cfg(test)] mod tests { use htycommons::common::current_local_datetime; use htycommons::jwt::jwt_encode_token; use htycommons::web::{AuthorizationHeader, HtyToken}; use super::current_org_id_from_auth; fn build_test_auth_header(current_org_id: Option<&str>) -> String { std::env::set_var("JWT_KEY", "htykc-org-test-key"); let token = HtyToken { token_id: "test-token-id".to_string(), hty_id: Some("teacher-1".to_string()), app_id: Some("app-1".to_string()), ts: current_local_datetime(), roles: None, tags: None, current_org_id: current_org_id.map(|value| value.to_string()), current_org_role_keys: Some(vec!["TEACHER".to_string()]), current_department_id: None, }; jwt_encode_token(token).expect("encode test token") } #[test] fn should_extract_current_org_id_from_auth_header() { let auth_header = build_test_auth_header(Some("org-456")); let parsed_org_id = current_org_id_from_auth(&AuthorizationHeader(auth_header)); assert_eq!(parsed_org_id, Some("org-456".to_string())); } #[test] fn should_return_none_when_auth_header_has_no_org_context() { let auth_header = build_test_auth_header(None); let parsed_org_id = current_org_id_from_auth(&AuthorizationHeader(auth_header)); assert_eq!(parsed_org_id, None); } }