Compare commits

...

5 Commits

Author SHA1 Message Date
97dbd4fcae Merge pull request 'use-sqliteconnection' (#86) from use-sqliteconnection into main
Some checks failed
CI/CD Pipeline / test (push) Has been cancelled
CI/CD Pipeline / deploy (push) Has been cancelled
Reviewed-on: star/stationslauf#86
2025-05-15 19:43:49 +02:00
a46cf6ed97 pedantic clippy
Some checks failed
CI/CD Pipeline / deploy (push) Has been cancelled
CI/CD Pipeline / test (push) Has been cancelled
2025-05-15 19:40:52 +02:00
2a0098b0cb pedantic clippy 2025-05-15 19:40:16 +02:00
69aed3be27 cargo clippy
All checks were successful
CI/CD Pipeline / test (push) Successful in 11m30s
CI/CD Pipeline / deploy (push) Has been skipped
2025-05-15 19:27:39 +02:00
79d22a0ad1 use sqlite connection instead of db pool, removes the need for 2 function (pool + tx)
Some checks failed
CI/CD Pipeline / deploy (push) Has been cancelled
CI/CD Pipeline / test (push) Has been cancelled
2025-05-15 19:26:05 +02:00
13 changed files with 449 additions and 350 deletions

View File

@@ -16,7 +16,7 @@ use rand::{
rng, rng,
}; };
use route::Route; use route::Route;
use sqlx::SqlitePool; use sqlx::{SqliteConnection, SqlitePool};
use std::sync::Arc; use std::sync::Arc;
use team::Team; use team::Team;
use tower_sessions::Session; use tower_sessions::Session;
@@ -35,7 +35,9 @@ fn generate_random_alphanumeric(length: usize) -> String {
} }
async fn highscore(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup { async fn highscore(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
let routes = Route::all(&db).await; let db = &mut *db.acquire().await.unwrap();
let routes = Route::all(db).await;
let content = html! { let content = html! {
h1 { h1 {
@@ -51,7 +53,7 @@ async fn highscore(State(db): State<Arc<SqlitePool>>, session: Session) -> Marku
thead { thead {
tr { tr {
td { (t!("team")) } td { (t!("team")) }
@for station in route.stations(&db).await { @for station in route.stations(db).await {
td { td {
(station) (station)
} }
@@ -65,7 +67,7 @@ async fn highscore(State(db): State<Arc<SqlitePool>>, session: Session) -> Marku
@let mut rank = 0; @let mut rank = 0;
@let mut amount_teams_iterated = 0; @let mut amount_teams_iterated = 0;
@let mut prev_points = i64::MAX; @let mut prev_points = i64::MAX;
@for team in route.teams_ordered_by_points(&db).await { @for team in route.teams_ordered_by_points(db).await {
@let mut total_points = 0; @let mut total_points = 0;
({ amount_teams_iterated += 1;"" }) ({ amount_teams_iterated += 1;"" })
tr { tr {
@@ -74,9 +76,9 @@ async fn highscore(State(db): State<Arc<SqlitePool>>, session: Session) -> Marku
(team.name) (team.name)
} }
} }
@for station in route.stations(&db).await { @for station in route.stations(db).await {
td { td {
@if let Some(rating) = Rating::find_by_team_and_station(&db, &team, &station).await { @if let Some(rating) = Rating::find_by_team_and_station(db, &team, &station).await {
@if let (Some(notes), Some(points)) = (rating.notes, rating.points) { @if let (Some(notes), Some(points)) = (rating.notes, rating.points) {
({total_points += points;""}) ({total_points += points;""})
em data-placement="bottom" data-tooltip=(notes) { (points) } em data-placement="bottom" data-tooltip=(notes) { (points) }
@@ -125,7 +127,7 @@ pub enum RunStatus {
} }
impl RunStatus { impl RunStatus {
pub async fn curr(db: &SqlitePool) -> Self { pub async fn curr(db: &mut SqliteConnection) -> Self {
let stations = Station::all(db).await; let stations = Station::all(db).await;
if stations.is_empty() { if stations.is_empty() {
return RunStatus::NoStationsYet; return RunStatus::NoStationsYet;
@@ -143,10 +145,12 @@ async fn index(
session: Session, session: Session,
auth_session: AuthSession, auth_session: AuthSession,
) -> Markup { ) -> Markup {
let db = &mut *db.acquire().await.unwrap();
let user = auth_session let user = auth_session
.user .user
.expect("Can only be called by loggedin people"); .expect("Can only be called by loggedin people");
let status = RunStatus::curr(&db).await; let status = RunStatus::curr(db).await;
let content = html! { let content = html! {
nav { nav {
ul { ul {
@@ -201,7 +205,7 @@ async fn index(
} }
}, },
RunStatus::Active => { RunStatus::Active => {
@let stations = Station::all(&db).await; @let stations = Station::all(db).await;
a href="/admin/end-run" onclick=(format!("return confirm('{}');", t!("confirm_end_run"))) { a href="/admin/end-run" onclick=(format!("return confirm('{}');", t!("confirm_end_run"))) {
button style="background-color: red;" { button style="background-color: red;" {
(t!("end_run")) (t!("end_run"))
@@ -230,7 +234,7 @@ async fn index(
} }
td { td {
ol { ol {
@for team in Team::all_with_first_station(&db, &station).await { @for team in Team::all_with_first_station(db, &station).await {
li { (team) } li { (team) }
} }
} }
@@ -241,7 +245,7 @@ async fn index(
} }
}, },
RunStatus::HasEnded => { RunStatus::HasEnded => {
@let stations = Station::all(&db).await; @let stations = Station::all(db).await;
a href="/admin/restart-run" onclick=(format!("return confirm('{}');", t!("confirm_restart_run"))) { a href="/admin/restart-run" onclick=(format!("return confirm('{}');", t!("confirm_restart_run"))) {
button style="background-color: red;" { button style="background-color: red;" {
(t!("restart_run")) (t!("restart_run"))
@@ -263,7 +267,7 @@ async fn index(
td { (station) } td { (station) }
td { td {
ol { ol {
@for team in Team::all_with_last_station(&db, &station).await { @for team in Team::all_with_last_station(db, &station).await {
li { (team) } li { (team) }
} }
} }
@@ -279,12 +283,16 @@ async fn index(
} }
async fn end_run(State(db): State<Arc<SqlitePool>>, session: Session) -> impl IntoResponse { async fn end_run(State(db): State<Arc<SqlitePool>>, session: Session) -> impl IntoResponse {
Team::end_run(&db).await; let db = &mut *db.acquire().await.unwrap();
Team::end_run(db).await;
suc!(session, t!("run_ended")); suc!(session, t!("run_ended"));
Redirect::to("/admin") Redirect::to("/admin")
} }
async fn restart_run(State(db): State<Arc<SqlitePool>>, session: Session) -> impl IntoResponse { async fn restart_run(State(db): State<Arc<SqlitePool>>, session: Session) -> impl IntoResponse {
Team::restart_run(&db).await; let db = &mut *db.acquire().await.unwrap();
Team::restart_run(db).await;
suc!(session, t!("run_restarted")); suc!(session, t!("run_restarted"));
Redirect::to("/admin") Redirect::to("/admin")
} }

View File

@@ -3,10 +3,8 @@ use crate::{
AppState, AppState,
}; };
use axum::Router; use axum::Router;
use futures::future::join_all;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Row, Sqlite, SqlitePool, Transaction}; use sqlx::{FromRow, Row, SqliteConnection};
use std::ops::DerefMut;
mod web; mod web;
@@ -17,29 +15,29 @@ pub(crate) struct Route {
} }
impl Route { impl Route {
pub(crate) async fn all(db: &SqlitePool) -> Vec<Self> { pub(crate) async fn all(db: &mut SqliteConnection) -> Vec<Self> {
sqlx::query_as::<_, Self>("SELECT id, name FROM route;") sqlx::query_as::<_, Self>("SELECT id, name FROM route;")
.fetch_all(db) .fetch_all(db)
.await .await
.unwrap() .unwrap()
} }
pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> { pub async fn find_by_id(db: &mut SqliteConnection, id: i64) -> Option<Self> {
sqlx::query_as!(Self, "SELECT id, name FROM route WHERE id = ?", id) sqlx::query_as!(Self, "SELECT id, name FROM route WHERE id = ?", id)
.fetch_one(db) .fetch_one(db)
.await .await
.ok() .ok()
} }
pub(crate) async fn create(db: &SqlitePool, name: &str) -> Result<Self, String> { pub(crate) async fn create(db: &mut SqliteConnection, name: &str) -> Result<Self, String> {
let route = sqlx::query!("INSERT INTO route(name) VALUES (?) RETURNING id", name) let route = sqlx::query!("INSERT INTO route(name) VALUES (?) RETURNING id", name)
.fetch_one(db) .fetch_one(&mut *db)
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
Ok(Self::find_by_id(db, route.id).await.expect("just created")) Ok(Self::find_by_id(db, route.id).await.expect("just created"))
} }
async fn update_name(&self, db: &SqlitePool, name: &str) { async fn update_name(&self, db: &mut SqliteConnection, name: &str) {
sqlx::query!("UPDATE route SET name = ? WHERE id = ?", name, self.id) sqlx::query!("UPDATE route SET name = ? WHERE id = ?", name, self.id)
.execute(db) .execute(db)
.await .await
@@ -48,7 +46,7 @@ impl Route {
pub(crate) async fn add_station( pub(crate) async fn add_station(
&self, &self,
db: &SqlitePool, db: &mut SqliteConnection,
station: &Station, station: &Station,
) -> Result<(), String> { ) -> Result<(), String> {
sqlx::query!( sqlx::query!(
@@ -71,7 +69,7 @@ impl Route {
Ok(()) Ok(())
} }
async fn delete_station(&self, db: &SqlitePool, station: &Station) -> bool { async fn delete_station(&self, db: &mut SqliteConnection, station: &Station) -> bool {
let result = sqlx::query!( let result = sqlx::query!(
"DELETE FROM route_station WHERE route_id = ? AND station_id = ?", "DELETE FROM route_station WHERE route_id = ? AND station_id = ?",
self.id, self.id,
@@ -84,13 +82,13 @@ impl Route {
result.rows_affected() > 0 result.rows_affected() > 0
} }
async fn move_station_higher(&self, db: &SqlitePool, station: &Station) -> bool { async fn move_station_higher(&self, db: &mut SqliteConnection, station: &Station) -> bool {
let result = sqlx::query!( let result = sqlx::query!(
"UPDATE route_station SET pos = pos-3 WHERE route_id = ? AND station_id = ?", "UPDATE route_station SET pos = pos-3 WHERE route_id = ? AND station_id = ?",
self.id, self.id,
station.id station.id
) )
.execute(db) .execute(&mut *db)
.await .await
.unwrap(); .unwrap();
@@ -126,7 +124,7 @@ DROP TABLE temp_pos;",
true true
} }
async fn delete(&self, db: &SqlitePool) -> Result<(), String> { async fn delete(&self, db: &mut SqliteConnection) -> Result<(), String> {
sqlx::query!("DELETE FROM route WHERE id = ?", self.id) sqlx::query!("DELETE FROM route WHERE id = ?", self.id)
.execute(db) .execute(db)
.await .await
@@ -134,7 +132,7 @@ DROP TABLE temp_pos;",
Ok(()) Ok(())
} }
pub(crate) async fn stations(&self, db: &SqlitePool) -> Vec<Station> { pub(crate) async fn stations(&self, db: &mut SqliteConnection) -> Vec<Station> {
// TODO: switch to macro // TODO: switch to macro
sqlx::query_as::<_, Station>( sqlx::query_as::<_, Station>(
" "
@@ -151,7 +149,7 @@ DROP TABLE temp_pos;",
.unwrap() .unwrap()
} }
pub(crate) async fn crewless_stations(&self, db: &SqlitePool) -> Vec<Station> { pub(crate) async fn crewless_stations(&self, db: &mut SqliteConnection) -> Vec<Station> {
self.stations(db) self.stations(db)
.await .await
.into_iter() .into_iter()
@@ -159,7 +157,7 @@ DROP TABLE temp_pos;",
.collect() .collect()
} }
pub(crate) async fn crewful_stations(&self, db: &SqlitePool) -> Vec<Station> { pub(crate) async fn crewful_stations(&self, db: &mut SqliteConnection) -> Vec<Station> {
self.stations(db) self.stations(db)
.await .await
.into_iter() .into_iter()
@@ -167,7 +165,7 @@ DROP TABLE temp_pos;",
.collect() .collect()
} }
async fn stations_not_in_route(&self, db: &SqlitePool) -> Vec<Station> { async fn stations_not_in_route(&self, db: &mut SqliteConnection) -> Vec<Station> {
// TODO: switch to macro // TODO: switch to macro
sqlx::query_as::<_, Station>( sqlx::query_as::<_, Station>(
" "
@@ -187,15 +185,18 @@ DROP TABLE temp_pos;",
.unwrap() .unwrap()
} }
pub(crate) async fn teams(&self, db: &SqlitePool) -> Vec<Team> { pub(crate) async fn teams(&self, db: &mut SqliteConnection) -> Vec<Team> {
Team::all_with_route(db, self).await Team::all_with_route(db, self).await
} }
pub(crate) async fn teams_ordered_by_points(&self, db: &SqlitePool) -> Vec<Team> { pub(crate) async fn teams_ordered_by_points(&self, db: &mut SqliteConnection) -> Vec<Team> {
let teams = Team::all_with_route(db, self).await; let teams = Team::all_with_route(db, self).await;
// First, collect all the points // First, collect all the points
let points_futures: Vec<_> = teams.iter().map(|team| team.get_curr_points(db)).collect(); let mut points = Vec::new();
let points = join_all(points_futures).await; for team in &teams {
points.push(team.get_curr_points(&mut *db).await);
}
// Create pairs of (team, points) // Create pairs of (team, points)
let mut team_with_points: Vec<_> = teams.into_iter().zip(points).collect(); let mut team_with_points: Vec<_> = teams.into_iter().zip(points).collect();
@@ -207,9 +208,9 @@ DROP TABLE temp_pos;",
team_with_points.into_iter().map(|(team, _)| team).collect() team_with_points.into_iter().map(|(team, _)| team).collect()
} }
pub(crate) async fn get_next_first_station_tx( pub(crate) async fn get_next_first_station(
&self, &self,
db: &mut Transaction<'_, Sqlite>, db: &mut SqliteConnection,
) -> Option<Station> { ) -> Option<Station> {
let Ok(row) = sqlx::query(&format!( let Ok(row) = sqlx::query(&format!(
" "
@@ -226,7 +227,7 @@ DROP TABLE temp_pos;",
LIMIT 1", LIMIT 1",
self.id self.id
)) ))
.fetch_one(db.deref_mut()) .fetch_one(&mut *db)
.await .await
else { else {
return None; // No station for route exists return None; // No station for route exists
@@ -234,22 +235,17 @@ DROP TABLE temp_pos;",
let next_first_station_id = row.get::<i64, _>("next_first_station_id"); let next_first_station_id = row.get::<i64, _>("next_first_station_id");
Some( Some(
Station::find_by_id_tx(db, next_first_station_id) Station::find_by_id(db, next_first_station_id)
.await .await
.expect("db constraints"), .expect("db constraints"),
) )
} }
pub(crate) async fn get_next_first_station(&self, db: &SqlitePool) -> Option<Station> {
let mut transaction = db.begin().await.unwrap();
let res = self.get_next_first_station_tx(&mut transaction).await; pub async fn next_station(
&self,
transaction.commit().await.unwrap(); db: &mut SqliteConnection,
target_station: &Station,
res ) -> Option<Station> {
}
pub async fn next_station(&self, db: &SqlitePool, target_station: &Station) -> Option<Station> {
let stations = Station::all(db).await; let stations = Station::all(db).await;
for station in stations { for station in stations {
if let Some(prev_station) = self.prev_station(db, &station).await { if let Some(prev_station) = self.prev_station(db, &station).await {
@@ -261,7 +257,11 @@ DROP TABLE temp_pos;",
None None
} }
pub async fn prev_station(&self, db: &SqlitePool, station: &Station) -> Option<Station> { pub async fn prev_station(
&self,
db: &mut SqliteConnection,
station: &Station,
) -> Option<Station> {
if station.crewless() { if station.crewless() {
return None; return None;
} }

View File

@@ -13,7 +13,9 @@ use std::sync::Arc;
use tower_sessions::Session; use tower_sessions::Session;
async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup { async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
let routes = Route::all(&db).await; let db = &mut *db.acquire().await.unwrap();
let routes = Route::all(db).await;
let content = html! { let content = html! {
h1 { h1 {
@@ -28,7 +30,7 @@ async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
ol { ol {
@for route in &routes{ @for route in &routes{
li { li {
@if route.stations(&db).await.is_empty() { @if route.stations(db).await.is_empty() {
em data-tooltip=(t!("route_has_no_station_assigned")) { em data-tooltip=(t!("route_has_no_station_assigned")) {
"⚠️" "⚠️"
} }
@@ -71,7 +73,9 @@ async fn create(
session: Session, session: Session,
Form(form): Form<CreateForm>, Form(form): Form<CreateForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
match Route::create(&db, &form.name).await { let db = &mut *db.acquire().await.unwrap();
match Route::create(db, &form.name).await {
Ok(_) => suc!(session, t!("route_create_succ", name = form.name)), Ok(_) => suc!(session, t!("route_create_succ", name = form.name)),
Err(e) => er!( Err(e) => er!(
session, session,
@@ -87,12 +91,14 @@ async fn delete(
session: Session, session: Session,
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(route) = Route::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(route) = Route::find_by_id(db, id).await else {
er!(session, t!("nonexisting_route", id = id)); er!(session, t!("nonexisting_route", id = id));
return Redirect::to("/admin/route"); return Redirect::to("/admin/route");
}; };
match route.delete(&db).await { match route.delete(db).await {
Ok(()) => suc!(session, t!("route_delete_succ", name = route.name)), Ok(()) => suc!(session, t!("route_delete_succ", name = route.name)),
Err(e) => er!( Err(e) => er!(
session, session,
@@ -108,15 +114,17 @@ async fn view(
session: Session, session: Session,
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
) -> Result<Markup, impl IntoResponse> { ) -> Result<Markup, impl IntoResponse> {
let Some(route) = Route::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(route) = Route::find_by_id(db, id).await else {
er!(session, t!("nonexisting_route", id = id)); er!(session, t!("nonexisting_route", id = id));
return Err(Redirect::to("/admin/route")); return Err(Redirect::to("/admin/route"));
}; };
let cur_stations = route.stations(&db).await; let cur_stations = route.stations(db).await;
let stations_not_in_route = route.stations_not_in_route(&db).await; let stations_not_in_route = route.stations_not_in_route(db).await;
let teams = route.teams(&db).await; let teams = route.teams(db).await;
let content = html! { let content = html! {
h1 { h1 {
@@ -220,12 +228,14 @@ async fn update_name(
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateNameForm>, Form(form): Form<UpdateNameForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(route) = Route::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(route) = Route::find_by_id(db, id).await else {
er!(session, t!("nonexisting_route", id = id)); er!(session, t!("nonexisting_route", id = id));
return Redirect::to("/admin/route"); return Redirect::to("/admin/route");
}; };
route.update_name(&db, &form.name).await; route.update_name(db, &form.name).await;
suc!( suc!(
session, session,
@@ -245,16 +255,18 @@ async fn add_station(
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<AddStationForm>, Form(form): Form<AddStationForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(route) = Route::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(route) = Route::find_by_id(db, id).await else {
er!(session, t!("nonexisting_route", id = id)); er!(session, t!("nonexisting_route", id = id));
return Redirect::to("/admin/route"); return Redirect::to("/admin/route");
}; };
let Some(station) = Station::find_by_id(&db, form.station).await else { let Some(station) = Station::find_by_id(&mut *db, form.station).await else {
er!(session, t!("nonexisting_station", id = form.station)); er!(session, t!("nonexisting_station", id = form.station));
return Redirect::to(&format!("/admin/route/{id}")); return Redirect::to(&format!("/admin/route/{id}"));
}; };
match route.add_station(&db, &station).await { match route.add_station(db, &station).await {
Ok(()) => suc!( Ok(()) => suc!(
session, session,
t!( t!(
@@ -282,16 +294,18 @@ async fn delete_station(
session: Session, session: Session,
axum::extract::Path((route_id, station_id)): axum::extract::Path<(i64, i64)>, axum::extract::Path((route_id, station_id)): axum::extract::Path<(i64, i64)>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(route) = Route::find_by_id(&db, route_id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(route) = Route::find_by_id(db, route_id).await else {
er!(session, t!("nonexisting_route", id = route_id)); er!(session, t!("nonexisting_route", id = route_id));
return Redirect::to("/admin/route"); return Redirect::to("/admin/route");
}; };
let Some(station) = Station::find_by_id(&db, station_id).await else { let Some(station) = Station::find_by_id(&mut *db, station_id).await else {
er!(session, t!("nonexisting_station", id = station_id)); er!(session, t!("nonexisting_station", id = station_id));
return Redirect::to(&format!("/admin/route/{route_id}")); return Redirect::to(&format!("/admin/route/{route_id}"));
}; };
if route.delete_station(&db, &station).await { if route.delete_station(db, &station).await {
suc!( suc!(
session, session,
t!( t!(
@@ -319,16 +333,18 @@ async fn move_station_higher(
session: Session, session: Session,
axum::extract::Path((route_id, station_id)): axum::extract::Path<(i64, i64)>, axum::extract::Path((route_id, station_id)): axum::extract::Path<(i64, i64)>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(route) = Route::find_by_id(&db, route_id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(route) = Route::find_by_id(db, route_id).await else {
er!(session, t!("nonexisting_route", id = route_id)); er!(session, t!("nonexisting_route", id = route_id));
return Redirect::to("/admin/route"); return Redirect::to("/admin/route");
}; };
let Some(station) = Station::find_by_id(&db, station_id).await else { let Some(station) = Station::find_by_id(&mut *db, station_id).await else {
er!(session, t!("nonexisting_station", id = station_id)); er!(session, t!("nonexisting_station", id = station_id));
return Redirect::to(&format!("/admin/route/{route_id}")); return Redirect::to(&format!("/admin/route/{route_id}"));
}; };
if route.move_station_higher(&db, &station).await { if route.move_station_higher(db, &station).await {
suc!( suc!(
session, session,
t!( t!(

View File

@@ -6,11 +6,9 @@ use crate::{
}; };
use axum::Router; use axum::Router;
use chrono::{DateTime, Local, NaiveDateTime, Utc}; use chrono::{DateTime, Local, NaiveDateTime, Utc};
use futures::{stream, StreamExt};
use maud::{html, Markup, Render}; use maud::{html, Markup, Render};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use sqlx::{FromRow, SqliteConnection};
use std::ops::DerefMut;
pub(crate) mod model; pub(crate) mod model;
pub(crate) mod print; pub(crate) mod print;
@@ -52,7 +50,7 @@ impl Render for Station {
} }
impl Station { impl Station {
pub(crate) async fn all(db: &SqlitePool) -> Vec<Self> { pub(crate) async fn all(db: &mut SqliteConnection) -> Vec<Self> {
sqlx::query_as::<_, Self>( sqlx::query_as::<_, Self>(
"SELECT id, name, notes, amount_people, last_login, ready, pw, lat, lng FROM station;", "SELECT id, name, notes, amount_people, last_login, ready, pw, lat, lng FROM station;",
) )
@@ -61,18 +59,7 @@ impl Station {
.unwrap() .unwrap()
} }
pub async fn find_by_id_tx(db: &mut Transaction<'_, Sqlite>, id: i64) -> Option<Self> { pub async fn find_by_id(db: &mut SqliteConnection, id: i64) -> Option<Self> {
sqlx::query_as!(
Self,
"SELECT id, name, notes, amount_people, last_login, ready, pw, lat, lng FROM station WHERE id = ?",
id
)
.fetch_one(db.deref_mut())
.await
.ok()
}
pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> {
sqlx::query_as!( sqlx::query_as!(
Self, Self,
"SELECT id, name, notes, amount_people, last_login, ready, pw, lat, lng FROM station WHERE id = ?", "SELECT id, name, notes, amount_people, last_login, ready, pw, lat, lng FROM station WHERE id = ?",
@@ -90,13 +77,13 @@ impl Station {
false false
} }
pub async fn login(db: &SqlitePool, id: i64, code: &str) -> Option<Self> { pub async fn login(db: &mut SqliteConnection, id: i64, code: &str) -> Option<Self> {
let station = sqlx::query_as!( let station = sqlx::query_as!(
Self, Self,
"SELECT id, name, notes, amount_people, last_login, ready, pw, lat, lng FROM station WHERE id = ? AND pw = ?", "SELECT id, name, notes, amount_people, last_login, ready, pw, lat, lng FROM station WHERE id = ? AND pw = ?",
id, code id, code
) )
.fetch_one(db) .fetch_one(&mut *db)
.await .await
.ok()?; .ok()?;
@@ -111,7 +98,7 @@ impl Station {
Some(station) Some(station)
} }
pub async fn switch_ready(&self, db: &SqlitePool) { pub async fn switch_ready(&self, db: &mut SqliteConnection) {
let new_ready_status = !self.ready; let new_ready_status = !self.ready;
sqlx::query!( sqlx::query!(
"UPDATE station SET ready = ? WHERE id = ?", "UPDATE station SET ready = ? WHERE id = ?",
@@ -123,18 +110,18 @@ impl Station {
.unwrap(); .unwrap();
} }
pub(crate) async fn create(db: &SqlitePool, name: &str) -> Result<Self, String> { pub(crate) async fn create(db: &mut SqliteConnection, name: &str) -> Result<Self, String> {
let code = generate_random_alphanumeric(8); let code = generate_random_alphanumeric(8);
let station_id = sqlx::query!( let station_id = sqlx::query!(
"INSERT INTO station(name, pw) VALUES (?, ?) RETURNING id", "INSERT INTO station(name, pw) VALUES (?, ?) RETURNING id",
name, name,
code code
) )
.fetch_one(db) .fetch_one(&mut *db)
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let station = Station::find_by_id(db, station_id.id) let station = Station::find_by_id(&mut *db, station_id.id)
.await .await
.expect("just created"); .expect("just created");
@@ -150,7 +137,7 @@ impl Station {
pub(crate) async fn new_team_waiting( pub(crate) async fn new_team_waiting(
&self, &self,
db: &SqlitePool, db: &mut SqliteConnection,
team: &Team, team: &Team,
) -> Result<(), String> { ) -> Result<(), String> {
let teams = TeamsAtStationLocation::for_station(db, self).await; let teams = TeamsAtStationLocation::for_station(db, self).await;
@@ -170,7 +157,7 @@ impl Station {
pub(crate) async fn team_update( pub(crate) async fn team_update(
&self, &self,
db: &SqlitePool, db: &mut SqliteConnection,
team: &Team, team: &Team,
points: Option<i64>, points: Option<i64>,
notes: Option<String>, notes: Option<String>,
@@ -180,7 +167,7 @@ impl Station {
Some(n) => Some(n), Some(n) => Some(n),
None => None, None => None,
}; };
let teams = TeamsAtStationLocation::for_station(db, self).await; let teams = TeamsAtStationLocation::for_station(&mut *db, self).await;
let waiting_teams: Vec<&Team> = teams.waiting.iter().map(|(team, _)| team).collect(); let waiting_teams: Vec<&Team> = teams.waiting.iter().map(|(team, _)| team).collect();
let doing_teams: Vec<&Team> = teams.doing.iter().map(|(team, _)| team).collect(); let doing_teams: Vec<&Team> = teams.doing.iter().map(|(team, _)| team).collect();
@@ -207,7 +194,7 @@ impl Station {
pub(crate) async fn remove_team_waiting( pub(crate) async fn remove_team_waiting(
&self, &self,
db: &SqlitePool, db: &mut SqliteConnection,
team: &Team, team: &Team,
) -> Result<(), String> { ) -> Result<(), String> {
let teams = TeamsAtStationLocation::for_station(db, self).await; let teams = TeamsAtStationLocation::for_station(db, self).await;
@@ -223,7 +210,11 @@ impl Station {
Ok(()) Ok(())
} }
pub(crate) async fn team_starting(&self, db: &SqlitePool, team: &Team) -> Result<(), String> { pub(crate) async fn team_starting(
&self,
db: &mut SqliteConnection,
team: &Team,
) -> Result<(), String> {
let teams = TeamsAtStationLocation::for_station(db, self).await; let teams = TeamsAtStationLocation::for_station(db, self).await;
let waiting_teams: Vec<&Team> = teams.waiting.iter().map(|(team, _)| team).collect(); let waiting_teams: Vec<&Team> = teams.waiting.iter().map(|(team, _)| team).collect();
@@ -246,7 +237,7 @@ impl Station {
pub(crate) async fn remove_team_doing( pub(crate) async fn remove_team_doing(
&self, &self,
db: &SqlitePool, db: &mut SqliteConnection,
team: &Team, team: &Team,
) -> Result<(), String> { ) -> Result<(), String> {
let teams = TeamsAtStationLocation::for_station(db, self).await; let teams = TeamsAtStationLocation::for_station(db, self).await;
@@ -273,7 +264,11 @@ impl Station {
Ok(()) Ok(())
} }
pub(crate) async fn team_finished(&self, db: &SqlitePool, team: &Team) -> Result<(), String> { pub(crate) async fn team_finished(
&self,
db: &mut SqliteConnection,
team: &Team,
) -> Result<(), String> {
let teams = TeamsAtStationLocation::for_station(db, self).await; let teams = TeamsAtStationLocation::for_station(db, self).await;
let doing_teams: Vec<&Team> = teams.doing.iter().map(|(team, _)| team).collect(); let doing_teams: Vec<&Team> = teams.doing.iter().map(|(team, _)| team).collect();
@@ -296,7 +291,7 @@ impl Station {
pub(crate) async fn remove_team_left( pub(crate) async fn remove_team_left(
&self, &self,
db: &SqlitePool, db: &mut SqliteConnection,
team: &Team, team: &Team,
) -> Result<(), String> { ) -> Result<(), String> {
let teams = TeamsAtStationLocation::for_station(db, self).await; let teams = TeamsAtStationLocation::for_station(db, self).await;
@@ -325,7 +320,7 @@ impl Station {
Ok(()) Ok(())
} }
async fn delete(&self, db: &SqlitePool) -> Result<(), String> { async fn delete(&self, db: &mut SqliteConnection) -> Result<(), String> {
sqlx::query!("DELETE FROM station WHERE id = ?", self.id) sqlx::query!("DELETE FROM station WHERE id = ?", self.id)
.execute(db) .execute(db)
.await .await
@@ -333,7 +328,7 @@ impl Station {
Ok(()) Ok(())
} }
async fn routes(&self, db: &SqlitePool) -> Vec<Route> { async fn routes(&self, db: &mut SqliteConnection) -> Vec<Route> {
sqlx::query_as::<_, Route>( sqlx::query_as::<_, Route>(
" "
SELECT r.id, r.name SELECT r.id, r.name
@@ -349,7 +344,7 @@ impl Station {
.unwrap() .unwrap()
} }
pub async fn is_in_route(&self, db: &SqlitePool, route: &Route) -> bool { pub async fn is_in_route(&self, db: &mut SqliteConnection, route: &Route) -> bool {
for r in self.routes(db).await { for r in self.routes(db).await {
if r.id == route.id { if r.id == route.id {
return true; return true;
@@ -367,7 +362,7 @@ impl Station {
Some(datetime_utc.with_timezone(&Local)) Some(datetime_utc.with_timezone(&Local))
} }
pub(crate) async fn teams(&self, db: &SqlitePool) -> Vec<Team> { pub(crate) async fn teams(&self, db: &mut SqliteConnection) -> Vec<Team> {
sqlx::query_as::<_, Team>( sqlx::query_as::<_, Team>(
"SELECT DISTINCT t.id, t.name, t.notes, t.amount_people, t.first_station_id, t.last_station_id, t.route_id "SELECT DISTINCT t.id, t.name, t.notes, t.amount_people, t.first_station_id, t.last_station_id, t.route_id
FROM team t FROM team t
@@ -381,7 +376,7 @@ ORDER BY LOWER(t.name);",
.unwrap() .unwrap()
} }
pub(crate) async fn left_teams(&self, db: &SqlitePool) -> Vec<Team> { pub(crate) async fn left_teams(&self, db: &mut SqliteConnection) -> Vec<Team> {
sqlx::query_as::<_, Team>( sqlx::query_as::<_, Team>(
"SELECT t.id, t.name, t.notes, t.amount_people, t.first_station_id, t.last_station_id, t.route_id "SELECT t.id, t.name, t.notes, t.amount_people, t.first_station_id, t.last_station_id, t.route_id
FROM team t FROM team t
@@ -395,24 +390,20 @@ AND r.left_at IS NOT NULL;",
.unwrap() .unwrap()
} }
pub async fn teams_on_the_way(&self, db: &SqlitePool) -> Vec<TeamOnTheWay> { pub async fn teams_on_the_way(&self, db: &mut SqliteConnection) -> Vec<TeamOnTheWay> {
let mut ret = Vec::new(); let mut ret = Vec::new();
let teams = self.teams(db).await; let teams = self.teams(&mut *db).await;
let missing_teams: Vec<Team> = stream::iter(teams) let mut missing_teams = Vec::new();
.filter_map(|entry| async move { for team in teams {
if entry.been_at_station(db, self).await { if !team.been_at_station(&mut *db, self).await {
None missing_teams.push(team);
} else { }
Some(entry)
} }
})
.collect()
.await;
for team in missing_teams { for team in missing_teams {
let route = team.route(db).await; let route = team.route(&mut *db).await;
let Some(prev_station) = route.prev_station(db, self).await else { let Some(prev_station) = route.prev_station(db, self).await else {
continue; continue;
}; };
@@ -433,7 +424,7 @@ AND r.left_at IS NOT NULL;",
} }
} }
pub async fn some_team_has_last_station_id(db: &SqlitePool) -> bool { pub async fn some_team_has_last_station_id(db: &mut SqliteConnection) -> bool {
sqlx::query_scalar!("SELECT 1 FROM team WHERE last_station_id IS NOT NULL") sqlx::query_scalar!("SELECT 1 FROM team WHERE last_station_id IS NOT NULL")
.fetch_optional(db) .fetch_optional(db)
.await .await

View File

@@ -1,7 +1,7 @@
use crate::Station; use crate::Station;
use serde::Serialize; use serde::Serialize;
use sqlx::SqlitePool; use sqlx::Acquire;
use std::ops::DerefMut; use sqlx::SqliteConnection;
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug, PartialEq, Serialize)] #[derive(Error, Debug, PartialEq, Serialize)]
@@ -11,14 +11,14 @@ pub(crate) enum UpdateAmountPeopleError {
} }
impl Station { impl Station {
pub(crate) async fn update_name(&self, db: &SqlitePool, name: &str) { pub(crate) async fn update_name(&self, db: &mut SqliteConnection, name: &str) {
sqlx::query!("UPDATE station SET name = ? WHERE id = ?", name, self.id) sqlx::query!("UPDATE station SET name = ? WHERE id = ?", name, self.id)
.execute(db) .execute(db)
.await .await
.unwrap(); .unwrap();
} }
pub(crate) async fn update_notes(&self, db: &SqlitePool, notes: &str) { pub(crate) async fn update_notes(&self, db: &mut SqliteConnection, notes: &str) {
sqlx::query!("UPDATE station SET notes = ? WHERE id = ?", notes, self.id) sqlx::query!("UPDATE station SET notes = ? WHERE id = ?", notes, self.id)
.execute(db) .execute(db)
.await .await
@@ -27,7 +27,7 @@ impl Station {
pub(crate) async fn update_amount_people( pub(crate) async fn update_amount_people(
&self, &self,
db: &SqlitePool, db: &mut SqliteConnection,
amount_people: i64, amount_people: i64,
) -> Result<(), UpdateAmountPeopleError> { ) -> Result<(), UpdateAmountPeopleError> {
let mut transaction = db.begin().await.unwrap(); let mut transaction = db.begin().await.unwrap();
@@ -37,18 +37,18 @@ impl Station {
amount_people, amount_people,
self.id self.id
) )
.execute(transaction.deref_mut()) .execute(&mut *transaction)
.await .await
.unwrap(); .unwrap();
if amount_people == 0 { if amount_people == 0 {
let teams = self.teams(db).await; let teams = self.teams(&mut transaction).await;
for team in teams { for team in teams {
let route = team.route(db).await; let route = team.route(&mut transaction).await;
let Some(station) = route.get_next_first_station_tx(&mut transaction).await else { let Some(station) = route.get_next_first_station(&mut transaction).await else {
return Err(UpdateAmountPeopleError::LastStationCantBeCrewlessIfTeamExists); return Err(UpdateAmountPeopleError::LastStationCantBeCrewlessIfTeamExists);
}; };
team.update_first_station_tx(&mut transaction, &station) team.update_first_station(transaction.as_mut(), &station)
.await; .await;
} }
} }
@@ -58,7 +58,7 @@ impl Station {
Ok(()) Ok(())
} }
pub(crate) async fn update_amount_people_reset(&self, db: &SqlitePool) { pub(crate) async fn update_amount_people_reset(&self, db: &mut SqliteConnection) {
sqlx::query!( sqlx::query!(
"UPDATE station SET amount_people = NULL WHERE id = ?", "UPDATE station SET amount_people = NULL WHERE id = ?",
self.id self.id
@@ -68,7 +68,7 @@ impl Station {
.unwrap(); .unwrap();
} }
pub(crate) async fn update_location(&self, db: &SqlitePool, lat: f64, lng: f64) { pub(crate) async fn update_location(&self, db: &mut SqliteConnection, lat: f64, lng: f64) {
sqlx::query!( sqlx::query!(
"UPDATE station SET lat = ?, lng = ? WHERE id = ?", "UPDATE station SET lat = ?, lng = ? WHERE id = ?",
lat, lat,
@@ -80,7 +80,7 @@ impl Station {
.unwrap(); .unwrap();
} }
pub(crate) async fn update_location_clear(&self, db: &SqlitePool) { pub(crate) async fn update_location_clear(&self, db: &mut SqliteConnection) {
sqlx::query!( sqlx::query!(
"UPDATE station SET lat = NULL, lng=NULL WHERE id = ?", "UPDATE station SET lat = NULL, lng=NULL WHERE id = ?",
self.id self.id
@@ -103,30 +103,30 @@ mod test {
#[sqlx::test] #[sqlx::test]
async fn succ_update_not_last_crewful_station() { async fn succ_update_not_last_crewful_station() {
let pool = testdb!(); let pool = testdb!();
let db = &mut *pool.acquire().await.unwrap();
let station = Station::create(&pool, "Teststation").await.unwrap(); let station = Station::create(db, "Teststation").await.unwrap();
let crew_station = Station::create(&pool, "Bemannte Teststation") let crew_station = Station::create(db, "Bemannte Teststation").await.unwrap();
.await let route = Route::create(db, "Testroute").await.unwrap();
.unwrap(); route.add_station(db, &station).await.unwrap();
let route = Route::create(&pool, "Testroute").await.unwrap(); route.add_station(db, &crew_station).await.unwrap();
route.add_station(&pool, &station).await.unwrap(); let _ = Team::create(db, "Testteam", &route).await.unwrap();
route.add_station(&pool, &crew_station).await.unwrap();
let _ = Team::create(&pool, "Testteam", &route).await.unwrap();
assert_eq!(station.update_amount_people(&pool, 0).await, Ok(())); assert_eq!(station.update_amount_people(db, 0).await, Ok(()));
} }
#[sqlx::test] #[sqlx::test]
async fn fail_update_last_crewful_station() { async fn fail_update_last_crewful_station() {
let pool = testdb!(); let pool = testdb!();
let db = &mut *pool.acquire().await.unwrap();
let station = Station::create(&pool, "Teststation").await.unwrap(); let station = Station::create(db, "Teststation").await.unwrap();
let route = Route::create(&pool, "Testroute").await.unwrap(); let route = Route::create(db, "Testroute").await.unwrap();
route.add_station(&pool, &station).await.unwrap(); route.add_station(db, &station).await.unwrap();
let _ = Team::create(&pool, "Testteam", &route).await.unwrap(); let _ = Team::create(db, "Testteam", &route).await.unwrap();
assert_eq!( assert_eq!(
station.update_amount_people(&pool, 0).await, station.update_amount_people(db, 0).await,
Err(UpdateAmountPeopleError::LastStationCantBeCrewlessIfTeamExists) Err(UpdateAmountPeopleError::LastStationCantBeCrewlessIfTeamExists)
); );
} }

View File

@@ -73,9 +73,9 @@ pub(crate) async fn station_pdf(stations: Vec<Station>) -> Vec<u8> {
write!( write!(
content, content,
r#") r")
#create_card_grid(cards)"# #create_card_grid(cards)"
) )
.unwrap(); .unwrap();
@@ -92,7 +92,9 @@ pub(crate) async fn station_pdf(stations: Vec<Station>) -> Vec<u8> {
} }
async fn index(State(db): State<Arc<SqlitePool>>) -> impl IntoResponse { async fn index(State(db): State<Arc<SqlitePool>>) -> impl IntoResponse {
let stations = Station::all(&db).await; let db = &mut *db.acquire().await.unwrap();
let stations = Station::all(db).await;
let pdf = station_pdf(stations).await; let pdf = station_pdf(stations).await;
( (

View File

@@ -54,15 +54,14 @@ impl TypstWrapperWorld {
source: Source::detached(source), source: Source::detached(source),
time: time::OffsetDateTime::now_utc(), time: time::OffsetDateTime::now_utc(),
cache_directory: std::env::var_os("CACHE_DIRECTORY") cache_directory: std::env::var_os("CACHE_DIRECTORY")
.map(|os_path| os_path.into()) .map_or(std::env::temp_dir(), std::convert::Into::into),
.unwrap_or(std::env::temp_dir()),
http: ureq::Agent::new_with_defaults(), http: ureq::Agent::new_with_defaults(),
files: Arc::new(Mutex::new(HashMap::new())), files: Arc::new(Mutex::new(HashMap::new())),
} }
} }
} }
/// A File that will be stored in the HashMap. /// A File that will be stored in the `HashMap`.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct FileEntry { struct FileEntry {
bytes: Bytes, bytes: Bytes,

View File

@@ -15,6 +15,7 @@ use axum::{
use maud::{html, Markup}; use maud::{html, Markup};
use serde::Deserialize; use serde::Deserialize;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use std::fmt::Write;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use tower_sessions::Session; use tower_sessions::Session;
@@ -28,7 +29,9 @@ async fn create(
session: Session, session: Session,
Form(form): Form<CreateForm>, Form(form): Form<CreateForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
match Station::create(&db, &form.name).await { let db = &mut *db.acquire().await.unwrap();
match Station::create(db, &form.name).await {
Ok(_) => suc!(session, t!("station_create_succ", name = form.name)), Ok(_) => suc!(session, t!("station_create_succ", name = form.name)),
Err(e) => er!( Err(e) => er!(
session, session,
@@ -48,12 +51,14 @@ async fn delete(
session: Session, session: Session,
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::find_by_id(db, id).await else {
er!(session, t!("nonexisting_station", id = id)); er!(session, t!("nonexisting_station", id = id));
return Redirect::to("/admin/station"); return Redirect::to("/admin/station");
}; };
match station.delete(&db).await { match station.delete(db).await {
Ok(()) => suc!(session, t!("station_delete_succ", name = station.name)), Ok(()) => suc!(session, t!("station_delete_succ", name = station.name)),
Err(e) => er!( Err(e) => er!(
session, session,
@@ -73,12 +78,14 @@ async fn view(
session: Session, session: Session,
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
) -> Result<Markup, impl IntoResponse> { ) -> Result<Markup, impl IntoResponse> {
let Some(station) = Station::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::find_by_id(db, id).await else {
er!(session, t!("nonexisting_station", id = id)); er!(session, t!("nonexisting_station", id = id));
return Err(Redirect::to("/admin/station")); return Err(Redirect::to("/admin/station"));
}; };
let ratings = Rating::for_station(&db, &station).await; let ratings = Rating::for_station(db, &station).await;
// maybe switch to maud-display impl of station // maybe switch to maud-display impl of station
let content = html! { let content = html! {
@@ -226,7 +233,7 @@ async fn view(
tr { tr {
td { td {
a href=(format!("/admin/team/{}", rating.team_id)) { a href=(format!("/admin/team/{}", rating.team_id)) {
(rating.team(&db).await.name) (rating.team(db).await.name)
} }
} }
td { td {
@@ -348,12 +355,14 @@ async fn update_name(
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateNameForm>, Form(form): Form<UpdateNameForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::find_by_id(db, id).await else {
er!(session, t!("nonexisting_station", id = id)); er!(session, t!("nonexisting_station", id = id));
return Redirect::to("/admin/station"); return Redirect::to("/admin/station");
}; };
station.update_name(&db, &form.name).await; station.update_name(db, &form.name).await;
suc!( suc!(
session, session,
@@ -373,12 +382,14 @@ async fn update_notes(
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateNotesForm>, Form(form): Form<UpdateNotesForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::find_by_id(db, id).await else {
er!(session, t!("nonexisting_station", id = id)); er!(session, t!("nonexisting_station", id = id));
return Redirect::to("/admin/station"); return Redirect::to("/admin/station");
}; };
station.update_notes(&db, &form.notes).await; station.update_notes(db, &form.notes).await;
suc!(session, t!("station_new_notes", station = station.name)); suc!(session, t!("station_new_notes", station = station.name));
@@ -395,18 +406,20 @@ async fn update_amount_people(
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateAmountPeopleForm>, Form(form): Form<UpdateAmountPeopleForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::find_by_id(db, id).await else {
er!(session, t!("nonexisting_station", id = id)); er!(session, t!("nonexisting_station", id = id));
return Redirect::to("/admin/station"); return Redirect::to("/admin/station");
}; };
match station.update_amount_people(&db, form.amount_people).await { match station.update_amount_people(db, form.amount_people).await {
Ok(()) => suc!( Ok(()) => suc!(
session, session,
t!("station_new_crew_amount", station = station.name) t!("station_new_crew_amount", station = station.name)
), ),
Err(UpdateAmountPeopleError::LastStationCantBeCrewlessIfTeamExists) => { Err(UpdateAmountPeopleError::LastStationCantBeCrewlessIfTeamExists) => {
er!(session, t!("last_station_has_to_be_crewful")) er!(session, t!("last_station_has_to_be_crewful"));
} }
} }
@@ -418,12 +431,14 @@ async fn update_amount_people_reset(
session: Session, session: Session,
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::find_by_id(&mut *db, id).await else {
er!(session, t!("nonexisting_station", id = id)); er!(session, t!("nonexisting_station", id = id));
return Redirect::to("/admin/station"); return Redirect::to("/admin/station");
}; };
station.update_amount_people_reset(&db).await; station.update_amount_people_reset(db).await;
suc!( suc!(
session, session,
@@ -438,12 +453,14 @@ async fn quick(
session: Session, session: Session,
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
) -> Result<Markup, impl IntoResponse> { ) -> Result<Markup, impl IntoResponse> {
let Some(station) = Station::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::find_by_id(db, id).await else {
er!(session, t!("nonexisting_station", id = id)); er!(session, t!("nonexisting_station", id = id));
return Err(Redirect::to("/admin/station")); return Err(Redirect::to("/admin/station"));
}; };
let teams = station.teams(&db).await; let teams = station.teams(db).await;
// maybe switch to maud-display impl of team // maybe switch to maud-display impl of team
let content = html! { let content = html! {
@@ -470,7 +487,7 @@ async fn quick(
} }
} }
td { td {
@if let Some(rating) = Rating::find_by_team_and_station(&db, team, &station).await { @if let Some(rating) = Rating::find_by_team_and_station(db, team, &station).await {
a href=(format!("/s/{}/{}", station.id, station.pw)){ a href=(format!("/s/{}/{}", station.id, station.pw)){
@if let Some(points) = rating.points { @if let Some(points) = rating.points {
em data-tooltip=(t!("already_entered")) { em data-tooltip=(t!("already_entered")) {
@@ -506,7 +523,9 @@ async fn quick_post(
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<QuickUpdate>, Form(form): Form<QuickUpdate>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::find_by_id(db, id).await else {
er!(session, t!("nonexisting_station", id = id)); er!(session, t!("nonexisting_station", id = id));
return Redirect::to("/admin/station"); return Redirect::to("/admin/station");
}; };
@@ -516,25 +535,27 @@ async fn quick_post(
for (team_id, points) in &form.fields { for (team_id, points) in &form.fields {
let Ok(team_id) = team_id.parse::<i64>() else { let Ok(team_id) = team_id.parse::<i64>() else {
ret.push_str(&format!( let _ = write!(
ret,
"Skipped team_id={team_id} because this id can't be parsed as i64" "Skipped team_id={team_id} because this id can't be parsed as i64"
)); );
continue; continue;
}; };
let Ok(points) = points.parse::<i64>() else { let Ok(points) = points.parse::<i64>() else {
ret.push_str(&format!( let _ = write!(
"Skipped team_id={team_id} because points {} can't be parsed as i64", ret,
points "Skipped team_id={team_id} because {points} points can't be parsed as i64",
)); );
continue; continue;
}; };
let Some(team) = Team::find_by_id(&db, team_id).await else { let Some(team) = Team::find_by_id(db, team_id).await else {
ret.push_str(&format!( let _ = write!(
ret,
"Skipped team_id={team_id} because this team does not exist" "Skipped team_id={team_id} because this team does not exist"
)); );
continue; continue;
}; };
if Rating::find_by_team_and_station(&db, &team, &station) if Rating::find_by_team_and_station(db, &team, &station)
.await .await
.is_some() .is_some()
{ {
@@ -548,7 +569,7 @@ async fn quick_post(
continue; continue;
} }
Rating::create_quick(&db, &team, &station, points).await; Rating::create_quick(db, &team, &station, points).await;
amount_succ += 1; amount_succ += 1;
} }
@@ -577,12 +598,14 @@ async fn update_location(
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateLocationForm>, Form(form): Form<UpdateLocationForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::find_by_id(db, id).await else {
er!(session, t!("nonexisting_station", id = id)); er!(session, t!("nonexisting_station", id = id));
return Redirect::to("/admin/station"); return Redirect::to("/admin/station");
}; };
station.update_location(&db, form.lat, form.lng).await; station.update_location(db, form.lat, form.lng).await;
suc!(session, t!("location_changed", station = station.name)); suc!(session, t!("location_changed", station = station.name));
@@ -594,12 +617,14 @@ async fn update_location_clear(
session: Session, session: Session,
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::find_by_id(db, id).await else {
er!(session, t!("nonexisting_station", id = id)); er!(session, t!("nonexisting_station", id = id));
return Redirect::to("/admin/station"); return Redirect::to("/admin/station");
}; };
station.update_location_clear(&db).await; station.update_location_clear(db).await;
suc!(session, t!("location_deleted", station = station.name)); suc!(session, t!("location_deleted", station = station.name));
@@ -607,7 +632,9 @@ async fn update_location_clear(
} }
async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup { async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
let stations = Station::all(&db).await; let db = &mut *db.acquire().await.unwrap();
let stations = Station::all(db).await;
let content = html! { let content = html! {
h1 { h1 {
@@ -641,7 +668,7 @@ async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
} }
tbody { tbody {
@for station in &stations { @for station in &stations {
@let status = TeamsAtStationLocation::for_station(&db, station).await; @let status = TeamsAtStationLocation::for_station(db, station).await;
tr { tr {
td { td {
@if station.ready { @if station.ready {
@@ -649,7 +676,7 @@ async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
small { "🟢 " } small { "🟢 " }
} }
} }
@if station.routes(&db).await.is_empty() { @if station.routes(db).await.is_empty() {
a href="/admin/route" { a href="/admin/route" {
em data-tooltip=(t!("station_warning_not_assigned_route")) { em data-tooltip=(t!("station_warning_not_assigned_route")) {
"⚠️ " "⚠️ "
@@ -667,8 +694,8 @@ async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
} }
td { td {
em data-tooltip=(t!("station_team_progress", arrived=status.total_teams-status.not_yet_here.len() as i64, total=status.total_teams, waiting= status.waiting.len(), active=status.doing.len() )) { em data-tooltip=(t!("station_team_progress", arrived=status.total_teams-status.not_yet_here.len() as i64, total=status.total_teams, waiting= status.waiting.len(), active=status.doing.len() )) {
@if status.not_yet_here.len() == 0 { @if status.not_yet_here.is_empty() {
@if status.waiting.len() == 0 && status.doing.len() == 0 { @if status.waiting.is_empty() && status.doing.is_empty() {
"" ""
}@else{ }@else{
"🔜" "🔜"

View File

@@ -7,8 +7,7 @@ use axum::Router;
use chrono::{DateTime, Local, NaiveDateTime, Utc}; use chrono::{DateTime, Local, NaiveDateTime, Utc};
use maud::{html, Markup, Render}; use maud::{html, Markup, Render};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use sqlx::{FromRow, SqliteConnection};
use std::ops::DerefMut;
mod web; mod web;
@@ -41,7 +40,7 @@ pub(crate) struct LastContactTeam {
} }
impl LastContactTeam { impl LastContactTeam {
pub(crate) async fn all_sort_missing(db: &SqlitePool) -> Vec<Self> { pub(crate) async fn all_sort_missing(db: &mut SqliteConnection) -> Vec<Self> {
let rows = sqlx::query_as::<_, (i64, i64, Option<NaiveDateTime>)>( let rows = sqlx::query_as::<_, (i64, i64, Option<NaiveDateTime>)>(
"SELECT "SELECT
t.id AS team_id, t.id AS team_id,
@@ -65,13 +64,15 @@ LEFT JOIN station s ON last_contact.station_id = s.id
ORDER BY ORDER BY
last_contact.last_contact_time DESC", last_contact.last_contact_time DESC",
) )
.fetch_all(db) .fetch_all(&mut *db)
.await .await
.unwrap(); .unwrap();
let mut ret = Vec::new(); let mut ret = Vec::new();
for (team_id, station_id, last_contact_time) in rows { for (team_id, station_id, last_contact_time) in rows {
ret.push(LastContactTeam { ret.push(LastContactTeam {
team: Team::find_by_id(db, team_id).await.expect("db constraints"), team: Team::find_by_id(&mut *db, team_id)
.await
.expect("db constraints"),
station: Station::find_by_id(db, station_id).await, station: Station::find_by_id(db, station_id).await,
last_contact_time, last_contact_time,
}); });
@@ -95,7 +96,7 @@ pub(crate) enum CreateError {
} }
impl Team { impl Team {
pub(crate) async fn all(db: &SqlitePool) -> Vec<Self> { pub(crate) async fn all(db: &mut SqliteConnection) -> Vec<Self> {
sqlx::query_as::<_, Self>( sqlx::query_as::<_, Self>(
"SELECT id, name, notes, amount_people, first_station_id, last_station_id, route_id FROM team ORDER BY name;", "SELECT id, name, notes, amount_people, first_station_id, last_station_id, route_id FROM team ORDER BY name;",
) )
@@ -104,7 +105,7 @@ impl Team {
.unwrap() .unwrap()
} }
pub(crate) async fn all_with_route(db: &SqlitePool, route: &Route) -> Vec<Self> { pub(crate) async fn all_with_route(db: &mut SqliteConnection, route: &Route) -> Vec<Self> {
sqlx::query_as!( sqlx::query_as!(
Team, Team,
"SELECT id, name, notes, amount_people, first_station_id, last_station_id, route_id FROM team WHERE route_id = ?;", "SELECT id, name, notes, amount_people, first_station_id, last_station_id, route_id FROM team WHERE route_id = ?;",
@@ -115,7 +116,10 @@ impl Team {
.unwrap() .unwrap()
} }
pub(crate) async fn all_with_first_station(db: &SqlitePool, station: &Station) -> Vec<Self> { pub(crate) async fn all_with_first_station(
db: &mut SqliteConnection,
station: &Station,
) -> Vec<Self> {
sqlx::query_as!( sqlx::query_as!(
Team, Team,
"select id, name, notes, amount_people, first_station_id, last_station_id, route_id from team where first_station_id = ?;", "select id, name, notes, amount_people, first_station_id, last_station_id, route_id from team where first_station_id = ?;",
@@ -126,7 +130,10 @@ impl Team {
.unwrap() .unwrap()
} }
pub(crate) async fn all_with_last_station(db: &SqlitePool, station: &Station) -> Vec<Self> { pub(crate) async fn all_with_last_station(
db: &mut SqliteConnection,
station: &Station,
) -> Vec<Self> {
sqlx::query_as!( sqlx::query_as!(
Team, Team,
"select id, name, notes, amount_people, first_station_id, last_station_id, route_id from team where last_station_id = ?;", "select id, name, notes, amount_people, first_station_id, last_station_id, route_id from team where last_station_id = ?;",
@@ -137,7 +144,7 @@ impl Team {
.unwrap() .unwrap()
} }
pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> { pub async fn find_by_id(db: &mut SqliteConnection, id: i64) -> Option<Self> {
sqlx::query_as!( sqlx::query_as!(
Self, Self,
"SELECT id, name, notes, amount_people, first_station_id, last_station_id, route_id FROM team WHERE id = ?", "SELECT id, name, notes, amount_people, first_station_id, last_station_id, route_id FROM team WHERE id = ?",
@@ -149,7 +156,7 @@ impl Team {
} }
pub(crate) async fn create( pub(crate) async fn create(
db: &SqlitePool, db: &mut SqliteConnection,
name: &str, name: &str,
route: &Route, route: &Route,
) -> Result<i64, CreateError> { ) -> Result<i64, CreateError> {
@@ -171,14 +178,14 @@ impl Team {
Ok(result.id.unwrap()) Ok(result.id.unwrap())
} }
async fn update_name(&self, db: &SqlitePool, name: &str) { async fn update_name(&self, db: &mut SqliteConnection, name: &str) {
sqlx::query!("UPDATE team SET name = ? WHERE id = ?", name, self.id) sqlx::query!("UPDATE team SET name = ? WHERE id = ?", name, self.id)
.execute(db) .execute(db)
.await .await
.unwrap(); .unwrap();
} }
async fn update_end_station(&self, db: &SqlitePool, station: &Station) { async fn update_end_station(&self, db: &mut SqliteConnection, station: &Station) {
sqlx::query!( sqlx::query!(
"UPDATE team SET last_station_id = ? WHERE id = ?", "UPDATE team SET last_station_id = ? WHERE id = ?",
station.id, station.id,
@@ -189,14 +196,14 @@ impl Team {
.unwrap(); .unwrap();
} }
async fn update_notes(&self, db: &SqlitePool, notes: &str) { async fn update_notes(&self, db: &mut SqliteConnection, notes: &str) {
sqlx::query!("UPDATE team SET notes = ? WHERE id = ?", notes, self.id) sqlx::query!("UPDATE team SET notes = ? WHERE id = ?", notes, self.id)
.execute(db) .execute(db)
.await .await
.unwrap(); .unwrap();
} }
async fn update_amount_people(&self, db: &SqlitePool, amount_people: i64) { async fn update_amount_people(&self, db: &mut SqliteConnection, amount_people: i64) {
sqlx::query!( sqlx::query!(
"UPDATE team SET amount_people = ? WHERE id = ?", "UPDATE team SET amount_people = ? WHERE id = ?",
amount_people, amount_people,
@@ -207,7 +214,7 @@ impl Team {
.unwrap(); .unwrap();
} }
async fn update_route(&self, db: &SqlitePool, route: &Route) -> Result<String, ()> { async fn update_route(&self, db: &mut SqliteConnection, route: &Route) -> Result<String, ()> {
let Some(station) = route.get_next_first_station(db).await else { let Some(station) = route.get_next_first_station(db).await else {
return Err(()); return Err(());
}; };
@@ -225,7 +232,7 @@ impl Team {
Ok(station.name) Ok(station.name)
} }
pub(crate) async fn update_first_station(&self, db: &SqlitePool, station: &Station) { pub(crate) async fn update_first_station(&self, db: &mut SqliteConnection, station: &Station) {
sqlx::query!( sqlx::query!(
"UPDATE team SET first_station_id = ? WHERE id = ?", "UPDATE team SET first_station_id = ? WHERE id = ?",
station.id, station.id,
@@ -236,22 +243,7 @@ impl Team {
.unwrap(); .unwrap();
} }
pub(crate) async fn update_first_station_tx( async fn update_last_station(&self, db: &mut SqliteConnection, station: &Station) {
&self,
db: &mut Transaction<'_, Sqlite>,
station: &Station,
) {
sqlx::query!(
"UPDATE team SET first_station_id = ? WHERE id = ?",
station.id,
self.id
)
.execute(db.deref_mut())
.await
.unwrap();
}
async fn update_last_station(&self, db: &SqlitePool, station: &Station) {
sqlx::query!( sqlx::query!(
"UPDATE team SET last_station_id = ? WHERE id = ?", "UPDATE team SET last_station_id = ? WHERE id = ?",
station.id, station.id,
@@ -262,14 +254,14 @@ impl Team {
.unwrap(); .unwrap();
} }
async fn update_amount_people_reset(&self, db: &SqlitePool) { async fn update_amount_people_reset(&self, db: &mut SqliteConnection) {
sqlx::query!("UPDATE team SET amount_people = NULL WHERE id = ?", self.id) sqlx::query!("UPDATE team SET amount_people = NULL WHERE id = ?", self.id)
.execute(db) .execute(db)
.await .await
.unwrap(); .unwrap();
} }
async fn delete(&self, db: &SqlitePool) -> Result<(), String> { async fn delete(&self, db: &mut SqliteConnection) -> Result<(), String> {
sqlx::query!("DELETE FROM team WHERE id = ?", self.id) sqlx::query!("DELETE FROM team WHERE id = ?", self.id)
.execute(db) .execute(db)
.await .await
@@ -277,13 +269,13 @@ impl Team {
Ok(()) Ok(())
} }
pub async fn first_station(&self, db: &SqlitePool) -> Station { pub async fn first_station(&self, db: &mut SqliteConnection) -> Station {
Station::find_by_id(db, self.first_station_id) Station::find_by_id(db, self.first_station_id)
.await .await
.expect("db constraints") .expect("db constraints")
} }
pub async fn last_station(&self, db: &SqlitePool) -> Option<Station> { pub async fn last_station(&self, db: &mut SqliteConnection) -> Option<Station> {
if let Some(last_station_id) = self.last_station_id { if let Some(last_station_id) = self.last_station_id {
Station::find_by_id(db, last_station_id).await Station::find_by_id(db, last_station_id).await
} else { } else {
@@ -291,13 +283,13 @@ impl Team {
} }
} }
pub async fn route(&self, db: &SqlitePool) -> Route { pub async fn route(&self, db: &mut SqliteConnection) -> Route {
Route::find_by_id(db, self.route_id) Route::find_by_id(db, self.route_id)
.await .await
.expect("db constraints") .expect("db constraints")
} }
pub async fn get_curr_points(&self, db: &SqlitePool) -> i64 { pub async fn get_curr_points(&self, db: &mut SqliteConnection) -> i64 {
sqlx::query!( sqlx::query!(
"SELECT IFNULL(sum(points), 0) as points FROM rating WHERE team_id = ?", "SELECT IFNULL(sum(points), 0) as points FROM rating WHERE team_id = ?",
self.id self.id
@@ -308,13 +300,13 @@ impl Team {
.points .points
} }
pub async fn been_at_station(&self, db: &SqlitePool, station: &Station) -> bool { pub async fn been_at_station(&self, db: &mut SqliteConnection, station: &Station) -> bool {
Rating::find_by_team_and_station(db, self, station) Rating::find_by_team_and_station(db, self, station)
.await .await
.is_some() .is_some()
} }
pub(crate) async fn end_station(&self, db: &SqlitePool) -> Station { pub(crate) async fn end_station(&self, db: &mut SqliteConnection) -> Station {
match LastContactTeam::all_sort_missing(db) match LastContactTeam::all_sort_missing(db)
.await .await
.into_iter() .into_iter()
@@ -355,7 +347,7 @@ impl Team {
} }
} }
pub async fn end_run(db: &SqlitePool) { pub async fn end_run(db: &mut SqliteConnection) {
// set `last_station_id` to the next station where `left_at` is not null // set `last_station_id` to the next station where `left_at` is not null
let teams = Team::all(db).await; let teams = Team::all(db).await;
for team in teams { for team in teams {
@@ -364,7 +356,7 @@ impl Team {
} }
} }
pub async fn restart_run(db: &SqlitePool) { pub async fn restart_run(db: &mut SqliteConnection) {
sqlx::query!("UPDATE team SET last_station_id = null") sqlx::query!("UPDATE team SET last_station_id = null")
.execute(db) .execute(db)
.await .await

View File

@@ -14,7 +14,8 @@ use axum::{
}; };
use maud::{html, Markup, PreEscaped}; use maud::{html, Markup, PreEscaped};
use serde::Deserialize; use serde::Deserialize;
use sqlx::SqlitePool; use sqlx::{SqliteConnection, SqlitePool};
use std::fmt::Write;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use tower_sessions::Session; use tower_sessions::Session;
@@ -29,13 +30,15 @@ async fn create(
session: Session, session: Session,
Form(form): Form<CreateForm>, Form(form): Form<CreateForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(route) = Route::find_by_id(&db, form.route_id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(route) = Route::find_by_id(db, form.route_id).await else {
er!(session, t!("nonexisting_route", id = form.route_id)); er!(session, t!("nonexisting_route", id = form.route_id));
return Redirect::to("/admin/team"); return Redirect::to("/admin/team");
}; };
let id = match Team::create(&db, &form.name, &route).await { let id = match Team::create(db, &form.name, &route).await {
Ok(id) => { Ok(id) => {
suc!(session, t!("team_created", team = form.name)); suc!(session, t!("team_created", team = form.name));
id id
@@ -70,12 +73,14 @@ async fn delete(
session: Session, session: Session,
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(team) = Team::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(team) = Team::find_by_id(db, id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Redirect::to("/admin/team"); return Redirect::to("/admin/team");
}; };
match team.delete(&db).await { match team.delete(db).await {
Ok(()) => suc!(session, t!("team_deleted", team = team.name)), Ok(()) => suc!(session, t!("team_deleted", team = team.name)),
Err(e) => er!( Err(e) => er!(
session, session,
@@ -86,7 +91,12 @@ async fn delete(
Redirect::to("/admin/team") Redirect::to("/admin/team")
} }
async fn quick(db: Arc<SqlitePool>, team: &Team, stations: Vec<Station>, redirect: &str) -> Markup { async fn quick(
db: &mut SqliteConnection,
team: &Team,
stations: Vec<Station>,
redirect: &str,
) -> Markup {
html! { html! {
h1 { h1 {
a href=(format!("/admin/team/{}", team.id)) { "↩️" } a href=(format!("/admin/team/{}", team.id)) { "↩️" }
@@ -114,7 +124,7 @@ async fn quick(db: Arc<SqlitePool>, team: &Team, stations: Vec<Station>, redirec
} }
} }
td { td {
@if let Some(rating) = Rating::find_by_team_and_station(&db, team, station).await { @if let Some(rating) = Rating::find_by_team_and_station(db, team, station).await {
a href=(format!("/s/{}/{}", station.id, station.pw)){ a href=(format!("/s/{}/{}", station.id, station.pw)){
@if let Some(points) = rating.points { @if let Some(points) = rating.points {
em data-tooltip=(t!("already_entered")) { em data-tooltip=(t!("already_entered")) {
@@ -142,12 +152,14 @@ async fn quick_crewless(
session: Session, session: Session,
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
) -> Result<Markup, impl IntoResponse> { ) -> Result<Markup, impl IntoResponse> {
let Some(team) = Team::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(team) = Team::find_by_id(db, id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Err(Redirect::to("/admin/team")); return Err(Redirect::to("/admin/team"));
}; };
let stations: Vec<Station> = team.route(&db).await.crewless_stations(&db).await; let stations: Vec<Station> = team.route(db).await.crewless_stations(db).await;
let content = quick(db, &team, stations, "/crewless").await; let content = quick(db, &team, stations, "/crewless").await;
@@ -159,12 +171,14 @@ async fn quick_all(
session: Session, session: Session,
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
) -> Result<Markup, impl IntoResponse> { ) -> Result<Markup, impl IntoResponse> {
let Some(team) = Team::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(team) = Team::find_by_id(db, id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Err(Redirect::to("/admin/team")); return Err(Redirect::to("/admin/team"));
}; };
let stations = team.route(&db).await.stations(&db).await; let stations = team.route(db).await.stations(db).await;
let content = quick(db, &team, stations, "").await; let content = quick(db, &team, stations, "").await;
@@ -183,7 +197,9 @@ async fn quick_post(
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<QuickUpdate>, Form(form): Form<QuickUpdate>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(team) = Team::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(team) = Team::find_by_id(db, id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Redirect::to("/admin/team"); return Redirect::to("/admin/team");
}; };
@@ -193,24 +209,27 @@ async fn quick_post(
for (station_id, points) in &form.fields { for (station_id, points) in &form.fields {
let Ok(station_id) = station_id.parse::<i64>() else { let Ok(station_id) = station_id.parse::<i64>() else {
ret.push_str(&format!( let _ = write!(
ret,
"Skipped stationid={station_id} because this id can't be parsed as i64" "Skipped stationid={station_id} because this id can't be parsed as i64"
)); );
continue; continue;
}; };
let Ok(points) = points.parse::<i64>() else { let Ok(points) = points.parse::<i64>() else {
ret.push_str(&format!( let _ = write!(
"Skipped stationid={station_id} because points {points} can't be parsed as i64", ret,
)); "Skipped stationid={station_id} because {points} points can't be parsed as i64",
);
continue; continue;
}; };
let Some(station) = Station::find_by_id(&db, station_id).await else { let Some(station) = Station::find_by_id(&mut *db, station_id).await else {
ret.push_str(&format!( let _ = write!(
ret,
"Skipped stationid={station_id} because this station does not exist" "Skipped stationid={station_id} because this station does not exist"
)); );
continue; continue;
}; };
if Rating::find_by_team_and_station(&db, &team, &station) if Rating::find_by_team_and_station(db, &team, &station)
.await .await
.is_some() .is_some()
{ {
@@ -224,7 +243,7 @@ async fn quick_post(
continue; continue;
} }
Rating::create_quick(&db, &team, &station, points).await; Rating::create_quick(db, &team, &station, points).await;
amount_succ += 1; amount_succ += 1;
} }
@@ -247,15 +266,17 @@ async fn view(
session: Session, session: Session,
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
) -> Result<Markup, impl IntoResponse> { ) -> Result<Markup, impl IntoResponse> {
let Some(team) = Team::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(team) = Team::find_by_id(db, id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Err(Redirect::to("/admin/team")); return Err(Redirect::to("/admin/team"));
}; };
let first_station = team.first_station(&db).await; let first_station = team.first_station(db).await;
let last_station = team.last_station(&db).await; let last_station = team.last_station(db).await;
let routes = Route::all(&db).await; let routes = Route::all(db).await;
let stations = team.route(&db).await.crewful_stations(&db).await; let stations = team.route(db).await.crewful_stations(db).await;
// maybe switch to maud-display impl of team // maybe switch to maud-display impl of team
let content = html! { let content = html! {
@@ -335,8 +356,8 @@ async fn view(
tr { tr {
th scope="row" { (t!("route")) }; th scope="row" { (t!("route")) };
td { td {
a href=(format!("/admin/route/{}", &team.route(&db).await.id)) { a href=(format!("/admin/route/{}", &team.route(db).await.id)) {
(&team.route(&db).await.name) (&team.route(db).await.name)
} }
@if routes.len() > 1 { @if routes.len() > 1 {
details { details {
@@ -344,7 +365,7 @@ async fn view(
form action=(format!("/admin/team/{}/update-route", team.id)) method="post" { form action=(format!("/admin/team/{}/update-route", team.id)) method="post" {
select name="route_id" aria-label=(t!("select_route")) required { select name="route_id" aria-label=(t!("select_route")) required {
@for route in &routes { @for route in &routes {
@if route.id != team.route(&db).await.id { @if route.id != team.route(db).await.id {
option value=(route.id) { option value=(route.id) {
(route.name) (route.name)
} }
@@ -377,7 +398,7 @@ async fn view(
@if station.id != first_station.id { @if station.id != first_station.id {
option value=(station.id) { option value=(station.id) {
(station.name) (station.name)
@let amount_start_teams = Team::all_with_first_station(&db, station).await.len(); @let amount_start_teams = Team::all_with_first_station(db, station).await.len();
@if amount_start_teams > 0 { @if amount_start_teams > 0 {
@if amount_start_teams == 1 { @if amount_start_teams == 1 {
" (" " ("
@@ -456,12 +477,14 @@ async fn update_name(
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateNameForm>, Form(form): Form<UpdateNameForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(team) = Team::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(team) = Team::find_by_id(db, id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Redirect::to("/admin/team"); return Redirect::to("/admin/team");
}; };
team.update_name(&db, &form.name).await; team.update_name(db, &form.name).await;
suc!( suc!(
session, session,
@@ -481,12 +504,14 @@ async fn update_notes(
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateNotesForm>, Form(form): Form<UpdateNotesForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(team) = Team::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(team) = Team::find_by_id(db, id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Redirect::to("/admin/team"); return Redirect::to("/admin/team");
}; };
team.update_notes(&db, &form.notes).await; team.update_notes(db, &form.notes).await;
suc!(session, t!("notes_edited", team = team.name)); suc!(session, t!("notes_edited", team = team.name));
@@ -503,12 +528,14 @@ async fn update_amount_people(
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateAmountPeopleForm>, Form(form): Form<UpdateAmountPeopleForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(team) = Team::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(team) = Team::find_by_id(db, id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Redirect::to("/admin/team"); return Redirect::to("/admin/team");
}; };
team.update_amount_people(&db, form.amount_people).await; team.update_amount_people(db, form.amount_people).await;
suc!(session, t!("amount_teammembers_edited", team = team.name)); suc!(session, t!("amount_teammembers_edited", team = team.name));
@@ -525,18 +552,20 @@ async fn update_route(
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateRouteForm>, Form(form): Form<UpdateRouteForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let db = &mut *db.acquire().await.unwrap();
// TODO: move sanity checks into mod.rs // TODO: move sanity checks into mod.rs
let Some(team) = Team::find_by_id(&db, id).await else { let Some(team) = Team::find_by_id(db, id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Redirect::to("/admin/team"); return Redirect::to("/admin/team");
}; };
let Some(route) = Route::find_by_id(&db, form.route_id).await else { let Some(route) = Route::find_by_id(db, form.route_id).await else {
er!(session, t!("nonexisting_route", id = form.route_id)); er!(session, t!("nonexisting_route", id = form.route_id));
return Redirect::to(&format!("/admin/team/{id}")); return Redirect::to(&format!("/admin/team/{id}"));
}; };
match team.update_route(&db, &route).await { match team.update_route(db, &route).await {
Ok(new_first_station_name) => suc!( Ok(new_first_station_name) => suc!(
session, session,
t!( t!(
@@ -569,12 +598,14 @@ async fn update_first_station(
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateFirstStationForm>, Form(form): Form<UpdateFirstStationForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(team) = Team::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(team) = Team::find_by_id(db, id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Redirect::to("/admin/team"); return Redirect::to("/admin/team");
}; };
let Some(station) = Station::find_by_id(&db, form.first_station_id).await else { let Some(station) = Station::find_by_id(&mut *db, form.first_station_id).await else {
er!( er!(
session, session,
t!("nonexisting_station", id = form.first_station_id) t!("nonexisting_station", id = form.first_station_id)
@@ -583,21 +614,22 @@ async fn update_first_station(
return Redirect::to(&format!("/admin/team/{id}")); return Redirect::to(&format!("/admin/team/{id}"));
}; };
if !station.is_in_route(&db, &team.route(&db).await).await { let route = team.route(db).await;
if !station.is_in_route(db, &route).await {
er!( er!(
session, session,
t!( t!(
"first_station_not_edited_not_on_route", "first_station_not_edited_not_on_route",
station = station.name, station = station.name,
team = team.name, team = team.name,
route = team.route(&db).await.name route = team.route(db).await.name
) )
); );
return Redirect::to(&format!("/admin/team/{id}")); return Redirect::to(&format!("/admin/team/{id}"));
} }
team.update_first_station(&db, &station).await; team.update_first_station(db, &station).await;
suc!( suc!(
session, session,
@@ -621,12 +653,14 @@ async fn update_last_station(
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateLastStationForm>, Form(form): Form<UpdateLastStationForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(team) = Team::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(team) = Team::find_by_id(db, id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Redirect::to("/admin/team"); return Redirect::to("/admin/team");
}; };
let Some(station) = Station::find_by_id(&db, form.last_station_id).await else { let Some(station) = Station::find_by_id(&mut *db, form.last_station_id).await else {
er!( er!(
session, session,
t!("nonexisting_station", id = form.last_station_id) t!("nonexisting_station", id = form.last_station_id)
@@ -634,21 +668,22 @@ async fn update_last_station(
return Redirect::to(&format!("/admin/team/{id}")); return Redirect::to(&format!("/admin/team/{id}"));
}; };
if !station.is_in_route(&db, &team.route(&db).await).await { let route = team.route(db).await;
if !station.is_in_route(db, &route).await {
er!( er!(
session, session,
t!( t!(
"last_station_not_edited_not_on_route", "last_station_not_edited_not_on_route",
station = station.name, station = station.name,
team = team.name, team = team.name,
route = team.route(&db).await.name route = team.route(db).await.name
) )
); );
return Redirect::to(&format!("/admin/team/{id}")); return Redirect::to(&format!("/admin/team/{id}"));
} }
team.update_last_station(&db, &station).await; team.update_last_station(db, &station).await;
suc!( suc!(
session, session,
@@ -667,12 +702,14 @@ async fn update_amount_people_reset(
session: Session, session: Session,
axum::extract::Path(id): axum::extract::Path<i64>, axum::extract::Path(id): axum::extract::Path<i64>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(team) = Team::find_by_id(&db, id).await else { let db = &mut *db.acquire().await.unwrap();
let Some(team) = Team::find_by_id(db, id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Redirect::to("/admin/team"); return Redirect::to("/admin/team");
}; };
team.update_amount_people_reset(&db).await; team.update_amount_people_reset(db).await;
suc!(session, t!("amount_teammembers_edited", team = team.name)); suc!(session, t!("amount_teammembers_edited", team = team.name));
@@ -680,7 +717,9 @@ async fn update_amount_people_reset(
} }
async fn lost(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup { async fn lost(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
let losts = LastContactTeam::all_sort_missing(&db).await; let db = &mut *db.acquire().await.unwrap();
let losts = LastContactTeam::all_sort_missing(db).await;
let content = html! { let content = html! {
h1 { h1 {
@@ -731,8 +770,10 @@ async fn lost(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
} }
async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup { async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
let teams = Team::all(&db).await; let db = &mut *db.acquire().await.unwrap();
let routes = Route::all(&db).await;
let teams = Team::all(db).await;
let routes = Route::all(db).await;
let content = html! { let content = html! {
h1 { h1 {
@@ -792,7 +833,7 @@ async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
@for route in &routes { @for route in &routes {
h2 { (route.name) } h2 { (route.name) }
ol { ol {
@for team in &route.teams(&db).await{ @for team in &route.teams(db).await{
li { li {
a href=(format!("/admin/team/{}", team.id)){ a href=(format!("/admin/team/{}", team.id)){
(team.name) (team.name)

View File

@@ -302,7 +302,7 @@ pub async fn start(listener: TcpListener, db: SqlitePool) {
tokio::spawn(async move { tokio::spawn(async move {
// Kick-off typst compilation, to reduce wait time for 1st load // Kick-off typst compilation, to reduce wait time for 1st load
let stations = Station::all(&db).await; let stations = Station::all(&mut db.acquire().await.unwrap()).await;
station_pdf(stations).await; station_pdf(stations).await;
}); });

View File

@@ -4,7 +4,7 @@ use crate::{
}; };
use chrono::{DateTime, Local, NaiveDateTime, Utc}; use chrono::{DateTime, Local, NaiveDateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{FromRow, SqlitePool}; use sqlx::{FromRow, SqliteConnection};
use std::collections::HashMap; use std::collections::HashMap;
#[derive(FromRow, Debug, Serialize, Deserialize)] #[derive(FromRow, Debug, Serialize, Deserialize)]
@@ -20,7 +20,7 @@ pub(crate) struct Rating {
impl Rating { impl Rating {
pub(crate) async fn create( pub(crate) async fn create(
db: &SqlitePool, db: &mut SqliteConnection,
station: &Station, station: &Station,
team: &Team, team: &Team,
) -> Result<(), String> { ) -> Result<(), String> {
@@ -35,7 +35,12 @@ impl Rating {
Ok(()) Ok(())
} }
pub(crate) async fn create_quick(db: &SqlitePool, team: &Team, station: &Station, points: i64) { pub(crate) async fn create_quick(
db: &mut SqliteConnection,
team: &Team,
station: &Station,
points: i64,
) {
sqlx::query!( sqlx::query!(
"INSERT INTO rating(team_id, station_id, points, arrived_at, started_at, left_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)", "INSERT INTO rating(team_id, station_id, points, arrived_at, started_at, left_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
team.id, team.id,
@@ -47,19 +52,19 @@ impl Rating {
.unwrap(); .unwrap();
} }
pub(crate) async fn team(&self, db: &SqlitePool) -> Team { pub(crate) async fn team(&self, db: &mut SqliteConnection) -> Team {
Team::find_by_id(db, self.team_id) Team::find_by_id(db, self.team_id)
.await .await
.expect("db constraints") .expect("db constraints")
} }
pub(crate) async fn station(&self, db: &SqlitePool) -> Station { pub(crate) async fn station(&self, db: &mut SqliteConnection) -> Station {
Station::find_by_id(db, self.station_id) Station::find_by_id(db, self.station_id)
.await .await
.expect("db constraints") .expect("db constraints")
} }
pub(crate) async fn for_station(db: &SqlitePool, station: &Station) -> Vec<Self> { pub(crate) async fn for_station(db: &mut SqliteConnection, station: &Station) -> Vec<Self> {
sqlx::query_as::<_, Self>("SELECT team_id, station_id, points, notes, arrived_at, started_at, left_at FROM rating WHERE station_id = ?;") sqlx::query_as::<_, Self>("SELECT team_id, station_id, points, notes, arrived_at, started_at, left_at FROM rating WHERE station_id = ?;")
.bind(station.id) .bind(station.id)
.fetch_all(db) .fetch_all(db)
@@ -68,7 +73,7 @@ impl Rating {
} }
pub(crate) async fn update( pub(crate) async fn update(
db: &SqlitePool, db: &mut SqliteConnection,
station: &Station, station: &Station,
team: &Team, team: &Team,
points: Option<i64>, points: Option<i64>,
@@ -88,7 +93,7 @@ impl Rating {
} }
pub(crate) async fn delete( pub(crate) async fn delete(
db: &SqlitePool, db: &mut SqliteConnection,
station: &Station, station: &Station,
team: &Team, team: &Team,
) -> Result<(), String> { ) -> Result<(), String> {
@@ -103,7 +108,7 @@ impl Rating {
Ok(()) Ok(())
} }
pub async fn find_by_team_and_station( pub async fn find_by_team_and_station(
db: &SqlitePool, db: &mut SqliteConnection,
team: &Team, team: &Team,
station: &Station, station: &Station,
) -> Option<Self> { ) -> Option<Self> {
@@ -152,7 +157,10 @@ pub(crate) struct TeamsAtStationLocation {
} }
impl TeamsAtStationLocation { impl TeamsAtStationLocation {
pub(crate) async fn for_station(db: &SqlitePool, station: &Station) -> TeamsAtStationLocation { pub(crate) async fn for_station(
db: &mut SqliteConnection,
station: &Station,
) -> TeamsAtStationLocation {
let teams = station.teams(db).await; let teams = station.teams(db).await;
let total_teams = teams.len() as i64; let total_teams = teams.len() as i64;
@@ -165,8 +173,7 @@ impl TeamsAtStationLocation {
let mut done = true; let mut done = true;
for team in teams { for team in teams {
match Rating::find_by_team_and_station(db, &team, station).await { if let Some(rating) = Rating::find_by_team_and_station(db, &team, station).await {
Some(rating) => {
if rating.left_at.is_some() { if rating.left_at.is_some() {
if rating.points.is_some() { if rating.points.is_some() {
left_and_rated.push((team, rating)); left_and_rated.push((team, rating));
@@ -181,11 +188,9 @@ impl TeamsAtStationLocation {
done = false; done = false;
waiting.push((team, rating)); waiting.push((team, rating));
} }
} } else {
None => {
done = false; done = false;
not_yet_here.push(team) not_yet_here.push(team);
}
} }
} }
@@ -203,9 +208,9 @@ impl TeamsAtStationLocation {
not_yet_here_by_route, not_yet_here_by_route,
waiting, waiting,
doing, doing,
done,
left_not_yet_rated, left_not_yet_rated,
left_and_rated, left_and_rated,
done,
} }
} }
} }

View File

@@ -21,7 +21,9 @@ async fn view(
session: Session, session: Session,
axum::extract::Path((id, code)): axum::extract::Path<(i64, String)>, axum::extract::Path((id, code)): axum::extract::Path<(i64, String)>,
) -> Markup { ) -> Markup {
let Some(station) = Station::login(&db, id, &code).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::login(db, id, &code).await else {
let content = html! { let content = html! {
article class="error" { article class="error" {
(t!("invalid_rating_code")) (t!("invalid_rating_code"))
@@ -30,9 +32,9 @@ async fn view(
return partials::page(content, session, false).await; return partials::page(content, session, false).await;
}; };
let teams = TeamsAtStationLocation::for_station(&db, &station).await; let teams = TeamsAtStationLocation::for_station(db, &station).await;
let teams_on_the_way = station.teams_on_the_way(&db).await; let teams_on_the_way = station.teams_on_the_way(db).await;
let status = RunStatus::curr(&db).await; let status = RunStatus::curr(db).await;
let content = html! { let content = html! {
h1 { h1 {
@@ -47,7 +49,7 @@ async fn view(
"👋" "👋"
(t!("station_info")) (t!("station_info"))
" " " "
@let first_teams = Team::all_with_first_station(&db, &station).await; @let first_teams = Team::all_with_first_station(db, &station).await;
@if first_teams.is_empty() { @if first_teams.is_empty() {
(t!("station_has_no_teams_to_take_to_start")) (t!("station_has_no_teams_to_take_to_start"))
} @else{ } @else{
@@ -145,7 +147,7 @@ async fn view(
} }
@if status == RunStatus::HasEnded { @if status == RunStatus::HasEnded {
(t!("station_done")) (t!("station_done"))
@let teams_to_take_home = Team::all_with_last_station(&db, &station).await; @let teams_to_take_home = Team::all_with_last_station(db, &station).await;
@if !teams_to_take_home.is_empty() { @if !teams_to_take_home.is_empty() {
@if teams_to_take_home.len() == 1 { @if teams_to_take_home.len() == 1 {
(t!("take_home_the_following_team")) (t!("take_home_the_following_team"))
@@ -379,12 +381,14 @@ async fn ready(
session: Session, session: Session,
axum::extract::Path((id, code)): axum::extract::Path<(i64, String)>, axum::extract::Path((id, code)): axum::extract::Path<(i64, String)>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::login(&db, id, &code).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::login(db, id, &code).await else {
er!(session, t!("invalid_rating_code")); er!(session, t!("invalid_rating_code"));
return Redirect::to("/s/{id}/{code}"); return Redirect::to("/s/{id}/{code}");
}; };
station.switch_ready(&db).await; station.switch_ready(db).await;
suc!(session, t!("succ_change")); suc!(session, t!("succ_change"));
Redirect::to(&format!("/s/{id}/{code}")) Redirect::to(&format!("/s/{id}/{code}"))
@@ -400,16 +404,18 @@ async fn new_waiting(
axum::extract::Path((id, code)): axum::extract::Path<(i64, String)>, axum::extract::Path((id, code)): axum::extract::Path<(i64, String)>,
Form(form): Form<NewWaitingForm>, Form(form): Form<NewWaitingForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::login(&db, id, &code).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::login(db, id, &code).await else {
er!(session, t!("invalid_rating_code")); er!(session, t!("invalid_rating_code"));
return Redirect::to("/s/{id}/{code}"); return Redirect::to("/s/{id}/{code}");
}; };
let Some(team) = Team::find_by_id(&db, form.team_id).await else { let Some(team) = Team::find_by_id(db, form.team_id).await else {
er!(session, t!("nonexisting_team", id = form.team_id)); er!(session, t!("nonexisting_team", id = form.team_id));
return Redirect::to("/s/{id}/{code}"); return Redirect::to("/s/{id}/{code}");
}; };
match station.new_team_waiting(&db, &team).await { match station.new_team_waiting(db, &team).await {
Ok(()) => suc!(session, t!("team_added_to_waiting", team = team.name)), Ok(()) => suc!(session, t!("team_added_to_waiting", team = team.name)),
Err(e) => err!(session, "{e}"), Err(e) => err!(session, "{e}"),
} }
@@ -422,17 +428,19 @@ async fn remove_waiting(
session: Session, session: Session,
axum::extract::Path((id, code, team_id)): axum::extract::Path<(i64, String, i64)>, axum::extract::Path((id, code, team_id)): axum::extract::Path<(i64, String, i64)>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::login(&db, id, &code).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::login(db, id, &code).await else {
er!(session, t!("invalid_rating_code")); er!(session, t!("invalid_rating_code"));
return Redirect::to("/s/{id}/{code}"); return Redirect::to("/s/{id}/{code}");
}; };
let Some(team) = Team::find_by_id(&db, team_id).await else { let Some(team) = Team::find_by_id(db, team_id).await else {
er!(session, t!("nonexisting_team", id = team_id)); er!(session, t!("nonexisting_team", id = team_id));
return Redirect::to("/s/{id}/{code}"); return Redirect::to("/s/{id}/{code}");
}; };
match station.remove_team_waiting(&db, &team).await { match station.remove_team_waiting(db, &team).await {
Ok(()) => suc!(session, t!("team_removed_from_waiting", team = team.name)), Ok(()) => suc!(session, t!("team_removed_from_waiting", team = team.name)),
Err(e) => err!(session, "{e}"), Err(e) => err!(session, "{e}"),
} }
@@ -445,17 +453,18 @@ async fn team_starting(
session: Session, session: Session,
axum::extract::Path((id, code, team_id)): axum::extract::Path<(i64, String, i64)>, axum::extract::Path((id, code, team_id)): axum::extract::Path<(i64, String, i64)>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::login(&db, id, &code).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::login(&mut *db, id, &code).await else {
er!(session, t!("invalid_rating_code")); er!(session, t!("invalid_rating_code"));
return Redirect::to("/s/{id}/{code}"); return Redirect::to("/s/{id}/{code}");
}; };
let Some(team) = Team::find_by_id(&db, team_id).await else { let Some(team) = Team::find_by_id(&mut *db, team_id).await else {
er!(session, t!("nonexisting_team", id = team_id)); er!(session, t!("nonexisting_team", id = team_id));
return Redirect::to("/s/{id}/{code}"); return Redirect::to("/s/{id}/{code}");
}; };
match station.team_starting(&db, &team).await { match station.team_starting(&mut *db, &team).await {
Ok(()) => suc!(session, t!("team_added_to_active", team = team.name)), Ok(()) => suc!(session, t!("team_added_to_active", team = team.name)),
Err(e) => err!(session, "{e}"), Err(e) => err!(session, "{e}"),
} }
@@ -468,17 +477,19 @@ async fn remove_doing(
session: Session, session: Session,
axum::extract::Path((id, code, team_id)): axum::extract::Path<(i64, String, i64)>, axum::extract::Path((id, code, team_id)): axum::extract::Path<(i64, String, i64)>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::login(&db, id, &code).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::login(db, id, &code).await else {
er!(session, t!("invalid_rating_code")); er!(session, t!("invalid_rating_code"));
return Redirect::to("/s/{id}/{code}"); return Redirect::to("/s/{id}/{code}");
}; };
let Some(team) = Team::find_by_id(&db, team_id).await else { let Some(team) = Team::find_by_id(db, team_id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Redirect::to("/s/{id}/{code}"); return Redirect::to("/s/{id}/{code}");
}; };
match station.remove_team_doing(&db, &team).await { match station.remove_team_doing(db, &team).await {
Ok(()) => suc!(session, t!("team_removed_from_active", team = team.name)), Ok(()) => suc!(session, t!("team_removed_from_active", team = team.name)),
Err(e) => err!(session, "{e}"), Err(e) => err!(session, "{e}"),
} }
@@ -491,17 +502,19 @@ async fn team_finished(
session: Session, session: Session,
axum::extract::Path((id, code, team_id)): axum::extract::Path<(i64, String, i64)>, axum::extract::Path((id, code, team_id)): axum::extract::Path<(i64, String, i64)>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::login(&db, id, &code).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::login(db, id, &code).await else {
er!(session, t!("invalid_rating_code")); er!(session, t!("invalid_rating_code"));
return Redirect::to("/s/{id}/{code}"); return Redirect::to("/s/{id}/{code}");
}; };
let Some(team) = Team::find_by_id(&db, team_id).await else { let Some(team) = Team::find_by_id(db, team_id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Redirect::to("/s/{id}/{code}"); return Redirect::to("/s/{id}/{code}");
}; };
match station.team_finished(&db, &team).await { match station.team_finished(db, &team).await {
Ok(()) => suc!(session, t!("team_added_to_finished", team = team.name)), Ok(()) => suc!(session, t!("team_added_to_finished", team = team.name)),
Err(e) => err!(session, "{e}"), Err(e) => err!(session, "{e}"),
} }
@@ -514,17 +527,19 @@ async fn remove_left(
session: Session, session: Session,
axum::extract::Path((id, code, team_id)): axum::extract::Path<(i64, String, i64)>, axum::extract::Path((id, code, team_id)): axum::extract::Path<(i64, String, i64)>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::login(&db, id, &code).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::login(db, id, &code).await else {
er!(session, t!("invalid_rating_code")); er!(session, t!("invalid_rating_code"));
return Redirect::to("/s/{id}/{code}"); return Redirect::to("/s/{id}/{code}");
}; };
let Some(team) = Team::find_by_id(&db, team_id).await else { let Some(team) = Team::find_by_id(db, team_id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Redirect::to("/s/{id}/{code}"); return Redirect::to("/s/{id}/{code}");
}; };
match station.remove_team_left(&db, &team).await { match station.remove_team_left(db, &team).await {
Ok(()) => suc!(session, t!("team_removed_from_finished", team = team.name)), Ok(()) => suc!(session, t!("team_removed_from_finished", team = team.name)),
Err(e) => err!(session, "{e}"), Err(e) => err!(session, "{e}"),
} }
@@ -543,17 +558,18 @@ async fn team_update(
axum::extract::Path((id, code, team_id)): axum::extract::Path<(i64, String, i64)>, axum::extract::Path((id, code, team_id)): axum::extract::Path<(i64, String, i64)>,
Form(form): Form<TeamUpdateForm>, Form(form): Form<TeamUpdateForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let Some(station) = Station::login(&db, id, &code).await else { let db = &mut *db.acquire().await.unwrap();
let Some(station) = Station::login(db, id, &code).await else {
er!(session, t!("invalid_rating_code")); er!(session, t!("invalid_rating_code"));
return Redirect::to("/s/{id}/{code}"); return Redirect::to("/s/{id}/{code}");
}; };
let Some(team) = Team::find_by_id(&db, team_id).await else { let Some(team) = Team::find_by_id(db, team_id).await else {
er!(session, t!("nonexisting_team", id = id)); er!(session, t!("nonexisting_team", id = id));
return Redirect::to("/s/{id}/{code}"); return Redirect::to("/s/{id}/{code}");
}; };
match station match station
.team_update(&db, &team, form.points, form.notes) .team_update(db, &team, form.points, form.notes)
.await .await
{ {
Ok(()) => suc!(session, t!("rating_updated", team = team.name)), Ok(()) => suc!(session, t!("rating_updated", team = team.name)),
@@ -587,8 +603,9 @@ mod test {
#[sqlx::test] #[sqlx::test]
async fn test_wrong_station() { async fn test_wrong_station() {
let pool = testdb!(); let pool = testdb!();
let db = &mut *pool.acquire().await.unwrap();
Station::create(&pool, "Teststation").await.unwrap(); Station::create(db, "Teststation").await.unwrap();
let server = TestServer::new(router(pool)).unwrap(); let server = TestServer::new(router(pool)).unwrap();
@@ -600,9 +617,10 @@ mod test {
#[sqlx::test] #[sqlx::test]
async fn test_correct_station() { async fn test_correct_station() {
let pool = testdb!(); let pool = testdb!();
let db = &mut *pool.acquire().await.unwrap();
Station::create(&pool, "42-Station").await.unwrap(); Station::create(db, "42-Station").await.unwrap();
let stations = Station::all(&pool).await; let stations = Station::all(db).await;
let station = stations.last().unwrap(); let station = stations.last().unwrap();
let server = TestServer::new(router(pool)).unwrap(); let server = TestServer::new(router(pool)).unwrap();