diff --git a/Cargo.lock b/Cargo.lock index 7fea9cc..8fe814f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -605,6 +605,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -684,6 +685,7 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -2228,6 +2230,7 @@ dependencies = [ "axum-test", "chrono", "dotenv", + "futures", "maud", "password-auth", "rust-i18n", diff --git a/Cargo.toml b/Cargo.toml index 906f47d..244cbdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ async-trait = "0.1" password-auth = "1.0" tower-sessions-sqlx-store-chrono = { version = "0.14", features = ["sqlite"] } tracing-subscriber = "0.3" +futures = "0.3" [dev-dependencies] diff --git a/src/admin/mod.rs b/src/admin/mod.rs index 4cade01..afa776c 100644 --- a/src/admin/mod.rs +++ b/src/admin/mod.rs @@ -1,13 +1,80 @@ -use crate::{auth::Backend, page, AppState}; -use axum::{routing::get, Router}; +use crate::{auth::Backend, models::rating::Rating, page, AppState}; +use axum::{extract::State, routing::get, Router}; use axum_login::login_required; use maud::{html, Markup}; +use route::Route; +use sqlx::SqlitePool; +use std::sync::Arc; use tower_sessions::Session; pub(crate) mod route; pub(crate) mod station; pub(crate) mod team; +async fn highscore(State(db): State>, session: Session) -> Markup { + let routes = Route::all(&db).await; + + let content = html! { + h1 { + a href="/admin" { "↩️" } + "Highscore" + } + @for (idx, route) in routes.into_iter().enumerate() { + details open[idx==0] { + summary { (route.name) } + + table { + thead { + tr { + td { "Team" } + @for station in route.stations(&db).await { + td { + a href=(format!("/admin/station/{}", station.id)){ + (station.name) + } + } + } + td { "Gesamtpunkte" } + } + } + tbody { + @for team in route.teams_ordered_by_points(&db).await { + @let mut total_points = 0; + tr { + td { + a href=(format!("/admin/team/{}", team.id)) { + (team.name) + } + } + @for station in route.stations(&db).await { + td { + @if let Some(rating) = Rating::find_by_team_and_station(&db, &team, &station).await { + @if let (Some(notes), Some(points)) = (rating.notes, rating.points) { + ({total_points += points;""}) + em data-tooltip=(notes) { (points) } + }@else if let Some(points) = rating.points { + ({total_points += points;""}) + (points) + }@else { + em data-tooltip="Station hat Team noch nicht bewertet" { + "?" + } + } + } + } + } + td { (total_points) } + } + } + } + } + } + } + }; + + page(content, session, false).await +} + async fn index(session: Session) -> Markup { let content = html! { h1 { (t!("app_name")) } @@ -31,6 +98,11 @@ async fn index(session: Session) -> Markup { (t!("teams")) } } + li { + a role="button" href="/admin/highscore" { + "Highscore" + } + } } } }; @@ -40,6 +112,7 @@ async fn index(session: Session) -> Markup { pub(super) fn routes() -> Router { Router::new() .route("/", get(index)) + .route("/highscore", get(highscore)) .nest("/station", station::routes()) .nest("/route", route::routes()) .nest("/team", team::routes()) diff --git a/src/admin/route/mod.rs b/src/admin/route/mod.rs index 0697555..3a6b1c5 100644 --- a/src/admin/route/mod.rs +++ b/src/admin/route/mod.rs @@ -1,8 +1,9 @@ use crate::{ - AppState, admin::{station::Station, team::Team}, + AppState, }; use axum::Router; +use futures::future::join_all; use serde::{Deserialize, Serialize}; use sqlx::{FromRow, Row, SqlitePool}; @@ -167,6 +168,22 @@ DROP TABLE temp_pos;", Team::all_with_route(db, self).await } + pub(crate) async fn teams_ordered_by_points(&self, db: &SqlitePool) -> Vec { + let teams = Team::all_with_route(db, self).await; + // First, collect all the points + let points_futures: Vec<_> = teams.iter().map(|team| team.get_curr_points(db)).collect(); + let points = join_all(points_futures).await; + + // Create pairs of (team, points) + let mut team_with_points: Vec<_> = teams.into_iter().zip(points).collect(); + + // Sort by points (descending) + team_with_points.sort_by(|a, b| b.1.cmp(&a.1)); + + // Extract just the teams in sorted order + team_with_points.into_iter().map(|(team, _)| team).collect() + } + pub(crate) async fn get_next_first_station(&self, db: &SqlitePool) -> Option { let Ok(row) = sqlx::query(&format!( " diff --git a/src/admin/station/mod.rs b/src/admin/station/mod.rs index a68274c..8f3cf86 100644 --- a/src/admin/station/mod.rs +++ b/src/admin/station/mod.rs @@ -1,8 +1,8 @@ use super::team::Team; use crate::{ - AppState, admin::route::Route, models::rating::{Rating, TeamsAtStationLocation}, + AppState, }; use axum::Router; use chrono::{DateTime, Local, NaiveDateTime, Utc}; @@ -353,7 +353,8 @@ impl Station { "SELECT DISTINCT t.id, t.name, t.notes, t.amount_people, t.first_station_id, t.route_id FROM team t JOIN route_station rs ON t.route_id = rs.route_id -WHERE rs.station_id = ?;", +WHERE rs.station_id = ? +ORDER BY t.name;", ) .bind(self.id) .fetch_all(db) diff --git a/src/admin/station/web.rs b/src/admin/station/web.rs index 95f649d..8d0ddc6 100644 --- a/src/admin/station/web.rs +++ b/src/admin/station/web.rs @@ -1,18 +1,17 @@ use crate::{ - AppState, admin::station::Station, er, err, models::rating::{Rating, TeamsAtStationLocation}, partials::page, - suc, succ, + suc, succ, AppState, }; use axum::{ - Form, Router, extract::State, response::{IntoResponse, Redirect}, routing::{get, post}, + Form, Router, }; -use maud::{Markup, html}; +use maud::{html, Markup}; use serde::Deserialize; use sqlx::SqlitePool; use std::sync::Arc; diff --git a/src/admin/team/mod.rs b/src/admin/team/mod.rs index f1efd27..c6d6b12 100644 --- a/src/admin/team/mod.rs +++ b/src/admin/team/mod.rs @@ -1,6 +1,6 @@ use crate::{ - AppState, admin::{route::Route, station::Station}, + AppState, }; use axum::Router; use chrono::{DateTime, Local, NaiveDateTime, Utc}; @@ -77,7 +77,7 @@ enum CreateError { impl Team { pub(crate) async fn all(db: &SqlitePool) -> Vec { sqlx::query_as::<_, Self>( - "SELECT id, name, notes, amount_people, first_station_id, route_id FROM team;", + "SELECT id, name, notes, amount_people, first_station_id, route_id FROM team ORDER BY name;", ) .fetch_all(db) .await @@ -216,6 +216,17 @@ impl Team { .await .expect("db constraints") } + + pub async fn get_curr_points(&self, db: &SqlitePool) -> i64 { + sqlx::query!( + "SELECT IFNULL(sum(points), 0) as points FROM rating WHERE team_id = ?", + self.id + ) + .fetch_one(db) + .await + .unwrap() + .points + } } pub(super) fn routes() -> Router { diff --git a/src/auth.rs b/src/auth.rs index 7ff9729..5f68a26 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -36,7 +36,7 @@ impl AuthUser for User { } fn session_auth_hash(&self) -> &[u8] { - &self.pw.as_bytes() + self.pw.as_bytes() } }