feat: fix frontend and activate imports
This commit is contained in:
@@ -43,19 +43,11 @@ pub async fn create_question(
|
||||
.bind(payload.difficulty.as_deref().unwrap_or("medium"))
|
||||
.bind(payload.tags.as_deref())
|
||||
.bind(payload.skill_assessed.as_deref())
|
||||
.bind(payload.media_url.as_deref())
|
||||
.bind(payload.media_type.as_deref())
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
// If audio generation requested, trigger it asynchronously
|
||||
if payload.generate_audio.unwrap_or(false) {
|
||||
tokio::spawn(async move {
|
||||
let _ = generate_audio_for_question(question.id, pool.clone()).await;
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Json(question))
|
||||
}
|
||||
|
||||
@@ -437,129 +429,6 @@ pub async fn import_from_mysql(
|
||||
Ok(Json(imported_questions))
|
||||
}
|
||||
|
||||
// ==================== Audio Generation ====================
|
||||
|
||||
/// POST /api/question-bank/{id}/generate-audio - Generate audio for a question using Bark
|
||||
pub async fn generate_audio(
|
||||
Org(org_ctx): Org,
|
||||
Path(id): Path<Uuid>,
|
||||
State(pool): State<PgPool>,
|
||||
payload: Option<Json<common::models::GenerateAudioPayload>>,
|
||||
) -> Result<StatusCode, (StatusCode, String)> {
|
||||
// Get question
|
||||
let question: QuestionBank = sqlx::query_as(
|
||||
"SELECT * FROM question_bank WHERE id = $1 AND organization_id = $2"
|
||||
)
|
||||
.bind(id)
|
||||
.bind(org_ctx.id)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.map_err(|e| match e {
|
||||
sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "Question not found".to_string()),
|
||||
_ => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
|
||||
})?;
|
||||
|
||||
// Spawn async task for audio generation
|
||||
tokio::spawn(async move {
|
||||
let _ = generate_audio_for_question_with_params(id, pool, payload.map(|p| p.0)).await;
|
||||
});
|
||||
|
||||
Ok(StatusCode::ACCEPTED)
|
||||
}
|
||||
|
||||
async fn generate_audio_for_question(
|
||||
question_id: Uuid,
|
||||
pool: PgPool,
|
||||
) -> Result<(), String> {
|
||||
generate_audio_for_question_with_params(question_id, pool, None).await
|
||||
}
|
||||
|
||||
async fn generate_audio_for_question_with_params(
|
||||
question_id: Uuid,
|
||||
pool: PgPool,
|
||||
payload: Option<common::models::GenerateAudioPayload>,
|
||||
) -> Result<(), String> {
|
||||
use reqwest::Client;
|
||||
use serde_json::json;
|
||||
|
||||
// Get question text
|
||||
let question_text: String = sqlx::query_scalar("SELECT audio_text FROM question_bank WHERE id = $1")
|
||||
.bind(question_id)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get question: {}", e))?
|
||||
.unwrap_or_default();
|
||||
|
||||
let text = payload.as_ref().map(|p| p.text.clone()).unwrap_or(question_text);
|
||||
|
||||
// Update status to generating
|
||||
sqlx::query("UPDATE question_bank SET audio_status = 'generating' WHERE id = $1")
|
||||
.bind(question_id)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to update status: {}", e))?;
|
||||
|
||||
// Call Bark TTS API
|
||||
let bark_url = std::env::var("BARK_API_URL").unwrap_or_else(|_| "http://localhost:8000".to_string());
|
||||
let client = Client::new();
|
||||
|
||||
let voice = payload.as_ref().and_then(|p| p.voice.clone()).unwrap_or_else(|| "v2/en_speaker_1".to_string());
|
||||
let speed = payload.as_ref().and_then(|p| p.speed).unwrap_or(1.0);
|
||||
|
||||
let response = client
|
||||
.post(&format!("{}/api/generate", bark_url))
|
||||
.json(&json!({
|
||||
"text": text,
|
||||
"voice": voice,
|
||||
"speed": speed,
|
||||
"output_format": "mp3"
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Bark API request failed: {}", e))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
sqlx::query("UPDATE question_bank SET audio_status = 'failed' WHERE id = $1")
|
||||
.bind(question_id)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.map_err(|_| "Failed to update status".to_string())?;
|
||||
|
||||
return Err(format!("Bark API returned error: {}", response.status()));
|
||||
}
|
||||
|
||||
// Save audio file
|
||||
let audio_bytes = response.bytes().await.map_err(|e| format!("Failed to get audio bytes: {}", e))?;
|
||||
|
||||
// Save to uploads directory
|
||||
let filename = format!("question_{}.mp3", question_id);
|
||||
let file_path = format!("uploads/audio/{}", filename);
|
||||
|
||||
std::fs::create_dir_all("uploads/audio").map_err(|e| format!("Failed to create directory: {}", e))?;
|
||||
std::fs::write(&file_path, &audio_bytes).map_err(|e| format!("Failed to save audio: {}", e))?;
|
||||
|
||||
// Update question with audio URL
|
||||
let audio_url = format!("/audio/{}", filename);
|
||||
sqlx::query(
|
||||
"UPDATE question_bank SET audio_url = $1, audio_status = 'ready', audio_metadata = $2 WHERE id = $3"
|
||||
)
|
||||
.bind(&audio_url)
|
||||
.bind(&json!({
|
||||
"voice": voice,
|
||||
"speed": speed,
|
||||
"generated_at": chrono::Utc::now().to_rfc3339(),
|
||||
"file_size": audio_bytes.len(),
|
||||
}))
|
||||
.bind(question_id)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to update question: {}", e))?;
|
||||
|
||||
tracing::info!("Generated audio for question {}", question_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ==================== Helpers ====================
|
||||
|
||||
fn map_mysql_question_type(mysql_type: i32) -> QuestionBankType {
|
||||
|
||||
@@ -96,7 +96,7 @@ async fn main() {
|
||||
});
|
||||
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin(Any)
|
||||
.allow_origin("http://localhost:3000".parse::<http::HeaderValue>().unwrap())
|
||||
.allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE, Method::OPTIONS, Method::PATCH])
|
||||
.allow_headers([
|
||||
header::CONTENT_TYPE,
|
||||
@@ -104,7 +104,7 @@ async fn main() {
|
||||
header::HeaderName::from_static("x-requested-with"),
|
||||
header::HeaderName::from_static("x-organization-id"),
|
||||
])
|
||||
.expose_headers([header::CONTENT_LENGTH]);
|
||||
.expose_headers([header::CONTENT_LENGTH, header::CONTENT_TYPE]);
|
||||
|
||||
// Rate limiting: Deshabilitado temporalmente por problemas de compatibilidad con tower-governor
|
||||
// Para habilitar en producción, configurar con GovernorLayer y ajustar los límites apropiadamente
|
||||
@@ -343,10 +343,6 @@ async fn main() {
|
||||
"/question-bank/import-mysql",
|
||||
post(handlers_question_bank::import_from_mysql),
|
||||
)
|
||||
.route(
|
||||
"/question-bank/{id}/generate-audio",
|
||||
post(handlers_question_bank::generate_audio),
|
||||
)
|
||||
.route(
|
||||
"/question-bank/mysql-courses",
|
||||
get(handlers_question_bank::list_mysql_courses),
|
||||
@@ -392,10 +388,6 @@ async fn main() {
|
||||
.route(
|
||||
"/branding",
|
||||
get(handlers_branding::get_organization_branding),
|
||||
)
|
||||
.route(
|
||||
"/organization",
|
||||
get(handlers::get_public_organization),
|
||||
);
|
||||
|
||||
let public_routes = Router::new()
|
||||
|
||||
Reference in New Issue
Block a user