fresh start
This commit is contained in:
		
							parent
							
								
									2d5039aa75
								
							
						
					
					
						commit
						a3b28f8105
					
				
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										11
									
								
								Cargo.toml
								
								
								
								
							
							
						
						
									
										11
									
								
								Cargo.toml
								
								
								
								
							| 
						 | 
					@ -1,17 +1,8 @@
 | 
				
			||||||
[package]
 | 
					[package]
 | 
				
			||||||
name = "tracking_pixel"
 | 
					name = "tracking_pixel"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.2.0"
 | 
				
			||||||
edition = "2021"
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
					# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
askama = "0.12.0"
 | 
					 | 
				
			||||||
base64 = "0.21.0"
 | 
					 | 
				
			||||||
chrono = "0.4.24"
 | 
					 | 
				
			||||||
crypto-hash = "0.3.4"
 | 
					 | 
				
			||||||
oneshot = "0.1.5"
 | 
					 | 
				
			||||||
rand = "0.8.5"
 | 
					 | 
				
			||||||
rouille="3.6.1"
 | 
					 | 
				
			||||||
rusqlite = { version = "0.29.0", features = ["bundled", "chrono"] }
 | 
					 | 
				
			||||||
thiserror = "1.0.40"
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +0,0 @@
 | 
				
			||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html>
 | 
					 | 
				
			||||||
    <head>
 | 
					 | 
				
			||||||
        <title>Hello</title>
 | 
					 | 
				
			||||||
    </head>
 | 
					 | 
				
			||||||
    <body>
 | 
					 | 
				
			||||||
        <h1>
 | 
					 | 
				
			||||||
            Hallo
 | 
					 | 
				
			||||||
        </h1>
 | 
					 | 
				
			||||||
    </body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
							
								
								
									
										101
									
								
								src/db.rs
								
								
								
								
							
							
						
						
									
										101
									
								
								src/db.rs
								
								
								
								
							| 
						 | 
					@ -1,101 +0,0 @@
 | 
				
			||||||
use rusqlite::{Connection};
 | 
					 | 
				
			||||||
use std::{sync::mpsc::{Sender, Receiver, channel, SendError}, thread::{self, JoinHandle}, any::Any};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum DBMessage<T> {
 | 
					 | 
				
			||||||
    HangUp,
 | 
					 | 
				
			||||||
    Message(T)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug)]
 | 
					 | 
				
			||||||
pub struct DBSender<MsgT> {
 | 
					 | 
				
			||||||
    sender: Sender<DBMessage<MsgT>>
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// #[derive(Clone)] requires MsgT to be Clone, but that's not necessary
 | 
					 | 
				
			||||||
impl<MsgT> Clone for DBSender<MsgT> {
 | 
					 | 
				
