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
39 changed files with 825 additions and 1147 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,11 +1,7 @@
use std::ops::DerefMut; use std::ops::DerefMut;
use super::{ use super::{role::Role, user::User};
logbook::{Logbook, LogbookWithBoatAndRowers}, use chrono::NaiveDateTime;
role::Role,
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};
@@ -18,115 +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>),
Logbook(ReasonLogbook<'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}"
))
.user(changed_user),
Reason::NewUserNote(changed_by, user, explanation) => {
Self::new(&format!("({changed_by}) {explanation}")).user(user)
}
Reason::Logbook(logbook) => logbook.into(),
}
}
}
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})"))
.user(user)
.keep_until_days(7)
}
ReasonAuth::DeletedUserLogin(user) => Self::new(&format!(
"{user} wollte sich einloggen, klappte jedoch nicht weil der Account gelöscht wurde."
))
.user(user)
.keep_until_days(30),
ReasonAuth::WrongPw(user) => Self::new(&format!(
"User {user} wollte sich einloggen, hat jedoch das falsche Passwort angegeben."
))
.user(user)
.keep_until_days(7),
}
}
}
pub enum ReasonLogbook<'a> {
// `User` tried to login with `String` as UserAgent
BoardOrAdminDeleted(&'a User, &'a LogbookWithBoatAndRowers),
}
impl<'a> From<ReasonLogbook<'a>> for Reason<'a> {
fn from(logbook_reason: ReasonLogbook<'a>) -> Self {
Reason::Logbook(logbook_reason)
}
}
impl From<ReasonLogbook<'_>> for ActivityBuilder {
fn from(value: ReasonLogbook<'_>) -> Self {
match value {
ReasonLogbook::BoardOrAdminDeleted(user, logbook) => Self::new(&format!(
"{user} hat den Logbuch-Eintrag gelöscht: {logbook}"
))
.user(user)
.logbook(&logbook.logbook)
.keep_until_days(7),
}
}
}
pub struct ActivityBuilder { pub struct ActivityBuilder {
text: String, text: String,
relevant_for: String, relevant_for: String,
@@ -134,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 {
@@ -145,7 +31,7 @@ impl ActivityBuilder {
} }
#[must_use] #[must_use]
pub fn user(self, user: &User) -> Self { pub fn relevant_for_user(self, user: &User) -> Self {
Self { Self {
relevant_for: format!("{}user-{};", self.relevant_for, user.id), relevant_for: format!("{}user-{};", self.relevant_for, user.id),
..self ..self
@@ -153,30 +39,13 @@ impl ActivityBuilder {
} }
#[must_use] #[must_use]
pub fn role(self, role: &Role) -> Self { pub fn relevant_for_role(self, role: &Role) -> Self {
Self { Self {
relevant_for: format!("{}role-{};", self.relevant_for, role.id), relevant_for: format!("{}role-{};", self.relevant_for, role.id),
..self ..self
} }
} }
#[must_use]
pub fn logbook(self, logbook: &Logbook) -> Self {
Self {
relevant_for: format!("{}logbook-{};", self.relevant_for, logbook.id),
..self
}
}
#[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;
} }
@@ -241,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,15 +1,15 @@
use std::ops::DerefMut; use std::ops::DerefMut;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use rocket::serde::{Deserialize, Serialize}; use itertools::Itertools;
use rocket::FromForm; use rocket::FromForm;
use rocket::serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use crate::model::boathouse::Boathouse; use crate::model::boathouse::Boathouse;
use super::location::Location; use super::location::Location;
use super::user::User; use super::user::User;
use std::fmt::Display;
#[derive(FromRow, Debug, Serialize, Deserialize, Eq, Hash, PartialEq, Clone)] #[derive(FromRow, Debug, Serialize, Deserialize, Eq, Hash, PartialEq, Clone)]
pub struct Boat { pub struct Boat {
@@ -32,17 +32,6 @@ pub struct Boat {
pub deleted: bool, pub deleted: bool,
} }
impl Display for Boat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let private_or_club_boat = if self.owner.is_some() {
"privat"
} else {
"Vereinsboot"
};
write!(f, "{} ({}, {private_or_club_boat})", self.name, self.cat())
}
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum BoatDamage { pub enum BoatDamage {
@@ -113,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 {
@@ -141,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;
} }
@@ -190,10 +176,8 @@ AND date('now') BETWEEN start_date AND end_date;",
"Vereinsfremde Boote".to_string() "Vereinsfremde Boote".to_string()
} else if self.default_shipmaster_only_steering { } else if self.default_shipmaster_only_steering {
format!("{}+", self.amount_seats - 1) format!("{}+", self.amount_seats - 1)
} else if self.skull {
format!("{}x", self.amount_seats)
} else { } else {
format!("{}-", self.amount_seats) format!("{}x", self.amount_seats)
} }
} }
@@ -273,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

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

