1use crate::config::DatabaseConfig;
2use sqlx_core::migrate::Migrator;
3use sqlx_postgres::{PgPool, PgPoolOptions};
4use std::path::{Path, PathBuf};
5use std::time::{Duration, Instant};
6use tokio::time::sleep;
7use tracing::{info, warn};
8
9pub async fn setup_database(config: &DatabaseConfig) -> Result<PgPool, anyhow::Error> {
18 let retry_deadline = Duration::from_secs(60); let max_interval = Duration::from_secs(30); let mut delay = Duration::from_millis(500);
21 let start = Instant::now();
22
23 let pool = loop {
24 info!("Attempting to connect to Postgres...");
25
26 match PgPoolOptions::new()
27 .max_connections(config.max_connections)
28 .acquire_timeout(Duration::from_secs(30))
30 .connect(&config.url)
31 .await
32 {
33 Ok(pool) => break pool,
34 Err(err) => {
35 if start.elapsed() >= retry_deadline {
36 warn!(error = %err, "Postgres not ready; retries exhausted");
37 return Err(err.into());
38 }
39
40 warn!(error = %err, "Postgres not ready yet; retrying");
41 sleep(delay).await;
42 delay = (delay.saturating_mul(2)).min(max_interval);
43 }
44 }
45 };
46
47 let candidate_dirs = [
53 config.migrations_dir.as_ref().map(PathBuf::from),
54 Some(PathBuf::from("./migrations")),
55 Some(PathBuf::from(concat!(
56 env!("CARGO_MANIFEST_DIR"),
57 "/migrations"
58 ))),
59 ];
60
61 let mut last_error = None;
62 let mut migrator = None;
63
64 for dir in candidate_dirs.into_iter().flatten() {
65 match Migrator::new(Path::new(&dir)).await {
66 Ok(found) => {
67 info!("Using migrations from {}", dir.display());
68 migrator = Some(found);
69 break;
70 }
71 Err(err) => {
72 last_error = Some((dir, err));
73 }
74 }
75 }
76
77 let migrator = migrator.ok_or_else(|| match last_error {
78 Some((dir, err)) => {
79 anyhow::anyhow!("failed to load migrations from {}: {}", dir.display(), err)
80 }
81 None => anyhow::anyhow!("failed to resolve migrations directory"),
82 })?;
83
84 migrator.run(&pool).await?;
85 info!("Migrations applied");
86 Ok(pool)
87}