1 Commits

Author SHA1 Message Date
Marie Birner
8500ba826f [TASK] try to include boat and shipmaster in update popup
Some checks failed
CI/CD Pipeline / test (push) Failing after 22m47s
CI/CD Pipeline / deploy-staging (push) Has been skipped
CI/CD Pipeline / deploy-main (push) Has been skipped
2025-05-05 21:41:52 +02:00
33 changed files with 723 additions and 928 deletions

View File

@@ -1,51 +0,0 @@
name: Update Cargo Dependencies
on:
schedule:
- cron: '0 2 * * 5' # Run weekly on Friday at 2am
workflow_dispatch: # Allow manual triggering
jobs:
update-dependencies:
runs-on: ubuntu-latest
container: git.hofer.link/philipp/ci-images:rust-latest
steps:
- uses: actions/checkout@v3
- name: Update dependencies
run: |
cargo upgrade
cargo update
- name: Create Pull Request Staging
uses: https://git.hofer.link/philipp/create-pull-request@18ef1fdad70eec569ab10292c1fa79c1b5296370
with:
token: ${{ secrets.GITEATOKEN }}
commit-message: Update Cargo dependencies
title: Update Cargo dependencies
body: |
This PR updates Cargo dependencies to their latest versions.
@philipp
- Run `cargo upgrade` to update version requirements in Cargo.toml
- Run `cargo update` to update Cargo.lock
branch: update-cargo-dependencies
delete-branch: true
- name: Create Pull Request Main
uses: https://git.hofer.link/philipp/create-pull-request@18ef1fdad70eec569ab10292c1fa79c1b5296370
with:
token: ${{ secrets.GITEATOKEN }}
commit-message: Update Cargo dependencies
title: Update Cargo dependencies
body: |
This PR updates Cargo dependencies to their latest versions.
@philipp
- Run `cargo upgrade` to update version requirements in Cargo.toml
- Run `cargo update` to update Cargo.lock
branch: update-cargo-dependencies
base: main
delete-branch: true

210
Cargo.lock generated
View File

