Skip to main content

romance_core/addon/
dashboard.rs

1use 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        // Delete files
33        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        // Remove from handlers/mod.rs
59        super::remove_line_from_file(
60            &project_root.join("backend/src/handlers/mod.rs"),
61            "pub mod dev_dashboard;",
62        )?;
63
64        // Remove from routes/mod.rs
65        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        // Remove from frontend App.tsx (both import and Route)
75        super::remove_line_from_file(
76            &project_root.join("frontend/src/App.tsx"),
77            "DevDashboard",
78        )?;
79
80        // Regenerate AI context
81        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    // Discover entities
106    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    // Generate dev dashboard handler
140    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    // Generate dev dashboard routes
151    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    // Generate frontend dashboard
162    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    // Register routes
173    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    // Register frontend route
191    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}