Some checks failed
CI / Rustfmt (push) Successful in 23s
CI / Cargo Audit & Deny (push) Successful in 30s
CI / Web Blocking Checks (push) Successful in 48s
CI / Security Blocking Checks (push) Successful in 8s
CI / Clippy (push) Failing after 1m55s
CI / Web Advisory Checks (push) Successful in 35s
CI / Security Advisory Checks (push) Successful in 37s
CI / Tests (push) Successful in 8m5s
132 lines
3.5 KiB
Rust
132 lines
3.5 KiB
Rust
//! Attune Notifier Service - Real-time notification delivery
|
|
|
|
use anyhow::Result;
|
|
use attune_common::config::Config;
|
|
use clap::Parser;
|
|
use tracing::{error, info};
|
|
|
|
mod postgres_listener;
|
|
mod service;
|
|
mod subscriber_manager;
|
|
mod websocket_server;
|
|
|
|
use service::NotifierService;
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(name = "attune-notifier")]
|
|
#[command(about = "Attune Notifier Service - Real-time notifications", long_about = None)]
|
|
struct Args {
|
|
/// Path to configuration file
|
|
#[arg(short, long)]
|
|
config: Option<String>,
|
|
|
|
/// Log level (trace, debug, info, warn, error)
|
|
#[arg(short, long, default_value = "info")]
|
|
log_level: String,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
// Install HMAC-only JWT crypto provider (must be before any token operations)
|
|
attune_common::auth::install_crypto_provider();
|
|
|
|
let args = Args::parse();
|
|
|
|
// Initialize tracing with specified log level
|
|
let log_level = args
|
|
.log_level
|
|
.parse::<tracing::Level>()
|
|
.unwrap_or(tracing::Level::INFO);
|
|
|
|
tracing_subscriber::fmt()
|
|
.with_max_level(log_level)
|
|
.with_target(false)
|
|
.with_thread_ids(true)
|
|
.init();
|
|
|
|
info!("Starting Attune Notifier Service");
|
|
|
|
// Load configuration
|
|
if let Some(config_path) = args.config {
|
|
std::env::set_var("ATTUNE_CONFIG", config_path);
|
|
}
|
|
|
|
let config = Config::load()?;
|
|
config.validate()?;
|
|
|
|
info!("Configuration loaded successfully");
|
|
info!("Environment: {}", config.environment);
|
|
info!("Database: {}", mask_password(&config.database.url));
|
|
|
|
let notifier_config = config
|
|
.notifier
|
|
.as_ref()
|
|
.ok_or_else(|| anyhow::anyhow!("Notifier configuration not found in config file"))?;
|
|
|
|
info!(
|
|
"Listening on: {}:{}",
|
|
notifier_config.host, notifier_config.port
|
|
);
|
|
|
|
// Create and start the notifier service
|
|
let service = NotifierService::new(config).await?;
|
|
|
|
info!("Notifier Service initialized successfully");
|
|
|
|
// Set up graceful shutdown handler
|
|
let service_clone = std::sync::Arc::new(service);
|
|
let service_for_shutdown = service_clone.clone();
|
|
|
|
tokio::spawn(async move {
|
|
tokio::signal::ctrl_c()
|
|
.await
|
|
.expect("Failed to listen for Ctrl+C");
|
|
info!("Received shutdown signal");
|
|
|
|
if let Err(e) = service_for_shutdown.shutdown().await {
|
|
error!("Error during shutdown: {}", e);
|
|
}
|
|
});
|
|
|
|
// Start the service (blocks until shutdown)
|
|
if let Err(e) = service_clone.start().await {
|
|
error!("Notifier service error: {}", e);
|
|
return Err(e);
|
|
}
|
|
|
|
info!("Attune Notifier Service stopped");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Mask password in database URL for logging
|
|
fn mask_password(url: &str) -> String {
|
|
if let Some(at_pos) = url.rfind('@') {
|
|
if let Some(colon_pos) = url[..at_pos].rfind(':') {
|
|
let mut masked = url.to_string();
|
|
masked.replace_range(colon_pos + 1..at_pos, "****");
|
|
return masked;
|
|
}
|
|
}
|
|
url.to_string()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_mask_password() {
|
|
let url = "postgresql://user:password@localhost:5432/db";
|
|
let masked = mask_password(url);
|
|
assert_eq!(masked, "postgresql://user:****@localhost:5432/db");
|
|
}
|
|
|
|
#[test]
|
|
fn test_mask_password_no_password() {
|
|
let url = "postgresql://localhost:5432/db";
|
|
let masked = mask_password(url);
|
|
assert_eq!(masked, "postgresql://localhost:5432/db");
|
|
}
|
|
}
|