@@ -1,4 +1,4 @@
use std::{fmt::Display, ops::DerefMut}; use std::ops::DerefMut;
use chrono::{Datelike, Duration, Local, NaiveDateTime}; use chrono::{Datelike, Duration, Local, NaiveDateTime};
use rocket::FromForm; use rocket::FromForm;
@@ -6,15 +6,8 @@ use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use super::{ use super::{
activity::{ActivityBuilder, ReasonLogbook}, boat::Boat, log::Log, notification::Notification, role::Role, rower::Rower, user::User,
boat::Boat,
log::Log,
notification::Notification,
role::Role,
rower::Rower,
user::User,
}; };
use crate::model::user::VecUser;
#[derive(FromRow, Serialize, Deserialize, Clone, Debug)] #[derive(FromRow, Serialize, Deserialize, Clone, Debug)]
pub struct Logbook { pub struct Logbook {
@@ -122,54 +115,6 @@ pub struct LogbookWithBoatAndRowers {
pub rowers: Vec<User>, pub rowers: Vec<User>,
} }
impl Display for LogbookWithBoatAndRowers {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(arrival) = self.logbook.arrival {
let departure_date = format!("{}", self.logbook.departure.format("%Y-%m-%d"));
let arrival_date = format!("{}", arrival.format("%Y-%m-%d"));
if departure_date == arrival_date {
write!(
f,
"Datum: {}: Start: {}, Ende: {}; ",
&self.logbook.departure.format("%d. %m. %Y"),
&self.logbook.departure.format("%H:%M"),
&arrival.format("%H:%M")
)?;
} else {
write!(
f,
"{} - {}; ",
&self.logbook.departure.format("%d. %m. %Y"),
&arrival.format("%d. %m. %Y"),
)?;
}
} else {
write!(
f,
"Start: {}",
&self.logbook.departure.format("%d. %m. %Y %H:%M")
)?;
}
if let Some(destination) = &self.logbook.destination {
write!(f, "Ziel: {destination}; ")?;
}
write!(f, "Boot: {}; ", self.boat)?;
if let Some(distance) = self.logbook.distance_in_km {
write!(f, "Distanz: {distance} km; ")?;
}
write!(f, "Schiffsführer: {}; ", self.shipmaster_user)?;
write!(f, "Steuerperson: {}; ", self.steering_user)?;
write!(f, "Rudernde: {}; ", VecUser(&self.rowers))?;
if let Some(comments) = &self.logbook.comments {
if !comments.trim().is_empty() {
write!(f, "Kommentar: {comments}; ")?;
}
}
Ok(())
}
}
impl LogbookWithBoatAndRowers { impl LogbookWithBoatAndRowers {
pub(crate) async fn from(db: &SqlitePool, log: Logbook) -> Self { pub(crate) async fn from(db: &SqlitePool, log: Logbook) -> Self {
let mut tx = db.begin().await.unwrap(); let mut tx = db.begin().await.unwrap();
@@ -422,6 +367,7 @@ ORDER BY departure DESC
min_distance: i32, min_distance: i32,
year: i32, year: i32,
filter: Filter, filter: Filter,
exclude_last_log: bool,
) -> Vec<LogbookWithBoatAndRowers> { ) -> Vec<LogbookWithBoatAndRowers> {
let logs: Vec<Logbook> = sqlx::query_as( let logs: Vec<Logbook> = sqlx::query_as(
&format!(" &format!("
@@ -453,6 +399,9 @@ ORDER BY departure DESC
} }
} }
} }
if exclude_last_log {
ret.pop();
}
ret ret
} }
@@ -862,22 +811,43 @@ ORDER BY departure DESC
} }
pub async fn delete(&self, db: &SqlitePool, user: &User) -> Result<(), LogbookDeleteError> { pub async fn delete(&self, db: &SqlitePool, user: &User) -> Result<(), LogbookDeleteError> {
Log::create(db, format!("{} deleted trip: {self:?}", user.name)).await;
if self.arrival.is_none() { if self.arrival.is_none() {
if user.has_role(db, "admin").await if user.has_role(db, "admin").await
|| user.has_role(db, "Vorstand").await || user.has_role(db, "Vorstand").await
|| user.id == self.shipmaster || user.id == self.shipmaster
{ {
Log::create(db, format!("{} deleted trip: {self:?}", user.name)).await;
let now = Local::now().naive_local(); let now = Local::now().naive_local();
let difference = now - self.departure; let difference = now - self.departure;
if difference > Duration::hours(1) { if difference > Duration::hours(1) {
let vorstand = Role::find_by_name(db, "Vorstand").await.unwrap(); let vorstand = Role::find_by_name(db, "Vorstand").await.unwrap();
let logbook = LogbookWithBoatAndRowers::from(db, self.clone()).await; let logbook = LogbookWithBoatAndRowers::from(db, self.clone()).await;
let mut msg = format!(
"{} hat folgenden Logbuch-Eintrag jetzt gelöscht, welcher bereits vor über einer Stunde begonnen wurde: Schiffsführer: {}, Steuerperson: {}, Abfahrt: {}",
user.name,
logbook.steering_user.name,
logbook.steering_user.name,
logbook.logbook.departure.format("%Y-%m-%d %H:%M")
);
if let Some(destination) = logbook.logbook.destination {
msg.push_str(&format!(", Ziel: {}", destination));
} else {
msg.push_str(", kein Ziel eingegeben");
}
msg.push_str(", Ruderer: ");
let mut it = logbook.rowers.clone().into_iter().peekable();
while let Some(rower) = it.next() {
msg.push_str(&rower.name);
if it.peek().is_some() {
msg.push_str(" + ");
}
}
Notification::create_for_role( Notification::create_for_role(
db, db,
&vorstand, &vorstand,
&format!("{user} hat folgenden Logbuch-Eintrag jetzt gelöscht, welcher bereits vor über einer Stunde begonnen wurde: {logbook}"), &msg,
"Ungewöhnliches Verhalten", "Ungewöhnliches Verhalten",
None, None,
None, None,
@@ -892,24 +862,8 @@ ORDER BY departure DESC
return Ok(()); return Ok(());
} }
} else { } else {
// Only admins+Vorstand can delete completed logbook entries // Only admins can delete completed logbook entries
if user.has_role(db, "admin").await || user.has_role(db, "Vorstand").await { if user.has_role(db, "admin").await {
let logbookdetails = LogbookWithBoatAndRowers::from(db, self.clone()).await;
ActivityBuilder::from(ReasonLogbook::BoardOrAdminDeleted(user, &logbookdetails))
.save(db)
.await;
let vorstand = Role::find_by_name(db, "Vorstand").await.unwrap();
Notification::create_for_role(
db,
&vorstand,
&format!("{user} hat den Logbuch-Eintrag gelöscht: {logbookdetails}"),
"Logbuch gelöscht",
None,
None,
)
.await;
sqlx::query!("DELETE FROM logbook WHERE id=?", self.id) sqlx::query!("DELETE FROM logbook WHERE id=?", self.id)
.execute(db) .execute(db)
.await .await

View File

@@ -1,9 +1,9 @@
use std::{error::Error, fs}; use std::{error::Error, fs};
use lettre::{ use lettre::{
message::{header::ContentType, Attachment, MultiPart, SinglePart},
transport::smtp::authentication::Credentials,
Address, Message, SmtpTransport, Transport, Address, Message, SmtpTransport, Transport,
message::{Attachment, MultiPart, SinglePart, header::ContentType},
transport::smtp::authentication::Credentials,
}; };
use sqlx::{Sqlite, SqlitePool, Transaction}; use sqlx::{Sqlite, SqlitePool, Transaction};
@@ -161,11 +161,6 @@ impl Mail {
continue; continue;
} }
} }
if user.has_role(db, "schnupperant").await || user.has_role(db, "scheckbuch").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();
@@ -261,7 +256,7 @@ Der Vorstand");
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{user} hat die Info-Mail bzgl. Gebühren gesendet bekommen." "{user} hat die Info-Mail bzgl. Gebühren gesendet bekommen."
)) ))
.user(&user) .relevant_for_user(&user)
.save(db) .save(db)
.await; .await;
} }
@@ -278,11 +273,6 @@ Der Vorstand");
continue; continue;
} }
} }
if user.has_role(db, "schnupperant").await || user.has_role(db, "scheckbuch").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;
@@ -388,7 +378,7 @@ Der Vorstand");
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{user} hat die Mahn-Mail bzgl. Gebühren gesendet bekommen." "{user} hat die Mahn-Mail bzgl. Gebühren gesendet bekommen."
)) ))
.user(&user) .relevant_for_user(&user)
.save(db) .save(db)
.await; .await;
} }

