feat(classroom): add SQLite persistence + security hardening
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

M11-03: Classroom data persistence
- New persist.rs: SQLite-backed ClassroomPersistence with open/load_all/save
- Schema: classrooms (JSON blob) + classroom_chats tables
- generate.rs: auto-persist classroom after generation
- chat.rs: auto-persist chat messages after each exchange
- mod.rs: init_persistence() for app setup integration

M1-01: Gemini API key now uses x-goog-api-key header
- No longer leaks API key in URL query params or debug logs

M1-03/04: Mutex unwrap() replaced with unwrap_or_else(|e| e.into_inner())
- MemoryMiddleware and LoopGuardMiddleware recover from poison

M2-08: Agent creation input validation
- Reject empty names, out-of-range temperature (0-2), zero max_tokens

M11-06: Classroom chat message ID uses crypto.randomUUID()
This commit is contained in:
iven
2026-04-04 19:26:59 +08:00
parent 619bad30cb
commit 88172aa651
4 changed files with 207 additions and 1 deletions

View File

@@ -95,6 +95,7 @@ pub async fn classroom_generate(
store: State<'_, ClassroomStore>,
tasks: State<'_, GenerationTasks>,
kernel_state: State<'_, KernelState>,
persistence: State<'_, crate::classroom_commands::persist::ClassroomPersistence>,
request: ClassroomGenerateRequest,
) -> Result<ClassroomGenerateResponse, String> {
if request.topic.trim().is_empty() {
@@ -217,7 +218,11 @@ pub async fn classroom_generate(
// Store classroom
{
let mut s = store.lock().await;
s.insert(classroom_id.clone(), classroom);
s.insert(classroom_id.clone(), classroom.clone());
// Persist to SQLite
if let Err(e) = persistence.save_classroom(&classroom).await {
tracing::warn!("[ClassroomGenerate] Failed to persist classroom {}: {}", classroom_id, e);
}
}
// Clear generation task