rustpbx 0.3.18

A SIP PBX implementation in Rust
Documentation
use anyhow::{Context, Result};
use sea_orm::{Database, DatabaseConnection};
use sea_orm_migration::MigratorTrait;
use sqlx::Connection;
use url::Url;

pub mod add_rewrite_columns;
pub mod call_record;
pub mod call_record_dashboard_index;
pub mod call_record_from_number_index;
pub mod call_record_indices;
pub mod call_record_optimization_indices;
pub mod department;
pub mod extension;
pub mod extension_department;
pub mod frequency_limit;
pub mod migration;
pub mod policy;
pub mod presence;
pub mod routing;
pub mod sip_trunk;
pub mod user;

pub fn prepare_sqlite_database(database_url: &str) -> Result<()> {
    let Some(path_part) = database_url.strip_prefix("sqlite://") else {
        return Ok(());
    };

    let (path_str, _) = path_part.split_once('?').unwrap_or((path_part, ""));
    if path_str.is_empty() || path_str.starts_with(':') {
        return Ok(());
    }

    let path = std::path::Path::new(path_str);
    if let Some(parent) = path.parent() {
        if !parent.as_os_str().is_empty() {
            std::fs::create_dir_all(parent).with_context(|| {
                format!(
                    "failed to create directory for console database at {}",
                    parent.display()
                )
            })?;
        }
    }

    if !path.exists() {
        std::fs::OpenOptions::new()
            .create(true)
            .write(true)
            .open(path)
            .with_context(|| {
                format!(
                    "failed to create console database file at {}",
                    path.display()
                )
            })?;
    }

    Ok(())
}

async fn prepare_mysql_database(database_url: &str) -> Result<()> {
    let url = Url::parse(database_url)?;

    let database_name = url.path().trim_start_matches('/');
    if database_name.is_empty() {
        return Err(anyhow::anyhow!("No database specified"));
    }

    let mut server_url = url.clone();
    server_url.set_path("/mysql");
    server_url.set_query(None);
    let server_url_str = server_url.to_string();

    let mut conn = sqlx::MySqlConnection::connect(&server_url_str)
        .await
        .with_context(|| format!("failed to connect to MySQL server: {}", server_url_str))?;

    sqlx::query(&format!(
        "CREATE DATABASE IF NOT EXISTS `{}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",
        database_name
    ))
    .execute(&mut conn)
    .await
    .with_context(|| format!("failed to create database: {}", database_name))?;

    conn.close().await?;
    Ok(())
}

pub async fn create_db(database_url: &str) -> Result<DatabaseConnection> {
    if database_url.starts_with("sqlite://") {
        prepare_sqlite_database(database_url)?;
    } else if database_url.starts_with("mysql://") || database_url.starts_with("mysqlx://") {
        prepare_mysql_database(database_url).await?;
    }

    let db = Database::connect(database_url)
        .await
        .with_context(|| format!("failed to connect admin database: {}", database_url))?;

    migration::Migrator::up(&db, None)
        .await
        .context("failed to run database migrations")?;
    Ok(db)
}