mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 22:52:22 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3774b358a5 | ||
|
|
c6b612e5a0 | ||
|
|
91586ee797 | ||
|
|
87adf94947 | ||
|
|
f850f9b3a3 | ||
|
|
0eb8359e26 | ||
|
|
7d961ea93b | ||
|
|
375caedec6 | ||
|
|
2d8d2eb194 | ||
|
|
69e788b363 | ||
|
|
8ea5321503 | ||
|
|
76c03fa4d4 | ||
|
|
a4285fb2ac |
216
Cargo.lock
generated
216
Cargo.lock
generated
@@ -32,12 +32,6 @@ version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
@@ -476,9 +470,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.3"
|
||||
version = "2.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
|
||||
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -540,10 +534,11 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.34"
|
||||
version = "1.2.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
|
||||
checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
@@ -561,17 +556,16 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.41"
|
||||
version = "0.4.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -595,9 +589,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.46"
|
||||
version = "4.5.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57"
|
||||
checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -605,9 +599,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.46"
|
||||
version = "4.5.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41"
|
||||
checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -617,9 +611,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.45"
|
||||
version = "4.5.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
|
||||
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -842,9 +836,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.4.0"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||
checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
"serde",
|
||||
@@ -1008,12 +1002,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.13"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1090,6 +1084,12 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d"
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.1"
|
||||
@@ -1288,7 +1288,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.3+wasi-0.2.4",
|
||||
"wasi 0.14.5+wasi-0.2.4",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@@ -1339,7 +1339,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.11.1",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -1737,9 +1737,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.11.0"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
|
||||
checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.5",
|
||||
@@ -1811,9 +1811,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
version = "0.3.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
@@ -1873,9 +1873,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.4"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
@@ -1896,9 +1896,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
dependencies = [
|
||||
"value-bag",
|
||||
]
|
||||
@@ -2599,9 +2599,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.37.5"
|
||||
version = "0.38.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
|
||||
checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -3017,7 +3017,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustical"
|
||||
version = "0.9.2"
|
||||
version = "0.9.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -3060,7 +3060,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustical_caldav"
|
||||
version = "0.9.2"
|
||||
version = "0.9.4"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-trait",
|
||||
@@ -3100,7 +3100,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustical_carddav"
|
||||
version = "0.9.2"
|
||||
version = "0.9.4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
@@ -3132,7 +3132,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustical_dav"
|
||||
version = "0.9.2"
|
||||
version = "0.9.4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
@@ -3157,7 +3157,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustical_dav_push"
|
||||
version = "0.9.2"
|
||||
version = "0.9.4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
@@ -3182,7 +3182,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustical_frontend"
|
||||
version = "0.9.2"
|
||||
version = "0.9.4"
|
||||
dependencies = [
|
||||
"askama",
|
||||
"askama_web",
|
||||
@@ -3215,7 +3215,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustical_ical"
|
||||
version = "0.9.2"
|
||||
version = "0.9.4"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"chrono",
|
||||
@@ -3233,7 +3233,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustical_oidc"
|
||||
version = "0.9.2"
|
||||
version = "0.9.4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
@@ -3248,7 +3248,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustical_store"
|
||||
version = "0.9.2"
|
||||
version = "0.9.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3282,7 +3282,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustical_store_sqlite"
|
||||
version = "0.9.2"
|
||||
version = "0.9.4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -3303,7 +3303,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustical_xml"
|
||||
version = "0.9.2"
|
||||
version = "0.9.4"
|
||||
dependencies = [
|
||||
"quick-xml",
|
||||
"thiserror 2.0.16",
|
||||
@@ -3312,15 +3312,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.8"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3530,7 +3530,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.11.1",
|
||||
"schemars 0.9.0",
|
||||
"schemars 1.0.4",
|
||||
"serde",
|
||||
@@ -3690,7 +3690,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"hashbrown 0.15.5",
|
||||
"hashlink",
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.11.1",
|
||||
"log",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
@@ -3994,12 +3994,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.41"
|
||||
version = "0.3.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
|
||||
checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
@@ -4009,15 +4008,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.4"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
|
||||
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.22"
|
||||
version = "0.2.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
|
||||
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
@@ -4132,7 +4131,7 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
|
||||
dependencies = [
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.11.1",
|
||||
"serde",
|
||||
"serde_spanned 1.0.0",
|
||||
"toml_datetime 0.7.0",
|
||||
@@ -4165,7 +4164,7 @@ version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.11.1",
|
||||
"serde",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
@@ -4228,7 +4227,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.11.1",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"sync_wrapper",
|
||||
@@ -4461,9 +4460,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
@@ -4518,9 +4517,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.18.0"
|
||||
version = "1.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be"
|
||||
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"js-sys",
|
||||
@@ -4590,9 +4589,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.3+wasi-0.2.4"
|
||||
version = "0.14.5+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95"
|
||||
checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4"
|
||||
dependencies = [
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.0+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
@@ -4605,21 +4613,22 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
@@ -4631,9 +4640,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.50"
|
||||
version = "0.4.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
|
||||
checksum = "0ca85039a9b469b38336411d6d6ced91f3fc87109a2a27b0c197663f5144dffe"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -4644,9 +4653,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -4654,9 +4663,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4667,18 +4676,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.77"
|
||||
version = "0.3.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
|
||||
checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -4715,11 +4724,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4730,7 +4739,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
@@ -4763,13 +4772,19 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4778,7 +4793,7 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4817,6 +4832,15 @@ dependencies = [
|
||||
"windows-targets 0.53.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
|
||||
dependencies = [
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
@@ -4854,7 +4878,7 @@ version = "0.53.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
"windows_aarch64_gnullvm 0.53.0",
|
||||
"windows_aarch64_msvc 0.53.0",
|
||||
"windows_i686_gnu 0.53.0",
|
||||
@@ -5014,9 +5038,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.45.0"
|
||||
version = "0.45.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814"
|
||||
checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
@@ -5067,18 +5091,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.26"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
|
||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.26"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
|
||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
members = ["crates/*"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.9.2"
|
||||
version = "0.9.4"
|
||||
edition = "2024"
|
||||
description = "A CalDAV server"
|
||||
documentation = "https://lennart-k.github.io/rustical/"
|
||||
repository = "https://github.com/lennart-k/rustical"
|
||||
license = "AGPL-3.0-or-later"
|
||||
|
||||
@@ -16,7 +17,7 @@ description.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
resolver = "2"
|
||||
publish = false
|
||||
publish = true
|
||||
|
||||
[features]
|
||||
debug = ["opentelemetry"]
|
||||
@@ -61,7 +62,7 @@ tokio = { version = "1", features = [
|
||||
url = "2.5"
|
||||
base64 = "0.22"
|
||||
thiserror = "2.0"
|
||||
quick-xml = { version = "0.37" }
|
||||
quick-xml = { version = "0.38" }
|
||||
rust-embed = "8.5"
|
||||
tower-sessions = "0.14"
|
||||
futures-core = "0.3.31"
|
||||
|
||||
@@ -45,4 +45,5 @@ CMD ["/usr/local/bin/rustical"]
|
||||
ENV RUSTICAL_DATA_STORE__SQLITE__DB_URL=/var/lib/rustical/db.sqlite3
|
||||
|
||||
LABEL org.opencontainers.image.authors="Lennart K github.com/lennart-k"
|
||||
LABEL org.opencontainers.image.licenses="AGPL-3.0-or-later"
|
||||
EXPOSE 4000
|
||||
|
||||
@@ -4,14 +4,15 @@ a CalDAV/CardDAV server
|
||||
|
||||
> [!WARNING]
|
||||
RustiCal is under **active development**!
|
||||
While I've been successfully using RustiCal productively for a few weeks now,
|
||||
While I've been successfully using RustiCal productively for some months now and there seems to be a growing user base,
|
||||
you'd still be one of the first testers so expect bugs and rough edges.
|
||||
If you still want to play around with it in its current state, absolutely feel free to do so and to open up an issue if something is not working. :)
|
||||
If you still want to use it in its current state, absolutely feel free to do so and to open up an issue if something is not working. :)
|
||||
|
||||
## Features
|
||||
|
||||
- easy to backup, everything saved in one SQLite database
|
||||
- also export feature in the frontend
|
||||
- Import your existing calendars in the frontend
|
||||
- **[WebDAV Push](https://github.com/bitfireAT/webdav-push/)** support, so near-instant synchronisation to DAVx5
|
||||
- lightweight (the container image contains only one binary)
|
||||
- adequately fast (I'd love to say blazingly fast™ :fire: but I don't have any benchmarks)
|
||||
|
||||
@@ -43,24 +43,24 @@ pub async fn route_get<C: CalendarStore, S: SubscriptionStore>(
|
||||
let mut ical_calendar_builder = IcalCalendarBuilder::version("4.0")
|
||||
.gregorian()
|
||||
.prodid("RustiCal");
|
||||
if calendar.displayname.is_some() {
|
||||
if let Some(displayname) = calendar.meta.displayname {
|
||||
ical_calendar_builder = ical_calendar_builder.set(Property {
|
||||
name: "X-WR-CALNAME".to_owned(),
|
||||
value: calendar.displayname,
|
||||
value: Some(displayname),
|
||||
params: None,
|
||||
});
|
||||
}
|
||||
if calendar.description.is_some() {
|
||||
if let Some(description) = calendar.meta.description {
|
||||
ical_calendar_builder = ical_calendar_builder.set(Property {
|
||||
name: "X-WR-CALDESC".to_owned(),
|
||||
value: calendar.description,
|
||||
value: Some(description),
|
||||
params: None,
|
||||
});
|
||||
}
|
||||
if calendar.timezone_id.is_some() {
|
||||
if let Some(timezone_id) = calendar.timezone_id {
|
||||
ical_calendar_builder = ical_calendar_builder.set(Property {
|
||||
name: "X-WR-TIMEZONE".to_owned(),
|
||||
value: calendar.timezone_id,
|
||||
value: Some(timezone_id),
|
||||
params: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ use ical::{
|
||||
parser::{Component, ComponentMut},
|
||||
};
|
||||
use rustical_ical::{CalendarObject, CalendarObjectType};
|
||||
use rustical_store::{Calendar, CalendarStore, SubscriptionStore, auth::Principal};
|
||||
use rustical_store::{
|
||||
Calendar, CalendarMetadata, CalendarStore, SubscriptionStore, auth::Principal,
|
||||
};
|
||||
use std::io::BufReader;
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -83,10 +85,12 @@ pub async fn route_import<C: CalendarStore, S: SubscriptionStore>(
|
||||
let new_cal = Calendar {
|
||||
principal,
|
||||
id: cal_id,
|
||||
displayname,
|
||||
order: 0,
|
||||
description,
|
||||
color: None,
|
||||
meta: CalendarMetadata {
|
||||
displayname,
|
||||
order: 0,
|
||||
description,
|
||||
color: None,
|
||||
},
|
||||
timezone_id,
|
||||
deleted_at: None,
|
||||
synctoken: 0,
|
||||
|
||||
@@ -8,7 +8,7 @@ use ical::IcalParser;
|
||||
use rustical_dav::xml::HrefElement;
|
||||
use rustical_ical::CalendarObjectType;
|
||||
use rustical_store::auth::Principal;
|
||||
use rustical_store::{Calendar, CalendarStore, SubscriptionStore};
|
||||
use rustical_store::{Calendar, CalendarMetadata, CalendarStore, SubscriptionStore};
|
||||
use rustical_xml::{Unparsed, XmlDeserialize, XmlDocument, XmlRootTag};
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -112,11 +112,13 @@ pub async fn route_mkcalendar<C: CalendarStore, S: SubscriptionStore>(
|
||||
let calendar = Calendar {
|
||||
id: cal_id.to_owned(),
|
||||
principal: principal.to_owned(),
|
||||
order: request.calendar_order.unwrap_or(0),
|
||||
displayname: request.displayname,
|
||||
meta: CalendarMetadata {
|
||||
order: request.calendar_order.unwrap_or(0),
|
||||
displayname: request.displayname,
|
||||
color: request.calendar_color,
|
||||
description: request.calendar_description,
|
||||
},
|
||||
timezone_id,
|
||||
color: request.calendar_color,
|
||||
description: request.calendar_description,
|
||||
deleted_at: None,
|
||||
synctoken: 0,
|
||||
subscription_url: request.source.map(|href| href.href),
|
||||
|
||||
@@ -116,19 +116,17 @@ impl CompFilterElement {
|
||||
// TODO: Implement prop-filter (and comp-filter?) at some point
|
||||
|
||||
if let Some(time_range) = &self.time_range {
|
||||
if let Some(start) = &time_range.start {
|
||||
if let Some(last_occurence) = cal_object.get_last_occurence().unwrap_or(None) {
|
||||
if start.deref() > &last_occurence.utc() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if let Some(start) = &time_range.start
|
||||
&& let Some(last_occurence) = cal_object.get_last_occurence().unwrap_or(None)
|
||||
&& start.deref() > &last_occurence.utc()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if let Some(end) = &time_range.end {
|
||||
if let Some(first_occurence) = cal_object.get_first_occurence().unwrap_or(None) {
|
||||
if end.deref() < &first_occurence.utc() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if let Some(end) = &time_range.end
|
||||
&& let Some(first_occurence) = cal_object.get_first_occurence().unwrap_or(None)
|
||||
&& end.deref() < &first_occurence.utc()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
@@ -156,15 +154,15 @@ impl From<&FilterElement> for CalendarQuery {
|
||||
for comp_filter in comp_filter_vcalendar.comp_filter.iter() {
|
||||
// A calendar object cannot contain both VEVENT and VTODO, so we only have to handle
|
||||
// whatever we get first
|
||||
if matches!(comp_filter.name.as_str(), "VEVENT" | "VTODO") {
|
||||
if let Some(time_range) = &comp_filter.time_range {
|
||||
let start = time_range.start.as_ref().map(|start| start.date_naive());
|
||||
let end = time_range.end.as_ref().map(|end| end.date_naive());
|
||||
return CalendarQuery {
|
||||
time_start: start,
|
||||
time_end: end,
|
||||
};
|
||||
}
|
||||
if matches!(comp_filter.name.as_str(), "VEVENT" | "VTODO")
|
||||
&& let Some(time_range) = &comp_filter.time_range
|
||||
{
|
||||
let start = time_range.start.as_ref().map(|start| start.date_naive());
|
||||
let end = time_range.end.as_ref().map(|end| end.date_naive());
|
||||
return CalendarQuery {
|
||||
time_start: start,
|
||||
time_end: end,
|
||||
};
|
||||
}
|
||||
}
|
||||
Default::default()
|
||||
|
||||
@@ -128,10 +128,10 @@ impl Resource for CalendarResource {
|
||||
Ok(match prop {
|
||||
CalendarPropWrapperName::Calendar(prop) => CalendarPropWrapper::Calendar(match prop {
|
||||
CalendarPropName::CalendarColor => {
|
||||
CalendarProp::CalendarColor(self.cal.color.clone())
|
||||
CalendarProp::CalendarColor(self.cal.meta.color.clone())
|
||||
}
|
||||
CalendarPropName::CalendarDescription => {
|
||||
CalendarProp::CalendarDescription(self.cal.description.clone())
|
||||
CalendarProp::CalendarDescription(self.cal.meta.description.clone())
|
||||
}
|
||||
CalendarPropName::CalendarTimezone => {
|
||||
CalendarProp::CalendarTimezone(self.cal.timezone_id.as_ref().and_then(|tzid| {
|
||||
@@ -146,7 +146,7 @@ impl Resource for CalendarResource {
|
||||
CalendarProp::CalendarTimezoneId(self.cal.timezone_id.clone())
|
||||
}
|
||||
CalendarPropName::CalendarOrder => {
|
||||
CalendarProp::CalendarOrder(Some(self.cal.order))
|
||||
CalendarProp::CalendarOrder(Some(self.cal.meta.order))
|
||||
}
|
||||
CalendarPropName::SupportedCalendarComponentSet => {
|
||||
CalendarProp::SupportedCalendarComponentSet(self.cal.components.clone().into())
|
||||
@@ -187,11 +187,11 @@ impl Resource for CalendarResource {
|
||||
match prop {
|
||||
CalendarPropWrapper::Calendar(prop) => match prop {
|
||||
CalendarProp::CalendarColor(color) => {
|
||||
self.cal.color = color;
|
||||
self.cal.meta.color = color;
|
||||
Ok(())
|
||||
}
|
||||
CalendarProp::CalendarDescription(description) => {
|
||||
self.cal.description = description;
|
||||
self.cal.meta.description = description;
|
||||
Ok(())
|
||||
}
|
||||
CalendarProp::CalendarTimezone(timezone) => {
|
||||
@@ -236,7 +236,7 @@ impl Resource for CalendarResource {
|
||||
Ok(())
|
||||
}
|
||||
CalendarProp::CalendarOrder(order) => {
|
||||
self.cal.order = order.unwrap_or_default();
|
||||
self.cal.meta.order = order.unwrap_or_default();
|
||||
Ok(())
|
||||
}
|
||||
CalendarProp::SupportedCalendarComponentSet(comp_set) => {
|
||||
@@ -264,11 +264,11 @@ impl Resource for CalendarResource {
|
||||
match prop {
|
||||
CalendarPropWrapperName::Calendar(prop) => match prop {
|
||||
CalendarPropName::CalendarColor => {
|
||||
self.cal.color = None;
|
||||
self.cal.meta.color = None;
|
||||
Ok(())
|
||||
}
|
||||
CalendarPropName::CalendarDescription => {
|
||||
self.cal.description = None;
|
||||
self.cal.meta.description = None;
|
||||
Ok(())
|
||||
}
|
||||
CalendarPropName::CalendarTimezone | CalendarPropName::CalendarTimezoneId => {
|
||||
@@ -277,7 +277,7 @@ impl Resource for CalendarResource {
|
||||
}
|
||||
CalendarPropName::TimezoneServiceSet => Err(rustical_dav::Error::PropReadOnly),
|
||||
CalendarPropName::CalendarOrder => {
|
||||
self.cal.order = 0;
|
||||
self.cal.meta.order = 0;
|
||||
Ok(())
|
||||
}
|
||||
CalendarPropName::SupportedCalendarComponentSet => {
|
||||
@@ -300,10 +300,10 @@ impl Resource for CalendarResource {
|
||||
}
|
||||
|
||||
fn get_displayname(&self) -> Option<&str> {
|
||||
self.cal.displayname.as_deref()
|
||||
self.cal.meta.displayname.as_deref()
|
||||
}
|
||||
fn set_displayname(&mut self, name: Option<String>) -> Result<(), rustical_dav::Error> {
|
||||
self.cal.displayname = name;
|
||||
self.cal.meta.displayname = name;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -79,5 +79,5 @@ async fn test_propfind() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let output = response.serialize_to_string().unwrap();
|
||||
let _output = response.serialize_to_string().unwrap();
|
||||
}
|
||||
|
||||
@@ -66,6 +66,9 @@ impl<PN: XmlDeserialize> XmlDeserialize for PropElement<PN> {
|
||||
Event::Text(_) | Event::CData(_) => {
|
||||
return Err(XmlError::UnsupportedEvent("Not expecting text here"));
|
||||
}
|
||||
Event::GeneralRef(_) => {
|
||||
return Err(::rustical_xml::XmlError::UnsupportedEvent("GeneralRef"));
|
||||
}
|
||||
Event::Decl(_) | Event::Comment(_) | Event::DocType(_) | Event::PI(_) => { /* ignore */
|
||||
}
|
||||
Event::End(_end) => {
|
||||
|
||||
@@ -29,16 +29,11 @@ impl XmlSerialize for TagList {
|
||||
});
|
||||
let has_prefix = prefix.is_some();
|
||||
let tagname = tag.map(|tag| [&prefix.unwrap_or_default(), tag].concat());
|
||||
let qname = tagname
|
||||
.as_ref()
|
||||
.map(|tagname| ::quick_xml::name::QName(tagname.as_bytes()));
|
||||
|
||||
if let Some(qname) = &qname {
|
||||
let mut bytes_start = BytesStart::from(qname.to_owned());
|
||||
if !has_prefix {
|
||||
if let Some(ns) = &ns {
|
||||
bytes_start.push_attribute((b"xmlns".as_ref(), ns.as_ref()));
|
||||
}
|
||||
if let Some(tagname) = tagname.as_ref() {
|
||||
let mut bytes_start = BytesStart::new(tagname);
|
||||
if !has_prefix && let Some(ns) = &ns {
|
||||
bytes_start.push_attribute((b"xmlns".as_ref(), ns.as_ref()));
|
||||
}
|
||||
writer.write_event(Event::Start(bytes_start))?;
|
||||
}
|
||||
@@ -51,8 +46,8 @@ impl XmlSerialize for TagList {
|
||||
el.write_empty()?;
|
||||
}
|
||||
|
||||
if let Some(qname) = &qname {
|
||||
writer.write_event(Event::End(BytesEnd::from(qname.to_owned())))?;
|
||||
if let Some(tagname) = tagname.as_ref() {
|
||||
writer.write_event(Event::End(BytesEnd::new(tagname)))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export class DeleteButton extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
let text = this.trash ? 'Move to trash' : 'Delete'
|
||||
let text = this.trash ? 'Trash' : 'Delete'
|
||||
return html`<button class="delete" @click=${e => this._onClick(e)}>${text}</button>`
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export class EditAddressbookForm extends LitElement {
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<button @click=${() => this.dialog.value.showModal()}>Edit addressbook</button>
|
||||
<button @click=${() => this.dialog.value.showModal()}>Edit</button>
|
||||
<dialog ${ref(this.dialog)}>
|
||||
<h3>Edit addressbook</h3>
|
||||
<form @submit=${this.submit} ${ref(this.form)}>
|
||||
|
||||
@@ -40,7 +40,7 @@ export class EditCalendarForm extends LitElement {
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<button @click=${() => this.dialog.value.showModal()}>Edit calendar</button>
|
||||
<button @click=${() => this.dialog.value.showModal()}>Edit</button>
|
||||
<dialog ${ref(this.dialog)}>
|
||||
<h3>Edit calendar</h3>
|
||||
<form @submit=${this.submit} ${ref(this.form)}>
|
||||
|
||||
@@ -19,7 +19,7 @@ let DeleteButton = class extends i {
|
||||
return this;
|
||||
}
|
||||
render() {
|
||||
let text = this.trash ? "Move to trash" : "Delete";
|
||||
let text = this.trash ? "Trash" : "Delete";
|
||||
return x`<button class="delete" @click=${(e) => this._onClick(e)}>${text}</button>`;
|
||||
}
|
||||
async _onClick(event) {
|
||||
|
||||
@@ -27,7 +27,7 @@ let EditAddressbookForm = class extends i {
|
||||
}
|
||||
render() {
|
||||
return x`
|
||||
<button @click=${() => this.dialog.value.showModal()}>Edit addressbook</button>
|
||||
<button @click=${() => this.dialog.value.showModal()}>Edit</button>
|
||||
<dialog ${n(this.dialog)}>
|
||||
<h3>Edit addressbook</h3>
|
||||
<form @submit=${this.submit} ${n(this.form)}>
|
||||
|
||||
@@ -28,7 +28,7 @@ let EditCalendarForm = class extends i {
|
||||
}
|
||||
render() {
|
||||
return x`
|
||||
<button @click=${() => this.dialog.value.showModal()}>Edit calendar</button>
|
||||
<button @click=${() => this.dialog.value.showModal()}>Edit</button>
|
||||
<dialog ${n(this.dialog)}>
|
||||
<h3>Edit calendar</h3>
|
||||
<form @submit=${this.submit} ${n(this.form)}>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<h2>{{ user.id }}'s Calendars</h2>
|
||||
<ul class="collection-list">
|
||||
{% for (meta, calendar) in calendars %}
|
||||
{% let color = calendar.color.to_owned().unwrap_or("transparent".to_owned()) %}
|
||||
{% let color = calendar.meta.color.to_owned().unwrap_or("transparent".to_owned()) %}
|
||||
<li class="collection-list-item" style="--color: {{ color }}">
|
||||
<a href="/frontend/user/{{ calendar.principal }}/calendar/{{ calendar.id }}"></a>
|
||||
<div class="inner">
|
||||
<span class="title">
|
||||
{%- if calendar.principal != user.id -%}{{ calendar.principal }}/{%- endif -%}
|
||||
{{ calendar.displayname.to_owned().unwrap_or(calendar.id.to_owned()) }}
|
||||
{{ calendar.meta.displayname.to_owned().unwrap_or(calendar.id.to_owned()) }}
|
||||
<div class="comps">
|
||||
{% for comp in calendar.components %}
|
||||
<span>{{ comp }}</span>
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
</span>
|
||||
<span class="description">
|
||||
{% if let Some(description) = calendar.description %}{{ description }}{% endif %}
|
||||
{% if let Some(description) = calendar.meta.description %}{{ description }}{% endif %}
|
||||
</span>
|
||||
{% if let Some(subscription_url) = calendar.subscription_url %}
|
||||
<span class="subscription-url">{{ subscription_url }}</span>
|
||||
@@ -29,9 +29,9 @@
|
||||
principal="{{ calendar.principal }}"
|
||||
cal_id="{{ calendar.id }}"
|
||||
timezone_id="{{ calendar.timezone_id.as_deref().unwrap_or_default() }}"
|
||||
displayname="{{ calendar.displayname.as_deref().unwrap_or_default() }}"
|
||||
description="{{ calendar.description.as_deref().unwrap_or_default() }}"
|
||||
color="{{ calendar.color.as_deref().unwrap_or_default() }}"
|
||||
displayname="{{ calendar.meta.displayname.as_deref().unwrap_or_default() }}"
|
||||
description="{{ calendar.meta.description.as_deref().unwrap_or_default() }}"
|
||||
color="{{ calendar.meta.color.as_deref().unwrap_or_default() }}"
|
||||
components="{{ calendar.components | json }}"
|
||||
></edit-calendar-form>
|
||||
<delete-button trash href="/caldav/principal/{{ calendar.principal }}/{{ calendar.id }}"></delete-button>
|
||||
@@ -51,13 +51,13 @@
|
||||
<h3>Deleted Calendars</h3>
|
||||
<ul class="collection-list">
|
||||
{% for (meta, calendar) in deleted_calendars %}
|
||||
{% let color = calendar.color.to_owned().unwrap_or("transparent".to_owned()) %}
|
||||
{% let color = calendar.meta.color.to_owned().unwrap_or("transparent".to_owned()) %}
|
||||
<li class="collection-list-item" style="--color: {{ color }}">
|
||||
<a href="/frontend/user/{{ calendar.principal }}/calendar/{{ calendar.id}}"></a>
|
||||
<div class="inner">
|
||||
<span class="title">
|
||||
{%- if calendar.principal != user.id -%}{{ calendar.principal }}/{%- endif -%}
|
||||
{{ calendar.displayname.to_owned().unwrap_or(calendar.id.to_owned()) }}
|
||||
{{ calendar.meta.displayname.to_owned().unwrap_or(calendar.id.to_owned()) }}
|
||||
<div class="comps">
|
||||
{% for comp in calendar.components %}
|
||||
<span>{{ comp }}</span>
|
||||
@@ -65,7 +65,7 @@
|
||||
</div>
|
||||
</span>
|
||||
<span class="description">
|
||||
{% if let Some(description) = calendar.description %}{{ description }}{% endif %}
|
||||
{% if let Some(description) = calendar.meta.description %}{{ description }}{% endif %}
|
||||
</span>
|
||||
<div class="actions">
|
||||
<form action="/frontend/user/{{ calendar.principal }}/calendar/{{ calendar.id}}/restore" method="POST"
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% let name = calendar.displayname.to_owned().unwrap_or(calendar.id.to_owned()) %}
|
||||
{% let name = calendar.meta.displayname.to_owned().unwrap_or(calendar.id.to_owned()) %}
|
||||
<h1>{{ calendar.principal }}/{{ name }}</h1>
|
||||
{% if let Some(description) = calendar.description %}<p>{{ description }}</p>{% endif%}
|
||||
{% if let Some(description) = calendar.meta.description %}<p>{{ description }}</p>{% endif%}
|
||||
|
||||
{% if let Some(subscription_url) = calendar.subscription_url %}
|
||||
<h2>Subscription URL</h2>
|
||||
@@ -25,9 +25,6 @@
|
||||
{% if let Some(timezone_id) = calendar.timezone_id %}
|
||||
<p>{{ timezone_id }}</p>
|
||||
{% endif %}
|
||||
{% if let Some(timezone) = calendar.get_vtimezone() %}
|
||||
<textarea rows="16" readonly>{{ timezone }}</textarea>
|
||||
{% endif %}
|
||||
|
||||
<pre>{{ calendar|json }}</pre>
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use tower::Service;
|
||||
|
||||
#[derive(Clone, RustEmbed, Default)]
|
||||
#[folder = "public/assets"]
|
||||
#[allow(dead_code)] // Since this is not used with the frontend-dev feature
|
||||
pub struct Assets;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
|
||||
@@ -192,20 +192,19 @@ pub async fn route_get_oidc_callback<US: UserStore + Clone>(
|
||||
.await
|
||||
.map_err(|e| OidcError::UserInfo(e.to_string()))?;
|
||||
|
||||
if let Some(require_group) = &oidc_config.require_group {
|
||||
if !user_info_claims
|
||||
if let Some(require_group) = &oidc_config.require_group
|
||||
&& !user_info_claims
|
||||
.additional_claims()
|
||||
.groups
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.contains(require_group)
|
||||
{
|
||||
return Ok((
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"User is not in an authorized group to use RustiCal",
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
{
|
||||
return Ok((
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"User is not in an authorized group to use RustiCal",
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
|
||||
let user_id = match oidc_config.claim_userid {
|
||||
|
||||
@@ -72,12 +72,11 @@ where
|
||||
let mut inner = self.inner.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
if let Some(session) = request.extensions().get::<Session>() {
|
||||
if let Ok(Some(user_id)) = session.get::<String>("user").await {
|
||||
if let Ok(Some(user)) = ap.get_principal(&user_id).await {
|
||||
request.extensions_mut().insert(user);
|
||||
}
|
||||
}
|
||||
if let Some(session) = request.extensions().get::<Session>()
|
||||
&& let Ok(Some(user_id)) = session.get::<String>("user").await
|
||||
&& let Ok(Some(user)) = ap.get_principal(&user_id).await
|
||||
{
|
||||
request.extensions_mut().insert(user);
|
||||
}
|
||||
|
||||
if let Some(auth) = auth_header {
|
||||
|
||||
@@ -6,13 +6,23 @@ use rustical_ical::CalendarObjectType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct Calendar {
|
||||
pub principal: String,
|
||||
pub id: String,
|
||||
pub struct CalendarMetadata {
|
||||
// Attributes that may be outsourced
|
||||
pub displayname: Option<String>,
|
||||
pub order: i64,
|
||||
pub description: Option<String>,
|
||||
pub color: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct Calendar {
|
||||
// Attributes that may be outsourced
|
||||
#[serde(flatten)]
|
||||
pub meta: CalendarMetadata,
|
||||
|
||||
// Common calendar attributes
|
||||
pub principal: String,
|
||||
pub id: String,
|
||||
pub timezone_id: Option<String>,
|
||||
pub deleted_at: Option<NaiveDateTime>,
|
||||
pub synctoken: i64,
|
||||
|
||||
@@ -1,282 +1,208 @@
|
||||
use crate::CalendarStore;
|
||||
use async_trait::async_trait;
|
||||
use derive_more::Constructor;
|
||||
use rustical_ical::CalendarObject;
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
Calendar, CalendarStore, Error, calendar_store::CalendarQuery,
|
||||
contact_birthday_store::BIRTHDAYS_PREFIX,
|
||||
};
|
||||
|
||||
#[derive(Debug, Constructor)]
|
||||
pub struct CombinedCalendarStore<CS: CalendarStore, BS: CalendarStore> {
|
||||
cal_store: Arc<CS>,
|
||||
birthday_store: Arc<BS>,
|
||||
pub trait PrefixedCalendarStore: CalendarStore {
|
||||
const PREFIX: &'static str;
|
||||
}
|
||||
|
||||
impl<CS: CalendarStore, BS: CalendarStore> Clone for CombinedCalendarStore<CS, BS> {
|
||||
fn clone(&self) -> Self {
|
||||
#[derive(Clone)]
|
||||
pub struct CombinedCalendarStore {
|
||||
stores: HashMap<&'static str, Arc<dyn CalendarStore>>,
|
||||
default: Arc<dyn CalendarStore>,
|
||||
}
|
||||
|
||||
impl CombinedCalendarStore {
|
||||
pub fn new(default: Arc<dyn CalendarStore>) -> Self {
|
||||
Self {
|
||||
cal_store: self.cal_store.clone(),
|
||||
birthday_store: self.birthday_store.clone(),
|
||||
stores: HashMap::new(),
|
||||
default,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_store<CS: PrefixedCalendarStore>(mut self, store: Arc<CS>) -> Self {
|
||||
let store: Arc<dyn CalendarStore> = store;
|
||||
self.stores.insert(CS::PREFIX, store);
|
||||
self
|
||||
}
|
||||
|
||||
fn store_for_id(&self, id: &str) -> Arc<dyn CalendarStore> {
|
||||
self.stores
|
||||
.iter()
|
||||
.find(|&(prefix, _store)| id.starts_with(prefix))
|
||||
.map(|(_prefix, store)| store.clone())
|
||||
.unwrap_or(self.default.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<CS: CalendarStore, BS: CalendarStore> CalendarStore for CombinedCalendarStore<CS, BS> {
|
||||
impl CalendarStore for CombinedCalendarStore {
|
||||
#[inline]
|
||||
async fn get_calendar(
|
||||
&self,
|
||||
principal: &str,
|
||||
id: &str,
|
||||
show_deleted: bool,
|
||||
) -> Result<Calendar, Error> {
|
||||
if id.starts_with(BIRTHDAYS_PREFIX) {
|
||||
self.birthday_store
|
||||
.get_calendar(principal, id, show_deleted)
|
||||
.await
|
||||
} else {
|
||||
self.cal_store
|
||||
.get_calendar(principal, id, show_deleted)
|
||||
.await
|
||||
}
|
||||
) -> Result<crate::Calendar, crate::Error> {
|
||||
self.store_for_id(id)
|
||||
.get_calendar(principal, id, show_deleted)
|
||||
.await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn update_calendar(
|
||||
&self,
|
||||
principal: String,
|
||||
id: String,
|
||||
calendar: Calendar,
|
||||
calendar: crate::Calendar,
|
||||
) -> Result<(), crate::Error> {
|
||||
if id.starts_with(BIRTHDAYS_PREFIX) {
|
||||
self.birthday_store
|
||||
.update_calendar(principal, id, calendar)
|
||||
.await
|
||||
} else {
|
||||
self.cal_store
|
||||
.update_calendar(principal, id, calendar)
|
||||
.await
|
||||
}
|
||||
self.store_for_id(&id)
|
||||
.update_calendar(principal, id, calendar)
|
||||
.await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn insert_calendar(&self, calendar: Calendar) -> Result<(), Error> {
|
||||
if calendar.id.starts_with(BIRTHDAYS_PREFIX) {
|
||||
Err(Error::ReadOnly)
|
||||
} else {
|
||||
self.cal_store.insert_calendar(calendar).await
|
||||
}
|
||||
async fn insert_calendar(&self, calendar: crate::Calendar) -> Result<(), crate::Error> {
|
||||
self.store_for_id(&calendar.id)
|
||||
.insert_calendar(calendar)
|
||||
.await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn get_calendars(&self, principal: &str) -> Result<Vec<Calendar>, Error> {
|
||||
Ok([
|
||||
self.cal_store.get_calendars(principal).await?,
|
||||
self.birthday_store.get_calendars(principal).await?,
|
||||
]
|
||||
.concat())
|
||||
async fn delete_calendar(
|
||||
&self,
|
||||
principal: &str,
|
||||
name: &str,
|
||||
use_trashbin: bool,
|
||||
) -> Result<(), crate::Error> {
|
||||
self.store_for_id(name)
|
||||
.delete_calendar(principal, name, use_trashbin)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn restore_calendar(&self, principal: &str, name: &str) -> Result<(), crate::Error> {
|
||||
self.store_for_id(name)
|
||||
.restore_calendar(principal, name)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn sync_changes(
|
||||
&self,
|
||||
principal: &str,
|
||||
cal_id: &str,
|
||||
synctoken: i64,
|
||||
) -> Result<(Vec<rustical_ical::CalendarObject>, Vec<String>, i64), crate::Error> {
|
||||
self.store_for_id(cal_id)
|
||||
.sync_changes(principal, cal_id, synctoken)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn import_calendar(
|
||||
&self,
|
||||
calendar: crate::Calendar,
|
||||
objects: Vec<rustical_ical::CalendarObject>,
|
||||
merge_existing: bool,
|
||||
) -> Result<(), crate::Error> {
|
||||
self.store_for_id(&calendar.id)
|
||||
.import_calendar(calendar, objects, merge_existing)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn calendar_query(
|
||||
&self,
|
||||
principal: &str,
|
||||
cal_id: &str,
|
||||
query: crate::calendar_store::CalendarQuery,
|
||||
) -> Result<Vec<rustical_ical::CalendarObject>, crate::Error> {
|
||||
self.store_for_id(cal_id)
|
||||
.calendar_query(principal, cal_id, query)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn restore_object(
|
||||
&self,
|
||||
principal: &str,
|
||||
cal_id: &str,
|
||||
object_id: &str,
|
||||
) -> Result<(), crate::Error> {
|
||||
self.store_for_id(cal_id)
|
||||
.restore_object(principal, cal_id, object_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn calendar_metadata(
|
||||
&self,
|
||||
principal: &str,
|
||||
cal_id: &str,
|
||||
) -> Result<crate::CollectionMetadata, crate::Error> {
|
||||
self.store_for_id(cal_id)
|
||||
.calendar_metadata(principal, cal_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_objects(
|
||||
&self,
|
||||
principal: &str,
|
||||
cal_id: &str,
|
||||
) -> Result<Vec<rustical_ical::CalendarObject>, crate::Error> {
|
||||
self.store_for_id(cal_id)
|
||||
.get_objects(principal, cal_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn put_object(
|
||||
&self,
|
||||
principal: String,
|
||||
cal_id: String,
|
||||
object: rustical_ical::CalendarObject,
|
||||
overwrite: bool,
|
||||
) -> Result<(), crate::Error> {
|
||||
self.store_for_id(&cal_id)
|
||||
.put_object(principal, cal_id, object, overwrite)
|
||||
.await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn delete_object(
|
||||
&self,
|
||||
principal: &str,
|
||||
cal_id: &str,
|
||||
object_id: &str,
|
||||
use_trashbin: bool,
|
||||
) -> Result<(), Error> {
|
||||
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||
self.birthday_store
|
||||
.delete_object(principal, cal_id, object_id, use_trashbin)
|
||||
.await
|
||||
} else {
|
||||
self.cal_store
|
||||
.delete_object(principal, cal_id, object_id, use_trashbin)
|
||||
.await
|
||||
}
|
||||
) -> Result<(), crate::Error> {
|
||||
self.store_for_id(cal_id)
|
||||
.delete_object(principal, cal_id, object_id, use_trashbin)
|
||||
.await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn get_object(
|
||||
&self,
|
||||
principal: &str,
|
||||
cal_id: &str,
|
||||
object_id: &str,
|
||||
show_deleted: bool,
|
||||
) -> Result<CalendarObject, Error> {
|
||||
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||
self.birthday_store
|
||||
.get_object(principal, cal_id, object_id, show_deleted)
|
||||
.await
|
||||
} else {
|
||||
self.cal_store
|
||||
.get_object(principal, cal_id, object_id, show_deleted)
|
||||
.await
|
||||
}
|
||||
) -> Result<rustical_ical::CalendarObject, crate::Error> {
|
||||
self.store_for_id(cal_id)
|
||||
.get_object(principal, cal_id, object_id, show_deleted)
|
||||
.await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn sync_changes(
|
||||
async fn get_calendars(&self, principal: &str) -> Result<Vec<crate::Calendar>, crate::Error> {
|
||||
let mut calendars = self.default.get_calendars(principal).await?;
|
||||
for store in self.stores.values() {
|
||||
calendars.extend(store.get_calendars(principal).await?);
|
||||
}
|
||||
Ok(calendars)
|
||||
}
|
||||
|
||||
async fn get_deleted_calendars(
|
||||
&self,
|
||||
principal: &str,
|
||||
cal_id: &str,
|
||||
synctoken: i64,
|
||||
) -> Result<(Vec<CalendarObject>, Vec<String>, i64), Error> {
|
||||
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||
self.birthday_store
|
||||
.sync_changes(principal, cal_id, synctoken)
|
||||
.await
|
||||
} else {
|
||||
self.cal_store
|
||||
.sync_changes(principal, cal_id, synctoken)
|
||||
.await
|
||||
) -> Result<Vec<crate::Calendar>, crate::Error> {
|
||||
let mut calendars = self.default.get_deleted_calendars(principal).await?;
|
||||
for store in self.stores.values() {
|
||||
calendars.extend(store.get_deleted_calendars(principal).await?);
|
||||
}
|
||||
Ok(calendars)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn calendar_metadata(
|
||||
&self,
|
||||
principal: &str,
|
||||
cal_id: &str,
|
||||
) -> Result<crate::CollectionMetadata, Error> {
|
||||
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||
self.birthday_store
|
||||
.calendar_metadata(principal, cal_id)
|
||||
.await
|
||||
} else {
|
||||
self.cal_store.calendar_metadata(principal, cal_id).await
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
async fn get_objects(
|
||||
&self,
|
||||
principal: &str,
|
||||
cal_id: &str,
|
||||
) -> Result<Vec<CalendarObject>, Error> {
|
||||
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||
self.birthday_store.get_objects(principal, cal_id).await
|
||||
} else {
|
||||
self.cal_store.get_objects(principal, cal_id).await
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn calendar_query(
|
||||
&self,
|
||||
principal: &str,
|
||||
cal_id: &str,
|
||||
query: CalendarQuery,
|
||||
) -> Result<Vec<CalendarObject>, Error> {
|
||||
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||
self.birthday_store
|
||||
.calendar_query(principal, cal_id, query)
|
||||
.await
|
||||
} else {
|
||||
self.cal_store
|
||||
.calendar_query(principal, cal_id, query)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn restore_calendar(&self, principal: &str, name: &str) -> Result<(), Error> {
|
||||
if name.starts_with(BIRTHDAYS_PREFIX) {
|
||||
self.birthday_store.restore_calendar(principal, name).await
|
||||
} else {
|
||||
self.cal_store.restore_calendar(principal, name).await
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn import_calendar(
|
||||
&self,
|
||||
calendar: Calendar,
|
||||
objects: Vec<CalendarObject>,
|
||||
merge_existing: bool,
|
||||
) -> Result<(), Error> {
|
||||
if calendar.id.starts_with(BIRTHDAYS_PREFIX) {
|
||||
self.birthday_store
|
||||
.import_calendar(calendar, objects, merge_existing)
|
||||
.await
|
||||
} else {
|
||||
self.cal_store
|
||||
.import_calendar(calendar, objects, merge_existing)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn delete_calendar(
|
||||
&self,
|
||||
principal: &str,
|
||||
name: &str,
|
||||
use_trashbin: bool,
|
||||
) -> Result<(), Error> {
|
||||
if name.starts_with(BIRTHDAYS_PREFIX) {
|
||||
self.birthday_store
|
||||
.delete_calendar(principal, name, use_trashbin)
|
||||
.await
|
||||
} else {
|
||||
self.cal_store
|
||||
.delete_calendar(principal, name, use_trashbin)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn get_deleted_calendars(&self, principal: &str) -> Result<Vec<Calendar>, Error> {
|
||||
Ok([
|
||||
self.birthday_store.get_deleted_calendars(principal).await?,
|
||||
self.cal_store.get_deleted_calendars(principal).await?,
|
||||
]
|
||||
.concat())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn restore_object(
|
||||
&self,
|
||||
principal: &str,
|
||||
cal_id: &str,
|
||||
object_id: &str,
|
||||
) -> Result<(), Error> {
|
||||
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||
self.birthday_store
|
||||
.restore_object(principal, cal_id, object_id)
|
||||
.await
|
||||
} else {
|
||||
self.cal_store
|
||||
.restore_object(principal, cal_id, object_id)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn put_object(
|
||||
&self,
|
||||
principal: String,
|
||||
cal_id: String,
|
||||
object: CalendarObject,
|
||||
overwrite: bool,
|
||||
) -> Result<(), Error> {
|
||||
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||
self.birthday_store
|
||||
.put_object(principal, cal_id, object, overwrite)
|
||||
.await
|
||||
} else {
|
||||
self.cal_store
|
||||
.put_object(principal, cal_id, object, overwrite)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_read_only(&self, cal_id: &str) -> bool {
|
||||
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||
self.birthday_store.is_read_only(cal_id)
|
||||
} else {
|
||||
self.cal_store.is_read_only(cal_id)
|
||||
}
|
||||
self.store_for_id(cal_id).is_read_only(cal_id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::{Addressbook, AddressbookStore, Calendar, CalendarStore, Error};
|
||||
use crate::{
|
||||
Addressbook, AddressbookStore, Calendar, CalendarStore, Error, calendar::CalendarMetadata,
|
||||
combined_calendar_store::PrefixedCalendarStore,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use derive_more::derive::Constructor;
|
||||
use rustical_ical::{AddressObject, CalendarObject, CalendarObjectType};
|
||||
@@ -10,16 +13,22 @@ pub(crate) const BIRTHDAYS_PREFIX: &str = "_birthdays_";
|
||||
#[derive(Constructor, Clone)]
|
||||
pub struct ContactBirthdayStore<AS: AddressbookStore>(Arc<AS>);
|
||||
|
||||
impl<AS: AddressbookStore> PrefixedCalendarStore for ContactBirthdayStore<AS> {
|
||||
const PREFIX: &'static str = BIRTHDAYS_PREFIX;
|
||||
}
|
||||
|
||||
fn birthday_calendar(addressbook: Addressbook) -> Calendar {
|
||||
Calendar {
|
||||
principal: addressbook.principal,
|
||||
id: format!("{}{}", BIRTHDAYS_PREFIX, addressbook.id),
|
||||
displayname: addressbook
|
||||
.displayname
|
||||
.map(|name| format!("{name} birthdays")),
|
||||
order: 0,
|
||||
description: None,
|
||||
color: None,
|
||||
meta: CalendarMetadata {
|
||||
displayname: addressbook
|
||||
.displayname
|
||||
.map(|name| format!("{name} birthdays")),
|
||||
order: 0,
|
||||
description: None,
|
||||
color: None,
|
||||
},
|
||||
timezone_id: None,
|
||||
deleted_at: addressbook.deleted_at,
|
||||
synctoken: addressbook.synctoken,
|
||||
|
||||
@@ -22,7 +22,7 @@ pub use secret::Secret;
|
||||
pub use subscription_store::*;
|
||||
|
||||
pub use addressbook::Addressbook;
|
||||
pub use calendar::Calendar;
|
||||
pub use calendar::{Calendar, CalendarMetadata};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CollectionOperationInfo {
|
||||
|
||||
@@ -433,14 +433,14 @@ impl AddressbookStore for SqliteAddressbookStore {
|
||||
Self::_delete_addressbook(&mut *tx, principal, addressbook_id, use_trashbin).await?;
|
||||
tx.commit().await.map_err(crate::Error::from)?;
|
||||
|
||||
if let Some(addressbook) = addressbook {
|
||||
if let Err(err) = self.sender.try_send(CollectionOperation {
|
||||
if let Some(addressbook) = addressbook
|
||||
&& let Err(err) = self.sender.try_send(CollectionOperation {
|
||||
data: CollectionOperationInfo::Delete,
|
||||
topic: addressbook.push_topic,
|
||||
}) {
|
||||
error!("Push notification about deleted addressbook failed: {err}");
|
||||
};
|
||||
}
|
||||
})
|
||||
{
|
||||
error!("Push notification about deleted addressbook failed: {err}");
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use derive_more::derive::Constructor;
|
||||
use rustical_ical::{CalDateTime, CalendarObject, CalendarObjectType};
|
||||
use rustical_store::calendar_store::CalendarQuery;
|
||||
use rustical_store::synctoken::format_synctoken;
|
||||
use rustical_store::{Calendar, CalendarStore, CollectionMetadata, Error};
|
||||
use rustical_store::{Calendar, CalendarMetadata, CalendarStore, CollectionMetadata, Error};
|
||||
use rustical_store::{CollectionOperation, CollectionOperationInfo};
|
||||
use sqlx::types::chrono::NaiveDateTime;
|
||||
use sqlx::{Acquire, Executor, Sqlite, SqlitePool, Transaction};
|
||||
@@ -69,10 +69,12 @@ impl From<CalendarRow> for Calendar {
|
||||
Self {
|
||||
principal: value.principal,
|
||||
id: value.id,
|
||||
displayname: value.displayname,
|
||||
order: value.order,
|
||||
description: value.description,
|
||||
color: value.color,
|
||||
meta: CalendarMetadata {
|
||||
displayname: value.displayname,
|
||||
order: value.order,
|
||||
description: value.description,
|
||||
color: value.color,
|
||||
},
|
||||
timezone_id: value.timezone_id,
|
||||
deleted_at: value.deleted_at,
|
||||
synctoken: value.synctoken,
|
||||
@@ -159,10 +161,10 @@ impl SqliteCalendarStore {
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"#,
|
||||
calendar.principal,
|
||||
calendar.id,
|
||||
calendar.displayname,
|
||||
calendar.description,
|
||||
calendar.order,
|
||||
calendar.color,
|
||||
calendar.meta.displayname,
|
||||
calendar.meta.description,
|
||||
calendar.meta.order,
|
||||
calendar.meta.color,
|
||||
calendar.subscription_url,
|
||||
calendar.timezone_id,
|
||||
calendar.push_topic,
|
||||
@@ -189,10 +191,10 @@ impl SqliteCalendarStore {
|
||||
WHERE (principal, id) = (?, ?)"#,
|
||||
calendar.principal,
|
||||
calendar.id,
|
||||
calendar.displayname,
|
||||
calendar.description,
|
||||
calendar.order,
|
||||
calendar.color,
|
||||
calendar.meta.displayname,
|
||||
calendar.meta.description,
|
||||
calendar.meta.order,
|
||||
calendar.meta.color,
|
||||
calendar.timezone_id,
|
||||
calendar.push_topic,
|
||||
comp_event, comp_todo, comp_journal,
|
||||
@@ -351,7 +353,6 @@ impl SqliteCalendarStore {
|
||||
object: CalendarObject,
|
||||
overwrite: bool,
|
||||
) -> Result<(), Error> {
|
||||
// TODO: Prevent objects from being commited to a subscription calendar
|
||||
let (object_id, ics) = (object.get_id(), object.get_ics());
|
||||
|
||||
let first_occurence = object
|
||||
@@ -554,14 +555,14 @@ impl CalendarStore for SqliteCalendarStore {
|
||||
Self::_delete_calendar(&mut *tx, principal, id, use_trashbin).await?;
|
||||
tx.commit().await.map_err(crate::Error::from)?;
|
||||
|
||||
if let Some(cal) = cal {
|
||||
if let Err(err) = self.sender.try_send(CollectionOperation {
|
||||
if let Some(cal) = cal
|
||||
&& let Err(err) = self.sender.try_send(CollectionOperation {
|
||||
data: CollectionOperationInfo::Delete,
|
||||
topic: cal.push_topic,
|
||||
}) {
|
||||
error!("Push notification about deleted calendar failed: {err}");
|
||||
};
|
||||
}
|
||||
})
|
||||
{
|
||||
error!("Push notification about deleted calendar failed: {err}");
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -667,11 +668,16 @@ impl CalendarStore for SqliteCalendarStore {
|
||||
object: CalendarObject,
|
||||
overwrite: bool,
|
||||
) -> Result<(), Error> {
|
||||
// TODO: Prevent objects from being commited to a subscription calendar
|
||||
let mut tx = self.db.begin().await.map_err(crate::Error::from)?;
|
||||
|
||||
let object_id = object.get_id().to_owned();
|
||||
|
||||
let calendar = Self::_get_calendar(&mut *tx, &principal, &cal_id, true).await?;
|
||||
if calendar.subscription_url.is_some() {
|
||||
// We cannot commit an object to a subscription calendar
|
||||
return Err(Error::ReadOnly);
|
||||
}
|
||||
|
||||
Self::_put_object(
|
||||
&mut *tx,
|
||||
principal.to_owned(),
|
||||
|
||||
@@ -34,12 +34,11 @@ impl Enum {
|
||||
});
|
||||
let has_prefix = prefix.is_some();
|
||||
let tagname = tag.map(|tag| [&prefix.unwrap_or_default(), tag].concat());
|
||||
let qname = tagname.as_ref().map(|tagname| ::quick_xml::name::QName(tagname.as_bytes()));
|
||||
|
||||
const enum_untagged: bool = #enum_untagged;
|
||||
|
||||
if let Some(qname) = &qname {
|
||||
let mut bytes_start = BytesStart::from(qname.to_owned());
|
||||
if let Some(tagname) = tagname.as_ref() {
|
||||
let mut bytes_start = BytesStart::new(tagname);
|
||||
if !has_prefix {
|
||||
if let Some(ns) = &ns {
|
||||
bytes_start.push_attribute((b"xmlns".as_ref(), ns.as_ref()));
|
||||
@@ -50,8 +49,8 @@ impl Enum {
|
||||
|
||||
#(#variant_serializers);*
|
||||
|
||||
if let Some(qname) = &qname {
|
||||
writer.write_event(Event::End(BytesEnd::from(qname.to_owned())))?;
|
||||
if let Some(tagname) = tagname.as_ref() {
|
||||
writer.write_event(Event::End(BytesEnd::new(tagname)))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -66,6 +66,9 @@ impl Enum {
|
||||
Event::CData(cdata) => {
|
||||
return Err(::rustical_xml::XmlError::UnsupportedEvent("CDATA"));
|
||||
}
|
||||
Event::GeneralRef(_) => {
|
||||
return Err(::rustical_xml::XmlError::UnsupportedEvent("GeneralRef"));
|
||||
}
|
||||
Event::Decl(_) => { /* <?xml ... ?> ignore this */ }
|
||||
Event::Comment(_) => { /* ignore */ }
|
||||
Event::DocType(_) => { /* ignore */ }
|
||||
|
||||
@@ -111,10 +111,9 @@ impl NamedStruct {
|
||||
});
|
||||
let has_prefix = prefix.is_some();
|
||||
let tagname = tag.map(|tag| [&prefix.unwrap_or_default(), tag].concat());
|
||||
let qname = tagname.as_ref().map(|tagname| ::quick_xml::name::QName(tagname.as_bytes()));
|
||||
|
||||
if let Some(qname) = &qname {
|
||||
let mut bytes_start = BytesStart::from(qname.to_owned());
|
||||
if let Some(tagname) = tagname.as_ref() {
|
||||
let mut bytes_start = BytesStart::new(tagname);
|
||||
if !has_prefix {
|
||||
if let Some(ns) = &ns {
|
||||
bytes_start.push_attribute((b"xmlns".as_ref(), ns.as_ref()));
|
||||
@@ -133,8 +132,8 @@ impl NamedStruct {
|
||||
}
|
||||
if !#is_empty {
|
||||
#(#tag_writers);*
|
||||
if let Some(qname) = &qname {
|
||||
writer.write_event(Event::End(BytesEnd::from(qname.to_owned())))?;
|
||||
if let Some(tagname) = tagname.as_ref() {
|
||||
writer.write_event(Event::End(BytesEnd::new(tagname)))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -148,6 +148,8 @@ impl NamedStruct {
|
||||
}
|
||||
}
|
||||
|
||||
let mut string = String::new();
|
||||
|
||||
if !empty {
|
||||
loop {
|
||||
let event = reader.read_event_into(&mut buf)?;
|
||||
@@ -167,12 +169,23 @@ impl NamedStruct {
|
||||
}
|
||||
}
|
||||
Event::Text(bytes_text) => {
|
||||
let text = bytes_text.unescape()?;
|
||||
#(#text_field_branches)*
|
||||
let text = bytes_text.decode()?;
|
||||
string.push_str(&text);
|
||||
}
|
||||
Event::CData(cdata) => {
|
||||
let text = String::from_utf8(cdata.to_vec())?;
|
||||
#(#text_field_branches)*
|
||||
string.push_str(&text);
|
||||
}
|
||||
Event::GeneralRef(gref) => {
|
||||
if let Some(char) = gref.resolve_char_ref()? {
|
||||
string.push(char);
|
||||
} else if let Some(text) =
|
||||
quick_xml::escape::resolve_xml_entity(&gref.xml_content()?)
|
||||
{
|
||||
string.push_str(text);
|
||||
} else {
|
||||
return Err(XmlError::UnsupportedEvent("invalid XML ref"));
|
||||
}
|
||||
}
|
||||
Event::Decl(_) => { /* <?xml ... ?> ignore this */ }
|
||||
Event::Comment(_) => { /* ignore */ }
|
||||
@@ -185,6 +198,9 @@ impl NamedStruct {
|
||||
}
|
||||
}
|
||||
|
||||
let text = string;
|
||||
#(#text_field_branches)*
|
||||
|
||||
Ok(Self {
|
||||
#(#builder_field_builds),*
|
||||
})
|
||||
|
||||
@@ -8,6 +8,8 @@ pub enum XmlError {
|
||||
#[error(transparent)]
|
||||
QuickXmlError(#[from] quick_xml::Error),
|
||||
#[error(transparent)]
|
||||
QuickXmlEncodingError(#[from] quick_xml::encoding::EncodingError),
|
||||
#[error(transparent)]
|
||||
QuickXmlAttrError(#[from] quick_xml::events::attributes::AttrError),
|
||||
#[error(transparent)]
|
||||
FromUtf8Error(#[from] FromUtf8Error),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::XmlRootTag;
|
||||
use quick_xml::{
|
||||
events::{BytesStart, Event, attributes::Attribute},
|
||||
name::{Namespace, QName},
|
||||
name::Namespace,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
pub use xml_derive::XmlSerialize;
|
||||
@@ -76,9 +76,8 @@ impl XmlSerialize for () {
|
||||
});
|
||||
let has_prefix = prefix.is_some();
|
||||
let tagname = tag.map(|tag| [&prefix.unwrap_or_default(), tag].concat());
|
||||
let qname = tagname.as_ref().map(|tagname| QName(tagname.as_bytes()));
|
||||
if let Some(qname) = &qname {
|
||||
let mut bytes_start = BytesStart::from(qname.to_owned());
|
||||
if let Some(tagname) = tagname.as_ref() {
|
||||
let mut bytes_start = BytesStart::new(tagname);
|
||||
if !has_prefix && let Some(ns) = &ns {
|
||||
bytes_start.push_attribute((b"xmlns".as_ref(), ns.as_ref()));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{XmlDeserialize, XmlError, XmlSerialize};
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
|
||||
use quick_xml::name::{Namespace, QName};
|
||||
use quick_xml::name::Namespace;
|
||||
use std::collections::HashMap;
|
||||
use std::num::{ParseFloatError, ParseIntError};
|
||||
use std::{convert::Infallible, io::BufRead};
|
||||
@@ -77,20 +77,23 @@ impl<T: ValueDeserialize> XmlDeserialize for T {
|
||||
loop {
|
||||
match reader.read_event_into(&mut buf)? {
|
||||
Event::Text(bytes_text) => {
|
||||
let text = bytes_text.unescape()?;
|
||||
if !string.is_empty() {
|
||||
// Content already written
|
||||
return Err(XmlError::UnsupportedEvent("content already written"));
|
||||
}
|
||||
string = text.to_string();
|
||||
let text = bytes_text.decode()?;
|
||||
string.push_str(&text);
|
||||
}
|
||||
Event::CData(cdata) => {
|
||||
let text = String::from_utf8(cdata.to_vec())?;
|
||||
if !string.is_empty() {
|
||||
// Content already written
|
||||
return Err(XmlError::UnsupportedEvent("content already written"));
|
||||
string.push_str(&text);
|
||||
}
|
||||
Event::GeneralRef(gref) => {
|
||||
if let Some(char) = gref.resolve_char_ref()? {
|
||||
string.push(char);
|
||||
} else if let Some(text) =
|
||||
quick_xml::escape::resolve_xml_entity(&gref.xml_content()?)
|
||||
{
|
||||
string.push_str(text);
|
||||
} else {
|
||||
return Err(XmlError::UnsupportedEvent("invalid XML ref"));
|
||||
}
|
||||
string = text;
|
||||
}
|
||||
Event::End(_) => break,
|
||||
Event::Eof => return Err(XmlError::Eof),
|
||||
@@ -123,17 +126,16 @@ impl<T: ValueSerialize> XmlSerialize for T {
|
||||
});
|
||||
let has_prefix = prefix.is_some();
|
||||
let tagname = tag.map(|tag| [&prefix.unwrap_or_default(), tag].concat());
|
||||
let qname = tagname.as_ref().map(|tagname| QName(tagname.as_bytes()));
|
||||
if let Some(qname) = &qname {
|
||||
let mut bytes_start = BytesStart::from(qname.to_owned());
|
||||
if let Some(tagname) = tagname.as_ref() {
|
||||
let mut bytes_start = BytesStart::new(tagname);
|
||||
if !has_prefix && let Some(ns) = &ns {
|
||||
bytes_start.push_attribute((b"xmlns".as_ref(), ns.as_ref()));
|
||||
}
|
||||
writer.write_event(Event::Start(bytes_start))?;
|
||||
}
|
||||
writer.write_event(Event::Text(BytesText::new(&self.serialize())))?;
|
||||
if let Some(qname) = &qname {
|
||||
writer.write_event(Event::End(BytesEnd::from(qname.to_owned())))?;
|
||||
if let Some(tagname) = tagname {
|
||||
writer.write_event(Event::End(BytesEnd::new(tagname)))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -275,7 +275,7 @@ fn test_xml_cdata() {
|
||||
<document>
|
||||
<![CDATA[some text]]>
|
||||
<href><![CDATA[some stuff]]></href>
|
||||
<okay>></okay>
|
||||
<okay>nice>text</okay>
|
||||
</document>
|
||||
"#,
|
||||
)
|
||||
@@ -285,11 +285,25 @@ fn test_xml_cdata() {
|
||||
Document {
|
||||
hello: "some text".to_owned(),
|
||||
href: "some stuff".to_owned(),
|
||||
okay: ">".to_owned()
|
||||
okay: "nice>text".to_owned()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quickxml_bytesref() {
|
||||
let gt = quick_xml::events::BytesRef::new("gt");
|
||||
assert!(!gt.is_char_ref());
|
||||
let result = if !gt.is_char_ref() {
|
||||
quick_xml::escape::resolve_xml_entity(>.xml_content().unwrap())
|
||||
.unwrap()
|
||||
.to_string()
|
||||
} else {
|
||||
gt.xml_content().unwrap().to_string()
|
||||
};
|
||||
assert_eq!(result, ">");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_xml_decl() {
|
||||
#[derive(Debug, XmlDeserialize, XmlRootTag, PartialEq)]
|
||||
@@ -307,14 +321,14 @@ fn test_struct_xml_decl() {
|
||||
let doc = Document::parse_str(
|
||||
r#"
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<document><child>Hello!</child></document>"#,
|
||||
<document><child>Hello!&</child></document>"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
doc,
|
||||
Document {
|
||||
child: Child {
|
||||
text: "Hello!".to_owned()
|
||||
text: "Hello!&".to_owned()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
a CalDAV/CardDAV server
|
||||
|
||||
!!! warning
|
||||
RustiCal is under **active development**!
|
||||
While I've been successfully using RustiCal productively for a few weeks now,
|
||||
you'd still be one of the first testers so expect bugs and rough edges.
|
||||
If you still want to play around with it in its current state, absolutely feel free to do so and to open up an issue if something is not working. :)
|
||||
RustiCal is under **active development**!
|
||||
While I've been successfully using RustiCal productively for some months now and there seems to be a growing user base,
|
||||
you'd still be one of the first testers so expect bugs and rough edges.
|
||||
If you still want to use it in its current state, absolutely feel free to do so and to open up an issue if something is not working. :)
|
||||
|
||||
[Installation](installation/index.md){ .md-button }
|
||||
|
||||
@@ -14,6 +14,7 @@ a CalDAV/CardDAV server
|
||||
|
||||
- easy to backup, everything saved in one SQLite database
|
||||
- also export feature in the frontend
|
||||
- Import your existing calendars in the frontend
|
||||
- **[WebDAV Push](https://github.com/bitfireAT/webdav-push/)** support, so near-instant synchronisation to DAVx5
|
||||
- lightweight (the container image contains only one binary)
|
||||
- adequately fast (I'd love to say blazingly fast™ :fire: but I don't have any benchmarks)
|
||||
|
||||
@@ -40,10 +40,9 @@ pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
|
||||
dav_push_enabled: bool,
|
||||
session_cookie_samesite_strict: bool,
|
||||
) -> Router<()> {
|
||||
let combined_cal_store = Arc::new(CombinedCalendarStore::new(
|
||||
cal_store.clone(),
|
||||
ContactBirthdayStore::new(addr_store.clone()).into(),
|
||||
));
|
||||
let birthday_store = Arc::new(ContactBirthdayStore::new(addr_store.clone()));
|
||||
let combined_cal_store =
|
||||
Arc::new(CombinedCalendarStore::new(cal_store.clone()).with_store(birthday_store));
|
||||
|
||||
let mut router = Router::new()
|
||||
.merge(caldav_router(
|
||||
|
||||
Reference in New Issue
Block a user