370 lines
11 KiB
Rust
370 lines
11 KiB
Rust
use crate::{
|
|
AppState,
|
|
admin::{route::Route, station::Station},
|
|
rating::Rating,
|
|
};
|
|
use axum::Router;
|
|
use chrono::{DateTime, Local, NaiveDateTime, Utc};
|
|
use maud::{Markup, Render, html};
|
|
use serde::{Deserialize, Serialize};
|
|
use sqlx::{FromRow, SqliteConnection};
|
|
|
|
mod web;
|
|
|
|
#[derive(FromRow, Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
pub(crate) struct Team {
|
|
pub(crate) id: i64,
|
|
pub(crate) name: String,
|
|
pub(crate) notes: Option<String>,
|
|
pub(crate) amount_people: Option<i64>,
|
|
first_station_id: i64,
|
|
last_station_id: Option<i64>,
|
|
pub(crate) route_id: i64,
|
|
}
|
|
|
|
impl Render for Team {
|
|
fn render(&self) -> Markup {
|
|
html! {
|
|
a href=(format!("/admin/team/{}", self.id)){
|
|
(self.name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(FromRow, Debug, Serialize, Deserialize, PartialEq)]
|
|
pub(crate) struct LastContactTeam {
|
|
team: Team,
|
|
station: Option<Station>,
|
|
last_contact_time: Option<NaiveDateTime>,
|
|
}
|
|
|
|
impl LastContactTeam {
|
|
pub(crate) async fn all_sort_missing(db: &mut SqliteConnection) -> Vec<Self> {
|
|
let rows = sqlx::query_as::<_, (i64, i64, Option<NaiveDateTime>)>(
|
|
"SELECT
|
|
t.id AS team_id,
|
|
last_contact.station_id AS station_id,
|
|
last_contact.last_contact_time AS last_contact_time
|
|
FROM
|
|
team t
|
|
LEFT JOIN (
|
|
SELECT
|
|
r.team_id,
|
|
r.station_id,
|
|
MAX(COALESCE(r.left_at, r.started_at, r.arrived_at)) AS last_contact_time
|
|
FROM
|
|
rating r
|
|
GROUP BY
|
|
r.team_id
|
|
HAVING
|
|
last_contact_time = MAX(COALESCE(r.left_at, r.started_at, r.arrived_at))
|
|
) AS last_contact ON t.id = last_contact.team_id
|
|
LEFT JOIN station s ON last_contact.station_id = s.id
|
|
ORDER BY
|
|
last_contact.last_contact_time DESC",
|
|
)
|
|
.fetch_all(&mut *db)
|
|
.await
|
|
.unwrap();
|
|
let mut ret = Vec::new();
|
|
for (team_id, station_id, last_contact_time) in rows {
|
|
ret.push(LastContactTeam {
|
|
team: Team::find_by_id(&mut *db, team_id)
|
|
.await
|
|
.expect("db constraints"),
|
|
station: Station::find_by_id(db, station_id).await,
|
|
last_contact_time,
|
|
});
|
|
}
|
|
ret
|
|
}
|
|
|
|
pub(crate) fn local_last_contact(&self) -> Option<DateTime<Local>> {
|
|
let Some(last_contact) = &self.last_contact_time else {
|
|
return None;
|
|
};
|
|
let datetime_utc = DateTime::<Utc>::from_naive_utc_and_offset(*last_contact, Utc);
|
|
Some(datetime_utc.with_timezone(&Local))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) enum CreateError {
|
|
NoStationForRoute,
|
|
DuplicateName(String),
|
|
}
|
|
|
|
impl Team {
|
|
pub(crate) async fn all(db: &mut SqliteConnection) -> Vec<Self> {
|
|
sqlx::query_as::<_, Self>(
|
|
"SELECT id, name, notes, amount_people, first_station_id, last_station_id, route_id FROM team ORDER BY name;",
|
|
)
|
|
.fetch_all(db)
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
pub(crate) async fn all_with_route(db: &mut SqliteConnection, route: &Route) -> Vec<Self> {
|
|
sqlx::query_as!(
|
|
Team,
|
|
"SELECT id, name, notes, amount_people, first_station_id, last_station_id, route_id FROM team WHERE route_id = ?;",
|
|
route.id
|
|
)
|
|
.fetch_all(db)
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
pub(crate) async fn all_with_first_station(
|
|
db: &mut SqliteConnection,
|
|
station: &Station,
|
|
) -> Vec<Self> {
|
|
sqlx::query_as!(
|
|
Team,
|
|
"select id, name, notes, amount_people, first_station_id, last_station_id, route_id from team where first_station_id = ?;",
|
|
station.id
|
|
)
|
|
.fetch_all(db)
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
pub(crate) async fn all_with_last_station(
|
|
db: &mut SqliteConnection,
|
|
station: &Station,
|
|
) -> Vec<Self> {
|
|
sqlx::query_as!(
|
|
Team,
|
|
"select id, name, notes, amount_people, first_station_id, last_station_id, route_id from team where last_station_id = ?;",
|
|
station.id
|
|
)
|
|
.fetch_all(db)
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
pub async fn find_by_id(db: &mut SqliteConnection, id: i64) -> Option<Self> {
|
|
sqlx::query_as!(
|
|
Self,
|
|
"SELECT id, name, notes, amount_people, first_station_id, last_station_id, route_id FROM team WHERE id = ?",
|
|
id
|
|
)
|
|
.fetch_one(db)
|
|
.await
|
|
.ok()
|
|
}
|
|
|
|
pub(crate) async fn create(
|
|
db: &mut SqliteConnection,
|
|
name: &str,
|
|
route: &Route,
|
|
) -> Result<i64, CreateError> {
|
|
// get next station id which has the lowest amount of teams to have in the first place
|
|
// assigned
|
|
let Some(station) = route.get_next_first_station(db).await else {
|
|
return Err(CreateError::NoStationForRoute);
|
|
};
|
|
|
|
let result = sqlx::query!(
|
|
"INSERT INTO team(name, route_id, first_station_id) VALUES (?, ?, ?) RETURNING id",
|
|
name,
|
|
route.id,
|
|
station.id
|
|
)
|
|
.fetch_one(db)
|
|
.await
|
|
.map_err(|e| CreateError::DuplicateName(e.to_string()))?;
|
|
Ok(result.id.unwrap())
|
|
}
|
|
|
|
async fn update_name(&self, db: &mut SqliteConnection, name: &str) {
|
|
sqlx::query!("UPDATE team SET name = ? WHERE id = ?", name, self.id)
|
|
.execute(db)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
async fn update_end_station(&self, db: &mut SqliteConnection, station: &Station) {
|
|
sqlx::query!(
|
|
"UPDATE team SET last_station_id = ? WHERE id = ?",
|
|
station.id,
|
|
self.id
|
|
)
|
|
.execute(db)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
async fn update_notes(&self, db: &mut SqliteConnection, notes: &str) {
|
|
sqlx::query!("UPDATE team SET notes = ? WHERE id = ?", notes, self.id)
|
|
.execute(db)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
async fn update_amount_people(&self, db: &mut SqliteConnection, amount_people: i64) {
|
|
sqlx::query!(
|
|
"UPDATE team SET amount_people = ? WHERE id = ?",
|
|
amount_people,
|
|
self.id
|
|
)
|
|
.execute(db)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
async fn update_route(&self, db: &mut SqliteConnection, route: &Route) -> Result<String, ()> {
|
|
let Some(station) = route.get_next_first_station(db).await else {
|
|
return Err(());
|
|
};
|
|
|
|
sqlx::query!(
|
|
"UPDATE team SET route_id = ?, first_station_id = ? WHERE id = ?",
|
|
route.id,
|
|
station.id,
|
|
self.id
|
|
)
|
|
.execute(db)
|
|
.await
|
|
.unwrap();
|
|
|
|
Ok(station.name)
|
|
}
|
|
|
|
pub(crate) async fn update_first_station(&self, db: &mut SqliteConnection, station: &Station) {
|
|
sqlx::query!(
|
|
"UPDATE team SET first_station_id = ? WHERE id = ?",
|
|
station.id,
|
|
self.id
|
|
)
|
|
.execute(db)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
async fn update_last_station(&self, db: &mut SqliteConnection, station: &Station) {
|
|
sqlx::query!(
|
|
"UPDATE team SET last_station_id = ? WHERE id = ?",
|
|
station.id,
|
|
self.id
|
|
)
|
|
.execute(db)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
async fn update_amount_people_reset(&self, db: &mut SqliteConnection) {
|
|
sqlx::query!("UPDATE team SET amount_people = NULL WHERE id = ?", self.id)
|
|
.execute(db)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
async fn delete(&self, db: &mut SqliteConnection) -> Result<(), String> {
|
|
sqlx::query!("DELETE FROM team WHERE id = ?", self.id)
|
|
.execute(db)
|
|
.await
|
|
.map_err(|e| e.to_string())?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn first_station(&self, db: &mut SqliteConnection) -> Station {
|
|
Station::find_by_id(db, self.first_station_id)
|
|
.await
|
|
.expect("db constraints")
|
|
}
|
|
|
|
pub async fn last_station(&self, db: &mut SqliteConnection) -> Option<Station> {
|
|
if let Some(last_station_id) = self.last_station_id {
|
|
Station::find_by_id(db, last_station_id).await
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub async fn route(&self, db: &mut SqliteConnection) -> Route {
|
|
Route::find_by_id(db, self.route_id)
|
|
.await
|
|
.expect("db constraints")
|
|
}
|
|
|
|
pub async fn get_curr_points(&self, db: &mut SqliteConnection) -> i64 {
|
|
sqlx::query!(
|
|
"SELECT IFNULL(sum(points), 0) as points FROM rating WHERE team_id = ?",
|
|
self.id
|
|
)
|
|
.fetch_one(db)
|
|
.await
|
|
.unwrap()
|
|
.points
|
|
}
|
|
|
|
pub async fn been_at_station(&self, db: &mut SqliteConnection, station: &Station) -> bool {
|
|
Rating::find_by_team_and_station(db, self, station)
|
|
.await
|
|
.is_some()
|
|
}
|
|
|
|
pub(crate) async fn end_station(&self, db: &mut SqliteConnection) -> Station {
|
|
match LastContactTeam::all_sort_missing(db)
|
|
.await
|
|
.into_iter()
|
|
.find(|last_contact_team| &last_contact_team.team == self)
|
|
{
|
|
Some(last_contact_team) => {
|
|
if let Some(station) = last_contact_team.station {
|
|
// Team already made some contact with a station
|
|
match Rating::find_by_team_and_station(db, self, &station).await {
|
|
Some(rating) => {
|
|
if rating.left_at.is_none() {
|
|
rating.station(db).await
|
|
} else {
|
|
let next_station = self
|
|
.route(db)
|
|
.await
|
|
.next_station(db, &station)
|
|
.await
|
|
.unwrap_or(station.clone());
|
|
if Rating::find_by_team_and_station(db, self, &next_station)
|
|
.await
|
|
.is_some()
|
|
{
|
|
station // last station for team
|
|
} else {
|
|
next_station
|
|
}
|
|
}
|
|
}
|
|
None => self.first_station(db).await,
|
|
}
|
|
} else {
|
|
// Team has made no contact yet -> next station should be the first one
|
|
self.first_station(db).await
|
|
}
|
|
}
|
|
None => unreachable!(),
|
|
}
|
|
}
|
|
|
|
pub async fn end_run(db: &mut SqliteConnection) {
|
|
// set `last_station_id` to the next station where `left_at` is not null
|
|
let teams = Team::all(db).await;
|
|
for team in teams {
|
|
let end_station = team.end_station(db).await;
|
|
team.update_end_station(db, &end_station).await;
|
|
}
|
|
}
|
|
|
|
pub async fn restart_run(db: &mut SqliteConnection) {
|
|
sqlx::query!("UPDATE team SET last_station_id = null")
|
|
.execute(db)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
pub(super) fn routes() -> Router<AppState> {
|
|
web::routes()
|
|
}
|