View File

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

@@ -2,7 +2,7 @@ use std::cmp;
use chrono::{Datelike, Local, NaiveDate}; use chrono::{Datelike, Local, NaiveDate};
use serde::Serialize; use serde::Serialize;
use sqlx::{Acquire, Sqlite, SqlitePool, Transaction}; use sqlx::{Sqlite, SqlitePool, Transaction};
use crate::model::{ use crate::model::{
logbook::{Filter, Logbook, LogbookWithBoatAndRowers}, logbook::{Filter, Logbook, LogbookWithBoatAndRowers},
@@ -141,7 +141,11 @@ impl Status {
} }
} }
pub(crate) async fn for_user_tx(db: &mut Transaction<'_, Sqlite>, user: &User) -> Option<Self> { pub(crate) async fn for_user_tx(
db: &mut Transaction<'_, Sqlite>,
user: &User,
exclude_last_log: bool,
) -> Option<Self> {
let Ok(agebracket) = AgeBracket::try_from(user) else { let Ok(agebracket) = AgeBracket::try_from(user) else {
return None; return None;
}; };
@@ -160,6 +164,7 @@ impl Status {
agebracket.required_dist_single_day_in_km(), agebracket.required_dist_single_day_in_km(),
year, year,
Filter::SingleDayOnly, Filter::SingleDayOnly,
exclude_last_log,
) )
.await; .await;
let multi_day_trips_over_required_distance = let multi_day_trips_over_required_distance =
@@ -169,6 +174,7 @@ impl Status {
agebracket.required_dist_multi_day_in_km(), agebracket.required_dist_multi_day_in_km(),
year, year,
Filter::MultiDayOnly, Filter::MultiDayOnly,
exclude_last_log,
) )
.await; .await;
@@ -189,7 +195,7 @@ impl Status {
pub(crate) async fn for_user(db: &SqlitePool, user: &User) -> Option<Self> { pub(crate) async fn for_user(db: &SqlitePool, user: &User) -> Option<Self> {
let mut tx = db.begin().await.unwrap(); let mut tx = db.begin().await.unwrap();
let ret = Self::for_user_tx(&mut tx, user).await; let ret = Self::for_user_tx(&mut tx, user, false).await;
tx.commit().await.unwrap(); tx.commit().await.unwrap();
ret ret
} }
@@ -198,19 +204,11 @@ impl Status {
db: &mut Transaction<'_, Sqlite>, db: &mut Transaction<'_, Sqlite>,
user: &User, user: &User,
) -> bool { ) -> bool {
if let Some(status) = Self::for_user_tx(db, user).await { if let Some(status) = Self::for_user_tx(db, user, false).await {
// if user has agebracket... // if user has agebracket...
if status.achieved { if status.achieved {
// ... and has achieved the 'Fahrtenabzeichen' // ... and has achieved the 'Fahrtenabzeichen'
let mut without_last = db.begin().await.unwrap(); let without_last_entry = Self::for_user_tx(db, user, true).await.unwrap();
let last = Logbook::completed_with_user_tx(&mut without_last, user).await;
let last = last.last().unwrap();
sqlx::query!("DELETE FROM logbook WHERE id=?", last.logbook.id)
.execute(&mut *without_last)
.await
.unwrap(); //Okay, because we can only create a Logbook of a valid id
let without_last_entry = Self::for_user_tx(&mut without_last, user).await.unwrap();
if !without_last_entry.achieved { if !without_last_entry.achieved {
// ... and this wasn't the case before the last logentry // ... and this wasn't the case before the last logentry
return true; return true;

View File

@@ -40,12 +40,8 @@ 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, "{}", formatted_name)
} else {
write!(f, "{}", self.name) write!(f, "{}", self.name)
} }
}
} }
impl Role { impl Role {
@@ -158,7 +154,7 @@ WHERE name like ?
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{updated_by} hat Rolle {self} von {self:#?} auf FORMATTED_NAME={formatted_name}, DESC={desc} aktualisiert." "{updated_by} hat Rolle {self} von {self:#?} auf FORMATTED_NAME={formatted_name}, DESC={desc} aktualisiert."
)).role(self).save(db).await; )).relevant_for_role(self).save(db).await;
Ok(()) Ok(())
} }

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();

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,15 +14,13 @@ 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,
note.to_string(),
))
.save(db) .save(db)
.await; .await;
@@ -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;
} }
@@ -138,7 +143,10 @@ impl User {
None => format!("{updated_by} hat eine Adresse für {self} hinzugefügt: {new_address}"), None => format!("{updated_by} hat eine Adresse für {self} hinzugefügt: {new_address}"),
}; };
ActivityBuilder::new(&msg).user(self).save(db).await; ActivityBuilder::new(&msg)
.relevant_for_user(self)
.save(db)
.await;
} }
pub(crate) async fn update_nickname( pub(crate) async fn update_nickname(
@@ -171,7 +179,10 @@ impl User {
"{updated_by} hat einen neuen Spitznamen für {self} hinzugefügt: {new_nickname}" "{updated_by} hat einen neuen Spitznamen für {self} hinzugefügt: {new_nickname}"
), ),
}; };
ActivityBuilder::new(&msg).user(self).save(db).await; ActivityBuilder::new(&msg)
.relevant_for_user(self)
.save(db)
.await;
Ok(()) Ok(())
} }
@@ -200,7 +211,10 @@ impl User {
), ),
}; };
ActivityBuilder::new(&msg).user(self).save(db).await; ActivityBuilder::new(&msg)
.relevant_for_user(self)
.save(db)
.await;
} }
pub(crate) async fn update_birthdate( pub(crate) async fn update_birthdate(
@@ -227,7 +241,10 @@ impl User {
} }
}; };
ActivityBuilder::new(&msg).user(self).save(db).await; ActivityBuilder::new(&msg)
.relevant_for_user(self)
.save(db)
.await;
} }
pub(crate) async fn update_family( pub(crate) async fn update_family(
@@ -249,7 +266,7 @@ impl User {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{updated_by} hat {self} zu einer Familie hinzugefügt." "{updated_by} hat {self} zu einer Familie hinzugefügt."
)) ))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;
} else { } else {
@@ -260,7 +277,7 @@ impl User {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{updated_by} hat die Familienzugehörigkeit von {self} gelöscht." "{updated_by} hat die Familienzugehörigkeit von {self} gelöscht."
)) ))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;
}; };
@@ -294,19 +311,8 @@ 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"))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;
} }
@@ -325,7 +331,7 @@ impl User {
) )
.await; .await;
ActivityBuilder::new(&format!("{updated_by} hat {self} zum Bootsführer gemacht")) ActivityBuilder::new(&format!("{updated_by} hat {self} zum Bootsführer gemacht"))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;
} }
@@ -336,14 +342,14 @@ 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)"))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;
} }
@@ -365,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");
} }
@@ -380,7 +386,7 @@ impl User {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{updated_by} hat die Ermäßigung von {self} von {old} auf {new} geändert" "{updated_by} hat die Ermäßigung von {self} von {old} auf {new} geändert"
)) ))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;
@@ -412,7 +418,7 @@ impl User {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{updated_by} hat die Rolle {role} von {self} entfernt." "{updated_by} hat die Rolle {role} von {self} entfernt."
)) ))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;
} }
@@ -439,7 +445,7 @@ impl User {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{updated_by} hat den Bezahlstatus von {self} auf 'nicht bezahlt' gesetzt." "{updated_by} hat den Bezahlstatus von {self} auf 'nicht bezahlt' gesetzt."
)) ))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;
} }
@@ -462,7 +468,7 @@ impl User {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{updated_by} hat den Bezahlstatus von {self} auf 'bezahlt' gesetzt." "{updated_by} hat den Bezahlstatus von {self} auf 'bezahlt' gesetzt."
)) ))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;
} }
@@ -499,7 +505,7 @@ impl User {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{updated_by} hat die Rolle '{role}' dem Benutzer {self} hinzugefügt." "{updated_by} hat die Rolle '{role}' dem Benutzer {self} hinzugefügt."
)) ))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;
} }
@@ -507,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,
@@ -544,7 +541,7 @@ impl User {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{updated_by} hat die Mitgliedserklärung (PDF) für user {self} hinzugefügt." "{updated_by} hat die Mitgliedserklärung (PDF) für user {self} hinzugefügt."
)) ))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;

