gitdataai/libs/transport/lib.rs

172 lines
5.6 KiB
Rust

use crate::seq::SeqAllocator;
use config::AppConfig;
use db::cache::AppCache;
use db::database::AppDatabase;
use service::AppService;
use std::collections::HashMap;
#[derive(Clone)]
pub struct AppTransport {
pub db: AppDatabase,
pub service: AppService,
pub config: AppConfig,
pub cache: AppCache,
pub nats: Option<async_nats::Client>,
pub seq: SeqAllocator,
pub ack: ack::AckTracker,
pub reconnect: reconnect::ReconnectManager,
pub dedup: dedup::DeduplicationManager,
pub rate_limiter: security::RateLimiter,
pub csrf: security::CsrfProtection,
pub circuit_breaker: circuit_breaker::CircuitBreaker,
}
pub mod ack;
pub mod bus;
pub mod cdn;
pub mod circuit_breaker;
pub mod dedup;
pub mod e2e;
pub mod envelope;
pub mod error;
pub mod event;
pub mod handler;
pub mod metrics;
pub mod pagination;
pub mod reconnect;
pub mod richtext;
pub mod search;
pub mod security;
pub mod seq;
pub mod token;
pub mod unread;
impl AppTransport {
pub async fn new(
service: AppService,
config: AppConfig,
) -> Result<Self, crate::error::AppTransportError> {
let nats = if config.nats_is_enabled() {
let url = match config.nats_url() {
Some(u) => u,
None => {
tracing::warn!("NATS_URL not configured, running without NATS transport");
return Self::build(service, config, None);
}
};
let token = match config.nats_token() {
Some(t) => t,
None => {
tracing::warn!("NATS_TOKEN not configured, running without NATS transport");
return Self::build(service, config, None);
}
};
let opts = async_nats::ConnectOptions::with_token(token)
.retry_on_initial_connect()
.connection_timeout(std::time::Duration::from_secs(10))
.reconnect_delay_callback(|attempts| {
let base = std::time::Duration::from_secs(1);
let delay = base.saturating_mul(2u32.saturating_pow(attempts as u32));
std::cmp::min(delay, std::time::Duration::from_secs(30))
})
.event_callback(|event| async move {
match event {
async_nats::Event::Connected => {
tracing::info!("NATS connected");
}
async_nats::Event::Disconnected => {
tracing::warn!("NATS disconnected, reconnecting");
}
async_nats::Event::ServerError(e) => {
tracing::warn!(error = %e, "NATS server error");
}
_ => {}
}
});
match opts.connect(&url).await {
Ok(client) => {
tracing::info!(url = %url, "NATS connected");
Some(client)
}
Err(e) => {
tracing::warn!(error = %e, url = %url, "NATS connect failed, running without NATS transport");
None
}
}
} else {
tracing::info!("NATS not enabled (NATS_URL or NATS_TOKEN not set)");
None
};
Self::build(service, config, nats)
}
fn build(
service: AppService,
config: AppConfig,
nats: Option<async_nats::Client>,
) -> Result<Self, crate::error::AppTransportError> {
Ok(Self {
db: service.db.clone(),
cache: service.cache.clone(),
seq: SeqAllocator::new(service.cache.clone(), service.db.clone()),
ack: ack::AckTracker::new(service.cache.clone()),
reconnect: reconnect::ReconnectManager::new(service.cache.clone(), service.db.clone()),
dedup: dedup::DeduplicationManager::new(service.cache.clone()),
rate_limiter: security::RateLimiter::new(service.cache.clone()),
csrf: security::CsrfProtection::new(service.cache.clone()),
circuit_breaker: circuit_breaker::CircuitBreaker::new(),
service,
config,
nats,
})
}
pub fn nats_connected(&self) -> bool {
self.nats.is_some()
}
pub async fn publish_nats(
&self,
subject: &str,
payload: &[u8],
) -> Result<(), crate::error::AppTransportError> {
match &self.nats {
Some(client) => {
let js = async_nats::jetstream::new(client.clone());
js.publish(subject.to_string(), payload.to_vec().into())
.await
.map_err(|e| {
tracing::warn!(error = %e, subject = %subject, "NATS publish failed");
crate::error::AppTransportError::Internal
})?;
Ok(())
}
None => {
tracing::debug!(subject = %subject, "NATS not connected, skipping publish");
Ok(())
}
}
}
pub async fn seq(&self, room: models::RoomId) -> Result<i64, crate::error::AppTransportError> {
self.seq.seq(room).await
}
pub async fn bootstrap_seq(
&self,
room: models::RoomId,
) -> Result<i64, crate::error::AppTransportError> {
self.seq.bootstrap(room).await
}
pub async fn bootstrap_all_seq(
&self,
rooms: Vec<models::RoomId>,
) -> Result<HashMap<models::RoomId, i64>, crate::error::AppTransportError> {
self.seq.bootstrap_all(rooms).await
}
}