docbox-management 0.12.0

Management core library for managing docbox used by the cli and other tools
use crate::{
    database::DatabaseProvider,
    tenant::{
        MigrateTenantsOutcome, TenantTarget,
        get_tenants::get_tenants,
        migrate_tenant_search::{MigrateTenantSearchError, migrate_tenant_search},
    },
};
use docbox_core::{
    database::{
        DbErr,
        models::tenant::{Tenant, TenantId},
    },
    search::SearchIndexFactory,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum MigrateTenantsSearchError {
    #[error("failed to get tenants: {0}")]
    GetTenants(DbErr),

    #[error(transparent)]
    MigrateTenant(#[from] MigrateTenantSearchError),
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MigrateTenantsSearchConfig {
    /// Filter to a specific environment
    pub env: Option<String>,
    /// Filter to a specific tenant
    pub tenant_id: Option<TenantId>,
    /// Filter to skip failed migrations and continue
    pub skip_failed: bool,
    /// Specific migrations to run
    pub target_migration_name: Option<String>,
}

#[tracing::instrument(skip(db_provider, search_factory))]
pub async fn migrate_tenants_search(
    db_provider: &impl DatabaseProvider,
    search_factory: &SearchIndexFactory,
    config: MigrateTenantsSearchConfig,
) -> Result<MigrateTenantsOutcome, MigrateTenantsSearchError> {
    let tenants = get_tenants(db_provider)
        .await
        .map_err(MigrateTenantsSearchError::GetTenants)?;

    // Filter to our desired tenants
    let tenants: Vec<Tenant> = tenants
        .into_iter()
        .filter(|tenant| {
            if config.env.as_ref().is_some_and(|env| tenant.env.ne(env)) {
                return false;
            }

            if config
                .tenant_id
                .as_ref()
                .is_some_and(|schema| tenant.id.ne(schema))
            {
                return false;
            }

            true
        })
        .collect();

    let mut applied_tenants = Vec::new();
    let mut failed_tenants = Vec::new();

    for tenant in tenants {
        let result = migrate_tenant_search(
            db_provider,
            search_factory,
            &tenant,
            config.target_migration_name.as_deref(),
        )
        .await;
        match result {
            Ok(_) => {
                applied_tenants.push(TenantTarget {
                    env: tenant.env,
                    name: tenant.name,
                    tenant_id: tenant.id,
                });
            }
            Err(error) => {
                failed_tenants.push((
                    error.to_string(),
                    TenantTarget {
                        env: tenant.env,
                        name: tenant.name,
                        tenant_id: tenant.id,
                    },
                ));

                tracing::error!(?error, "failed to apply tenant migration");

                if !config.skip_failed {
                    tracing::debug!(?applied_tenants, "completed migrations");
                    break;
                }

                return Err(MigrateTenantsSearchError::MigrateTenant(error));
            }
        }
    }

    Ok(MigrateTenantsOutcome {
        applied_tenants,
        failed_tenants,
    })
}