wsb-daemon 0.248.6265

wsb's daemon: the workbench, the GitHub mirror's schedule and peer continuity, for every workspace on the machine
use wsb::prelude::*;
use axum::{
    extract::{Query, State},
    http::StatusCode,
    Json,
};

use super::sessions::ErrorResponse;
use wsb::fleet::git::tree::SubmoduleTree;
use crate::web::notes;
use crate::web::state::WebState;

fn internal_err(e: anyhow::Error) -> (StatusCode, Json<ErrorResponse>) {
    (
        StatusCode::INTERNAL_SERVER_ERROR,
        Json(ErrorResponse {
            error: e.to_string(),
        }),
    )
}

#[derive(Deserialize)]
pub struct PathQuery {
    pub path: Option<String>,
}

/// GET /path-notes?path=<linked_path>
/// Returns notes whose content contains a markdown link to `path`.
/// Without `?path=`, returns all notes in the workspace.
pub async fn list_path_notes(
    State(state): State<WebState>,
    Query(q): Query<PathQuery>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<ErrorResponse>)> {
    let root = state.workspace_root.clone();
    let notes = match q.path {
        Some(ref path) => notes::list_path_notes(&root, path).map_err(internal_err)?,
        None => notes::list_all_notes(&root).map_err(internal_err)?,
    };
    Ok(Json(serde_json::json!({ "notes": notes })))
}

#[derive(Deserialize)]
pub struct CreateNoteBody {
    /// Workspace-relative path the new note should link to.
    pub path: String,
}

/// POST /path-notes
/// Creates a new `.md` file in `docs/` (or `<submodule>/docs/`) pre-seeded
/// with a link to `path`.  Returns the note metadata; the client should open
/// `note.path` in an editor tab.
pub async fn create_path_note(
    State(state): State<WebState>,
    Json(body): Json<CreateNoteBody>,
) -> Result<(StatusCode, Json<serde_json::Value>), (StatusCode, Json<ErrorResponse>)> {
    if body.path.trim().is_empty() {
        return Err((
            StatusCode::UNPROCESSABLE_ENTITY,
            Json(ErrorResponse {
                error: "path must not be empty".to_string(),
            }),
        ));
    }

    let root = state.workspace_root.clone();

    // Every repository in the nesting tree, so a note about a file inside a
    // nested submodule is filed in the repository that holds the file rather
    // than in one of its ancestors.
    let submodule_paths: Vec<String> = match SubmoduleTree::discover_allow_empty(&root) {
        Ok(tree) => tree
            .top_down()
            .iter()
            .map(|node| node.display_path.clone())
            .collect(),
        Err(e) => return Err(internal_err(e.into())),
    };

    let note =
        notes::create_path_note(&root, &body.path, &submodule_paths).map_err(internal_err)?;
    Ok((
        StatusCode::CREATED,
        Json(serde_json::json!({ "note": note })),
    ))
}