View File

@@ -86,7 +86,7 @@ impl ClubMemberUser {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{modified_by} hat {self} zu einem regulären hochgestuft." "{modified_by} hat {self} zu einem regulären hochgestuft."
)) ))
.user(&self) .relevant_for_user(&self)
.save(db) .save(db)
.await; .await;
@@ -122,7 +122,7 @@ impl ClubMemberUser {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{modified_by} hat {self} zu einem unterstützenden Mitglied gemacht." "{modified_by} hat {self} zu einem unterstützenden Mitglied gemacht."
)) ))
.user(&self) .relevant_for_user(&self)
.save(db) .save(db)
.await; .await;
@@ -158,7 +158,7 @@ impl ClubMemberUser {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{modified_by} hat {self} zu ein förderndes Mitglied gemacht." "{modified_by} hat {self} zu ein förderndes Mitglied gemacht."
)) ))
.user(&self) .relevant_for_user(&self)
.save(db) .save(db)
.await; .await;

View File

@@ -1,8 +1,7 @@
use super::User; use super::User;
use crate::{ use crate::{
model::family::Family, BOAT_STORAGE, DUAL_MEMBERSHIP, EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE, BOAT_STORAGE, EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE, FAMILY_TWO, FOERDERND, REGULAR,
FAMILY_TWO, FOERDERND, REGULAR, RENNRUDERBEITRAG, SCHECKBUCH, STUDENT_OR_PUPIL, TRIAL_ROWING, RENNRUDERBEITRAG, STUDENT_OR_PUPIL, UNTERSTUETZEND, model::family::Family,
TRIAL_ROWING_REDUCED, UNTERSTUETZEND,
}; };
use chrono::{Datelike, Local, NaiveDate}; use chrono::{Datelike, Local, NaiveDate};
use serde::Serialize; use serde::Serialize;
@@ -69,8 +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
&& !self.has_role(db, "scheckbuch").await
{ {
return None; return None;
} }
@@ -110,8 +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
&& !self.has_role(db, "scheckbuch").await
{ {
return fee; return fee;
} }
@@ -131,10 +126,8 @@ 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) = if let Ok(member_since_date) = NaiveDate::parse_from_str(member_since_date, "%Y-%m-%d")
NaiveDate::parse_from_str(member_since_date, "%Y-%m-%d")
{ {
if member_since_date.year() == Local::now().year() if member_since_date.year() == Local::now().year()
&& !self.has_role(db, "no-einschreibgebuehr").await && !self.has_role(db, "no-einschreibgebuehr").await
@@ -143,7 +136,6 @@ impl User {
} }
} }
} }
}
let halfprice = if let Some(member_since_date) = &self.member_since_date { let halfprice = if let Some(member_since_date) = &self.member_since_date {
match NaiveDate::parse_from_str(member_since_date, "%Y-%m-%d") { match NaiveDate::parse_from_str(member_since_date, "%Y-%m-%d") {
@@ -158,15 +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, "scheckbuch").await {
fee.add("Scheckbuch".into(), SCHECKBUCH);
} 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);
@@ -179,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 {
@@ -198,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,7 +1,8 @@
use super::{regular::ClubMember, ManageUserUser, User}; use super::{ManageUserUser, User, regular::ClubMember};
use crate::{ use crate::{
NonEmptyString,
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role}, model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
special_user, NonEmptyString, special_user,
}; };
use chrono::NaiveDate; use chrono::NaiveDate;
use rocket::{async_trait, fs::TempFile}; use rocket::{async_trait, fs::TempFile};
@@ -44,7 +45,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"User {self} hat die Info-Mail bzgl. neues förderndes Mitglied (Handbuch und WLAN Infos) an {mail} gesendet bekommen" "User {self} hat die Info-Mail bzgl. neues förderndes Mitglied (Handbuch und WLAN Infos) an {mail} gesendet bekommen"
)) ))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;

