From 272b5eb61cbdee4c5ffb8acca460590f43163c38 Mon Sep 17 00:00:00 2001 From: Philipp Hofer Date: Tue, 8 Apr 2025 14:00:14 +0200 Subject: [PATCH] split route code --- src/lib.rs | 25 +++-- src/route/mod.rs | 167 ++++++++++++++++++++++++++++++ src/{route.rs => route/web.rs} | 165 +---------------------------- src/station/mod.rs | 4 +- src/station/{routes.rs => web.rs} | 0 5 files changed, 193 insertions(+), 168 deletions(-) create mode 100644 src/route/mod.rs rename src/{route.rs => route/web.rs} (72%) rename src/station/{routes.rs => web.rs} (100%) diff --git a/src/lib.rs b/src/lib.rs index bfb6c5c..c52eefd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ -use axum::{Router, body::Body, response::Response, routing::get}; -use maud::{Markup, html}; +use axum::{body::Body, response::Response, routing::get, Router}; +use maud::{html, Markup}; use partials::page; use sqlx::SqlitePool; use std::sync::Arc; @@ -79,11 +79,24 @@ async fn serve_marker_png() -> Response { async fn index(session: Session) -> Markup { let content = html! { h1 { "Stationslauf-App" } - a role="button" href="/station" { - "Stationen" + nav { + ul { + li { + a role="button" href="/station" { + "Stationen" + } + } + li { + a role="button" href="/route" { + "Routen" + } + } + li { + a role="button" href="/group" { + "Gruppen" + } + } } - a role="button" href="/route" { - "Routen" } }; page(content, session, false).await diff --git a/src/route/mod.rs b/src/route/mod.rs new file mode 100644 index 0000000..4970e4c --- /dev/null +++ b/src/route/mod.rs @@ -0,0 +1,167 @@ +use crate::station::Station; +use axum::Router; +use serde::{Deserialize, Serialize}; +use sqlx::{FromRow, SqlitePool}; +use std::sync::Arc; + +mod web; + +#[derive(FromRow, Debug, Serialize, Deserialize)] +pub(crate) struct Route { + pub(crate) id: i64, + name: String, +} + +impl Route { + async fn all(db: &SqlitePool) -> Vec { + sqlx::query_as::<_, Self>("SELECT id, name FROM route;") + .fetch_all(db) + .await + .unwrap() + } + + pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option { + sqlx::query_as!(Self, "SELECT id, name FROM route WHERE id = ?", id) + .fetch_one(db) + .await + .ok() + } + + async fn create(db: &SqlitePool, name: &str) -> Result<(), String> { + sqlx::query!("INSERT INTO route(name) VALUES (?)", name) + .execute(db) + .await + .map_err(|e| e.to_string())?; + Ok(()) + } + + async fn update_name(&self, db: &SqlitePool, name: &str) { + sqlx::query!("UPDATE route SET name = ? WHERE id = ?", name, self.id) + .execute(db) + .await + .unwrap(); + } + + async fn add_station(&self, db: &SqlitePool, station: &Station) -> Result<(), String> { + sqlx::query!( + r#" + INSERT INTO route_station (route_id, station_id, pos) + VALUES (?, ?, ( + SELECT COALESCE(MAX(pos), 0) + 2 + FROM route_station + WHERE route_id = ? + )) + "#, + self.id, + station.id, + self.id + ) + .execute(db) + .await + .map_err(|e| e.to_string())?; + + Ok(()) + } + + async fn delete_station(&self, db: &SqlitePool, station: &Station) -> bool { + let result = sqlx::query!( + "DELETE FROM route_station WHERE route_id = ? AND station_id = ?", + self.id, + station.id + ) + .execute(db) + .await + .unwrap(); + + result.rows_affected() > 0 + } + + async fn move_station_higher(&self, db: &SqlitePool, station: &Station) -> bool { + let result = sqlx::query!( + "UPDATE route_station SET pos = pos-3 WHERE route_id = ? AND station_id = ?", + self.id, + station.id + ) + .execute(db) + .await + .unwrap(); + + if result.rows_affected() == 0 { + return false; + } + + sqlx::query( + &format!( + " +-- Step 1: Create a temporary table with new rank values +CREATE TEMP TABLE IF NOT EXISTS temp_pos AS +SELECT + route_id, + station_id, + ROW_NUMBER() OVER (ORDER BY pos ASC) AS new_rank +FROM + route_station WHERE route_id = {}; + +-- Step 2: Update the original table +UPDATE route_station +SET pos = (SELECT 2*new_rank FROM temp_pos WHERE temp_pos.route_id = route_station.route_id AND temp_pos.station_id = route_station.station_id) WHERE route_id = {}; + +-- Step 3: Drop the temporary table (no longer needed) +DROP TABLE temp_pos;", + self.id, + self.id, + )) + .execute(db) + .await + .unwrap(); + + true + } + + async fn delete(&self, db: &SqlitePool) -> Result<(), String> { + sqlx::query!("DELETE FROM route WHERE id = ?", self.id) + .execute(db) + .await + .map_err(|e| e.to_string())?; + Ok(()) + } + + async fn stations(&self, db: &SqlitePool) -> Vec { + sqlx::query_as::<_, Station>( + " + SELECT s.id, s.name, s.notes, s.amount_people, s.last_login, s.pw, s.lat, s.lng + FROM station s + JOIN route_station rs ON s.id = rs.station_id + WHERE rs.route_id = ? + ORDER BY rs.pos + ", + ) + .bind(self.id) + .fetch_all(db) + .await + .unwrap() + } + + async fn stations_not_in_route(&self, db: &SqlitePool) -> Vec { + sqlx::query_as::<_, Station>( + " + SELECT id, name, notes, amount_people, last_login, pw, lat, lng + FROM station + WHERE id NOT IN ( + SELECT station_id + FROM route_station + WHERE route_id = ? + ) + ORDER BY name + ", + ) + .bind(self.id) + .fetch_all(db) + .await + .unwrap() + } +} + +pub(super) fn routes() -> Router> { + web::routes() +} diff --git a/src/route.rs b/src/route/web.rs similarity index 72% rename from src/route.rs rename to src/route/web.rs index 45f912c..9a4f855 100644 --- a/src/route.rs +++ b/src/route/web.rs @@ -1,172 +1,17 @@ +use super::Route; use crate::{err, page, station::Station, succ}; use axum::{ - Form, Router, extract::State, response::{IntoResponse, Redirect}, routing::{get, post}, + Form, Router, }; -use maud::{Markup, PreEscaped, html}; -use serde::{Deserialize, Serialize}; -use sqlx::{FromRow, SqlitePool}; +use maud::{html, Markup, PreEscaped}; +use serde::Deserialize; +use sqlx::SqlitePool; use std::sync::Arc; use tower_sessions::Session; -#[derive(FromRow, Debug, Serialize, Deserialize)] -pub(crate) struct Route { - pub(crate) id: i64, - name: String, -} - -impl Route { - async fn all(db: &SqlitePool) -> Vec { - sqlx::query_as::<_, Self>("SELECT id, name FROM route;") - .fetch_all(db) - .await - .unwrap() - } - - pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option { - sqlx::query_as!(Self, "SELECT id, name FROM route WHERE id = ?", id) - .fetch_one(db) - .await - .ok() - } - - async fn create(db: &SqlitePool, name: &str) -> Result<(), String> { - sqlx::query!("INSERT INTO route(name) VALUES (?)", name) - .execute(db) - .await - .map_err(|e| e.to_string())?; - Ok(()) - } - - async fn update_name(&self, db: &SqlitePool, name: &str) { - sqlx::query!("UPDATE route SET name = ? WHERE id = ?", name, self.id) - .execute(db) - .await - .unwrap(); - } - - async fn add_station(&self, db: &SqlitePool, station: &Station) -> Result<(), String> { - sqlx::query!( - r#" - INSERT INTO route_station (route_id, station_id, pos) - VALUES (?, ?, ( - SELECT COALESCE(MAX(pos), 0) + 2 - FROM route_station - WHERE route_id = ? - )) - "#, - self.id, - station.id, - self.id - ) - .execute(db) - .await - .map_err(|e| e.to_string())?; - - Ok(()) - } - - async fn delete_station(&self, db: &SqlitePool, station: &Station) -> bool { - let result = sqlx::query!( - "DELETE FROM route_station WHERE route_id = ? AND station_id = ?", - self.id, - station.id - ) - .execute(db) - .await - .unwrap(); - - result.rows_affected() > 0 - } - - async fn move_station_higher(&self, db: &SqlitePool, station: &Station) -> bool { - let result = sqlx::query!( - "UPDATE route_station SET pos = pos-3 WHERE route_id = ? AND station_id = ?", - self.id, - station.id - ) - .execute(db) - .await - .unwrap(); - - if result.rows_affected() == 0 { - return false; - } - - sqlx::query( - &format!( - " --- Step 1: Create a temporary table with new rank values -CREATE TEMP TABLE IF NOT EXISTS temp_pos AS -SELECT - route_id, - station_id, - ROW_NUMBER() OVER (ORDER BY pos ASC) AS new_rank -FROM - route_station WHERE route_id = {}; - --- Step 2: Update the original table -UPDATE route_station -SET pos = (SELECT 2*new_rank FROM temp_pos WHERE temp_pos.route_id = route_station.route_id AND temp_pos.station_id = route_station.station_id) WHERE route_id = {}; - --- Step 3: Drop the temporary table (no longer needed) -DROP TABLE temp_pos;", - self.id, - self.id, - )) - .execute(db) - .await - .unwrap(); - - true - } - - async fn delete(&self, db: &SqlitePool) -> Result<(), String> { - sqlx::query!("DELETE FROM route WHERE id = ?", self.id) - .execute(db) - .await - .map_err(|e| e.to_string())?; - Ok(()) - } - - async fn stations(&self, db: &SqlitePool) -> Vec { - sqlx::query_as::<_, Station>( - " - SELECT s.id, s.name, s.notes, s.amount_people, s.last_login, s.pw, s.lat, s.lng - FROM station s - JOIN route_station rs ON s.id = rs.station_id - WHERE rs.route_id = ? - ORDER BY rs.pos - ", - ) - .bind(self.id) - .fetch_all(db) - .await - .unwrap() - } - - async fn stations_not_in_route(&self, db: &SqlitePool) -> Vec { - sqlx::query_as::<_, Station>( - " - SELECT id, name, notes, amount_people, last_login, pw, lat, lng - FROM station - WHERE id NOT IN ( - SELECT station_id - FROM route_station - WHERE route_id = ? - ) - ORDER BY name - ", - ) - .bind(self.id) - .fetch_all(db) - .await - .unwrap() - } -} - async fn index(State(db): State>, session: Session) -> Markup { let routes = Route::all(&db).await; diff --git a/src/station/mod.rs b/src/station/mod.rs index c70b7e9..9e1ebf5 100644 --- a/src/station/mod.rs +++ b/src/station/mod.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, SqlitePool}; use std::sync::Arc; -mod routes; +mod web; #[derive(FromRow, Debug, Serialize, Deserialize)] pub(crate) struct Station { @@ -130,5 +130,5 @@ impl Station { } pub(super) fn routes() -> Router> { - routes::routes() + web::routes() } diff --git a/src/station/routes.rs b/src/station/web.rs similarity index 100% rename from src/station/routes.rs rename to src/station/web.rs