feat(course-package): add org_visible to course_group and course_package_item table
Migrations:
- htyws: add org_id, org_visible columns to course_group
- htykc: create course_package_item table
Backend:
- CourseGroup: add org_visible, org_id fields; find_org_visible_by_org_id query
- CoursePackageItem: new model with sync/list API
- SUPERVISOR role check for package item management
- New endpoint: GET /api/v1/ws/find_org_visible_course_groups
- New endpoints: POST /api/v1/clazz/course-package/item/sync,
GET /api/v1/clazz/course-package/item/list/{package_id}
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+3
-1
@@ -24,7 +24,7 @@ mod ws_xiaoke;
|
||||
use crate::ws_course_package::{
|
||||
create_course_package, delete_course_package, find_all_active_course_packages_by_org_with_page,
|
||||
find_all_course_packages_by_created_by_with_page, find_course_package_by_id,
|
||||
update_course_package,
|
||||
list_course_package_items, sync_course_package_items, update_course_package,
|
||||
};
|
||||
use crate::ws_xiaoke::{batch_save_clazz_attendance, find_clazz_attendance_by_clazz_id};
|
||||
use crate::ws_xiaoke::{
|
||||
@@ -110,6 +110,8 @@ pub fn clazz_router(db_url: &str) -> Router {
|
||||
.route("/api/v1/clazz/course-package/{id}", get(find_course_package_by_id))
|
||||
.route("/api/v1/clazz/course-package/my-packages", get(find_all_course_packages_by_created_by_with_page))
|
||||
.route("/api/v1/clazz/course-package/org-packages", get(find_all_active_course_packages_by_org_with_page))
|
||||
.route("/api/v1/clazz/course-package/item/sync", post(sync_course_package_items))
|
||||
.route("/api/v1/clazz/course-package/item/list/{package_id}", get(list_course_package_items))
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.with_state(shared_db_state);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use htycommons::web::{
|
||||
wrap_json_anyhow_err, wrap_json_ok_resp, AuthorizationHeader, HtyHostHeader,
|
||||
HtySudoerTokenHeader,
|
||||
};
|
||||
use htykc_models::models::{CoursePackage, ReqCoursePackage};
|
||||
use htykc_models::models::{CoursePackage, CoursePackageItem, ReqCoursePackage, ReqCoursePackageItem};
|
||||
use htycommons::uuid;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::DerefMut;
|
||||
@@ -212,3 +212,86 @@ pub async fn find_all_active_course_packages_by_org_with_page(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 课包内容关联(course_package_item)---
|
||||
|
||||
fn require_supervisor_role(auth: &AuthorizationHeader) -> anyhow::Result<()> {
|
||||
let token = jwt_decode_token(&(*auth).clone())?;
|
||||
let has_role = token.current_org_role_keys
|
||||
.as_ref()
|
||||
.map(|keys| keys.iter().any(|k| k == "SUPERVISOR"))
|
||||
.unwrap_or(false);
|
||||
if !has_role {
|
||||
return Err(anyhow!(HtyErr {
|
||||
code: HtyErrCode::AuthenticationFailed,
|
||||
reason: Some("需要 SUPERVISOR 角色才能管理机构课包内容".to_string()),
|
||||
}));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn sync_course_package_items(
|
||||
_sudoer: HtySudoerTokenHeader,
|
||||
auth: AuthorizationHeader,
|
||||
State(db_pool): State<Arc<DbState>>,
|
||||
Json(body): Json<ReqCoursePackageItem>,
|
||||
) -> Json<HtyResponse<usize>> {
|
||||
let result = (|| -> anyhow::Result<usize> {
|
||||
require_supervisor_role(&auth)?;
|
||||
let token = jwt_decode_token(&(*auth).clone())?;
|
||||
let hty_id = token.hty_id.ok_or_else(|| anyhow!("hty_id is required"))?;
|
||||
let package_id = body.package_id.ok_or_else(|| anyhow!("package_id is required"))?;
|
||||
let group_ids = body.course_group_ids.clone().ok_or_else(|| anyhow!("course_group_ids is required"))?;
|
||||
|
||||
let mut conn_holder = extract_conn(fetch_db_conn(&db_pool)?);
|
||||
let conn = conn_holder.deref_mut();
|
||||
|
||||
// 先软删除现有项目
|
||||
CoursePackageItem::logic_delete_by_package_id(&package_id, conn)?;
|
||||
|
||||
// 批量创建新项目
|
||||
let now = current_local_datetime();
|
||||
let items: Vec<CoursePackageItem> = group_ids.iter().enumerate().map(|(i, gid)| {
|
||||
CoursePackageItem {
|
||||
id: uuid(),
|
||||
package_id: package_id.clone(),
|
||||
course_group_id: gid.clone(),
|
||||
sort_order: Some(i as i32),
|
||||
created_at: now,
|
||||
created_by: Some(hty_id.clone()),
|
||||
is_delete: Some(false),
|
||||
}
|
||||
}).collect();
|
||||
|
||||
let count = items.len();
|
||||
if !items.is_empty() {
|
||||
CoursePackageItem::batch_create(&items, conn)?;
|
||||
}
|
||||
Ok(count)
|
||||
})();
|
||||
match result {
|
||||
Ok(ok) => wrap_json_ok_resp(ok),
|
||||
Err(e) => {
|
||||
error!("sync_course_package_items -> failed, e: {}", e);
|
||||
wrap_json_anyhow_err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_course_package_items(
|
||||
_sudoer: HtySudoerTokenHeader,
|
||||
_auth: AuthorizationHeader,
|
||||
State(db_pool): State<Arc<DbState>>,
|
||||
Path(package_id): Path<String>,
|
||||
) -> Json<HtyResponse<Vec<CoursePackageItem>>> {
|
||||
let result = (|| -> anyhow::Result<Vec<CoursePackageItem>> {
|
||||
CoursePackageItem::find_by_package_id(&package_id, extract_conn(fetch_db_conn(&db_pool)?).deref_mut())
|
||||
})();
|
||||
match result {
|
||||
Ok(ok) => wrap_json_ok_resp(ok),
|
||||
Err(e) => {
|
||||
error!("list_course_package_items -> failed, e: {}", e);
|
||||
wrap_json_anyhow_err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user