View File

@@ -13,7 +13,7 @@ use rocket::{
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::{
log::Log, log::Log,
logbook::Logbook, logbook::Logbook,
@@ -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>,
@@ -65,21 +66,6 @@ impl Display for User {
} }
} }
pub(crate) struct VecUser<'a>(pub &'a Vec<User>);
impl Display for VecUser<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.0
.iter()
.map(|user| user.name.as_str())
.collect::<Vec<_>>()
.join(", ")
)
}
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct UserWithDetails { pub struct UserWithDetails {
#[serde(flatten)] #[serde(flatten)]
@@ -276,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 ?
", ",
@@ -291,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 ?
", ",
@@ -308,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)=?
", ",
@@ -353,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 {}
@@ -381,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
@@ -397,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;
" "
) )
@@ -422,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
@@ -472,7 +458,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
smtp_pw, smtp_pw,
).await?; ).await?;
ActivityBuilder::new(&format!("User {self} hat eine Mail bekommen, dass seine 5 Ausfahrten mit der heutigen Ausfahrt aufgebraucht sind, und dass der nächste Schritt eine Vereinsmitgliedschaft wäre (inkl. Links zu Beitrittserklärung + Info, dass sie an info@ geschickt werden soll.")).user(self).save_tx(db).await; ActivityBuilder::new(&format!("User {self} hat eine Mail bekommen, dass seine 5 Ausfahrten mit der heutigen Ausfahrt aufgebraucht sind, und dass der nächste Schritt eine Vereinsmitgliedschaft wäre (inkl. Links zu Beitrittserklärung + Info, dass sie an info@ geschickt werden soll.")).relevant_for_user(self).save_tx(db).await;
Ok(()) Ok(())
} }
@@ -480,25 +466,49 @@ 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 {
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; 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( "User {user} wollte sich einloggen, klappte jedoch nicht weil er gelöscht wurde."
db, ))
&board, .relevant_for_user(&user)
&format!(
"{user} wollte sich einloggen, klappte jedoch nicht weil der Account gelöscht wurde."
),
"Fehlgeschlagener Login",
None,
None,
)
.await;
}
ActivityBuilder::from(ReasonAuth::DeletedUserLogin(&user))
.save(db) .save(db)
.await; .await;
return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has
@@ -510,7 +520,10 @@ 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!(
"User {user} wollte sich einloggen, hat jedoch das falsche Passwort angegeben."
))
.relevant_for_user(&user)
.save(db) .save(db)
.await; .await;
Err(LoginError::InvalidAuthenticationCombo) Err(LoginError::InvalidAuthenticationCombo)
@@ -520,17 +533,15 @@ 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)
))
.user(self)
.save(db) .save(db)
.await; .await;
} }
@@ -541,8 +552,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!("{self} hat sein Passwort geändert.")) ActivityBuilder::new(&format!(
.user(self) "Passwort von User {self} wurde erfolgreich geändert."
))
.relevant_for_user(self)
.save(db) .save(db)
.await; .await;
} }
@@ -564,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) {
@@ -572,7 +589,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
.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} wurde von {deleted_by} gelöscht.")) ActivityBuilder::new(&format!("User {self} wurde von {deleted_by} gelöscht."))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;
} }
@@ -667,7 +684,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
) )
.await; .await;
ActivityBuilder::new(&format!("5 Scheckbuchausfahrten von {self} wurden mit der heutigen Ausfahrt aufgebraucht. Info-Mail wurde an {self} geschickt + alle Steuerberechtigten informiert, dass wir pot. ein neues Mitglied haben")) ActivityBuilder::new(&format!("5 Scheckbuchausfahrten von {self} wurden mit der heutigen Ausfahrt aufgebraucht. Info-Mail wurde an {self} geschickt + alle Steuerberechtigten informiert, dass wir pot. ein neues Mitglied haben"))
.user(self) .relevant_for_user(self)
.save_tx(db) .save_tx(db)
.await; .await;
} }
@@ -685,7 +702,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
) )
.await; .await;
ActivityBuilder::new(&format!("{self} hat nun bereits die {amount_trips}. seiner 5 Scheckbuchausfahrten absolviert. Vorstand wurde via Notification informiert.")) ActivityBuilder::new(&format!("{self} hat nun bereits die {amount_trips}. seiner 5 Scheckbuchausfahrten absolviert. Vorstand wurde via Notification informiert."))
.user(self) .relevant_for_user(self)
.save_tx(db) .save_tx(db)
.await; .await;
} }
@@ -710,7 +727,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{self} hat das heurige Fahrtenabzeichen geschafft! Der Vorstand + {self} wurde via Notification informiert." "{self} hat das heurige Fahrtenabzeichen geschafft! Der Vorstand + {self} wurde via Notification informiert."
)) ))
.user(self) .relevant_for_user(self)
.save_tx(db) .save_tx(db)
.await; .await;
@@ -732,7 +749,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
) )
.await; .await;
ActivityBuilder::new(&format!("{self} hat den Äquatorpreis in {level} geschafft! Der Vorstand + {self} wurde via Notification informiert.")) ActivityBuilder::new(&format!("{self} hat den Äquatorpreis in {level} geschafft! Der Vorstand + {self} wurde via Notification informiert."))
.user(self) .relevant_for_user(self)
.save_tx(db) .save_tx(db)
.await; .await;
@@ -907,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;
@@ -982,9 +999,8 @@ mod 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);

View File

