Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-server/src/services/mod.rs

//! The gRPC services over [`AppState`](crate::state::AppState). Every handler
//! that touches a provider or the ledger runs on `spawn_blocking`: the contracts
//! are sync (the caller owns the runtime), and redb is a blocking store.

/// The operator's read of the REFUSED vendor callbacks — the bounded ring in
/// [`crate::refusals`], which is the only thing a delivery monetize threw away
/// leaves behind.
/// **ONE monetize's accounts**: every `(product, tenant)`, the vendor
/// instance that bills it, and its rows.
pub mod accounts;
pub mod callback_admin;
pub mod entitlement;
/// **The estate, drawn**: one board, one chip per machine, rendered here
/// because this is the only process that holds both halves of it — the
/// declaration in `[machines]` and whatever the plugin read off the boxes.
pub mod estate;
/// **A private gunnar, ordered**: `EstateOrder.Start` and `.Status` over
/// [`crate::estate_order`].
pub mod estate_order;
/// **The estates that exist, and the New-estate slot** (OVW-CLEAN): `Estates.Overview` and
/// `.NewEstate` over [`crate::estate_order::standing`] and [`crate::estate_order::new_estate`].
pub mod estates;
pub mod events;
/// The pull channel (`Fleet`) and the estate guardian's console (`Guardian`) over
/// [`crate::fleet`].
pub mod fleet;
/// The data-set growth's console: status, quote, the operator's approval,
/// resume — over [`crate::grow`].
pub mod ledger;
pub mod products;
pub mod purchase;
/// The operator's read of what the VENDOR says it took and monetize did not
/// fulfil — the durable rows in [`crate::reconcile`].
pub mod reconcile_admin;
/// The paid-signup seam's read side: the return page's poll and the operator's list.
pub mod signup;
/// **The operator's SITE BOOK** over the linked [`crate::site_book::SiteBook`].
pub mod site_book;
/// The tax return, read side: the periods a year is filed in, and one filled form.
pub mod tax;
// The ONE box's plugins from the release bucket, as a queued job (2026-10-17).
pub mod plugins;
/// **The operator's TEST BUTTON** over [`crate::test_purchase`].
pub mod test_purchase;
pub mod usage;

use std::sync::Arc;

use tonic::Status;

use crate::state::AppState;

/// Run `f` on the blocking pool with the state.
pub async fn blocking<T, F>(state: &Arc<AppState>, f: F) -> Result<T, Status>
where
    T: Send + 'static,
    F: FnOnce(&AppState) -> Result<T, Status> + Send + 'static,
{
    let state = state.clone();
    tokio::task::spawn_blocking(move || f(&state))
        .await
        .map_err(|e| Status::internal(format!("handler panicked: {e}")))?
}

pub fn ledger_err(e: monetize::ledger::LedgerError) -> Status {
    Status::internal(format!("ledger: {e}"))
}

/// **A plugin's refusal, as a `Status` that keeps the two arms apart.**
///
/// `Unavailable` is the retryable one — the product's box is down, nothing was
/// read — and it maps to gRPC's own `UNAVAILABLE` so a client and any proxy
/// read it as transient. `Refused` is the product saying no: an unknown tenant,
/// a credential it will not accept, a verb it does not have. Collapsing the two
/// into `internal` would make "try again in a moment" and "this will never
/// work" the same sentence.
pub fn product_err(e: monetize_product::ProductError) -> Status {
    match e {
        monetize_product::ProductError::Unavailable(why) => Status::unavailable(why),
        monetize_product::ProductError::Refused(why) => Status::failed_precondition(why),
    }
}

pub fn product_or_not_found<'a>(
    state: &'a AppState,
    id: &str,
) -> Result<monetize_product::Served<'a>, Status> {
    state.registry.find_product(id).ok_or_else(|| {
        Status::not_found(format!(
            "product {id:?} is not served here; linked: {:?}",
            state.registry.product_ids()
        ))
    })
}