@@ -221,9 +221,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.75" version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [ dependencies = [
"addr2line", "addr2line",
"cfg-if", "cfg-if",
@@ -303,9 +303,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.23.0" version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@@ -321,9 +321,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.21" version = "1.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@@ -336,9 +336,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.41" version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
@@ -615,9 +615,9 @@ dependencies = [
[[package]] [[package]]
name = "der" name = "der"
version = "0.7.10" version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
dependencies = [ dependencies = [
"const-oid", "const-oid",
"pem-rfc7468", "pem-rfc7468",
@@ -635,9 +635,9 @@ dependencies = [
[[package]] [[package]]
name = "deunicode" name = "deunicode"
version = "1.6.2" version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" checksum = "dc55fe0d1f6c107595572ec8b107c0999bb1a2e0b75e37429a4fb0d6474a0e7d"
[[package]] [[package]]
name = "devise" name = "devise"
@@ -1028,9 +1028,9 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.16" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@@ -1126,9 +1126,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.3" version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
dependencies = [ dependencies = [
"allocator-api2", "allocator-api2",
"equivalent", "equivalent",
@@ -1141,7 +1141,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [ dependencies = [
"hashbrown 0.15.3", "hashbrown 0.15.2",
] ]
[[package]] [[package]]
@@ -1158,9 +1158,9 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.5.1" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
[[package]] [[package]]
name = "hex" name = "hex"
@@ -1476,7 +1476,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.15.3", "hashbrown 0.15.2",
"serde", "serde",
] ]
@@ -1521,7 +1521,7 @@ version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [ dependencies = [
"hermit-abi 0.5.1", "hermit-abi 0.5.0",
"libc", "libc",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@@ -1549,9 +1549,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]] [[package]]
name = "jiff" name = "jiff"
version = "0.2.13" version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" checksum = "e5ad87c89110f55e4cd4dc2893a9790820206729eaf221555f742d540b0724a0"
dependencies = [ dependencies = [
"jiff-static", "jiff-static",
"log", "log",
@@ -1562,9 +1562,9 @@ dependencies = [
[[package]] [[package]]
name = "jiff-static" name = "jiff-static"
version = "0.2.13" version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" checksum = "d076d5b64a7e2fe6f0743f02c43ca4a6725c0f904203bfe276a5b3e793103605"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1594,9 +1594,9 @@ dependencies = [
[[package]] [[package]]
name = "kqueue" name = "kqueue"
version = "1.1.1" 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 = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
dependencies = [ dependencies = [
"kqueue-sys", "kqueue-sys",
"libc", "libc",
@@ -1654,9 +1654,9 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]] [[package]]
name = "libm" name = "libm"
version = "0.2.14" 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 = "a25169bd5913a4b437588a7e3d127cd6e90127b60e0ffbd834a38f1599e016b8" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]] [[package]]
name = "libredox" name = "libredox"
@@ -2002,9 +2002,9 @@ dependencies = [
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.108" version = "0.9.107"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@@ -2267,14 +2267,14 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [ dependencies = [
"zerocopy 0.8.25", "zerocopy 0.8.24",
] ]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.95" version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -2294,9 +2294,9 @@ dependencies = [
[[package]] [[package]]
name = "psm" name = "psm"
version = "0.1.26" version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" checksum = "f58e5423e24c18cc840e1c98370b3993c6649cd1678b4d24318bcf0a083cbe88"
dependencies = [ dependencies = [
"cc", "cc",
] ]
@@ -2355,14 +2355,14 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom 0.2.16", "getrandom 0.2.15",
] ]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.12" version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.0",
] ]
@@ -2439,7 +2439,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
"getrandom 0.2.16", "getrandom 0.2.15",
"libc", "libc",
"untrusted", "untrusted",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@@ -2594,9 +2594,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.0.7" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.0",
"errno", "errno",
@@ -2607,9 +2607,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.27" version = "0.23.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
dependencies = [ dependencies = [
"log", "log",
"once_cell", "once_cell",
@@ -2637,9 +2637,9 @@ checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.103.2" version = "0.103.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7149975849f1abb3832b246010ef62ccc80d3a76169517ada7188252b9cfb437" checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03"
dependencies = [ dependencies = [
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
@@ -2777,9 +2777,9 @@ dependencies = [
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.10.9" version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
@@ -2803,9 +2803,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.5" version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -2885,9 +2885,9 @@ dependencies = [
[[package]] [[package]]
name = "sqlx" name = "sqlx"
version = "0.8.5" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c3a85280daca669cfd3bcb68a337882a8bc57ec882f72c5d13a430613a738e" checksum = "14e22987355fbf8cfb813a0cf8cd97b1b4ec834b94dbd759a9e8679d41fabe83"
dependencies = [ dependencies = [
"sqlx-core", "sqlx-core",
"sqlx-macros", "sqlx-macros",
@@ -2898,9 +2898,9 @@ dependencies = [
[[package]] [[package]]
name = "sqlx-core" name = "sqlx-core"
version = "0.8.5" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f743f2a3cea30a58cd479013f75550e879009e3a02f616f18ca699335aa248c3" checksum = "55c4720d7d4cd3d5b00f61d03751c685ad09c33ae8290c8a2c11335e0604300b"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes",
@@ -2913,7 +2913,7 @@ dependencies = [
"futures-intrusive", "futures-intrusive",
"futures-io", "futures-io",
"futures-util", "futures-util",
"hashbrown 0.15.3", "hashbrown 0.15.2",
"hashlink", "hashlink",
"indexmap", "indexmap",
"log", "log",
@@ -2930,14 +2930,14 @@ dependencies = [
"tokio-stream", "tokio-stream",
"tracing", "tracing",
"url", "url",
"webpki-roots 0.26.11", "webpki-roots",
] ]
[[package]] [[package]]
name = "sqlx-macros" name = "sqlx-macros"
version = "0.8.5" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4200e0fde19834956d4252347c12a083bdcb237d7a1a1446bffd8768417dce" checksum = "175147fcb75f353ac7675509bc58abb2cb291caf0fd24a3623b8f7e3eb0a754b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2948,9 +2948,9 @@ dependencies = [
[[package]] [[package]]
name = "sqlx-macros-core" name = "sqlx-macros-core"
version = "0.8.5" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "882ceaa29cade31beca7129b6beeb05737f44f82dbe2a9806ecea5a7093d00b7" checksum = "1cde983058e53bfa75998e1982086c5efe3c370f3250bf0357e344fa3352e32b"
dependencies = [ dependencies = [
"dotenvy", "dotenvy",
"either", "either",
@@ -2974,9 +2974,9 @@ dependencies = [
[[package]] [[package]]
name = "sqlx-mysql" name = "sqlx-mysql"
version = "0.8.5" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0afdd3aa7a629683c2d750c2df343025545087081ab5942593a5288855b1b7a7" checksum = "847d2e5393a4f39e47e4f36cab419709bc2b83cbe4223c60e86e1471655be333"
dependencies = [ dependencies = [
"atoi", "atoi",
"base64", "base64",
@@ -3017,9 +3017,9 @@ dependencies = [
[[package]] [[package]]
name = "sqlx-postgres" name = "sqlx-postgres"
version = "0.8.5" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0bedbe1bbb5e2615ef347a5e9d8cd7680fb63e77d9dafc0f29be15e53f1ebe6" checksum = "cc35947a541b9e0a2e3d85da444f1c4137c13040267141b208395a0d0ca4659f"
dependencies = [ dependencies = [
"atoi", "atoi",
"base64", "base64",
@@ -3055,9 +3055,9 @@ dependencies = [
[[package]] [[package]]
name = "sqlx-sqlite" name = "sqlx-sqlite"
version = "0.8.5" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c26083e9a520e8eb87a06b12347679b142dc2ea29e6e409f805644a7a979a5bc" checksum = "6c48291dac4e5ed32da0927a0b981788be65674aeb62666d19873ab4289febde"
dependencies = [ dependencies = [
"atoi", "atoi",
"chrono", "chrono",
@@ -3095,9 +3095,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]] [[package]]
name = "stacker" name = "stacker"
version = "0.1.21" version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" checksum = "601f9201feb9b09c00266478bf459952b9ef9a6b94edb2f21eba14ab681a60a9"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
@@ -3134,9 +3134,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.101" version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3145,9 +3145,9 @@ dependencies = [
[[package]] [[package]]
name = "synstructure" name = "synstructure"
version = "0.13.2" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3277,9 +3277,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.45.0" version = "1.44.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@@ -3316,9 +3316,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.15" version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@@ -3329,9 +3329,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.22" version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned",
@@ -3341,33 +3341,26 @@ dependencies = [
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.9" version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [ dependencies = [
"serde", "serde",
] ]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.22.26" version = "0.22.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"toml_write", "winnow 0.7.6",
"winnow 0.7.9",
] ]
[[package]]
name = "toml_write"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
[[package]] [[package]]
name = "tower-service" name = "tower-service"
version = "0.3.3" version = "0.3.3"
@@ -3574,9 +3567,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]] [[package]]
name = "ureq" name = "ureq"
version = "3.0.11" version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7a3e9af6113ecd57b8c63d3cd76a385b2e3881365f1f489e54f49801d0c83ea" checksum = "4b0351ca625c7b41a8e4f9bb6c5d9755f67f62c2187ebedecacd9974674b271d"
dependencies = [ dependencies = [
"base64", "base64",
"cookie_store", "cookie_store",
@@ -3590,14 +3583,14 @@ dependencies = [
"serde_json", "serde_json",
"ureq-proto", "ureq-proto",
"utf-8", "utf-8",
"webpki-roots 0.26.11", "webpki-roots",
] ]
[[package]] [[package]]
name = "ureq-proto" name = "ureq-proto"
version = "0.4.1" version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fadf18427d33828c311234884b7ba2afb57143e6e7e69fda7ee883b624661e36" checksum = "ae239d0a3341aebc94259414d1dc67cfce87d41cbebc816772c91b77902fafa4"
dependencies = [ dependencies = [
"base64", "base64",
"http 1.3.1", "http 1.3.1",
@@ -3773,18 +3766,9 @@ dependencies = [
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "0.26.11" version = "0.26.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9"
dependencies = [
"webpki-roots 1.0.0",
]
[[package]]
name = "webpki-roots"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb"
dependencies = [ dependencies = [
"rustls-pki-types", "rustls-pki-types",
] ]
@@ -4057,9 +4041,9 @@ dependencies = [
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.9" version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -4129,11 +4113,11 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.25" version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
dependencies = [ dependencies = [
"zerocopy-derive 0.8.25", "zerocopy-derive 0.8.24",
] ]
[[package]] [[package]]
@@ -4149,9 +4133,9 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.25" version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@@ -9,7 +9,7 @@ rowing-tera = ["rocket_dyn_templates", "tera"]
rest = [] rest = []
[dependencies] [dependencies]
rocket = { version = "0.5", features = ["secrets"]} rocket = { version = "0.5.0", features = ["secrets"]}
rocket_dyn_templates = {version = "0.2", features = [ "tera" ], optional = true } rocket_dyn_templates = {version = "0.2", features = [ "tera" ], optional = true }
log = "0.4" log = "0.4"
env_logger = "0.11" env_logger = "0.11"
@@ -19,15 +19,15 @@ serde = { version = "1.0", features = [ "derive" ]}
serde_json = "1.0" serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"]} chrono = { version = "0.4", features = ["serde"]}
chrono-tz = "0.10" chrono-tz = "0.10"
tera = { version = "1.20", features = ["date-locale"], optional = true} tera = { version = "1.18", features = ["date-locale"], optional = true}
ics = "0.5" ics = "0.5"
futures = "0.3" futures = "0.3"
lettre = "0.11" lettre = "0.11"
csv = "1.3" csv = "1.3"
itertools = "0.14" itertools = "0.14"
job_scheduler_ng = "2.2" job_scheduler_ng = "2.0"
ureq = { version = "3.0", features = ["json"] } ureq = { version = "3.0", features = ["json"] }
regex = "1.11" regex = "1.10"
urlencoding = "2.1" urlencoding = "2.1"
[target.'cfg(not(windows))'.dependencies] [target.'cfg(not(windows))'.dependencies]

View File

@@ -115,7 +115,7 @@ test("Cox can start and finish trip", async ({ page }, testInfo) => {
await page.getByPlaceholder("Passwort").press("Enter"); await page.getByPlaceholder("Passwort").press("Enter");
await page.goto("/log/show"); await page.goto("/log/show");
await page.getByRole('link', { name: 'Joe' }).nth(1).click(); await page.getByText('(cox2)').click();
page.once("dialog", (dialog) => { page.once("dialog", (dialog) => {
dialog.accept().catch(() => {}); dialog.accept().catch(() => {});
}); });
@@ -208,6 +208,7 @@ test("Kiosk can start and finish trip", async ({ page }, testInfo) => {
await page.getByRole('link', { name: 'Logbuch' }).click(); await page.getByRole('link', { name: 'Logbuch' }).click();
await expect(page.locator('body')).toContainText('Joe'); await expect(page.locator('body')).toContainText('Joe');
await expect(page.locator('body')).toContainText('(cox2)');
await expect(page.locator('body')).toContainText('Ottensheim (25 km)'); await expect(page.locator('body')).toContainText('Ottensheim (25 km)');
await expect(page.locator('body')).toContainText('Ruderer: cox2, rower2'); await expect(page.locator('body')).toContainText('Ruderer: cox2, rower2');
@@ -224,7 +225,7 @@ test("Kiosk can start and finish trip", async ({ page }, testInfo) => {
await page.getByPlaceholder("Passwort").press("Enter"); await page.getByPlaceholder("Passwort").press("Enter");
await page.goto("/log/show"); await page.goto("/log/show");
await page.getByRole('link', { name: 'Joe' }).nth(1).click(); await page.getByText('(cox2)').click();
page.once("dialog", (dialog) => { page.once("dialog", (dialog) => {
dialog.accept().catch(() => {}); dialog.accept().catch(() => {});
}); });
@@ -285,6 +286,7 @@ test("Cox can start and finish trip with cox steering only", async ({ page }, te
await page.goto('/log/show'); await page.goto('/log/show');
await expect(page.locator('body')).toContainText('cox_only_steering_boat'); await expect(page.locator('body')).toContainText('cox_only_steering_boat');
await expect(page.locator('body')).toContainText('(cox2 - handgesteuert)');
await expect(page.locator('body')).toContainText('Ottensheim (25 km)'); await expect(page.locator('body')).toContainText('Ottensheim (25 km)');
@@ -300,7 +302,7 @@ test("Cox can start and finish trip with cox steering only", async ({ page }, te
await page.getByPlaceholder("Passwort").press("Enter"); await page.getByPlaceholder("Passwort").press("Enter");
await page.goto("/log/show"); await page.goto("/log/show");
await page.getByRole("link", { name: "cox_only_steering_boat" }).click(); await page.getByText('(cox2 - handgesteuert)').click();
page.once("dialog", (dialog) => { page.once("dialog", (dialog) => {
dialog.accept().catch(() => {}); dialog.accept().catch(() => {});
}); });
@@ -369,7 +371,7 @@ test("Kiosk can start and finish trip in one stop", async ({ page }, testInfo) =
await page.getByPlaceholder("Passwort").press("Enter"); await page.getByPlaceholder("Passwort").press("Enter");
await page.goto("/log/show"); await page.goto("/log/show");
await page.getByRole('link', { name: 'Joe' }).nth(1).click(); await page.getByText('(cox2)').click();
page.once("dialog", (dialog) => { page.once("dialog", (dialog) => {
dialog.accept().catch(() => {}); dialog.accept().catch(() => {});
}); });

View File

@@ -23,9 +23,6 @@ pub(crate) const UNTERSTUETZEND: i64 = 2500;
pub(crate) const FOERDERND: i64 = 8500; pub(crate) const FOERDERND: i64 = 8500;
pub(crate) const SCHECKBUCH: i64 = 3000; pub(crate) const SCHECKBUCH: i64 = 3000;
pub(crate) const EINSCHREIBGEBUEHR: i64 = 3000; pub(crate) const EINSCHREIBGEBUEHR: i64 = 3000;
pub(crate) const DUAL_MEMBERSHIP: i64 = 18000;
pub(crate) const TRIAL_ROWING: i64 = 12000;
pub(crate) const TRIAL_ROWING_REDUCED: i64 = 6000;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct NonEmptyString(String); pub struct NonEmptyString(String);

View File

@@ -1,10 +1,7 @@
use std::ops::DerefMut; use std::ops::DerefMut;
use super::{ use super::{role::Role, user::User};
role::Role, use chrono::NaiveDateTime;
user::{ManageUserUser, User},
};
use chrono::{DateTime, Duration, Local, NaiveDateTime, TimeZone, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
@@ -17,89 +14,6 @@ pub struct Activity {
pub keep_until: Option<NaiveDateTime>, pub keep_until: Option<NaiveDateTime>,
} }
#[derive(Serialize, Deserialize, Debug)]
pub struct ActivityWithDetails {
#[serde(flatten)]
pub(crate) activity: Activity,
keep_until_days: Option<i64>,
}
impl From<Activity> for ActivityWithDetails {
fn from(activity: Activity) -> Self {
let keep_until_days = activity.keep_until.map(|keep_until| {
let now = Utc::now().naive_utc();
let duration = keep_until.signed_duration_since(now);
duration.num_days()
});
Self {
keep_until_days,
activity,
}
}
}
// TODO: add `reason` as additional db field, to be able to query and show this to the users
pub enum Reason<'a> {
Auth(ReasonAuth<'a>),
// `User` changed the data of `User`, explanation in `String`
UserDataChange(&'a ManageUserUser, &'a User, String),
// New Note for User
NewUserNote(&'a ManageUserUser, &'a User, String),
}
impl From<Reason<'_>> for ActivityBuilder {
fn from(value: Reason<'_>) -> Self {
match value {
Reason::Auth(auth) => auth.into(),
Reason::UserDataChange(changed_by, changed_user, explanation) => Self::new(&format!(
"{changed_by} hat die Daten von {changed_user} aktualisiert: {explanation}"
))
.relevant_for_user(changed_user),
Reason::NewUserNote(changed_by, user, explanation) => {
Self::new(&format!("({changed_by}) {explanation}")).relevant_for_user(user)
}
}
}
}
pub enum ReasonAuth<'a> {
// `User` tried to login with `String` as UserAgent
SuccLogin(&'a User, String),
// `User` tried to login which was already deleted
DeletedUserLogin(&'a User),
// `User` tried to login, supplied wrong PW
WrongPw(&'a User),
}
impl<'a> From<ReasonAuth<'a>> for Reason<'a> {
fn from(auth_reason: ReasonAuth<'a>) -> Self {
Reason::Auth(auth_reason)
}
}
impl From<ReasonAuth<'_>> for ActivityBuilder {
fn from(value: ReasonAuth<'_>) -> Self {
match value {
ReasonAuth::SuccLogin(user, agent) => {
Self::new(&format!("{user} hat sich eingeloggt (User-Agent: {agent})"))
.relevant_for_user(user)
.keep_until_days(7)
}
ReasonAuth::DeletedUserLogin(user) => Self::new(&format!(
"User {user} wollte sich einloggen, klappte jedoch nicht weil er gelöscht wurde."
))
.relevant_for_user(user)
.keep_until_days(30),
ReasonAuth::WrongPw(user) => Self::new(&format!(
"User {user} wollte sich einloggen, hat jedoch das falsche Passwort angegeben."
))
.relevant_for_user(user)
.keep_until_days(7),
}
}
}
pub struct ActivityBuilder { pub struct ActivityBuilder {
text: String, text: String,
relevant_for: String, relevant_for: String,
@@ -107,7 +21,6 @@ pub struct ActivityBuilder {
} }
impl ActivityBuilder { impl ActivityBuilder {
/// TODO: maybe make this private, and only allow specific acitivites defined in `Reason`
#[must_use] #[must_use]
pub fn new(text: &str) -> Self { pub fn new(text: &str) -> Self {
Self { Self {
@@ -133,15 +46,6 @@ impl ActivityBuilder {
} }
} }
#[must_use]
pub fn keep_until_days(self, days: i64) -> Self {
let now = Utc::now().naive_utc();
Self {
keep_until: Some(now + Duration::days(days)),
..self
}
}
pub async fn save(self, db: &SqlitePool) { pub async fn save(self, db: &SqlitePool) {
Activity::create(db, &self.text, &self.relevant_for, self.keep_until).await; Activity::create(db, &self.text, &self.relevant_for, self.keep_until).await;
} }
@@ -206,30 +110,4 @@ ORDER BY created_at DESC;
.await .await
.unwrap() .unwrap()
} }
async fn last(db: &SqlitePool) -> Vec<Self> {
sqlx::query_as!(
Self,
"
SELECT id, created_at, text, relevant_for, keep_until FROM activity
ORDER BY id DESC
LIMIT 1000
"
)
.fetch_all(db)
.await
.unwrap()
}
pub async fn show(db: &SqlitePool) -> String {
let mut ret = String::new();
for log in Self::last(db).await {
let utc_time: DateTime<Utc> = Utc::from_utc_datetime(&Utc, &log.created_at);
let local_time = utc_time.with_timezone(&Local);
ret.push_str(&format!("- {local_time}: {}\n", log.text));
}
ret
}
} }

View File

@@ -1,6 +1,7 @@
use std::ops::DerefMut; use std::ops::DerefMut;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use itertools::Itertools;
use rocket::FromForm; use rocket::FromForm;
use rocket::serde::{Deserialize, Serialize}; use rocket::serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
@@ -101,27 +102,12 @@ impl Boat {
} }
pub async fn shipmaster_allowed(&self, db: &SqlitePool, user: &User) -> bool { pub async fn shipmaster_allowed(&self, db: &SqlitePool, user: &User) -> bool {
let mut tx = db.begin().await.unwrap();
let ret = self.shipmaster_allowed_tx(&mut tx, user).await;
tx.commit().await.unwrap();
ret
}
pub async fn shipmaster_allowed_tx(
&self,
db: &mut Transaction<'_, Sqlite>,
user: &User,
) -> bool {
if user.has_role_tx(db, "admin").await {
return true;
}
if let Some(owner_id) = self.owner { if let Some(owner_id) = self.owner {
return owner_id == user.id; return owner_id == user.id;
} }
if user.has_role_tx(db, "Rennrudern").await { if user.has_role(db, "Rennrudern").await {
let ottensheim = Location::find_by_name_tx(db, "Ottensheim".into()) let ottensheim = Location::find_by_name(db, "Ottensheim".into())
.await .await
.unwrap(); .unwrap();
if self.location_id == ottensheim.id { if self.location_id == ottensheim.id {
@@ -129,10 +115,22 @@ impl Boat {
} }
} }
if self.name == "Externes Boot" { if self.amount_seats == 1 {
return true; return true;
} }
user.allowed_to_steer(db).await
}
pub async fn shipmaster_allowed_tx(
&self,
db: &mut Transaction<'_, Sqlite>,
user: &User,
) -> bool {
if let Some(owner_id) = self.owner {
return owner_id == user.id;
}
if self.amount_seats == 1 { if self.amount_seats == 1 {
return true; return true;
} }
@@ -259,16 +257,58 @@ ORDER BY
} }
pub async fn for_user(db: &SqlitePool, user: &User) -> Vec<BoatWithDetails> { pub async fn for_user(db: &SqlitePool, user: &User) -> Vec<BoatWithDetails> {
let all_boats = Self::all(db).await; if user.has_role(db, "admin").await {
let mut filtered_boats = Vec::new(); return Self::all(db).await;
for boat in all_boats {
if boat.boat.shipmaster_allowed(db, user).await {
filtered_boats.push(boat);
}
} }
let mut boats = if user.allowed_to_steer(db).await {
sqlx::query_as!(
Boat,
"
SELECT id, name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, default_destination, skull, external, deleted, convert_handoperated_possible
FROM boat
WHERE (owner is null or owner = ?) AND deleted = 0
ORDER BY amount_seats DESC
",
user.id
)
.fetch_all(db)
.await
.unwrap() //TODO: fixme
} else {
sqlx::query_as!(
Boat,
"
SELECT id, name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, default_destination, skull, external, deleted, convert_handoperated_possible
FROM boat
WHERE (owner = ? OR (owner is null and amount_seats = 1)) AND deleted = 0
ORDER BY amount_seats DESC
",
user.id
)
.fetch_all(db)
.await
.unwrap() //TODO: fixme
};
filtered_boats if user.has_role(db, "Rennrudern").await {
let ottensheim = Location::find_by_name(db, "Ottensheim".into())
.await
.unwrap();
let boats_in_ottensheim = sqlx::query_as!(
Boat,
"SELECT id, name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, default_destination, skull, external, deleted, convert_handoperated_possible
FROM boat
WHERE (owner is null and location_id = ?) AND deleted = 0
ORDER BY amount_seats DESC
",ottensheim.id)
.fetch_all(db)
.await
.unwrap(); //TODO: fixme
boats.extend(boats_in_ottensheim.into_iter());
}
let boats = boats.into_iter().unique().collect();
Self::boats_to_details(db, boats).await
} }
pub async fn all_at_location(db: &SqlitePool, location: String) -> Vec<BoatWithDetails> { pub async fn all_at_location(db: &SqlitePool, location: String) -> Vec<BoatWithDetails> {

View File

@@ -2,8 +2,8 @@ use std::io::Write;
use chrono::{Duration, NaiveDate, NaiveTime}; use chrono::{Duration, NaiveDate, NaiveTime};
use ics::{ use ics::{
properties::{DtEnd, DtStart, Summary},
ICalendar, ICalendar,
properties::{DtEnd, DtStart, Summary},
}; };
use serde::Serialize; use serde::Serialize;
use sqlx::{FromRow, Row, SqlitePool}; use sqlx::{FromRow, Row, SqlitePool};
@@ -142,14 +142,6 @@ WHERE planned_event.id like ?
.ok() .ok()
} }
async fn trip_type(&self, db: &SqlitePool) -> Option<TripType> {
if let Some(trip_type_id) = self.trip_type_id {
TripType::find_by_id(db, trip_type_id).await
} else {
None
}
}
pub async fn get_pinned_for_day(db: &SqlitePool, day: NaiveDate) -> Vec<EventWithDetails> { pub async fn get_pinned_for_day(db: &SqlitePool, day: NaiveDate) -> Vec<EventWithDetails> {
let mut events = Self::get_for_day(db, day).await; let mut events = Self::get_for_day(db, day).await;
events.retain(|e| e.event.always_show); events.retain(|e| e.event.always_show);
@@ -493,16 +485,7 @@ WHERE trip_details.id=?
let original_time = NaiveTime::parse_from_str(&self.planned_starting_time, "%H:%M") let original_time = NaiveTime::parse_from_str(&self.planned_starting_time, "%H:%M")
.expect("Failed to parse time"); .expect("Failed to parse time");
let later_time = original_time + Duration::hours(3);
let long_trip = match self.trip_type(db).await {
Some(a) if a.name == "Lange Ausfahrt" => true,
_ => false,
};
let later_time = if long_trip {
original_time + Duration::hours(6)
} else {
original_time + Duration::hours(3)
};
if later_time > original_time { if later_time > original_time {
// Check if no day-overflow // Check if no day-overflow
let time_three_hours_later = later_time.format("%H%M").to_string(); let time_three_hours_later = later_time.format("%H%M").to_string();

View File

@@ -86,7 +86,7 @@ GROUP BY family.id;"
} }
pub async fn members(&self, db: &SqlitePool) -> Vec<User> { pub async fn members(&self, db: &SqlitePool) -> Vec<User> {
sqlx::query_as!(User, "SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, phone, address, family_id, user_token FROM user WHERE family_id = ?", self.id) sqlx::query_as!(User, "SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token FROM user WHERE family_id = ?", self.id)
.fetch_all(db) .fetch_all(db)
.await .await
.unwrap() .unwrap()

View File

@@ -1,6 +1,5 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use sqlx::{FromRow, SqlitePool};
use std::ops::DerefMut;
#[derive(FromRow, Debug, Serialize, Deserialize)] #[derive(FromRow, Debug, Serialize, Deserialize)]
pub struct Location { pub struct Location {
@@ -38,20 +37,6 @@ impl Location {
.await .await
.ok() .ok()
} }
pub async fn find_by_name_tx(db: &mut Transaction<'_, Sqlite>, name: String) -> Option<Self> {
sqlx::query_as!(
Self,
"
SELECT id, name
FROM location
WHERE name=?
",
name
)
.fetch_one(db.deref_mut())
.await
.ok()
}
pub async fn all(db: &SqlitePool) -> Vec<Self> { pub async fn all(db: &SqlitePool) -> Vec<Self> {
sqlx::query_as!(Self, "SELECT id, name FROM location") sqlx::query_as!(Self, "SELECT id, name FROM location")

View File

@@ -1,16 +1,74 @@
use super::activity::ActivityBuilder; use std::ops::DerefMut;
use sqlx::{Sqlite, SqlitePool, Transaction};
pub struct Log {} use chrono::{DateTime, Local, NaiveDateTime, TimeZone, Utc};
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
#[derive(FromRow, Debug, Serialize, Deserialize)]
pub struct Log {
pub msg: String,
pub created_at: NaiveDateTime,
}
// TODO: remove and convert to proper acitvities
impl Log { impl Log {
pub async fn create(db: &SqlitePool, msg: String) -> bool { pub async fn create(db: &SqlitePool, msg: String) -> bool {
ActivityBuilder::new(&msg).save(db).await; sqlx::query!("INSERT INTO log(msg) VALUES (?)", msg,)
true .execute(db)
.await
.is_ok()
} }
pub async fn create_with_tx(db: &mut Transaction<'_, Sqlite>, msg: String) -> bool { pub async fn create_with_tx(db: &mut Transaction<'_, Sqlite>, msg: String) -> bool {
ActivityBuilder::new(&msg).save_tx(db).await; sqlx::query!("INSERT INTO log(msg) VALUES (?)", msg,)
true .execute(db.deref_mut())
.await
.is_ok()
}
async fn last(db: &SqlitePool) -> Vec<Log> {
sqlx::query_as!(
Log,
"
SELECT msg, created_at
FROM log
ORDER BY id DESC
LIMIT 1000
"
)
.fetch_all(db)
.await
.unwrap()
}
pub async fn generate_feed(db: &SqlitePool) -> String {
let mut ret = String::from(
r#"<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Ruder App Admin Feed</title>
<link>app.rudernlinz.at</link>
<description>An RSS feed with activities from app.rudernlinz.at</description>"#,
);
for log in Self::last(db).await {
let utc_time: DateTime<Utc> = Utc::from_utc_datetime(&Utc, &log.created_at);
let local_time = utc_time.with_timezone(&Local);
ret.push_str("<item><title>");
ret.push_str(&format!("({}) {}", local_time, log.msg));
ret.push_str("</title></item>");
}
ret.push_str("</channel>");
ret.push_str("</rss>");
ret.replace('\n', "")
}
pub async fn show(db: &SqlitePool) -> String {
let mut ret = String::new();
for log in Self::last(db).await {
let utc_time: DateTime<Utc> = Utc::from_utc_datetime(&Utc, &log.created_at);
let local_time = utc_time.with_timezone(&Local);
ret.push_str(&format!("- {} - {}\n", local_time, log.msg));
}
ret
} }
} }

View File

@@ -161,11 +161,6 @@ impl Mail {
continue; continue;
} }
} }
if user.has_role(db, "schnupperant").await {
continue;
}
if !user.has_role(db, "paid").await || test.is_some() { if !user.has_role(db, "paid").await || test.is_some() {
let mut is_family = false; let mut is_family = false;
let mut send_to = String::new(); let mut send_to = String::new();
@@ -278,11 +273,6 @@ Der Vorstand");
continue; continue;
} }
} }
if user.has_role(db, "schnupperant").await {
continue;
}
if let Some(fee) = user.fee(db).await { if let Some(fee) = user.fee(db).await {
if !fee.paid || test.is_some() { if !fee.paid || test.is_some() {
let mut is_family = false; let mut is_family = false;

View File

@@ -1,6 +1,6 @@
use std::io::Write; use std::io::Write;
use ics::{components::Property, ICalendar}; use ics::{ICalendar, components::Property};
use sqlx::SqlitePool; use sqlx::SqlitePool;
use crate::model::{event::Event, trip::Trip, user::User}; use crate::model::{event::Event, trip::Trip, user::User};
@@ -19,7 +19,7 @@ pub(crate) async fn get_personal_cal(db: &SqlitePool, user: &User) -> String {
let trips = Trip::all_with_user(db, user).await; let trips = Trip::all_with_user(db, user).await;
for trip in trips { for trip in trips {
calendar.add_event(trip.get_vevent(db, user).await); calendar.add_event(trip.get_vevent(user).await);
} }
let mut buf = Vec::new(); let mut buf = Vec::new();
write!(&mut buf, "{}", calendar).unwrap(); write!(&mut buf, "{}", calendar).unwrap();

View File

@@ -40,11 +40,7 @@ impl Ord for Role {
impl Display for Role { impl Display for Role {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(formatted_name) = &self.formatted_name { write!(f, "{}", self.name)
write!(f, "{}", formatted_name)
} else {
write!(f, "{}", self.name)
}
} }
} }

View File

@@ -23,7 +23,7 @@ impl Rower {
sqlx::query_as!( sqlx::query_as!(
User, User,
" "
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, phone, address, family_id, user_token SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token
FROM user FROM user
WHERE id in (SELECT rower_id FROM rower WHERE logbook_id=?) WHERE id in (SELECT rower_id FROM rower WHERE logbook_id=?)
", ",

View File

@@ -145,15 +145,7 @@ WHERE trip_details.id=?
.ok() .ok()
} }
async fn trip_type(&self, db: &SqlitePool) -> Option<TripType> { pub(crate) async fn get_vevent(self, user: &User) -> ics::Event {
if let Some(trip_type_id) = self.trip_type_id {
TripType::find_by_id(db, trip_type_id).await
} else {
None
}
}
pub(crate) async fn get_vevent<'a>(self, db: &'a SqlitePool, user: &'a User) -> ics::Event<'a> {
let mut vevent = let mut vevent =
ics::Event::new(format!("trip-{}@rudernlinz.at", self.id), "19900101T180000"); ics::Event::new(format!("trip-{}@rudernlinz.at", self.id), "19900101T180000");
let time_str = self.planned_starting_time.replace(':', ""); let time_str = self.planned_starting_time.replace(':', "");
@@ -171,15 +163,7 @@ WHERE trip_details.id=?
let original_time = NaiveTime::parse_from_str(&self.planned_starting_time, "%H:%M") let original_time = NaiveTime::parse_from_str(&self.planned_starting_time, "%H:%M")
.expect("Failed to parse time"); .expect("Failed to parse time");
let long_trip = match self.trip_type(db).await { let later_time = original_time + Duration::hours(3);
Some(a) if a.name == "Lange Ausfahrt" => true,
_ => false,
};
let later_time = if long_trip {
original_time + Duration::hours(6)
} else {
original_time + Duration::hours(3)
};
if later_time > original_time { if later_time > original_time {
// Check if no day-overflow // Check if no day-overflow
let time_three_hours_later = later_time.format("%H%M").to_string(); let time_three_hours_later = later_time.format("%H%M").to_string();
@@ -583,9 +567,11 @@ mod test {
let last_notification = &Notification::for_user(&pool, &cox).await[0]; let last_notification = &Notification::for_user(&pool, &cox).await[0];
assert!(last_notification assert!(
.message last_notification
.starts_with("cox2 hat eine Ausfahrt zur selben Zeit")); .message
.starts_with("cox2 hat eine Ausfahrt zur selben Zeit")
);
} }
#[sqlx::test] #[sqlx::test]

View File

@@ -2,10 +2,7 @@
use super::{AllowedToEditPaymentStatusUser, ManageUserUser, User}; use super::{AllowedToEditPaymentStatusUser, ManageUserUser, User};
use crate::model::{ use crate::model::{
activity::{self, ActivityBuilder}, activity::ActivityBuilder, family::Family, mail::valid_mails, notification::Notification,
family::Family,
mail::valid_mails,
notification::Notification,
role::Role, role::Role,
}; };
use chrono::NaiveDate; use chrono::NaiveDate;
@@ -17,17 +14,15 @@ impl User {
&self, &self,
db: &SqlitePool, db: &SqlitePool,
updated_by: &ManageUserUser, updated_by: &ManageUserUser,
user: &User,
note: &str, note: &str,
) -> Result<(), String> { ) -> Result<(), String> {
let note = note.trim(); let note = note.trim();
ActivityBuilder::from(activity::Reason::UserDataChange( ActivityBuilder::new(&format!("({updated_by}) {note}"))
updated_by, .relevant_for_user(user)
self, .save(db)
note.to_string(), .await;
))
.save(db)
.await;
Ok(()) Ok(())
} }
@@ -52,11 +47,18 @@ impl User {
.unwrap(); //Okay, because we can only create a User of a valid id .unwrap(); //Okay, because we can only create a User of a valid id
let msg = match &self.mail { let msg = match &self.mail {
Some(old_mail) => format!("Mail-Adresse von {old_mail} auf {new_mail} geändert."), Some(old_mail) => {
None => format!("Neue Mail-Adresse für: {new_mail}"), format!(
"{updated_by} hat die Mail-Adresse von {self} von {old_mail} auf {new_mail} geändert."
)
}
None => {
format!("{updated_by} eine neue Mail-Adresse für {self} hinzugefügt: {new_mail}")
}
}; };
ActivityBuilder::from(activity::Reason::UserDataChange(updated_by, self, msg)) ActivityBuilder::new(&msg)
.relevant_for_user(self)
.save(db) .save(db)
.await; .await;
@@ -87,16 +89,19 @@ impl User {
query.execute(db).await.unwrap(); //Okay, because we can only create a User of a valid id query.execute(db).await.unwrap(); //Okay, because we can only create a User of a valid id
let msg = match &self.phone { let msg = match &self.phone {
Some(old_phone) if new_phone.is_empty() => { Some(old_phone) if new_phone.is_empty() => format!(
format!("Telefonnummer wurde entfernt (alte Nummer: {old_phone})") "{updated_by} hat die Telefonnummer von {self} entfernt (alte Nummer: {old_phone})"
} ),
Some(old_phone) => { Some(old_phone) => format!(
format!("Telefonnummer wurde von {old_phone} auf {new_phone} geändert.") "{updated_by} hat die Telefonnummer von {self} von {old_phone} auf {new_phone} geändert."
} ),
None => format!("Neue Telefonnummer hinzugefügt: {new_phone}"), None => format!(
"{updated_by} hat eine neue Telefonnummer für {self} hinzugefügt: {new_phone}"
),
}; };
ActivityBuilder::from(activity::Reason::UserDataChange(updated_by, self, msg)) ActivityBuilder::new(&msg)
.relevant_for_user(self)
.save(db) .save(db)
.await; .await;
} }
@@ -306,17 +311,6 @@ impl User {
None, None,
) )
.await; .await;
Notification::create(
db,
self,
&format!(
"Liebe neue Steuerperson, gratuliere zur geschafften Steuerprüfung 💪. Du kannst ab sofort selber Ausfahrten ausschreiben und der Steuerpersonen Signal-Gruppe beitreten: https://signal.group/#CjQKIHJInNb3zSVW7ipLo7_ygIqVxhxUaaNYx4sy2jdklLsIEhBHJNM2KZM1UnBdQxWy_Gdp"
),
"Gratulation",
None,
None,
)
.await;
ActivityBuilder::new(&format!("{updated_by} hat {self} zur Steuerperson gemacht")) ActivityBuilder::new(&format!("{updated_by} hat {self} zur Steuerperson gemacht"))
.relevant_for_user(self) .relevant_for_user(self)
.save(db) .save(db)
@@ -348,13 +342,13 @@ impl User {
Notification::create_for_role( Notification::create_for_role(
db, db,
&vorstand, &vorstand,
&format!("Lieber Vorstand, {self} ist ab sofort kein {old} mehr."), &format!("Lieber Vorstand, {self} ist ab kein {old} mehr."),
"Steuerperson--;", "Steuerperson --",
None, None,
None, None,
) )
.await; .await;
ActivityBuilder::new(&format!("{updated_by} hat {self} zum normalen Mitglied gemacht (keine Steuerperson/Schiffsführer mehr)")) ActivityBuilder::new(&format!("{updated_by} hat {self} zum normalen Mitlgied gemacht (keine Steuerperson/Schiffsführer mehr)"))
.relevant_for_user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;
@@ -377,14 +371,14 @@ impl User {
if let Some(old_financial) = self.financial(db).await { if let Some(old_financial) = self.financial(db).await {
self.remove_role(db, updated_by, &old_financial).await?; self.remove_role(db, updated_by, &old_financial).await?;
old.push_str(&old_financial.to_string()); old.push_str(&old_financial.name);
} else { } else {
old.push_str("Keine Ermäßigung"); old.push_str("Keine Ermäßigung");
} }
if let Some(new_financial) = financial { if let Some(new_financial) = financial {
self.add_role(db, updated_by, &new_financial).await?; self.add_role(db, updated_by, &new_financial).await?;
new.push_str(&new_financial.to_string()); new.push_str(&new_financial.name);
} else { } else {
new.push_str("Keine Ermäßigung"); new.push_str("Keine Ermäßigung");
} }
@@ -519,15 +513,6 @@ impl User {
Ok(()) Ok(())
} }
pub(crate) async fn remove_membership_pdf(&self, db: &SqlitePool, updated_by: &ManageUserUser) {
sqlx::query!(
"UPDATE user SET membership_pdf = null where id = ?",
self.id
)
.execute(db)
.await
.unwrap(); //Okay, because we can only create a User of a valid id
}
pub(crate) async fn add_membership_pdf( pub(crate) async fn add_membership_pdf(
&self, &self,
db: &SqlitePool, db: &SqlitePool,

View File

@@ -1,8 +1,7 @@
use super::User; use super::User;
use crate::{ use crate::{
BOAT_STORAGE, DUAL_MEMBERSHIP, EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE, FAMILY_TWO, FOERDERND, BOAT_STORAGE, EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE, FAMILY_TWO, FOERDERND, REGULAR,
REGULAR, RENNRUDERBEITRAG, STUDENT_OR_PUPIL, TRIAL_ROWING, TRIAL_ROWING_REDUCED, RENNRUDERBEITRAG, STUDENT_OR_PUPIL, UNTERSTUETZEND, model::family::Family,
UNTERSTUETZEND, model::family::Family,
}; };
use chrono::{Datelike, Local, NaiveDate}; use chrono::{Datelike, Local, NaiveDate};
use serde::Serialize; use serde::Serialize;
@@ -69,7 +68,6 @@ impl User {
if !self.has_role(db, "Donau Linz").await if !self.has_role(db, "Donau Linz").await
&& !self.has_role(db, "Unterstützend").await && !self.has_role(db, "Unterstützend").await
&& !self.has_role(db, "Förderndes Mitglied").await && !self.has_role(db, "Förderndes Mitglied").await
&& !self.has_role(db, "schnupperant").await
{ {
return None; return None;
} }
@@ -109,7 +107,6 @@ impl User {
if !self.has_role(db, "Donau Linz").await if !self.has_role(db, "Donau Linz").await
&& !self.has_role(db, "Unterstützend").await && !self.has_role(db, "Unterstützend").await
&& !self.has_role(db, "Förderndes Mitglied").await && !self.has_role(db, "Förderndes Mitglied").await
&& !self.has_role(db, "schnupperant").await
{ {
return fee; return fee;
} }
@@ -129,16 +126,13 @@ impl User {
); );
} }
if !self.has_role(db, "schnupperant").await { if let Some(member_since_date) = &self.member_since_date {
if let Some(member_since_date) = &self.member_since_date { if let Ok(member_since_date) = NaiveDate::parse_from_str(member_since_date, "%Y-%m-%d")
if let Ok(member_since_date) = {
NaiveDate::parse_from_str(member_since_date, "%Y-%m-%d") if member_since_date.year() == Local::now().year()
&& !self.has_role(db, "no-einschreibgebuehr").await
{ {
if member_since_date.year() == Local::now().year() fee.add("Einschreibgebühr".into(), EINSCHREIBGEBUEHR);
&& !self.has_role(db, "no-einschreibgebuehr").await
{
fee.add("Einschreibgebühr".into(), EINSCHREIBGEBUEHR);
}
} }
} }
} }
@@ -156,13 +150,7 @@ impl User {
false false
}; };
if self.has_role(db, "schnupperant").await { if self.has_role(db, "Unterstützend").await {
if self.has_role(db, "Student").await || self.has_role(db, "Schüler").await {
fee.add("Schnupperkurs (reduziert)".into(), TRIAL_ROWING_REDUCED);
} else {
fee.add("Schnupperkurs".into(), TRIAL_ROWING);
}
} else if self.has_role(db, "Unterstützend").await {
fee.add("Unterstützendes Mitglied".into(), UNTERSTUETZEND); fee.add("Unterstützendes Mitglied".into(), UNTERSTUETZEND);
} else if self.has_role(db, "Förderndes Mitglied").await { } else if self.has_role(db, "Förderndes Mitglied").await {
fee.add("Förderndes Mitglied".into(), FOERDERND); fee.add("Förderndes Mitglied".into(), FOERDERND);
@@ -175,18 +163,6 @@ impl User {
} }
} else if self.has_role(db, "Ehrenmitglied").await { } else if self.has_role(db, "Ehrenmitglied").await {
fee.add("Ehrenmitglied".into(), 0); fee.add("Ehrenmitglied".into(), 0);
} else if self.has_role(db, "dual_membership").await {
if halfprice {
fee.add(
"Doppelmitgliedschaft mit anderem österr. Ruderverein (Halbpreis)".into(),
DUAL_MEMBERSHIP / 2,
);
} else {
fee.add(
"Doppelmitgliedschaft mit anderem österr. Ruderverein".into(),
DUAL_MEMBERSHIP,
);
}
} else if halfprice { } else if halfprice {
fee.add("Mitgliedsbeitrag (Halbpreis)".into(), REGULAR / 2); fee.add("Mitgliedsbeitrag (Halbpreis)".into(), REGULAR / 2);
} else { } else {
@@ -194,19 +170,6 @@ impl User {
} }
} }
if !self.has_role(db, "schnupperant").await
&& self.has_role(db, "participated_schnupperkurs").await
{
if self.has_role(db, "Student").await || self.has_role(db, "Schüler").await {
fee.add(
"Anrechnung reduzierter Schnupperkurs".into(),
-TRIAL_ROWING_REDUCED,
);
} else {
fee.add("Anrechnung Schnupperkurs".into(), -TRIAL_ROWING);
}
}
fee fee
} }
} }

View File

@@ -1,21 +1,20 @@
use std::{fmt::Display, ops::DerefMut}; use std::{fmt::Display, ops::DerefMut};
use argon2::{Argon2, PasswordHasher, password_hash::SaltString}; use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
use chrono::{Datelike, Local, NaiveDate}; use chrono::{Datelike, Local, NaiveDate};
use log::info; use log::info;
use rocket::async_trait; use rocket::async_trait;
use rocket::{ use rocket::{
Request,
http::{Cookie, Status}, http::{Cookie, Status},
request::{FromRequest, Outcome}, request::{FromRequest, Outcome},
time::{Duration, OffsetDateTime}, time::{Duration, OffsetDateTime},
Request,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use super::activity::{ActivityBuilder, ReasonAuth}; use super::activity::ActivityBuilder;
use super::{ use super::{
Day,
log::Log, log::Log,
logbook::Logbook, logbook::Logbook,
mail::Mail, mail::Mail,
@@ -24,6 +23,7 @@ use super::{
role::Role, role::Role,
stat::Stat, stat::Stat,
tripdetails::TripDetails, tripdetails::TripDetails,
Day,
}; };
use crate::AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD; use crate::AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD;
use scheckbuch::ScheckbuchUser; use scheckbuch::ScheckbuchUser;
@@ -53,6 +53,7 @@ pub struct User {
pub birthdate: Option<String>, pub birthdate: Option<String>,
pub mail: Option<String>, pub mail: Option<String>,
pub nickname: Option<String>, pub nickname: Option<String>,
pub notes: Option<String>,
pub phone: Option<String>, pub phone: Option<String>,
pub address: Option<String>, pub address: Option<String>,
pub family_id: Option<i64>, pub family_id: Option<i64>,
@@ -261,7 +262,7 @@ AND r.cluster = 'skill';
sqlx::query_as!( sqlx::query_as!(
Self, Self,
" "
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, phone, address, family_id, user_token SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token
FROM user FROM user
WHERE id like ? WHERE id like ?
", ",
@@ -276,7 +277,7 @@ WHERE id like ?
sqlx::query_as!( sqlx::query_as!(
Self, Self,
" "
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, phone, address, family_id, user_token SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token
FROM user FROM user
WHERE id like ? WHERE id like ?
", ",
@@ -293,7 +294,7 @@ WHERE id like ?
sqlx::query_as!( sqlx::query_as!(
Self, Self,
" "
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, phone, address, family_id, user_token SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token
FROM user FROM user
WHERE lower(name)=? WHERE lower(name)=?
", ",
@@ -338,7 +339,7 @@ WHERE lower(name)=?
pub async fn all_with_order(db: &SqlitePool, sort: &str, asc: bool) -> Vec<Self> { pub async fn all_with_order(db: &SqlitePool, sort: &str, asc: bool) -> Vec<Self> {
let mut query = format!( let mut query = format!(
" "
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, phone, address, family_id, user_token SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token
FROM user FROM user
WHERE deleted = 0 WHERE deleted = 0
ORDER BY {} ORDER BY {}
@@ -366,7 +367,7 @@ WHERE lower(name)=?
sqlx::query_as!( sqlx::query_as!(
Self, Self,
" "
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, phone, address, family_id, user_token SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token
FROM user u FROM user u
JOIN user_role ur ON u.id = ur.user_id JOIN user_role ur ON u.id = ur.user_id
WHERE ur.role_id = ? AND deleted = 0 WHERE ur.role_id = ? AND deleted = 0
@@ -382,14 +383,14 @@ ORDER BY name;
sqlx::query_as!( sqlx::query_as!(
Self, Self,
" "
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, phone, address, family_id, user_token FROM user SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token FROM user
WHERE family_id IS NOT NULL WHERE family_id IS NOT NULL
GROUP BY family_id GROUP BY family_id
UNION UNION
-- Select users with a null family_id, without grouping -- Select users with a null family_id, without grouping
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, phone, address, family_id, user_token FROM user SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token FROM user
WHERE family_id IS NULL; WHERE family_id IS NULL;
" "
) )
@@ -407,7 +408,7 @@ WHERE family_id IS NULL;
sqlx::query_as!( sqlx::query_as!(
Self, Self,
" "
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, phone, address, family_id, user_token SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token
FROM user FROM user
WHERE deleted = 0 AND (SELECT COUNT(*) FROM user_role WHERE user_id=user.id AND role_id = (SELECT id FROM role WHERE name = 'cox')) > 0 WHERE deleted = 0 AND (SELECT COUNT(*) FROM user_role WHERE user_id=user.id AND role_id = (SELECT id FROM role WHERE name = 'cox')) > 0
ORDER BY last_access DESC ORDER BY last_access DESC
@@ -465,29 +466,53 @@ ASKÖ Ruderverein Donau Linz", self.name),
pub async fn login(db: &SqlitePool, name: &str, pw: &str) -> Result<Self, LoginError> { pub async fn login(db: &SqlitePool, name: &str, pw: &str) -> Result<Self, LoginError> {
let name = name.trim().to_lowercase(); // just to make sure... let name = name.trim().to_lowercase(); // just to make sure...
let Some(user) = User::find_by_name(db, &name).await else { let Some(user) = User::find_by_name(db, &name).await else {
Log::create(db, format!("Username ({name}) not found (tried to login)")).await; if ![
"n-sageder",
"p-hofer",
"marie-birner",
"daniel-kortschak",
"rudernlinz",
"m-birner",
"s-sollberger",
"d-kortschak",
"wwwadmin",
"wadminw",
"admin",
"m sageder",
"d kortschak",
"a almousa",
"p hofer",
"s sollberger",
"n sageder",
"wp-system",
"s.sollberger",
"m.birner",
"m-sageder",
"a-almousa",
"m.sageder",
"n.sageder",
"a.almousa",
"p.hofer",
"philipp-hofer",
"d.kortschak",
"[login]",
]
.contains(&name.as_str())
{
Log::create(db, format!("Username ({name}) not found (tried to login)")).await;
}
return Err(LoginError::InvalidAuthenticationCombo); // Username not found return Err(LoginError::InvalidAuthenticationCombo); // Username not found
}; };
if user.deleted { if user.deleted {
if let Some(board) = Role::find_by_name(db, "Vorstand").await { ActivityBuilder::new(&format!(
Notification::create_for_role(
db,
&board,
&format!(
"User {user} wollte sich einloggen, klappte jedoch nicht weil er gelöscht wurde." "User {user} wollte sich einloggen, klappte jedoch nicht weil er gelöscht wurde."
), ))
"Fehlgeschlagener Login", .relevant_for_user(&user)
None, .save(db)
None, .await;
)
.await;
}
ActivityBuilder::from(ReasonAuth::DeletedUserLogin(&user))
.save(db)
.await;
return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has
//been deleted //been deleted
} }
if let Some(user_pw) = user.pw.as_ref() { if let Some(user_pw) = user.pw.as_ref() {
@@ -495,9 +520,12 @@ ASKÖ Ruderverein Donau Linz", self.name),
if password_hash == user_pw { if password_hash == user_pw {
return Ok(user); return Ok(user);
} }
ActivityBuilder::from(ReasonAuth::WrongPw(&user)) ActivityBuilder::new(&format!(
.save(db) "User {user} wollte sich einloggen, hat jedoch das falsche Passwort angegeben."
.await; ))
.relevant_for_user(&user)
.save(db)
.await;
Err(LoginError::InvalidAuthenticationCombo) Err(LoginError::InvalidAuthenticationCombo)
} else { } else {
info!("User {name} has no PW set"); info!("User {name} has no PW set");
@@ -505,19 +533,17 @@ ASKÖ Ruderverein Donau Linz", self.name),
} }
} }
pub async fn reset_pw(&self, db: &SqlitePool, changed_by: &ManageUserUser) { pub async fn reset_pw(&self, db: &SqlitePool) {
sqlx::query!("UPDATE user SET pw = null where id = ?", self.id) sqlx::query!("UPDATE user SET pw = null where id = ?", self.id)
.execute(db) .execute(db)
.await .await
.unwrap(); //Okay, because we can only create a User of a valid id .unwrap(); //Okay, because we can only create a User of a valid id
// TODO: add responsible person // TODO: add responsible person
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!("Passwort von User {self} wurde zurückgesetzt."))
"{changed_by} hat das Passwort von User {self} zurückgesetzt." .relevant_for_user(self)
)) .save(db)
.relevant_for_user(self) .await;
.save(db)
.await;
} }
pub async fn update_pw(&self, db: &SqlitePool, pw: &str) { pub async fn update_pw(&self, db: &SqlitePool, pw: &str) {
@@ -526,10 +552,12 @@ ASKÖ Ruderverein Donau Linz", self.name),
.execute(db) .execute(db)
.await .await
.unwrap(); //Okay, because we can only create a User of a valid id .unwrap(); //Okay, because we can only create a User of a valid id
ActivityBuilder::new(&format!("{self} hat sein Passwort geändert.")) ActivityBuilder::new(&format!(
.relevant_for_user(self) "Passwort von User {self} wurde erfolgreich geändert."
.save(db) ))
.await; .relevant_for_user(self)
.save(db)
.await;
} }
fn get_hashed_pw(pw: &str) -> String { fn get_hashed_pw(pw: &str) -> String {
@@ -549,6 +577,10 @@ ASKÖ Ruderverein Donau Linz", self.name),
.execute(db) .execute(db)
.await .await
.unwrap(); //Okay, because we can only create a User of a valid id .unwrap(); //Okay, because we can only create a User of a valid id
ActivityBuilder::new(&format!("User {self} hat sich eingeloggt."))
.relevant_for_user(self)
.save(db)
.await;
} }
pub async fn delete(&self, db: &SqlitePool, deleted_by: &ManageUserUser) { pub async fn delete(&self, db: &SqlitePool, deleted_by: &ManageUserUser) {
@@ -590,9 +622,9 @@ ASKÖ Ruderverein Donau Linz", self.name),
pub(crate) async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 { pub(crate) async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 {
if self.allowed_to_steer(db).await { if self.allowed_to_steer(db).await {
let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); //Ok, let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); //Ok,
//december //december
//has 31 //has 31
//days //days
let days_left_in_year = end_of_year let days_left_in_year = end_of_year
.signed_duration_since(Local::now().date_naive()) .signed_duration_since(Local::now().date_naive())
.num_days() .num_days()
@@ -601,9 +633,9 @@ ASKÖ Ruderverein Donau Linz", self.name),
if days_left_in_year <= 31 { if days_left_in_year <= 31 {
let end_of_next_year = let end_of_next_year =
NaiveDate::from_ymd_opt(Local::now().year() + 1, 12, 31).unwrap(); //Ok, NaiveDate::from_ymd_opt(Local::now().year() + 1, 12, 31).unwrap(); //Ok,
//december //december
//has 31 //has 31
//days //days
end_of_next_year end_of_next_year
.signed_duration_since(Local::now().date_naive()) .signed_duration_since(Local::now().date_naive())
.num_days() .num_days()
@@ -835,8 +867,8 @@ special_user!(SteeringUser, +"cox", +"Bootsführer");
special_user!(AdminUser, +"admin"); special_user!(AdminUser, +"admin");
special_user!(AllowedForPlannedTripsUser, +"Donau Linz", +"scheckbuch", +"Förderndes Mitglied"); special_user!(AllowedForPlannedTripsUser, +"Donau Linz", +"scheckbuch", +"Förderndes Mitglied");
special_user!(DonauLinzUser, +"Donau Linz", -"Unterstützend", -"Förderndes Mitglied"); // TODO: special_user!(DonauLinzUser, +"Donau Linz", -"Unterstützend", -"Förderndes Mitglied"); // TODO:
// remove -> // remove ->
// RegularUser // RegularUser
special_user!(SchnupperBetreuerUser, +"schnupper-betreuer"); special_user!(SchnupperBetreuerUser, +"schnupper-betreuer");
special_user!(VorstandUser, +"admin", +"Vorstand"); special_user!(VorstandUser, +"admin", +"Vorstand");
special_user!(EventUser, +"manage_events"); special_user!(EventUser, +"manage_events");
@@ -892,7 +924,7 @@ impl UserWithMembershipPdf {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::{model::user::ManageUserUser, testdb}; use crate::testdb;
use super::User; use super::User;
use sqlx::SqlitePool; use sqlx::SqlitePool;
@@ -950,30 +982,25 @@ mod test {
#[sqlx::test] #[sqlx::test]
fn wrong_pw() { fn wrong_pw() {
let pool = testdb!(); let pool = testdb!();
assert!( assert!(User::login(&pool, "admin".into(), "admi".into())
User::login(&pool, "admin".into(), "admi".into()) .await
.await .is_err());
.is_err()
);
} }
#[sqlx::test] #[sqlx::test]
fn wrong_username() { fn wrong_username() {
let pool = testdb!(); let pool = testdb!();
assert!( assert!(User::login(&pool, "admi".into(), "admin".into())
User::login(&pool, "admi".into(), "admin".into()) .await
.await .is_err());
.is_err()
);
} }
#[sqlx::test] #[sqlx::test]
fn reset() { fn reset() {
let pool = testdb!(); let pool = testdb!();
let user = User::find_by_id(&pool, 1).await.unwrap(); let user = User::find_by_id(&pool, 1).await.unwrap();
let changed_by = ManageUserUser::new(&pool, &user).await.unwrap();
user.reset_pw(&pool, &changed_by).await; user.reset_pw(&pool).await;
let user = User::find_by_id(&pool, 1).await.unwrap(); let user = User::find_by_id(&pool, 1).await.unwrap();
assert_eq!(user.pw, None); assert_eq!(user.pw, None);
@@ -984,11 +1011,9 @@ mod test {
let pool = testdb!(); let pool = testdb!();
let user = User::find_by_id(&pool, 1).await.unwrap(); let user = User::find_by_id(&pool, 1).await.unwrap();
assert!( assert!(User::login(&pool, "admin".into(), "abc".into())
User::login(&pool, "admin".into(), "abc".into()) .await
.await .is_err());
.is_err()
);
user.update_pw(&pool, "abc".into()).await; user.update_pw(&pool, "abc".into()).await;

View File

@@ -93,8 +93,6 @@ Beim nächsten Treffen im Verein, erinnere jemand vom Vorstand (https://rudernli
Damit du dich noch mehr verbunden fühlst (:-)), haben wir im Bootshaus ein WLAN für Vereinsmitglieder 'ASKÖ Ruderverein Donau Linz' eingerichtet. Das Passwort dafür lautet 'donau1921' (ohne Anführungszeichen). Bitte gib das Passwort an keine vereinsfremden Personen weiter. Damit du dich noch mehr verbunden fühlst (:-)), haben wir im Bootshaus ein WLAN für Vereinsmitglieder 'ASKÖ Ruderverein Donau Linz' eingerichtet. Das Passwort dafür lautet 'donau1921' (ohne Anführungszeichen). Bitte gib das Passwort an keine vereinsfremden Personen weiter.
Falls du deinen Mitgliedsbeitrag noch nicht bezahlt hast, erledige dies bitte demnächst. Den genauen Betrag und einen QR Code, den du mit deiner Bankapp scannen kannst findest du unter https://app.rudernlinz.at/planned
Wir freuen uns darauf, dich bald am Wasser zu sehen und gemeinsam tolle Erfahrungen zu sammeln! Wir freuen uns darauf, dich bald am Wasser zu sehen und gemeinsam tolle Erfahrungen zu sammeln!
Riemen- & Dollenbruch Riemen- & Dollenbruch

View File

@@ -75,9 +75,9 @@ impl ScheckbuchUser {
Notification::create_for_steering_people( Notification::create_for_steering_people(
db, db,
&format!( &format!(
"Liebe Steuerberechtigte, {} hatte ein Scheckbuch und ist nun seit {} ein neues reguläres Mitglied. 🎉", "Liebe Steuerberechtigte, {} hatte ein Scheckbuch und ist nun seit {} es ein neues reguläres Mitglied. 🎉",
self.name, self.name,
member_since self.member_since_date.clone().unwrap()
), ),
"Neues Vereinsmitglied", "Neues Vereinsmitglied",
None, None,
@@ -133,9 +133,9 @@ impl ScheckbuchUser {
db, db,
&vorstand, &vorstand,
&format!( &format!(
"Lieber Vorstand, {} hatte ein Scheckbuch und ist nun seit {} ein neues unterstützendes Mitglied.", "Lieber Vorstand, {} hatte ein Scheckbuch und ist nun seit {} es ein neues unterstützendes Mitglied.",
self.name, self.name,
member_since self.member_since_date.clone().unwrap()
), ),
"Neues unterstützendes Vereinsmitglied", "Neues unterstützendes Vereinsmitglied",
None, None,
@@ -187,9 +187,9 @@ impl ScheckbuchUser {
db, db,
&vorstand, &vorstand,
&format!( &format!(
"Lieber Vorstand, {} hatte ein Scheckbuch und ist nun seit {} ein neues förderndes Mitglied.", "Lieber Vorstand, {} hatte ein Scheckbuch und ist nun seit {} es ein neues förderndes Mitglied.",
self.name, self.name,
member_since self.member_since_date.clone().unwrap()
), ),
"Neues förderndes Vereinsmitglied", "Neues förderndes Vereinsmitglied",
None, None,

View File

@@ -65,32 +65,20 @@ impl SchnupperantUser {
.await?; .await?;
// Change roles // Change roles
let paid = Role::find_by_name(db, "paid").await.unwrap(); let regular = Role::find_by_name(db, "Donau Linz").await.unwrap();
if self.user.remove_role(db, changed_by, &paid).await.is_err() {
self.remove_membership_pdf(db, changed_by).await;
return Err("Kann noch kein normales Mitglied werden, da die Schnupperkurs-Gebühr noch nicht bezahlt wurde.".into());
}
let scheckbook = Role::find_by_name(db, "schnupperant").await.unwrap(); let scheckbook = Role::find_by_name(db, "schnupperant").await.unwrap();
self.user.remove_role(db, changed_by, &scheckbook).await?; self.user.remove_role(db, changed_by, &scheckbook).await?;
let regular = Role::find_by_name(db, "Donau Linz").await.unwrap();
self.user.add_role(db, changed_by, &regular).await?; self.user.add_role(db, changed_by, &regular).await?;
let participated_schnupperkurs = Role::find_by_name(db, "participated_schnupperkurs")
.await
.unwrap();
self.user
.add_role(db, changed_by, &participated_schnupperkurs)
.await?;
// Notify // Notify
let regular = RegularUser::new(db, &self.user).await.unwrap(); let regular = RegularUser::new(db, &self.user).await.unwrap();
regular.send_welcome_mail_to_user(db, smtp_pw).await?; regular.send_welcome_mail_to_user(db, smtp_pw).await?;
Notification::create_for_steering_people( Notification::create_for_steering_people(
db, db,
&format!( &format!(
"Liebe Steuerberechtigte, {} nahm an unserem Schnupperkurs teil und ist nun seit {member_since} ein neues reguläres Mitglied. 🎉", "Liebe Steuerberechtigte, {} nahm an unserem Schnupperkurs teil und ist nun seit {} ein neues reguläres Mitglied. 🎉",
self.name self.name,
self.member_since_date.clone().unwrap()
), ),
"Neues Vereinsmitglied", "Neues Vereinsmitglied",
None, None,
@@ -215,11 +203,6 @@ impl SchnupperantUser {
.await?; .await?;
// Change roles // Change roles
let paid = Role::find_by_name(db, "paid").await.unwrap();
if self.user.remove_role(db, changed_by, &paid).await.is_err() {
self.remove_membership_pdf(db, changed_by).await;
return Err("Kann noch kein normales Mitglied werden, da die Schnupperkurs-Gebühr noch nicht bezahlt wurde.".into());
}
let unterstuetzend = Role::find_by_name(db, "Unterstützend").await.unwrap(); let unterstuetzend = Role::find_by_name(db, "Unterstützend").await.unwrap();
let scheckbook = Role::find_by_name(db, "schnupperant").await.unwrap(); let scheckbook = Role::find_by_name(db, "schnupperant").await.unwrap();
self.user.remove_role(db, changed_by, &scheckbook).await?; self.user.remove_role(db, changed_by, &scheckbook).await?;
@@ -284,11 +267,6 @@ impl SchnupperantUser {
.await?; .await?;
// Change roles // Change roles
let paid = Role::find_by_name(db, "paid").await.unwrap();
if self.user.remove_role(db, changed_by, &paid).await.is_err() {
self.remove_membership_pdf(db, changed_by).await;
return Err("Kann noch kein normales Mitglied werden, da die Schnupperkurs-Gebühr noch nicht bezahlt wurde.".into());
}
let unterstuetzend = Role::find_by_name(db, "Förderndes Mitglied").await.unwrap(); let unterstuetzend = Role::find_by_name(db, "Förderndes Mitglied").await.unwrap();
let scheckbook = Role::find_by_name(db, "schnupperant").await.unwrap(); let scheckbook = Role::find_by_name(db, "schnupperant").await.unwrap();
self.user.remove_role(db, changed_by, &scheckbook).await?; self.user.remove_role(db, changed_by, &scheckbook).await?;
@@ -329,7 +307,7 @@ impl SchnupperantUser {
// TODO: make private // TODO: make private
pub(crate) async fn notify(&self, db: &SqlitePool, smtp_pw: &str) -> Result<(), String> { pub(crate) async fn notify(&self, db: &SqlitePool, smtp_pw: &str) -> Result<(), String> {
self.notify_coxes_about_new_schnupperant(db).await; self.notify_coxes_about_new_scheckbuch(db).await;
self.send_welcome_mail_to_user(db, smtp_pw).await?; self.send_welcome_mail_to_user(db, smtp_pw).await?;
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
@@ -357,27 +335,19 @@ impl SchnupperantUser {
mail, mail,
"ASKÖ Ruderverein Donau Linz | Anmeldung Schnupperkurs", "ASKÖ Ruderverein Donau Linz | Anmeldung Schnupperkurs",
format!( format!(
"Hallo {0}, "Hallo {0},
es freut uns sehr, dich bei unserem Schnupperkurs willkommen heißen zu dürfen. es freut uns sehr, dich bei unserem Schnupperkurs willkommen heißen zu dürfen. Detaillierte Informationen folgen noch, ich werde sie dir ein paar Tage vor dem Termin zusenden.
Bitte überweise die {1} € auf unser Bankkonto (IBAN: AT58 2032 0321 0072 9256) und gib beim Verwendungszweck 'Schnupperkurs {0}' an.
Detaillierte Informationen folgen noch, du wirst sie ein paar Tage vor dem Termin bekommen (wenn das Wetter/Wasserstand/... abschätzbar ist).
Riemen- & Dollenbruch, Riemen- & Dollenbruch,
ASKÖ Ruderverein Donau Linz", ASKÖ Ruderverein Donau Linz", self.name),
self.name,
self.fee(db).await.unwrap().sum_in_cents/100
),
smtp_pw, smtp_pw,
) ).await?;
.await?;
Ok(()) Ok(())
} }
async fn notify_coxes_about_new_schnupperant(&self, db: &SqlitePool) { async fn notify_coxes_about_new_scheckbuch(&self, db: &SqlitePool) {
if let Some(role) = Role::find_by_name(db, "schnupper-betreuer").await { if let Some(role) = Role::find_by_name(db, "schnupper-betreuer").await {
Notification::create_for_role( Notification::create_for_role(
db, db,

View File

@@ -3,7 +3,10 @@ use rocket::{FromForm, Route, State, form::Form, get, post, routes};
use rocket_dyn_templates::{Template, context}; use rocket_dyn_templates::{Template, context};
use sqlx::SqlitePool; use sqlx::SqlitePool;
use crate::model::{activity::Activity, role::Role, user::AdminUser}; use crate::{
model::{log::Log, role::Role, user::AdminUser},
tera::Config,
};
pub mod boat; pub mod boat;
pub mod event; pub mod event;
@@ -13,9 +16,18 @@ pub mod role;
pub mod schnupper; pub mod schnupper;
pub mod user; pub mod user;
#[get("/rss?<key>")]
async fn rss(db: &State<SqlitePool>, key: &str, config: &State<Config>) -> String {
if key.eq(&config.rss_key) {
Log::generate_feed(db).await
} else {
"Not allowed".into()
}
}
#[get("/rss", rank = 2)] #[get("/rss", rank = 2)]
async fn show_activities(db: &State<SqlitePool>, _admin: AdminUser) -> String { async fn show_rss(db: &State<SqlitePool>, _admin: AdminUser) -> String {
Activity::show(db).await Log::show(db).await
} }
#[get("/list")] #[get("/list")]
@@ -71,6 +83,6 @@ pub fn routes() -> Vec<Route> {
ret.append(&mut mail::routes()); ret.append(&mut mail::routes());
ret.append(&mut event::routes()); ret.append(&mut event::routes());
ret.append(&mut role::routes()); ret.append(&mut role::routes());
ret.append(&mut routes![show_activities, show_list, list]); ret.append(&mut routes![rss, show_rss, show_list, list]);
ret ret
} }

View File

@@ -3,14 +3,13 @@ use crate::model::{
user::{AdminUser, UserWithDetails, VorstandUser}, user::{AdminUser, UserWithDetails, VorstandUser},
}; };
use rocket::{ use rocket::{
FromForm, Route, State,
form::Form, form::Form,
get, post, get, post,
request::FlashMessage, request::FlashMessage,
response::{Flash, Redirect}, response::{Flash, Redirect},
routes, routes, FromForm, Route, State,
}; };
use rocket_dyn_templates::{Template, tera::Context}; use rocket_dyn_templates::{tera::Context, Template};
use sqlx::SqlitePool; use sqlx::SqlitePool;
#[get("/role")] #[get("/role")]

View File

@@ -1,17 +1,17 @@
use crate::{ use crate::{
model::{ model::{
activity::{Activity, ActivityWithDetails}, activity::Activity,
family::Family, family::Family,
log::Log, log::Log,
logbook::Logbook, logbook::Logbook,
mail::valid_mails, mail::valid_mails,
role::Role, role::Role,
user::{ user::{
AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails,
UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser,
clubmember::ClubMemberUser, foerdernd::FoerderndUser, member::Member, clubmember::ClubMemberUser, foerdernd::FoerderndUser, member::Member,
regular::RegularUser, scheckbuch::ScheckbuchUser, schnupperant::SchnupperantUser, regular::RegularUser, scheckbuch::ScheckbuchUser, schnupperant::SchnupperantUser,
schnupperinterest::SchnupperInterestUser, unterstuetzend::UnterstuetzendUser, schnupperinterest::SchnupperInterestUser, unterstuetzend::UnterstuetzendUser,
AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails,
UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser,
}, },
}, },
tera::Config, tera::Config,
@@ -19,7 +19,6 @@ use crate::{
use chrono::NaiveDate; use chrono::NaiveDate;
use futures::future::join_all; use futures::future::join_all;
use rocket::{ use rocket::{
FromForm, Request, Route, State,
form::Form, form::Form,
fs::TempFile, fs::TempFile,
get, get,
@@ -27,9 +26,9 @@ use rocket::{
post, post,
request::{FlashMessage, FromRequest, Outcome}, request::{FlashMessage, FromRequest, Outcome},
response::{Flash, Redirect}, response::{Flash, Redirect},
routes, routes, FromForm, Request, Route, State,
}; };
use rocket_dyn_templates::{Template, tera::Context}; use rocket_dyn_templates::{tera::Context, Template};
use sqlx::SqlitePool; use sqlx::SqlitePool;
// Custom request guard to extract the Referer header // Custom request guard to extract the Referer header
@@ -136,17 +135,13 @@ async fn view(
if user.name == "Externe Steuerperson" { if user.name == "Externe Steuerperson" {
return Err(Flash::error( return Err(Flash::error(
Redirect::to("/admin/user"), Redirect::to("/admin/user"),
"Diese besondere Person kannst du dir leider nicht anschauen, mein lieber neugieriger Ruderant!", "Diese besondere Person kannst du dir leider nicht anschauen, mein lieber neugieriger Ruderant!"
)); ));
} }
let member = Member::from(db, user.clone()).await; let member = Member::from(db, user.clone()).await;
let fee = user.fee(db).await; let fee = user.fee(db).await;
let activities: Vec<ActivityWithDetails> = Activity::for_user(db, &user) let activities = Activity::for_user(db, &user).await;
.await
.into_iter()
.map(Into::into)
.collect();
let financial = Role::all_cluster(db, "financial").await; let financial = Role::all_cluster(db, "financial").await;
let user_financial = user.financial(db).await; let user_financial = user.financial(db).await;
let skill = Role::all_cluster(db, "skill").await; let skill = Role::all_cluster(db, "skill").await;
@@ -281,7 +276,7 @@ async fn resetpw(db: &State<SqlitePool>, admin: ManageUserUser, user: i32) -> Fl
format!("{} has resetted the pw for {}", admin.user.name, user.name), format!("{} has resetted the pw for {}", admin.user.name, user.name),
) )
.await; .await;
user.reset_pw(db, &admin).await; user.reset_pw(db).await;
Flash::success( Flash::success(
Redirect::to("/admin/user"), Redirect::to("/admin/user"),
format!("Passwort von {} zurückgesetzt", user.name), format!("Passwort von {} zurückgesetzt", user.name),
@@ -354,7 +349,7 @@ async fn add_note(
); );
}; };
match user.add_note(db, &admin, &data.note).await { match user.add_note(db, &admin, &user, &data.note).await {
Ok(_) => Flash::success( Ok(_) => Flash::success(
Redirect::to(format!("/admin/user/{}", user.id)), Redirect::to(format!("/admin/user/{}", user.id)),
"Notiz hinzugefügt", "Notiz hinzugefügt",

View File

@@ -1,4 +1,5 @@
use rocket::{ use rocket::{
FromForm, Request, Route, State,
form::Form, form::Form,
get, get,
http::{Cookie, CookieJar}, http::{Cookie, CookieJar},
@@ -8,13 +9,11 @@ use rocket::{
response::{Flash, Redirect}, response::{Flash, Redirect},
routes, routes,
time::{Duration, OffsetDateTime}, time::{Duration, OffsetDateTime},
FromForm, Request, Route, State,
}; };
use rocket_dyn_templates::{context, tera, Template}; use rocket_dyn_templates::{Template, context, tera};
use sqlx::SqlitePool; use sqlx::SqlitePool;
use crate::model::{ use crate::model::{
activity::{ActivityBuilder, ReasonAuth},
log::Log, log::Log,
user::{LoginError, User}, user::{LoginError, User},
}; };
@@ -83,9 +82,14 @@ async fn login(
cookies.add_private(Cookie::new("loggedin_user", format!("{}", user.id))); cookies.add_private(Cookie::new("loggedin_user", format!("{}", user.id)));
ActivityBuilder::from(ReasonAuth::SuccLogin(&user, agent.0)) Log::create(
.save(db) db,
.await; format!(
"Succ login of {} with this useragent: {}",
login.name, agent.0
),
)
.await;
// Check for redirect_url cookie and redirect accordingly // Check for redirect_url cookie and redirect accordingly
match cookies.get_private("redirect_url") { match cookies.get_private("redirect_url") {

View File

@@ -1,7 +1,6 @@
use std::net::IpAddr; use std::net::IpAddr;
use rocket::{ use rocket::{
Request, Route, State,
form::Form, form::Form,
get, get,
http::{Cookie, CookieJar}, http::{Cookie, CookieJar},
@@ -10,8 +9,9 @@ use rocket::{
response::{Flash, Redirect}, response::{Flash, Redirect},
routes, routes,
time::{Duration, OffsetDateTime}, time::{Duration, OffsetDateTime},
Request, Route, State,
}; };
use rocket_dyn_templates::{Template, context}; use rocket_dyn_templates::{context, Template};
use sqlx::SqlitePool; use sqlx::SqlitePool;
use tera::Context; use tera::Context;
@@ -585,7 +585,7 @@ mod test {
use sqlx::SqlitePool; use sqlx::SqlitePool;
use crate::model::logbook::Logbook; use crate::model::logbook::Logbook;
use crate::tera::{User, log::Boat}; use crate::tera::{log::Boat, User};
use crate::testdb; use crate::testdb;
#[sqlx::test] #[sqlx::test]

View File

@@ -3,42 +3,35 @@
{% extends "base" %} {% extends "base" %}
{% block content %} {% block content %}
<div class="max-w-screen-lg w-full dark:text-white"> <div class="max-w-screen-lg w-full dark:text-white">
<h1 class="h1">Rollen</h1> <h1 class="h1">Rolle</h1>
<div class="search-wrapper"> <div class="grid ">
<label for="name" class="sr-only">Suche</label> <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"
<input type="search" role="alert">
name="name" <h2 class="h2">Rolle</h2>
id="filter-js" {% for role in roles %}
class="search-bar" <div data-filterable="true"
placeholder="Suchen nach Namen..."> data-filter="{{ role.name }}"
</div> class="w-full border-t">
<div id="filter-result-js" class="search-result"></div> <form action="/admin/role/{{ role.id }}"
<div class="border-r border-l border-gray-200 dark:border-primary-600"> data-filterable="true"
{% for role in roles %} method="post"
<div data-filterable="true" class="bg-white dark:bg-primary-900 p-4 w-full">
data-filter="{{ role.name }} {{ role.formatted_name }}" <div class="w-full">
class="w-full border-t"> <input type="hidden" name="id" value="{{ role.id }}" />
<form action="/admin/role/{{ role.id }}" <div class="font-bold mb-1 text-black dark:text-white">
data-filterable="true" {{ role.name }}
method="post" <br />
class="bg-white dark:bg-primary-900 p-4 w-full">
<div class="w-full">
<input type="hidden" name="id" value="{{ role.id }}" />
<div class="font-bold mb-1 text-black dark:text-white">
{{ role.name }}
<br />
</div>
<div class="grid md:grid-cols-3 gap-3">
{{ macros::input(label='Name (formatiert)', name='formatted_name', type='text', value=role.formatted_name) }}
{{ macros::input(label='Beschreibung', name='desc', type='text', value=role.desc) }}
<div class="flex items-end">
<input value="Ändern" type="submit" class="w-full btn btn-primary" />
</div>
</div>
</div>
</form>
</div> </div>
{% endfor %} <div class="grid md:grid-cols-3 gap-3">
{{ macros::input(label='Formatierter Name', name='formatted_name', type='text', value=role.formatted_name) }}
{{ macros::input(label='Beschreibung', name='desc', type='text', value=role.desc) }}
<input value="Ändern" type="submit" class="w-28 btn btn-primary" />
</div>
</div>
</form>
</div>
{% endfor %}
</div>
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@@ -8,39 +8,42 @@
<summary class="px-3 cursor-pointer text-md font-bold text-primary-950 dark:text-white"> <summary class="px-3 cursor-pointer text-md font-bold text-primary-950 dark:text-white">
Neue Person hinzufügen Neue Person hinzufügen
</summary> </summary>
<div class="grid sm:grid-cols-3 gap-3 mt-3"> <div class="grid sm:grid-cols-3 gap-3 mt-3">
<button type="button" <button type="button"
onclick="document.getElementById('add-clubuser').showModal()" onclick="document.getElementById('add-clubuser').showModal()"
class="btn btn-primary">🥳 Vereinsmitglied</button> class="btn btn-primary">Vereinsmitglied</button>
<button type="button" <button type="button"
onclick="document.getElementById('add-scheckbuch').showModal()" onclick="document.getElementById('add-scheckbuch').showModal()"
class="btn btn-dark">🧑‍🏫 Scheckbuch</button> class="btn btn-dark">Scheckbuch</button>
<button type="button" <button type="button"
onclick="document.getElementById('add-schnupperkurs').showModal()" onclick="document.getElementById('add-schnupperkurs').showModal()"
class="btn btn-dark">👨‍🎓 Schnupperkurs</button> class="btn btn-dark">Schnupperkurs</button>
</div>
<dialog id="add-clubuser"
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md" </div>
onclick="document.getElementById('add-clubuser').close()"> <dialog id="add-clubuser"
<div onclick="event.stopPropagation();" class="p-3"> class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
<button type="button" onclick="document.getElementById('add-clubuser').close()">
onclick="document.getElementById('add-clubuser').close()" <div onclick="event.stopPropagation();" class="p-3">
title="Schließen" <button type="button"
class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3"> onclick="document.getElementById('add-clubuser').close()"
<svg class="inline h-5 w-5" title="Schließen"
width="16" class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3">
height="16" <svg class="inline h-5 w-5"
fill="currentColor" width="16"
viewBox="0 0 16 16"> height="16"
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path> fill="currentColor"
</svg> viewBox="0 0 16 16">
</button> <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
<div class="mt-8"> </svg>
<h2 class="h3 mb-3">Neues Vereinsmitglied</h2> </button>
<form action="/admin/user/new/clubmember" <div class="mt-8">
method="post" <h2 class="h3 mb-3">Neues Vereinsmitglied</h2>
enctype="multipart/form-data" <form action="/admin/user/new/clubmember"
class="grid gap-3"> method="post"
enctype="multipart/form-data"
class="grid gap-3">
<div> <div>
<label for="membertype" class="text-sm text-gray-600 dark:text-gray-100">Mitgliedstyp</label> <label for="membertype" class="text-sm text-gray-600 dark:text-gray-100">Mitgliedstyp</label>
<select name="membertype" id="membertype" class="input rounded-md "> <select name="membertype" id="membertype" class="input rounded-md ">
@@ -58,80 +61,83 @@
{{ macros::input(label='Adresse', name='address', type="text", required=true) }} {{ macros::input(label='Adresse', name='address', type="text", required=true) }}
{{ macros::input(label='Beitrittserklärung', name='membership_pdf', type="file", accept='application/pdf', required=true) }} {{ macros::input(label='Beitrittserklärung', name='membership_pdf', type="file", accept='application/pdf', required=true) }}
<input value="Neues Vereinsmitglied anlegen" <input value="Neues Vereinsmitglied anlegen"
type="submit" type="submit"
class="btn btn-primary" /> class="btn btn-primary" />
</form> </form>
</div> </div>
</div>
</dialog>
<dialog id="add-scheckbuch"
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
onclick="document.getElementById('add-scheckbuch').close()">
<div onclick="event.stopPropagation();" class="p-3">
<button type="button"
onclick="document.getElementById('add-scheckbuch').close()"
title="Schließen"
class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3">
<svg class="inline h-5 w-5"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16">
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
</svg>
</button>
<div class="mt-8">
<h2 class="h3 mb-3">Neues Scheckbuch</h2>
<form action="/admin/user/new/scheckbuch"
method="post"
enctype="multipart/form-data"
class="grid gap-3">
{{ macros::input(label='Name', name='name', type="text", required=true) }}
{{ macros::input(label='Mailadresse', name='mail', type="email", required=true, placeholder='user@mail.at') }}
<input value="Neues Scheckbuch anlegen"
type="submit"
class="btn btn-primary" />
</form>
</div> </div>
</div> </dialog>
</dialog>
<dialog id="add-schnupperkurs" <dialog id="add-scheckbuch"
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md" class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
onclick="document.getElementById('add-schnupperkurs').close()"> onclick="document.getElementById('add-scheckbuch').close()">
<div onclick="event.stopPropagation();" class="p-3"> <div onclick="event.stopPropagation();" class="p-3">
<button type="button" <button type="button"
onclick="document.getElementById('add-schnupperkurs').close()" onclick="document.getElementById('add-scheckbuch').close()"
title="Schließen" title="Schließen"
class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3"> class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3">
<svg class="inline h-5 w-5" <svg class="inline h-5 w-5"
width="16" width="16"
height="16" height="16"
fill="currentColor" fill="currentColor"
viewBox="0 0 16 16"> viewBox="0 0 16 16">
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path> <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
</svg> </svg>
</button> </button>
<div class="mt-8"> <div class="mt-8">
<form action="/admin/user/new/schnupper" <h2 class="h3 mb-3">Neues Scheckbuch</h2>
method="post" <form action="/admin/user/new/scheckbuch"
enctype="multipart/form-data" method="post"
class="grid gap-3"> enctype="multipart/form-data"
<h2 class="h3 mb-3">Neuer Schnupperant</h2> class="grid gap-3">
<div> {{ macros::input(label='Name', name='name', type="text", required=true) }}
<label for="schnupper_type" class="text-sm text-gray-600 dark:text-gray-100">Typ</label> {{ macros::input(label='Mailadresse', name='mail', type="email", required=true, placeholder='user@mail.at') }}
<select name="schnupper_type" id="schnupper_type" class="input rounded-md "> <input value="Neues Scheckbuch anlegen"
<option value="schnupperInterested">Interessiert am Schnupperkurs</option> type="submit"
<option value="schnupperant">Fixe Schnupperkurs-Anmeldung</option> class="btn btn-primary" />
</select> </form>
</div> </div>
{{ macros::input(label='Name', name='name', type="text", required=true) }} </div>
{{ macros::input(label='Mailadresse', name='mail', type="email", required=true, placeholder='user@mail.at') }} </dialog>
{{ macros::select(label="Finanzielles", data=financial, name='financial_id', display=['name'], default="Keine Ermäßigung") }}
<input value="Hinzufügen" type="submit" class="btn btn-primary" /> <dialog id="add-schnupperkurs"
</form> class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
onclick="document.getElementById('add-schnupperkurs').close()">
<div onclick="event.stopPropagation();" class="p-3">
<button type="button"
onclick="document.getElementById('add-schnupperkurs').close()"
title="Schließen"
class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3">
<svg class="inline h-5 w-5"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16">
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
</svg>
</button>
<div class="mt-8">
<form action="/admin/user/new/schnupper"
method="post"
enctype="multipart/form-data"
class="grid gap-3">
<h2 class="h3 mb-3">Neuer Schnupperant</h2>
<div>
<label for="schnupper_type" class="text-sm text-gray-600 dark:text-gray-100">Typ</label>
<select name="schnupper_type" id="schnupper_type" class="input rounded-md ">
<option value="schnupperInterested">Interessiert am Schnupperkurs</option>
<option value="schnupperant">Fixe Schnupperkurs-Anmeldung</option>
</select>
</div> </div>
</div> {{ macros::input(label='Name', name='name', type="text", required=true) }}
</dialog> {{ macros::input(label='Mailadresse', name='mail', type="email", required=true, placeholder='user@mail.at') }}
{{ macros::select(label="Finanzielles", data=financial, name='financial_id', display=['name'], default="Keine Ermäßigung") }}
<input value="Hinzufügen" type="submit" class="btn btn-primary" />
</form>
</div>
</div>
</dialog>
</details> </details>
{% endif %} {% endif %}
<!-- START filterBar --> <!-- START filterBar -->

View File

@@ -4,9 +4,7 @@
{% block content %} {% block content %}
<div class="max-w-screen-lg w-full"> <div class="max-w-screen-lg w-full">
{% if "admin" in loggedin_user.roles or "Vorstand" in loggedin_user.roles %} {% if "admin" in loggedin_user.roles or "Vorstand" in loggedin_user.roles %}
<div class="mb-5 lg:mb-0"> <a href="/admin/user" class="link link-primary link-no-underline">&larr; Userverwaltung</a>
<a href="/admin/user" class="link link-primary link-no-underline">&larr; Userverwaltung</a>
</div>
{% endif %} {% endif %}
<h1 class="h1">{{ user.name }}</h1> <h1 class="h1">{{ user.name }}</h1>
<div class="grid sm:grid-cols-2 gap-8 my-8"> <div class="grid sm:grid-cols-2 gap-8 my-8">
@@ -121,12 +119,12 @@
</div> </div>
{% if allowed_to_edit %} {% if allowed_to_edit %}
<div class="py-3"> <div class="py-3">
<div class="text-right"> <div class="mt-3 text-right">
<button type="button" <button type="button"
onclick="document.getElementById('change-member-type').showModal()" onclick="document.getElementById('change-member-type').showModal()"
class="btn btn-dark">Mitgliedsstatus ändern</button> class="btn btn-dark">Mitgliedsstatus ändern</button>
<a href="/admin/user/{{ user.id }}/delete" <a href="/admin/user/{{ user.id }}/delete"
class="btn btn-alert mt-3" class="btn btn-alert"
onclick="return confirm('Ist {{ user.name }} wirklich aus dem Verein ausgetreten?');"> onclick="return confirm('Ist {{ user.name }} wirklich aus dem Verein ausgetreten?');">
{% include "includes/delete-icon" %} {% include "includes/delete-icon" %}
Mitglied ist ausgetreten Mitglied ist ausgetreten
@@ -387,11 +385,9 @@
{% endif %} {% endif %}
{% else %} {% else %}
{% if "paid" in user.roles %} {% if "paid" in user.roles %}
{% for key, value in member %}
{% for key, value in member %}
{% if loop.first %}{{ key }}{% endif %} {% if loop.first %}{{ key }}{% endif %}
{% endfor %} {% endfor %} hat schon bezahlt
hat schon bezahlt
{% else %} {% else %}
{% for key, value in member %} {% for key, value in member %}
@@ -406,15 +402,11 @@
{% endif %} {% endif %}
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow"> <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow">
<h2 class="h2">Aktivitäten</h2> <h2 class="h2">Aktivitäten</h2>
<div class="mx-3 max-h-60 overflow-y-scroll"> <div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600">
<div class="py-3"> <div class="py-3">
<ul class="list-disc ms-4"> <ul class="list-disc ms-4">
{% for activity in activities %} {% for activity in activities %}
<li> <li>{{ activity.created_at | date(format="%d. %m. %Y") }}: {{ activity.text }}</li>
<strong>{{ activity.created_at | date(format="%d. %m. %Y") }}:</strong> <small>{{ activity.text }}
{% if activity.keep_until_days %}(⏳ {{ activity.keep_until_days }} Tage){% endif %}
</small>
</li>
{% else %} {% else %}
<li>Noch keine Aktivität... Stay tuned 😆</li> <li>Noch keine Aktivität... Stay tuned 😆</li>
{% endfor %} {% endfor %}

View File

@@ -183,130 +183,139 @@
<div class="border-t bg-white dark:bg-primary-900 py-3 px-4 relative" <div class="border-t bg-white dark:bg-primary-900 py-3 px-4 relative"
data-filterable="true" data-filterable="true"
data-filter="{{ log.boat.name }} {% for rower in log.rowers %}{{ rower.name }}{% endfor %}"> data-filter="{{ log.boat.name }} {% for rower in log.rowers %}{{ rower.name }}{% endfor %}">
{% if log.logtype and not hide_type %} {% if log.logtype and not hide_type %}
<div class="absolute top-0 right-0 bg-primary-100 rounded-bl-md text-primary-950 text-xs w-32 px-2 py-1 text-center font-bold"> <div class="absolute top-0 right-0 bg-primary-100 rounded-bl-md text-primary-950 text-xs w-32 px-2 py-1 text-center font-bold">
{% if log.logtype == 1 %} {% if log.logtype == 1 %}
Wanderfahrt Wanderfahrt
{% else %} {% else %}
{% if log.logtype == 2 %} {% if log.logtype == 2 %}
Regatta Regatta
{% else %} {% else %}
{{ log.logtype }} {{ log.logtype }}
{% endif %} {% endif %}
{% endif %}
</div>
{% endif %}
<div {% if log.logtype %}class="mt-4 sm:mt-0"{% endif %}>
{% if allowed_to_edit %}
<a href="#"
onclick="document.getElementById('change-{{ log.id }}').showModal()"
class="link link-black font-bold">{{ log.boat.name }}</a>
{% else %}
<strong class="text-black dark:text-white">{{ log.boat.name }}</strong>
{% endif %}
<small class="text-gray-600 dark:text-gray-100">({{ log.shipmaster_user.name -}}
{% if log.shipmaster_only_steering %}
- handgesteuert
{%- endif -%}
)</small>
<small class="block text-gray-600 dark:text-gray-100">
{% if state == "completed" and log.departure | date(format='%d.%m.%Y') == log.arrival | date(format='%d.%m.%Y') %}
{{ log.departure | date(format='%d.%m.%Y') }}
({{ log.departure | date(format='%H:%M') }}
-
{{ log.arrival | date(format='%H:%M') }})
{% else %}
{{ log.departure | date(format='%d.%m.%Y (%H:%M)') }}
{% if state == "completed" %}
-
{{ log.arrival | date(format='%d.%m.%Y (%H:%M)') }}
{% endif %}
{% endif %}
</small>
{% set amount_rowers = log.rowers | length %}
{% set amount_guests = log.boat.amount_seats - amount_rowers %}
{% if allowed_to_close and state == "on_water" %}
{{ log::home(log=log) }}
{% else %}
<div class="text-black dark:text-white">
{{ log.destination }}
{% if state == "completed" %}
<small class="text-gray-600 dark:text-gray-100">({{ log.distance_in_km }}
km)</small>
{% endif %}
{% if log.comments %}<span class="text-sm italic">- "{{ log.comments }}"</span>{% endif %}
</div>
{% if amount_guests > 0 or log.rowers | length > 0 %}
{% if not log.boat.amount_seats == 1 %}
<div class="text-sm text-gray-600 dark:text-gray-100">
Ruderer:
{% for rower in log.rowers -%}
{{ rower.name }}
{%- if rower.id == log.steering_user.id and rower.id != log.shipmaster_user.id %}
(Steuerperson){%- endif -%}
{%- if not loop.last or amount_guests > 0 and not log.boat.external %},{% endif %}
{% endfor -%}
{% if amount_guests > 0 and not log.boat.external %}
Gäste
<small class="text-gray-600 dark:text-gray-100">(ohne Account)</small>:
{{ amount_guests }}
{% endif %}
</div>
{% endif %} {% endif %}
{% endif %}
{% endif %}
</div>
{% if allowed_to_edit %}
<dialog id="change-{{ log.id }}"
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
onclick="document.getElementById('change-{{ log.id }}').close()">
<div onclick="event.stopPropagation();" class="p-3">
<button type="button"
onclick="document.getElementById('change-{{ log.id }}').close()"
title="Schließen"
class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3">
<svg class="inline h-5 w-5"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16">
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
</svg>
</button>
<div class="mt-8">
<h2 class="h3">Eintrag '{{ log.boat.name }}' ändern</h2>
<p class="text-center mb-3">{{ log.id }}</p>
<form action="/log/update" method="post" class="grid gap-3">
<input type="hidden" name="id" value="{{ log.id }}" />
<input type="hidden" name="boat_id" value="{{ log.boat_id }}" />
<input type="hidden" name="shipmaster" value="{{ log.shipmaster }}" />
<input type="hidden"
name="steering_person"
value="{{ log.steering_person }}" />
{{ macros::checkbox(label='Handgesteuert', name='shipmaster_only_steering', id=log.shipmaster_only_steering,checked=log.shipmaster_only_steering) }}
<input type="datetime-local"
class="input rounded-md"
name="departure"
value="{{ log.departure }}" />
<input type="datetime-local"
class="input rounded-md"
name="arrival"
value="{{ log.arrival }}" />
<input type="hidden" name="destination" value="{{ log.destination }}" />
<input type="hidden" name="distance_in_km" value="{{ log.distance_in_km }}" />
<input type="hidden" name="comments" value="{{ log.comments }}" />
<input type="hidden" name="logtype" value="{{ log.logtype }}" />
<input type="submit" class="btn btn-primary" value="Updaten" />
</form>
<a href="/log/{{ log.id }}/delete"
class="w-28 btn btn-alert mt-3"
onclick="return confirm('Willst du diesen Logbucheintrag wirklich löschen?');">
{% include "includes/delete-icon" %}
Löschen
</a>
</div>
</div> </div>
</dialog> {% endif %}
<div {% if log.logtype %}class="mt-4 sm:mt-0"{% endif %}>
{% if allowed_to_edit %}
<a href="#"
onclick="document.getElementById('change-{{ log.id }}').showModal()"
class="link link-black font-bold">{{ log.boat.name }}</a>
{% else %}
<strong class="text-black dark:text-white">
{{ log.boat.name }}
</strong>
{% endif %}
<small class="text-gray-600 dark:text-gray-100">({{ log.shipmaster_user.name -}}
{% if log.shipmaster_only_steering %}
- handgesteuert
{%- endif -%}
)</small>
<small class="block text-gray-600 dark:text-gray-100">
{% if state == "completed" and log.departure | date(format='%d.%m.%Y') == log.arrival | date(format='%d.%m.%Y') %}
{{ log.departure | date(format='%d.%m.%Y') }}
({{ log.departure | date(format='%H:%M') }}
-
{{ log.arrival | date(format='%H:%M') }})
{% else %}
{{ log.departure | date(format='%d.%m.%Y (%H:%M)') }}
{% if state == "completed" %}
-
{{ log.arrival | date(format='%d.%m.%Y (%H:%M)') }}
{% endif %}
{% endif %}
</small>
{% set amount_rowers = log.rowers | length %}
{% set amount_guests = log.boat.amount_seats - amount_rowers %}
{% if allowed_to_close and state == "on_water" %}
{{ log::home(log=log) }}
{% else %}
<div class="text-black dark:text-white">
{{ log.destination }}
{% if state == "completed" %}
<small class="text-gray-600 dark:text-gray-100">({{ log.distance_in_km }}
km)</small>
{% endif %}
{% if log.comments %}<span class="text-sm italic">- "{{ log.comments }}"</span>{% endif %}
</div>
{% if amount_guests > 0 or log.rowers | length > 0 %}
{% if not log.boat.amount_seats == 1 %}
<div class="text-sm text-gray-600 dark:text-gray-100">
Ruderer:
{% for rower in log.rowers -%}
{{ rower.name }}
{%- if rower.id == log.steering_user.id and rower.id != log.shipmaster_user.id %}
(Steuerperson){%- endif -%}
{%- if not loop.last or amount_guests > 0 and not log.boat.external %},{% endif %}
{% endfor -%}
{% if amount_guests > 0 and not log.boat.external %}
Gäste
<small class="text-gray-600 dark:text-gray-100">(ohne Account)</small>:
{{ amount_guests }}
{% endif %}
</div>
{% endif %}
{% endif %}
{% endif %}
</div>
{% if allowed_to_edit %}
<dialog id="change-{{ log.id }}"
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
onclick="document.getElementById('change-{{ log.id }}').close()">
<div onclick="event.stopPropagation();" class="p-3">
<button type="button"
onclick="document.getElementById('change-{{ log.id }}').close()"
title="Schließen"
class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3">
<svg class="inline h-5 w-5"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16">
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
</svg>
</button>
<div class="mt-8">
<h2 class="h3">Eintrag '{{ log.boat.name }}' ändern </h2>
<p class="text-center mb-3">ID: {{ log.id }}</p>
<form action="/log/update" method="post" class="grid gap-3">
<input type="hidden" name="id" value="{{ log.id }}" />
<input type="hidden"
name="steering_person"
value="{{ log.steering_person }}" />
{{ macros::select(label="Boot", data=boats, name="boat_id", id="boat_id{{ log.id }}", selected_id=log.boat.id ,display=["name", " (","amount_seats", " x)"]) }}
{{ macros::select(label="Schiffsführer", data=log.rowers, name="shipmaster", id="shipmaster{{ log.id }}", selected_id=log.shipmaster_user.id) }}
{{ macros::checkbox(label='Handgesteuert', name='shipmaster_only_steering', id=log.shipmaster_only_steering,checked=log.shipmaster_only_steering) }}
<div>
<label for="departure" class=" text-sm text-gray-600 dark:text-white ">
Abfahrt
</label>
<input type="datetime-local" class="input rounded-md" name="departure" value="{{ log.departure }}" />
</div>
<div>
<label for="arrival" class=" text-sm text-gray-600 dark:text-white ">
Ankunft
</label>
<input type="datetime-local" class="input rounded-md" name="arrival" value="{{ log.arrival }}" />
</div>
<input type="hidden" name="destination" value="{{ log.destination }}" />
<input type="hidden" name="distance_in_km" value="{{ log.distance_in_km }}" />
<input type="hidden" name="comments" value="{{ log.comments }}" />
<input type="hidden" name="logtype" value="{{ log.logtype }}" />
<input type="submit" class="btn btn-primary" value="Updaten" />
</form>
<a href="/log/{{ log.id }}/delete"
class="w-28 btn btn-alert mt-3"
onclick="return confirm('Willst du diesen Logbucheintrag wirklich löschen?');">
{% include "includes/delete-icon" %}
Löschen
</a>
</div>
</div>
</dialog>
{% endif %} {% endif %}
</div> </div>
{% endmacro show_old %} {% endmacro show_old %}

View File

@@ -212,9 +212,8 @@
</h3> </h3>
</summary> </summary>
<div class="mt-3"> <div class="mt-3">
{% if achievements.curr_equatorprice_name == "Diamant" %} {% if price.level == "DONE" %}
Gratuliere, du hast alles in deinem Rudererleben erreicht, was es (beim Äquatorpreis) zu erreichen gibt. Gratuliere, du hast alles in deinem Rudererleben erreicht, was es (beim Äquatorpreis) zu erreichen gibt.
Insgesamt bist du schon stolze {{ price.rowed_km }} km gerudert.
{% else %} {% else %}
<label for="equatorprice" class="label">{{ price.desc }} ({{ price.rowed_km }} / {{ price.required_km }} km)</label> <label for="equatorprice" class="label">{{ price.desc }} ({{ price.rowed_km }} / {{ price.required_km }} km)</label>
<progress id="equatorprice" <progress id="equatorprice"
@@ -418,9 +417,6 @@
<li class="py-1"> <li class="py-1">
<a href="/admin/boat" class="block w-100 py-2 hover:text-primary-600">Boote</a> <a href="/admin/boat" class="block w-100 py-2 hover:text-primary-600">Boote</a>
</li> </li>
<li class="py-1">
<a href="https://cloud.rudernlinz.at/login?user={{ loggedin_user.name }}" target="_blank" class="block w-100 py-2 hover:text-primary-600">Nextcloud ↗️</a>
</li>
</ul> </ul>
</div> </div>
{% endif %} {% endif %}

View File

@@ -26,7 +26,7 @@
{% for log in logs %} {% for log in logs %}
{% set_global allowed_to_edit = false %} {% set_global allowed_to_edit = false %}
{% if loggedin_user %} {% if loggedin_user %}
{% if "Vorstand" in loggedin_user.roles or "admin" in loggedin_user.roles %} {% if "Vorstand" in loggedin_user.roles %}
{% set_global allowed_to_edit = true %} {% set_global allowed_to_edit = true %}
{% endif %} {% endif %}
{% endif %} {% endif %}