work on routes
This commit is contained in:
parent
d054b3340c
commit
72abdf1182
@ -11,7 +11,7 @@ CREATE TABLE station (
|
|||||||
|
|
||||||
CREATE TABLE route (
|
CREATE TABLE route (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
name TEXT NOT NULL -- e.g. 'wiwö'
|
name TEXT NOT NULL UNIQUE -- e.g. 'wiwö'
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE route_station (
|
CREATE TABLE route_station (
|
||||||
@ -29,8 +29,8 @@ CREATE TABLE "group" (
|
|||||||
notes TEXT,
|
notes TEXT,
|
||||||
amount_people INTEGER,
|
amount_people INTEGER,
|
||||||
first_station_id INTEGER NOT NULL,
|
first_station_id INTEGER NOT NULL,
|
||||||
FOREIGN KEY (first_station_id) REFERENCES station(id)
|
|
||||||
route_id INTEGER NOT NULL,
|
route_id INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY (first_station_id) REFERENCES station(id),
|
||||||
FOREIGN KEY (route_id) REFERENCES route(id)
|
FOREIGN KEY (route_id) REFERENCES route(id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ use tokio::net::TcpListener;
|
|||||||
use tower_sessions::{MemoryStore, Session, SessionManagerLayer};
|
use tower_sessions::{MemoryStore, Session, SessionManagerLayer};
|
||||||
|
|
||||||
mod partials;
|
mod partials;
|
||||||
|
mod route;
|
||||||
mod station;
|
mod station;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
@ -81,6 +82,9 @@ async fn index(session: Session) -> Markup {
|
|||||||
a role="button" href="/station" {
|
a role="button" href="/station" {
|
||||||
"Stationen"
|
"Stationen"
|
||||||
}
|
}
|
||||||
|
a role="button" href="/route" {
|
||||||
|
"Routen"
|
||||||
|
}
|
||||||
};
|
};
|
||||||
page(content, session, false).await
|
page(content, session, false).await
|
||||||
}
|
}
|
||||||
@ -93,6 +97,7 @@ pub async fn start(listener: TcpListener, db: SqlitePool) {
|
|||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(index))
|
.route("/", get(index))
|
||||||
.nest("/station", station::routes())
|
.nest("/station", station::routes())
|
||||||
|
.nest("/route", route::routes())
|
||||||
.route("/pico.css", get(serve_pico_css))
|
.route("/pico.css", get(serve_pico_css))
|
||||||
.route("/style.css", get(serve_my_css))
|
.route("/style.css", get(serve_my_css))
|
||||||
.route("/leaflet.css", get(serve_leaflet_css))
|
.route("/leaflet.css", get(serve_leaflet_css))
|
||||||
|
201
src/route.rs
Normal file
201
src/route.rs
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
use crate::{err, page, succ};
|
||||||
|
use axum::{
|
||||||
|
extract::State,
|
||||||
|
response::{IntoResponse, Redirect},
|
||||||
|
routing::{get, post},
|
||||||
|
Form, Router,
|
||||||
|
};
|
||||||
|
use maud::{html, Markup};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::{FromRow, SqlitePool};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tower_sessions::Session;
|
||||||
|
|
||||||
|
#[derive(FromRow, Debug, Serialize, Deserialize)]
|
||||||
|
struct Route {
|
||||||
|
id: i64,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Route {
|
||||||
|
async fn all(db: &SqlitePool) -> Vec<Self> {
|
||||||
|
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<Self> {
|
||||||
|
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 delete(&self, db: &SqlitePool) {
|
||||||
|
sqlx::query!("DELETE FROM route WHERE id = ?", self.id)
|
||||||
|
.execute(db)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
|
||||||
|
let routes = Route::all(&db).await;
|
||||||
|
|
||||||
|
let content = html! {
|
||||||
|
h1 {
|
||||||
|
a href="/" { "↩️" }
|
||||||
|
"Routen"
|
||||||
|
}
|
||||||
|
ol {
|
||||||
|
@for route in &routes{
|
||||||
|
li {
|
||||||
|
a href=(format!("/route/{}", route.id)){
|
||||||
|
(route.name)
|
||||||
|
}
|
||||||
|
a href=(format!("/route/{}/delete", route.id))
|
||||||
|
onclick="return confirm('Bist du sicher, dass die Route gelöscht werden soll? Das kann _NICHT_ mehr rückgängig gemacht werden.');" {
|
||||||
|
"🗑️"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h2 { "Neue Route" }
|
||||||
|
form action="/route" method="post" {
|
||||||
|
fieldset role="group" {
|
||||||
|
input type="text" name="name" placeholder="Routenname" required;
|
||||||
|
input type="submit" value="Neue Route";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
page(content, session, false).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct CreateForm {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create(
|
||||||
|
State(db): State<Arc<SqlitePool>>,
|
||||||
|
session: Session,
|
||||||
|
Form(form): Form<CreateForm>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
match Route::create(&db, &form.name).await {
|
||||||
|
Ok(_) => succ!(session,"Route '{}' erfolgreich erstellt!", form.name),
|
||||||
|
Err(e) => err!(
|
||||||
|
session,
|
||||||
|
"Route '{}' konnte _NICHT_ erstellt werden, da es bereits eine Route mit diesem Namen gibt ({e})!",
|
||||||
|
form.name
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
Redirect::to("/route")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete(
|
||||||
|
State(db): State<Arc<SqlitePool>>,
|
||||||
|
session: Session,
|
||||||
|
axum::extract::Path(id): axum::extract::Path<i64>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let Some(route) = Route::find_by_id(&db, id).await else {
|
||||||
|
err!(
|
||||||
|
session,
|
||||||
|
"Route mit ID {id} konnte nicht gelöscht werden, da sie nicht existiert"
|
||||||
|
);
|
||||||
|
|
||||||
|
return Redirect::to("/route");
|
||||||
|
};
|
||||||
|
|
||||||
|
route.delete(&db).await;
|
||||||
|
|
||||||
|
succ!(session, "Route '{}' erfolgreich gelöscht!", route.name);
|
||||||
|
|
||||||
|
Redirect::to("/route")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn view(
|
||||||
|
State(db): State<Arc<SqlitePool>>,
|
||||||
|
session: Session,
|
||||||
|
axum::extract::Path(id): axum::extract::Path<i64>,
|
||||||
|
) -> Result<Markup, impl IntoResponse> {
|
||||||
|
let Some(route) = Route::find_by_id(&db, id).await else {
|
||||||
|
err!(
|
||||||
|
session,
|
||||||
|
"Route mit ID {id} konnte nicht geöffnet werden, da sie nicht existiert"
|
||||||
|
);
|
||||||
|
|
||||||
|
return Err(Redirect::to("/route"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = html! {
|
||||||
|
h1 {
|
||||||
|
a href="/route" { "↩️" }
|
||||||
|
"Route " (route.name)
|
||||||
|
}
|
||||||
|
details {
|
||||||
|
summary { "Routenname bearbeiten ✏️" }
|
||||||
|
form action=(format!("/route/{}/name", route.id)) method="post" {
|
||||||
|
input type="text" name="name" value=(route.name) required;
|
||||||
|
input type="submit" value="Speichern";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(page(content, session, false).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct UpdateNameForm {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
async fn update_name(
|
||||||
|
State(db): State<Arc<SqlitePool>>,
|
||||||
|
session: Session,
|
||||||
|
axum::extract::Path(id): axum::extract::Path<i64>,
|
||||||
|
Form(form): Form<UpdateNameForm>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let Some(route) = Route::find_by_id(&db, id).await else {
|
||||||
|
err!(
|
||||||
|
session,
|
||||||
|
"Route mit ID {id} konnte nicht geöffnet werden, da sie nicht existiert"
|
||||||
|
);
|
||||||
|
|
||||||
|
return Redirect::to("/route");
|
||||||
|
};
|
||||||
|
|
||||||
|
route.update_name(&db, &form.name).await;
|
||||||
|
|
||||||
|
succ!(
|
||||||
|
session,
|
||||||
|
"Name der Route '{}' wurden erfolgreich bearbeitet!",
|
||||||
|
route.name
|
||||||
|
);
|
||||||
|
|
||||||
|
Redirect::to(&format!("/route/{id}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn routes() -> Router<Arc<SqlitePool>> {
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(index))
|
||||||
|
.route("/", post(create))
|
||||||
|
.route("/{id}/delete", get(delete))
|
||||||
|
.route("/{id}", get(view))
|
||||||
|
.route("/{id}/name", post(update_name))
|
||||||
|
}
|
@ -31,7 +31,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 like ?",
|
"SELECT id, name, notes, amount_people, last_login, pw, lat, lng FROM station WHERE id = ?",
|
||||||
id
|
id
|
||||||
)
|
)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
|
@ -22,13 +22,7 @@ async fn create(
|
|||||||
Form(form): Form<CreateForm>,
|
Form(form): Form<CreateForm>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match Station::create(&db, &form.name).await {
|
match Station::create(&db, &form.name).await {
|
||||||
Ok(_) => session
|
Ok(_) => succ!(session,"Station '{}' erfolgreich erstellt!", form.name),
|
||||||
.insert(
|
|
||||||
"succ",
|
|
||||||
&format!("Station '{}' erfolgreich erstellt!", form.name),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap(),
|
|
||||||
Err(e) => err!(
|
Err(e) => err!(
|
||||||
session,
|
session,
|
||||||
"Station '{}' konnte _NICHT_ erstellt werden, da es bereits eine Station mit diesem Namen gibt ({e})!",
|
"Station '{}' konnte _NICHT_ erstellt werden, da es bereits eine Station mit diesem Namen gibt ({e})!",
|
||||||
@ -402,6 +396,7 @@ async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
|
|||||||
pub(super) fn routes() -> Router<Arc<SqlitePool>> {
|
pub(super) fn routes() -> Router<Arc<SqlitePool>> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(index))
|
.route("/", get(index))
|
||||||
|
.route("/", post(create))
|
||||||
.route("/{id}", get(view))
|
.route("/{id}", get(view))
|
||||||
.route("/{id}/delete", get(delete))
|
.route("/{id}/delete", get(delete))
|
||||||
.route("/{id}/notes", post(update_notes))
|
.route("/{id}/notes", post(update_notes))
|
||||||
@ -409,5 +404,4 @@ pub(super) fn routes() -> Router<Arc<SqlitePool>> {
|
|||||||
.route("/{id}/amount-people-reset", get(update_amount_people_reset))
|
.route("/{id}/amount-people-reset", get(update_amount_people_reset))
|
||||||
.route("/{id}/location", post(update_location))
|
.route("/{id}/location", post(update_location))
|
||||||
.route("/{id}/location-clear", get(update_location_clear))
|
.route("/{id}/location-clear", get(update_location_clear))
|
||||||
.route("/", post(create))
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user