use std::sync::Arc;
use async_trait::async_trait;
use axum::response::ErrorResponse;
use http::StatusCode;
use spacetimedb::address::Address;
use spacetimedb::auth::identity::{DecodingKey, EncodingKey};
use spacetimedb::client::ClientActorIndex;
use spacetimedb::database_instance_context_controller::DatabaseInstanceContextController;
use spacetimedb::energy::{EnergyBalance, EnergyQuanta};
use spacetimedb::host::{HostController, UpdateDatabaseResult};
use spacetimedb::identity::Identity;
use spacetimedb::messages::control_db::{Database, DatabaseInstance, IdentityEmail, Node};
use spacetimedb::module_host_context::ModuleHostContext;
use spacetimedb::sendgrid_controller::SendGridController;
use spacetimedb_client_api_messages::name::{DomainName, InsertDomainResult, RegisterTldResult, Tld};
use spacetimedb_client_api_messages::recovery::RecoveryCode;
pub mod auth;
pub mod routes;
pub mod util;
#[async_trait]
pub trait NodeDelegate: Send + Sync {
fn gather_metrics(&self) -> Vec<prometheus::proto::MetricFamily>;
fn database_instance_context_controller(&self) -> &DatabaseInstanceContextController;
fn host_controller(&self) -> &Arc<HostController>;
fn client_actor_index(&self) -> &ClientActorIndex;
fn sendgrid_controller(&self) -> Option<&SendGridController>;
fn public_key(&self) -> &DecodingKey;
fn public_key_bytes(&self) -> &[u8];
fn private_key(&self) -> &EncodingKey;
async fn load_module_host_context(&self, db: Database, instance_id: u64) -> anyhow::Result<ModuleHostContext>;
}
pub struct DatabaseDef {
pub address: Address,
pub program_bytes: Vec<u8>,
pub num_replicas: u32,
}
#[async_trait]
pub trait ControlStateDelegate: ControlStateReadAccess + ControlStateWriteAccess + Send + Sync {}
impl<T: ControlStateReadAccess + ControlStateWriteAccess + Send + Sync> ControlStateDelegate for T {}
pub trait ControlStateReadAccess {
fn get_node_id(&self) -> Option<u64>;
fn get_node_by_id(&self, node_id: u64) -> anyhow::Result<Option<Node>>;
fn get_nodes(&self) -> anyhow::Result<Vec<Node>>;
fn get_database_by_id(&self, id: u64) -> anyhow::Result<Option<Database>>;
fn get_database_by_address(&self, address: &Address) -> anyhow::Result<Option<Database>>;
fn get_databases(&self) -> anyhow::Result<Vec<Database>>;
fn get_database_instance_by_id(&self, id: u64) -> anyhow::Result<Option<DatabaseInstance>>;
fn get_database_instances(&self) -> anyhow::Result<Vec<DatabaseInstance>>;
fn get_leader_database_instance_by_database(&self, database_id: u64) -> Option<DatabaseInstance>;
fn get_identities_for_email(&self, email: &str) -> anyhow::Result<Vec<IdentityEmail>>;
fn get_emails_for_identity(&self, identity: &Identity) -> anyhow::Result<Vec<IdentityEmail>>;
fn get_recovery_codes(&self, email: &str) -> anyhow::Result<Vec<RecoveryCode>>;
fn get_energy_balance(&self, identity: &Identity) -> anyhow::Result<Option<EnergyBalance>>;
fn lookup_address(&self, domain: &DomainName) -> anyhow::Result<Option<Address>>;
fn reverse_lookup(&self, address: &Address) -> anyhow::Result<Vec<DomainName>>;
}
#[async_trait]
pub trait ControlStateWriteAccess: Send + Sync {
async fn create_address(&self) -> anyhow::Result<Address>;
async fn publish_database(
&self,
identity: &Identity,
publisher_address: Option<Address>,
spec: DatabaseDef,
) -> anyhow::Result<Option<UpdateDatabaseResult>>;
async fn delete_database(&self, identity: &Identity, address: &Address) -> anyhow::Result<()>;
async fn create_identity(&self) -> anyhow::Result<Identity>;
async fn add_email(&self, identity: &Identity, email: &str) -> anyhow::Result<()>;
async fn insert_recovery_code(&self, identity: &Identity, email: &str, code: RecoveryCode) -> anyhow::Result<()>;
async fn add_energy(&self, identity: &Identity, amount: EnergyQuanta) -> anyhow::Result<()>;
async fn withdraw_energy(&self, identity: &Identity, amount: EnergyQuanta) -> anyhow::Result<()>;
async fn register_tld(&self, identity: &Identity, tld: Tld) -> anyhow::Result<RegisterTldResult>;
async fn create_dns_record(
&self,
identity: &Identity,
domain: &DomainName,
address: &Address,
) -> anyhow::Result<InsertDomainResult>;
}
impl<T: ControlStateReadAccess + ?Sized> ControlStateReadAccess for Arc<T> {
fn get_node_id(&self) -> Option<u64> {
(**self).get_node_id()
}
fn get_node_by_id(&self, node_id: u64) -> anyhow::Result<Option<Node>> {
(**self).get_node_by_id(node_id)
}
fn get_nodes(&self) -> anyhow::Result<Vec<Node>> {
(**self).get_nodes()
}
fn get_database_by_id(&self, id: u64) -> anyhow::Result<Option<Database>> {
(**self).get_database_by_id(id)
}
fn get_database_by_address(&self, address: &Address) -> anyhow::Result<Option<Database>> {
(**self).get_database_by_address(address)
}
fn get_databases(&self) -> anyhow::Result<Vec<Database>> {
(**self).get_databases()
}
fn get_database_instance_by_id(&self, id: u64) -> anyhow::Result<Option<DatabaseInstance>> {
(**self).get_database_instance_by_id(id)
}
fn get_database_instances(&self) -> anyhow::Result<Vec<DatabaseInstance>> {
(**self).get_database_instances()
}
fn get_leader_database_instance_by_database(&self, database_id: u64) -> Option<DatabaseInstance> {
(**self).get_leader_database_instance_by_database(database_id)
}
fn get_identities_for_email(&self, email: &str) -> anyhow::Result<Vec<IdentityEmail>> {
(**self).get_identities_for_email(email)
}
fn get_emails_for_identity(&self, identity: &Identity) -> anyhow::Result<Vec<IdentityEmail>> {
(**self).get_emails_for_identity(identity)
}
fn get_recovery_codes(&self, email: &str) -> anyhow::Result<Vec<RecoveryCode>> {
(**self).get_recovery_codes(email)
}
fn get_energy_balance(&self, identity: &Identity) -> anyhow::Result<Option<EnergyBalance>> {
(**self).get_energy_balance(identity)
}
fn lookup_address(&self, domain: &DomainName) -> anyhow::Result<Option<Address>> {
(**self).lookup_address(domain)
}
fn reverse_lookup(&self, address: &Address) -> anyhow::Result<Vec<DomainName>> {
(**self).reverse_lookup(address)
}
}
#[async_trait]
impl<T: ControlStateWriteAccess + ?Sized> ControlStateWriteAccess for Arc<T> {
async fn create_address(&self) -> anyhow::Result<Address> {
(**self).create_address().await
}
async fn publish_database(
&self,
identity: &Identity,
publisher_address: Option<Address>,
spec: DatabaseDef,
) -> anyhow::Result<Option<UpdateDatabaseResult>> {
(**self).publish_database(identity, publisher_address, spec).await
}
async fn delete_database(&self, identity: &Identity, address: &Address) -> anyhow::Result<()> {
(**self).delete_database(identity, address).await
}
async fn create_identity(&self) -> anyhow::Result<Identity> {
(**self).create_identity().await
}
async fn add_email(&self, identity: &Identity, email: &str) -> anyhow::Result<()> {
(**self).add_email(identity, email).await
}
async fn insert_recovery_code(&self, identity: &Identity, email: &str, code: RecoveryCode) -> anyhow::Result<()> {
(**self).insert_recovery_code(identity, email, code).await
}
async fn add_energy(&self, identity: &Identity, amount: EnergyQuanta) -> anyhow::Result<()> {
(**self).add_energy(identity, amount).await
}
async fn withdraw_energy(&self, identity: &Identity, amount: EnergyQuanta) -> anyhow::Result<()> {
(**self).withdraw_energy(identity, amount).await
}
async fn register_tld(&self, identity: &Identity, tld: Tld) -> anyhow::Result<RegisterTldResult> {
(**self).register_tld(identity, tld).await
}
async fn create_dns_record(
&self,
identity: &Identity,
domain: &DomainName,
address: &Address,
) -> anyhow::Result<InsertDomainResult> {
(**self).create_dns_record(identity, domain, address).await
}
}
#[async_trait]
impl<T: NodeDelegate + ?Sized> NodeDelegate for Arc<T> {
fn gather_metrics(&self) -> Vec<prometheus::proto::MetricFamily> {
(**self).gather_metrics()
}
fn database_instance_context_controller(&self) -> &DatabaseInstanceContextController {
(**self).database_instance_context_controller()
}
fn host_controller(&self) -> &Arc<HostController> {
(**self).host_controller()
}
fn client_actor_index(&self) -> &ClientActorIndex {
(**self).client_actor_index()
}
fn public_key(&self) -> &DecodingKey {
(**self).public_key()
}
fn public_key_bytes(&self) -> &[u8] {
(**self).public_key_bytes()
}
fn private_key(&self) -> &EncodingKey {
(**self).private_key()
}
fn sendgrid_controller(&self) -> Option<&SendGridController> {
(**self).sendgrid_controller()
}
async fn load_module_host_context(&self, db: Database, instance_id: u64) -> anyhow::Result<ModuleHostContext> {
(**self).load_module_host_context(db, instance_id).await
}
}
pub fn log_and_500(e: impl std::fmt::Display) -> ErrorResponse {
log::error!("internal error: {e:#}");
(StatusCode::INTERNAL_SERVER_ERROR, format!("{e:#}")).into()
}