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/test_purchase.rs

//! **`TestPurchase`** — the operator's test button over [`crate::test_purchase`].
//! Thin: the operator gate, the blocking pool, the run's view.

use std::sync::Arc;

use monetize_proto as pb;
use monetize_proto::test_purchase_server::TestPurchase;
use tonic::{Request, Response, Status};

use crate::state::AppState;

pub struct TestPurchaseService(pub Arc<AppState>);

/// **The operator's door only.** The interceptor puts the marker on an
/// operator-bearer call and on a control caller (the ONE box's console);
/// anything else — the service bearer a product console holds — is refused.
fn operator<T>(req: &Request<T>) -> Result<(), Status> {
    if req.extensions().get::<crate::auth::Operator>().is_some() {
        return Ok(());
    }
    Err(Status::permission_denied(
        "the test button is the operator's: the operator bearer, or the console's signed call",
    ))
}

async fn on_pool<T: Send + 'static>(
    state: &Arc<AppState>,
    f: impl FnOnce(&Arc<AppState>) -> Result<T, Status> + Send + 'static,
) -> Result<T, Status> {
    let state = state.clone();
    tokio::task::spawn_blocking(move || f(&state))
        .await
        .map_err(|e| Status::internal(format!("handler panicked: {e}")))?
}

#[tonic::async_trait]
impl TestPurchase for TestPurchaseService {
    async fn actions(
        &self,
        req: Request<pb::TestActionsRequest>,
    ) -> Result<Response<pb::TestActionsResponse>, Status> {
        operator(&req)?;
        let product = req.into_inner().product;
        let actions = on_pool(&self.0, move |s| {
            Ok(crate::test_purchase::actions(s, product.trim()))
        })
        .await?;
        Ok(Response::new(pb::TestActionsResponse { actions }))
    }

    async fn start(
        &self,
        req: Request<pb::TestPurchaseStartRequest>,
    ) -> Result<Response<pb::TestPurchaseRun>, Status> {
        operator(&req)?;
        let r = req.into_inner();
        let run = on_pool(&self.0, move |s| {
            let run =
                crate::test_purchase::start(s, r.product.trim(), r.action.trim(), &r.site, r.live)?;
            if run.kind == "private_instance" && run.refused.is_empty() {
                // Nudged as `EstateOrder.Start` nudges: the order is driven from here on by the tick.
                crate::estate_order::tick(s);
            }
            Ok(crate::test_purchase::view(s, &run))
        })
        .await?;
        Ok(Response::new(run))
    }

    async fn list(
        &self,
        req: Request<pb::TestPurchaseListRequest>,
    ) -> Result<Response<pb::TestPurchaseListResponse>, Status> {
        operator(&req)?;
        let limit = req.into_inner().limit as usize;
        let runs = on_pool(&self.0, move |s| {
            Ok(s.tests
                .list(limit)
                .iter()
                .map(|r| crate::test_purchase::view(s, r))
                .collect())
        })
        .await?;
        Ok(Response::new(pb::TestPurchaseListResponse { runs }))
    }

    async fn status(
        &self,
        req: Request<pb::TestPurchaseStatusRequest>,
    ) -> Result<Response<pb::TestPurchaseRun>, Status> {
        operator(&req)?;
        let id = req.into_inner().id;
        let run = on_pool(&self.0, move |s| {
            let run = s
                .tests
                .get(id.trim())
                .ok_or_else(|| Status::not_found(format!("no test run {id:?}")))?;
            Ok(crate::test_purchase::view(s, &run))
        })
        .await?;
        Ok(Response::new(run))
    }
}