1 Commits

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

View File

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

210
Cargo.lock generated
View File

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

View File

@@ -9,7 +9,7 @@ rowing-tera = ["rocket_dyn_templates", "tera"]
rest = []
[dependencies]
rocket = { version = "0.5", features = ["secrets"]}
rocket = { version = "0.5.0", features = ["secrets"]}
rocket_dyn_templates = {version = "0.2", features = [ "tera" ], optional = true }
log = "0.4"
env_logger = "0.11"
@@ -19,15 +19,15 @@ serde = { version = "1.0", features = [ "derive" ]}
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"]}
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"
futures = "0.3"
lettre = "0.11"
csv = "1.3"
itertools = "0.14"
job_scheduler_ng = "2.2"
job_scheduler_ng = "2.0"
ureq = { version = "3.0", features = ["json"] }
regex = "1.11"
regex = "1.10"
urlencoding = "2.1"
[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.goto("/log/show");
await page.getByRole('link', { name: 'Joe' }).nth(1).click();
await page.getByText('(cox2)').click();
page.once("dialog", (dialog) => {
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 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('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.goto("/log/show");
await page.getByRole('link', { name: 'Joe' }).nth(1).click();
await page.getByText('(cox2)').click();
page.once("dialog", (dialog) => {
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 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)');
@@ -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.goto("/log/show");
await page.getByRole("link", { name: "cox_only_steering_boat" }).click();
await page.getByText('(cox2 - handgesteuert)').click();
page.once("dialog", (dialog) => {
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.goto("/log/show");
await page.getByRole('link', { name: 'Joe' }).nth(1).click();
await page.getByText('(cox2)').click();
page.once("dialog", (dialog) => {
dialog.accept().catch(() => {});
});

View File

@@ -23,9 +23,6 @@ pub(crate) const UNTERSTUETZEND: i64 = 2500;
pub(crate) const FOERDERND: i64 = 8500;
pub(crate) const SCHECKBUCH: 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)]
pub struct NonEmptyString(String);

View File

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

View File

@@ -1,6 +1,7 @@
use std::ops::DerefMut;
use chrono::NaiveDateTime;
use itertools::Itertools;
use rocket::FromForm;
use rocket::serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
@@ -101,27 +102,12 @@ impl Boat {
}
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 {
return owner_id == user.id;
}
if user.has_role_tx(db, "Rennrudern").await {
let ottensheim = Location::find_by_name_tx(db, "Ottensheim".into())
if user.has_role(db, "Rennrudern").await {
let ottensheim = Location::find_by_name(db, "Ottensheim".into())
.await
.unwrap();
if self.location_id == ottensheim.id {
@@ -129,10 +115,22 @@ impl Boat {
}
}
if self.name == "Externes Boot" {
if self.amount_seats == 1 {
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 {
return true;
}
@@ -259,16 +257,58 @@ ORDER BY
}
pub async fn for_user(db: &SqlitePool, user: &User) -> Vec<BoatWithDetails> {
let all_boats = Self::all(db).await;
let mut filtered_boats = Vec::new();
for boat in all_boats {
if boat.boat.shipmaster_allowed(db, user).await {
filtered_boats.push(boat);
}
if user.has_role(db, "admin").await {
return Self::all(db).await;
}
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> {

View File

@@ -2,8 +2,8 @@ use std::io::Write;
use chrono::{Duration, NaiveDate, NaiveTime};
use ics::{
properties::{DtEnd, DtStart, Summary},
ICalendar,
properties::{DtEnd, DtStart, Summary},
};
use serde::Serialize;
use sqlx::{FromRow, Row, SqlitePool};
@@ -142,14 +142,6 @@ WHERE planned_event.id like ?
.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> {
let mut events = Self::get_for_day(db, day).await;
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")
.expect("Failed to parse time");
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)
};
let later_time = original_time + Duration::hours(3);
if later_time > original_time {
// Check if no day-overflow
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> {
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)
.await
.unwrap()

View File

@@ -1,6 +1,5 @@
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use std::ops::DerefMut;
use sqlx::{FromRow, SqlitePool};
#[derive(FromRow, Debug, Serialize, Deserialize)]
pub struct Location {
@@ -38,20 +37,6 @@ impl Location {
.await
.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> {
sqlx::query_as!(Self, "SELECT id, name FROM location")

View File

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

View File

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

View File

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

View File

@@ -40,11 +40,7 @@ impl Ord for Role {
impl Display for Role {
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)
}
}

View File

@@ -23,7 +23,7 @@ impl Rower {
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
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 id in (SELECT rower_id FROM rower WHERE logbook_id=?)
",

View File

@@ -145,15 +145,7 @@ WHERE trip_details.id=?
.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(crate) async fn get_vevent<'a>(self, db: &'a SqlitePool, user: &'a User) -> ics::Event<'a> {
pub(crate) async fn get_vevent(self, user: &User) -> ics::Event {
let mut vevent =
ics::Event::new(format!("trip-{}@rudernlinz.at", self.id), "19900101T180000");
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")
.expect("Failed to parse time");
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)
};
let later_time = original_time + Duration::hours(3);
if later_time > original_time {
// Check if no day-overflow
let time_three_hours_later = later_time.format("%H%M").to_string();
@@ -583,9 +567,11 @@ mod test {
let last_notification = &Notification::for_user(&pool, &cox).await[0];
assert!(last_notification
.message
.starts_with("cox2 hat eine Ausfahrt zur selben Zeit"));
assert!(
last_notification
.message
.starts_with("cox2 hat eine Ausfahrt zur selben Zeit")
);
}
#[sqlx::test]

View File

@@ -2,10 +2,7 @@
use super::{AllowedToEditPaymentStatusUser, ManageUserUser, User};
use crate::model::{
activity::{self, ActivityBuilder},
family::Family,
mail::valid_mails,
notification::Notification,
activity::ActivityBuilder, family::Family, mail::valid_mails, notification::Notification,
role::Role,
};
use chrono::NaiveDate;
@@ -17,17 +14,15 @@ impl User {
&self,
db: &SqlitePool,
updated_by: &ManageUserUser,
user: &User,
note: &str,
) -> Result<(), String> {
let note = note.trim();
ActivityBuilder::from(activity::Reason::UserDataChange(
updated_by,
self,
note.to_string(),
))
.save(db)
.await;
ActivityBuilder::new(&format!("({updated_by}) {note}"))
.relevant_for_user(user)
.save(db)
.await;
Ok(())
}
@@ -52,11 +47,18 @@ impl User {
.unwrap(); //Okay, because we can only create a User of a valid id
let msg = match &self.mail {
Some(old_mail) => format!("Mail-Adresse von {old_mail} auf {new_mail} geändert."),
None => format!("Neue Mail-Adresse für: {new_mail}"),
Some(old_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)
.await;
@@ -87,16 +89,19 @@ impl User {
query.execute(db).await.unwrap(); //Okay, because we can only create a User of a valid id
let msg = match &self.phone {
Some(old_phone) if new_phone.is_empty() => {
format!("Telefonnummer wurde entfernt (alte Nummer: {old_phone})")
}
Some(old_phone) => {
format!("Telefonnummer wurde von {old_phone} auf {new_phone} geändert.")
}
None => format!("Neue Telefonnummer hinzugefügt: {new_phone}"),
Some(old_phone) if new_phone.is_empty() => format!(
"{updated_by} hat die Telefonnummer von {self} entfernt (alte Nummer: {old_phone})"
),
Some(old_phone) => format!(
"{updated_by} hat die Telefonnummer von {self} von {old_phone} auf {new_phone} geändert."
),
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)
.await;
}
@@ -306,17 +311,6 @@ impl User {
None,
)
.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"))
.relevant_for_user(self)
.save(db)
@@ -348,13 +342,13 @@ impl User {
Notification::create_for_role(
db,
&vorstand,
&format!("Lieber Vorstand, {self} ist ab sofort kein {old} mehr."),
"Steuerperson--;",
&format!("Lieber Vorstand, {self} ist ab kein {old} mehr."),
"Steuerperson --",
None,
None,
)
.await;
ActivityBuilder::new(&format!("{updated_by} hat {self} zum normalen Mitglied gemacht (keine Steuerperson/Schiffsführer mehr)"))
ActivityBuilder::new(&format!("{updated_by} hat {self} zum normalen Mitlgied gemacht (keine Steuerperson/Schiffsführer mehr)"))
.relevant_for_user(self)
.save(db)
.await;
@@ -377,14 +371,14 @@ impl User {
if let Some(old_financial) = self.financial(db).await {
self.remove_role(db, updated_by, &old_financial).await?;
old.push_str(&old_financial.to_string());
old.push_str(&old_financial.name);
} else {
old.push_str("Keine Ermäßigung");
}
if let Some(new_financial) = financial {
self.add_role(db, updated_by, &new_financial).await?;
new.push_str(&new_financial.to_string());
new.push_str(&new_financial.name);
} else {
new.push_str("Keine Ermäßigung");
}
@@ -519,15 +513,6 @@ impl User {
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(
&self,
db: &SqlitePool,

View File

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

View File

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

View File

@@ -93,8 +93,6 @@ Beim nächsten Treffen im Verein, erinnere jemand vom Vorstand (https://rudernli
Damit du dich noch mehr verbunden fühlst (:-)), haben wir im Bootshaus ein WLAN für Vereinsmitglieder 'ASKÖ Ruderverein Donau Linz' eingerichtet. Das Passwort dafür lautet 'donau1921' (ohne Anführungszeichen). Bitte gib das Passwort an keine vereinsfremden Personen weiter.
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!
Riemen- & Dollenbruch

View File

@@ -75,9 +75,9 @@ impl ScheckbuchUser {
Notification::create_for_steering_people(
db,
&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,
member_since
self.member_since_date.clone().unwrap()
),
"Neues Vereinsmitglied",
None,
@@ -133,9 +133,9 @@ impl ScheckbuchUser {
db,
&vorstand,
&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,
member_since
self.member_since_date.clone().unwrap()
),
"Neues unterstützendes Vereinsmitglied",
None,
@@ -187,9 +187,9 @@ impl ScheckbuchUser {
db,
&vorstand,
&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,
member_since
self.member_since_date.clone().unwrap()
),
"Neues förderndes Vereinsmitglied",
None,

View File

@@ -65,32 +65,20 @@ impl SchnupperantUser {
.await?;
// 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 regular = Role::find_by_name(db, "Donau Linz").await.unwrap();
let scheckbook = Role::find_by_name(db, "schnupperant").await.unwrap();
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?;
let participated_schnupperkurs = Role::find_by_name(db, "participated_schnupperkurs")
.await
.unwrap();
self.user
.add_role(db, changed_by, &participated_schnupperkurs)
.await?;
// Notify
let regular = RegularUser::new(db, &self.user).await.unwrap();
regular.send_welcome_mail_to_user(db, smtp_pw).await?;
Notification::create_for_steering_people(
db,
&format!(
"Liebe Steuerberechtigte, {} nahm an unserem Schnupperkurs teil und ist nun seit {member_since} ein neues reguläres Mitglied. 🎉",
self.name
"Liebe Steuerberechtigte, {} nahm an unserem Schnupperkurs teil und ist nun seit {} ein neues reguläres Mitglied. 🎉",
self.name,
self.member_since_date.clone().unwrap()
),
"Neues Vereinsmitglied",
None,
@@ -215,11 +203,6 @@ impl SchnupperantUser {
.await?;
// 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 scheckbook = Role::find_by_name(db, "schnupperant").await.unwrap();
self.user.remove_role(db, changed_by, &scheckbook).await?;
@@ -284,11 +267,6 @@ impl SchnupperantUser {
.await?;
// 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 scheckbook = Role::find_by_name(db, "schnupperant").await.unwrap();
self.user.remove_role(db, changed_by, &scheckbook).await?;
@@ -329,7 +307,7 @@ impl SchnupperantUser {
// TODO: make private
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?;
ActivityBuilder::new(&format!(
@@ -357,27 +335,19 @@ impl SchnupperantUser {
mail,
"ASKÖ Ruderverein Donau Linz | Anmeldung Schnupperkurs",
format!(
"Hallo {0},
"Hallo {0},
es freut uns sehr, dich bei unserem Schnupperkurs willkommen heißen zu dürfen.
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).
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.
Riemen- & Dollenbruch,
ASKÖ Ruderverein Donau Linz",
self.name,
self.fee(db).await.unwrap().sum_in_cents/100
),
ASKÖ Ruderverein Donau Linz", self.name),
smtp_pw,
)
.await?;
).await?;
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 {
Notification::create_for_role(
db,

View File

@@ -3,7 +3,10 @@ use rocket::{FromForm, Route, State, form::Form, get, post, routes};
use rocket_dyn_templates::{Template, context};
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 event;
@@ -13,9 +16,18 @@ pub mod role;
pub mod schnupper;
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)]
async fn show_activities(db: &State<SqlitePool>, _admin: AdminUser) -> String {
Activity::show(db).await
async fn show_rss(db: &State<SqlitePool>, _admin: AdminUser) -> String {
Log::show(db).await
}
#[get("/list")]
@@ -71,6 +83,6 @@ pub fn routes() -> Vec<Route> {
ret.append(&mut mail::routes());
ret.append(&mut event::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
}

View File

@@ -3,14 +3,13 @@ use crate::model::{
user::{AdminUser, UserWithDetails, VorstandUser},
};
use rocket::{
FromForm, Route, State,
form::Form,
get, post,
request::FlashMessage,
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;
#[get("/role")]

View File

@@ -1,17 +1,17 @@
use crate::{
model::{
activity::{Activity, ActivityWithDetails},
activity::Activity,
family::Family,
log::Log,
logbook::Logbook,
mail::valid_mails,
role::Role,
user::{
AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails,
UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser,
clubmember::ClubMemberUser, foerdernd::FoerderndUser, member::Member,
regular::RegularUser, scheckbuch::ScheckbuchUser, schnupperant::SchnupperantUser,
schnupperinterest::SchnupperInterestUser, unterstuetzend::UnterstuetzendUser,
AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails,
UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser,
},
},
tera::Config,
@@ -19,7 +19,6 @@ use crate::{
use chrono::NaiveDate;
use futures::future::join_all;
use rocket::{
FromForm, Request, Route, State,
form::Form,
fs::TempFile,
get,
@@ -27,9 +26,9 @@ use rocket::{
post,
request::{FlashMessage, FromRequest, Outcome},
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;
// Custom request guard to extract the Referer header
@@ -136,17 +135,13 @@ async fn view(
if user.name == "Externe Steuerperson" {
return Err(Flash::error(
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 fee = user.fee(db).await;
let activities: Vec<ActivityWithDetails> = Activity::for_user(db, &user)
.await
.into_iter()
.map(Into::into)
.collect();
let activities = Activity::for_user(db, &user).await;
let financial = Role::all_cluster(db, "financial").await;
let user_financial = user.financial(db).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),
)
.await;
user.reset_pw(db, &admin).await;
user.reset_pw(db).await;
Flash::success(
Redirect::to("/admin/user"),
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(
Redirect::to(format!("/admin/user/{}", user.id)),
"Notiz hinzugefügt",

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,9 +4,7 @@
{% block content %}
<div class="max-w-screen-lg w-full">
{% 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>
</div>
<a href="/admin/user" class="link link-primary link-no-underline">&larr; Userverwaltung</a>
{% endif %}
<h1 class="h1">{{ user.name }}</h1>
<div class="grid sm:grid-cols-2 gap-8 my-8">
@@ -121,12 +119,12 @@
</div>
{% if allowed_to_edit %}
<div class="py-3">
<div class="text-right">
<div class="mt-3 text-right">
<button type="button"
onclick="document.getElementById('change-member-type').showModal()"
class="btn btn-dark">Mitgliedsstatus ändern</button>
<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?');">
{% include "includes/delete-icon" %}
Mitglied ist ausgetreten
@@ -387,11 +385,9 @@
{% endif %}
{% else %}
{% if "paid" in user.roles %}
{% for key, value in member %}
{% for key, value in member %}
{% if loop.first %}{{ key }}{% endif %}
{% endfor %}
hat schon bezahlt
{% endfor %} hat schon bezahlt
{% else %}
{% for key, value in member %}
@@ -406,15 +402,11 @@
{% endif %}
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow">
<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">
<ul class="list-disc ms-4">
{% for activity in activities %}
<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>
<li>{{ activity.created_at | date(format="%d. %m. %Y") }}: {{ activity.text }}</li>
{% else %}
<li>Noch keine Aktivität... Stay tuned 😆</li>
{% endfor %}

View File

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

View File

@@ -212,9 +212,8 @@
</h3>
</summary>
<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.
Insgesamt bist du schon stolze {{ price.rowed_km }} km gerudert.
{% else %}
<label for="equatorprice" class="label">{{ price.desc }} ({{ price.rowed_km }} / {{ price.required_km }} km)</label>
<progress id="equatorprice"
@@ -418,9 +417,6 @@
<li class="py-1">
<a href="/admin/boat" class="block w-100 py-2 hover:text-primary-600">Boote</a>
</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>
</div>
{% endif %}

View File

@@ -26,7 +26,7 @@
{% for log in logs %}
{% set_global allowed_to_edit = false %}
{% 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 %}
{% endif %}
{% endif %}