1mod cli;
6mod db;
7mod models;
8mod routes;
9mod tests;
10mod utility;
11
12use crate::db::schema;
13use colored::Colorize;
14use quorum_core::db as core_db;
15use quorum_core::startup;
16use reqwest::Client;
17use routes::route::create_router;
18use std::net::SocketAddr;
19use std::time::Duration;
20use std::time::Instant;
21
22#[tokio::main]
23async fn main() {
24 print!("\x1B[2J\x1B[1;1H");
26
27 startup::print_banner();
28 startup::print_initializing();
29
30 let timer = startup::create_timer();
31 match quorum_core::utility::config::Config::load() {
32 Ok(_) => startup::print_step("Loading config", true, startup::elapsed(timer)),
33 Err(e) => {
34 startup::print_step("Loading config", false, startup::elapsed(timer));
35 eprintln!("{}", format!(" Error: {}", e).red());
36 std::process::exit(1);
37 }
38 };
39
40 let config = quorum_core::utility::config::Config::get();
42
43 let timer = startup::create_timer();
45 let _db = match core_db::init().await {
46 Ok(db) => {
47 startup::print_step("Opening database", true, startup::elapsed(timer));
48 db
49 }
50 Err(e) => {
51 startup::print_step("Opening database", false, startup::elapsed(timer));
52 eprintln!("{}", format!(" Error: {}", e).red());
53 std::process::exit(1);
54 }
55 };
56
57 let timer = startup::create_timer();
59 match schema::init(&_db).await {
60 Ok(_) => {
61 startup::print_step("Initializing schema", true, startup::elapsed(timer));
62 let _ = core_db::queries::server_logs::log_startup(
63 &_db,
64 startup::elapsed(timer).as_millis() as i64,
65 )
66 .await;
67 }
68 Err(e) => {
69 startup::print_step("Initializing schema", false, startup::elapsed(timer));
70 eprintln!("{}", format!(" Error: {}", e).red());
71 let _ = core_db::queries::server_logs::log_error(&_db, e.to_string(), 0).await;
72 std::process::exit(1);
73 }
74 }
75
76 startup::print_ready(config.server_port);
77
78 let server_start = Instant::now();
81
82 let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(false);
84
85 let app = create_router(_db.clone());
87 let host = config
88 .server_host
89 .parse::<std::net::IpAddr>()
90 .expect("Invalid SERVER_HOST IP address");
91 let addr = SocketAddr::from((host, config.server_port));
92 let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
93 let server = axum::serve(
94 listener,
95 app.into_make_service_with_connect_info::<SocketAddr>(),
96 );
97
98 let mut server_task = tokio::spawn(async move {
99 server
100 .with_graceful_shutdown(async move {
101 let _ = shutdown_rx.changed().await;
102 })
103 .await
104 });
105
106 let health_url = format!(
107 "http://{}:{}/health",
108 config.server_host, config.server_port
109 );
110 let client = Client::new();
111 let mut is_ready = false;
112 let max_retries = 20;
113 let retry_delay = Duration::from_millis(50);
114
115 for _ in 0..max_retries {
116 match client.get(&health_url).send().await {
117 Ok(response) if response.status().is_success() => {
118 is_ready = true;
119 break;
120 }
121 Ok(_) => {
122 tokio::time::sleep(retry_delay).await;
123 continue;
124 }
125 Err(_) => {
126 tokio::time::sleep(retry_delay).await;
127 continue;
128 }
129 }
130 }
131
132 if !is_ready {
133 eprintln!("{}", "Server failed to start in time.".red());
134 std::process::exit(1);
135 }
136
137 if config.enable_testing {
139 tests::run_all_tests().await;
140 }
141
142 cli::spawn_cli(_db.clone(), server_start, shutdown_tx).await;
144
145 #[cfg(not(debug_assertions))]
146 {
147 loop {
148 tokio::select! {
149 _ = &mut server_task => {
150 break;
151 }
152
153 result = tokio::signal::ctrl_c() => {
154 match result {
155 Ok(()) => {
156 println!(
157 "\nYou cannot shut down the server with Ctrl+C. Use server:shutdown."
158 );
159 },
160 Err(_) => {
161 println!("{}", "Failed to listen for Ctrl+C".red());
162 }
163 }
164 }
165 }
166 }
167 }
168
169 #[cfg(debug_assertions)]
170 {
171 let shutdown = async {
172 let _ = tokio::signal::ctrl_c().await;
173 };
174
175 tokio::select! {
176 _ = &mut server_task => {},
177 _ = shutdown => {
178 println!("\nShutting down...");
179 let _ = quorum_core::db::queries::server_logs::log_shutdown(&_db, server_start.elapsed().as_millis() as i64).await;
180 }
181 }
182 }
183}