huskies: merge 606_story_extract_project_service

This commit is contained in:
dave
2026-04-24 14:56:54 +00:00
parent d9e883c21d
commit 1910365321
7 changed files with 376 additions and 13 deletions
+24 -10
View File
@@ -1,6 +1,7 @@
//! HTTP project endpoints — REST API for project initialization and context management.
use crate::http::context::{AppContext, OpenApiResult, bad_request};
use crate::io::fs;
//! HTTP project endpoints — thin adapters over `service::project`.
use crate::http::context::{AppContext, OpenApiResult, bad_request, not_found};
use crate::service::project::{self as svc, Error as ProjectError};
use poem::http::StatusCode;
use poem_openapi::{Object, OpenApi, Tags, payload::Json};
use serde::Deserialize;
use std::sync::Arc;
@@ -15,6 +16,17 @@ struct PathPayload {
path: String,
}
/// Map a typed [`ProjectError`] to a `poem::Error` with the appropriate HTTP status.
fn map_project_error(e: ProjectError) -> poem::Error {
match e {
ProjectError::PathNotFound(msg) => not_found(msg),
ProjectError::NotADirectory(msg) => bad_request(msg),
ProjectError::Internal(msg) => {
poem::Error::from_string(msg, StatusCode::INTERNAL_SERVER_ERROR)
}
}
}
pub struct ProjectApi {
pub ctx: Arc<AppContext>,
}
@@ -26,8 +38,8 @@ impl ProjectApi {
/// Returns null when no project is open.
#[oai(path = "/project", method = "get")]
async fn get_current_project(&self) -> OpenApiResult<Json<Option<String>>> {
let result = fs::get_current_project(&self.ctx.state, self.ctx.store.as_ref())
.map_err(bad_request)?;
let result = svc::get_current_project(&self.ctx.state, self.ctx.store.as_ref())
.map_err(map_project_error)?;
Ok(Json(result))
}
@@ -36,14 +48,14 @@ impl ProjectApi {
/// Persists the selected path for later sessions.
#[oai(path = "/project", method = "post")]
async fn open_project(&self, payload: Json<PathPayload>) -> OpenApiResult<Json<String>> {
let confirmed = fs::open_project(
let confirmed = svc::open_project(
payload.0.path,
&self.ctx.state,
self.ctx.store.as_ref(),
self.ctx.agents.port(),
)
.await
.map_err(bad_request)?;
.map_err(map_project_error)?;
Ok(Json(confirmed))
}
@@ -55,21 +67,23 @@ impl ProjectApi {
"[MERGE-DEBUG] DELETE /project called! \
Backtrace: this is the only code path that clears project_root."
);
fs::close_project(&self.ctx.state, self.ctx.store.as_ref()).map_err(bad_request)?;
svc::close_project(&self.ctx.state, self.ctx.store.as_ref()).map_err(map_project_error)?;
Ok(Json(true))
}
/// List known projects from the store.
#[oai(path = "/projects", method = "get")]
async fn list_known_projects(&self) -> OpenApiResult<Json<Vec<String>>> {
let projects = fs::get_known_projects(self.ctx.store.as_ref()).map_err(bad_request)?;
let projects =
svc::get_known_projects(self.ctx.store.as_ref()).map_err(map_project_error)?;
Ok(Json(projects))
}
/// Forget a known project path.
#[oai(path = "/projects/forget", method = "post")]
async fn forget_known_project(&self, payload: Json<PathPayload>) -> OpenApiResult<Json<bool>> {
fs::forget_known_project(payload.0.path, self.ctx.store.as_ref()).map_err(bad_request)?;
svc::forget_known_project(payload.0.path, self.ctx.store.as_ref())
.map_err(map_project_error)?;
Ok(Json(true))
}
}