diff --git a/migration.sql b/migration.sql index 25ca245..2cacfd6 100644 --- a/migration.sql +++ b/migration.sql @@ -4,6 +4,7 @@ CREATE TABLE IF NOT EXISTS station ( notes TEXT, amount_people INTEGER, last_login DATETIME, + ready BOOLEAN NOT NULL DEFAULT false, pw TEXT NOT NULL DEFAULT (upper(hex(randomblob(4)))), lat REAL, lng REAL diff --git a/src/admin/station/mod.rs b/src/admin/station/mod.rs index 8f3cf86..7884339 100644 --- a/src/admin/station/mod.rs +++ b/src/admin/station/mod.rs @@ -19,6 +19,7 @@ pub(crate) struct Station { amount_people: Option, last_login: Option, // TODO use proper timestamp (NaiveDateTime?) pub(crate) pw: String, + pub(crate) ready: bool, pub(crate) lat: Option, pub(crate) lng: Option, } @@ -26,7 +27,7 @@ pub(crate) struct Station { impl Station { pub(crate) async fn all(db: &SqlitePool) -> Vec { 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) .await @@ -36,7 +37,7 @@ impl Station { pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option { sqlx::query_as!( 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 ) .fetch_one(db) @@ -47,7 +48,7 @@ impl Station { pub async fn login(db: &SqlitePool, id: i64, code: &str) -> Option { let station = sqlx::query_as!( 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 ) .fetch_one(db) @@ -65,6 +66,18 @@ impl 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> { sqlx::query!("INSERT INTO station(name) VALUES (?)", name) .execute(db) diff --git a/src/admin/station/web.rs b/src/admin/station/web.rs index 8d0ddc6..18a0396 100644 --- a/src/admin/station/web.rs +++ b/src/admin/station/web.rs @@ -509,9 +509,14 @@ async fn index(State(db): State>, session: Session) -> Markup { @let status = TeamsAtStationLocation::for_station(&db, station).await; tr { td { + @if station.ready { + em data-tooltip="Station bereit!" { + small { "🟢 " } + } + } @if station.routes(&db).await.is_empty() { em data-tooltip=(t!("station_warning_not_assigned_route")) { - "⚠️" + "⚠️ " } } a href=(format!("/admin/station/{}", station.id)){ diff --git a/src/station.rs b/src/station.rs index b7d7e29..5b10f07 100644 --- a/src/station.rs +++ b/src/station.rs @@ -39,32 +39,43 @@ async fn view( h1 { (format!("Station {}", station.name)) } @if let (Some(lat), Some(lng)) = (station.lat, station.lng) { article { - b { "Hier befindet sich deine Station:" } - div id="map" style="height: 500px" {} - script { (format!(" - const map = L.map('map').setView([{lat}, {lng}], 14); + details open[(!station.ready)]{ + summary { "Stationsort" } + b { "Hier befindet sich deine Station:" } + 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', {{ - attribution: '© OpenStreetMap contributors' - }}).addTo(map); + L.tileLayer('https://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png', {{ + attribution: '© OpenStreetMap contributors' + }}).addTo(map); - const myIcon = L.icon({{ - iconUrl: '/marker.png', - iconAnchor: [12, 41] -}}); - currentMarker = L.marker([{lat}, {lng}], {{icon: myIcon}}).addTo(map); - map.setView([lat, lng], 14); - - ")) - } - sub { - a href=(format!("https://www.google.com/maps?q={lat},{lng}")) target="_blank" { - "Google Maps..." + const myIcon = L.icon({{ + iconUrl: '/marker.png', + iconAnchor: [12, 41] + }}); + currentMarker = L.marker([{lat}, {lng}], {{icon: myIcon}}).addTo(map); + map.setView([lat, lng], 14); + ")) + } + div { + sub { + a href=(format!("https://www.google.com/maps?q={lat},{lng}")) target="_blank" { + "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 { "Insgesamt sollten " @@ -234,6 +245,25 @@ async fn view( partials::page(content, session, use_map).await } +async fn ready( + State(db): State>, + 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)] struct NewWaitingForm { team_id: i64, @@ -459,6 +489,7 @@ async fn team_update( pub(super) fn routes() -> Router { Router::new() .route("/", get(view)) + .route("/ready", get(ready)) .route("/new-waiting", post(new_waiting)) .route("/remove-waiting/{team_id}", get(remove_waiting)) .route("/team-starting/{team_id}", get(team_starting))