feat: Introduce new interactive content blocks including Fill-in-the-Blanks, Short Answer, Ordering, and Matching, with corresponding API, database, and UI integration.

This commit is contained in:
2025-12-19 17:03:26 -03:00
parent 0988213eb7
commit 57b8d7c0a1
17 changed files with 1513 additions and 32 deletions
+62 -1
View File
@@ -3,12 +3,73 @@ use axum::{
http::StatusCode,
Json,
};
use common::models::{Course, Module, Lesson};
use common::models::{Course, Module, Lesson, PublishedCourse, PublishedModule};
use sqlx::PgPool;
use uuid::Uuid;
use serde_json::json;
use serde::{Deserialize, Serialize};
pub async fn publish_course(
State(pool): State<PgPool>,
Path(id): Path<Uuid>,
) -> Result<StatusCode, StatusCode> {
// 1. Fetch Course
let course = sqlx::query_as::<_, Course>("SELECT * FROM courses WHERE id = $1")
.bind(id)
.fetch_one(&pool)
.await
.map_err(|_| StatusCode::NOT_FOUND)?;
// 2. Fetch Modules
let modules = sqlx::query_as::<_, Module>("SELECT * FROM modules WHERE course_id = $1 ORDER BY position")
.bind(id)
.fetch_all(&pool)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let mut pub_modules = Vec::new();
// 3. Fetch Lessons for each Module
for module in modules {
let lessons = sqlx::query_as::<_, Lesson>("SELECT * FROM lessons WHERE module_id = $1 ORDER BY position")
.bind(module.id)
.fetch_all(&pool)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
pub_modules.push(PublishedModule {
module,
lessons,
});
}
let payload = PublishedCourse {
course,
modules: pub_modules,
};
// 4. Send to LMS
// Using service name for Docker compatibility
let client = reqwest::Client::new();
let res = client.post("http://lms-service:3002/ingest")
.json(&payload)
.send()
.await
.map_err(|e| {
tracing::error!("Failed to reach LMS service: {}", e);
StatusCode::BAD_GATEWAY
})?;
if !res.status().is_success() {
tracing::error!("LMS ingestion failed with status: {}", res.status());
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
log_action(&pool, Uuid::new_v4(), "PUBLISH", "Course", id, json!({})).await;
Ok(StatusCode::OK)
}
#[derive(Deserialize)]
pub struct ModuleQuery {
pub course_id: Option<Uuid>,
+7
View File
@@ -22,6 +22,12 @@ async fn main() {
.await
.expect("Failed to connect to database");
// Run migrations automatically
sqlx::migrate!("./migrations")
.run(&pool)
.await
.expect("Failed to run migrations");
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
@@ -30,6 +36,7 @@ async fn main() {
let app = Router::new()
.route("/courses", get(handlers::get_courses).post(handlers::create_course))
.route("/courses/{id}", get(handlers::get_course))
.route("/courses/{id}/publish", post(handlers::publish_course))
.route("/modules", get(handlers::get_modules).post(handlers::create_module))
.route("/lessons", get(handlers::get_lessons).post(handlers::create_lesson))
.route("/lessons/{id}", get(handlers::get_lesson).put(handlers::update_lesson))