add planned_trip functionality

This commit is contained in:
philipp 2023-04-04 12:19:56 +02:00
parent 3dfc64071c
commit 3d907487a1
12 changed files with 273 additions and 32 deletions

32
Cargo.lock generated
View File

@ -229,8 +229,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"serde",
"time 0.1.45",
"wasm-bindgen",
"winapi",
]
@ -289,7 +293,7 @@ dependencies = [
"rand",
"sha2",
"subtle",
"time",
"time 0.3.20",
"version_check",
]
@ -716,7 +720,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
@ -1203,7 +1207,7 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.45.0",
]
@ -1732,7 +1736,7 @@ dependencies = [
"serde",
"state",
"tempfile",
"time",
"time 0.3.20",
"tokio",
"tokio-stream",
"tokio-util",
@ -1792,7 +1796,7 @@ dependencies = [
"smallvec",
"stable-pattern",
"state",
"time",
"time 0.3.20",
"tokio",
"uncased",
]
@ -1802,6 +1806,7 @@ name = "rot"
version = "0.1.0"
dependencies = [
"argon2",
"chrono",
"env_logger",
"log",
"rocket",
@ -2237,6 +2242,17 @@ dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "time"
version = "0.3.20"
@ -2618,6 +2634,12 @@ dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"

View File

@ -14,3 +14,5 @@ sqlx = { version = "0.6", features = ["sqlite", "runtime-tokio-rustls", "macros"
argon2 = "0.5"
serde = { version = "1.0", features = [ "derive" ]}
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"]}

View File

@ -17,31 +17,12 @@
# DB
- users
- id: i32
- name: String
- pw: String
- is_cox: bool (false)
- is_admin: bool (false)
- is_guest: bool (true)
- planned_event
- id: i32
- name: String (e.g. "USI")
- planned_amount_cox: i32
- trip_details_id: i32 (trip_details.id)
- allow_guests: bool (false)
- trip
- id: i32
- cox_id: i32 (user.id)
- trip_details: Option<i32> (trip_details.id)
- planned_event_id: Option<i32> (planned_event.id)
- created: chrono::DateTime
- trip_details
- id: i32
- planned_starting_time: String
- max_people: i32
- day: chrono::NaiveDate
- notes: String
- user_trip
- trip_details_id: i32 (trip_details.id)
- user_id: i32 (user.id)
@ -54,7 +35,7 @@
- [x] User passwort zurücksetzen
- [x] Cox + admin + guest setzen
- [ ] Ausfahrten
- [ ] CRUD planned_event
- [ ] CRUD trip_details
- [x] CRUD planned_event
- [x] CRUD trip_details
- [ ] CRUD trip
- [ ] CRUD user_trip

View File

@ -6,3 +6,21 @@ CREATE TABLE IF NOT EXISTS "user" (
"is_admin" boolean NOT NULL DEFAULT FALSE,
"is_guest" boolean NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS "trip_details" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"planned_starting_time" text NOT NULL,
"max_people" INTEGER NOT NULL,
"day" TEXT NOT NULL,
"notes" TEXT
);
CREATE TABLE IF NOT EXISTS "planned_event" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" text NOT NULL,
"planned_amount_cox" INTEGER unsigned NOT NULL,
"allow_guests" boolean NOT NULL default false,
"trip_details_id" INTEGER NOT NULL,
"created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(trip_details_id) REFERENCES trip_details(id) ON DELETE CASCADE
);

View File

@ -1,2 +1,25 @@
use chrono::NaiveDate;
use serde::Serialize;
use sqlx::SqlitePool;
use self::planned_event::PlannedEvent;
pub mod planned_event;
pub mod tripdetails;
pub mod user;
//pub mod users;
#[derive(Serialize)]
pub struct Day {
day: NaiveDate,
planned_events: Vec<PlannedEvent>,
}
impl Day {
pub async fn new(db: &SqlitePool, day: NaiveDate) -> Self {
Self {
day,
planned_events: PlannedEvent::get_for_day(db, day).await,
}
}
}

View File

@ -0,0 +1,55 @@
use chrono::NaiveDate;
use serde::Serialize;
use sqlx::SqlitePool;
#[derive(Serialize)]
pub struct PlannedEvent {
id: i64,
name: String,
planned_amount_cox: i64,
allow_guests: bool,
trip_details_id: i64,
planned_starting_time: String,
max_people: i64,
day: String,
notes: Option<String>,
}
impl PlannedEvent {
pub async fn get_for_day(db: &SqlitePool, day: NaiveDate) -> Vec<Self> {
sqlx::query_as!(
PlannedEvent,
"
SELECT planned_event.id, name, planned_amount_cox, allow_guests, trip_details_id, planned_starting_time, max_people, day, notes
FROM planned_event
INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id
"
)
.fetch_all(db)
.await
.unwrap() //TODO: fixme
}
pub async fn new(
db: &SqlitePool,
name: String,
planned_amount_cox: i32,
allow_guests: bool,
trip_details_id: i64,
) {
sqlx::query!(
"INSERT INTO planned_event(name, planned_amount_cox, allow_guests, trip_details_id) VALUES(?, ?, ?, ?)",
name, planned_amount_cox, allow_guests, trip_details_id
)
.execute(db)
.await
.unwrap(); //TODO: fixme
}
pub async fn delete(db: &SqlitePool, id: i64) {
sqlx::query!("DELETE FROM planned_event WHERE id = ?", id)
.execute(db)
.await
.unwrap(); //TODO: fixme
}
}

33
src/model/tripdetails.rs Normal file
View File

@ -0,0 +1,33 @@
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, SqlitePool};
#[derive(FromRow, Debug, Serialize, Deserialize)]
pub struct TripDetails {
pub id: i64,
planned_starting_time: String,
max_people: i32,
day: String,
notes: Option<String>,
}
impl TripDetails {
pub async fn new(
db: &SqlitePool,
planned_starting_time: String,
max_people: i32,
day: String,
notes: Option<String>,
) -> i64 {
let query = sqlx::query!(
"INSERT INTO trip_details(planned_starting_time, max_people, day, notes) VALUES(?, ?, ?, ?)" ,
planned_starting_time,
max_people,
day,
notes
)
.execute(db)
.await
.unwrap(); //TODO: fixme
query.last_insert_rowid()
}
}

View File

@ -1,9 +1,11 @@
use rocket::Route;
pub mod planned_event;
pub mod user;
pub fn routes() -> Vec<Route> {
let mut ret = Vec::new();
ret.append(&mut user::routes());
ret.append(&mut planned_event::routes());
ret
}

View File

@ -0,0 +1,61 @@
use rocket::{
form::Form,
get, post,
response::{Flash, Redirect},
routes, FromForm, Route, State,
};
use sqlx::SqlitePool;
use crate::model::{planned_event::PlannedEvent, tripdetails::TripDetails, user::AdminUser};
//TODO: add constraints (e.g. planned_amount_cox > 0)
#[derive(FromForm)]
struct AddPlannedEventForm {
day: String,
name: String,
planned_amount_cox: i32,
allow_guests: bool,
planned_starting_time: String,
max_people: i32,
notes: Option<String>,
}
#[post("/planned-event", data = "<data>")]
async fn create(
db: &State<SqlitePool>,
data: Form<AddPlannedEventForm>,
_admin: AdminUser,
) -> Flash<Redirect> {
//TODO: fix clones()
let trip_details_id = TripDetails::new(
db,
data.planned_starting_time.clone(),
data.max_people,
data.day.clone(),
data.notes.clone(),
)
.await;
//TODO: fix clone()
PlannedEvent::new(
db,
data.name.clone(),
data.planned_amount_cox,
data.allow_guests,
trip_details_id,
)
.await;
Flash::success(Redirect::to("/"), "Successfully planned the event")
}
#[get("/planned-event/<id>/delete")]
async fn delete(db: &State<SqlitePool>, id: i64, _admin: AdminUser) -> Flash<Redirect> {
PlannedEvent::delete(db, id).await;
Flash::success(Redirect::to("/"), "Successfully deleted the event")
}
pub fn routes() -> Vec<Route> {
routes![create, delete]
}

View File

@ -38,7 +38,11 @@ struct UserEditForm {
}
#[post("/user", data = "<data>")]
async fn update(db: &State<SqlitePool>, data: Form<UserEditForm>) -> Flash<Redirect> {
async fn update(
db: &State<SqlitePool>,
data: Form<UserEditForm>,
_admin: AdminUser,
) -> Flash<Redirect> {
let user = User::find_by_id(db, data.id).await;
let user = match user {
Ok(user) => user,

View File

@ -1,15 +1,21 @@
use rocket::{catch, catchers, get, response::Redirect, routes, Build, Rocket};
use chrono::{Duration, Local, NaiveDate};
use rocket::{catch, catchers, get, response::Redirect, routes, Build, Rocket, State};
use rocket_dyn_templates::{context, Template};
use sqlx::SqlitePool;
use crate::model::user::User;
use crate::model::{user::User, Day};
mod admin;
mod auth;
#[get("/")]
fn index(user: User) -> Template {
Template::render("index", context! {loggedin_user: user})
async fn index(db: &State<SqlitePool>, user: User) -> Template {
let mut days = Vec::new();
for i in 0..6 {
let date = (Local::now() + Duration::days(i)).date_naive();
days.push(Day::new(db, date).await);
}
Template::render("index", context! {loggedin_user: user, days})
}
#[catch(401)] //unauthorized

View File

@ -27,6 +27,40 @@
{% endif %}
{% endif %}
<h1>Hi</h1>
<h1>Ausfahrten</h1>
{% for day in days %}
<h2>{{ day.day }}</h2>
{% for planned_event in day.planned_events %}
<h3>Planned event '{{ planned_event.name }}'</h3>
Planned amount cox: {{ planned_event.planned_amount_cox }}<br />
Allow guests: {{ planned_event.allow_guests }}<br />
Planned starting time: {{ planned_event.planned_starting_time }}<br />
Max people: {{ planned_event.max_people }}<br />
Notes: {{ planned_event.notes }}<br />
{% if loggedin_user.is_admin %}
<a href="/admin/planned-event/{{ planned_event.id }}/delete">DELETE</a>
{% endif %}
{% endfor %}
{% if loggedin_user.is_admin %}
<h3>Add planned event</h3>
<form action="/admin/planned-event" method="post">
<input type="hidden" name="day" value="{{ day.day }}" />
<input type="text" name="name" placeholder="name (e.g. USI)" />
<input type="number" name="planned_amount_cox" placeholder="Anzahl Steuerleute" />
Gäste <input type="checkbox" name="allow_guests" />
<input type="text" name="planned_starting_time" placeholder="Startzeit" />
<input type="number" name="max_people" placeholder="Anzahl Ruderer" />
<input type="text" name="notes" placeholder="Anmerkungen" />
<input type="submit" />
</form>
{% endif %}
<hr/>
{% endfor %}
{% endblock content %}