Take the pain out of keeping all your calendars together

feat: add ical calendar merging

A key function of calpoll is taking multiple .ics calendars and merging
them together. This adds basic ical merging as follows:

- "Calendars" are in a database
- Calendars have multiple "subscriptions" (ical subscription URLs)
- When getting events from a calendar, we fetch all subscriptions, parse
them and add everything to one big ical file
- We expose this over http so this can itself be used as an ical
subscription URL

The reason we do it like this rather than, say, having an endpoint to
/merge/ calendars and be done with it is we want this to be an
application which you can share URLs out of even if you don't want to
make the subscription URLs of each underlying calendar public (e.g.
Google Calendar private .ics subscription links). We would also like the
ability to add support for more calendar sources in the future -
particularly, say, I need support for WebDav calendars for Stalwart.

---

You can test this change by using the merge-holidays-moon-phases.sql
queries in test-data/ to set up a test calendar like so:

$ cat test-data/merge-holidays-moon-phases.sql | psql $DATABASE_URL
$ curl http://127.0.0.1:3000/calendar/32da8949-b21c-469d-a2bc-f5763af186c6
BEGIN:VCALENDAR
VERSION:2.0
PRODID:ICALENDAR-RS
CALSCALE:GREGORIAN
BEGIN:VEVENT
CLASS:PUBLIC
CREATED:20240522T091304Z
DESCRIPTION:Public holiday
[...]

$ curl http://127.0.0.1:3000/calendar/00069420-74d2-4ba8-85d6-4fc679810c02
BEGIN:VCALENDAR
VERSION:2.0
PRODID:ICALENDAR-RS
CALSCALE:GREGORIAN
END:VCALENDAR

$ curl http://127.0.0.1:3000/calendar/non-uuid
Invalid URL: Cannot parse `id` with value `non-uuid`: UUID parsing
failed: invalid character: expected an optional prefix of `urn:uuid:`
followed by [0-9a-fA-F-], found `n` at 1

---

For these tests to work, you'll need to have $DATABASE_URL set to your
database, and you'll have to be running the code. The easiest way to do
that is with devenv and the nilla shell provided here...

$ devenv up
[...wait for TUI showing postgres running...]

[...in a separate terminal...]
$ nilla shell
nilla$ sqlx database reset
nilla$ cat test-data/merge-holidays-moon-phases.sql | psql $DATABASE_URL
nilla$ cargo leptos watch

a.starrysky.fyi 7c4f8b3f 9b78d4f8

