romance_core/addon/
storage.rs1use crate::addon::Addon;
2use anyhow::Result;
3use std::path::Path;
4
5pub struct StorageAddon;
6
7impl Addon for StorageAddon {
8 fn name(&self) -> &str {
9 "storage"
10 }
11
12 fn check_prerequisites(&self, project_root: &Path) -> Result<()> {
13 super::check_romance_project(project_root)
14 }
15
16 fn is_already_installed(&self, project_root: &Path) -> bool {
17 project_root.join("backend/src/storage.rs").exists()
18 }
19
20 fn install(&self, project_root: &Path) -> Result<()> {
21 install_storage(project_root)
22 }
23
24 fn uninstall(&self, project_root: &Path) -> Result<()> {
25 use colored::Colorize;
26
27 println!("{}", "Uninstalling file storage...".bold());
28
29 if super::remove_file_if_exists(&project_root.join("backend/src/storage.rs"))? {
31 println!(" {} backend/src/storage.rs", "delete".red());
32 }
33 if super::remove_file_if_exists(&project_root.join("backend/src/handlers/upload.rs"))? {
34 println!(" {} backend/src/handlers/upload.rs", "delete".red());
35 }
36 if super::remove_file_if_exists(&project_root.join("backend/src/routes/upload.rs"))? {
37 println!(" {} backend/src/routes/upload.rs", "delete".red());
38 }
39 if super::remove_file_if_exists(
40 &project_root.join("frontend/src/components/FileUpload.tsx"),
41 )? {
42 println!(
43 " {} frontend/src/components/FileUpload.tsx",
44 "delete".red()
45 );
46 }
47
48 super::remove_mod_from_main(project_root, "storage")?;
50
51 super::remove_line_from_file(
53 &project_root.join("backend/src/handlers/mod.rs"),
54 "pub mod upload;",
55 )?;
56
57 super::remove_line_from_file(
59 &project_root.join("backend/src/routes/mod.rs"),
60 "pub mod upload;",
61 )?;
62 super::remove_line_from_file(
63 &project_root.join("backend/src/routes/mod.rs"),
64 ".merge(upload::router())",
65 )?;
66
67 super::remove_toml_section(project_root, "storage")?;
69
70 crate::ai_context::regenerate(project_root).ok();
72
73 println!();
74 println!(
75 "{}",
76 "File storage uninstalled successfully.".green().bold()
77 );
78
79 Ok(())
80 }
81}
82
83fn install_storage(project_root: &Path) -> Result<()> {
84 use crate::template::TemplateEngine;
85 use crate::utils;
86 use colored::Colorize;
87 use tera::Context;
88
89 println!("{}", "Installing file storage...".bold());
90
91 let engine = TemplateEngine::new()?;
92 let ctx = Context::new();
93
94 let content = engine.render("addon/storage/storage.rs.tera", &ctx)?;
96 utils::write_file(&project_root.join("backend/src/storage.rs"), &content)?;
97 println!(" {} backend/src/storage.rs", "create".green());
98
99 let content = engine.render("addon/storage/upload_handler.rs.tera", &ctx)?;
101 utils::write_file(
102 &project_root.join("backend/src/handlers/upload.rs"),
103 &content,
104 )?;
105 println!(" {} backend/src/handlers/upload.rs", "create".green());
106
107 let content = engine.render("addon/storage/upload_routes.rs.tera", &ctx)?;
109 utils::write_file(
110 &project_root.join("backend/src/routes/upload.rs"),
111 &content,
112 )?;
113 println!(" {} backend/src/routes/upload.rs", "create".green());
114
115 let content = engine.render("addon/storage/FileUpload.tsx.tera", &ctx)?;
117 utils::write_file(
118 &project_root.join("frontend/src/components/FileUpload.tsx"),
119 &content,
120 )?;
121 println!(
122 " {} frontend/src/components/FileUpload.tsx",
123 "create".green()
124 );
125
126 super::add_mod_to_main(project_root, "storage")?;
128
129 let mods_marker = "// === ROMANCE:MODS ===";
131 utils::insert_at_marker(
132 &project_root.join("backend/src/handlers/mod.rs"),
133 mods_marker,
134 "pub mod upload;",
135 )?;
136 utils::insert_at_marker(
137 &project_root.join("backend/src/routes/mod.rs"),
138 mods_marker,
139 "pub mod upload;",
140 )?;
141 utils::insert_at_marker(
142 &project_root.join("backend/src/routes/mod.rs"),
143 "// === ROMANCE:ROUTES ===",
144 " .merge(upload::router())",
145 )?;
146
147 crate::generator::auth::insert_cargo_dependency(
149 &project_root.join("backend/Cargo.toml"),
150 &[
151 ("axum", r#"{ version = "0.8", features = ["json", "multipart"] }"#),
152 ("mime", r#""0.3""#),
153 ("async-trait", r#""0.1""#),
154 ],
155 )?;
156
157 super::append_env_var(
159 &project_root.join("backend/.env"),
160 "UPLOAD_DIR=./uploads",
161 )?;
162 super::append_env_var(
163 &project_root.join("backend/.env"),
164 "UPLOAD_URL=/uploads",
165 )?;
166 super::append_env_var(
167 &project_root.join("backend/.env"),
168 "MAX_FILE_SIZE=10MB",
169 )?;
170 super::append_env_var(
171 &project_root.join("backend/.env.example"),
172 "UPLOAD_DIR=./uploads",
173 )?;
174 super::append_env_var(
175 &project_root.join("backend/.env.example"),
176 "UPLOAD_URL=/uploads",
177 )?;
178 super::append_env_var(
179 &project_root.join("backend/.env.example"),
180 "MAX_FILE_SIZE=10MB",
181 )?;
182
183 std::fs::create_dir_all(project_root.join("backend/uploads"))?;
185 println!(" {} backend/uploads/", "create".green());
186
187 let config_path = project_root.join("romance.toml");
189 let content = std::fs::read_to_string(&config_path)?;
190 if !content.contains("[storage]") {
191 let new_content = format!(
192 "{}\n[storage]\nbackend = \"local\"\nupload_dir = \"./uploads\"\nmax_file_size = \"10MB\"\n",
193 content.trim_end()
194 );
195 std::fs::write(&config_path, new_content)?;
196 }
197
198 println!();
199 println!("{}", "File storage installed successfully!".green().bold());
200 println!(" Use `avatar:image` or `document:file` field types in entity generation.");
201 println!(" Configure storage in romance.toml under [storage].");
202
203 Ok(())
204}