bark-rest 0.2.2

a REST server built on top of the bark-wallet crate
use axum::extract::{Path, State};
use axum::routing::{get, post};
use axum::{debug_handler, Json, Router};
use anyhow::Context;
use utoipa::OpenApi;

use bark::movement::MovementId;

use crate::error::{self, HandlerResult};
use crate::ServerState;

#[derive(OpenApi)]
#[openapi(
	paths(
		list,
		update_metadata,
	),
	components(schemas(
		bark_json::movements::Movement,
		error::InternalServerError,
		error::NotFoundError,
		error::BadRequestError,
	)),
	tags((name = "history", description = "Inspect and annotate wallet movement history."))
)]
pub struct HistoryApiDoc;

pub fn router() -> Router<ServerState> {
	Router::new()
		.route("/", get(list))
		.route("/{id}/metadata", post(update_metadata))
}

#[utoipa::path(
	get,
	path = "",
	summary = "Get wallet history",
	responses(
		(status = 200, description = "Returns the wallet history", body = Vec<bark_json::movements::Movement>),
		(status = 500, description = "Internal server error", body = error::InternalServerError)
	),
	description = "Returns the full history of wallet movements ordered from newest to \
		oldest. A movement represents any wallet operation that affects VTXOs—an arkoor \
		send or receive, Lightning send or receive, board, offboard, or refresh. Each \
		entry records which VTXOs were consumed and produced, the effective balance \
		change (if any), fees paid, and the operation status.",
	tag = "history"
)]
#[debug_handler]
pub async fn list(
	State(state): State<ServerState>,
) -> HandlerResult<Json<Vec<bark_json::movements::Movement>>> {
	let wallet = state.require_wallet()?;
	let movements = wallet.history().await.context("Failed to get movements")?;

	let json_movements = movements
		.into_iter()
		.map(|m| bark_json::movements::Movement::try_from(m)
			.context("Failed to convert movement to JSON")
		).collect::<Result<Vec<_>, _>>()?;

	Ok(axum::Json(json_movements))
}

#[utoipa::path(
	post,
	path = "/{id}/metadata",
	summary = "Patch movement metadata",
	params(
		("id" = u32, Path, description = "Movement identifier."),
	),
	request_body(
		content = serde_json::Value,
		content_type = "application/merge-patch+json",
		description = "RFC 7396 JSON Merge Patch. The body is applied directly to the \
			movement's metadata object: any field with value `null` is removed, every \
			other field is recursively merged.",
	),
	responses(
		(status = 200, description = "Metadata updated"),
		(status = 500, description = "Internal server error", body = error::InternalServerError),
	),
	description = "Applies an [RFC 7396](https://www.rfc-editor.org/rfc/rfc7396) JSON Merge \
		Patch to a movement's metadata. Use this to annotate history entries after the fact \
		(e.g. refund notes, counterparty info). Keys set to `null` are removed; other values \
		are recursively merged.",
	tag = "history"
)]
#[debug_handler]
pub async fn update_metadata(
	State(state): State<ServerState>,
	Path(id): Path<u32>,
	Json(patch): Json<serde_json::Value>,
) -> HandlerResult<()> {
	let wallet = state.require_wallet()?;
	wallet.update_history_metadata(MovementId::new(id), &patch).await?;
	Ok(())
}