Files
setra/src/api/image.rs
T

129 lines
3.8 KiB
Rust

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<String>,
pub language: Option<String>,
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for ReqMeta {
type Error = ();
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
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("/<id>")]
pub async fn get(id: &str, meta: ReqMeta, state: &State<AppState>) -> Result<NamedFile, Status> {
let id = match Uuid::parse_str(id).ok() {
Some(id) => id,
None => return Err(Status::BadRequest),
};
let tracker: Option<Tracker>;
let result: Option<QueryResult<usize>>;
let now = Utc::now().naive_utc();
{
let mut db = state.db.lock().unwrap();
let tresult = trackers::dsl::trackers
.filter(trackers::id.eq(id))
.first::<Tracker>(&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)
}