maincopy-server 0.1.0

Self-hosted publishing server with exact previews and explicit release approval
Documentation
use axum::{
    Router,
    body::{Body, to_bytes},
    http::{HeaderMap, Method, Request},
    response::Response,
};
use serde_json::Value;
use tower::ServiceExt;

use maincopy_server::{
    domain::publication::PublicLedgerProjection,
    frontend_assets::embedded_manifest,
    render::{SiteSnapshotReader, compile_content_catalog, render_site_shell},
    web::{PublicState, Readiness},
};
use markdown_compiler::{ContentTreeLimits, discover_content_tree, prepare_content};

pub fn public_state(readiness: Readiness) -> PublicState {
    let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/content");
    let tree = discover_content_tree(&root, ContentTreeLimits::default())
        .expect("example content tree must be discoverable");
    let content = prepare_content(&tree).expect("example content and assets must prepare");
    let catalog = std::sync::Arc::new(
        compile_content_catalog(&content).expect("example catalog must compile"),
    );
    let ledger = PublicLedgerProjection::empty();
    let shell = render_site_shell(catalog, embedded_manifest(), &ledger)
        .expect("empty public shell must render");
    let snapshot = shell
        .into_snapshot()
        .expect("empty public snapshot must build");
    PublicState {
        snapshots: SiteSnapshotReader::from_snapshot(snapshot),
        readiness,
    }
}

pub async fn get(app: Router, path: &str) -> Response {
    request(app, Method::GET, path).await
}

pub async fn request(app: Router, method: Method, path: &str) -> Response {
    request_with_headers(app, method, path, HeaderMap::new()).await
}

pub async fn request_with_headers(
    app: Router,
    method: Method,
    path: &str,
    headers: HeaderMap,
) -> Response {
    let mut request = Request::builder()
        .method(method)
        .uri(path)
        .body(Body::empty())
        .expect("test request must be valid");
    *request.headers_mut() = headers;

    app.oneshot(request)
        .await
        .expect("router must produce a response")
}

pub async fn body_bytes(response: Response) -> axum::body::Bytes {
    to_bytes(response.into_body(), usize::MAX)
        .await
        .expect("response body must be readable")
}

pub async fn json_body(response: Response) -> Value {
    let body = body_bytes(response).await;

    serde_json::from_slice(&body).expect("response body must be valid JSON")
}