@@ -1,7 +1,8 @@
use super::{ManageUserUser, User}; use super::{ManageUserUser, User};
use crate::{ use crate::{
NonEmptyString,
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role}, model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
special_user, NonEmptyString, special_user,
}; };
use chrono::NaiveDate; use chrono::NaiveDate;
use rocket::{async_trait, fs::TempFile, tokio::io::AsyncReadExt}; use rocket::{async_trait, fs::TempFile, tokio::io::AsyncReadExt};
@@ -51,7 +52,7 @@ pub trait ClubMember {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{created_by} hat Mitglied {user} mit der Rolle {role} angelegt." "{created_by} hat Mitglied {user} mit der Rolle {role} angelegt."
)) ))
.user(&user) .relevant_for_user(&user)
.save(db) .save(db)
.await; .await;
@@ -92,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
@@ -102,7 +101,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
).await?; ).await?;
ActivityBuilder::new(&format!("Willkommensmail für {self} wurde an {mail} verschickt (Handbuch, Signal-Gruppe, App-Info, Fingerprint, WLAN).")) ActivityBuilder::new(&format!("Willkommensmail für {self} wurde an {mail} verschickt (Handbuch, Signal-Gruppe, App-Info, Fingerprint, WLAN)."))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;

View File

@@ -2,12 +2,13 @@ use super::foerdernd::FoerderndUser;
use super::regular::RegularUser; use super::regular::RegularUser;
use super::unterstuetzend::UnterstuetzendUser; use super::unterstuetzend::UnterstuetzendUser;
use super::{ManageUserUser, User}; use super::{ManageUserUser, User};
use crate::NonEmptyString;
use crate::model::activity::ActivityBuilder; use crate::model::activity::ActivityBuilder;
use crate::model::role::Role; use crate::model::role::Role;
use crate::NonEmptyString;
use crate::{ use crate::{
SCHECKBUCH,
model::{mail::Mail, notification::Notification}, model::{mail::Mail, notification::Notification},
special_user, SCHECKBUCH, special_user,
}; };
use chrono::NaiveDate; use chrono::NaiveDate;
use rocket::async_trait; use rocket::async_trait;
@@ -74,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,
@@ -87,7 +88,7 @@ impl ScheckbuchUser {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{changed_by} hat den Scheckbuch-User {self} auf ein reguläres Mitglied upgegraded! Die Steuerpersonen wurden via Notification informiert." "{changed_by} hat den Scheckbuch-User {self} auf ein reguläres Mitglied upgegraded! Die Steuerpersonen wurden via Notification informiert."
)) ))
.user(&self) .relevant_for_user(&self)
.save(db) .save(db)
.await; .await;
@@ -132,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,
@@ -143,7 +144,7 @@ impl ScheckbuchUser {
.await; .await;
} }
ActivityBuilder::new(&format!("{changed_by} hat den Scheckbuch-User {self} auf ein unterstützendes Mitglied upgegraded!")) ActivityBuilder::new(&format!("{changed_by} hat den Scheckbuch-User {self} auf ein unterstützendes Mitglied upgegraded!"))
.user(&self) .relevant_for_user(&self)
.save(db) .save(db)
.await; .await;
@@ -186,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,
@@ -199,7 +200,7 @@ impl ScheckbuchUser {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{changed_by} hat den Scheckbuch-User {self} auf ein förderndes Mitglied upgegraded!" "{changed_by} hat den Scheckbuch-User {self} auf ein förderndes Mitglied upgegraded!"
)) ))
.user(&self) .relevant_for_user(&self)
.save(db) .save(db)
.await; .await;
@@ -214,7 +215,7 @@ impl ScheckbuchUser {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{self} hat eine Info-Mail bekommen (Erklärung Scheckbuch, Ruderapp) und alle Steuerberechtigten wurden informiert." "{self} hat eine Info-Mail bekommen (Erklärung Scheckbuch, Ruderapp) und alle Steuerberechtigten wurden informiert."
)) ))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;
@@ -294,7 +295,7 @@ ASKÖ Ruderverein Donau Linz", self.name, SCHECKBUCH/100),
user.notify(db, smtp_pw).await?; user.notify(db, smtp_pw).await?;
ActivityBuilder::new(&format!("{created_by} hat Scheckbuch {user} angelegt.")) ActivityBuilder::new(&format!("{created_by} hat Scheckbuch {user} angelegt."))
.user(&user) .relevant_for_user(&user)
.save(db) .save(db)
.await; .await;

View File

@@ -4,9 +4,9 @@ use super::scheckbuch::ScheckbuchUser;
use super::schnupperinterest::SchnupperInterestUser; use super::schnupperinterest::SchnupperInterestUser;
use super::unterstuetzend::UnterstuetzendUser; use super::unterstuetzend::UnterstuetzendUser;
use super::{ManageUserUser, User}; use super::{ManageUserUser, User};
use crate::NonEmptyString;
use crate::model::activity::ActivityBuilder; use crate::model::activity::ActivityBuilder;
use crate::model::role::Role; use crate::model::role::Role;
use crate::NonEmptyString;
use crate::{ use crate::{
model::{mail::Mail, notification::Notification}, model::{mail::Mail, notification::Notification},
special_user, special_user,
@@ -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,
@@ -101,7 +89,7 @@ impl SchnupperantUser {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{changed_by} hat den Schnupperant {self} auf ein reguläres Mitglied upgegraded!" "{changed_by} hat den Schnupperant {self} auf ein reguläres Mitglied upgegraded!"
)) ))
.user(&self) .relevant_for_user(&self)
.save(db) .save(db)
.await; .await;
@@ -142,7 +130,7 @@ impl SchnupperantUser {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{changed_by} hat dem ehemaligen Schnupperant {self} nun ein Scheckbuch gegeben" "{changed_by} hat dem ehemaligen Schnupperant {self} nun ein Scheckbuch gegeben"
)) ))
.user(&self) .relevant_for_user(&self)
.save(db) .save(db)
.await; .await;
@@ -184,7 +172,7 @@ impl SchnupperantUser {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{changed_by} hat dem eigentlichen Schnupperanten {self} wieder auf die 'Interessierten'-Liste zurückgegeben." "{changed_by} hat dem eigentlichen Schnupperanten {self} wieder auf die 'Interessierten'-Liste zurückgegeben."
)) ))
.user(&self) .relevant_for_user(&self)
.save(db) .save(db)
.await; .await;
@@ -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?;
@@ -253,7 +236,7 @@ impl SchnupperantUser {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{changed_by} hat den Schnupperant {self} auf ein unterstützendes Mitglied upgegraded!" "{changed_by} hat den Schnupperant {self} auf ein unterstützendes Mitglied upgegraded!"
)) ))
.user(&self) .relevant_for_user(&self)
.save(db) .save(db)
.await; .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?;
@@ -320,7 +298,7 @@ impl SchnupperantUser {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{changed_by} hat den Schnupperant {self} auf ein förderndes Mitglied upgegraded!" "{changed_by} hat den Schnupperant {self} auf ein förderndes Mitglied upgegraded!"
)) ))
.user(&self) .relevant_for_user(&self)
.save(db) .save(db)
.await; .await;
@@ -329,13 +307,13 @@ 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!(
"{self} hat eine Mail bekommen (Inhalt: wir freuen uns auf ihn + senden detailliertere Infos später zu) und die Schnupperbetreuer wurden via Notification informiert." "{self} hat eine Mail bekommen (Inhalt: wir freuen uns auf ihn + senden detailliertere Infos später zu) und die Schnupperbetreuer wurden via Notification informiert."
)) ))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;
@@ -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,
@@ -423,7 +393,7 @@ ASKÖ Ruderverein Donau Linz",
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{created_by} hat {user} zur fixen Schnupperkurs-Anmeldung hinzugefügt." "{created_by} hat {user} zur fixen Schnupperkurs-Anmeldung hinzugefügt."
)) ))
.user(&user) .relevant_for_user(&user)
.save(db) .save(db)
.await; .await;

