mblue/
lib.rs

1use std::{sync::{Arc}, fs::{self}, rc::Rc, collections::{HashMap}, println};
2use actix_files as afs;
3use futures_util::{FutureExt};
4use types::{AppSettings};
5use uuid::Uuid;
6
7use actix_web::{App, HttpServer, web::{self}, HttpResponse, HttpRequest};
8
9use tera::{Tera, Context};
10
11use crate::{types::AppState, components::load_component_custom_function};
12
13use self::broadcast::Broadcaster;
14
15pub mod types;
16pub mod broadcast;
17mod components;
18mod routes;
19
20
21pub async fn configure(settings: AppSettings) -> std::io::Result<()>{
22    let broadcaster = Broadcaster::create();
23    // Create the Actix HTTP server
24    HttpServer::new(move || {
25        let mut tera = Tera::default();
26        //Loading layout
27        let layout_content = fs::read_to_string(&settings.master_layout).expect("Error loading layout");
28        tera.add_raw_template("layout", &layout_content).expect("Error adding template");
29        println!("----------------------------------------------------");
30        println!("Starting route listening for {:?}",std::thread::current().id());
31        let mut app = App::new();
32        app = app.service(routes::state_changed_action);        
33        app = app.service(routes::state_changed);        
34        app = app.service(routes::component_changed);
35        app = app.service(routes::components);
36        app = app.service(afs::Files::new(&settings.static_folder, &settings.static_folder).show_files_listing());
37
38        let dynamic_pages = settings.pages.clone();     
39        let components = settings.components.clone();
40
41        for (key,comp) in &components {
42            let template = comp.template.clone();
43            let template_clone = &template;
44            tera.add_raw_template(key, template_clone.clone().unwrap().as_str()).expect("Error adding template");
45        }
46
47        //Makes sure tera is filled with all templates
48        for (key,component) in &components {            
49            let component_name = key.clone().to_string();
50            let comp = component.clone();
51            let custom_function = load_component_custom_function(
52                tera.clone()
53                ,component_name.clone()
54                ,comp.load.clone().unwrap()
55            );
56
57            tera.register_function(
58                &key,
59                custom_function
60            );
61        }
62
63
64        app = app.app_data(web::Data::new(AppState {
65            broadcaster: Arc::clone(&broadcaster),
66            components: components.clone(),
67            pages: dynamic_pages.clone(),
68            tera: tera.clone()
69        }));
70
71        // Add routes to the app
72        for route in dynamic_pages {
73            let route_cloned = route.clone();
74            let route_path = Rc::new(route_cloned.clone().path);
75            
76            let mut has_action = false;
77            let mut has_load = false;
78
79            if route.action.is_some(){
80                has_action = true;
81            }
82
83            if route.load.is_some() {
84                has_load = true;
85            }
86
87            if has_action {
88                app = app.route(&route_path, web::post().to(move |req: HttpRequest,state: web::Data<AppState>, form: web::Form<HashMap<String, String>>| {
89                let app_state = state.clone(); // Clone app_state inside the closure
90                let mut result = "".to_string();
91                async move {
92                    let path = req.path().clone();
93                    let mut _page = state.pages.iter().find(|p| p.path == path);
94                    if _page.is_some(){
95                        let mut context = Context::new();
96                        let page = _page.unwrap();
97                        let mut tera = app_state.tera.clone();
98                        let template = page.template.clone();
99                        tera.add_raw_template(&path, &template).expect("Error adding template");
100        
101                        //First we need to call the action, and then we populate the load
102                        let page_action_function = page.action.clone().unwrap();
103                        let action_context = page_action_function(
104                            Arc::new(req.clone()),
105                            Arc::new(context.clone()),
106                            form
107                        ).await;
108                        let load_context =  page.load.clone().unwrap()(
109                            Arc::new(req.clone()),
110                            action_context
111                        ).await;
112
113                        context.extend(Arc::try_unwrap(load_context).expect("Error unwrapping context"));  
114
115                        let body = tera.render(&path, &context).expect("Error rendering template");
116                        let mut layout_context = Context::new();
117                        layout_context.insert("body", &body);
118                        let id = Uuid::new_v4();
119                        layout_context.insert("uuid", &id.to_string());
120        
121                        result = tera.render("layout", &layout_context).expect("Error rendering layout template");
122                    }
123
124                    HttpResponse::Ok().body(result)                    
125                }.boxed_local()                    
126                }));
127            }
128
129            if has_load {
130                app = app.route(&route_path, web::get().to(move |req: HttpRequest,state: web::Data<AppState>| {
131                    let app_state = state.clone(); // Clone app_state inside the closure
132                    let mut result = "".to_string();
133                    async move {
134                        let path = req.path().clone();
135                        let mut _page = state.pages.iter().find(|p| p.path == path);
136                        if _page.is_some(){
137                            let mut context = Context::new();
138                            context.insert("state","null");
139                            let page = _page.unwrap();
140                            let mut tera = app_state.tera.clone();
141                            let template = page.template.clone();
142                            tera.add_raw_template(&path, &template).expect("Error adding template");
143    
144                            let page_function = page.load.clone().unwrap();
145                            let action_context = page_function(
146                                Arc::new(req.clone()),
147                                Arc::new(context.clone())
148                            ).await;
149    
150                            context.extend(Arc::try_unwrap(action_context).expect("Error unwrapping context"));  
151    
152                            let body = tera.render(&path, &context).expect("Error rendering template");
153                            let mut layout_context = Context::new();
154                            layout_context.insert("body", &body);
155                            let id = Uuid::new_v4();
156                            layout_context.insert("uuid", &id.to_string());
157            
158                            result = tera.render("layout", &layout_context).expect("Error rendering layout template");
159                        }
160    
161                        HttpResponse::Ok().body(result)                    
162                    }.boxed_local()                    
163                    }));
164    
165                    println!("Listening to {}",&route_path);               
166                }
167            }
168
169
170        app
171    })
172    .bind(format!("{}:{}", settings.address, settings.port)).expect("Error binding server and/or port")
173    .run()
174    .await
175    
176
177
178}
179