use std::fs;
use std::path::Path;
use dialoguer::{theme::ColorfulTheme, Select};
use colored::*;
#[derive(Debug, Clone, Copy)]
pub enum ProjectType {
Fullstack,
Api,
Microservice,
Serverless,
}
impl ProjectType {
fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"fullstack" => Some(Self::Fullstack),
"api" => Some(Self::Api),
"microservice" => Some(Self::Microservice),
"serverless" => Some(Self::Serverless),
_ => None,
}
}
fn as_str(&self) -> &'static str {
match self {
Self::Fullstack => "Fullstack Application",
Self::Api => "REST API",
Self::Microservice => "Microservice",
Self::Serverless => "Serverless Function",
}
}
}
pub fn create_project(name: &str, project_type: Option<String>) -> Result<(), Box<dyn std::error::Error>> {
println!("{}", format!("🚀 Initializing new Oxidite project: {}", name).green().bold());
let p_type = if let Some(t) = project_type {
ProjectType::from_str(&t).ok_or("Invalid project type. Options: fullstack, api, microservice, serverless")?
} else {
let selections = &[
"Fullstack Application (Frontend + Backend)",
"REST API (Backend only)",
"Microservice (Minimal, specialized)",
"Serverless Function (Event-driven)",
];
let selection = Select::with_theme(&ColorfulTheme::default())
.with_prompt("Select project type")
.default(0)
.items(&selections[..])
.interact()?;
match selection {
0 => ProjectType::Fullstack,
1 => ProjectType::Api,
2 => ProjectType::Microservice,
3 => ProjectType::Serverless,
_ => unreachable!(),
}
};
println!("Creating {}...", p_type.as_str().cyan());
fs::create_dir(name)?;
let project_path = Path::new(name);
let src_path = project_path.join("src");
fs::create_dir(&src_path)?;
fs::create_dir(src_path.join("models"))?;
fs::create_dir(src_path.join("routes"))?;
fs::create_dir(src_path.join("controllers"))?;
fs::create_dir(src_path.join("services"))?;
fs::create_dir(src_path.join("middleware"))?;
fs::create_dir(src_path.join("utils"))?;
fs::create_dir(src_path.join("config"))?;
fs::create_dir(project_path.join("tests"))?;
match p_type {
ProjectType::Fullstack => {
fs::create_dir(project_path.join("templates"))?;
fs::create_dir(project_path.join("public"))?;
fs::create_dir(project_path.join("public/css"))?;
fs::create_dir(project_path.join("public/js"))?;
fs::create_dir(project_path.join("public/images"))?;
},
ProjectType::Microservice => {
fs::create_dir(src_path.join("queues"))?;
},
_ => {}
}
create_cargo_toml(project_path, name, p_type)?;
create_config_toml(project_path, p_type)?;
create_main_rs(project_path, p_type)?;
create_boilerplate(project_path, p_type)?;
let gitignore = r#"/target
Cargo.lock
*.db
*.log
.env
"#;
fs::write(project_path.join(".gitignore"), gitignore)?;
println!("\n{}", "✅ Project created successfully!".green().bold());
println!("\n📂 Project structure:");
println!(" ├── src/");
println!(" │ ├── models/");
println!(" │ ├── routes/");
println!(" │ ├── controllers/");
println!(" │ ├── services/");
println!(" │ └── middleware/");
if let ProjectType::Fullstack = p_type {
println!(" ├── templates/");
println!(" └── public/");
}
println!(" └── config.toml");
println!("\n🚀 Next steps:");
println!(" cd {}", name);
println!(" cargo run");
Ok(())
}
fn create_cargo_toml(path: &Path, name: &str, p_type: ProjectType) -> std::io::Result<()> {
let mut dependencies = String::from(r#"
oxidite-core = "0.1"
oxidite-config = "0.1"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
"#);
match p_type {
ProjectType::Fullstack => {
dependencies.push_str(r#"
oxidite-template = "0.1"
oxidite-auth = "0.1"
oxidite-db = "0.1"
oxidite-middleware = "0.1"
"#);
},
ProjectType::Api => {
dependencies.push_str(r#"
oxidite-auth = "0.1"
oxidite-db = "0.1"
oxidite-middleware = "0.1"
"#);
},
ProjectType::Microservice => {
dependencies.push_str(r#"
oxidite-queue = "0.1"
oxidite-middleware = "0.1"
"#);
},
ProjectType::Serverless => {
}
}
let content = format!(r#"[package]
name = "{}"
version = "0.1.0"
edition = "2021"
[dependencies]{}
"#, name, dependencies);
fs::write(path.join("Cargo.toml"), content)
}
fn create_config_toml(path: &Path, p_type: ProjectType) -> std::io::Result<()> {
let mut content = String::from(r#"# Oxidite Configuration
[server]
host = "127.0.0.1"
port = 8080
"#);
match p_type {
ProjectType::Fullstack => {
content.push_str(r#"
[database]
url = "sqlite://data.db"
[templates]
directory = "templates"
[static]
directory = "public"
"#);
},
ProjectType::Api => {
content.push_str(r#"
[database]
url = "sqlite://data.db"
"#);
},
ProjectType::Microservice => {
content.push_str(r#"
[queue]
url = "redis://localhost"
"#);
},
_ => {}
}
fs::write(path.join("config.toml"), content)
}
fn create_main_rs(path: &Path, p_type: ProjectType) -> std::io::Result<()> {
let content = match p_type {
ProjectType::Fullstack => r#"use oxidite_core::{Router, Server, OxiditeRequest, OxiditeResponse};
use oxidite_config::Config;
use oxidite_template::serve_static;
mod routes;
mod controllers;
mod models;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::load()?;
let addr = format!("{}:{}", config.get("server.host")?, config.get("server.port")?);
let mut router = Router::new();
// Register routes
routes::register(&mut router);
// Static files
// Static files (fallback)
router.get("/*", serve_static);
let server = Server::new(router);
println!("🚀 Server running on http://{}", addr);
server.listen(addr.parse()?).await?;
Ok(())
}
"#,
ProjectType::Api => r#"use oxidite_core::{Router, Server, OxiditeRequest, OxiditeResponse};
use oxidite_config::Config;
mod routes;
mod controllers;
mod models;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::load()?;
let addr = format!("{}:{}", config.get("server.host")?, config.get("server.port")?);
let mut router = Router::new();
// Register routes
routes::register(&mut router);
let server = Server::new(router);
println!("🚀 API Server running on http://{}", addr);
server.listen(addr.parse()?).await?;
Ok(())
}
"#,
ProjectType::Microservice => r#"use oxidite_core::{Router, Server};
use oxidite_config::Config;
mod routes;
mod services;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::load()?;
let addr = format!("{}:{}", config.get("server.host")?, config.get("server.port")?);
let mut router = Router::new();
routes::register(&mut router);
let server = Server::new(router);
println!("🚀 Microservice running on http://{}", addr);
server.listen(addr.parse()?).await?;
Ok(())
}
"#,
ProjectType::Serverless => r#"use oxidite_core::{OxiditeRequest, OxiditeResponse};
pub async fn handler(req: OxiditeRequest) -> Result<OxiditeResponse, oxidite_core::Error> {
Ok(OxiditeResponse::json(serde_json::json!({
"message": "Hello from Serverless Function!"
})))
}
// Local dev server
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
use oxidite_core::{Router, Server};
let mut router = Router::new();
router.get("/", handler);
let server = Server::new(router);
println!("🚀 Function running locally on http://127.0.0.1:8080");
server.listen("127.0.0.1:8080".parse()?).await?;
Ok(())
}
"#,
};
fs::write(path.join("src/main.rs"), content)
}
fn create_boilerplate(path: &Path, p_type: ProjectType) -> std::io::Result<()> {
fs::write(path.join("src/models/mod.rs"), "")?;
fs::write(path.join("src/controllers/mod.rs"), "")?;
fs::write(path.join("src/services/mod.rs"), "")?;
fs::write(path.join("src/middleware/mod.rs"), "")?;
let routes_content = match p_type {
ProjectType::Fullstack => r#"use oxidite_core::{Router, OxiditeRequest, OxiditeResponse};
pub fn register(router: &mut Router) {
router.get("/", index);
}
async fn index(_req: OxiditeRequest) -> Result<OxiditeResponse, oxidite_core::Error> {
Ok(OxiditeResponse::html("<h1>Welcome to Oxidite Fullstack!</h1>"))
}
"#,
ProjectType::Api => r#"use oxidite_core::{Router, OxiditeRequest, OxiditeResponse};
pub fn register(router: &mut Router) {
router.get("/api/health", health);
}
async fn health(_req: OxiditeRequest) -> Result<OxiditeResponse, oxidite_core::Error> {
Ok(OxiditeResponse::json(serde_json::json!({"status": "ok"})))
}
"#,
_ => r#"use oxidite_core::Router;
pub fn register(_router: &mut Router) {
// Register routes
}
"#,
};
fs::write(path.join("src/routes/mod.rs"), routes_content)?;
if let ProjectType::Fullstack = p_type {
fs::write(path.join("public/css/style.css"), "body { font-family: sans-serif; }")?;
fs::write(path.join("public/js/app.js"), "console.log('App loaded');")?;
fs::write(path.join("templates/base.html"), "<html><body>{% block content %}{% endblock %}</body></html>")?;
fs::write(path.join("templates/index.html"), "{% extends \"base.html\" %}{% block content %}<h1>Hello</h1>{% endblock %}")?;
}
Ok(())
}