verified
+1
.gitignore
··· 5 5 .sass-cache 6 6 /debug 7 7 /target 8 + /test-data/private-*
+672 -4
Cargo.lock
··· 18 18 checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 19 19 20 20 [[package]] 21 + name = "android_system_properties" 22 + version = "0.1.5" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 25 + dependencies = [ 26 + "libc", 27 + ] 28 + 29 + [[package]] 21 30 name = "any_spawner" 22 31 version = "0.3.0" 23 32 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 228 237 name = "calpoll" 229 238 version = "0.1.0" 230 239 dependencies = [ 240 + "anyhow", 231 241 "axum", 232 242 "console_error_panic_hook", 243 + "icalendar", 233 244 "leptos", 234 245 "leptos_axum", 235 246 "leptos_meta", 236 247 "leptos_router", 248 + "reqwest", 237 249 "sqlx", 238 250 "tokio", 251 + "uuid", 239 252 "wasm-bindgen", 240 253 ] 241 254 ··· 262 275 checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 263 276 264 277 [[package]] 278 + name = "cfg_aliases" 279 + version = "0.2.1" 280 + source = "registry+https://github.com/rust-lang/crates.io-index" 281 + checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 282 + 283 + [[package]] 284 + name = "chrono" 285 + version = "0.4.42" 286 + source = "registry+https://github.com/rust-lang/crates.io-index" 287 + checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" 288 + dependencies = [ 289 + "iana-time-zone", 290 + "js-sys", 291 + "num-traits", 292 + "wasm-bindgen", 293 + "windows-link 0.2.1", 294 + ] 295 + 296 + [[package]] 297 + name = "chrono-tz" 298 + version = "0.10.4" 299 + source = "registry+https://github.com/rust-lang/crates.io-index" 300 + checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" 301 + dependencies = [ 302 + "chrono", 303 + "phf", 304 + ] 305 + 306 + [[package]] 265 307 name = "codee" 266 308 version = "0.3.3" 267 309 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 463 505 ] 464 506 465 507 [[package]] 508 + name = "deranged" 509 + version = "0.5.4" 510 + source = "registry+https://github.com/rust-lang/crates.io-index" 511 + checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" 512 + dependencies = [ 513 + "powerfmt", 514 + ] 515 + 516 + [[package]] 466 517 name = "derive-where" 467 518 version = "1.6.0" 468 519 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 767 818 checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 768 819 dependencies = [ 769 820 "cfg-if", 821 + "js-sys", 770 822 "libc", 771 823 "wasi", 824 + "wasm-bindgen", 772 825 ] 773 826 774 827 [[package]] ··· 824 877 version = "1.3.0" 825 878 source = "registry+https://github.com/rust-lang/crates.io-index" 826 879 checksum = "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f" 880 + 881 + [[package]] 882 + name = "h2" 883 + version = "0.4.12" 884 + source = "registry+https://github.com/rust-lang/crates.io-index" 885 + checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" 886 + dependencies = [ 887 + "atomic-waker", 888 + "bytes", 889 + "fnv", 890 + "futures-core", 891 + "futures-sink", 892 + "http", 893 + "indexmap", 894 + "slab", 895 + "tokio", 896 + "tokio-util", 897 + "tracing", 898 + ] 827 899 828 900 [[package]] 829 901 name = "hashbrown" ··· 989 1061 "bytes", 990 1062 "futures-channel", 991 1063 "futures-core", 1064 + "h2", 992 1065 "http", 993 1066 "http-body", 994 1067 "httparse", ··· 998 1071 "pin-utils", 999 1072 "smallvec", 1000 1073 "tokio", 1074 + "want", 1075 + ] 1076 + 1077 + [[package]] 1078 + name = "hyper-rustls" 1079 + version = "0.27.7" 1080 + source = "registry+https://github.com/rust-lang/crates.io-index" 1081 + checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 1082 + dependencies = [ 1083 + "http", 1084 + "hyper", 1085 + "hyper-util", 1086 + "rustls", 1087 + "rustls-pki-types", 1088 + "tokio", 1089 + "tokio-rustls", 1090 + "tower-service", 1091 + "webpki-roots 1.0.3", 1092 + ] 1093 + 1094 + [[package]] 1095 + name = "hyper-tls" 1096 + version = "0.6.0" 1097 + source = "registry+https://github.com/rust-lang/crates.io-index" 1098 + checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 1099 + dependencies = [ 1100 + "bytes", 1101 + "http-body-util", 1102 + "hyper", 1103 + "hyper-util", 1104 + "native-tls", 1105 + "tokio", 1106 + "tokio-native-tls", 1107 + "tower-service", 1001 1108 ] 1002 1109 1003 1110 [[package]] ··· 1006 1113 source = "registry+https://github.com/rust-lang/crates.io-index" 1007 1114 checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" 1008 1115 dependencies = [ 1116 + "base64", 1009 1117 "bytes", 1118 + "futures-channel", 1010 1119 "futures-core", 1120 + "futures-util", 1011 1121 "http", 1012 1122 "http-body", 1013 1123 "hyper", 1124 + "ipnet", 1125 + "libc", 1126 + "percent-encoding", 1014 1127 "pin-project-lite", 1128 + "socket2", 1129 + "system-configuration", 1015 1130 "tokio", 1016 1131 "tower-service", 1132 + "tracing", 1133 + "windows-registry", 1134 + ] 1135 + 1136 + [[package]] 1137 + name = "iana-time-zone" 1138 + version = "0.1.64" 1139 + source = "registry+https://github.com/rust-lang/crates.io-index" 1140 + checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" 1141 + dependencies = [ 1142 + "android_system_properties", 1143 + "core-foundation-sys", 1144 + "iana-time-zone-haiku", 1145 + "js-sys", 1146 + "log", 1147 + "wasm-bindgen", 1148 + "windows-core", 1149 + ] 1150 + 1151 + [[package]] 1152 + name = "iana-time-zone-haiku" 1153 + version = "0.1.2" 1154 + source = "registry+https://github.com/rust-lang/crates.io-index" 1155 + checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1156 + dependencies = [ 1157 + "cc", 1158 + ] 1159 + 1160 + [[package]] 1161 + name = "icalendar" 1162 + version = "0.17.5" 1163 + source = "registry+https://github.com/rust-lang/crates.io-index" 1164 + checksum = "f25bc68d1c3113be52919708c870cabe55ba0646b9dade87913fe565aa956a3b" 1165 + dependencies = [ 1166 + "chrono", 1167 + "chrono-tz", 1168 + "iso8601", 1169 + "nom", 1170 + "nom-language", 1171 + "serde", 1172 + "time", 1173 + "uuid", 1017 1174 ] 1018 1175 1019 1176 [[package]] ··· 1149 1306 ] 1150 1307 1151 1308 [[package]] 1309 + name = "ipnet" 1310 + version = "2.11.0" 1311 + source = "registry+https://github.com/rust-lang/crates.io-index" 1312 + checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1313 + 1314 + [[package]] 1315 + name = "iri-string" 1316 + version = "0.7.8" 1317 + source = "registry+https://github.com/rust-lang/crates.io-index" 1318 + checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" 1319 + dependencies = [ 1320 + "memchr", 1321 + "serde", 1322 + ] 1323 + 1324 + [[package]] 1325 + name = "iso8601" 1326 + version = "0.6.3" 1327 + source = "registry+https://github.com/rust-lang/crates.io-index" 1328 + checksum = "e1082f0c48f143442a1ac6122f67e360ceee130b967af4d50996e5154a45df46" 1329 + dependencies = [ 1330 + "nom", 1331 + ] 1332 + 1333 + [[package]] 1152 1334 name = "itertools" 1153 1335 version = "0.14.0" 1154 1336 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1471 1653 checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 1472 1654 1473 1655 [[package]] 1656 + name = "lru-slab" 1657 + version = "0.1.2" 1658 + source = "registry+https://github.com/rust-lang/crates.io-index" 1659 + checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 1660 + 1661 + [[package]] 1474 1662 name = "manyhow" 1475 1663 version = "0.11.4" 1476 1664 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1583 1771 checksum = "60993920e071b0c9b66f14e2b32740a4e27ffc82854dcd72035887f336a09a28" 1584 1772 1585 1773 [[package]] 1774 + name = "nom" 1775 + version = "8.0.0" 1776 + source = "registry+https://github.com/rust-lang/crates.io-index" 1777 + checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" 1778 + dependencies = [ 1779 + "memchr", 1780 + ] 1781 + 1782 + [[package]] 1783 + name = "nom-language" 1784 + version = "0.1.0" 1785 + source = "registry+https://github.com/rust-lang/crates.io-index" 1786 + checksum = "2de2bc5b451bfedaef92c90b8939a8fff5770bdcc1fafd6239d086aab8fa6b29" 1787 + dependencies = [ 1788 + "nom", 1789 + ] 1790 + 1791 + [[package]] 1586 1792 name = "num-bigint-dig" 1587 1793 version = "0.8.4" 1588 1794 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1598 1804 "smallvec", 1599 1805 "zeroize", 1600 1806 ] 1807 + 1808 + [[package]] 1809 + name = "num-conv" 1810 + version = "0.1.0" 1811 + source = "registry+https://github.com/rust-lang/crates.io-index" 1812 + checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1601 1813 1602 1814 [[package]] 1603 1815 name = "num-integer" ··· 1731 1943 "libc", 1732 1944 "redox_syscall", 1733 1945 "smallvec", 1734 - "windows-link", 1946 + "windows-link 0.2.1", 1735 1947 ] 1736 1948 1737 1949 [[package]] ··· 1760 1972 version = "2.3.2" 1761 1973 source = "registry+https://github.com/rust-lang/crates.io-index" 1762 1974 checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 1975 + 1976 + [[package]] 1977 + name = "phf" 1978 + version = "0.12.1" 1979 + source = "registry+https://github.com/rust-lang/crates.io-index" 1980 + checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" 1981 + dependencies = [ 1982 + "phf_shared", 1983 + ] 1984 + 1985 + [[package]] 1986 + name = "phf_shared" 1987 + version = "0.12.1" 1988 + source = "registry+https://github.com/rust-lang/crates.io-index" 1989 + checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" 1990 + dependencies = [ 1991 + "siphasher", 1992 + ] 1763 1993 1764 1994 [[package]] 1765 1995 name = "pin-project" ··· 1830 2060 ] 1831 2061 1832 2062 [[package]] 2063 + name = "powerfmt" 2064 + version = "0.2.0" 2065 + source = "registry+https://github.com/rust-lang/crates.io-index" 2066 + checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 2067 + 2068 + [[package]] 1833 2069 name = "ppv-lite86" 1834 2070 version = "0.2.21" 1835 2071 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1904 2140 ] 1905 2141 1906 2142 [[package]] 2143 + name = "quinn" 2144 + version = "0.11.9" 2145 + source = "registry+https://github.com/rust-lang/crates.io-index" 2146 + checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" 2147 + dependencies = [ 2148 + "bytes", 2149 + "cfg_aliases", 2150 + "pin-project-lite", 2151 + "quinn-proto", 2152 + "quinn-udp", 2153 + "rustc-hash", 2154 + "rustls", 2155 + "socket2", 2156 + "thiserror 2.0.17", 2157 + "tokio", 2158 + "tracing", 2159 + "web-time", 2160 + ] 2161 + 2162 + [[package]] 2163 + name = "quinn-proto" 2164 + version = "0.11.13" 2165 + source = "registry+https://github.com/rust-lang/crates.io-index" 2166 + checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" 2167 + dependencies = [ 2168 + "bytes", 2169 + "getrandom 0.3.4", 2170 + "lru-slab", 2171 + "rand 0.9.2", 2172 + "ring", 2173 + "rustc-hash", 2174 + "rustls", 2175 + "rustls-pki-types", 2176 + "slab", 2177 + "thiserror 2.0.17", 2178 + "tinyvec", 2179 + "tracing", 2180 + "web-time", 2181 + ] 2182 + 2183 + [[package]] 2184 + name = "quinn-udp" 2185 + version = "0.5.14" 2186 + source = "registry+https://github.com/rust-lang/crates.io-index" 2187 + checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" 2188 + dependencies = [ 2189 + "cfg_aliases", 2190 + "libc", 2191 + "once_cell", 2192 + "socket2", 2193 + "tracing", 2194 + "windows-sys 0.60.2", 2195 + ] 2196 + 2197 + [[package]] 1907 2198 name = "quote" 1908 2199 version = "1.0.41" 1909 2200 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2091 2382 checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 2092 2383 2093 2384 [[package]] 2385 + name = "reqwest" 2386 + version = "0.12.24" 2387 + source = "registry+https://github.com/rust-lang/crates.io-index" 2388 + checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" 2389 + dependencies = [ 2390 + "base64", 2391 + "bytes", 2392 + "encoding_rs", 2393 + "futures-core", 2394 + "h2", 2395 + "http", 2396 + "http-body", 2397 + "http-body-util", 2398 + "hyper", 2399 + "hyper-rustls", 2400 + "hyper-tls", 2401 + "hyper-util", 2402 + "js-sys", 2403 + "log", 2404 + "mime", 2405 + "native-tls", 2406 + "percent-encoding", 2407 + "pin-project-lite", 2408 + "quinn", 2409 + "rustls", 2410 + "rustls-pki-types", 2411 + "serde", 2412 + "serde_json", 2413 + "serde_urlencoded", 2414 + "sync_wrapper", 2415 + "tokio", 2416 + "tokio-native-tls", 2417 + "tokio-rustls", 2418 + "tower", 2419 + "tower-http", 2420 + "tower-service", 2421 + "url", 2422 + "wasm-bindgen", 2423 + "wasm-bindgen-futures", 2424 + "web-sys", 2425 + "webpki-roots 1.0.3", 2426 + ] 2427 + 2428 + [[package]] 2429 + name = "ring" 2430 + version = "0.17.14" 2431 + source = "registry+https://github.com/rust-lang/crates.io-index" 2432 + checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 2433 + dependencies = [ 2434 + "cc", 2435 + "cfg-if", 2436 + "getrandom 0.2.16", 2437 + "libc", 2438 + "untrusted", 2439 + "windows-sys 0.52.0", 2440 + ] 2441 + 2442 + [[package]] 2094 2443 name = "rsa" 2095 2444 version = "0.9.8" 2096 2445 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2154 2503 ] 2155 2504 2156 2505 [[package]] 2506 + name = "rustls" 2507 + version = "0.23.34" 2508 + source = "registry+https://github.com/rust-lang/crates.io-index" 2509 + checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" 2510 + dependencies = [ 2511 + "once_cell", 2512 + "ring", 2513 + "rustls-pki-types", 2514 + "rustls-webpki", 2515 + "subtle", 2516 + "zeroize", 2517 + ] 2518 + 2519 + [[package]] 2520 + name = "rustls-pki-types" 2521 + version = "1.12.0" 2522 + source = "registry+https://github.com/rust-lang/crates.io-index" 2523 + checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" 2524 + dependencies = [ 2525 + "web-time", 2526 + "zeroize", 2527 + ] 2528 + 2529 + [[package]] 2530 + name = "rustls-webpki" 2531 + version = "0.103.7" 2532 + source = "registry+https://github.com/rust-lang/crates.io-index" 2533 + checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" 2534 + dependencies = [ 2535 + "ring", 2536 + "rustls-pki-types", 2537 + "untrusted", 2538 + ] 2539 + 2540 + [[package]] 2157 2541 name = "rustversion" 2158 2542 version = "1.0.22" 2159 2543 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2415 2799 "digest", 2416 2800 "rand_core 0.6.4", 2417 2801 ] 2802 + 2803 + [[package]] 2804 + name = "siphasher" 2805 + version = "1.0.1" 2806 + source = "registry+https://github.com/rust-lang/crates.io-index" 2807 + checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 2418 2808 2419 2809 [[package]] 2420 2810 name = "slab" ··· 2506 2896 "native-tls", 2507 2897 "once_cell", 2508 2898 "percent-encoding", 2899 + "rustls", 2509 2900 "serde", 2510 2901 "serde_json", 2511 2902 "sha2", ··· 2516 2907 "tracing", 2517 2908 "url", 2518 2909 "uuid", 2910 + "webpki-roots 0.26.11", 2519 2911 ] 2520 2912 2521 2913 [[package]] ··· 2713 3105 version = "1.0.2" 2714 3106 source = "registry+https://github.com/rust-lang/crates.io-index" 2715 3107 checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 3108 + dependencies = [ 3109 + "futures-core", 3110 + ] 2716 3111 2717 3112 [[package]] 2718 3113 name = "synstructure" ··· 2726 3121 ] 2727 3122 2728 3123 [[package]] 3124 + name = "system-configuration" 3125 + version = "0.6.1" 3126 + source = "registry+https://github.com/rust-lang/crates.io-index" 3127 + checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 3128 + dependencies = [ 3129 + "bitflags", 3130 + "core-foundation", 3131 + "system-configuration-sys", 3132 + ] 3133 + 3134 + [[package]] 3135 + name = "system-configuration-sys" 3136 + version = "0.6.0" 3137 + source = "registry+https://github.com/rust-lang/crates.io-index" 3138 + checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 3139 + dependencies = [ 3140 + "core-foundation-sys", 3141 + "libc", 3142 + ] 3143 + 3144 + [[package]] 2729 3145 name = "tachys" 2730 3146 version = "0.2.10" 2731 3147 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2822 3238 ] 2823 3239 2824 3240 [[package]] 3241 + name = "time" 3242 + version = "0.3.44" 3243 + source = "registry+https://github.com/rust-lang/crates.io-index" 3244 + checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" 3245 + dependencies = [ 3246 + "deranged", 3247 + "num-conv", 3248 + "powerfmt", 3249 + "serde", 3250 + "time-core", 3251 + ] 3252 + 3253 + [[package]] 3254 + name = "time-core" 3255 + version = "0.1.6" 3256 + source = "registry+https://github.com/rust-lang/crates.io-index" 3257 + checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" 3258 + 3259 + [[package]] 2825 3260 name = "tinystr" 2826 3261 version = "0.8.1" 2827 3262 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2873 3308 ] 2874 3309 2875 3310 [[package]] 3311 + name = "tokio-native-tls" 3312 + version = "0.3.1" 3313 + source = "registry+https://github.com/rust-lang/crates.io-index" 3314 + checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 3315 + dependencies = [ 3316 + "native-tls", 3317 + "tokio", 3318 + ] 3319 + 3320 + [[package]] 3321 + name = "tokio-rustls" 3322 + version = "0.26.4" 3323 + source = "registry+https://github.com/rust-lang/crates.io-index" 3324 + checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" 3325 + dependencies = [ 3326 + "rustls", 3327 + "tokio", 3328 + ] 3329 + 3330 + [[package]] 2876 3331 name = "tokio-stream" 2877 3332 version = "0.1.17" 2878 3333 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2970 3425 "http-body-util", 2971 3426 "http-range-header", 2972 3427 "httpdate", 3428 + "iri-string", 2973 3429 "mime", 2974 3430 "mime_guess", 2975 3431 "percent-encoding", 2976 3432 "pin-project-lite", 2977 3433 "tokio", 2978 3434 "tokio-util", 3435 + "tower", 2979 3436 "tower-layer", 2980 3437 "tower-service", 2981 3438 "tracing", ··· 3024 3481 dependencies = [ 3025 3482 "once_cell", 3026 3483 ] 3484 + 3485 + [[package]] 3486 + name = "try-lock" 3487 + version = "0.2.5" 3488 + source = "registry+https://github.com/rust-lang/crates.io-index" 3489 + checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 3027 3490 3028 3491 [[package]] 3029 3492 name = "tungstenite" ··· 3114 3577 checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 3115 3578 3116 3579 [[package]] 3580 + name = "untrusted" 3581 + version = "0.9.0" 3582 + source = "registry+https://github.com/rust-lang/crates.io-index" 3583 + checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 3584 + 3585 + [[package]] 3117 3586 name = "url" 3118 3587 version = "2.5.7" 3119 3588 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3151 3620 dependencies = [ 3152 3621 "getrandom 0.3.4", 3153 3622 "js-sys", 3623 + "serde", 3154 3624 "wasm-bindgen", 3155 3625 ] 3156 3626 ··· 3174 3644 dependencies = [ 3175 3645 "same-file", 3176 3646 "winapi-util", 3647 + ] 3648 + 3649 + [[package]] 3650 + name = "want" 3651 + version = "0.3.1" 3652 + source = "registry+https://github.com/rust-lang/crates.io-index" 3653 + checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 3654 + dependencies = [ 3655 + "try-lock", 3177 3656 ] 3178 3657 3179 3658 [[package]] ··· 3317 3796 ] 3318 3797 3319 3798 [[package]] 3799 + name = "web-time" 3800 + version = "1.1.0" 3801 + source = "registry+https://github.com/rust-lang/crates.io-index" 3802 + checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 3803 + dependencies = [ 3804 + "js-sys", 3805 + "wasm-bindgen", 3806 + ] 3807 + 3808 + [[package]] 3809 + name = "webpki-roots" 3810 + version = "0.26.11" 3811 + source = "registry+https://github.com/rust-lang/crates.io-index" 3812 + checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" 3813 + dependencies = [ 3814 + "webpki-roots 1.0.3", 3815 + ] 3816 + 3817 + [[package]] 3818 + name = "webpki-roots" 3819 + version = "1.0.3" 3820 + source = "registry+https://github.com/rust-lang/crates.io-index" 3821 + checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" 3822 + dependencies = [ 3823 + "rustls-pki-types", 3824 + ] 3825 + 3826 + [[package]] 3320 3827 name = "whoami" 3321 3828 version = "1.6.1" 3322 3829 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3334 3841 dependencies = [ 3335 3842 "windows-sys 0.61.2", 3336 3843 ] 3844 + 3845 + [[package]] 3846 + name = "windows-core" 3847 + version = "0.62.2" 3848 + source = "registry+https://github.com/rust-lang/crates.io-index" 3849 + checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" 3850 + dependencies = [ 3851 + "windows-implement", 3852 + "windows-interface", 3853 + "windows-link 0.2.1", 3854 + "windows-result 0.4.1", 3855 + "windows-strings 0.5.1", 3856 + ] 3857 + 3858 + [[package]] 3859 + name = "windows-implement" 3860 + version = "0.60.2" 3861 + source = "registry+https://github.com/rust-lang/crates.io-index" 3862 + checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 3863 + dependencies = [ 3864 + "proc-macro2", 3865 + "quote", 3866 + "syn", 3867 + ] 3868 + 3869 + [[package]] 3870 + name = "windows-interface" 3871 + version = "0.59.3" 3872 + source = "registry+https://github.com/rust-lang/crates.io-index" 3873 + checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 3874 + dependencies = [ 3875 + "proc-macro2", 3876 + "quote", 3877 + "syn", 3878 + ] 3879 + 3880 + [[package]] 3881 + name = "windows-link" 3882 + version = "0.1.3" 3883 + source = "registry+https://github.com/rust-lang/crates.io-index" 3884 + checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 3337 3885 3338 3886 [[package]] 3339 3887 name = "windows-link" ··· 3342 3890 checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 3343 3891 3344 3892 [[package]] 3893 + name = "windows-registry" 3894 + version = "0.5.3" 3895 + source = "registry+https://github.com/rust-lang/crates.io-index" 3896 + checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" 3897 + dependencies = [ 3898 + "windows-link 0.1.3", 3899 + "windows-result 0.3.4", 3900 + "windows-strings 0.4.2", 3901 + ] 3902 + 3903 + [[package]] 3904 + name = "windows-result" 3905 + version = "0.3.4" 3906 + source = "registry+https://github.com/rust-lang/crates.io-index" 3907 + checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 3908 + dependencies = [ 3909 + "windows-link 0.1.3", 3910 + ] 3911 + 3912 + [[package]] 3913 + name = "windows-result" 3914 + version = "0.4.1" 3915 + source = "registry+https://github.com/rust-lang/crates.io-index" 3916 + checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 3917 + dependencies = [ 3918 + "windows-link 0.2.1", 3919 + ] 3920 + 3921 + [[package]] 3922 + name = "windows-strings" 3923 + version = "0.4.2" 3924 + source = "registry+https://github.com/rust-lang/crates.io-index" 3925 + checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 3926 + dependencies = [ 3927 + "windows-link 0.1.3", 3928 + ] 3929 + 3930 + [[package]] 3931 + name = "windows-strings" 3932 + version = "0.5.1" 3933 + source = "registry+https://github.com/rust-lang/crates.io-index" 3934 + checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 3935 + dependencies = [ 3936 + "windows-link 0.2.1", 3937 + ] 3938 + 3939 + [[package]] 3345 3940 name = "windows-sys" 3346 3941 version = "0.48.0" 3347 3942 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3352 3947 3353 3948 [[package]] 3354 3949 name = "windows-sys" 3950 + version = "0.52.0" 3951 + source = "registry+https://github.com/rust-lang/crates.io-index" 3952 + checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 3953 + dependencies = [ 3954 + "windows-targets 0.52.6", 3955 + ] 3956 + 3957 + [[package]] 3958 + name = "windows-sys" 3355 3959 version = "0.60.2" 3356 3960 source = "registry+https://github.com/rust-lang/crates.io-index" 3357 3961 checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" ··· 3365 3969 source = "registry+https://github.com/rust-lang/crates.io-index" 3366 3970 checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 3367 3971 dependencies = [ 3368 - "windows-link", 3972 + "windows-link 0.2.1", 3369 3973 ] 3370 3974 3371 3975 [[package]] ··· 3385 3989 3386 3990 [[package]] 3387 3991 name = "windows-targets" 3992 + version = "0.52.6" 3993 + source = "registry+https://github.com/rust-lang/crates.io-index" 3994 + checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 3995 + dependencies = [ 3996 + "windows_aarch64_gnullvm 0.52.6", 3997 + "windows_aarch64_msvc 0.52.6", 3998 + "windows_i686_gnu 0.52.6", 3999 + "windows_i686_gnullvm 0.52.6", 4000 + "windows_i686_msvc 0.52.6", 4001 + "windows_x86_64_gnu 0.52.6", 4002 + "windows_x86_64_gnullvm 0.52.6", 4003 + "windows_x86_64_msvc 0.52.6", 4004 + ] 4005 + 4006 + [[package]] 4007 + name = "windows-targets" 3388 4008 version = "0.53.5" 3389 4009 source = "registry+https://github.com/rust-lang/crates.io-index" 3390 4010 checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 3391 4011 dependencies = [ 3392 - "windows-link", 4012 + "windows-link 0.2.1", 3393 4013 "windows_aarch64_gnullvm 0.53.1", 3394 4014 "windows_aarch64_msvc 0.53.1", 3395 4015 "windows_i686_gnu 0.53.1", 3396 - "windows_i686_gnullvm", 4016 + "windows_i686_gnullvm 0.53.1", 3397 4017 "windows_i686_msvc 0.53.1", 3398 4018 "windows_x86_64_gnu 0.53.1", 3399 4019 "windows_x86_64_gnullvm 0.53.1", ··· 3405 4025 version = "0.48.5" 3406 4026 source = "registry+https://github.com/rust-lang/crates.io-index" 3407 4027 checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 4028 + 4029 + [[package]] 4030 + name = "windows_aarch64_gnullvm" 4031 + version = "0.52.6" 4032 + source = "registry+https://github.com/rust-lang/crates.io-index" 4033 + checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 3408 4034 3409 4035 [[package]] 3410 4036 name = "windows_aarch64_gnullvm" ··· 3420 4046 3421 4047 [[package]] 3422 4048 name = "windows_aarch64_msvc" 4049 + version = "0.52.6" 4050 + source = "registry+https://github.com/rust-lang/crates.io-index" 4051 + checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 4052 + 4053 + [[package]] 4054 + name = "windows_aarch64_msvc" 3423 4055 version = "0.53.1" 3424 4056 source = "registry+https://github.com/rust-lang/crates.io-index" 3425 4057 checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" ··· 3432 4064 3433 4065 [[package]] 3434 4066 name = "windows_i686_gnu" 4067 + version = "0.52.6" 4068 + source = "registry+https://github.com/rust-lang/crates.io-index" 4069 + checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 4070 + 4071 + [[package]] 4072 + name = "windows_i686_gnu" 3435 4073 version = "0.53.1" 3436 4074 source = "registry+https://github.com/rust-lang/crates.io-index" 3437 4075 checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" 4076 + 4077 + [[package]] 4078 + name = "windows_i686_gnullvm" 4079 + version = "0.52.6" 4080 + source = "registry+https://github.com/rust-lang/crates.io-index" 4081 + checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 3438 4082 3439 4083 [[package]] 3440 4084 name = "windows_i686_gnullvm" ··· 3450 4094 3451 4095 [[package]] 3452 4096 name = "windows_i686_msvc" 4097 + version = "0.52.6" 4098 + source = "registry+https://github.com/rust-lang/crates.io-index" 4099 + checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 4100 + 4101 + [[package]] 4102 + name = "windows_i686_msvc" 3453 4103 version = "0.53.1" 3454 4104 source = "registry+https://github.com/rust-lang/crates.io-index" 3455 4105 checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" ··· 3462 4112 3463 4113 [[package]] 3464 4114 name = "windows_x86_64_gnu" 4115 + version = "0.52.6" 4116 + source = "registry+https://github.com/rust-lang/crates.io-index" 4117 + checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 4118 + 4119 + [[package]] 4120 + name = "windows_x86_64_gnu" 3465 4121 version = "0.53.1" 3466 4122 source = "registry+https://github.com/rust-lang/crates.io-index" 3467 4123 checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" ··· 3474 4130 3475 4131 [[package]] 3476 4132 name = "windows_x86_64_gnullvm" 4133 + version = "0.52.6" 4134 + source = "registry+https://github.com/rust-lang/crates.io-index" 4135 + checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 4136 + 4137 + [[package]] 4138 + name = "windows_x86_64_gnullvm" 3477 4139 version = "0.53.1" 3478 4140 source = "registry+https://github.com/rust-lang/crates.io-index" 3479 4141 checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" ··· 3483 4145 version = "0.48.5" 3484 4146 source = "registry+https://github.com/rust-lang/crates.io-index" 3485 4147 checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 4148 + 4149 + [[package]] 4150 + name = "windows_x86_64_msvc" 4151 + version = "0.52.6" 4152 + source = "registry+https://github.com/rust-lang/crates.io-index" 4153 + checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 3486 4154 3487 4155 [[package]] 3488 4156 name = "windows_x86_64_msvc"
+10 -2
Cargo.toml
··· 15 15 leptos_meta = { version = "0.8.0" } 16 16 tokio = { version = "1", features = ["rt-multi-thread"], optional = true } 17 17 wasm-bindgen = { version = "=0.2.100", optional = true } 18 - sqlx = { version = "0.8.6", features = ["runtime-tokio", "tls-native-tls", "postgres", "uuid", "macros"] } 18 + sqlx = { version = "0.8.6", features = ["runtime-tokio", "tls-native-tls", "postgres", "uuid", "macros", "tls-rustls-ring-webpki"], optional = true } 19 + uuid = { version = "1.18.1", features = ["js", "serde", "v4"], optional = true } 20 + anyhow = "1.0.100" 21 + reqwest = { version = "0.12.24", features = ["rustls-tls-webpki-roots", "charset", "http2"], optional = true } 22 + icalendar = { version = "0.17.5", features = ["parser", "serde", "chrono-tz", "time",], optional = true } 19 23 20 24 [features] 21 25 hydrate = [ ··· 25 29 ] 26 30 ssr = [ 27 31 "dep:axum", 28 - "dep:tokio", 32 + "dep:icalendar", 29 33 "dep:leptos_axum", 34 + "dep:reqwest", 35 + "dep:sqlx", 36 + "dep:tokio", 37 + "dep:uuid", 30 38 "leptos/ssr", 31 39 "leptos_meta/ssr", 32 40 "leptos_router/ssr",
+26
migrations/20251026145212_add-calendars.sql
··· 1 + CREATE TABLE "subscriptions" ( 2 + "id" uuid PRIMARY KEY, 3 + "url" varchar NOT NULL 4 + ); 5 + 6 + CREATE TABLE "calendars" ( 7 + "id" uuid PRIMARY KEY, 8 + "user" uuid NOT NULL 9 + ); 10 + 11 + CREATE TABLE "users" ( 12 + "id" uuid PRIMARY KEY 13 + ); 14 + 15 + CREATE TABLE "calendars_subscriptions" ( 16 + "calendars_id" uuid, 17 + "subscriptions_id" uuid, 18 + PRIMARY KEY ("calendars_id", "subscriptions_id") 19 + ); 20 + 21 + ALTER TABLE "calendars_subscriptions" ADD FOREIGN KEY ("calendars_id") REFERENCES "calendars" ("id"); 22 + 23 + ALTER TABLE "calendars_subscriptions" ADD FOREIGN KEY ("subscriptions_id") REFERENCES "subscriptions" ("id"); 24 + 25 + 26 + ALTER TABLE "calendars" ADD CONSTRAINT "user_calendars" FOREIGN KEY ("user") REFERENCES "users" ("id");
+56 -2
src/calendar.rs
··· 1 + use std::ops::DerefMut; 2 + 1 3 use axum::extract::Path; 4 + use leptos::prelude::ServerFnError; 5 + use leptos::server; 6 + use tokio::task::JoinSet; 7 + use uuid::Uuid; 8 + 9 + use crate::STATE; 2 10 3 - pub(crate) async fn route(Path(_path): Path<String>) -> String { 4 - "hello world!".to_string() 11 + pub(crate) async fn route( 12 + Path(id): Path<Uuid> 13 + ) -> String { 14 + events_by_calendar(id).await.unwrap() 15 + } 16 + 17 + #[server] 18 + async fn events_by_calendar(id: Uuid) -> anyhow::Result<String, ServerFnError> { 19 + let state = STATE.get().unwrap(); 20 + 21 + println!("{:?}", id); 22 + 23 + let subscription_urls = sqlx::query!( 24 + "SELECT url FROM subscriptions 25 + JOIN calendars_subscriptions on subscriptions.id = calendars_subscriptions.subscriptions_id 26 + JOIN calendars on calendars.id = calendars_subscriptions.calendars_id 27 + WHERE calendars.id = $1", 28 + id 29 + ).fetch_all(state.sqlx_connection.lock().await.deref_mut()).await?; 30 + 31 + println!("{:#?}", subscription_urls); 32 + 33 + let mut requests = JoinSet::new(); 34 + 35 + for url in subscription_urls { 36 + requests.spawn((async || { 37 + let result = state.reqwest_client.get(url.url).send().await; 38 + 39 + let Ok(response) = result else { 40 + return None 41 + }; 42 + 43 + response.text().await.ok() 44 + })()); 45 + } 46 + 47 + let results = requests.join_all().await; 48 + let icals = results.iter().filter_map(|ical| ical.to_owned()); 49 + 50 + let calendars: Vec<icalendar::Calendar> = icals.map(|ical| ical.parse::<icalendar::Calendar>()).filter_map(|calendar| calendar.ok()).collect(); 51 + 52 + let mut merged = icalendar::Calendar::new(); 53 + 54 + for calendar in calendars { 55 + merged.extend(calendar.components); 56 + } 57 + 58 + Ok(merged.to_string()) 5 59 }
+28
src/main.rs
··· 1 + use std::sync::OnceLock; 2 + use sqlx::PgConnection; 3 + use tokio::sync::Mutex; 4 + 1 5 mod calendar; 6 + 7 + #[cfg(feature = "ssr")] 8 + #[derive(Debug)] 9 + pub struct State { 10 + sqlx_connection: Mutex<PgConnection>, 11 + reqwest_client: reqwest::Client, 12 + } 13 + 14 + #[cfg(feature = "ssr")] 15 + static STATE: OnceLock<State> = OnceLock::new(); 2 16 3 17 #[cfg(feature = "ssr")] 4 18 #[tokio::main] 5 19 async fn main() { 20 + use std::env; 21 + 6 22 use axum::{routing::get, Router}; 7 23 use leptos::logging::log; 8 24 use leptos::prelude::*; 9 25 use leptos_axum::{generate_route_list, LeptosRoutes}; 10 26 use calpoll::app::*; 27 + use sqlx::Connection; 28 + 29 + let connection = 30 + PgConnection::connect( 31 + env::var("DATABASE_URL") 32 + .expect("Please ensure you set your database URL in the $DATABASE_URL environment variable") 33 + .as_str()).await.unwrap(); 34 + 35 + STATE.set(State { 36 + sqlx_connection: Mutex::new(connection), 37 + reqwest_client: reqwest::Client::new(), 38 + }).unwrap(); 11 39 12 40 let conf = get_configuration(None).unwrap(); 13 41 let addr = conf.leptos_options.site_addr;
+9
test-data/merge-holidays-moon-phases.sql
··· 1 + INSERT INTO users ("id") VALUES ('11cd80d6-cf3d-4d93-9d47-4a383eaf1a10'); 2 + 3 + INSERT INTO calendars ("id", "user") VALUES ('32da8949-b21c-469d-a2bc-f5763af186c6', '11cd80d6-cf3d-4d93-9d47-4a383eaf1a10'); 4 + 5 + INSERT INTO subscriptions ("id", "url") VALUES ('797de795-0ae5-443c-91d1-1d68f9d44889', 'https://calendar.google.com/calendar/ical/en.uk%23holiday%40group.v.calendar.google.com/public/basic.ics'); 6 + INSERT INTO subscriptions ("id", "url") VALUES ('53d15eed-9db4-40ac-a5b1-b6df114847b2', 'https://calendar.google.com/calendar/ical/ht3jlfaac5lfd6263ulfh4tql8%40group.calendar.google.com/public/basic.ics'); 7 + 8 + INSERT INTO calendars_subscriptions ("calendars_id", "subscriptions_id") VALUES ('32da8949-b21c-469d-a2bc-f5763af186c6', '797de795-0ae5-443c-91d1-1d68f9d44889'); 9 + INSERT INTO calendars_subscriptions ("calendars_id", "subscriptions_id") VALUES ('32da8949-b21c-469d-a2bc-f5763af186c6', '53d15eed-9db4-40ac-a5b1-b6df114847b2');