View File

@@ -1,9 +1,9 @@
use super::scheckbuch::ScheckbuchUser; use super::scheckbuch::ScheckbuchUser;
use super::schnupperant::SchnupperantUser; use super::schnupperant::SchnupperantUser;
use super::{ManageUserUser, User}; use super::{ManageUserUser, User};
use crate::NonEmptyString;
use crate::model::activity::ActivityBuilder; use crate::model::activity::ActivityBuilder;
use crate::model::role::Role; use crate::model::role::Role;
use crate::NonEmptyString;
use crate::{model::notification::Notification, special_user}; use crate::{model::notification::Notification, special_user};
use rocket::async_trait; use rocket::async_trait;
use sqlx::SqlitePool; use sqlx::SqlitePool;
@@ -44,7 +44,7 @@ impl SchnupperInterestUser {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"Der Schnupperinteressierte {self} hat sich (ohne Schnupperkurs) doch gleich direkt für ein Scheckbuch entschieden. {changed_by} hat dieses eingerichtet." "Der Schnupperinteressierte {self} hat sich (ohne Schnupperkurs) doch gleich direkt für ein Scheckbuch entschieden. {changed_by} hat dieses eingerichtet."
)) ))
.user(&self) .relevant_for_user(&self)
.save(db) .save(db)
.await; .await;
@@ -86,7 +86,7 @@ impl SchnupperInterestUser {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"Der Schnupperinteressierte {self} hat sich zum Schnupperkurs angemeldet." "Der Schnupperinteressierte {self} hat sich zum Schnupperkurs angemeldet."
)) ))
.user(&self) .relevant_for_user(&self)
.save(db) .save(db)
.await; .await;
@@ -99,7 +99,7 @@ impl SchnupperInterestUser {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"Der Schnupperbetreuer hat eine Info via Notification bekommen, dass {self} Interesse an einen Schnupperkurs hat." "Der Schnupperbetreuer hat eine Info via Notification bekommen, dass {self} Interesse an einen Schnupperkurs hat."
)) ))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;
@@ -153,7 +153,7 @@ impl SchnupperInterestUser {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{created_by} hat Schnupper-Interessierten {user} angelegt." "{created_by} hat Schnupper-Interessierten {user} angelegt."
)) ))
.user(&user) .relevant_for_user(&user)
.save(db) .save(db)
.await; .await;

View File

@@ -1,7 +1,8 @@
use super::{regular::ClubMember, ManageUserUser, User}; use super::{ManageUserUser, User, regular::ClubMember};
use crate::{ use crate::{
NonEmptyString,
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role}, model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
special_user, NonEmptyString, special_user,
}; };
use chrono::NaiveDate; use chrono::NaiveDate;
use rocket::{async_trait, fs::TempFile}; use rocket::{async_trait, fs::TempFile};
@@ -44,7 +45,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{self} hat eine Mail an {mail} bekommen, mit Infos dass er/sie nun ein unterstützendes Mitglied ist (Handbuch, WLAN)." "{self} hat eine Mail an {mail} bekommen, mit Infos dass er/sie nun ein unterstützendes Mitglied ist (Handbuch, WLAN)."
)) ))
.user(self) .relevant_for_user(self)
.save(db) .save(db)
.await; .await;

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

@@ -14,7 +14,6 @@ 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,8 +82,13 @@ 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,
format!(
"Succ login of {} with this useragent: {}",
login.name, agent.0
),
)
.await; .await;
// Check for redirect_url cookie and redirect accordingly // Check for redirect_url cookie and redirect accordingly

View File

