Skip to content

Commit 44dafc0

Browse files
committed
feat(http): add identity and setup endpoints + OpenAPI
1 parent cd345e3 commit 44dafc0

2 files changed

Lines changed: 103 additions & 1 deletion

File tree

src/http.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,14 @@ struct ReindexFileBody {
332332
file: String,
333333
}
334334

335+
#[derive(Debug, Deserialize)]
336+
struct SetupBody {
337+
mode: String,
338+
name: Option<String>,
339+
role: Option<String>,
340+
purpose: Option<String>,
341+
}
342+
335343
// ---------------------------------------------------------------------------
336344
// CORS
337345
// ---------------------------------------------------------------------------
@@ -388,6 +396,9 @@ pub fn build_router(state: ApiState) -> Router {
388396
.route("/api/delete", post(handle_delete))
389397
// Index maintenance
390398
.route("/api/reindex-file", post(handle_reindex_file))
399+
// Identity endpoints
400+
.route("/api/identity", get(handle_identity))
401+
.route("/api/setup", post(handle_setup))
391402
// Migration endpoints
392403
.route("/api/migrate/preview", post(handle_migrate_preview))
393404
.route("/api/migrate/apply", post(handle_migrate_apply))
@@ -1066,6 +1077,60 @@ async fn handle_reindex_file(
10661077
})))
10671078
}
10681079

1080+
// ---------------------------------------------------------------------------
1081+
// Identity / setup endpoint handlers
1082+
// ---------------------------------------------------------------------------
1083+
1084+
async fn handle_identity(
1085+
State(state): State<ApiState>,
1086+
headers: HeaderMap,
1087+
) -> Result<impl IntoResponse, ApiError> {
1088+
authorize(&headers, &state, false)?;
1089+
let store = state.store.lock().await;
1090+
let config = crate::config::Config::load().unwrap_or_default();
1091+
let block = crate::identity::format_identity_block(&config, &store)
1092+
.map_err(|e| ApiError::internal(&format!("{e:#}")))?;
1093+
Ok(Json(serde_json::json!({ "identity": block })))
1094+
}
1095+
1096+
async fn handle_setup(
1097+
State(state): State<ApiState>,
1098+
headers: HeaderMap,
1099+
Json(body): Json<SetupBody>,
1100+
) -> Result<impl IntoResponse, ApiError> {
1101+
authorize(&headers, &state, true)?;
1102+
match body.mode.as_str() {
1103+
"detect" => {
1104+
let result = crate::onboarding::run_detect_json(&state.vault_path)
1105+
.map_err(|e| ApiError::internal(&format!("{e:#}")))?;
1106+
Ok(Json(result))
1107+
}
1108+
"apply" => {
1109+
let mut config = crate::config::Config::load().unwrap_or_default();
1110+
let data_dir = crate::config::Config::data_dir()
1111+
.map_err(|e| ApiError::internal(&format!("{e:#}")))?;
1112+
let flags = crate::onboarding::ApplyFlags {
1113+
name: body.name,
1114+
role: body.role,
1115+
purpose: body.purpose,
1116+
identity_only: false,
1117+
reindex_only: false,
1118+
};
1119+
let result = crate::onboarding::run_apply_json(
1120+
&state.vault_path,
1121+
&mut config,
1122+
&data_dir,
1123+
flags,
1124+
)
1125+
.map_err(|e| ApiError::internal(&format!("{e:#}")))?;
1126+
Ok(Json(result))
1127+
}
1128+
other => Err(ApiError::bad_request(&format!(
1129+
"Unknown mode: {other}. Use 'detect' or 'apply'."
1130+
))),
1131+
}
1132+
}
1133+
10691134
// ---------------------------------------------------------------------------
10701135
// Tests
10711136
// ---------------------------------------------------------------------------

src/openapi.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ pub fn build_openapi_spec(server_url: &str) -> serde_json::Value {
2929
paths.insert("/api/delete".into(), build_delete());
3030
paths.insert("/api/reindex-file".into(), build_reindex_file());
3131

32+
// Identity endpoints
33+
paths.insert("/api/identity".into(), build_identity_endpoint());
34+
paths.insert("/api/setup".into(), build_setup_endpoint());
35+
3236
// Migration endpoints
3337
paths.insert("/api/migrate/preview".into(), build_migrate_preview());
3438
paths.insert("/api/migrate/apply".into(), build_migrate_apply());
@@ -38,7 +42,7 @@ pub fn build_openapi_spec(server_url: &str) -> serde_json::Value {
3842
"openapi": "3.1.0",
3943
"info": {
4044
"title": "engraph",
41-
"version": "1.5.5",
45+
"version": "1.6.0",
4246
"description": "AI-powered semantic search and management API for Obsidian vaults."
4347
},
4448
"servers": [{ "url": server_url }],
@@ -450,6 +454,39 @@ fn build_reindex_file() -> serde_json::Value {
450454
})
451455
}
452456

457+
fn build_identity_endpoint() -> serde_json::Value {
458+
serde_json::json!({
459+
"get": {
460+
"operationId": "getIdentity",
461+
"summary": "Returns compact user identity (L0) and current context (L1).",
462+
"responses": { "200": { "description": "Identity block as JSON with 'identity' key" } }
463+
}
464+
})
465+
}
466+
467+
fn build_setup_endpoint() -> serde_json::Value {
468+
serde_json::json!({
469+
"post": {
470+
"operationId": "setup",
471+
"summary": "Run first-time setup or update identity. Use 'detect' to inspect, 'apply' to configure.",
472+
"requestBody": {
473+
"required": true,
474+
"content": { "application/json": { "schema": {
475+
"type": "object",
476+
"required": ["mode"],
477+
"properties": {
478+
"mode": { "type": "string", "description": "'detect' or 'apply'" },
479+
"name": { "type": "string", "description": "User name (apply mode)" },
480+
"role": { "type": "string", "description": "User role (apply mode)" },
481+
"purpose": { "type": "string", "description": "Vault purpose (apply mode)" }
482+
}
483+
}}}
484+
},
485+
"responses": { "200": { "description": "Setup result as JSON" } }
486+
}
487+
})
488+
}
489+
453490
fn build_migrate_preview() -> serde_json::Value {
454491
serde_json::json!({
455492
"post": {

0 commit comments

Comments
 (0)