feat: add external SAM ID support to courses and update API documentation
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
ALTER TABLE courses
|
||||
ADD COLUMN IF NOT EXISTS external_sam_id BIGINT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_courses_org_external_sam_id
|
||||
ON courses (organization_id, external_sam_id)
|
||||
WHERE external_sam_id IS NOT NULL;
|
||||
@@ -1009,8 +1009,8 @@ pub async fn ingest_course(
|
||||
|
||||
// 2. Insertar o actualizar (Upsert) Curso
|
||||
sqlx::query(
|
||||
"INSERT INTO courses (id, title, description, instructor_id, start_date, end_date, passing_percentage, certificate_template, updated_at, organization_id, pacing_mode, price, currency)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
||||
"INSERT INTO courses (id, title, description, instructor_id, start_date, end_date, passing_percentage, certificate_template, updated_at, organization_id, pacing_mode, price, currency, external_sam_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
title = EXCLUDED.title,
|
||||
description = EXCLUDED.description,
|
||||
@@ -1023,7 +1023,8 @@ pub async fn ingest_course(
|
||||
organization_id = EXCLUDED.organization_id,
|
||||
pacing_mode = EXCLUDED.pacing_mode,
|
||||
price = EXCLUDED.price,
|
||||
currency = EXCLUDED.currency"
|
||||
currency = EXCLUDED.currency,
|
||||
external_sam_id = EXCLUDED.external_sam_id"
|
||||
)
|
||||
.bind(payload.course.id)
|
||||
.bind(&payload.course.title)
|
||||
@@ -1038,6 +1039,7 @@ pub async fn ingest_course(
|
||||
.bind(&payload.course.pacing_mode)
|
||||
.bind(payload.course.price)
|
||||
.bind(&payload.course.currency)
|
||||
.bind(payload.course.external_sam_id)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(|e: sqlx::Error| {
|
||||
|
||||
@@ -470,7 +470,7 @@ async fn main() {
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<body>
|
||||
<script id="api-reference" data-url="/api-docs/openapi.json"></script>
|
||||
<script id="api-reference" data-url="./api-docs/openapi.json"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -50,13 +50,26 @@ pub struct LessonSchema {
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
/// Módulo de curso
|
||||
/// Módulo de curso dentro de la ingesta.
|
||||
///
|
||||
/// `lessons` puede venir como `[]` (vacío), pero no debe venir `null`.
|
||||
#[derive(utoipa::ToSchema, serde::Serialize)]
|
||||
pub struct ModuleSchema {
|
||||
pub struct IngestModuleSchema {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub position: i32,
|
||||
pub created_at: String,
|
||||
pub lessons: Vec<LessonSchema>,
|
||||
}
|
||||
|
||||
/// Instructor asignado al curso.
|
||||
#[derive(utoipa::ToSchema, serde::Serialize)]
|
||||
pub struct CourseInstructorSchema {
|
||||
pub id: String,
|
||||
pub user_id: String,
|
||||
/// Ej: "primary", "instructor", "assistant"
|
||||
pub role: String,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
/// Organización
|
||||
@@ -71,6 +84,7 @@ pub struct OrgSchema {
|
||||
#[derive(utoipa::ToSchema, serde::Serialize)]
|
||||
pub struct CourseSchema {
|
||||
pub id: String,
|
||||
pub external_sam_id: Option<i64>,
|
||||
pub title: String,
|
||||
pub description: Option<String>,
|
||||
pub price: f64,
|
||||
@@ -82,12 +96,17 @@ pub struct CourseSchema {
|
||||
/// Payload completo de ingesta de curso — usado por el sistema externo para crear/actualizar cursos
|
||||
#[derive(utoipa::ToSchema, serde::Serialize)]
|
||||
pub struct IngestCourseRequest {
|
||||
/// Obligatorio. No debe ser `null`.
|
||||
pub organization: OrgSchema,
|
||||
/// Obligatorio. No debe ser `null`.
|
||||
pub course: CourseSchema,
|
||||
/// Puede venir como `[]` (vacío), pero no `null`.
|
||||
pub grading_categories: Vec<GradingCategorySchema>,
|
||||
pub modules: Vec<ModuleSchema>,
|
||||
pub lessons: Vec<LessonSchema>,
|
||||
pub instructors: Vec<String>,
|
||||
/// Puede venir como `[]` (vacío), pero no `null`.
|
||||
/// Las lecciones se envían dentro de cada módulo en `modules[].lessons`.
|
||||
pub modules: Vec<IngestModuleSchema>,
|
||||
/// Opcional. Puede omitirse o venir como `null` o `[]`.
|
||||
pub instructors: Option<Vec<CourseInstructorSchema>>,
|
||||
}
|
||||
|
||||
/// Entrada del catálogo Tipo Nota
|
||||
@@ -107,7 +126,7 @@ pub struct TipoNotaSchema {
|
||||
info(
|
||||
title = "OpenCCB LMS — API de Integración",
|
||||
version = "1.0.0",
|
||||
description = "API para integrar plataformas externas: creación de cursos, inscripción de alumnos y sincronización de notas."
|
||||
description = "API para integrar plataformas externas: creación de cursos, inscripción de alumnos y sincronización de notas.\n\nReglas de nulabilidad y arreglos (aplican a todos los endpoints de esta API):\n- Campos opcionales (Option): pueden omitirse o enviarse como null.\n- Campos de arreglo no opcionales (Vec): deben enviarse como arreglo; pueden ser [] pero no null.\n- Objetos requeridos del payload: no deben enviarse como null.\n- En /ingest, enviar modules: [] reemplaza la estructura del curso y elimina módulos/lecciones previos en LMS."
|
||||
),
|
||||
paths(
|
||||
ingest_course,
|
||||
@@ -121,8 +140,9 @@ pub struct TipoNotaSchema {
|
||||
IngestCourseRequest,
|
||||
OrgSchema,
|
||||
CourseSchema,
|
||||
ModuleSchema,
|
||||
IngestModuleSchema,
|
||||
LessonSchema,
|
||||
CourseInstructorSchema,
|
||||
GradingCategorySchema,
|
||||
EnrollRequest,
|
||||
GradeSubmissionRequest,
|
||||
|
||||
Reference in New Issue
Block a user