218 lines
6.6 KiB
Rust
218 lines
6.6 KiB
Rust
use std::sync::Arc;
|
|
|
|
use ::agent::task::service::TaskService;
|
|
use avatar::AppAvatar;
|
|
use config::AppConfig;
|
|
use db::cache::AppCache;
|
|
use db::database::AppDatabase;
|
|
use email::AppEmail;
|
|
use queue::{
|
|
start_email_worker, EmailEnvelope, EmailSendFn, EmailSendFut, GetRedis, MessageProducer, RedisFuture,
|
|
RedisPubSub,
|
|
};
|
|
use room::metrics::RoomMetrics;
|
|
use room::RoomService;
|
|
use serde::{Deserialize, Serialize};
|
|
use slog::{Drain, OwnedKVList, Record};
|
|
use utoipa::ToSchema;
|
|
use ws_token::WsTokenService;
|
|
|
|
#[derive(Clone)]
|
|
pub struct AppService {
|
|
pub db: AppDatabase,
|
|
pub config: AppConfig,
|
|
pub cache: AppCache,
|
|
pub email: AppEmail,
|
|
pub logs: slog::Logger,
|
|
pub avatar: AppAvatar,
|
|
pub room: RoomService,
|
|
pub ws_token: Arc<WsTokenService>,
|
|
pub queue_producer: MessageProducer,
|
|
}
|
|
|
|
impl AppService {
|
|
pub async fn start_room_workers(
|
|
&self,
|
|
shutdown_rx: tokio::sync::broadcast::Receiver<()>,
|
|
log: slog::Logger,
|
|
) -> anyhow::Result<()> {
|
|
self.room.start_workers(shutdown_rx, log).await
|
|
}
|
|
|
|
pub fn build_slog_logger(level: &str) -> slog::Logger {
|
|
let level_filter = match level {
|
|
"trace" => 0usize,
|
|
"debug" => 1usize,
|
|
"info" => 2usize,
|
|
"warn" => 3usize,
|
|
"error" => 4usize,
|
|
_ => 2usize,
|
|
};
|
|
|
|
struct StderrDrain(usize);
|
|
|
|
impl Drain for StderrDrain {
|
|
type Ok = ();
|
|
type Err = ();
|
|
#[inline]
|
|
fn log(&self, record: &Record, _logger: &OwnedKVList) -> Result<(), ()> {
|
|
let slog_level = match record.level() {
|
|
slog::Level::Trace => 0,
|
|
slog::Level::Debug => 1,
|
|
slog::Level::Info => 2,
|
|
slog::Level::Warning => 3,
|
|
slog::Level::Error => 4,
|
|
slog::Level::Critical => 5,
|
|
};
|
|
if slog_level < self.0 {
|
|
return Ok(());
|
|
}
|
|
let _ = eprintln!(
|
|
"{} [{}] {}:{} - {}",
|
|
chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ"),
|
|
record.level().to_string(),
|
|
record
|
|
.file()
|
|
.rsplit_once('/')
|
|
.map(|(_, s)| s)
|
|
.unwrap_or(record.file()),
|
|
record.line(),
|
|
record.msg(),
|
|
);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
let drain = StderrDrain(level_filter);
|
|
let drain = std::sync::Mutex::new(drain);
|
|
let drain = slog::Fuse::new(drain);
|
|
slog::Logger::root(drain, slog::o!())
|
|
}
|
|
|
|
pub async fn new(config: AppConfig) -> anyhow::Result<Self> {
|
|
let db = AppDatabase::init(&config).await?;
|
|
let cache = AppCache::init(&config).await?;
|
|
let email = AppEmail::init(&config).await?;
|
|
let avatar = AppAvatar::init(&config).await?;
|
|
|
|
let log_level = config.log_level().unwrap_or_else(|_| "info".to_string());
|
|
let logs = Self::build_slog_logger(&log_level);
|
|
|
|
// Build get_redis closure for MessageProducer
|
|
let get_redis: Arc<
|
|
dyn Fn() -> tokio::task::JoinHandle<anyhow::Result<deadpool_redis::cluster::Connection>>
|
|
+ Send
|
|
+ Sync,
|
|
> = Arc::new({
|
|
let pool = cache.redis_pool().clone();
|
|
move || {
|
|
let pool = pool.clone();
|
|
tokio::spawn(async move { pool.get().await.map_err(|e| anyhow::anyhow!("{}", e)) })
|
|
}
|
|
});
|
|
|
|
let redis_pubsub = Some(RedisPubSub {
|
|
get_redis: get_redis.clone(),
|
|
log: logs.clone(),
|
|
});
|
|
|
|
let message_producer =
|
|
MessageProducer::new(get_redis.clone(), redis_pubsub.clone(), 10000, logs.clone());
|
|
|
|
// Build RoomService
|
|
let task_service = Arc::new(TaskService::new(db.clone()));
|
|
let room_metrics = Arc::new(RoomMetrics::default());
|
|
let room_manager = Arc::new(room::connection::RoomConnectionManager::new(
|
|
room_metrics.clone(),
|
|
));
|
|
|
|
let redis_url = config
|
|
.redis_urls()
|
|
.ok()
|
|
.and_then(|urls| urls.first().cloned())
|
|
.unwrap_or_else(|| "redis://127.0.0.1:6379".to_string());
|
|
|
|
let room = RoomService::new(
|
|
db.clone(),
|
|
cache.clone(),
|
|
message_producer.clone(),
|
|
room_manager,
|
|
redis_url,
|
|
None,
|
|
Some(task_service.clone()),
|
|
logs.clone(),
|
|
None,
|
|
);
|
|
|
|
// Build WsTokenService
|
|
let ws_token = Arc::new(WsTokenService::new(get_redis.clone()));
|
|
|
|
Ok(Self {
|
|
db,
|
|
config,
|
|
cache,
|
|
email,
|
|
logs,
|
|
avatar,
|
|
room,
|
|
ws_token,
|
|
queue_producer: message_producer,
|
|
})
|
|
}
|
|
|
|
pub async fn start_email_workers(
|
|
&self,
|
|
shutdown_rx: tokio::sync::broadcast::Receiver<()>,
|
|
) -> anyhow::Result<()> {
|
|
let get_redis_fn = self.queue_producer.get_redis.clone();
|
|
let get_redis: GetRedis = Arc::new(move || -> RedisFuture {
|
|
let get_redis_fn = get_redis_fn.clone();
|
|
Box::pin(async move { get_redis_fn().await? })
|
|
});
|
|
|
|
let email = self.email.clone();
|
|
let logs = self.logs.clone();
|
|
let send_fn: EmailSendFn = Arc::new(move |envelopes: Vec<EmailEnvelope>| -> EmailSendFut {
|
|
let email = email.clone();
|
|
let logs = logs.clone();
|
|
Box::pin(async move {
|
|
for envelope in envelopes {
|
|
let to = envelope.to.clone();
|
|
let msg = email::EmailMessage {
|
|
to: envelope.to,
|
|
subject: envelope.subject,
|
|
body: envelope.body,
|
|
};
|
|
if let Err(e) = email.send(msg).await {
|
|
slog::error!(logs, "email send failed"; "to" => to, "error" => %e);
|
|
}
|
|
}
|
|
Ok(())
|
|
})
|
|
});
|
|
|
|
start_email_worker(get_redis, send_fn, shutdown_rx, self.logs.clone()).await;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub mod agent;
|
|
pub mod auth;
|
|
pub mod error;
|
|
pub mod git;
|
|
pub mod issue;
|
|
pub mod project;
|
|
pub mod pull_request;
|
|
pub mod search;
|
|
pub mod skill;
|
|
pub mod user;
|
|
pub mod utils;
|
|
pub mod workspace;
|
|
pub mod ws_token;
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
|
pub struct Pager {
|
|
pub page: i64,
|
|
pub par_page: i64,
|
|
}
|