romance_core/addon/
dashboard.rs1use crate::addon::Addon;
2use crate::generator::context::markers;
3use anyhow::Result;
4use std::path::Path;
5
6pub struct DashboardAddon;
7
8impl Addon for DashboardAddon {
9 fn name(&self) -> &str {
10 "dashboard"
11 }
12
13 fn check_prerequisites(&self, project_root: &Path) -> Result<()> {
14 super::check_romance_project(project_root)
15 }
16
17 fn is_already_installed(&self, project_root: &Path) -> bool {
18 project_root
19 .join("frontend/src/features/dev/DevDashboard.tsx")
20 .exists()
21 }
22
23 fn install(&self, project_root: &Path) -> Result<()> {
24 install_dashboard(project_root)
25 }
26
27 fn uninstall(&self, project_root: &Path) -> Result<()> {
28 use colored::Colorize;
29
30 println!("{}", "Uninstalling dev dashboard...".bold());
31
32 if super::remove_file_if_exists(
34 &project_root.join("backend/src/handlers/dev_dashboard.rs"),
35 )? {
36 println!(
37 " {} backend/src/handlers/dev_dashboard.rs",
38 "delete".red()
39 );
40 }
41 if super::remove_file_if_exists(
42 &project_root.join("backend/src/routes/dev_dashboard.rs"),
43 )? {
44 println!(
45 " {} backend/src/routes/dev_dashboard.rs",
46 "delete".red()
47 );
48 }
49 if super::remove_file_if_exists(
50 &project_root.join("frontend/src/features/dev/DevDashboard.tsx"),
51 )? {
52 println!(
53 " {} frontend/src/features/dev/DevDashboard.tsx",
54 "delete".red()
55 );
56 }
57
58 super::remove_line_from_file(
60 &project_root.join("backend/src/handlers/mod.rs"),
61 "pub mod dev_dashboard;",
62 )?;
63
64 super::remove_line_from_file(
66 &project_root.join("backend/src/routes/mod.rs"),
67 "pub mod dev_dashboard;",
68 )?;
69 super::remove_line_from_file(
70 &project_root.join("backend/src/routes/mod.rs"),
71 ".merge(dev_dashboard::router())",
72 )?;
73
74 super::remove_line_from_file(
76 &project_root.join("frontend/src/App.tsx"),
77 "DevDashboard",
78 )?;
79
80 crate::ai_context::regenerate(project_root).ok();
82
83 println!();
84 println!(
85 "{}",
86 "Dev dashboard uninstalled successfully.".green().bold()
87 );
88
89 Ok(())
90 }
91}
92
93fn install_dashboard(project_root: &Path) -> Result<()> {
94 use crate::relation;
95 use crate::template::TemplateEngine;
96 use crate::utils;
97 use colored::Colorize;
98 use heck::{ToLowerCamelCase, ToPascalCase};
99 use tera::Context;
100
101 println!("{}", "Installing dev dashboard...".bold());
102
103 let engine = TemplateEngine::new()?;
104
105 let entity_names = relation::discover_entities(project_root)?;
107 let entities_dir = project_root.join("backend/src/entities");
108 let entities: Vec<serde_json::Value> = entity_names
109 .iter()
110 .filter(|n| {
111 if *n == "user" || *n == "audit_entry" {
112 return false;
113 }
114 let model_path = entities_dir.join(format!("{}.rs", n));
115 if model_path.exists() {
116 if let Ok(content) = std::fs::read_to_string(&model_path) {
117 return content.contains("ROMANCE:CUSTOM");
118 }
119 }
120 false
121 })
122 .map(|name| {
123 serde_json::json!({
124 "name": name.to_pascal_case(),
125 "name_snake": name,
126 "name_camel": name.to_lower_camel_case(),
127 })
128 })
129 .collect();
130
131 let has_auth = project_root.join("backend/src/auth.rs").exists();
132 let has_audit = project_root.join("backend/src/audit.rs").exists();
133
134 let mut ctx = Context::new();
135 ctx.insert("entities", &entities);
136 ctx.insert("has_auth", &has_auth);
137 ctx.insert("has_audit", &has_audit);
138
139 let content = engine.render("addon/dashboard/dev_handlers.rs.tera", &ctx)?;
141 utils::write_file(
142 &project_root.join("backend/src/handlers/dev_dashboard.rs"),
143 &content,
144 )?;
145 println!(
146 " {} backend/src/handlers/dev_dashboard.rs",
147 "create".green()
148 );
149
150 let content = engine.render("addon/dashboard/dev_routes.rs.tera", &ctx)?;
152 utils::write_file(
153 &project_root.join("backend/src/routes/dev_dashboard.rs"),
154 &content,
155 )?;
156 println!(
157 " {} backend/src/routes/dev_dashboard.rs",
158 "create".green()
159 );
160
161 let content = engine.render("addon/dashboard/DevDashboard.tsx.tera", &ctx)?;
163 utils::write_file(
164 &project_root.join("frontend/src/features/dev/DevDashboard.tsx"),
165 &content,
166 )?;
167 println!(
168 " {} frontend/src/features/dev/DevDashboard.tsx",
169 "create".green()
170 );
171
172 let mods_marker = "// === ROMANCE:MODS ===";
174 utils::insert_at_marker(
175 &project_root.join("backend/src/handlers/mod.rs"),
176 mods_marker,
177 "pub mod dev_dashboard;",
178 )?;
179 utils::insert_at_marker(
180 &project_root.join("backend/src/routes/mod.rs"),
181 mods_marker,
182 "pub mod dev_dashboard;",
183 )?;
184 utils::insert_at_marker(
185 &project_root.join("backend/src/routes/mod.rs"),
186 "// === ROMANCE:ROUTES ===",
187 " .merge(dev_dashboard::router())",
188 )?;
189
190 utils::insert_at_marker(
192 &project_root.join("frontend/src/App.tsx"),
193 "// === ROMANCE:IMPORTS ===",
194 "import DevDashboard from '@/features/dev/DevDashboard';",
195 )?;
196 utils::insert_at_marker(
197 &project_root.join("frontend/src/App.tsx"),
198 markers::APP_ROUTES,
199 " <Route path=\"/dev\" element={<DevDashboard />} />",
200 )?;
201
202 println!();
203 println!(
204 "{}",
205 "Dev dashboard installed successfully!".green().bold()
206 );
207 println!(" Visit /dev to see the developer dashboard.");
208
209 Ok(())
210}