use crate::models::AppState; use crate::models::alert::Alert; use crate::models::hit::Hit; use crate::models::tracker::Tracker; use crate::schema::{hits, trackers}; use chrono::Utc; use diesel::{ExpressionMethods, QueryDsl, QueryResult, RunQueryDsl}; use rocket::fs::NamedFile; use rocket::http::Status; use rocket::request::{FromRequest, Outcome}; use rocket::{Request, State}; use std::path::Path; use uuid::Uuid; pub struct ReqMeta { pub ip: String, pub agent: Option, pub language: Option, } #[rocket::async_trait] impl<'r> FromRequest<'r> for ReqMeta { type Error = (); async fn from_request(req: &'r Request<'_>) -> Outcome { let headers = req.headers(); let agent = headers.get_one("User-Agent").map(|s| s.to_string()); let language = headers.get_one("Accept-Language").map(|s| s.to_string()); let ip = req .client_ip() .map(|ip| ip.to_string()) .or_else(|| { headers .get_one("X-Forwarded-For") .map(|s| s.split(',').next().unwrap_or("").trim().to_string()) }) .unwrap_or_else(|| "unknown".to_string()); Outcome::Success(ReqMeta { ip, agent, language, }) } } #[get("/")] pub async fn get(id: &str, meta: ReqMeta, state: &State) -> Result { let id = match Uuid::parse_str(id).ok() { Some(id) => id, None => return Err(Status::BadRequest), }; let tracker: Option; let result: Option>; let now = Utc::now().naive_utc(); { let mut db = state.db.lock().unwrap(); let tresult = trackers::dsl::trackers .filter(trackers::id.eq(id)) .first::(&mut *db) .ok(); if tresult.is_none() { return Err(Status::NotFound); } tracker = Some(tresult.unwrap()); result = Some( diesel::insert_into(hits::table) .values(&Hit { id: Uuid::new_v4(), tracker_id: id, ip: meta.ip.clone(), agent: meta.agent.clone(), language: meta.language.clone(), created_at: now, }) .execute(&mut *db), ); if result.as_ref().unwrap().is_err() { error!("Failed to insert hit: {:?}", result); } } let url = state.gotify.url.clone(); let token = state.gotify.token.clone(); let tracker = tracker.unwrap(); let tracker_name = tracker.name.clone().unwrap_or("unknown".to_string()); let tracker_id = tracker.id.clone(); if state.gotify.enabled { let alert = Alert { title: format!("Tracker '{tracker_name}' ({tracker_id}) has been hit"), message: format!( "IP: {}\nAgent: {}\nLanguage: {}\nCreated at: {}\nError: {}", meta.ip, meta.agent.unwrap_or("unknown".to_string()), meta.language.unwrap_or("unknown".to_string()), now.to_string(), result .unwrap() .err() .map(|e| e.to_string()) .unwrap_or_else(|| "none".to_string()), ), priority: 5, }; let client = reqwest::Client::new(); let result = client .post(format!("{url}/message?token={token}")) .json(&alert) .send() .await; if result.is_err() { error!("Failed to send a alert to {url}: {result:?}"); } } NamedFile::open(Path::new(state.static_dir.as_str()).join("image.png")) .await .map_err(|_| Status::InternalServerError) }