feat: creation and display of availabillies work
This commit is contained in:
parent
497e6fbf6c
commit
8840f0ab48
30
Cargo.lock
generated
30
Cargo.lock
generated
@ -648,6 +648,7 @@ dependencies = [
|
||||
"askama_actix",
|
||||
"chrono",
|
||||
"dotenv",
|
||||
"futures-util",
|
||||
"serde",
|
||||
"sqlx",
|
||||
]
|
||||
@ -995,9 +996,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.28"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-intrusive"
|
||||
@ -1031,6 +1032,17 @@ dependencies = [
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-rustls"
|
||||
version = "0.22.2"
|
||||
@ -1044,27 +1056,29 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.28"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
|
||||
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.28"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
|
||||
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.28"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
|
||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -18,3 +18,4 @@ actix-identity = "0.5.2"
|
||||
chrono = { version = "0.4.33", features = ["serde"] }
|
||||
actix-files = "0.6.5"
|
||||
askama_actix = "0.14.0"
|
||||
futures-util = "0.3.30"
|
||||
|
@ -37,7 +37,8 @@ CREATE TABLE availabillity
|
||||
userId INTEGER NOT NULL REFERENCES user_ (id),
|
||||
date DATE NOT NULL,
|
||||
startTime TIME,
|
||||
endTime TIME
|
||||
endTime TIME,
|
||||
comment TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE event
|
||||
|
@ -1,4 +1,5 @@
|
||||
pub mod routes;
|
||||
pub mod utils;
|
||||
pub mod redirect;
|
||||
|
||||
pub use routes::init;
|
||||
|
70
src/auth/redirect.rs
Normal file
70
src/auth/redirect.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use std::future::{ready, Ready};
|
||||
|
||||
use actix_identity::IdentityExt;
|
||||
use actix_web::{
|
||||
body::EitherBody,
|
||||
dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
|
||||
http, Error, HttpResponse,
|
||||
};
|
||||
use futures_util::future::LocalBoxFuture;
|
||||
|
||||
pub struct CheckLogin;
|
||||
|
||||
impl<S, B> Transform<S, ServiceRequest> for CheckLogin
|
||||
where
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
S::Future: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Response = ServiceResponse<EitherBody<B>>;
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Transform = CheckLoginMiddleware<S>;
|
||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
ready(Ok(CheckLoginMiddleware { service }))
|
||||
}
|
||||
}
|
||||
pub struct CheckLoginMiddleware<S> {
|
||||
service: S,
|
||||
}
|
||||
|
||||
impl<S, B> Service<ServiceRequest> for CheckLoginMiddleware<S>
|
||||
where
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
S::Future: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Response = ServiceResponse<EitherBody<B>>;
|
||||
type Error = Error;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
dev::forward_ready!(service);
|
||||
|
||||
fn call(&self, request: ServiceRequest) -> Self::Future {
|
||||
// Change this to see the change in outcome in the browser.
|
||||
// Usually this boolean would be acquired from a password check or other auth verification.
|
||||
let is_logged_in = request.get_identity().is_ok();
|
||||
|
||||
// Don't forward to `/login` if we are already on `/login`.
|
||||
if !is_logged_in && request.path() != "/login" && !request.path().starts_with("/static") {
|
||||
let (request, _pl) = request.into_parts();
|
||||
|
||||
let response = HttpResponse::Found()
|
||||
.insert_header((http::header::LOCATION, "/login"))
|
||||
.finish()
|
||||
// constructed responses map to "right" body
|
||||
.map_into_right_body();
|
||||
|
||||
return Box::pin(async { Ok(ServiceResponse::new(request, response)) });
|
||||
}
|
||||
|
||||
let res = self.service.call(request);
|
||||
|
||||
Box::pin(async move {
|
||||
// forwarded responses map to "left" body
|
||||
res.await.map(ServiceResponse::map_into_left_body)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
pub mod routes;
|
||||
mod get_availabillity_new;
|
||||
mod post_availabillity;
|
||||
|
||||
pub use routes::init;
|
||||
|
34
src/calendar/post_availabillity.rs
Normal file
34
src/calendar/post_availabillity.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||
use chrono::{NaiveDate, NaiveTime};
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::models::{availabillity::Availabillity, user::User};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AvailabillityForm {
|
||||
date: NaiveDate,
|
||||
start_time: Option<NaiveTime>,
|
||||
end_time: Option<NaiveTime>,
|
||||
comment: Option<String>
|
||||
}
|
||||
|
||||
#[actix_web::post("/availabillity/new")]
|
||||
pub async fn post_availabillity(
|
||||
user: Identity,
|
||||
pool: web::Data<PgPool>,
|
||||
form: web::Form<AvailabillityForm>,
|
||||
) -> impl Responder {
|
||||
let current_user = User::read_by_id(pool.as_ref(), user.id().unwrap().parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if let Ok(_) = Availabillity::create(pool.get_ref(), current_user.id, form.date, form.start_time, form.end_time, form.comment.clone()).await {
|
||||
HttpResponse::Found()
|
||||
.insert_header((LOCATION, "/"))
|
||||
.finish()
|
||||
} else {
|
||||
HttpResponse::BadRequest().body("Fehler beim erstellen")
|
||||
}
|
||||
}
|
@ -9,12 +9,12 @@ use crate::models::{
|
||||
area::Area, availabillity::Availabillity, event::Event, role::Role, user::User,
|
||||
};
|
||||
|
||||
use super::get_availabillity_new::get_availabillity_new;
|
||||
|
||||
use super::{get_availabillity_new::get_availabillity_new, post_availabillity::post_availabillity};
|
||||
|
||||
pub fn init(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(get_index);
|
||||
cfg.service(get_availabillity_new);
|
||||
cfg.service(post_availabillity);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -34,38 +34,36 @@ struct CalendarTemplate {
|
||||
|
||||
#[actix_web::get("/")]
|
||||
async fn get_index(
|
||||
user: Option<Identity>,
|
||||
user: Identity,
|
||||
pool: web::Data<PgPool>,
|
||||
query: web::Query<CalendarQuery>,
|
||||
) -> impl Responder {
|
||||
if let Some(user) = user {
|
||||
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
let date = match query.date {
|
||||
Some(given_date) => given_date,
|
||||
None => Utc::now().date_naive(),
|
||||
};
|
||||
let area = Area::read_by_id(pool.get_ref(), current_user.area_id)
|
||||
.await
|
||||
.unwrap();
|
||||
let events = Event::read_by_date(pool.get_ref(), date).await.unwrap();
|
||||
let availabillities = Availabillity::read_by_date(pool.get_ref(), date)
|
||||
.await
|
||||
.unwrap();
|
||||
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
let date = match query.date {
|
||||
Some(given_date) => given_date,
|
||||
None => Utc::now().date_naive(),
|
||||
};
|
||||
let area = Area::read_by_id(pool.get_ref(), current_user.area_id)
|
||||
.await
|
||||
.unwrap();
|
||||
let events = Event::read_by_date(pool.get_ref(), date).await.unwrap();
|
||||
let mut availabillities = Availabillity::read_by_date(pool.get_ref(), date)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let template = CalendarTemplate {
|
||||
user_role: current_user.role,
|
||||
date,
|
||||
area,
|
||||
events,
|
||||
availabillities,
|
||||
};
|
||||
|
||||
HttpResponse::Ok().body(template.render().unwrap())
|
||||
} else {
|
||||
HttpResponse::PermanentRedirect()
|
||||
.insert_header((LOCATION, "/login"))
|
||||
.finish()
|
||||
for avl in availabillities.iter_mut() {
|
||||
avl.load_user(pool.get_ref()).await.unwrap()
|
||||
}
|
||||
|
||||
let template = CalendarTemplate {
|
||||
user_role: current_user.role,
|
||||
date,
|
||||
area,
|
||||
events,
|
||||
availabillities,
|
||||
};
|
||||
|
||||
HttpResponse::Ok().body(template.render().unwrap())
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use actix_web::{web, App, HttpServer};
|
||||
use dotenv::dotenv;
|
||||
use sqlx::postgres::PgPool;
|
||||
|
||||
use crate::auth::redirect;
|
||||
mod auth;
|
||||
mod calendar;
|
||||
mod models;
|
||||
@ -24,12 +25,13 @@ async fn main() -> anyhow::Result<()> {
|
||||
.app_data(web::Data::new(pool.clone()))
|
||||
.configure(auth::init)
|
||||
.configure(calendar::init)
|
||||
.wrap(redirect::CheckLogin)
|
||||
.wrap(IdentityMiddleware::default())
|
||||
.wrap(SessionMiddleware::new(
|
||||
CookieSessionStore::default(),
|
||||
secret_key.clone(),
|
||||
))
|
||||
.service(actix_files::Files::new("", "./static").show_files_listing())
|
||||
.service(actix_files::Files::new("/static", "./static").show_files_listing())
|
||||
})
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
.run()
|
||||
|
@ -1,25 +1,41 @@
|
||||
use chrono::{NaiveDate, NaiveTime};
|
||||
use sqlx::{query, PgPool};
|
||||
|
||||
use super::user::User;
|
||||
|
||||
pub struct Availabillity {
|
||||
pub id: i32,
|
||||
pub user_id: i32,
|
||||
pub user: Option<User>,
|
||||
pub date: NaiveDate,
|
||||
pub start_time: Option<NaiveTime>,
|
||||
pub end_time: Option<NaiveTime>
|
||||
pub end_time: Option<NaiveTime>,
|
||||
pub comment: Option<String>,
|
||||
}
|
||||
|
||||
impl Availabillity {
|
||||
pub async fn create(pool: &PgPool, user_id: i32, date: NaiveDate, start_time: Option<NaiveTime>, end_time: Option<NaiveTime>) -> anyhow::Result<i32> {
|
||||
let mut result = match (start_time, end_time) {
|
||||
(Some(start_time), Some(end_time)) => query!("INSERT INTO availabillity (userId, date, startTime, endTime) VALUES ($1, $2, $3, $4) RETURNING id;", user_id, date, start_time, end_time).fetch_one(pool).await?.id,
|
||||
(_, _) => query!("INSERT INTO availabillity (userId, date) VALUES ($1, $2) RETURNING id;", user_id, date).fetch_one(pool).await?.id
|
||||
pub async fn create(
|
||||
pool: &PgPool,
|
||||
user_id: i32,
|
||||
date: NaiveDate,
|
||||
start_time: Option<NaiveTime>,
|
||||
end_time: Option<NaiveTime>,
|
||||
comment: Option<String>,
|
||||
) -> anyhow::Result<i32> {
|
||||
let result = match (start_time, end_time, comment) {
|
||||
(Some(start_time), Some(end_time), Some(comment)) => query!("INSERT INTO availabillity (userId, date, startTime, endTime, comment) VALUES ($1, $2, $3, $4, $5) RETURNING id;", user_id, date, start_time, end_time, comment).fetch_one(pool).await?.id,
|
||||
(Some(start_time), Some(end_time), None) => query!("INSERT INTO availabillity (userId, date, startTime, endTime) VALUES ($1, $2, $3, $4) RETURNING id;", user_id, date, start_time, end_time).fetch_one(pool).await?.id,
|
||||
(None, None, Some(comment)) => query!("INSERT INTO availabillity (userId, date, comment) VALUES ($1, $2, $3) RETURNING id;", user_id, date, comment).fetch_one(pool).await?.id,
|
||||
(_, _, _) => query!("INSERT INTO availabillity (userId, date) VALUES ($1, $2) RETURNING id;", user_id, date).fetch_one(pool).await?.id
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn read_by_date(pool: &PgPool, date: NaiveDate) -> anyhow::Result<Vec<Availabillity>> {
|
||||
pub async fn read_by_date(
|
||||
pool: &PgPool,
|
||||
date: NaiveDate,
|
||||
) -> anyhow::Result<Vec<Availabillity>> {
|
||||
let records = query!("SELECT * FROM availabillity WHERE date = $1", date)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
@ -29,12 +45,21 @@ impl Availabillity {
|
||||
.map(|a| Availabillity {
|
||||
id: a.id,
|
||||
user_id: a.userid,
|
||||
user: None,
|
||||
date: a.date,
|
||||
start_time: a.starttime,
|
||||
end_time: a.endtime
|
||||
end_time: a.endtime,
|
||||
comment: a.comment.clone(),
|
||||
})
|
||||
.collect();
|
||||
.collect();
|
||||
|
||||
Ok(availabillities)
|
||||
}
|
||||
|
||||
pub async fn load_user(&mut self, pool: &PgPool) -> anyhow::Result<()> {
|
||||
let user = User::read_by_id(pool, self.user_id).await?;
|
||||
self.user = Some(user);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,21 @@
|
||||
#[derive(sqlx::Type, Debug)]
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(sqlx::Type, Debug, Clone)]
|
||||
#[sqlx(type_name = "function", rename_all = "lowercase")]
|
||||
pub enum Function {
|
||||
Posten = 1,
|
||||
Wachhabender = 10,
|
||||
}
|
||||
|
||||
impl Display for Function {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Function::Posten => write!(f, "Posten"),
|
||||
Function::Wachhabender => write!(f, "Wachhabender")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Function {
|
||||
type Error = ();
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#[derive(sqlx::Type, Debug)]
|
||||
#[derive(sqlx::Type, Debug, Clone)]
|
||||
#[sqlx(type_name = "role", rename_all = "lowercase")]
|
||||
pub enum Role {
|
||||
Staff = 1,
|
||||
|
@ -3,6 +3,7 @@ use sqlx::PgPool;
|
||||
|
||||
use super::{function::Function, role::Role};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct User {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
@ -51,7 +52,7 @@ impl User {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read_by_id(pool: &PgPool, id: i32) -> Option<User> {
|
||||
pub async fn read_by_id(pool: &PgPool, id: i32) -> anyhow::Result<User> {
|
||||
let record = sqlx::query!(
|
||||
r#"
|
||||
SELECT id,
|
||||
@ -71,10 +72,9 @@ impl User {
|
||||
id,
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await;
|
||||
.await?;
|
||||
|
||||
match record {
|
||||
Ok(record) => Some(User {
|
||||
let user = User {
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
email: record.email,
|
||||
@ -86,12 +86,9 @@ impl User {
|
||||
locked: record.locked,
|
||||
last_login: record.lastlogin,
|
||||
receive_notifications: record.receivenotifications,
|
||||
}),
|
||||
Err(err) => {
|
||||
println!("User.read({id}): {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub async fn read_for_login(pool: &PgPool, email: &str) -> anyhow::Result<Option<User>> {
|
||||
|
@ -3,29 +3,14 @@
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<form>
|
||||
<form method="post" action="/availabillity/new">
|
||||
<h1 class="title">Neue Vefügbarkeit für den {{ date.format("%d.%m.%Y") }}</h1>
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">Funktion</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select name="function">
|
||||
<option value="1">Posten</option>
|
||||
<option value="10">Wachhabender</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="date" value="{{ date }}">
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">Zeitangabe</label>
|
||||
<label class="label">Dauer</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
@ -56,6 +41,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">Kommentar</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<textarea class="textarea" placeholder="nur Posten, nur Wachhabender, etc.."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label"></div>
|
||||
<div class="field-body">
|
||||
@ -69,6 +67,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Brass - Brasiwa Leipzig</title>
|
||||
<link rel="stylesheet" href="/bulma.css">
|
||||
<link rel="stylesheet" href="/static/bulma.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -57,14 +57,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if events.len() == 0 %}
|
||||
{% if availabillities.len() == 0 %}
|
||||
<div class="box">
|
||||
<h5 class="title is-5">keine Verfügbarkeiten eingetragen</h5>
|
||||
</div>
|
||||
{% else %}
|
||||
{% for availabillity in availabillities %}
|
||||
{% let user = availabillity.user.as_ref().unwrap() %}
|
||||
|
||||
<div class="box">
|
||||
<h5 class="title is-5">{{ availabillity.user_id }}</h5>
|
||||
<p>{{ user.name }}</p>
|
||||
<p>{{ user.function }}</p>
|
||||
{% if availabillity.start_time.is_some() && availabillity.end_time.is_some() %}
|
||||
<p>{{ availabillity.start_time.as_ref().unwrap() }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
Loading…
x
Reference in New Issue
Block a user