plane 0.5.5

Session backend orchestrator for ambitious browser-based apps.
Documentation
use crate::database::{connect::ConnectError, PlaneDatabase};
use chrono::{DateTime, Utc};
use plane_common::{
    names::{AnyNodeName, ControllerName},
    typed_socket::Handshake,
    types::{ClusterName, ConnectRequest, ConnectResponse, NodeId},
    PlaneClient,
};
use std::net::IpAddr;
use url::Url;

#[derive(Clone)]
pub struct Controller {
    pub db: PlaneDatabase,
    pub id: ControllerName,
    pub client: PlaneClient,
    pub default_cluster: Option<ClusterName>,
}

pub struct NodeHandle {
    pub id: NodeId,
    pub controller: ControllerName,
    db: Option<PlaneDatabase>,
    connection_start_time: DateTime<Utc>,
}

impl Drop for NodeHandle {
    fn drop(&mut self) {
        let db = self
            .db
            .take()
            .expect("self.db is always Some before dropped.");
        let id = self.id;
        let controller = self.controller.clone();
        let connection_start_time = self.connection_start_time;
        tokio::spawn(async move {
            if let Err(err) = db
                .node()
                .mark_offline(id, &controller, connection_start_time)
                .await
            {
                tracing::error!(?err, "Failed to mark node offline.");
            }
        });
    }
}

impl Controller {
    pub async fn register_node(
        &self,
        handshake: Handshake,
        cluster: Option<&ClusterName>,
        ip: IpAddr,
    ) -> Result<NodeHandle, sqlx::Error> {
        let name = AnyNodeName::try_from(handshake.name).map_err(|_| {
            sqlx::Error::Decode("Failed to decode node name from handshake.".into())
        })?;
        let kind = name.kind();
        let plane_version = handshake.version;

        let (node_id, connection_start_time) = self
            .db
            .node()
            .register(cluster, &name, kind, &self.id, &plane_version, ip)
            .await?;

        Ok(NodeHandle {
            id: node_id,
            db: Some(self.db.clone()),
            controller: self.id.clone(),
            connection_start_time,
        })
    }

    pub async fn new(
        db: PlaneDatabase,
        id: ControllerName,
        controller_url: Url,
        default_cluster: Option<ClusterName>,
    ) -> Self {
        let client = PlaneClient::new(controller_url);

        Self {
            db,
            id,
            client,
            default_cluster,
        }
    }

    pub async fn connect(
        &self,
        connect_request: &ConnectRequest,
    ) -> Result<ConnectResponse, ConnectError> {
        let response = self
            .db
            .connect(self.default_cluster.as_ref(), connect_request, &self.client)
            .await?;

        Ok(response)
    }
}