			||||||
    fn clone(&self) -> Self {
 | 
					 | 
				
			||||||
        Self {sender: self.sender.clone()}
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl<MsgT> DBSender<MsgT> {
 | 
					 | 
				
			||||||
    pub fn send(&self, msg: MsgT) -> Result<(), SendError<MsgT>>{
 | 
					 | 
				
			||||||
        if let Err(err) = self.sender.send(DBMessage::Message(msg)) {
 | 
					 | 
				
			||||||
            if let DBMessage::Message(msg) = err.0 {
 | 
					 | 
				
			||||||
                Err(SendError(msg))
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                unreachable!();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            Ok(())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    fn hang_up(&self) -> Result<(), SendError<()>> {
 | 
					 | 
				
			||||||
        if let Err(_) = self.sender.send(DBMessage::HangUp) {
 | 
					 | 
				
			||||||
            Err(SendError(()))
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else {
 | 
					 | 
				
			||||||
            Ok(())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug)]
 | 
					 | 
				
			||||||
pub struct DBConnection<MsgT: Send + 'static> 
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    pub sender: DBSender<MsgT>,
 | 
					 | 
				
			||||||
    join_handle: Option<JoinHandle<()>>
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl<MsgT: Send + 'static> DBConnection<MsgT>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    pub fn init<F: Fn(MsgT, &Connection) + Send + 'static>(connection: Connection, op: F) -> Self {
 | 
					 | 
				
			||||||
        let (sender, receiver) = channel();
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
        let join_handle = thread::spawn(move || Self::db_thread(receiver, connection, op));
 | 
					 | 
				
			||||||
        let sender = DBSender { sender };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Self {sender, join_handle: Some(join_handle)}
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    fn db_thread<F: Fn(MsgT, &Connection) + Send>(receiver: Receiver<DBMessage<MsgT>>, connection: Connection, op: F) {
 | 
					 | 
				
			||||||
        while let Ok(db_msg) = receiver.recv() {
 | 
					 | 
				
			||||||
            // _ = connection.execute("INSERT INTO hits VALUES(?1, ?2, ?3, ?4);", params![hit.pixel_id, hit.ip, hit.user_agent, hit.date]).unwrap();
 | 
					 | 
				
			||||||
            if let DBMessage::Message(msg) = db_msg {
 | 
					 | 
				
			||||||
                op(msg, &connection);
 | 
					 | 
				
			||||||
            } 
 | 
					 | 
				
			||||||
            else if let DBMessage::HangUp = db_msg {
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // execute last messages in buffer
 | 
					 | 
				
			||||||
        for db_msg in receiver.try_iter() {
 | 
					 | 
				
			||||||
            if let DBMessage::Message(msg) = db_msg {
 | 
					 | 
				
			||||||
                op(msg, &connection);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
                                                        // static is implied, but included for clearness
 | 
					 | 
				
			||||||
    pub fn finish(&mut self) -> Result<(), Box<dyn Any + Send + 'static>> {
 | 
					 | 
				
			||||||
        // let Self{sender, join_handle} = self;
 | 
					 | 
				
			||||||
        if let Err(err) = self.sender.hang_up() {
 | 
					 | 
				
			||||||
            return Err(Box::new(err));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if let Some(handle) = self.join_handle.take() {
 | 
					 | 
				
			||||||
            handle.join()
 | 
					 | 
				
			||||||
        } 
 | 
					 | 
				
			||||||
        else {
 | 
					 | 
				
			||||||
            Ok(())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl<MsgT: Send> Drop for DBConnection<MsgT> {
 | 
					 | 
				
			||||||
    fn drop(&mut self) {
 | 
					 | 
				
			||||||
        self.finish().unwrap();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,18 +0,0 @@
 | 
				
			||||||
use rouille::Response;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn javascript_redirect(path: &str) -> Response {
 | 
					 | 
				
			||||||
    Response::html(format!("
 | 
					 | 
				
			||||||
    <!DOCTYPE html>
 | 
					 | 
				
			||||||
    <html>
 | 
					 | 
				
			||||||
      <head>
 | 
					 | 
				
			||||||
        <title>Redirecting...</title>   
 | 
					 | 
				
			||||||
        <meta http-equiv=\"refresh\" content=\"0;URL='{path}'\">
 | 
					 | 
				
			||||||
        <script>
 | 
					 | 
				
			||||||
            window.location.href = \"{path}\";
 | 
					 | 
				
			||||||
        </script>
 | 
					 | 
				
			||||||
      </head>
 | 
					 | 
				
			||||||
      <body>
 | 
					 | 
				
			||||||
        <h1>Redirecting...</h1>
 | 
					 | 
				
			||||||
      </body>
 | 
					 | 
				
			||||||
    </html>"))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										206
									
								
								src/main.rs
								
								
								
								
							
							
						
						
									
										206
									
								
								src/main.rs
								
								
								
								
							| 
						 | 
					@ -1,205 +1 @@
 | 
				
			||||||
use std::{fs, sync::{Mutex}};
 | 
					fn main() {}
 | 
				
			||||||
 | 
					 | 
				
			||||||
use askama::Template;
 | 
					 | 
				
			||||||
use rouille::{Response, router, input, Request, try_or_400, post_input};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use pixel::{Hit, PixelManager, PixelManagerDelegate, TrackingPixel, User};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
mod db;
 | 
					 | 
				
			||||||
mod pixel;
 | 
					 | 
				
			||||||
mod helpers;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn main() {
 | 
					 | 
				
			||||||
    let white_1x1_pixel_png = fs::read("assets/white_pixel.png").unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let pixel_manager = PixelManager::build("app.db").unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let pixel_manager_delegate = PixelManagerDelegate::new(&pixel_manager);
 | 
					 | 
				
			||||||
    // let pixel_id = pixel_manager.create_pixel("Hallo mac").unwrap();
 | 
					 | 
				
			||||||
    // print!("{pixel_id}");
 | 
					 | 
				
			||||||
    // pixel_manager.register_hit(pixel_id, "ai pie", "joeser eedsjent", chrono::offset::Utc::now());
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    let delegate_mutex = Mutex::new(pixel_manager_delegate);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    rouille::start_server("0.0.0.0:3737", move |request| {
 | 
					 | 
				
			||||||
        router!(request,
 | 
					 | 
				
			||||||
            (GET) ["/manage"] => {
 | 
					 | 
				
			||||||
                let delegate = delegate_mutex.lock().unwrap().clone();
 | 
					 | 
				
			||||||
                // panicking on poisoned mutex is acceptable
 | 
					 | 
				
			||||||
                manage_index(request, &delegate)
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            (GET) ["/manage/pixel/{pixel_id}", pixel_id: String] => {
 | 
					 | 
				
			||||||
                let delegate = delegate_mutex.lock().unwrap().clone();
 | 
					 | 
				
			||||||
                manage_pixel(&pixel_id, request, &delegate)
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            (POST) ["/manage/pixel/create"] => {
 | 
					 | 
				
			||||||
                let delegate = delegate_mutex.lock().unwrap().clone();
 | 
					 | 
				
			||||||
                create_pixel(request, &delegate)
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            (POST) ["/manage/pixel/delete"] => {
 | 
					 | 
				
			||||||
                let delegate = delegate_mutex.lock().unwrap().clone();
 | 
					 | 
				
			||||||
                delete_pixel(request, &delegate)
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            (GET) ["/img/{id}/white.png", id: String] => {
 | 
					 | 
				
			||||||
                let delegate = delegate_mutex.lock().unwrap().clone();
 | 
					 | 
				
			||||||
                register_hit(&id, request, &delegate);
 | 
					 | 
				
			||||||
                Response::from_data("image/png", white_1x1_pixel_png.clone())
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            _ => {
 | 
					 | 
				
			||||||
                println!("404: {:#?}", request.url());
 | 
					 | 
				
			||||||
                Response::empty_404()
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Template)]
 | 
					 | 
				
			||||||
#[template(path = "index.html")]
 | 
					 | 
				
			||||||
struct IndexTemplate<'a> {
 | 
					 | 
				
			||||||
    username: &'a str,
 | 
					 | 
				
			||||||
    pixels: Vec<(String, String)>
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl<'a> IndexTemplate<'a> {
 | 
					 | 
				
			||||||
    fn new(username: &'a str, pixels: &Vec<TrackingPixel>) -> Self {
 | 
					 | 
				
			||||||
        Self {
 | 
					 | 
				
			||||||
            username,
 | 
					 | 
				
			||||||
            pixels: pixels.iter().map(|pix| (pix.name.clone(), pix.url_safe_encode())).collect()
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Template)]
 | 
					 | 
				
			||||||
#[template(path = "manage/pixel.html")]
 | 
					 | 
				
			||||||
struct PixelTemplate {
 | 
					 | 
				
			||||||
    pixel_name: String,
 | 
					 | 
				
			||||||
    hits: Vec<(String, String, String)>,
 | 
					 | 
				
			||||||
    pixel_url: String,
 | 
					 | 
				
			||||||
    pixel_id: String
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl PixelTemplate {
 | 
					 | 
				
			||||||
    fn new(pixel: &TrackingPixel, hits: &Vec<Hit>) -> Self {
 | 
					 | 
				
			||||||
        Self {
 | 
					 | 
				
			||||||
            pixel_name: pixel.name.clone(),
 | 
					 | 
				
			||||||
            hits: hits.iter().map(|hit| (hit.date.to_string(), hit.ip.clone(), hit.user_agent.clone())).collect(),
 | 
					 | 
				
			||||||
            pixel_url: get_url_for_pixel(&pixel),
 | 
					 | 
				
			||||||
            pixel_id: pixel.url_safe_encode()
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn manage_index(request: &Request, manager_delegate: &PixelManagerDelegate) -> Response {
 | 
					 | 
				
			||||||
    let user = match auth_user(request, &manager_delegate) {
 | 
					 | 
				
			||||||
        Ok(user) => user,
 | 
					 | 
				
			||||||
        Err(response) => return response
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    match manager_delegate.get_manager().get_pixels_for_user(&user) {
 | 
					 | 
				
			||||||
        Ok(pixels) => {
 | 
					 | 
				
			||||||
            let index_template = IndexTemplate::new(&user.username, &pixels);
 | 
					 | 
				
			||||||
            match index_template.render() {
 | 
					 | 
				
			||||||
                Ok(page) => Response::html(page),
 | 
					 | 
				
			||||||
                Err(_) => Response::text("Page error").with_status_code(500)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        Err(_) => Response::text("DB error").with_status_code(500)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn auth_user(request: &Request, manager_delegate: &PixelManagerDelegate) -> Result<User, Response> {
 | 
					 | 
				
			||||||
    let auth = match input::basic_http_auth(request) {
 | 
					 | 
				
			||||||
        Some(a) => a,
 | 
					 | 
				
			||||||
        None => return Err(Response::basic_http_auth_login_required("manage"))
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    match manager_delegate.get_manager().get_user(&auth.login, &auth.password) {
 | 
					 | 
				
			||||||
        Ok(user) => Ok(user),
 | 
					 | 
				
			||||||
        Err(_) => return Err(Response::basic_http_auth_login_required("manage"))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn manage_pixel(pixel_id: &str, request: &Request, manager_delegate: &PixelManagerDelegate) -> Response {
 | 
					 | 
				
			||||||
    let user = match auth_user(request, &manager_delegate) {
 | 
					 | 
				
			||||||
        Ok(user) => user,
 | 
					 | 
				
			||||||
        Err(response) => return response
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    let pixel = match manager_delegate.get_manager().get_pixel_from_encoded(pixel_id, Some(&user)) {
 | 
					 | 
				
			||||||
        Ok(pixel_opt) => {
 | 
					 | 
				
			||||||
            match pixel_opt {
 | 
					 | 
				
			||||||
                Some(pixel) => pixel,
 | 
					 | 
				
			||||||
                None => return Response::text("Unknown pixel").with_status_code(500)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        Err(_) => return Response::text("Encoding error").with_status_code(500)
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let hits = match manager_delegate.get_manager().get_hits_for_pixel(&pixel, Some(&user)) {
 | 
					 | 
				
			||||||
        Ok(hits) => hits,
 | 
					 | 
				
			||||||
        Err(_) => return Response::text("DB error").with_status_code(500)
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let pixel_template = PixelTemplate::new(&pixel, &hits);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    match pixel_template.render() {
 | 
					 | 
				
			||||||
        Ok(page) => Response::html(page),
 | 
					 | 
				
			||||||
        Err(_) => Response::text("Page error").with_status_code(500)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn create_pixel(request: &Request, manager_delegate: &PixelManagerDelegate) -> Response {
 | 
					 | 
				
			||||||
    let user = match auth_user(request, &manager_delegate) {
 | 
					 | 
				
			||||||
        Ok(user) => user,
 | 
					 | 
				
			||||||
        Err(response) => return response
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    let input = try_or_400!(post_input!(request, {
 | 
					 | 
				
			||||||
        pixel_name: String
 | 
					 | 
				
			||||||
    }));
 | 
					 | 
				
			||||||
    let _pixel = match manager_delegate.get_manager().create_pixel(&input.pixel_name, &user) {
 | 
					 | 
				
			||||||
        Ok(pixel) => pixel,
 | 
					 | 
				
			||||||
        Err(_) => return Response::empty_400()
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    // Response::redirect_303("/manage")
 | 
					 | 
				
			||||||
    helpers::javascript_redirect("/manage")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn delete_pixel(request: &Request, manager_delegate: &PixelManagerDelegate) -> Response {
 | 
					 | 
				
			||||||
    let user = match auth_user(request, &manager_delegate) {
 | 
					 | 
				
			||||||
        Ok(user) => user,
 | 
					 | 
				
			||||||
        Err(response) => return response,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let input = try_or_400!(post_input!(request, {
 | 
					 | 
				
			||||||
        pixel_id: String
 | 
					 | 
				
			||||||
    }));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let pixel = match manager_delegate.get_manager().get_pixel_from_encoded(&input.pixel_id, Some(&user)) {
 | 
					 | 
				
			||||||
        Ok(pixel) => pixel,
 | 
					 | 
				
			||||||
        Err(_) => return Response::text("DB error").with_status_code(500)
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    if let Some(pixel) = pixel {
 | 
					 | 
				
			||||||
        if manager_delegate.get_manager().delete_pixel(pixel, Some(&user)).is_err() {
 | 
					 | 
				
			||||||
            return Response::text("DB error").with_status_code(500);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        return Response::empty_400(); // could not find pixel
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // Response::redirect_303("/manage")
 | 
					 | 
				
			||||||
    helpers::javascript_redirect("/manage")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn register_hit(pixel_id: &str, request: &Request, manager_delegate: &PixelManagerDelegate) {
 | 
					 | 
				
			||||||
    let ua = request.header("User-Agent").unwrap_or_default();
 | 
					 | 
				
			||||||
    let ip = request.header("X-Forwarded-For").unwrap_or_default();
 | 
					 | 
				
			||||||
    _ = manager_delegate.get_manager().register_hit_with_encoded_pixel(pixel_id, &ip, ua, chrono::offset::Utc::now());
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn get_url_for_pixel(pixel: &TrackingPixel) -> String {
 | 
					 | 
				
			||||||
    format!("https://track.willem.page/img/{}/white.png", pixel.url_safe_encode())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										482
									
								
								src/pixel.rs
								
								
								
								
							
							
						
						
									
										482
									
								
								src/pixel.rs
								
								
								
								
							| 
						 | 
					@ -1,482 +0,0 @@
 | 
				
			||||||
use std::sync::mpsc;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use crate::db::{DBConnection, DBSender};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use base64::Engine;
 | 
					 | 
				
			||||||
use chrono::{DateTime, Utc};
 | 
					 | 
				
			||||||
use crypto_hash::{Algorithm, hex_digest};
 | 
					 | 
				
			||||||
use rusqlite::{params, Connection, OptionalExtension};
 | 
					 | 
				
			||||||
use thiserror::Error;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Error, Debug)]
 | 
					 | 
				
			||||||
pub enum Error {
 | 
					 | 
				
			||||||
    #[error("Username or password does not exist in database")]
 | 
					 | 
				
			||||||
    InvalidUserData(),
 | 
					 | 
				
			||||||
    #[error("Database did not respond")]
 | 
					 | 
				
			||||||
    RecvError(#[from] oneshot::RecvError),
 | 
					 | 
				
			||||||
    #[error("Could not send data to database")]
 | 
					 | 
				
			||||||
    SendError(#[from] mpsc::SendError<DBConnectionMessage>),
 | 
					 | 
				
			||||||
    #[error("Timeout receiving data from database")]
 | 
					 | 
				
			||||||
    TimeoutError(#[from] oneshot::RecvTimeoutError),
 | 
					 | 
				
			||||||
    #[error("User is not authenticated")]
 | 
					 | 
				
			||||||
    UserNotAuthed(),
 | 
					 | 
				
			||||||
    #[error("No user with this username and password")]
 | 
					 | 
				
			||||||
    InvalidCredentials(),
 | 
					 | 
				
			||||||
    #[error("Data was not added to database correctly")]
 | 
					 | 
				
			||||||
    DBError(),
 | 
					 | 
				
			||||||
    #[error("The username for this user already exists")]
 | 
					 | 
				
			||||||
    NewUserAlreadyExists(),
 | 
					 | 
				
			||||||
    #[error("Invalid encoding in ID")]
 | 
					 | 
				
			||||||
    InvalidEncodedID(#[from] base64::DecodeError),
 | 
					 | 
				
			||||||
    #[error("Pixel not found in users pixels")]
 | 
					 | 
				
			||||||
    PixelNotFound()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug)]
 | 
					 | 
				
			||||||
enum DBConnectionMessage {
 | 
					 | 
				
			||||||
    RegisterHit(Hit),
 | 
					 | 
				
			||||||
    CreatePixel(TrackingPixel),
 | 
					 | 
				
			||||||
    DeletePixel(i64),
 | 
					 | 
				
			||||||
    AuthUser(User, oneshot::Sender<bool>),
 | 
					 | 
				
			||||||
    GetUserFromName(String, oneshot::Sender<Option<User>>),
 | 
					 | 
				
			||||||
    GetUserFromId(i64, oneshot::Sender<Option<User>>),
 | 
					 | 
				
			||||||
    CreateUser(User),
 | 
					 | 
				
			||||||
    DeleteUser(i64),
 | 
					 | 
				
			||||||
    GetHitsForPixel(TrackingPixel, oneshot::Sender<Option<Vec<Hit>>>, Option<User>),
 | 
					 | 
				
			||||||
    GetPixelsForUser(User, oneshot::Sender<Option<Vec<TrackingPixel>>>),
 | 
					 | 
				
			||||||
    GetPixel(i64, Option<User>, oneshot::Sender<Option<TrackingPixel>>)
 | 
					 | 
				
			||||||
} // messy, there is probably a better way to do this but it works so oh well
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone)]
 | 
					 | 
				
			||||||
pub struct TrackingPixel {
 | 
					 | 
				
			||||||
    id: i64, // ID may not be omitted when creating new pixel
 | 
					 | 
				
			||||||
    pub name: String,
 | 
					 | 
				
			||||||
    user_id: i64
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl TrackingPixel {
 | 
					 | 
				
			||||||
    pub fn url_safe_encode(&self) -> String {
 | 
					 | 
				
			||||||
        base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.id.to_be_bytes())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
    fn url_safe_decode_id(encoded_id: &str) -> Result<i64, base64::DecodeError> {
 | 
					 | 
				
			||||||
        let bytes_vec = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(encoded_id)?;
 | 
					 | 
				
			||||||
        let mut bytes_arr = [0; 8];
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        for i in 0..8 {
 | 
					 | 
				
			||||||
            bytes_arr[i] = bytes_vec.get(i).cloned().unwrap_or_default();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        Ok(i64::from_be_bytes(bytes_arr))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone)]
 | 
					 | 
				
			||||||
pub struct Hit {
 | 
					 | 
				
			||||||
    id: Option<i64>, // ID is omitted when creating new hit
 | 
					 | 
				
			||||||
    pixel_id: i64,
 | 
					 | 
				
			||||||
    pub ip: String,
 | 
					 | 
				
			||||||
    pub user_agent: String,
 | 
					 | 
				
			||||||
    pub date: DateTime<Utc>
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, PartialEq)]
 | 
					 | 
				
			||||||
pub struct User {
 | 
					 | 
				
			||||||
    id: Option<i64>, // ID is omitted when creating new user
 | 
					 | 
				
			||||||
    pub username: String,
 | 
					 | 
				
			||||||
    pass_hash: String,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug)]
 | 
					 | 
				
			||||||
pub struct PixelManager {
 | 
					 | 
				
			||||||
        db_connection_sender: DBSender<DBConnectionMessage>,
 | 
					 | 
				
			||||||
        db_connection: Option<DBConnection<DBConnectionMessage>> // only original manager holds the connection, clones in delegate do not
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
impl PixelManager {
 | 
					 | 
				
			||||||
    pub fn build(db_file: &str) -> Option<Self> {
 | 
					 | 
				
			||||||
        let conn = Connection::open(db_file);
 | 
					 | 
				
			||||||
        if conn.is_err() {
 | 
					 | 
				
			||||||
            return None;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        let db_connection = DBConnection::init(conn.unwrap(), Self::db_delegate);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Some(Self{ db_connection_sender: db_connection.sender.clone(), db_connection: Some(db_connection) })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn db_delegate(msg: DBConnectionMessage, conn: &Connection) {
 | 
					 | 
				
			||||||
        _ = match msg { // beware, code folding recommended
 | 
					 | 
				
			||||||
            DBConnectionMessage::CreatePixel(pixel) => 
 | 
					 | 
				
			||||||
                conn.execute("INSERT INTO pixels (id, name, user_id) VALUES (?1, ?2, ?3)", params![pixel.id, pixel.name, pixel.user_id]),
 | 
					 | 
				
			||||||
            DBConnectionMessage::RegisterHit(hit) => 
 | 
					 | 
				
			||||||
                conn.execute("INSERT INTO hits (pixel_id, ip, user_agent, date) VALUES (?1, ?2, ?3, ?4)", params![hit.pixel_id, hit.ip, hit.user_agent, hit.date]),
 | 
					 | 
				
			||||||
            DBConnectionMessage::DeletePixel(pixel_id) => 
 | 
					 | 
				
			||||||
                conn.execute("DELETE FROM pixels WHERE id = ?1", [pixel_id]),
 | 
					 | 
				
			||||||
            DBConnectionMessage::AuthUser(user, response_sender) => {
 | 
					 | 
				
			||||||
                let result: Result<i64, rusqlite::Error>;
 | 
					 | 
				
			||||||
                if let User{ id: Some(id), username, pass_hash} = user {
 | 
					 | 
				
			||||||
                    result = conn.query_row("SELECT id FROM users WHERE id = ?1 AND username = ?2 AND pass_hash = ?3", params![id, username, pass_hash], 
 | 
					 | 
				
			||||||
                                                |row| row.get(0));
 | 
					 | 
				
			||||||
                    match result.optional() {
 | 
					 | 
				
			||||||
                        Ok(id) => {
 | 
					 | 
				
			||||||
                            if let Some(_) = id {
 | 
					 | 
				
			||||||
                                _ = response_sender.send(true);
 | 
					 | 
				
			||||||
                            } else {
 | 
					 | 
				
			||||||
                                _ = response_sender.send(false);
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            Ok(1) // 1 row affected
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        Err(e) => {Err(e)},
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                } else { // invalid user object
 | 
					 | 
				
			||||||
                    Ok(0) // 0 rows affected
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            DBConnectionMessage::GetUserFromName(username, response_sender) => {
 | 
					 | 
				
			||||||
                let result = conn.query_row("SELECT id, username, pass_hash FROM users WHERE username = ?1", [username], 
 | 
					 | 
				
			||||||
                |row| Ok(
 | 
					 | 
				
			||||||
                    User{
 | 
					 | 
				
			||||||
                        id: row.get(0)?,
 | 
					 | 
				
			||||||
                        username: row.get(1)?,
 | 
					 | 
				
			||||||
                        pass_hash: row.get(2)?
 | 
					 | 
				
			||||||
                    }));
 | 
					 | 
				
			||||||
                match result.optional() {
 | 
					 | 
				
			||||||
                    Ok(user) => {
 | 
					 | 
				
			||||||
                        if let Some(user) = user {
 | 
					 | 
				
			||||||
                            _ = response_sender.send(Some(user));
 | 
					 | 
				
			||||||
                            Ok(1) // 1 row affected
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        else {
 | 
					 | 
				
			||||||
                            _ = response_sender.send(None);
 | 
					 | 
				
			||||||
                            Ok(0) // no rows affected
 | 
					 | 
				
			||||||
                        }                        
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    Err(e) => {
 | 
					 | 
				
			||||||
                        _ = response_sender.send(None);
 | 
					 | 
				
			||||||
                        Err(e)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            DBConnectionMessage::GetUserFromId(id, response_sender) => {
 | 
					 | 
				
			||||||
                let result = conn.query_row("SELECT id, username, pass_hash FROM users WHERE id = ?1", [id], 
 | 
					 | 
				
			||||||
                |row| Ok(
 | 
					 | 
				
			||||||
                    User{
 | 
					 | 
				
			||||||
                        id: row.get(0)?,
 | 
					 | 
				
			||||||
                        username: row.get(1)?,
 | 
					 | 
				
			||||||
                        pass_hash: row.get(2)?
 | 
					 | 
				
			||||||
                    }));
 | 
					 | 
				
			||||||
                match result.optional() {
 | 
					 | 
				
			||||||
                    Ok(user) => {
 | 
					 | 
				
			||||||
                        if let Some(user) = user {
 | 
					 | 
				
			||||||
                            _ = response_sender.send(Some(user));
 | 
					 | 
				
			||||||
                            Ok(1) // 1 row affected
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        else {
 | 
					 | 
				
			||||||
                            _ = response_sender.send(None);
 | 
					 | 
				
			||||||
                            Ok(0) // no rows affected
 | 
					 | 
				
			||||||
                        }                        
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    Err(e) => {
 | 
					 | 
				
			||||||
                        _ = response_sender.send(None);
 | 
					 | 
				
			||||||
                        Err(e)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            DBConnectionMessage::CreateUser(user) => 
 | 
					 | 
				
			||||||
                conn.execute("INSERT INTO users (username, pass_hash) VALUES (?1, ?2)", params![user.username, user.pass_hash]),
 | 
					 | 
				
			||||||
            DBConnectionMessage::DeleteUser(id) => 
 | 
					 | 
				
			||||||
                conn.execute("DELETE from users WHERE id = ?1", [id]),
 | 
					 | 
				
			||||||
            DBConnectionMessage::GetHitsForPixel(pixel, response_sender, user) => 'gethitsforpixel: {
 | 
					 | 
				
			||||||
                let mut stmt = conn.prepare("SELECT id, pixel_id, ip, user_agent, date FROM hits WHERE pixel_id = ?1").unwrap();
 | 
					 | 
				
			||||||
                if let Some(user) = user {
 | 
					 | 
				
			||||||
                    if let Some(user_id) = user.id {
 | 
					 | 
				
			||||||
                        // check if pixel belongs to user
 | 
					 | 
				
			||||||
                        let pixel_id: Result<i64, _> = conn.query_row("SELECT id FROM pixels WHERE id = ?1 AND user_id = ?2", [pixel.id, user_id], |row| {
 | 
					 | 
				
			||||||
                            Ok(row.get(0)?)
 | 
					 | 
				
			||||||
                        });
 | 
					 | 
				
			||||||
                        if !pixel_id.is_ok() {
 | 
					 | 
				
			||||||
                            _ = response_sender.send(None);
 | 
					 | 
				
			||||||
                            break 'gethitsforpixel Ok(0);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                // if this fails something is very wrong with the underlying connection, so panicking the thread is perfectly acceptable
 | 
					 | 
				
			||||||
                let mapped_rows = stmt.query_map([pixel.id], |row| {
 | 
					 | 
				
			||||||
                    Ok(Hit {
 | 
					 | 
				
			||||||
                        id: Some(row.get(0)?),
 | 
					 | 
				
			||||||
                        pixel_id: row.get(1)?,
 | 
					 | 
				
			||||||
                        ip: row.get(2)?,
 | 
					 | 
				
			||||||
                        user_agent: row.get(3)?,
 | 
					 | 
				
			||||||
                        date: row.get(4)?,
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                }); // processing on the DB thread is a bad idea, but this really isn't very complex and I don't really care
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                match mapped_rows {
 | 
					 | 
				
			||||||
                    Ok(hits) => {
 | 
					 | 
				
			||||||
                        let hits: Vec<Hit> = hits.filter_map(|hit| hit.ok()).collect();
 | 
					 | 
				
			||||||
                        let rows_affected = hits.len();
 | 
					 | 
				
			||||||
                        _ = response_sender.send(Some(hits));
 | 
					 | 
				
			||||||
                        Ok(rows_affected)
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    Err(e) => {
 | 
					 | 
				
			||||||
                        _ = response_sender.send(None);
 | 
					 | 
				
			||||||
                        Err(e)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            DBConnectionMessage::GetPixelsForUser(user, response_sender) => {
 | 
					 | 
				
			||||||
                let mut stmt = conn.prepare("SELECT id,name,user_id FROM pixels WHERE user_id = ?1").unwrap(); // should't fail but if it does connection is bad and panicking is acceptable
 | 
					 | 
				
			||||||
                let mapped_rows = stmt.query_map([user.id], |row| {
 | 
					 | 
				
			||||||
                    Ok(TrackingPixel {
 | 
					 | 
				
			||||||
                        id: row.get(0)?,
 | 
					 | 
				
			||||||
                        name: row.get(1)?,
 | 
					 | 
				
			||||||
                        user_id: row.get(2)?
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                match mapped_rows {
 | 
					 | 
				
			||||||
                    Ok(pixels) => {
 | 
					 | 
				
			||||||
                        let pixels: Vec<TrackingPixel> = pixels.filter_map(|pixel| pixel.ok()).collect();
 | 
					 | 
				
			||||||
                        let rows_affected = pixels.len();
 | 
					 | 
				
			||||||
                        _ = response_sender.send(Some(pixels));
 | 
					 | 
				
			||||||
                        Ok(rows_affected)
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    Err(e) => {
 | 
					 | 
				
			||||||
                        _ = response_sender.send(None);
 | 
					 | 
				
			||||||
                        Err(e)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            DBConnectionMessage::GetPixel(id, user, response_sender) => {
 | 
					 | 
				
			||||||
                let mut stmt = conn.prepare("SELECT id, name, user_id FROM pixels WHERE id = ?1").unwrap();
 | 
					 | 
				
			||||||
                if let Some(user) = user {
 | 
					 | 
				
			||||||
                    if let Some(user_id) = user.id {
 | 
					 | 
				
			||||||
                        let sql_str = format!("SELECT id,name,user_id FROM pixels WHERE id = ?1 AND user_id = {}", user_id);
 | 
					 | 
				
			||||||
                        stmt = conn.prepare(&sql_str).unwrap();
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                let pixel = stmt.query_row([id], |row| {
 | 
					 | 
				
			||||||
                    Ok(TrackingPixel {
 | 
					 | 
				
			||||||
                        id: row.get(0)?,
 | 
					 | 
				
			||||||
                        name: row.get(1)?,
 | 
					 | 
				
			||||||
                        user_id: row.get(2)?
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                }).optional();
 | 
					 | 
				
			||||||
                match pixel {
 | 
					 | 
				
			||||||
                    Ok(pixel) => {
 | 
					 | 
				
			||||||
                        _ = response_sender.send(pixel);
 | 
					 | 
				
			||||||
                        Ok(1) // 1 row affected
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    Err(e) => {
 | 
					 | 
				
			||||||
                        _ = response_sender.send(None);
 | 
					 | 
				
			||||||
                        Err(e)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn create_pixel(&self, name: &str, user: &User) -> Result<TrackingPixel, Error> {
 | 
					 | 
				
			||||||
        let id = rand::random::<i64>().abs();
 | 
					 | 
				
			||||||
        // yes I don't check for duplicates. but whatever
 | 
					 | 
				
			||||||
        if user.id.is_none() {
 | 
					 | 
				
			||||||
            return Err(Error::UserNotAuthed());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        let pixel = TrackingPixel{id, name: name.to_string(), user_id: user.id.unwrap() };
 | 
					 | 
				
			||||||
        self.db_connection_sender.send(DBConnectionMessage::CreatePixel(pixel))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(TrackingPixel { id, name: name.to_string(), user_id: user.id.unwrap() })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn delete_pixel(&self, pixel: TrackingPixel, user: Option<&User>) -> Result<(), Error> {
 | 
					 | 
				
			||||||
        if let Some(user) = user {
 | 
					 | 
				
			||||||
            if let Ok(pixel) = self.get_pixel_from_id(pixel.id, Some(user)) {
 | 
					 | 
				
			||||||
                if pixel.is_none() { // pixel is not withing user's pixels
 | 
					 | 
				
			||||||
                    return Err(Error::PixelNotFound());
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Ok(self.db_connection_sender.send(DBConnectionMessage::DeletePixel(pixel.id))?)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    pub fn register_hit_with_encoded_pixel(&self, encoded_pixel: &str, ip: &str, user_agent: &str, date: DateTime<Utc>) -> Result<(), Error> {
 | 
					 | 
				
			||||||
        self.register_hit_with_pixel_id(TrackingPixel::url_safe_decode_id(encoded_pixel)?, ip, user_agent, date)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    pub fn register_hit_with_pixel(&self, pixel: &TrackingPixel, ip: &str, user_agent: &str, date: DateTime<Utc>) -> Result<(), Error> {
 | 
					 | 
				
			||||||
        self.register_hit_with_pixel_id(pixel.id, ip, user_agent, date)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    fn register_hit_with_pixel_id(&self, id: i64, ip: &str, user_agent: &str, date: DateTime<Utc>) -> Result<(), Error> {
 | 
					 | 
				
			||||||
        Ok(self.db_connection_sender.send(
 | 
					 | 
				
			||||||
            DBConnectionMessage::RegisterHit(
 | 
					 | 
				
			||||||
                Hit {
 | 
					 | 
				
			||||||
                    pixel_id: id,
 | 
					 | 
				
			||||||
                    ip: ip.to_string(),
 | 
					 | 
				
			||||||
                    user_agent: user_agent.to_string(),
 | 
					 | 
				
			||||||
                    date, id: None 
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
            )?
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn pass_hash(password: &str) -> String {
 | 
					 | 
				
			||||||
        hex_digest(Algorithm::SHA256, password.as_bytes())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn auth_user(&self, user: &User, password: &str) -> Result<bool, Error> {
 | 
					 | 
				
			||||||
        let (sender, receiver) = oneshot::channel();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let auth_user = User {
 | 
					 | 
				
			||||||
            pass_hash: Self::pass_hash(password),
 | 
					 | 
				
			||||||
            ..user.clone()
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.db_connection_sender.send(DBConnectionMessage::AuthUser(auth_user, sender))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let auth = receiver.recv()?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(auth)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn get_user_from_username(&self, username: &str) -> Result<Option<User>, Error> {
 | 
					 | 
				
			||||||
        let (sender, receiver) = oneshot::channel();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.db_connection_sender.send(
 | 
					 | 
				
			||||||
            DBConnectionMessage::GetUserFromName(username.to_string(), sender))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(receiver.recv()?)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    fn get_user_from_id(&self, id: i64) -> Result<Option<User>, Error> {
 | 
					 | 
				
			||||||
        let (sender, receiver) = oneshot::channel();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.db_connection_sender.send(DBConnectionMessage::GetUserFromId(id, sender))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(receiver.recv()?)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn create_user(&self, username: &str, password: &str) -> Result<User, Error>{
 | 
					 | 
				
			||||||
        let user = self.get_user_from_username(username)?;
 | 
					 | 
				
			||||||
        if user.is_some() {
 | 
					 | 
				
			||||||
            return Err(Error::NewUserAlreadyExists());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        let new_user = User {
 | 
					 | 
				
			||||||
            username: username.to_string(),
 | 
					 | 
				
			||||||
            pass_hash: Self::pass_hash(password),
 | 
					 | 
				
			||||||
            id: None
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        self.db_connection_sender.send(
 | 
					 | 
				
			||||||
            DBConnectionMessage::CreateUser(new_user))?;
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
        let user = self.get_user_from_username(username)?;
 | 
					 | 
				
			||||||
        // get user struct that has the id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if let Some(user) = user {
 | 
					 | 
				
			||||||
            Ok(user)
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            Err(Error::DBError())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn delete_user(&self, user: User) -> Result<(), Error> {
 | 
					 | 
				
			||||||
        let mut id = user.id;
 | 
					 | 
				
			||||||
        if id.is_none() {
 | 
					 | 
				
			||||||
            let db_user = self.get_user_from_username(&user.username)?;
 | 
					 | 
				
			||||||
             
 | 
					 | 
				
			||||||
            if let Some(db_user) = db_user {
 | 
					 | 
				
			||||||
                id = db_user.id; // must always be Some because get_user_from_username will always contain an id
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                return Err(Error::InvalidUserData())
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.db_connection_sender.send(DBConnectionMessage::DeleteUser(id.unwrap()))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn get_user(&self, username: &str, password: &str) -> Result<User, Error> {
 | 
					 | 
				
			||||||
        let user = self.get_user_from_username(username)?;
 | 
					 | 
				
			||||||
        if let Some(user) = user {
 | 
					 | 
				
			||||||
            let auth = self.auth_user(&user, password)?;
 | 
					 | 
				
			||||||
            if auth {
 | 
					 | 
				
			||||||
                Ok(user)
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                Err(Error::InvalidCredentials()) // wrong password
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            Err(Error::InvalidCredentials()) // username does not exist
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }  
 | 
					 | 
				
			||||||
    // optionally include a user
 | 
					 | 
				
			||||||
    pub fn get_hits_for_pixel(&self, pixel: &TrackingPixel, user: Option<&User>) -> Result<Vec<Hit>, Error> {
 | 
					 | 
				
			||||||
        let (sender, receiver) = oneshot::channel();
 | 
					 | 
				
			||||||
        self.db_connection_sender.send(DBConnectionMessage::GetHitsForPixel(pixel.clone(), sender, user.cloned()))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let hits = receiver.recv()? ;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        Ok(hits.unwrap_or_default())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn get_pixels_for_user(&self, user: &User) -> Result<Vec<TrackingPixel>, Error> {
 | 
					 | 
				
			||||||
        let (sender, receiver) = oneshot::channel();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.db_connection_sender.send(DBConnectionMessage::GetPixelsForUser(user.clone(), sender))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let pixels = receiver.recv()?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(pixels.unwrap_or_default())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn get_pixel_from_encoded(&self, encoded_id: &str, user: Option<&User>) -> Result<Option<TrackingPixel>, Error> {
 | 
					 | 
				
			||||||
        let id = TrackingPixel::url_safe_decode_id(encoded_id)?;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        self.get_pixel_from_id(id, user)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    fn get_pixel_from_id(&self, pixel_id: i64, user: Option<&User>) -> Result<Option<TrackingPixel>, Error> {
 | 
					 | 
				
			||||||
        let (sender, receiver) = oneshot::channel();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.db_connection_sender.send(DBConnectionMessage::GetPixel(pixel_id, user.cloned(), sender))?;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        Ok(receiver.recv()?)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// stores a version of the pixel manager struct with only a sender, which may be cloned and used in multiple threads
 | 
					 | 
				
			||||||
#[derive(Debug)]
 | 
					 | 
				
			||||||
pub struct PixelManagerDelegate {
 | 
					 | 
				
			||||||
    manager: PixelManager
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl PixelManagerDelegate {
 | 
					 | 
				
			||||||
    pub fn new(pixel_manager: &PixelManager) -> Self {
 | 
					 | 
				
			||||||
        let internal_pixel_manager = PixelManager {
 | 
					 | 
				
			||||||
            db_connection_sender: pixel_manager.db_connection_sender.clone(),
 | 
					 | 
				
			||||||
            db_connection: None
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        Self {manager: internal_pixel_manager}
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn get_manager(&self) -> &PixelManager {
 | 
					 | 
				
			||||||
        &self.manager
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Clone for PixelManagerDelegate {
 | 
					 | 
				
			||||||
    fn clone(&self) -> Self {
 | 
					 | 
				
			||||||
        let internal_pixel_manager = PixelManager {
 | 
					 | 
				
			||||||
            db_connection_sender: self.manager.db_connection_sender.clone(),
 | 
					 | 
				
			||||||
            db_connection: None
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        Self {manager: internal_pixel_manager}
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Loading…
	
		Reference in New Issue