use std::{fs, path::Path};
use super::{AuthDriver, CacheDriver, QueueDriver, WizardConfig};
pub fn generate(name: &str, root: &Path, config: &WizardConfig) -> anyhow::Result<()> {
for dir in &["src", "database/migrations"] {
fs::create_dir_all(root.join(dir))?;
}
let mut files: Vec<(&str, String)> = vec![
("Cargo.toml", cargo_toml(name, config)),
("src/main.rs", main_rs(name)),
(".env.example", env_example(config)),
(".gitignore", GITIGNORE.into()),
("database/migrations/.gitkeep", String::new()),
];
if config.include_examples {
files.push(("src/example.rs", EXAMPLE_RS.into()));
}
for (rel, content) in &files {
fs::write(root.join(rel), content)?;
println!(" create {rel}");
}
Ok(())
}
fn cargo_toml(name: &str, config: &WizardConfig) -> String {
let db_feat = config.db.sqlx_feature();
let mut extra = String::new();
if config.auth != AuthDriver::None {
extra.push_str("rok-auth = { version = \"0.1\", features = [\"axum\"] }\n");
}
if config.cache == CacheDriver::Redis {
extra.push_str("rok-cache = { version = \"0.1\", features = [\"redis\", \"axum\"] }\n");
} else if config.cache == CacheDriver::Memory {
extra.push_str("rok-cache = { version = \"0.1\" }\n");
}
if config.queue == QueueDriver::Postgres || config.queue == QueueDriver::Redis {
extra.push_str("rok-queue = { version = \"0.1\" }\n");
}
format!(
r#"[package]
name = "{name}"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "{name}"
path = "src/main.rs"
[dependencies]
axum = "0.8"
tokio = {{ version = "1", features = ["full"] }}
serde = {{ version = "1", features = ["derive"] }}
serde_json = "1"
dotenvy = "0.15"
sqlx = {{ version = "0.8", default-features = false, features = [
"macros", "runtime-tokio-native-tls", "{db_feat}",
] }}
rok-orm = {{ version = "0.1", features = ["{db_feat}"] }}
{extra}"#
)
}
fn main_rs(name: &str) -> String {
format!(
r#"use axum::{{routing::get, Router}};
#[tokio::main]
async fn main() {{
dotenvy::dotenv().ok();
let addr = std::env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0:3000".into());
let app = Router::new().route("/", get(|| async {{ "{name} is running" }}));
let listener = tokio::net::TcpListener::bind(&addr).await.expect("bind");
println!("{name} listening on {{addr}}");
axum::serve(listener, app).await.expect("server");
}}
"#
)
}
fn env_example(config: &WizardConfig) -> String {
let mut out = String::from("LISTEN_ADDR=0.0.0.0:3000\n");
let db_url = config.db.example_url();
out.push_str(&format!("DATABASE_URL={db_url}\n"));
if config.auth == AuthDriver::Jwt
|| config.auth == AuthDriver::Session
|| config.auth == AuthDriver::MagicLink
{
out.push_str("JWT_SECRET=change-me-to-a-random-secret\n");
}
if config.auth == AuthDriver::Social {
out.push_str("OAUTH_CLIENT_ID=\nOAUTH_CLIENT_SECRET=\n");
}
if config.cache == CacheDriver::Redis || config.queue == QueueDriver::Redis {
out.push_str("REDIS_URL=redis://localhost:6379\n");
}
out
}
const GITIGNORE: &str = "/target\n.env\n";
const EXAMPLE_RS: &str = r#"use axum::{extract::Path, response::IntoResponse, Json};
use serde_json::json;
/// GET /example/:id
pub async fn show(Path(id): Path<i64>) -> impl IntoResponse {
Json(json!({ "id": id, "message": "replace with real logic" }))
}
"#;