Refactor code structure for improved readability and maintainability
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -4,6 +4,7 @@ use axum::{
|
||||
http::{HeaderMap, header::AUTHORIZATION},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
response::sse::{Event, KeepAlive, Sse},
|
||||
Extension,
|
||||
};
|
||||
use aws_config::BehaviorVersion;
|
||||
@@ -4706,3 +4707,78 @@ fn extract_block_content(metadata: &Option<serde_json::Value>) -> String {
|
||||
}
|
||||
block_content
|
||||
}
|
||||
|
||||
// ─── SSE: Pizarra Colaborativa en Tiempo Real (Fase 37) ──────────────────────
|
||||
|
||||
pub async fn stream_lesson_collaborative_canvas(
|
||||
Org(org_ctx): Org,
|
||||
State(pool): State<PgPool>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
use std::convert::Infallible;
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
|
||||
let lesson_exists = sqlx::query_scalar::<_, bool>(
|
||||
"SELECT EXISTS(SELECT 1 FROM lessons WHERE id = $1 AND organization_id = $2)",
|
||||
)
|
||||
.bind(id)
|
||||
.bind(org_ctx.id)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("stream_lesson_collaborative_canvas: lesson check failed: {}", e);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
if !lesson_exists {
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
let (tx, rx) = tokio::sync::mpsc::channel::<Result<Event, Infallible>>(16);
|
||||
|
||||
tokio::spawn(async move {
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct CanvasRow {
|
||||
canvas_state: serde_json::Value,
|
||||
revision: i64,
|
||||
updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
let mut last_revision: i64 = -1;
|
||||
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||
|
||||
let result = sqlx::query_as::<_, CanvasRow>(
|
||||
"SELECT canvas_state, revision, updated_at FROM lesson_collaborative_canvases WHERE lesson_id = $1 AND organization_id = $2",
|
||||
)
|
||||
.bind(id)
|
||||
.bind(org_ctx.id)
|
||||
.fetch_optional(&pool)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(Some(row)) if row.revision != last_revision => {
|
||||
last_revision = row.revision;
|
||||
let payload = serde_json::json!({
|
||||
"lesson_id": id,
|
||||
"canvas_state": row.canvas_state,
|
||||
"revision": row.revision,
|
||||
"updated_at": row.updated_at.to_rfc3339(),
|
||||
});
|
||||
if tx.send(Ok(Event::default().data(payload.to_string()))).await.is_err() {
|
||||
break; // Cliente desconectado
|
||||
}
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
tracing::error!("stream_lesson_collaborative_canvas: poll error: {}", e);
|
||||
let _ = tx.send(Ok(Event::default().event("error").data("poll_error"))).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Sse::new(ReceiverStream::new(rx))
|
||||
.keep_alive(KeepAlive::default()))
|
||||
}
|
||||
|
||||
@@ -430,3 +430,66 @@ pub async fn lti_grade_passback(
|
||||
normalized_score: normalized,
|
||||
}))
|
||||
}
|
||||
|
||||
// ─── Rotación de Secreto LTI (Fase 37) ───────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct RotateSecretResponse {
|
||||
pub tool_id: Uuid,
|
||||
pub new_secret: String,
|
||||
pub rotated_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
pub async fn rotate_lti_tool_secret(
|
||||
Org(org_ctx): Org,
|
||||
_claims: Claims,
|
||||
State(pool): State<PgPool>,
|
||||
Path((course_id, tool_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<Json<RotateSecretResponse>, (StatusCode, String)> {
|
||||
use rand::Rng;
|
||||
|
||||
// Verificar que la herramienta pertenece al curso y organización
|
||||
let tool_exists = sqlx::query_scalar::<_, bool>(
|
||||
"SELECT EXISTS(SELECT 1 FROM lti_external_tools WHERE id = $1 AND course_id = $2 AND organization_id = $3)",
|
||||
)
|
||||
.bind(tool_id)
|
||||
.bind(course_id)
|
||||
.bind(org_ctx.id)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
if !tool_exists {
|
||||
return Err((StatusCode::NOT_FOUND, "Herramienta LTI no encontrada".to_string()));
|
||||
}
|
||||
|
||||
// Generar nuevo secreto aleatorio de 32 caracteres alfanuméricos
|
||||
let new_secret: String = rand::thread_rng()
|
||||
.sample_iter(&rand::distributions::Alphanumeric)
|
||||
.take(32)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
|
||||
sqlx::query(
|
||||
"UPDATE lti_external_tools SET shared_secret = $1, updated_at = $2 WHERE id = $3",
|
||||
)
|
||||
.bind(&new_secret)
|
||||
.bind(now)
|
||||
.bind(tool_id)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
tracing::info!(
|
||||
"rotate_lti_tool_secret: rotated secret for tool {} in course {} org {}",
|
||||
tool_id, course_id, org_ctx.id
|
||||
);
|
||||
|
||||
Ok(Json(RotateSecretResponse {
|
||||
tool_id,
|
||||
new_secret,
|
||||
rotated_at: now,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -168,6 +168,10 @@ async fn main() {
|
||||
get(handlers::get_lesson_collaborative_canvas)
|
||||
.put(handlers::update_lesson_collaborative_canvas),
|
||||
)
|
||||
.route(
|
||||
"/lessons/{id}/collaborative-canvas/stream",
|
||||
get(handlers::stream_lesson_collaborative_canvas),
|
||||
)
|
||||
.route("/lessons/{id}/bookmark", post(handlers::toggle_bookmark))
|
||||
.route("/bookmarks", get(handlers::get_user_bookmarks))
|
||||
.route("/grades", post(handlers::submit_lesson_score))
|
||||
@@ -210,6 +214,10 @@ async fn main() {
|
||||
put(handlers_lti_consumer::update_course_lti_tool)
|
||||
.delete(handlers_lti_consumer::delete_course_lti_tool),
|
||||
)
|
||||
.route(
|
||||
"/courses/{id}/lti-tools/{tool_id}/rotate-secret",
|
||||
post(handlers_lti_consumer::rotate_lti_tool_secret),
|
||||
)
|
||||
// Portafolio e insignias (Badges)
|
||||
.route("/profile/{user_id}", get(portfolio::get_public_profile))
|
||||
.route("/my/badges", get(portfolio::get_my_badges))
|
||||
|
||||
Reference in New Issue
Block a user