repos
/ analytics-rust master

analytics-rust

mirror archived upstream

Single-binary self-hosted website analytics on Rust axum: collector API, dashboards, world map, and PDF reports.

analyticsaxumdockerrustself-hostedsqliteviteweb-analytics

2.6 KB · 102 lines · Rust Raw History
  1use axum::{
  2    extract::State,
  3    response::{IntoResponse, Redirect, Response},
  4    routing::get,
  5    Router,
  6};
  7use tower_cookies::Cookies;
  8
  9use crate::render::render;
 10use crate::routes::auth::is_authenticated;
 11use crate::AppState;
 12
 13pub fn router() -> Router<AppState> {
 14    Router::new()
 15        .route("/", get(home))
 16        .route("/changelog", get(changelog))
 17        .route("/documentation", get(documentation))
 18}
 19
 20fn render_page(
 21    state: &AppState,
 22    template: &str,
 23    title: &str,
 24    description: &str,
 25    authed: bool,
 26    path: &str,
 27    extra: minijinja::Value,
 28) -> Response {
 29    render(
 30        state,
 31        template,
 32        path,
 33        authed,
 34        minijinja::context! {
 35            page => minijinja::context! { title => title, description => description },
 36            ..extra
 37        },
 38    )
 39}
 40
 41pub async fn home(State(state): State<AppState>, cookies: Cookies) -> Response {
 42    let authed = is_authenticated(&cookies, &state);
 43    if authed {
 44        return Redirect::to("/properties").into_response();
 45    }
 46    let totals: (i64, i64, Option<i64>) = sqlx::query_as(
 47        "SELECT \
 48           (SELECT COUNT(*) FROM properties), \
 49           (SELECT COUNT(*) FROM events), \
 50           (SELECT MIN(created_at) FROM events)",
 51    )
 52    .fetch_one(&state.pool)
 53    .await
 54    .unwrap_or((0, 0, None));
 55
 56    let first = totals.2.and_then(|ms| {
 57        chrono::DateTime::<chrono::Utc>::from_timestamp_millis(ms)
 58            .map(|d| d.format("%b %-d, %Y").to_string())
 59    });
 60
 61    render_page(
 62        &state,
 63        "pages/home.html",
 64        "Self-hosted analytics",
 65        "Self-hosted website analytics. Page views, clicks, scrolls, sessions, and custom events.",
 66        authed,
 67        "/",
 68        minijinja::context! {
 69            total_properties => totals.0,
 70            total_events => totals.1,
 71            total_users => 1,
 72            first_event_created_at => first,
 73        },
 74    )
 75}
 76
 77pub async fn changelog(State(state): State<AppState>, cookies: Cookies) -> Response {
 78    let authed = is_authenticated(&cookies, &state);
 79    render_page(
 80        &state,
 81        "pages/changelog.html",
 82        "Changelog",
 83        "What's new in Analytics.",
 84        authed,
 85        "/changelog",
 86        minijinja::context! {},
 87    )
 88}
 89
 90pub async fn documentation(State(state): State<AppState>, cookies: Cookies) -> Response {
 91    let authed = is_authenticated(&cookies, &state);
 92    render_page(
 93        &state,
 94        "pages/documentation.html",
 95        "Documentation",
 96        "How to embed and operate Analytics.",
 97        authed,
 98        "/documentation",
 99        minijinja::context! {},
100    )
101}