station is able to specify if they are ready; Fixes #25
All checks were successful
CI/CD Pipeline / test (push) Successful in 4m2s
CI/CD Pipeline / deploy (push) Successful in 2m40s

This commit is contained in:
Philipp Hofer 2025-04-13 21:20:58 +02:00
parent 45422c5ea7
commit 2a1d0a7616
4 changed files with 75 additions and 25 deletions

View File

@ -4,6 +4,7 @@ CREATE TABLE IF NOT EXISTS station (
notes TEXT, notes TEXT,
amount_people INTEGER, amount_people INTEGER,
last_login DATETIME, last_login DATETIME,
ready BOOLEAN NOT NULL DEFAULT false,
pw TEXT NOT NULL DEFAULT (upper(hex(randomblob(4)))), pw TEXT NOT NULL DEFAULT (upper(hex(randomblob(4)))),
lat REAL, lat REAL,
lng REAL lng REAL

View File

@ -19,6 +19,7 @@ pub(crate) struct Station {
amount_people: Option<i64>, amount_people: Option<i64>,
last_login: Option<NaiveDateTime>, // TODO use proper timestamp (NaiveDateTime?) last_login: Option<NaiveDateTime>, // TODO use proper timestamp (NaiveDateTime?)
pub(crate) pw: String, pub(crate) pw: String,
pub(crate) ready: bool,
pub(crate) lat: Option<f64>, pub(crate) lat: Option<f64>,
pub(crate) lng: Option<f64>, pub(crate) lng: Option<f64>,
} }
@ -26,7 +27,7 @@ pub(crate) struct Station {
impl Station { impl Station {
pub(crate) async fn all(db: &SqlitePool) -> Vec<Self> { pub(crate) async fn all(db: &SqlitePool) -> Vec<Self> {
sqlx::query_as::<_, Self>( sqlx::query_as::<_, Self>(
"SELECT id, name, notes, amount_people, last_login, pw, lat, lng FROM station;", "SELECT id, name, notes, amount_people, last_login, ready, pw, lat, lng FROM station;",
) )
.fetch_all(db) .fetch_all(db)
.await .await
@ -36,7 +37,7 @@ impl Station {
pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> { 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, pw, lat, lng FROM station WHERE id = ?", "SELECT id, name, notes, amount_people, last_login, ready, pw, lat, lng FROM station WHERE id = ?",
id id
) )
.fetch_one(db) .fetch_one(db)
@ -47,7 +48,7 @@ impl Station {
pub async fn login(db: &SqlitePool, id: i64, code: &str) -> Option<Self> { pub async fn login(db: &SqlitePool, 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, 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(db)
@ -65,6 +66,18 @@ impl Station {
Some(station) Some(station)
} }
pub async fn switch_ready(&self, db: &SqlitePool) {
let new_ready_status = !self.ready;
sqlx::query!(
"UPDATE station SET ready = ? WHERE id = ?",
new_ready_status,
self.id
)
.execute(db)
.await
.unwrap();
}
pub(crate) async fn create(db: &SqlitePool, name: &str) -> Result<(), String> { pub(crate) async fn create(db: &SqlitePool, name: &str) -> Result<(), String> {
sqlx::query!("INSERT INTO station(name) VALUES (?)", name) sqlx::query!("INSERT INTO station(name) VALUES (?)", name)
.execute(db) .execute(db)

View File

@ -509,9 +509,14 @@ async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
@let status = TeamsAtStationLocation::for_station(&db, station).await; @let status = TeamsAtStationLocation::for_station(&db, station).await;
tr { tr {
td { td {
@if station.ready {
em data-tooltip="Station bereit!" {
small { "🟢 " }
}
}
@if station.routes(&db).await.is_empty() { @if station.routes(&db).await.is_empty() {
em data-tooltip=(t!("station_warning_not_assigned_route")) { em data-tooltip=(t!("station_warning_not_assigned_route")) {
"⚠️" "⚠️ "
} }
} }
a href=(format!("/admin/station/{}", station.id)){ a href=(format!("/admin/station/{}", station.id)){

View File

@ -39,32 +39,43 @@ async fn view(
h1 { (format!("Station {}", station.name)) } h1 { (format!("Station {}", station.name)) }
@if let (Some(lat), Some(lng)) = (station.lat, station.lng) { @if let (Some(lat), Some(lng)) = (station.lat, station.lng) {
article { article {
b { "Hier befindet sich deine Station:" } details open[(!station.ready)]{
div id="map" style="height: 500px" {} summary { "Stationsort" }
script { (format!(" b { "Hier befindet sich deine Station:" }
const map = L.map('map').setView([{lat}, {lng}], 14); div id="map" style="height: 500px" {}
script { (format!("
const map = L.map('map').setView([{lat}, {lng}], 14);
L.tileLayer('https://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png', {{ L.tileLayer('https://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png', {{
attribution: '© OpenStreetMap contributors' attribution: '© OpenStreetMap contributors'
}}).addTo(map); }}).addTo(map);
const myIcon = L.icon({{ const myIcon = L.icon({{
iconUrl: '/marker.png', iconUrl: '/marker.png',
iconAnchor: [12, 41] iconAnchor: [12, 41]
}}); }});
currentMarker = L.marker([{lat}, {lng}], {{icon: myIcon}}).addTo(map); currentMarker = L.marker([{lat}, {lng}], {{icon: myIcon}}).addTo(map);
map.setView([lat, lng], 14); map.setView([lat, lng], 14);
"))
")) }
} div {
sub { sub {
a href=(format!("https://www.google.com/maps?q={lat},{lng}")) target="_blank" { a href=(format!("https://www.google.com/maps?q={lat},{lng}")) target="_blank" {
"Google Maps..." "Google Maps..."
}
}
}
hr;
a href=(format!("/s/{id}/{code}/ready")){
@if station.ready {
"Bin mit der Station doch noch nicht bereit..."
} @else {
button { "Ich bin bei meiner Station und bin bereit zu starten!" }
}
}
} }
} }
}
} }
article { article {
"Insgesamt sollten " "Insgesamt sollten "
@ -234,6 +245,25 @@ async fn view(
partials::page(content, session, use_map).await partials::page(content, session, use_map).await
} }
async fn ready(
State(db): State<Arc<SqlitePool>>,
session: Session,
axum::extract::Path((id, code)): axum::extract::Path<(i64, String)>,
) -> impl IntoResponse {
let Some(station) = Station::login(&db, id, &code).await else {
err!(
session,
"Falscher Quick-Einlogg-Link. Bitte nochmal scannen oder neu eingeben."
);
return Redirect::to("/s/{id}/{code}");
};
station.switch_ready(&db).await;
succ!(session, "Erfolgreich geändert");
Redirect::to(&format!("/s/{id}/{code}"))
}
#[derive(Deserialize)] #[derive(Deserialize)]
struct NewWaitingForm { struct NewWaitingForm {
team_id: i64, team_id: i64,
@ -459,6 +489,7 @@ async fn team_update(
pub(super) fn routes() -> Router<AppState> { pub(super) fn routes() -> Router<AppState> {
Router::new() Router::new()
.route("/", get(view)) .route("/", get(view))
.route("/ready", get(ready))
.route("/new-waiting", post(new_waiting)) .route("/new-waiting", post(new_waiting))
.route("/remove-waiting/{team_id}", get(remove_waiting)) .route("/remove-waiting/{team_id}", get(remove_waiting))
.route("/team-starting/{team_id}", get(team_starting)) .route("/team-starting/{team_id}", get(team_starting))