use std::io::Write;
use crate::tenancy::error::TenancyError;
use crate::tenancy::manage::args::next_value;
pub const TENANCY_MANAGE_BIN: &str =
"//! Generated by `manage startapp --with-manage-bin` (tenancy-aware).
//!
//! UX: `cargo run --bin manage -- create-tenant <slug>`,
//! `cargo run --bin manage -- run-server`, plus everything the
//! single-tenant dispatcher offers (`migrate`, `makemigrations`, …).
use crate::sql::sqlx::PgPool;
use crate::tenancy::TenantPools;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Pull your models into this binary so `inventory` registers
// them. For the `manage startapp <name>` shape:
// #[allow(unused_imports)]
// use super::<name>::models::*;
// Tenancy registry models (Org / Operator / User) come along
// automatically via the `crate::tenancy` crate import.
let registry_url = std::env::var(\"DATABASE_URL\")?;
let pool = PgPool::connect(®istry_url).await?;
let pools = TenantPools::new(pool);
let dir: &std::path::Path = \"./migrations\".as_ref();
crate::tenancy::manage::run(&pools, ®istry_url, dir, std::env::args().skip(1)).await?;
Ok(())
}
";
pub(super) fn startapp_cmd<W: Write>(
args: &[String],
w: &mut W,
) -> Result<(), TenancyError> {
let mut iter = args.iter();
let app_name = iter
.next()
.cloned()
.ok_or_else(|| TenancyError::Validation(usage()))?;
let mut with_manage_bin = false;
while let Some(flag) = iter.next() {
match flag.as_str() {
"--with-manage-bin" => with_manage_bin = true,
"--help" | "-h" => {
writeln!(w, "{}", usage())?;
return Ok(());
}
"--app-name" => {
let _ = next_value(&mut iter, "--app-name")?;
}
other => {
return Err(TenancyError::Validation(format!(
"startapp: unknown argument `{other}` (run --help for usage)"
)));
}
}
}
let opts = rustango::migrate::scaffold::StartAppOptions {
app_name: app_name.clone(),
manage_bin: with_manage_bin.then_some(TENANCY_MANAGE_BIN),
};
let cwd = std::env::current_dir().map_err(TenancyError::Io)?;
let report = rustango::migrate::scaffold::startapp(&cwd, &opts)
.map_err(TenancyError::Migrate)?;
write_report(w, &app_name, &report)
}
fn write_report<W: Write>(
w: &mut W,
app_name: &str,
report: &rustango::migrate::scaffold::StartAppReport,
) -> Result<(), TenancyError> {
if report.written.is_empty() && report.skipped.is_empty() {
writeln!(w, "startapp: nothing to do")?;
return Ok(());
}
writeln!(w, "startapp `{app_name}` (tenancy-aware)")?;
for path in &report.written {
writeln!(w, " + wrote {path}")?;
}
for path in &report.skipped {
writeln!(w, " · {path} already exists — left untouched")?;
}
if !report.written.is_empty() {
writeln!(w, "next:")?;
writeln!(
w,
" add `mod {app_name};` to src/main.rs (or src/lib.rs) so"
)?;
writeln!(
w,
" the derive macros' `inventory` registrations are pulled in."
)?;
writeln!(w)?;
writeln!(
w,
" for tenancy: `manage init-tenancy && manage migrate` to"
)?;
writeln!(
w,
" materialize the registry + tenant bootstrap migrations."
)?;
}
Ok(())
}
fn usage() -> String {
"startapp <name> [--with-manage-bin]\n \
Scaffold a Django-shape app module under src/<name>/ (mod.rs +\n \
models.rs + views.rs + urls.rs). Idempotent: existing files\n \
are left untouched. <name> must be a valid Rust identifier.\n\n \
--with-manage-bin\n \
Also write src/bin/manage.rs with the **tenancy-aware**\n \
dispatcher boilerplate (crate::tenancy::manage::run). Skipped\n \
if the file already exists."
.to_owned()
}