@@ -108,50 +108,26 @@ async fn index(
} }
#[get("/show", rank = 3)] #[get("/show", rank = 3)]
async fn show( async fn show(db: &State<SqlitePool>, user: DonauLinzUser) -> Template {
db: &State<SqlitePool>,
flash: Option<FlashMessage<'_>>,
user: DonauLinzUser,
) -> Template {
let logs = Logbook::completed(db).await; let logs = Logbook::completed(db).await;
let boats = Boat::all(db).await; let boats = Boat::all(db).await;
let users = User::all(db).await; let users = User::all(db).await;
let logtypes = LogType::all(db).await; let logtypes = LogType::all(db).await;
let mut context = Context::new(); Template::render(
if let Some(msg) = flash { "log.completed",
context.insert("flash", &msg.into_inner()); context!(logs, boats, users, logtypes, loggedin_user: &UserWithDetails::from_user(user.into_inner(), db).await),
} )
context.insert("logs", &logs);
context.insert("boats", &boats);
context.insert("users", &users);
context.insert("logtypes", &logtypes);
context.insert(
"loggedin_user",
&UserWithDetails::from_user(user.into_inner(), db).await,
);
Template::render("log.completed", context.into_json())
} }
#[get("/show?<year>", rank = 2)] #[get("/show?<year>", rank = 2)]
async fn show_for_year( async fn show_for_year(db: &State<SqlitePool>, user: VorstandUser, year: i32) -> Template {
db: &State<SqlitePool>,
flash: Option<FlashMessage<'_>>,
user: VorstandUser,
year: i32,
) -> Template {
let logs = Logbook::completed_in_year(db, year).await; let logs = Logbook::completed_in_year(db, year).await;
let mut context = Context::new(); Template::render(
if let Some(msg) = flash { "log.completed",
context.insert("flash", &msg.into_inner()); context!(logs, loggedin_user: &UserWithDetails::from_user(user.user, db).await),
} )
context.insert("logs", &logs);
context.insert(
"loggedin_user",
&UserWithDetails::from_user(user.into_inner(), db).await,
);
Template::render("log.completed", context.into_json())
} }
#[get("/show")] #[get("/show")]
@@ -537,7 +513,10 @@ async fn delete(db: &State<SqlitePool>, logbook_id: i64, user: DonauLinzUser) ->
) )
.await; .await;
match logbook.delete(db, &user).await { match logbook.delete(db, &user).await {
Ok(_) => Flash::success(Redirect::to(redirect), "Erfolgreich gelöscht"), Ok(_) => Flash::success(
Redirect::to(redirect),
format!("Eintrag {} von {} gelöscht!", logbook_id, user.name),
),
Err(LogbookDeleteError::NotYourEntry) => Flash::error( Err(LogbookDeleteError::NotYourEntry) => Flash::error(
Redirect::to(redirect), Redirect::to(redirect),
"Du hast nicht die Berechtigung, den Eintrag zu löschen!", "Du hast nicht die Berechtigung, den Eintrag zu löschen!",

View File

@@ -3,20 +3,14 @@
{% 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"
class="search-bar"
placeholder="Suchen nach Namen...">
</div>
<div id="filter-result-js" class="search-result"></div>
<div class="border-r border-l border-gray-200 dark:border-primary-600">
{% for role in roles %} {% for role in roles %}
<div data-filterable="true" <div data-filterable="true"
data-filter="{{ role.name }} {{ role.formatted_name }}" data-filter="{{ role.name }}"
class="w-full border-t"> class="w-full border-t">
<form action="/admin/role/{{ role.id }}" <form action="/admin/role/{{ role.id }}"
data-filterable="true" data-filterable="true"
@@ -29,11 +23,9 @@
<br /> <br />
</div> </div>
<div class="grid md:grid-cols-3 gap-3"> <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='Formatierter Name', name='formatted_name', type='text', value=role.formatted_name) }}
{{ macros::input(label='Beschreibung', name='desc', type='text', value=role.desc) }} {{ macros::input(label='Beschreibung', name='desc', type='text', value=role.desc) }}
<div class="flex items-end"> <input value="Ändern" type="submit" class="w-28 btn btn-primary" />
<input value="Ändern" type="submit" class="w-full btn btn-primary" />
</div>
</div> </div>
</div> </div>
</form> </form>
@@ -41,4 +33,5 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div>
{% endblock content %} {% endblock content %}

View File

@@ -8,16 +8,19 @@
<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> </div>
<dialog id="add-clubuser" <dialog id="add-clubuser"
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"
@@ -64,6 +67,7 @@
</div> </div>
</div> </div>
</dialog> </dialog>
<dialog id="add-scheckbuch" <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-scheckbuch').close()"> onclick="document.getElementById('add-scheckbuch').close()">
@@ -95,6 +99,7 @@
</div> </div>
</div> </div>
</dialog> </dialog>
<dialog id="add-schnupperkurs" <dialog id="add-schnupperkurs"
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-schnupperkurs').close()">
@@ -117,6 +122,7 @@
enctype="multipart/form-data" enctype="multipart/form-data"
class="grid gap-3"> class="grid gap-3">
<h2 class="h3 mb-3">Neuer Schnupperant</h2> <h2 class="h3 mb-3">Neuer Schnupperant</h2>
<div> <div>
<label for="schnupper_type" class="text-sm text-gray-600 dark:text-gray-100">Typ</label> <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 "> <select name="schnupper_type" id="schnupper_type" class="input rounded-md ">

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

@@ -202,7 +202,9 @@
onclick="document.getElementById('change-{{ log.id }}').showModal()" onclick="document.getElementById('change-{{ log.id }}').showModal()"
class="link link-black font-bold">{{ log.boat.name }}</a> class="link link-black font-bold">{{ log.boat.name }}</a>
{% else %} {% else %}
<strong class="text-black dark:text-white">{{ log.boat.name }}</strong> <strong class="text-black dark:text-white">
{{ log.boat.name }}
</strong>
{% endif %} {% endif %}
<small class="text-gray-600 dark:text-gray-100">({{ log.shipmaster_user.name -}} <small class="text-gray-600 dark:text-gray-100">({{ log.shipmaster_user.name -}}
{% if log.shipmaster_only_steering %} {% if log.shipmaster_only_steering %}
@@ -274,24 +276,31 @@
</svg> </svg>
</button> </button>
<div class="mt-8"> <div class="mt-8">
<h2 class="h3">Eintrag '{{ log.boat.name }}' ändern</h2> <h2 class="h3">Eintrag '{{ log.boat.name }}' ändern </h2>
<p class="text-center mb-3">{{ log.id }}</p> <p class="text-center mb-3">ID: {{ log.id }}</p>
<form action="/log/update" method="post" class="grid gap-3"> <form action="/log/update" method="post" class="grid gap-3">
<input type="hidden" name="id" value="{{ log.id }}" /> <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" <input type="hidden"
name="steering_person" name="steering_person"
value="{{ log.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) }} {{ macros::checkbox(label='Handgesteuert', name='shipmaster_only_steering', id=log.shipmaster_only_steering,checked=log.shipmaster_only_steering) }}
<input type="datetime-local" <div>
class="input rounded-md" <label for="departure" class=" text-sm text-gray-600 dark:text-white ">
name="departure" Abfahrt
value="{{ log.departure }}" /> </label>
<input type="datetime-local" <input type="datetime-local" class="input rounded-md" name="departure" value="{{ log.departure }}" />
class="input rounded-md" </div>
name="arrival" <div>
value="{{ log.arrival }}" /> <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="destination" value="{{ log.destination }}" />
<input type="hidden" name="distance_in_km" value="{{ log.distance_in_km }}" /> <input type="hidden" name="distance_in_km" value="{{ log.distance_in_km }}" />
<input type="hidden" name="comments" value="{{ log.comments }}" /> <input type="hidden" name="comments" value="{{ log.comments }}" />

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 %}