pub mod matrix;
use crate::core::ModelSchema;
use crate::migrate::{ddl, MigrateError};
use crate::sql::Pool;
pub async fn create_framework_tables(pool: &Pool) -> Result<(), MigrateError> {
let models: Vec<&'static ModelSchema> = crate::migrate::registered_models()
.into_iter()
.filter(|m| m.managed && m.table.starts_with("rustango_"))
.collect();
emit_tables(pool, &models).await
}
pub async fn create_tables(
pool: &Pool,
models: &[&'static ModelSchema],
) -> Result<(), MigrateError> {
emit_tables(pool, models).await
}
pub async fn create_tables_for<M: crate::core::Model>(pool: &Pool) -> Result<(), MigrateError> {
emit_tables(pool, &[M::SCHEMA]).await
}
pub async fn create_tables_in_schema(
pool: &Pool,
schema: &str,
models: &[&'static ModelSchema],
) -> Result<(), MigrateError> {
let dialect = pool.dialect();
for model in models {
let bare = ddl::create_table_if_not_exists_sql_with_dialect(dialect, model);
let quoted = dialect.quote_ident(model.table);
let qualified_target = format!("\"{schema}\".{quoted}");
let sql = bare.replacen("ed, &qualified_target, 1);
crate::sql::raw_execute_pool(pool, &sql, ::std::vec::Vec::new()).await?;
}
Ok(())
}
async fn emit_tables(pool: &Pool, models: &[&'static ModelSchema]) -> Result<(), MigrateError> {
let dialect = pool.dialect();
for model in models {
let sql = ddl::create_table_if_not_exists_sql_with_dialect(dialect, model);
crate::sql::raw_execute_pool(pool, &sql, ::std::vec::Vec::new()).await?;
}
for model in models {
for sql in ddl::create_constraints_sql_with_dialect(dialect, model) {
crate::sql::raw_execute_pool(pool, &sql, ::std::vec::Vec::new()).await?;
}
}
for model in models {
for sql in ddl::column_comment_statements_with_dialect(dialect, model) {
crate::sql::raw_execute_pool(pool, &sql, ::std::vec::Vec::new()).await?;
}
}
for model in models {
for sql in ddl::table_comment_statements_with_dialect(dialect, model) {
crate::sql::raw_execute_pool(pool, &sql, ::std::vec::Vec::new()).await?;
}
}
Ok(())
}
pub async fn migrate_framework(pool: &Pool) -> Result<(), MigrateError> {
let mut seen = std::collections::HashSet::new();
let models: Vec<&'static ModelSchema> = crate::migrate::registered_models()
.into_iter()
.filter(|m| m.managed && m.table.starts_with("rustango_") && seen.insert(m.table))
.collect();
let snapshot = crate::migrate::SchemaSnapshot::from_models(&models);
crate::migrate::apply_idempotent(pool, &snapshot)
.await
.map_err(|e| MigrateError::Validation(format!("{e}")))?;
Ok(())
}
#[cfg(feature = "tenancy")]
#[must_use]
pub fn org() -> crate::tenancy::Org {
crate::tenancy::Org {
id: crate::sql::Auto::Unset,
slug: "acme".into(),
display_name: "Acme".into(),
storage_mode: "database".into(),
backend_kind: "postgres".into(),
database_url: None,
schema_name: None,
host_pattern: None,
port: None,
path_prefix: None,
active: true,
created_at: chrono::Utc::now(),
brand_name: None,
brand_tagline: None,
logo_path: None,
favicon_path: None,
primary_color: None,
theme_mode: None,
}
}
#[cfg(feature = "tenancy")]
#[must_use]
pub fn user() -> crate::tenancy::User {
crate::tenancy::User {
id: crate::sql::Auto::Unset,
username: "alice".into(),
password_hash: String::new(),
#[cfg(feature = "sso")]
email: None,
is_superuser: false,
active: true,
created_at: chrono::Utc::now(),
data: serde_json::json!({}),
password_changed_at: None,
}
}
#[cfg(feature = "admin")]
#[must_use]
pub fn admin_user() -> crate::admin::AdminUser {
crate::admin::AdminUser {
id: crate::sql::Auto::Unset,
username: "admin".into(),
password_hash: String::new(),
#[cfg(feature = "admin-sso")]
email: None,
is_superuser: true,
active: true,
created_at: chrono::Utc::now(),
}
}
#[cfg(feature = "tenancy")]
pub fn reset_host_generation() {
crate::tenancy::reset_generation();
}
#[cfg(feature = "tenancy")]
pub fn expire_host_generation() {
crate::tenancy::expire_generation();
}
#[cfg(feature = "tenancy")]
pub fn reset_registry_breaker() {
crate::tenancy::reset_registry_breaker();
}
#[cfg(feature = "tenancy")]
pub fn reset_org_cache() {
crate::tenancy::reset_org_cache();
}
#[cfg(all(test, feature = "sqlite", feature = "tenancy", feature = "admin"))]
mod tests {
use super::*;
#[tokio::test]
async fn framework_tables_and_factories_roundtrip() {
let path = std::env::temp_dir().join("rustango_testkit_selftest.db");
let _ = std::fs::remove_file(&path);
let url = format!("sqlite:{}?mode=rwc", path.display());
let pool = crate::sql::Pool::connect(&url).await.expect("connect");
create_tables_for::<crate::tenancy::Org>(&pool)
.await
.expect("orgs");
create_tables_for::<crate::tenancy::User>(&pool)
.await
.expect("users");
create_tables_for::<crate::admin::AdminUser>(&pool)
.await
.expect("admin_users");
let mut org = crate::tenancy::Org {
slug: "acme".into(),
..org()
};
org.insert_pool(&pool).await.expect("insert org");
let mut admin = crate::admin::AdminUser {
username: "root".into(),
..admin_user()
};
admin.insert_pool(&pool).await.expect("insert admin");
let mut user = crate::tenancy::User {
username: "alice".into(),
..user()
};
user.insert_pool(&pool).await.expect("insert user");
let _ = std::fs::remove_file(&path);
}
}