(
state: &AppState,
task_id: Uuid,
)
| 1320 | } |
| 1321 | |
| 1322 | async fn cancel_task_internal( |
| 1323 | state: &AppState, |
| 1324 | task_id: Uuid, |
| 1325 | ) -> Result<impl IntoResponse, (StatusCode, String)> { |
| 1326 | let task: Task = sqlx::query_as( |
| 1327 | r#" |
| 1328 | UPDATE tasks |
| 1329 | SET status = 'cancelled', ended_at = now() |
| 1330 | WHERE id = $1 AND status IN ('pending', 'assigned', 'active') |
| 1331 | RETURNING id, mission_id, rover_id, status, progress, waypoint, lap, error, created_at, started_at, ended_at |
| 1332 | "#, |
| 1333 | ) |
| 1334 | .bind(task_id) |
| 1335 | .fetch_optional(&state.db) |
| 1336 | .await |
| 1337 | .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? |
| 1338 | .ok_or(( |
| 1339 | StatusCode::NOT_FOUND, |
| 1340 | "Task not found or already completed".to_string(), |
| 1341 | ))?; |
| 1342 | |
| 1343 | let rover_id = task.rover_id.clone(); |
| 1344 | |
| 1345 | info!(task_id = %task_id, rover_id = %rover_id, "Task cancelled"); |
| 1346 | |
| 1347 | // Clear rover's current task |
| 1348 | { |
| 1349 | let mut rovers = state.rovers.write().await; |
| 1350 | if let Some(rover) = rovers.get_mut(&rover_id) { |
| 1351 | rover.current_task = None; |
| 1352 | } |
| 1353 | } |
| 1354 | |
| 1355 | // Send cancel to rover |
| 1356 | state |
| 1357 | .send_to_rover(&rover_id, DispatchToRover::Cancel { task_id }) |
| 1358 | .await; |
| 1359 | |
| 1360 | // Broadcast update |
| 1361 | state.broadcast(BroadcastMessage::TaskUpdate { task: task.clone() }); |
| 1362 | state.broadcast(BroadcastMessage::RoverUpdate { |
| 1363 | rover_id, |
| 1364 | connected: true, |
| 1365 | task_id: None, |
| 1366 | trace_id: None, |
| 1367 | }); |
| 1368 | |
| 1369 | Ok(Json(task)) |
| 1370 | } |
| 1371 | |
| 1372 | // ============================================================================= |
| 1373 | // Voice Command API |
no test coverage detected