sandy/
lib.rs

1use std::collections::{HashMap};
2use std::net::TcpStream;
3use std::io::{Read, Write};
4use std::sync::{Arc, Mutex};
5use std::thread;
6use tera::{Context, Tera};
7use std::fs;
8use chrono::{Utc, Datelike};
9
10pub struct Server {
11    routes: HashMap<String, Arc<Mutex<dyn Fn(&str, HashMap<String, String>, &str, HashMap<String, String>) -> Result<String, String> + Send + Sync>>>,
12    static_routes: HashMap<String, String>,
13}
14
15impl Clone for Server {
16    fn clone(&self) -> Self {
17        Server {
18            routes: self.routes.clone(),
19            static_routes: self.static_routes.clone(),
20        }
21    }
22}
23
24impl Server {
25    pub fn new() -> Self {
26        Server {
27            routes: HashMap::new(),
28            static_routes: HashMap::new(),
29        }
30    }
31
32    pub fn route<F>(&mut self, path: &str, func: F)
33    where
34        F: Fn(&str, HashMap<String, String>, &str, HashMap<String, String>) -> Result<String, String> + 'static + Send + Sync,
35    {
36        self.routes.insert(path.to_string(), Arc::new(Mutex::new(func)));
37    }
38
39    pub fn static_route(&mut self, path: &str, content: &str) {
40        self.static_routes.insert(path.to_string(), content.to_string());
41    }
42
43    pub fn add_route_to_sitemap(&self, path: &str, lastmod: bool, changefreq: &str, priority: f32, base_url: &str) {
44        let current_date = Utc::now().format("%Y-%m-%d").to_string();
45        let full_url = format!("{}{}", base_url, path);
46
47        let mut sitemap_content = if let Ok(content) = fs::read_to_string("static/sitemap.xml") {
48            content
49        } else {
50            String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n</urlset>")
51        };
52
53        let url_to_check = format!("<loc>{}</loc>", full_url);
54        if !sitemap_content.contains(&url_to_check) {
55            let mut route_entry = String::new();
56            route_entry.push_str("    <url>\n");
57            route_entry.push_str(&format!("        <loc>{}</loc>\n", full_url));
58            if lastmod {
59                route_entry.push_str(&format!("        <lastmod>{}</lastmod>\n", current_date));
60            }
61            route_entry.push_str(&format!("        <changefreq>{}</changefreq>\n", changefreq));
62            route_entry.push_str(&format!("        <priority>{}</priority>\n", priority));
63            route_entry.push_str("    </url>\n");
64
65            sitemap_content.insert_str(sitemap_content.rfind("</urlset>").unwrap(), &route_entry);
66            if let Err(err) = fs::write("static/sitemap.xml", sitemap_content) {
67                eprintln!("Failed to write sitemap: {}", err);
68            }
69        } else {
70            println!("The route {} already exists in the sitemap.", url_to_check);
71        }
72    }
73
74    pub fn generate_sitemap(&self, sitemap: bool, lastmod: bool, changefreq: &str, priority: f32, base_url: &str) {
75        if sitemap {
76            let mut sitemap_content = if let Ok(content) = fs::read_to_string("static/sitemap.xml") {
77                content
78            } else {
79                String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n</urlset>")
80            };
81
82            let current_date = Utc::now().format("%Y-%m-%d").to_string();
83
84            for (path, _) in &self.static_routes {
85                let full_url = format!("{}{}", base_url, path);
86
87                let url_to_check = format!("<loc>{}</loc>", full_url);
88                if !sitemap_content.contains(&url_to_check) {
89                    let mut route_entry = String::new();
90                    route_entry.push_str("    <url>\n");
91                    route_entry.push_str(&format!("        <loc>{}</loc>\n", full_url));
92                    if lastmod {
93                        route_entry.push_str(&format!("        <lastmod>{}</lastmod>\n", current_date));
94                    }
95                    route_entry.push_str(&format!("        <changefreq>{}</changefreq>\n", changefreq));
96                    route_entry.push_str(&format!("        <priority>{}</priority>\n", priority));
97                    route_entry.push_str("    </url>\n");
98
99                    sitemap_content.insert_str(sitemap_content.rfind("</urlset>").unwrap(), &route_entry);
100                } else {
101                    println!("The route {} already exists in the sitemap.", url_to_check);
102                }
103            }
104
105            if let Err(err) = fs::write("static/sitemap.xml", sitemap_content) {
106                eprintln!("Failed to write sitemap: {}", err);
107            }
108        }
109    }
110
111    pub fn load_static_files(&mut self, static_folder: &str) {
112        if let Ok(entries) = fs::read_dir(static_folder) {
113            for entry in entries {
114                if let Ok(entry) = entry {
115                    let path = entry.path();
116                    if path.is_file() {
117                        if let Some(file_name) = path.file_name() {
118                            if let Some(file_str) = file_name.to_str() {
119                                let route = format!("/{}", file_str);
120                                self.static_route(&route, &path.to_string_lossy());
121                            }
122                        }
123                    }
124                }
125            }
126        }
127    }    
128
129    fn serve_static(&self, path: &str) -> Option<String> {
130        if let Some(file_path) = self.static_routes.get(path) {
131            if let Ok(content) = fs::read_to_string(&file_path) {
132                let response = format!(
133                    "HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",
134                    content.len(),
135                    content
136                );
137                return Some(response);
138            }
139        }
140        None
141    }    
142
143    fn handle_static(&self, path: &str) -> Option<String> {
144        if let Some(content) = self.static_routes.get(path) {
145            Some(content.clone())
146        } else {
147            None
148        }
149    }
150
151    fn path_matches_route(&self, path: &str, route: &str) -> Option<HashMap<String, String>> {
152        let path_parts: Vec<&str> = path.split('/').collect();
153        let route_parts: Vec<&str> = route.split('/').collect();
154    
155        if path_parts.len() != route_parts.len() {
156            return None;
157        }
158    
159        let mut params = HashMap::new();
160    
161        for (path_part, route_part) in path_parts.iter().zip(route_parts.iter()) {
162            if route_part.starts_with(':') {
163                let slug_name = &route_part[1..];
164                params.insert(slug_name.to_string(), path_part.to_string());
165            } else if path_part != route_part {
166                return None;
167            }
168        }
169    
170        Some(params)
171    }
172
173    fn handle_connection(&self, mut stream: TcpStream) {
174        let mut buffer = [0; 1024];
175        stream.read(&mut buffer).unwrap();
176    
177        let request = String::from_utf8_lossy(&buffer[..]);
178        let request_parts: Vec<&str> = request.split("\r\n\r\n").collect();
179    
180        let path_params: Vec<&str> = request_parts[0].split_whitespace().collect();
181        let path = path_params[1].split('?').next().unwrap_or("");
182    
183        if let Some(content) = self.serve_static(path) {
184            stream.write_all(content.as_bytes()).unwrap();
185            stream.flush().unwrap();
186            return;
187        }
188    
189        let method = path_params[0];
190        let mut params = HashMap::new();
191        let mut data = HashMap::new();
192    
193        if let Some(query_params) = path_params[1].splitn(2, '?').nth(1) {
194            let payload_parts: Vec<&str> = query_params.split("&").collect();
195            for part in payload_parts {
196                let kv: Vec<&str> = part.split("=").collect();
197                if kv.len() == 2 {
198                    params.insert(kv[0].to_string(), kv[1].to_string());
199                }
200            }
201        }
202    
203        let body_params: Vec<&str> = request_parts[1].split('&').collect();
204        for part in body_params {
205            let kv: Vec<&str> = part.split("=").collect();
206            if kv.len() == 2 {
207                data.insert(kv[0].to_string(), kv[1].to_string());
208            }
209        }
210    
211        let response = match self.static_routes.get(path) {
212            Some(content) => Ok(format!("{}", content)),
213            None => match self.routes.iter().find_map(|(route, handler)| {
214                if let Some(params) = self.path_matches_route(path, route) {
215                    let handler = handler.lock().unwrap();
216                    let cloned_params = params.clone();
217                    let cloned_data = data.clone();
218                    return Some(handler(path, cloned_params, method, cloned_data).map(|res| {
219                        res
220                    }));
221                }
222                None
223            }) {
224                Some(result) => result,
225                None => Err("HTTP/1.1 404 NOT FOUND\n\nPath Not Found".to_string()),
226            },
227        };
228    
229        match response {
230            Ok(result) => {
231                stream.write_all(result.as_bytes()).unwrap();
232                stream.flush().unwrap();
233            }
234            Err(err) => {
235                stream.write_all(err.as_bytes()).unwrap();
236                stream.flush().unwrap();
237            }
238        }
239    }    
240    
241
242    pub fn run(mut self, ip: &str, port: &str) {
243        self.load_static_files("static");
244        
245        let listener = std::net::TcpListener::bind(format!("{}:{}", ip, port)).unwrap();
246
247        for stream in listener.incoming() {
248            let stream = stream.unwrap();
249            let server = self.clone();
250
251            thread::spawn(move || {
252                server.handle_connection(stream);
253            });
254        }
255    }
256}
257
258pub struct TemplateEngine;
259
260impl TemplateEngine {
261    pub fn render(template: &str, context: &HashMap<&str, &str>) -> String {
262        let mut tera = Tera::default();
263        tera.add_raw_template("template", template).unwrap();
264
265        let mut ctx = Context::new();
266        for (key, val) in context {
267            ctx.insert(*key, val);
268        }
269
270        tera.render("template", &ctx).unwrap()
271    }
272
273    pub fn render_template(template_name: &str, context: &HashMap<&str, &str>) -> Result<String, String> {
274        let file_content = match std::fs::read_to_string(format!("templates/{}", template_name)) {
275            Ok(content) => content,
276            Err(_) => return Err("Template file not found".to_string()),
277        };
278
279        let mut tera = Tera::default();
280        tera.add_raw_template("template", &file_content).unwrap();
281
282        let mut ctx = Context::new();
283        for (key, val) in context {
284            ctx.insert(*key, val);
285        }
286
287        tera.render("template", &ctx).map_err(|e| e.to_string())
288    }
289}