Merge branch 'staging' into 'main'

Staging

See merge request PhilippHofer/rot!30
This commit is contained in:
PhilippHofer 2023-09-05 11:27:30 +00:00
commit 71ec5ce01a
No known key found for this signature in database
11 changed files with 366 additions and 146 deletions

107
Cargo.lock generated
View File

@ -263,9 +263,12 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.79" version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -335,7 +338,7 @@ dependencies = [
"rand", "rand",
"sha2", "sha2",
"subtle", "subtle",
"time 0.3.24", "time 0.3.25",
"version_check", "version_check",
] ]
@ -420,15 +423,15 @@ dependencies = [
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.3.6" version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8810e7e2cf385b1e9b50d68264908ec367ba642c96d02edfe61c39e88e2a3c01" checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929"
[[package]] [[package]]
name = "deunicode" name = "deunicode"
version = "0.4.3" version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" checksum = "d95203a6a50906215a502507c0f879a0ce7ff205a6111e2db2a5ef8e4bb92e43"
[[package]] [[package]]
name = "devise" name = "devise"
@ -563,13 +566,13 @@ dependencies = [
[[package]] [[package]]
name = "filetime" name = "filetime"
version = "0.2.21" version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall 0.2.16", "redox_syscall 0.3.5",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -754,9 +757,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]] [[package]]
name = "globset" name = "globset"
version = "0.4.12" version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aca8bbd8e0707c1887a8bbb7e6b40e228f251ff5d62c8220a4a7a53c73aff006" checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"bstr", "bstr",
@ -1090,9 +1093,9 @@ dependencies = [
[[package]] [[package]]
name = "kqueue" name = "kqueue"
version = "1.0.7" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98" checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
dependencies = [ dependencies = [
"kqueue-sys", "kqueue-sys",
"libc", "libc",
@ -1100,9 +1103,9 @@ dependencies = [
[[package]] [[package]]
name = "kqueue-sys" name = "kqueue-sys"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"libc", "libc",
@ -1139,9 +1142,9 @@ dependencies = [
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.3" version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -1440,9 +1443,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.7.1" version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d2d1d55045829d65aad9d389139882ad623b33b904e7c9f1b10c5b8927298e5" checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a"
dependencies = [ dependencies = [
"thiserror", "thiserror",
"ucd-trie", "ucd-trie",
@ -1450,9 +1453,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_derive" name = "pest_derive"
version = "2.7.1" version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f94bca7e7a599d89dea5dfa309e217e7906c3c007fb9c3299c40b10d6a315d3" checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853"
dependencies = [ dependencies = [
"pest", "pest",
"pest_generator", "pest_generator",
@ -1460,9 +1463,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_generator" name = "pest_generator"
version = "2.7.1" version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d490fe7e8556575ff6911e45567ab95e71617f43781e5c05490dc8d75c965c" checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929"
dependencies = [ dependencies = [
"pest", "pest",
"pest_meta", "pest_meta",
@ -1473,9 +1476,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_meta" name = "pest_meta"
version = "2.7.1" version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2674c66ebb4b4d9036012091b537aae5878970d6999f81a265034d85b136b341" checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"pest", "pest",
@ -1523,18 +1526,18 @@ dependencies = [
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.2" version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
dependencies = [ dependencies = [
"pin-project-internal", "pin-project-internal",
] ]
[[package]] [[package]]
name = "pin-project-internal" name = "pin-project-internal"
version = "1.1.2" version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1543,9 +1546,9 @@ dependencies = [
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.10" version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" checksum = "2c516611246607d0c04186886dbb3a754368ef82c79e9827a802c6d836dd111c"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
@ -1684,13 +1687,13 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.9.1" version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-automata 0.3.4", "regex-automata 0.3.6",
"regex-syntax 0.7.4", "regex-syntax 0.7.4",
] ]
@ -1705,9 +1708,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.3.4" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -1770,7 +1773,7 @@ dependencies = [
"serde", "serde",
"state", "state",
"tempfile", "tempfile",
"time 0.3.24", "time 0.3.25",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tokio-util", "tokio-util",
@ -1830,7 +1833,7 @@ dependencies = [
"smallvec", "smallvec",
"stable-pattern", "stable-pattern",
"state", "state",
"time 0.3.24", "time 0.3.25",
"tokio", "tokio",
"uncased", "uncased",
] ]
@ -1860,9 +1863,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.4" version = "0.38.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399"
dependencies = [ dependencies = [
"bitflags 2.3.3", "bitflags 2.3.3",
"errno", "errno",
@ -1937,18 +1940,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.179" version = "1.0.183"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a5bf42b8d227d4abf38a1ddb08602e229108a517cd4e5bb28f9c7eaafdce5c0" checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.179" version = "1.0.183"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "741e124f5485c7e60c03b043f79f320bff3527f4bbf12cf3831750dc46a0ec2c" checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2122,7 +2125,7 @@ dependencies = [
"sqlx-rt", "sqlx-rt",
"stringprep", "stringprep",
"thiserror", "thiserror",
"time 0.3.24", "time 0.3.25",
"tokio-stream", "tokio-stream",
"url", "url",
"webpki-roots", "webpki-roots",
@ -2216,9 +2219,9 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.7.0" version = "3.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
@ -2301,9 +2304,9 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.24" version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b79eabcd964882a646b3584543ccabeae7869e9ac32a46f6f22b7a5bd405308b" checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea"
dependencies = [ dependencies = [
"deranged", "deranged",
"itoa", "itoa",
@ -2969,9 +2972,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.5.2" version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bd122eb777186e60c3fdf765a58ac76e41c582f1f535fbf3314434c6b58f3f7" checksum = "acaaa1190073b2b101e15083c38ee8ec891b5e05cbee516521e94ec008f61e64"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]

View File

@ -48,10 +48,10 @@ CREATE TABLE IF NOT EXISTS "trip" (
); );
CREATE TABLE IF NOT EXISTS "user_trip" ( CREATE TABLE IF NOT EXISTS "user_trip" (
"user_id" INTEGER NOT NULL REFERENCES user(id), "user_id" INTEGER REFERENCES user(id),
"user_note" text, -- only shown if user_id = none
"trip_details_id" INTEGER NOT NULL REFERENCES trip_details(id), "trip_details_id" INTEGER NOT NULL REFERENCES trip_details(id),
"created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, "created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP
CONSTRAINT unq UNIQUE (user_id, trip_details_id) -- allow user to participate only once for each trip
); );
CREATE TABLE IF NOT EXISTS "log" ( CREATE TABLE IF NOT EXISTS "log" (

View File

@ -37,11 +37,12 @@ pub struct PlannedEventWithUserAndTriptype {
} }
//TODO: move to appropriate place //TODO: move to appropriate place
#[derive(Serialize)] #[derive(Serialize, Debug)]
pub struct Registration { pub struct Registration {
pub name: String, pub name: String,
pub registered_at: String, pub registered_at: String,
pub is_guest: bool, pub is_guest: bool,
pub is_real_guest: bool,
} }
impl PlannedEvent { impl PlannedEvent {
@ -120,29 +121,40 @@ INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id",
async fn get_all_cox(&self, db: &SqlitePool) -> Vec<Registration> { async fn get_all_cox(&self, db: &SqlitePool) -> Vec<Registration> {
//TODO: switch to join //TODO: switch to join
sqlx::query_as!( sqlx::query!(
Registration,
" "
SELECT SELECT
(SELECT name FROM user WHERE cox_id = id) as name, (SELECT name FROM user WHERE cox_id = id) as name,
(SELECT created_at FROM user WHERE cox_id = id) as registered_at, (SELECT created_at FROM user WHERE cox_id = id) as registered_at,
(SELECT is_guest FROM user WHERE cox_id = id) as is_guest (SELECT is_guest FROM user WHERE cox_id = id) as is_guest,
0 as is_real_guest
FROM trip WHERE planned_event_id = ? FROM trip WHERE planned_event_id = ?
", ",
self.id self.id
) )
.fetch_all(db) .fetch_all(db)
.await .await
.unwrap() //Okay, as PlannedEvent can only be created with proper DB backing .unwrap()
.into_iter()
.map(|r| Registration {
name: r.name,
registered_at: r.registered_at,
is_guest: r.is_guest,
is_real_guest: r.is_real_guest == 1,
})
.collect() //Okay, as PlannedEvent can only be created with proper DB backing
} }
async fn get_all_rower(&self, db: &SqlitePool) -> Vec<Registration> { async fn get_all_rower(&self, db: &SqlitePool) -> Vec<Registration> {
//TODO: switch to join //TODO: switch to join
sqlx::query_as!( sqlx::query!(
Registration,
" "
SELECT SELECT
(SELECT name FROM user WHERE user_trip.user_id = user.id) as name, CASE
WHEN user_id IS NOT NULL THEN (SELECT name FROM user WHERE user_trip.user_id = user.id)
ELSE user_note
END as name,
user_id IS NULL as is_real_guest,
(SELECT created_at FROM user WHERE user_trip.user_id = user.id) as registered_at, (SELECT created_at FROM user WHERE user_trip.user_id = user.id) as registered_at,
(SELECT is_guest FROM user WHERE user_trip.user_id = user.id) as is_guest (SELECT is_guest FROM user WHERE user_trip.user_id = user.id) as is_guest
FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM planned_event WHERE id = ?) FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM planned_event WHERE id = ?)
@ -151,7 +163,15 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM planned_even
) )
.fetch_all(db) .fetch_all(db)
.await .await
.unwrap() //Okay, as PlannedEvent can only be created with proper DB backing .unwrap()
.into_iter()
.map(|r| Registration {
name: r.name.unwrap(),
registered_at: r.registered_at,
is_guest: r.is_guest,
is_real_guest: r.is_real_guest == 1,
})
.collect()
} }
//TODO: add tests //TODO: add tests

View File

@ -126,11 +126,14 @@ WHERE day=?
} }
async fn get_all_rower(&self, db: &SqlitePool) -> Vec<Registration> { async fn get_all_rower(&self, db: &SqlitePool) -> Vec<Registration> {
sqlx::query_as!( sqlx::query!(
Registration,
" "
SELECT SELECT
(SELECT name FROM user WHERE user_trip.user_id = user.id) as name, CASE
WHEN user_id IS NOT NULL THEN (SELECT name FROM user WHERE user_trip.user_id = user.id)
ELSE user_note
END as name,
user_id IS NULL as is_real_guest,
(SELECT created_at FROM user WHERE user_trip.user_id = user.id) as registered_at, (SELECT created_at FROM user WHERE user_trip.user_id = user.id) as registered_at,
(SELECT is_guest FROM user WHERE user_trip.user_id = user.id) as is_guest (SELECT is_guest FROM user WHERE user_trip.user_id = user.id) as is_guest
FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE id = ?)", FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE id = ?)",
@ -138,7 +141,15 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE i
) )
.fetch_all(db) .fetch_all(db)
.await .await
.unwrap() //Okay, as Trip can only be created with proper DB backing .unwrap()
.into_iter()
.map(|r| Registration {
name: r.name.unwrap(),
registered_at: r.registered_at,
is_guest: r.is_guest,
is_real_guest: r.is_real_guest == 1,
})
.collect()
} }
/// Cox decides to update own trip. /// Cox decides to update own trip.
@ -497,7 +508,9 @@ mod test {
.unwrap(); .unwrap();
let user = User::find_by_name(&pool, "rower".into()).await.unwrap(); let user = User::find_by_name(&pool, "rower".into()).await.unwrap();
UserTrip::create(&pool, &user, &trip_details).await.unwrap(); UserTrip::create(&pool, &user, &trip_details, None)
.await
.unwrap();
let result = trip let result = trip
.delete(&pool, &cox) .delete(&pool, &cox)

View File

@ -1,3 +1,4 @@
use crate::model::user::User;
use chrono::NaiveDate; use chrono::NaiveDate;
use rocket::FromForm; use rocket::FromForm;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -89,6 +90,91 @@ ORDER BY day;",
.map(|a| NaiveDate::parse_from_str(&a, "%Y-%m-%d").unwrap()) .map(|a| NaiveDate::parse_from_str(&a, "%Y-%m-%d").unwrap())
.collect() .collect()
} }
pub(crate) async fn user_is_rower(&self, db: &SqlitePool, user: &User) -> bool {
//check if cox if planned_event
let is_rower = sqlx::query!(
"SELECT count(*) as amount
FROM user_trip
WHERE trip_details_id = ? AND user_id = ?",
self.id,
user.id
)
.fetch_one(db)
.await
.unwrap();
is_rower.amount > 0
}
async fn belongs_to_event(&self, db: &SqlitePool) -> bool {
let amount = sqlx::query!(
"SELECT count(*) as amount
FROM planned_event
WHERE trip_details_id = ?",
self.id,
)
.fetch_one(db)
.await
.unwrap();
amount.amount > 0
}
pub(crate) async fn user_allowed_to_change(&self, db: &SqlitePool, user: &User) -> bool {
if self.belongs_to_event(db).await {
return user.is_admin;
} else {
return self.user_is_cox(db, user).await != CoxAtTrip::No;
}
}
pub(crate) async fn user_is_cox(&self, db: &SqlitePool, user: &User) -> CoxAtTrip {
//check if cox if planned_event
let is_cox = sqlx::query!(
"SELECT count(*) as amount
FROM trip
WHERE planned_event_id = (
SELECT id FROM planned_event WHERE trip_details_id = ?
)
AND cox_id = ?",
self.id,
user.id
)
.fetch_one(db)
.await
.unwrap();
if is_cox.amount > 0 {
return CoxAtTrip::Yes(Action::Helping);
}
//check if cox if own event
let is_cox = sqlx::query!(
"SELECT count(*) as amount
FROM trip
WHERE trip_details_id = ?
AND cox_id = ?",
self.id,
user.id
)
.fetch_one(db)
.await
.unwrap();
if is_cox.amount > 0 {
return CoxAtTrip::Yes(Action::Own);
}
CoxAtTrip::No
}
}
#[derive(PartialEq, Debug)]
pub(crate) enum CoxAtTrip {
No,
Yes(Action),
}
#[derive(PartialEq, Debug)]
pub(crate) enum Action {
Helping,
Own,
} }
#[cfg(test)] #[cfg(test)]

View File

@ -1,6 +1,7 @@
use sqlx::SqlitePool; use sqlx::SqlitePool;
use super::{tripdetails::TripDetails, user::User}; use super::{tripdetails::TripDetails, user::User};
use crate::model::tripdetails::{Action, CoxAtTrip::Yes};
pub struct UserTrip {} pub struct UserTrip {}
@ -9,6 +10,7 @@ impl UserTrip {
db: &SqlitePool, db: &SqlitePool,
user: &User, user: &User,
trip_details: &TripDetails, trip_details: &TripDetails,
user_note: Option<String>,
) -> Result<(), UserTripError> { ) -> Result<(), UserTripError> {
if trip_details.is_full(db).await { if trip_details.is_full(db).await {
return Err(UserTripError::EventAlreadyFull); return Err(UserTripError::EventAlreadyFull);
@ -22,65 +24,75 @@ impl UserTrip {
return Err(UserTripError::GuestNotAllowedForThisEvent); return Err(UserTripError::GuestNotAllowedForThisEvent);
} }
//TODO: Check if user sees the event (otherwise she could forge trip_details_id //TODO: Check if user sees the event (otherwise she could forge trip_details_id)
//check if cox if own event let is_cox = trip_details.user_is_cox(db, user).await;
let is_cox = sqlx::query!( if user_note.is_none() {
"SELECT count(*) as amount if let Yes(action) = is_cox {
FROM trip match action {
WHERE trip_details_id = ? Action::Helping => return Err(UserTripError::AlreadyRegisteredAsCox),
AND cox_id = ?", Action::Own => return Err(UserTripError::CantRegisterAtOwnEvent),
trip_details.id, };
user.id
)
.fetch_one(db)
.await
.unwrap();
if is_cox.amount > 0 {
return Err(UserTripError::CantRegisterAtOwnEvent);
} }
//TODO: can probably move to trip.rs? if trip_details.user_is_rower(db, user).await {
//check if cox if planned_event return Err(UserTripError::AlreadyRegistered);
let is_cox = sqlx::query!(
"SELECT count(*) as amount
FROM trip
WHERE planned_event_id = (
SELECT id FROM planned_event WHERE trip_details_id = ?
)
AND cox_id = ?",
trip_details.id,
user.id
)
.fetch_one(db)
.await
.unwrap();
if is_cox.amount > 0 {
return Err(UserTripError::AlreadyRegisteredAsCox);
} }
match sqlx::query!( sqlx::query!(
"INSERT INTO user_trip (user_id, trip_details_id) VALUES(?, ?)", "INSERT INTO user_trip (user_id, trip_details_id) VALUES(?, ?)",
user.id, user.id,
trip_details.id trip_details.id,
) )
.execute(db) .execute(db)
.await .await
{ .unwrap();
Ok(_) => Ok(()), } else {
Err(_) => Err(UserTripError::AlreadyRegistered), if !trip_details.user_allowed_to_change(db, user).await {
return Err(UserTripError::NotAllowedToAddGuest);
} }
sqlx::query!(
"INSERT INTO user_trip (user_note, trip_details_id) VALUES(?, ?)",
user_note,
trip_details.id,
)
.execute(db)
.await
.unwrap();
}
Ok(())
} }
pub async fn delete( pub async fn delete(
db: &SqlitePool, db: &SqlitePool,
user: &User, user: &User,
trip_details: &TripDetails, trip_details: &TripDetails,
name: Option<String>,
) -> Result<(), UserTripDeleteError> { ) -> Result<(), UserTripDeleteError> {
if trip_details.is_locked { if trip_details.is_locked {
return Err(UserTripDeleteError::DetailsLocked); return Err(UserTripDeleteError::DetailsLocked);
} }
if let Some(name) = name {
if !trip_details.user_allowed_to_change(db, user).await {
return Err(UserTripDeleteError::NotAllowedToDeleteGuest);
}
if sqlx::query!(
"DELETE FROM user_trip WHERE user_note = ? AND trip_details_id = ?",
name,
trip_details.id
)
.execute(db)
.await
.unwrap()
.rows_affected()
== 0
{
return Err(UserTripDeleteError::GuestNotParticipating);
}
} else {
let _ = sqlx::query!( let _ = sqlx::query!(
"DELETE FROM user_trip WHERE user_id = ? AND trip_details_id = ?", "DELETE FROM user_trip WHERE user_id = ? AND trip_details_id = ?",
user.id, user.id,
@ -89,7 +101,7 @@ impl UserTrip {
.execute(db) .execute(db)
.await .await
.unwrap(); .unwrap();
}
Ok(()) Ok(())
} }
} }
@ -102,11 +114,14 @@ pub enum UserTripError {
DetailsLocked, DetailsLocked,
CantRegisterAtOwnEvent, CantRegisterAtOwnEvent,
GuestNotAllowedForThisEvent, GuestNotAllowedForThisEvent,
NotAllowedToAddGuest,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum UserTripDeleteError { pub enum UserTripDeleteError {
DetailsLocked, DetailsLocked,
GuestNotParticipating,
NotAllowedToDeleteGuest,
} }
#[cfg(test)] #[cfg(test)]
@ -130,7 +145,9 @@ mod test {
let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap();
UserTrip::create(&pool, &user, &trip_details).await.unwrap(); UserTrip::create(&pool, &user, &trip_details, None)
.await
.unwrap();
} }
#[sqlx::test] #[sqlx::test]
@ -143,12 +160,14 @@ mod test {
let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap();
UserTrip::create(&pool, &user, &trip_details).await.unwrap(); UserTrip::create(&pool, &user, &trip_details, None)
UserTrip::create(&pool, &user2, &trip_details) .await
.unwrap();
UserTrip::create(&pool, &user2, &trip_details, None)
.await .await
.unwrap(); .unwrap();
let result = UserTrip::create(&pool, &user3, &trip_details) let result = UserTrip::create(&pool, &user3, &trip_details, None)
.await .await
.expect_err("Expect registration to fail because trip is already full"); .expect_err("Expect registration to fail because trip is already full");
@ -162,9 +181,11 @@ mod test {
let user = User::find_by_name(&pool, "cox".into()).await.unwrap(); let user = User::find_by_name(&pool, "cox".into()).await.unwrap();
let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap();
UserTrip::create(&pool, &user, &trip_details).await.unwrap(); UserTrip::create(&pool, &user, &trip_details, None)
.await
.unwrap();
let result = UserTrip::create(&pool, &user, &trip_details) let result = UserTrip::create(&pool, &user, &trip_details, None)
.await .await
.expect_err("Expect registration to fail because user is same as responsible cox"); .expect_err("Expect registration to fail because user is same as responsible cox");
@ -179,7 +200,7 @@ mod test {
let trip_details = TripDetails::find_by_id(&pool, 2).await.unwrap(); let trip_details = TripDetails::find_by_id(&pool, 2).await.unwrap();
let result = UserTrip::create(&pool, &user, &trip_details) let result = UserTrip::create(&pool, &user, &trip_details, None)
.await .await
.expect_err("Expect registration to fail because user is same as responsible cox"); .expect_err("Expect registration to fail because user is same as responsible cox");
@ -196,12 +217,11 @@ mod test {
.try_into() .try_into()
.unwrap(); .unwrap();
let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap();
let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap(); let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap();
Trip::new_join(&pool, &cox, &planned_event).await.unwrap(); Trip::new_join(&pool, &cox, &planned_event).await.unwrap();
let result = UserTrip::create(&pool, &cox, &trip_details) let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap();
let result = UserTrip::create(&pool, &cox, &trip_details, None)
.await .await
.expect_err("Expect registration to fail because user is already registered as cox"); .expect_err("Expect registration to fail because user is already registered as cox");
@ -216,7 +236,7 @@ mod test {
let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap();
let result = UserTrip::create(&pool, &user, &trip_details) let result = UserTrip::create(&pool, &user, &trip_details, None)
.await .await
.expect_err("Not allowed for guests"); .expect_err("Not allowed for guests");

View File

@ -46,13 +46,18 @@ async fn index(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_
Template::render("index", context.into_json()) Template::render("index", context.into_json())
} }
#[get("/join/<trip_details_id>")] #[get("/join/<trip_details_id>?<user_note>")]
async fn join(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Flash<Redirect> { async fn join(
db: &State<SqlitePool>,
trip_details_id: i64,
user: User,
user_note: Option<String>,
) -> Flash<Redirect> {
let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else {
return Flash::error(Redirect::to("/"), "Trip_details do not exist."); return Flash::error(Redirect::to("/"), "Trip_details do not exist.");
}; };
match UserTrip::create(db, &user, &trip_details).await { match UserTrip::create(db, &user, &trip_details, user_note).await {
Ok(_) => { Ok(_) => {
Log::create( Log::create(
db, db,
@ -81,6 +86,10 @@ async fn join(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Flash
Redirect::to("/"), Redirect::to("/"),
"Bei dieser Ausfahrt können leider keine Gäste mitfahren.", "Bei dieser Ausfahrt können leider keine Gäste mitfahren.",
), ),
Err(UserTripError::NotAllowedToAddGuest) => Flash::error(
Redirect::to("/"),
"Du darfst keine Gäste hinzufügen.",
),
Err(UserTripError::DetailsLocked) => Flash::error( Err(UserTripError::DetailsLocked) => Flash::error(
Redirect::to("/"), Redirect::to("/"),
"Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.", "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.",
@ -88,13 +97,18 @@ async fn join(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Flash
} }
} }
#[get("/remove/<trip_details_id>")] #[get("/remove/<trip_details_id>/<name>")]
async fn remove(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Flash<Redirect> { async fn remove_guest(
db: &State<SqlitePool>,
trip_details_id: i64,
user: User,
name: String,
) -> Flash<Redirect> {
let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else {
return Flash::error(Redirect::to("/"), "TripDetailsId does not exist"); return Flash::error(Redirect::to("/"), "TripDetailsId does not exist");
}; };
match UserTrip::delete(db, &user, &trip_details).await { match UserTrip::delete(db, &user, &trip_details, Some(name)).await {
Ok(_) => { Ok(_) => {
Log::create( Log::create(
db, db,
@ -119,6 +133,50 @@ async fn remove(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Fla
Flash::error(Redirect::to("/"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.") Flash::error(Redirect::to("/"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.")
} }
Err(UserTripDeleteError::GuestNotParticipating) => {
Flash::error(Redirect::to("/"), "Gast nicht angemeldet.")
}
Err(UserTripDeleteError::NotAllowedToDeleteGuest) => Flash::error(
Redirect::to("/"),
"Keine Berechtigung um den Gast zu entfernen.",
),
}
}
#[get("/remove/<trip_details_id>")]
async fn remove(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Flash<Redirect> {
let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else {
return Flash::error(Redirect::to("/"), "TripDetailsId does not exist");
};
match UserTrip::delete(db, &user, &trip_details, None).await {
Ok(_) => {
Log::create(
db,
format!(
"User {} unregistered for trip_details.id={}",
user.name, trip_details_id
),
)
.await;
Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!")
}
Err(UserTripDeleteError::DetailsLocked) => {
Log::create(
db,
format!(
"User {} tried to unregister for locked trip_details.id={}",
user.name, trip_details_id
),
)
.await;
Flash::error(Redirect::to("/"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.")
}
Err(_) => {
panic!("Not possible to be here");
}
} }
} }
@ -135,7 +193,7 @@ pub struct Config {
pub fn config(rocket: Rocket<Build>) -> Rocket<Build> { pub fn config(rocket: Rocket<Build>) -> Rocket<Build> {
rocket rocket
.mount("/", routes![index, join, remove]) .mount("/", routes![index, join, remove, remove_guest])
.mount("/auth", auth::routes()) .mount("/auth", auth::routes())
.mount("/log", log::routes()) .mount("/log", log::routes())
.mount("/stat", stat::routes()) .mount("/stat", stat::routes())

View File

@ -20,7 +20,7 @@
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<label for="is_guest" class="flex items-center cursor-pointer hover:text-gray-100"><input type="checkbox" id="is_guest" name="is_guest" class="h-4 w-4 accent-gray-200 mr-2" checked="true"/> <label for="is_guest" class="flex items-center cursor-pointer hover:text-gray-100"><input type="checkbox" id="is_guest" name="is_guest" class="h-4 w-4 accent-gray-200 mr-2" checked="true"/>
Gast</label> Scheckbuch</label>
</div> </div>
</div> </div>
</div> </div>
@ -49,7 +49,7 @@
{% endif %} {% endif %}
</div> </div>
<div class="grid md:grid-cols-3"> <div class="grid md:grid-cols-3">
{{ macros::checkbox(label='Gast', name='is_guest', id=loop.index , checked=user.is_guest) }} {{ macros::checkbox(label='Scheckbuch', name='is_guest', id=loop.index , checked=user.is_guest) }}
{{ macros::checkbox(label='Steuerberechtigter', name='is_cox', id=loop.index , checked=user.is_cox) }} {{ macros::checkbox(label='Steuerberechtigter', name='is_cox', id=loop.index , checked=user.is_cox) }}
{{ macros::checkbox(label='Technical', name='is_tech', id=loop.index , checked=user.is_tech) }} {{ macros::checkbox(label='Technical', name='is_tech', id=loop.index , checked=user.is_tech) }}
{{ macros::checkbox(label='Admin', name='is_admin', id=loop.index , checked=user.is_admin) }} {{ macros::checkbox(label='Admin', name='is_admin', id=loop.index , checked=user.is_admin) }}

View File

@ -10,7 +10,7 @@
{% if loggedin_user.is_cox %} {% if loggedin_user.is_cox %}
{{ macros::faq( {{ macros::faq(
question='Was bedeuted "gesperrt"?', question='Was bedeuted "gesperrt"?',
answer='Sobald du keine An- und Abmeldungen mehr erlauben willst (zB weil es bereits eine Bootseinteilung gibt), kannst du eine Ausfahrt sperren. Um deinen administrativen Aufwand zu minimieren, versuche die Ausfahrt möglichst spät zu sperren (ansonsten bekommst du potentiell private Nachrichten von Rudernde, die sich noch an- oder abmelden wollen.') answer='Sobald du keine An- und Abmeldungen mehr erlauben willst (zB weil es bereits eine Bootseinteilung gibt), kannst du eine Ausfahrt sperren. Um deinen administrativen Aufwand zu minimieren, versuche die Ausfahrt möglichst spät zu sperren (ansonsten bekommst du potentiell private Nachrichten von Rudernde, die sich noch an- oder abmelden wollen).')
}} }}
{{ macros::faq( {{ macros::faq(

View File

@ -103,7 +103,7 @@
</div> </div>
{% endmacro alert %} {% endmacro alert %}
{% macro box(participants, empty_seats='', header='Freie Plätze:', text='Keine Ruderer angemeldet', bg='primary-600', color='white') %} {% macro box(participants, empty_seats='', header='Freie Plätze:', text='Keine Ruderer angemeldet', bg='primary-600', color='white', trip_details_id='', allow_removing=false) %}
<div class="text-{{ color }} bg-{{ bg }} text-center p-1 mt-1 rounded-t-md">{{ header }} <div class="text-{{ color }} bg-{{ bg }} text-center p-1 mt-1 rounded-t-md">{{ header }}
{{ empty_seats }}</div> {{ empty_seats }}</div>
<div class="p-2 border border-t-0 border-{{ bg }} mb-4 rounded-b-md"> <div class="p-2 border border-t-0 border-{{ bg }} mb-4 rounded-b-md">
@ -111,7 +111,13 @@
{% for rower in participants %} {% for rower in participants %}
{{ rower.name }} {{ rower.name }}
{% if rower.is_guest %} {% if rower.is_guest %}
<small class="text-gray-600">(Scheckbuch)</small>
{% endif %}
{% if rower.is_real_guest %}
<small class="text-gray-600">(Gast)</small> <small class="text-gray-600">(Gast)</small>
{% if allow_removing %}
<a href="/remove/{{ trip_details_id }}/{{ rower.name }}" class="btn btn-attention btn-fw">Abmelden</a>
{% endif %}
{% endif %} {% endif %}
<span class="hidden">(angemeldet seit <span class="hidden">(angemeldet seit
{{ rower.registered_at }})</span><br/> {{ rower.registered_at }})</span><br/>

View File

@ -115,10 +115,18 @@
{# --- START List Rowers --- #} {# --- START List Rowers --- #}
{% if planned_event.max_people > 0 %} {% if planned_event.max_people > 0 %}
{% set amount_cur_rower = planned_event.rower | length %} {% set amount_cur_rower = planned_event.rower | length %}
{{ macros::box(participants=planned_event.rower, empty_seats=planned_event.max_people - amount_cur_rower, bg='primary-100', color='black') }} {{ macros::box(participants=planned_event.rower, empty_seats=planned_event.max_people - amount_cur_rower, bg='primary-100', color='black', trip_details_id=planned_event.trip_details_id, allow_removing=loggedin_user.is_admin) }}
{% endif %} {% endif %}
{# --- END List Rowers --- #} {# --- END List Rowers --- #}
{% if loggedin_user.is_admin %}
<form action="/join/{{ planned_event.trip_details_id }}" method="get" />
{{ macros::input(label='Gast', name='user_note', type='text', required=true) }}
<input value="Gast hinzufügen" class="btn btn-primary" type="submit"/>
</form>
{% endif %}
{% if planned_event.allow_guests %} {% if planned_event.allow_guests %}
<div class="text-primary-900 bg-primary-50 text-center p-1 mb-4">Gäste willkommen!</div> <div class="text-primary-900 bg-primary-50 text-center p-1 mb-4">Gäste willkommen!</div>
{% endif %} {% endif %}
@ -213,7 +221,13 @@
{{ macros::box(participants=trip.rower,bg='[#f43f5e]',header='Absage') }} {{ macros::box(participants=trip.rower,bg='[#f43f5e]',header='Absage') }}
{% else %} {% else %}
{% set amount_cur_rower = trip.rower | length %} {% set amount_cur_rower = trip.rower | length %}
{{ macros::box(participants=trip.rower, empty_seats=trip.max_people - amount_cur_rower, bg='primary-100', color='black') }} {{ macros::box(participants=trip.rower, empty_seats=trip.max_people - amount_cur_rower, bg='primary-100', color='black', trip_details_id=trip.trip_details_id, allow_removing=loggedin_user.id == trip.cox_id) }}
{% if trip.cox_id == loggedin_user.id %}
<form action="/join/{{ trip.trip_details_id }}" method="get" />
{{ macros::input(label='Gast', name='user_note', type='text', required=true) }}
<input value="Gast hinzufügen" class="btn btn-primary" type="submit"/>
</form>
{% endif %}
{% endif %} {% endif %}
{# --- START Edit Form --- #} {# --- START Edit Form --- #}