diff --git a/db.sqlite b/db.sqlite index aaa52b8..d8a30b1 100644 Binary files a/db.sqlite and b/db.sqlite differ diff --git a/migration/src/lib.rs b/migration/src/lib.rs index cf8c035..1d4dcba 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -1,12 +1,18 @@ pub use sea_orm_migration::prelude::*; mod m20230208_114547_create_day; +mod m20230209_063357_create_user; +mod m20230209_074936_create_trip; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { - vec![Box::new(m20230208_114547_create_day::Migration)] + vec![ + Box::new(m20230208_114547_create_day::Migration), + Box::new(m20230209_063357_create_user::Migration), + Box::new(m20230209_074936_create_trip::Migration), + ] } } diff --git a/migration/src/m20230208_114547_create_day.rs b/migration/src/m20230208_114547_create_day.rs index 579d582..d39e642 100644 --- a/migration/src/m20230208_114547_create_day.rs +++ b/migration/src/m20230208_114547_create_day.rs @@ -12,7 +12,12 @@ impl MigrationTrait for Migration { .table(Day::Table) .if_not_exists() .col(ColumnDef::new(Day::Day).date().not_null().primary_key()) - .col(ColumnDef::new(Day::PlannedAmountCox).integer().default(0)) + .col( + ColumnDef::new(Day::PlannedAmountCox) + .not_null() + .integer() + .default(0), + ) .col( ColumnDef::new(Day::PlannedStartingTime) .string() @@ -38,7 +43,7 @@ impl MigrationTrait for Migration { /// Learn more at https://docs.rs/sea-query#iden #[derive(Iden)] -enum Day { +pub enum Day { Table, Day, PlannedAmountCox, diff --git a/migration/src/m20230209_063357_create_user.rs b/migration/src/m20230209_063357_create_user.rs new file mode 100644 index 0000000..cd6018a --- /dev/null +++ b/migration/src/m20230209_063357_create_user.rs @@ -0,0 +1,54 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(User::Table) + .if_not_exists() + .col( + ColumnDef::new(User::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(User::Name).string().not_null().unique_key()) + .col( + ColumnDef::new(User::IsCox) + .boolean() + .not_null() + .default(false), + ) + .col( + ColumnDef::new(User::IsAdmin) + .boolean() + .not_null() + .default(false), + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(User::Table).to_owned()) + .await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum User { + Table, + Id, + Name, + IsCox, + IsAdmin, +} diff --git a/migration/src/m20230209_074936_create_trip.rs b/migration/src/m20230209_074936_create_trip.rs new file mode 100644 index 0000000..00684c5 --- /dev/null +++ b/migration/src/m20230209_074936_create_trip.rs @@ -0,0 +1,67 @@ +use sea_orm_migration::prelude::*; + +use crate::m20230208_114547_create_day::Day; +use crate::m20230209_063357_create_user::User; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Trip::Table) + .if_not_exists() + .col(ColumnDef::new(Trip::Day).date().not_null()) + .foreign_key( + ForeignKey::create() + .name("FK_day") + .from(Trip::Table, Trip::Day) + .to(Day::Table, Day::Day), + ) + .col(ColumnDef::new(Trip::UserId).integer().not_null()) + .foreign_key( + ForeignKey::create() + .name("FK_userid") + .from(Trip::Table, Trip::UserId) + .to(User::Table, User::Id), + ) + .col(ColumnDef::new(Trip::CoxId).integer()) + .foreign_key( + ForeignKey::create() + .name("FK_coxid") + .from(Trip::Table, Trip::CoxId) + .to(User::Table, User::Id), + ) + .col(ColumnDef::new(Trip::Begin).string()) + .col( + ColumnDef::new(Trip::Created) + .timestamp() + .not_null() + .default("CURRENT_TIMESTAMP"), + ) + .primary_key(Index::create().col(Trip::Day).col(Trip::UserId)) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Trip::Table).to_owned()) + .await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +enum Trip { + Table, + Day, + UserId, + CoxId, + Begin, + Created, +} diff --git a/mod.rs b/mod.rs index 0f074bd..35c7ee8 100644 --- a/mod.rs +++ b/mod.rs @@ -3,3 +3,5 @@ pub mod prelude; pub mod day; +pub mod trip; +pub mod user; diff --git a/prelude.rs b/prelude.rs index 3330adb..800e07f 100644 --- a/prelude.rs +++ b/prelude.rs @@ -1,3 +1,5 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.0 pub use super::day::Entity as Day; +pub use super::trip::Entity as Trip; +pub use super::user::Entity as User; diff --git a/src/main.rs b/src/main.rs index 8b0b6e7..2a7721c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,23 +4,92 @@ extern crate rocket; mod models; use std::collections::HashMap; +use std::ops::Deref; use chrono::Duration; use chrono::Local; +use chrono::NaiveDate; +use rocket::fairing::AdHoc; +use rocket::form; +use rocket::form::ValueField; +use rocket::http::Cookie; +use rocket::http::CookieJar; +use rocket::request; +use rocket::request::FromRequest; +use rocket::request::Outcome; use rocket::response::Redirect; +use rocket::Request; use rocket::{form::Form, fs::FileServer, State}; use rocket_dyn_templates::context; use rocket_dyn_templates::Template; use sea_orm::ColumnTrait; +use sea_orm::ModelTrait; use sea_orm::QueryFilter; use sea_orm::{ ActiveModelTrait, Database, DatabaseConnection, EntityTrait, Order, QueryOrder, Set, }; +use serde::Serialize; -use crate::models::day; +use crate::models::{day, trip, user}; + +#[derive(Serialize, Debug)] +struct TripWithUser { + trip: trip::Model, + user: user::Model, +} + +impl TripWithUser { + async fn new(trip: trip::Model, db: &DatabaseConnection) -> Self { + Self { + trip: trip.clone(), + user: trip + .find_related(user::Entity) + .one(db) + .await + .unwrap() + .unwrap(), + } + } +} + +#[derive(Serialize, Debug)] +struct DayWithTrips { + day: day::Model, + trips: Vec, +} + +impl DayWithTrips { + async fn new(day: day::Model, db: &DatabaseConnection) -> Self { + let mut trips = Vec::new(); + for trip in day.find_related(trip::Entity).all(db).await.unwrap() { + trips.push(TripWithUser::new(trip, db).await); + } + + Self { day, trips } + } +} + +#[derive(Serialize)] +struct Name(user::Model); +#[rocket::async_trait] +impl<'r> FromRequest<'r> for Name { + type Error = rocket::Error; + + async fn from_request(req: &'r Request<'_>) -> request::Outcome { + match req.cookies().get("name") { + Some(name) => { + let db = req.guard::<&'r State>(); + let name = name.value(); + let user = find_or_create_user(name, db.await.unwrap().inner()).await; + Outcome::Success(Name(user)) + } + None => Outcome::Forward(()), //No cookie set + } + } +} #[get("/")] -async fn index(db: &State) -> Template { +async fn index(db: &State, name: Name) -> Template { let days: Vec = day::Entity::find() .filter(day::Column::Day.gte(format!("{}", Local::now().format("%Y-%m-%d")))) // don't show stuff from the past .order_by(day::Column::Day, Order::Asc) @@ -28,38 +97,76 @@ async fn index(db: &State) -> Template { .await .unwrap(); - let days: HashMap = - days.into_iter().map(|x| (x.day, x)).collect(); + let mut dwu = HashMap::new(); + for day in days { + dwu.insert( + format!("{}", day.day.format("%Y-%m-%d")), + DayWithTrips::new(day, db.inner()).await, + ); + } let mut next_days = Vec::new(); for i in 0..6 { next_days.push(Local::now() + Duration::days(i)); } - println!("{:?}", next_days); + Template::render("index", context! { dwu, next_days, name }) +} - Template::render("index", context! { days, next_days }) +#[get("/", rank = 2)] +fn name() -> Template { + Template::render("name", context! {}) +} + +#[derive(FromForm)] +struct NameForm(String); + +#[post("/setname", data = "")] +fn setname(cookies: &CookieJar<'_>, name: Form) -> Redirect { + cookies.add(Cookie::new("name", name.0.clone())); + Redirect::to("/") +} + +#[derive(Debug)] +struct NaiveDateForm(NaiveDate); + +impl<'v> rocket::form::FromFormField<'v> for NaiveDateForm { + fn from_value(field: ValueField<'v>) -> form::Result<'v, NaiveDateForm> { + let naivedate = chrono::NaiveDate::parse_from_str(&field.value, "%Y-%m-%d").unwrap(); //TODO: + //fixme + Ok(NaiveDateForm(naivedate)) + } +} + +impl Deref for NaiveDateForm { + type Target = NaiveDate; + fn deref(&self) -> &Self::Target { + &self.0 + } } #[derive(FromForm, Debug)] struct DayForm { - day: String, - planned_amount_cox: Option, + day: NaiveDateForm, + #[field(validate = range(0..20))] + planned_amount_cox: i32, planned_starting_time: Option, open_registration: bool, } #[put("/day", data = "")] async fn create(db: &State, day: Form) -> Redirect { - let id = chrono::NaiveDate::parse_from_str(&day.day, "%Y-%m-%d").unwrap(); let new_day = day::ActiveModel { - day: Set(id), + day: Set(*day.day), planned_amount_cox: Set(day.planned_amount_cox), planned_starting_time: Set(day.planned_starting_time.clone()), open_registration: Set(day.open_registration), }; - let day: Option = day::Entity::find_by_id(id).one(db.inner()).await.unwrap(); + let day: Option = day::Entity::find_by_id(*day.day) + .one(db.inner()) + .await + .unwrap(); match day { Some(_) => { new_day.update(db.inner()).await.unwrap(); //TODO: fixme @@ -72,13 +179,59 @@ async fn create(db: &State, day: Form) -> Redirect Redirect::to("/") } +#[derive(FromForm)] +struct RegisterForm { + day: NaiveDateForm, + name: String, +} + +async fn find_or_create_user(name: &str, db: &DatabaseConnection) -> user::Model { + let user = user::Entity::find() + .filter(user::Column::Name.eq(name)) + .one(db) + .await + .unwrap(); + + match user { + Some(user) => user, + None => { + let user = user::ActiveModel { + name: Set(name.clone().into()), + ..Default::default() + }; + user.insert(db).await.unwrap() + } + } +} + +#[put("/register", data = "")] +async fn register(db: &State, register: Form) -> Redirect { + let day = day::Entity::find_by_id(*register.day) + .one(db.inner()) + .await + .unwrap() + .expect("There's no trip on this date (yet)"); + + let user = find_or_create_user(®ister.name, db.inner()).await; + + let day = format!("{}", day.day.format("%Y-%m-%d")); + let trip = trip::ActiveModel { + day: Set(day), + user_id: Set(user.id), + ..Default::default() + }; + trip.insert(db.inner()).await.unwrap(); + + Redirect::to("/") +} + #[launch] async fn rocket() -> _ { rocket::build() .attach(Template::fairing()) .manage(Database::connect("sqlite://db.sqlite").await.unwrap()) .mount("/public", FileServer::from("static/")) - .mount("/", routes![index, create]) + .mount("/", routes![index, create, register, name, setname]) } //#[tokio::main] diff --git a/src/models/day.rs b/src/models/day.rs index 363514e..b48aa40 100644 --- a/src/models/day.rs +++ b/src/models/day.rs @@ -8,12 +8,21 @@ use serde::{Deserialize, Serialize}; pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub day: chrono::NaiveDate, - pub planned_amount_cox: Option, + pub planned_amount_cox: i32, pub planned_starting_time: Option, pub open_registration: bool, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} +pub enum Relation { + #[sea_orm(has_many = "super::trip::Entity")] + Trip, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Trip.def() + } +} impl ActiveModelBehavior for ActiveModel {} diff --git a/src/models/mod.rs b/src/models/mod.rs index 0f074bd..35c7ee8 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -3,3 +3,5 @@ pub mod prelude; pub mod day; +pub mod trip; +pub mod user; diff --git a/src/models/prelude.rs b/src/models/prelude.rs index 3330adb..800e07f 100644 --- a/src/models/prelude.rs +++ b/src/models/prelude.rs @@ -1,3 +1,5 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.0 pub use super::day::Entity as Day; +pub use super::trip::Entity as Trip; +pub use super::user::Entity as User; diff --git a/src/models/trip.rs b/src/models/trip.rs new file mode 100644 index 0000000..84ab78f --- /dev/null +++ b/src/models/trip.rs @@ -0,0 +1,58 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.0 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "trip")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub day: String, + #[sea_orm(primary_key, auto_increment = false)] + pub user_id: i32, + pub cox_id: Option, + pub begin: Option, + pub created: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::day::Entity", + from = "Column::Day", + to = "super::day::Column::Day", + on_update = "NoAction", + on_delete = "NoAction" + )] + Day, + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::CoxId", + to = "super::user::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + Cox, + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::UserId", + to = "super::user::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Day.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/models/user.rs b/src/models/user.rs new file mode 100644 index 0000000..4a44287 --- /dev/null +++ b/src/models/user.rs @@ -0,0 +1,19 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.0 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "user")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub name: String, + pub is_cox: bool, + pub is_admin: bool, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/templates/index.html.tera b/templates/index.html.tera index d6eaa7e..17782b0 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -6,12 +6,42 @@ {{ day | date(format="%d.%m.%Y")}}
- {% if days[day_string] and days[day_string].planned_amount_cox > 0%} - {% set cur_day = days[day_string] %} + {% if dwu[day_string] and dwu[day_string].day.planned_amount_cox > 0%} + {% set cur_day = dwu[day_string].day %} + {% set_global already_registered = false %} Geplante Steuerpersonen: {{ cur_day.planned_amount_cox}}
Geplante Abfahrtszeit: {{ cur_day.planned_starting_time }}
+ + Angemeldete Personen: +
    + {% for trip in dwu[day_string].trips %} +
  1. + {% if trip.user.name == name.name %} + {% set_global already_registered = true %} + DU + {% else %} + {{ trip.user.name }} + {% endif %} + {% endfor %} +
+ {% if cur_day.open_registration %} - ANMELDEN +
+ + +
+ + +
+
+ + +
+
+ +
+
+
+
{% else %} Anmeldung an diesem Tag leider nicht möglich (zB bei USI Kursen) {% endif %} diff --git a/templates/name.html.tera b/templates/name.html.tera new file mode 100644 index 0000000..52ae584 --- /dev/null +++ b/templates/name.html.tera @@ -0,0 +1,10 @@ +{% extends "base" %} +{% block content %} + +What's your name? +
+ + +
+{% endblock content %} + diff --git a/templates/registration.html.tera b/templates/registration.html.tera new file mode 100644 index 0000000..6d7dcad --- /dev/null +++ b/templates/registration.html.tera @@ -0,0 +1,7 @@ +
+ + +
+ + +
+