implement first version of ranking board; Fixes #20
This commit is contained in:
parent
b7f5ce27db
commit
1cbd77ec95
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -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",
|
||||
|
@ -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]
|
||||
|
@ -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<Arc<SqlitePool>>, 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<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(index))
|
||||
.route("/highscore", get(highscore))
|
||||
.nest("/station", station::routes())
|
||||
.nest("/route", route::routes())
|
||||
.nest("/team", team::routes())
|
||||
|
@ -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<Team> {
|
||||
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<Station> {
|
||||
let Ok(row) = sqlx::query(&format!(
|
||||
"
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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<Self> {
|
||||
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<AppState> {
|
||||
|
@ -36,7 +36,7 @@ impl AuthUser for User {
|
||||
}
|
||||
|
||||
fn session_auth_hash(&self) -> &[u8] {
|
||||
&self.pw.as_bytes()
|
||||
self.pw.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user