wsb 0.196.4203

Workspace - A tool suite for file operations, version management, and development workflow automation
Documentation
use crate::prelude::*;
use axum::{
    extract::{Path, State},
    http::StatusCode,
    Json,
};
use serde_json::json;

use super::sessions::ErrorResponse;
use crate::web::github::{CreateGistRequest, UpdateGistRequest};
use crate::web::state::WebState;

fn no_github() -> (StatusCode, Json<ErrorResponse>) {
    (
        StatusCode::UNAUTHORIZED,
        Json(ErrorResponse {
            error: "GitHub token not configured".to_string(),
        }),
    )
}

fn github_error(e: anyhow::Error) -> (StatusCode, Json<ErrorResponse>) {
    let message = e.to_string();
    if message.contains("status 401") || message.contains("status 403") {
        return (
            StatusCode::UNAUTHORIZED,
            Json(ErrorResponse {
                error: "GitHub token is invalid or lacks required access".to_string(),
            }),
        );
    }
    (
        StatusCode::BAD_GATEWAY,
        Json(ErrorResponse { error: message }),
    )
}

pub async fn list_gists(
    State(state): State<WebState>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<ErrorResponse>)> {
    let github = state.github.read().await;
    let client = github.as_ref().ok_or_else(no_github)?;
    let gists = client.list_gists().await.map_err(github_error)?;
    Ok(Json(json!({ "gists": gists })))
}

pub async fn get_gist(
    State(state): State<WebState>,
    Path(gist_id): Path<String>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<ErrorResponse>)> {
    let github = state.github.read().await;
    let client = github.as_ref().ok_or_else(no_github)?;
    let gist = client.get_gist(&gist_id).await.map_err(github_error)?;
    Ok(Json(json!({ "gist": gist })))
}

pub async fn create_gist(
    State(state): State<WebState>,
    Json(body): Json<CreateGistRequest>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<ErrorResponse>)> {
    let github = state.github.read().await;
    let client = github.as_ref().ok_or_else(no_github)?;
    let gist = client.create_gist(&body).await.map_err(github_error)?;
    Ok(Json(json!({ "gist": gist })))
}

pub async fn update_gist(
    State(state): State<WebState>,
    Path(gist_id): Path<String>,
    Json(body): Json<UpdateGistRequest>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<ErrorResponse>)> {
    let github = state.github.read().await;
    let client = github.as_ref().ok_or_else(no_github)?;
    let gist = client
        .update_gist(&gist_id, &body)
        .await
        .map_err(github_error)?;
    Ok(Json(json!({ "gist": gist })))
}

#[cfg(test)]
mod tests {
    use super::github_error;
    use axum::http::StatusCode;

    #[test]
    fn list_gists_response_shape_wraps_gists() {
        let payload = serde_json::json!({
            "gists": [{
                "id": "123",
                "description": "notes"
            }]
        });

        assert!(payload.get("gists").unwrap().is_array());
    }

    #[test]
    fn get_gist_response_shape_wraps_gist() {
        let payload = serde_json::json!({
            "gist": {
                "id": "123",
                "files": {
                    "notes.md": {
                        "content": "hello"
                    }
                }
            }
        });

        assert!(payload.get("gist").unwrap().is_object());
    }

    #[test]
    fn github_auth_errors_map_to_unauthorized() {
        let (status, body) = github_error(anyhow::anyhow!("failed with GitHub API status 401"));
        assert_eq!(status, StatusCode::UNAUTHORIZED);
        assert_eq!(
            body.0.error,
            "GitHub token is invalid or lacks required access"
        );
    }
}