use crate::server::app::AppState;
use crate::server::body::parse_query;
use crate::server::errors::{error, method_not_allowed};
use axum::http::{StatusCode, Uri};
use axum::response::{IntoResponse, Response};
use axum::routing::{get, post};
use axum::{Json, Router};
use nomoreide_core::agent_sessions::{
default_store_path, find_agent_session, list_agent_sessions, AgentSession,
};
use nomoreide_core::snapshot_manager::SnapshotManager;
use serde_json::{json, Value};
pub(crate) fn routes() -> Router<AppState> {
Router::new()
.route("/api/agent/change-sets", get(list))
.route(
"/api/agent/change-sets/:id",
get(read).fallback(method_not_allowed),
)
.route(
"/api/agent/change-sets/:id/restore",
post(restore).fallback(method_not_allowed),
)
.route(
"/api/agent/change-sets/:id/diff",
get(diff).fallback(method_not_allowed),
)
}
async fn list() -> Response {
let sessions = sessions().await;
Json(json!({ "ok": true, "sessions": sessions })).into_response()
}
async fn read(uri: Uri) -> Response {
let Some(session) = named(&uri).await else {
return error(StatusCode::NOT_FOUND, "Unknown session");
};
let Some(sha) = session.snapshot_sha.clone() else {
return Json(json!({ "ok": true, "session": session, "files": [] })).into_response();
};
match SnapshotManager::new(session.repo_path.clone())
.changed_files(&sha)
.await
{
Ok(files) => {
Json(json!({ "ok": true, "session": session, "files": files })).into_response()
}
Err(reason) => error(StatusCode::BAD_REQUEST, &reason.to_string()),
}
}
async fn restore(uri: Uri) -> Response {
let Some((session, sha)) = pinned(&uri).await else {
return error(StatusCode::NOT_FOUND, "Session has no snapshot");
};
match SnapshotManager::new(session.repo_path).restore(&sha).await {
Ok(result) => {
let mut envelope = json!({ "ok": true });
if let (Some(object), Ok(Value::Object(fields))) =
(envelope.as_object_mut(), serde_json::to_value(&result))
{
object.extend(fields);
}
Json(envelope).into_response()
}
Err(reason) => error(StatusCode::BAD_REQUEST, &reason.to_string()),
}
}
async fn diff(uri: Uri) -> Response {
let Some((session, sha)) = pinned(&uri).await else {
return error(StatusCode::NOT_FOUND, "Session has no snapshot");
};
let query = parse_query(&uri);
let path = query
.get("path")
.map(|value| value.trim())
.filter(|value| !value.is_empty());
match SnapshotManager::new(session.repo_path)
.diff(&sha, path)
.await
{
Ok(patch) => (
[(
axum::http::header::CONTENT_TYPE,
"text/plain; charset=utf-8",
)],
patch,
)
.into_response(),
Err(reason) => error(StatusCode::BAD_REQUEST, &reason.to_string()),
}
}
async fn pinned(uri: &Uri) -> Option<(AgentSession, String)> {
let session = named(uri).await?;
let sha = session.snapshot_sha.clone()?;
Some((session, sha))
}
async fn named(uri: &Uri) -> Option<AgentSession> {
let id = id_from(uri)?;
tokio::task::spawn_blocking(move || find_agent_session(&default_store_path(), &id))
.await
.ok()
.flatten()
}
async fn sessions() -> Vec<AgentSession> {
tokio::task::spawn_blocking(|| list_agent_sessions(&default_store_path()))
.await
.unwrap_or_default()
}
fn id_from(uri: &Uri) -> Option<String> {
uri.path()
.split('/')
.nth(4)
.filter(|segment| !segment.is_empty())
.map(str::to_string)
}