This commit is contained in:
philipp 2023-02-09 12:16:04 +01:00
parent 1934fef0dd
commit 5200150828
16 changed files with 446 additions and 20 deletions

BIN
db.sqlite

Binary file not shown.

View File

@ -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<Box<dyn MigrationTrait>> {
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),
]
}
}

View File

@ -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,

View File

@ -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,
}

View File

@ -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,
}

2
mod.rs
View File

@ -3,3 +3,5 @@
pub mod prelude;
pub mod day;
pub mod trip;
pub mod user;

View File

@ -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;

View File

@ -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<TripWithUser>,
}
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<Self, Self::Error> {
match req.cookies().get("name") {
Some(name) => {
let db = req.guard::<&'r State<DatabaseConnection>>();
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<DatabaseConnection>) -> Template {
async fn index(db: &State<DatabaseConnection>, name: Name) -> Template {
let days: Vec<day::Model> = 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<DatabaseConnection>) -> Template {
.await
.unwrap();
let days: HashMap<chrono::NaiveDate, day::Model> =
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 = "<name>")]
fn setname(cookies: &CookieJar<'_>, name: Form<NameForm>) -> 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<i32>,
day: NaiveDateForm,
#[field(validate = range(0..20))]
planned_amount_cox: i32,
planned_starting_time: Option<String>,
open_registration: bool,
}
#[put("/day", data = "<day>")]
async fn create(db: &State<DatabaseConnection>, day: Form<DayForm>) -> 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::Model> = day::Entity::find_by_id(id).one(db.inner()).await.unwrap();
let day: Option<day::Model> = 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<DatabaseConnection>, day: Form<DayForm>) -> 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 = "<register>")]
async fn register(db: &State<DatabaseConnection>, register: Form<RegisterForm>) -> 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(&register.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]

View File

@ -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<i32>,
pub planned_amount_cox: i32,
pub planned_starting_time: Option<String>,
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<super::trip::Entity> for Entity {
fn to() -> RelationDef {
Relation::Trip.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -3,3 +3,5 @@
pub mod prelude;
pub mod day;
pub mod trip;
pub mod user;

View File

@ -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;

58
src/models/trip.rs Normal file
View File

@ -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<i32>,
pub begin: Option<String>,
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<super::day::Entity> for Entity {
fn to() -> RelationDef {
Relation::Day.def()
}
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

19
src/models/user.rs Normal file
View File

@ -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 {}

View File

@ -6,12 +6,42 @@
{{ day | date(format="%d.%m.%Y")}}
<br />
{% 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}}<br />
Geplante Abfahrtszeit: {{ cur_day.planned_starting_time }}<br />
Angemeldete Personen:
<ol>
{% for trip in dwu[day_string].trips %}
<li>
{% if trip.user.name == name.name %}
{% set_global already_registered = true %}
DU
{% else %}
{{ trip.user.name }}
{% endif %}
{% endfor %}
</ol>
{% if cur_day.open_registration %}
ANMELDEN
<details>
<summary class="button">&plus;</summary>
<form method="post" action="/register">
<input type="hidden" name="_method" value="put" />
<input type="hidden" name="day" value="{{ day_string }}" />
<div class="row">
<div class="six columns">
<label for="name">Name</label>
<input class="u-full-width" type="text" id="name" name="name" value="{% if already_registered == false %}{{ name.name }}{% endif %}" />
</div>
<div class="six columns">
<input class="button-primary" type="submit" value="Speichern">
</div>
</div>
</form>
</details>
{% else %}
Anmeldung an diesem Tag leider nicht möglich (zB bei USI Kursen)
{% endif %}

10
templates/name.html.tera Normal file
View File

@ -0,0 +1,10 @@
{% extends "base" %}
{% block content %}
What's your name?
<form action="/setname" method="post">
<input type="text" name="name"/>
<input type="submit" value="Speichern"/>
</form>
{% endblock content %}

View File

@ -0,0 +1,7 @@
<details>
<summary class="button">&plus;</summary>
<form method="post" action="/register">
<input type="hidden" name="_method" value="put" />
</form>
</details>