feat: implement structured grading system with predefined assessment types

- Add structured grading policy with predefined types (Continuous Assessment, Midterm, Final Test, Exam)
- Replace free-text category input with combobox selection in Grading Policy page
- Update Lesson Editor to use dropdown selector for grading category assignment
- Fix create_grading_category handler to capture organization context
- Fix update_course handler to set audit context in database transaction
- Implement getImageUrl helper for proper asset path resolution
- Add unoptimized prop to organization logo images to bypass Next.js optimization
- Add database migrations for organization_id in content tables
- Seed default tutorial courses for Admin, Instructor, and Student roles
- Fix audit log constraints and content schema issues
This commit is contained in:
2026-01-12 00:52:26 -03:00
parent 3ddcaaaf15
commit 942780db1c
19 changed files with 476 additions and 92 deletions
+25 -3
View File
@@ -262,6 +262,22 @@ pub async fn update_course(
.and_then(|s| s.parse::<DateTime<Utc>>().ok())
.or(existing.end_date);
// BEGIN TRANSACTION
let mut tx = pool
.begin()
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
// Set auditing context
sqlx::query(
"SELECT set_config('app.current_user_id', $1, true), set_config('app.org_id', $2, true)",
)
.bind(claims.sub.to_string())
.bind(org_ctx.id.to_string())
.execute(&mut *tx)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let course = sqlx::query_as::<_, Course>(
"SELECT * FROM fn_update_course($1, $2, $3, $4, $5, $6, $7, $8, $9)",
)
@@ -274,7 +290,7 @@ pub async fn update_course(
.bind(start_date)
.bind(end_date)
.bind(certificate_template)
.fetch_one(&pool)
.fetch_one(&mut *tx)
.await
.map_err(|e| {
(
@@ -283,6 +299,10 @@ pub async fn update_course(
)
})?;
tx.commit()
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok(Json(course))
}
@@ -944,13 +964,15 @@ pub async fn get_grading_categories(
pub async fn create_grading_category(
State(pool): State<PgPool>,
Org(org_ctx): Org,
Json(payload): Json<GradingPayload>,
) -> Result<Json<common::models::GradingCategory>, (StatusCode, String)> {
let category = sqlx::query_as::<_, common::models::GradingCategory>(
"INSERT INTO grading_categories (course_id, name, weight, drop_count)
VALUES ($1, $2, $3, $4)
"INSERT INTO grading_categories (organization_id, course_id, name, weight, drop_count)
VALUES ($1, $2, $3, $4, $5)
RETURNING *",
)
.bind(org_ctx.id)
.bind(payload.course_id)
.bind(payload.name)
.bind(payload.weight)