use crate::error::ApiError; use crate::state::AppState; use axum::extract::{Path, State}; use axum::routing::get; use axum::{Json, Router}; use serde::Deserialize; use serde_json::{json, Value}; use std::sync::Arc; pub fn routes() -> Router> { Router::new() .route("/files/{*path}", get(read_file).put(write_file).patch(patch_file).delete(delete_file)) } async fn read_file( State(state): State>, Path(path): Path, ) -> Result, ApiError> { let file_path = state.vault_root.join(&path); if !file_path.exists() { return Err(ApiError::NotFound(format!("File '{}' not found", path))); } let content = std::fs::read_to_string(&file_path) .map_err(|e| ApiError::Vault(vault_core::VaultError::io(e, &file_path)))?; // Try to split frontmatter if let Ok((yaml, body)) = vault_core::frontmatter::split_frontmatter(&content) { let frontmatter: Value = serde_yaml::from_str(yaml).unwrap_or(Value::Null); Ok(Json(json!({ "path": path, "frontmatter": frontmatter, "body": body, }))) } else { Ok(Json(json!({ "path": path, "frontmatter": null, "body": content, }))) } } #[derive(Deserialize)] struct WriteFileBody { #[serde(default)] frontmatter: Option, #[serde(default)] body: Option, #[serde(default)] raw: Option, } async fn write_file( State(state): State>, Path(path): Path, Json(data): Json, ) -> Result, ApiError> { let file_path = state.vault_root.join(&path); if let Some(parent) = file_path.parent() { std::fs::create_dir_all(parent) .map_err(|e| ApiError::Vault(vault_core::VaultError::io(e, parent)))?; } let content = if let Some(raw) = data.raw { raw } else { let body = data.body.unwrap_or_default(); if let Some(fm) = data.frontmatter { let yaml = serde_yaml::to_string(&fm) .map_err(|e| ApiError::Internal(e.to_string()))?; format!("---\n{}---\n{}", yaml, body) } else { body } }; state.write_filter.register(file_path.clone()); std::fs::write(&file_path, content) .map_err(|e| ApiError::Vault(vault_core::VaultError::io(e, &file_path)))?; Ok(Json(json!({ "status": "written", "path": path }))) } async fn patch_file( State(state): State>, Path(path): Path, Json(updates): Json, ) -> Result, ApiError> { let file_path = state.vault_root.join(&path); if !file_path.exists() { return Err(ApiError::NotFound(format!("File '{}' not found", path))); } let content = std::fs::read_to_string(&file_path) .map_err(|e| ApiError::Vault(vault_core::VaultError::io(e, &file_path)))?; let updated = vault_core::frontmatter::update_frontmatter_fields(&content, &file_path, &updates) .map_err(ApiError::Vault)?; state.write_filter.register(file_path.clone()); std::fs::write(&file_path, updated) .map_err(|e| ApiError::Vault(vault_core::VaultError::io(e, &file_path)))?; Ok(Json(json!({ "status": "patched", "path": path }))) } async fn delete_file( State(state): State>, Path(path): Path, ) -> Result, ApiError> { let file_path = state.vault_root.join(&path); if !file_path.exists() { return Err(ApiError::NotFound(format!("File '{}' not found", path))); } std::fs::remove_file(&file_path) .map_err(|e| ApiError::Vault(vault_core::VaultError::io(e, &file_path)))?; Ok(Json(json!({ "status": "deleted", "path": path }))) }