use crate::addon::Addon;
use anyhow::Result;
use std::path::Path;
pub struct StorageAddon;
impl Addon for StorageAddon {
fn name(&self) -> &str {
"storage"
}
fn check_prerequisites(&self, project_root: &Path) -> Result<()> {
super::check_romance_project(project_root)
}
fn is_already_installed(&self, project_root: &Path) -> bool {
project_root.join("backend/src/storage.rs").exists()
}
fn install(&self, project_root: &Path) -> Result<()> {
install_storage(project_root)
}
fn uninstall(&self, project_root: &Path) -> Result<()> {
use colored::Colorize;
println!("{}", "Uninstalling file storage...".bold());
if super::remove_file_if_exists(&project_root.join("backend/src/storage.rs"))? {
println!(" {} backend/src/storage.rs", "delete".red());
}
if super::remove_file_if_exists(&project_root.join("backend/src/handlers/upload.rs"))? {
println!(" {} backend/src/handlers/upload.rs", "delete".red());
}
if super::remove_file_if_exists(&project_root.join("backend/src/routes/upload.rs"))? {
println!(" {} backend/src/routes/upload.rs", "delete".red());
}
if super::remove_file_if_exists(
&project_root.join("frontend/src/components/FileUpload.tsx"),
)? {
println!(
" {} frontend/src/components/FileUpload.tsx",
"delete".red()
);
}
super::remove_mod_from_main(project_root, "storage")?;
super::remove_line_from_file(
&project_root.join("backend/src/handlers/mod.rs"),
"pub mod upload;",
)?;
super::remove_line_from_file(
&project_root.join("backend/src/routes/mod.rs"),
"pub mod upload;",
)?;
super::remove_line_from_file(
&project_root.join("backend/src/routes/mod.rs"),
".merge(upload::router())",
)?;
super::remove_toml_section(project_root, "storage")?;
crate::ai_context::regenerate(project_root).ok();
println!();
println!(
"{}",
"File storage uninstalled successfully.".green().bold()
);
Ok(())
}
}
fn install_storage(project_root: &Path) -> Result<()> {
use crate::template::TemplateEngine;
use crate::utils;
use colored::Colorize;
use tera::Context;
println!("{}", "Installing file storage...".bold());
let engine = TemplateEngine::new()?;
let ctx = Context::new();
let content = engine.render("addon/storage/storage.rs.tera", &ctx)?;
utils::write_file(&project_root.join("backend/src/storage.rs"), &content)?;
println!(" {} backend/src/storage.rs", "create".green());
let content = engine.render("addon/storage/upload_handler.rs.tera", &ctx)?;
utils::write_file(
&project_root.join("backend/src/handlers/upload.rs"),
&content,
)?;
println!(" {} backend/src/handlers/upload.rs", "create".green());
let content = engine.render("addon/storage/upload_routes.rs.tera", &ctx)?;
utils::write_file(
&project_root.join("backend/src/routes/upload.rs"),
&content,
)?;
println!(" {} backend/src/routes/upload.rs", "create".green());
let content = engine.render("addon/storage/FileUpload.tsx.tera", &ctx)?;
utils::write_file(
&project_root.join("frontend/src/components/FileUpload.tsx"),
&content,
)?;
println!(
" {} frontend/src/components/FileUpload.tsx",
"create".green()
);
super::add_mod_to_main(project_root, "storage")?;
let mods_marker = "// === ROMANCE:MODS ===";
utils::insert_at_marker(
&project_root.join("backend/src/handlers/mod.rs"),
mods_marker,
"pub mod upload;",
)?;
utils::insert_at_marker(
&project_root.join("backend/src/routes/mod.rs"),
mods_marker,
"pub mod upload;",
)?;
utils::insert_at_marker(
&project_root.join("backend/src/routes/mod.rs"),
"// === ROMANCE:ROUTES ===",
" .merge(upload::router())",
)?;
crate::generator::auth::insert_cargo_dependency(
&project_root.join("backend/Cargo.toml"),
&[
("axum", r#"{ version = "0.8", features = ["json", "multipart"] }"#),
("mime", r#""0.3""#),
("async-trait", r#""0.1""#),
],
)?;
super::append_env_var(
&project_root.join("backend/.env"),
"UPLOAD_DIR=./uploads",
)?;
super::append_env_var(
&project_root.join("backend/.env"),
"UPLOAD_URL=/uploads",
)?;
super::append_env_var(
&project_root.join("backend/.env"),
"MAX_FILE_SIZE=10MB",
)?;
super::append_env_var(
&project_root.join("backend/.env.example"),
"UPLOAD_DIR=./uploads",
)?;
super::append_env_var(
&project_root.join("backend/.env.example"),
"UPLOAD_URL=/uploads",
)?;
super::append_env_var(
&project_root.join("backend/.env.example"),
"MAX_FILE_SIZE=10MB",
)?;
std::fs::create_dir_all(project_root.join("backend/uploads"))?;
println!(" {} backend/uploads/", "create".green());
let config_path = project_root.join("romance.toml");
let content = std::fs::read_to_string(&config_path)?;
if !content.contains("[storage]") {
let new_content = format!(
"{}\n[storage]\nbackend = \"local\"\nupload_dir = \"./uploads\"\nmax_file_size = \"10MB\"\n",
content.trim_end()
);
std::fs::write(&config_path, new_content)?;
}
println!();
println!("{}", "File storage installed successfully!".green().bold());
println!(" Use `avatar:image` or `document:file` field types in entity generation.");
println!(" Configure storage in romance.toml under [storage].");
Ok(())
}