re-uploading work

This commit is contained in:
2026-02-04 17:46:30 -06:00
commit 3b14c65998
1388 changed files with 381262 additions and 0 deletions

128
crates/notifier/src/main.rs Normal file
View File

@@ -0,0 +1,128 @@
//! 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<()> {
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");
}
}