diff --git a/apps/grades-sync/Cargo.lock b/apps/grades-sync/Cargo.lock new file mode 100644 index 000000000..5607890f8 --- /dev/null +++ b/apps/grades-sync/Cargo.lock @@ -0,0 +1,2355 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-write-file" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" +dependencies = [ + "nix", + "rand", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +dependencies = [ + "serde", +] + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e7cf40684ae96ade6232ed84582f40ce0a66efcd43a5117aef610534f8e0b8" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin 0.9.8", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "grades-sync" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "dotenv", + "env_logger", + "futures", + "itertools", + "log", + "regex", + "reqwest", + "serde", + "serde_json", + "sqlx", + "tokio", +] + +[[package]] +name = "h2" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "reqwest" +version = "0.11.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted", + "windows-sys 0.48.0", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +dependencies = [ + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "serde_json" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" +dependencies = [ + "ahash", + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "dotenvy", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", + "webpki-roots", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" +dependencies = [ + "atomic-write-file", + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" +dependencies = [ + "atoi", + "base64", + "bitflags 2.4.2", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" +dependencies = [ + "atoi", + "base64", + "bitflags 2.4.2", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", + "urlencoding", + "uuid", +] + +[[package]] +name = "stringprep" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +dependencies = [ + "finl_unicode", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" + +[[package]] +name = "web-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" + +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/apps/grades-sync/Cargo.toml b/apps/grades-sync/Cargo.toml new file mode 100644 index 000000000..baca1f6dc --- /dev/null +++ b/apps/grades-sync/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "grades-sync" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +sqlx = { version = "0.7", features = ["runtime-tokio", "tls-rustls", "uuid", "json", "migrate", "postgres"] } +tokio = { version = "1", features = ["full"] } +reqwest = { version = "0.11", features = ["json"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +anyhow = "1.0.79" +dotenv = "0.15.0" +log = { version = "0.4.20", features = [] } +env_logger = "0.11.1" +async-trait = "0.1.77" +regex = "1.10.3" +itertools = "0.12.1" +futures = "0.3.30" diff --git a/apps/grades-sync/README.md b/apps/grades-sync/README.md new file mode 100644 index 000000000..d96d63a34 --- /dev/null +++ b/apps/grades-sync/README.md @@ -0,0 +1,11 @@ +# Grades Sync + +Asynchronous grades synchronizer against HKDir's API. + +## Environment variables + +```env +DATABASE_URL=postgres://... +``` + +The `RUST_LOG` variable can be set to `info` or `debug` to enable logging. diff --git a/apps/grades-sync/migrations/20240209190449_schema.sql b/apps/grades-sync/migrations/20240209190449_schema.sql new file mode 100644 index 000000000..7e2ef2341 --- /dev/null +++ b/apps/grades-sync/migrations/20240209190449_schema.sql @@ -0,0 +1,25 @@ +START TRANSACTION; + +CREATE EXTENSION IF NOT EXISTS "fuzzystrmatch"; + +CREATE TABLE IF NOT EXISTS faculty +( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + ref_id TEXT NOT NULL, + name TEXT NOT NULL, + + CONSTRAINT faculty_ref_id_unique UNIQUE (ref_id) +); + +CREATE TABLE IF NOT EXISTS department +( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + ref_id TEXT NOT NULL, + faculty_id UUID NOT NULL, + name TEXT NOT NULL, + + CONSTRAINT department_fk_faculty_id FOREIGN KEY (faculty_id) REFERENCES faculty (id), + CONSTRAINT department_uq_ref_id UNIQUE (ref_id) +); + +COMMIT; diff --git a/apps/grades-sync/migrations/20240209195028_subject.sql b/apps/grades-sync/migrations/20240209195028_subject.sql new file mode 100644 index 000000000..e7f4a9792 --- /dev/null +++ b/apps/grades-sync/migrations/20240209195028_subject.sql @@ -0,0 +1,23 @@ +BEGIN TRANSACTION; + +CREATE TABLE IF NOT EXISTS subject +( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + ref_id TEXT NOT NULL, + name TEXT NOT NULL, + slug TEXT NOT NULL, + department_id UUID NOT NULL, + + instruction_language TEXT NOT NULL, + educational_level TEXT NOT NULL, + credits REAL NOT NULL, + + average_grade REAL NOT NULL DEFAULT 0.0, + total_students INT NOT NULL DEFAULT 0, + failed_students INT NOT NULL DEFAULT 0, + + CONSTRAINT subject_fk_department_id FOREIGN KEY (department_id) REFERENCES department (id), + CONSTRAINT subject_uq_ref_id UNIQUE (ref_id) +); + +COMMIT; diff --git a/apps/grades-sync/migrations/20240209212106_grades.sql b/apps/grades-sync/migrations/20240209212106_grades.sql new file mode 100644 index 000000000..67ab37e4d --- /dev/null +++ b/apps/grades-sync/migrations/20240209212106_grades.sql @@ -0,0 +1,25 @@ +START TRANSACTION; + +CREATE TYPE subject_grading_season AS ENUM ('WINTER', 'SPRING', 'SUMMER', 'AUTUMN'); + +CREATE TABLE IF NOT EXISTS subject_season_grade +( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + subject_id UUID NOT NULL, + season subject_grading_season NOT NULL, + year INTEGER NOT NULL, + + graded_a INTEGER, + graded_b INTEGER, + graded_c INTEGER, + graded_d INTEGER, + graded_e INTEGER, + graded_f INTEGER, + graded_pass INTEGER, + graded_fail INTEGER, + + CONSTRAINT subject_season_grade_fk_subject_id FOREIGN KEY (subject_id) REFERENCES subject (id), + CONSTRAINT subject_season_grade_unique UNIQUE (subject_id, season, year) +); + +COMMIT; diff --git a/apps/grades-sync/queries/departments.json b/apps/grades-sync/queries/departments.json new file mode 100644 index 000000000..e89052972 --- /dev/null +++ b/apps/grades-sync/queries/departments.json @@ -0,0 +1,39 @@ +{ + "tabell_id": 210, + "api_versjon": 1, + "statuslinje": "N", + "kodetekst": "J", + "desimal_separator": ".", + "variabler": [ + "*" + ], + "sortBy": [ + "Nivå" + ], + "filter": [ + { + "variabel": "Institusjonskode", + "selection": { + "filter": "item", + "values": [ + "1150" + ], + "exclude": [ + "" + ] + } + }, + { + "variabel": "Avdelingskode", + "selection": { + "filter": "all", + "values": [ + "*" + ], + "exclude": [ + "000000" + ] + } + } + ] +} \ No newline at end of file diff --git a/apps/grades-sync/queries/grades.json b/apps/grades-sync/queries/grades.json new file mode 100644 index 000000000..be027787d --- /dev/null +++ b/apps/grades-sync/queries/grades.json @@ -0,0 +1,55 @@ +{ + "tabell_id": 308, + "api_versjon": 1, + "statuslinje": "N", + "kodetekst": "J", + "desimal_separator": ".", + "variabler": [ + "*" + ], + "groupBy": [ + "Årstall", + "Semester", + "Karakter", + "Emnekode", + "Institusjonskode" + ], + "filter": [ + { + "variabel": "Institusjonskode", + "selection": { + "filter": "item", + "values": [ + "1150" + ], + "exclude": [ + "" + ] + } + }, + { + "variabel": "Emnekode", + "selection": { + "filter": "all", + "values": [ + "*" + ], + "exclude": [ + "" + ] + } + }, + { + "variabel": "Semester", + "selection": { + "filter": "all", + "values": [ + "*" + ], + "exclude": [ + "" + ] + } + } + ] +} \ No newline at end of file diff --git a/apps/grades-sync/queries/subjects.json b/apps/grades-sync/queries/subjects.json new file mode 100644 index 000000000..6124a9ff2 --- /dev/null +++ b/apps/grades-sync/queries/subjects.json @@ -0,0 +1,67 @@ +{ + "tabell_id": 208, + "api_versjon": 1, + "statuslinje": "N", + "kodetekst": "J", + "desimal_separator": ".", + "variabler": [ + "*" + ], + "sortBy": [ + "Årstall", + "Institusjonskode", + "Avdelingskode" + ], + "filter": [ + { + "variabel": "Institusjonskode", + "selection": { + "filter": "item", + "values": [ + "1150" + ], + "exclude": [ + "" + ] + } + }, + { + "variabel": "Nivåkode", + "selection": { + "filter": "item", + "values": [ + "HN", + "LN" + ], + "exclude": [ + "" + ] + } + }, + { + "variabel": "Avdelingskode", + "selection": { + "filter": "all", + "values": [ + "*" + ], + "exclude": [ + "000000" + ] + } + }, + { + "variabel": "Oppgave (ny fra h2012)", + "selection": { + "filter": "all", + "values": [ + "*" + ], + "exclude": [ + "1", + "2" + ] + } + } + ] +} \ No newline at end of file diff --git a/apps/grades-sync/src/department_repository.rs b/apps/grades-sync/src/department_repository.rs new file mode 100644 index 000000000..3f1f488cc --- /dev/null +++ b/apps/grades-sync/src/department_repository.rs @@ -0,0 +1,67 @@ +use crate::pg::Database; +use async_trait::async_trait; +use sqlx::types::Uuid; +use sqlx::FromRow; + +#[derive(Debug, FromRow)] +pub struct Department { + pub id: Uuid, + pub name: String, + pub ref_id: String, + pub faculty_id: Uuid, +} + +#[async_trait] +pub trait DepartmentRepository: Sync { + async fn create_department( + &self, + name: String, + ref_id: String, + faculty_id: Uuid, + ) -> Result; + async fn get_department_by_ref_id(&self, ref_id: String) -> Result; +} + +pub struct DepartmentRepositoryImpl<'a> { + db: &'a Database, +} + +impl<'a> DepartmentRepositoryImpl<'a> { + pub fn new(db: &'a Database) -> Self { + Self { db } + } +} + +#[async_trait] +impl<'a> DepartmentRepository for DepartmentRepositoryImpl<'a> { + async fn create_department( + &self, + name: String, + ref_id: String, + faculty_id: Uuid, + ) -> Result { + sqlx::query_as::<_, Department>( + r#" + INSERT INTO department (name, ref_id, faculty_id) VALUES ($1, $2, $3) + ON CONFLICT (ref_id) DO UPDATE SET name = $1, ref_id = $2, faculty_id = $3 + RETURNING *; + "#, + ) + .bind(name) + .bind(ref_id) + .bind(faculty_id) + .fetch_one(self.db) + .await + } + + async fn get_department_by_ref_id(&self, ref_id: String) -> Result { + sqlx::query_as::<_, Department>( + r#" + SELECT * FROM department WHERE ref_id = $1; + "#, + ) + .bind(ref_id) + .fetch_one(self.db) + .await + } +} diff --git a/apps/grades-sync/src/faculty_repository.rs b/apps/grades-sync/src/faculty_repository.rs new file mode 100644 index 000000000..0a8392f64 --- /dev/null +++ b/apps/grades-sync/src/faculty_repository.rs @@ -0,0 +1,43 @@ +use crate::pg::Database; +use async_trait::async_trait; +use sqlx::types::Uuid; +use sqlx::FromRow; + +#[derive(Debug, FromRow)] +pub struct Faculty { + pub id: Uuid, + pub name: String, + pub ref_id: String, +} + +#[async_trait] +pub trait FacultyRepository: Sync { + async fn create_faculty(&self, name: String, ref_id: String) -> Result; +} + +pub struct FacultyRepositoryImpl<'a> { + db: &'a Database, +} + +impl<'a> FacultyRepositoryImpl<'a> { + pub fn new(db: &'a Database) -> Self { + Self { db } + } +} + +#[async_trait] +impl<'a> FacultyRepository for FacultyRepositoryImpl<'a> { + async fn create_faculty(&self, name: String, ref_id: String) -> Result { + sqlx::query_as::<_, Faculty>( + r#" + INSERT INTO faculty (name, ref_id) VALUES ($1, $2) + ON CONFLICT (ref_id) DO UPDATE SET name = $1, ref_id = $2 + RETURNING *; + "#, + ) + .bind(name) + .bind(ref_id) + .fetch_one(self.db) + .await + } +} diff --git a/apps/grades-sync/src/grade_repository.rs b/apps/grades-sync/src/grade_repository.rs new file mode 100644 index 000000000..77eb4dae8 --- /dev/null +++ b/apps/grades-sync/src/grade_repository.rs @@ -0,0 +1,270 @@ +use crate::pg::Database; +use crate::subject_repository::Subject; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use sqlx::types::Uuid; +use sqlx::{Executor, FromRow}; + +#[derive(Debug, PartialEq, PartialOrd, sqlx::Type, Deserialize, Serialize, Copy, Clone)] +#[sqlx(type_name = "subject_grading_season", rename_all = "UPPERCASE")] +pub enum SubjectGradingSeason { + Winter, + Spring, + Summer, + Autumn, +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +pub enum SubjectGradingKey { + A, + B, + C, + D, + E, + F, + G, + H, +} + +impl SubjectGradingKey { + pub fn to_column_key_name(self) -> &'static str { + match self { + SubjectGradingKey::A => "graded_a", + SubjectGradingKey::B => "graded_b", + SubjectGradingKey::C => "graded_c", + SubjectGradingKey::D => "graded_d", + SubjectGradingKey::E => "graded_e", + SubjectGradingKey::F => "graded_f", + SubjectGradingKey::G => "graded_pass", + SubjectGradingKey::H => "graded_fail", + } + } + + pub fn to_multiplication_factor(self) -> f32 { + match self { + SubjectGradingKey::A => 5.0, + SubjectGradingKey::B => 4.0, + SubjectGradingKey::C => 3.0, + SubjectGradingKey::D => 2.0, + SubjectGradingKey::E => 1.0, + SubjectGradingKey::F => 0.0, + x => panic!("invalid grading key: {:?}", x), + } + } + + pub fn is_pass_or_fail_key(self) -> bool { + matches!(self, SubjectGradingKey::G | SubjectGradingKey::H) + } + + pub fn is_evaluated_as_failed(self) -> bool { + matches!(self, SubjectGradingKey::F | SubjectGradingKey::H) + } +} + +#[derive(Debug, FromRow)] +pub struct Grade { + pub id: Uuid, + pub subject_id: Uuid, + pub season: SubjectGradingSeason, + pub year: i32, + pub graded_a: Option, + pub graded_b: Option, + pub graded_c: Option, + pub graded_d: Option, + pub graded_e: Option, + pub graded_f: Option, + pub graded_pass: Option, + pub graded_fail: Option, +} + +impl Grade { + pub fn has_previously_been_graded(&self, key: SubjectGradingKey) -> bool { + match key { + SubjectGradingKey::A => self.graded_a.is_some(), + SubjectGradingKey::B => self.graded_b.is_some(), + SubjectGradingKey::C => self.graded_c.is_some(), + SubjectGradingKey::D => self.graded_d.is_some(), + SubjectGradingKey::E => self.graded_e.is_some(), + SubjectGradingKey::F => self.graded_f.is_some(), + SubjectGradingKey::G => self.graded_pass.is_some(), + SubjectGradingKey::H => self.graded_fail.is_some(), + } + } +} + +#[async_trait] +pub trait GradeRepository: Sync { + async fn create_grade( + &self, + subject_id: Uuid, + season: SubjectGradingSeason, + year: i32, + graded_a: Option, + graded_b: Option, + graded_c: Option, + graded_d: Option, + graded_f: Option, + graded_pass: Option, + graded_fail: Option, + ) -> Result; + async fn update_grade_record( + &self, + subject_id: Uuid, + season: SubjectGradingSeason, + year: i32, + key: SubjectGradingKey, + count: i32, + ) -> Result; + async fn find_grade_by_season( + &self, + subject_id: Uuid, + season: SubjectGradingSeason, + year: i32, + ) -> Result, sqlx::Error>; +} + +pub struct GradeRepositoryImpl<'a> { + db: &'a Database, +} + +impl<'a> GradeRepositoryImpl<'a> { + pub fn new(db: &'a Database) -> Self { + Self { db } + } +} + +#[async_trait] +impl<'a> GradeRepository for GradeRepositoryImpl<'a> { + async fn create_grade( + &self, + subject_id: Uuid, + season: SubjectGradingSeason, + year: i32, + graded_a: Option, + graded_b: Option, + graded_c: Option, + graded_d: Option, + graded_f: Option, + graded_pass: Option, + graded_fail: Option, + ) -> Result { + sqlx::query_as::<_, Grade>( + r#" + INSERT INTO subject_season_grade (subject_id, season, year, graded_a, graded_b, graded_c, graded_d, graded_f, graded_pass, graded_fail) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + RETURNING *; + "#, + ) + .bind(subject_id) + .bind(season) + .bind(year) + .bind(graded_a) + .bind(graded_b) + .bind(graded_c) + .bind(graded_d) + .bind(graded_f) + .bind(graded_pass) + .bind(graded_fail) + .fetch_one(self.db) + .await + } + + async fn update_grade_record( + &self, + subject_id: Uuid, + season: SubjectGradingSeason, + year: i32, + key: SubjectGradingKey, + count: i32, + ) -> Result { + // TODO: Refactor this logic out, it's only here because it's a transaction right now + let mut tx = self.db.begin().await?; + tx.execute("SET TRANSACTION ISOLATION LEVEL READ COMMITTED;").await?; + let grade = sqlx::query_as::<_, Grade>( + r#" + SELECT * FROM subject_season_grade WHERE subject_id = $1 AND season = $2 AND year = $3 + FOR UPDATE; + "#, + ) + .bind(subject_id) + .bind(season) + .bind(year) + .fetch_optional(&mut *tx) + .await?; + + let grade = match grade { + Some(grade) => grade, + None => { + sqlx::query_as::<_, Grade>( + r#" + INSERT INTO subject_season_grade (subject_id, season, year, graded_a, graded_b, graded_c, graded_d, graded_f, graded_pass, graded_fail) + VALUES ($1, $2, $3, NULL, NULL, NULL, NULL, NULL, NULL, NULL) + RETURNING *; + "# + ).bind(subject_id) + .bind(season) + .bind(year) + .fetch_one(&mut *tx) + .await? + } + }; + + // If there is already an existing grade record for the current combination of subject, + // season and year, then we skip. + if grade.has_previously_been_graded(key) { + return Ok(grade); + } + + let subject = + sqlx::query_as::<_, Subject>(r#"SELECT * FROM subject WHERE id = $1 FOR UPDATE;"#) + .bind(grade.subject_id) + .fetch_one(&mut *tx) + .await?; + + sqlx::query_as::<_, Subject>( + r#" + UPDATE subject SET total_students = $1, average_grade = $2, failed_students = $3 WHERE id = $4; + "# + ) + .bind(subject.get_next_total_students(count)) + .bind(subject.get_next_average(key, count)) + .bind(subject.get_next_failed_students(key, count)) + .bind(subject.id) + .fetch_optional(&mut *tx) + .await?; + + let grade = sqlx::query_as::<_, Grade>(&format!( + r#" + UPDATE subject_season_grade SET {} = $1 + WHERE id = $2 + RETURNING *; + "#, + key.to_column_key_name() + )) + .bind(count) + .bind(grade.id) + .fetch_one(&mut *tx) + .await; + + tx.commit().await?; + grade + } + + async fn find_grade_by_season( + &self, + subject_id: Uuid, + season: SubjectGradingSeason, + year: i32, + ) -> Result, sqlx::Error> { + sqlx::query_as::<_, Grade>( + r#" + SELECT * FROM subject_season_grade WHERE subject_id = $1 AND season = $2 AND year = $3; + "#, + ) + .bind(subject_id) + .bind(season) + .bind(year) + .fetch_optional(self.db) + .await + } +} diff --git a/apps/grades-sync/src/hkdir.rs b/apps/grades-sync/src/hkdir.rs new file mode 100644 index 000000000..9691c92cd --- /dev/null +++ b/apps/grades-sync/src/hkdir.rs @@ -0,0 +1,55 @@ +use crate::grade_repository::SubjectGradingSeason; +use crate::json::{HkdirDepartment, HkdirGrade, HkdirSubject}; +use reqwest::Client; +use serde_json::Value; + +const HKDIR_API_URL: &str = "https://dbh.hkdir.no/api/Tabeller/hentJSONTabellData"; + +pub async fn get_departments() -> reqwest::Result> { + let request_body = include_str!("../queries/departments.json") + .parse::() + .expect("invalid json"); + let client = Client::new(); + let response = client + .post(HKDIR_API_URL) + .json(&request_body) + .send() + .await?; + response.json::>().await +} + +pub async fn get_subjects() -> reqwest::Result> { + let request_body = include_str!("../queries/subjects.json") + .parse::() + .expect("invalid json"); + let client = Client::new(); + let response = client + .post(HKDIR_API_URL) + .json(&request_body) + .send() + .await?; + response.json::>().await +} + +pub async fn get_grades() -> reqwest::Result> { + let request_body = include_str!("../queries/grades.json") + .parse::() + .expect("invalid json"); + let client = Client::new(); + let response = client + .post(HKDIR_API_URL) + .json(&request_body) + .send() + .await?; + response.json::>().await +} + +pub fn map_season_index_to(index: &str) -> SubjectGradingSeason { + match index { + "0" => SubjectGradingSeason::Winter, + "1" => SubjectGradingSeason::Spring, + "2" => SubjectGradingSeason::Summer, + "3" => SubjectGradingSeason::Autumn, + x => panic!("attempted to parse invalid season {}", x), + } +} diff --git a/apps/grades-sync/src/job.rs b/apps/grades-sync/src/job.rs new file mode 100644 index 000000000..e700ecd67 --- /dev/null +++ b/apps/grades-sync/src/job.rs @@ -0,0 +1,203 @@ +use crate::faculty_repository::FacultyRepository; +use crate::hkdir::{get_departments, get_grades, get_subjects, map_season_index_to}; +use async_trait::async_trait; +use itertools::{Itertools}; +use std::cmp::min; + +use crate::department_repository::DepartmentRepository; +use crate::grade_repository::GradeRepository; +use crate::json::{HkdirDepartment, HkdirGrade, HkdirSubject}; +use crate::subject_repository::SubjectRepository; +use log::info; +use regex::Regex; + +#[async_trait] +pub trait JobService: Sync { + async fn perform_faculty_synchronization(&self) -> anyhow::Result<()>; + async fn perform_subject_synchronization(&self) -> anyhow::Result<()>; + async fn perform_grade_synchronization(&self) -> anyhow::Result<()>; + + async fn synchronize_single_faculty(&self, faculty: HkdirDepartment) -> anyhow::Result<()>; + async fn synchronize_single_subject(&self, subject: HkdirSubject) -> anyhow::Result<()>; + async fn synchronize_single_grade(&self, grade: HkdirGrade) -> anyhow::Result<()>; +} + +pub struct JobServiceImpl<'a> { + faculty_repository: &'a dyn FacultyRepository, + department_repository: &'a dyn DepartmentRepository, + subject_repository: &'a dyn SubjectRepository, + grade_repository: &'a dyn GradeRepository, +} + +impl<'a> JobServiceImpl<'a> { + pub fn new( + faculty_repository: &'a dyn FacultyRepository, + department_repository: &'a dyn DepartmentRepository, + subject_repository: &'a dyn SubjectRepository, + grade_repository: &'a dyn GradeRepository, + ) -> Self { + Self { + faculty_repository, + department_repository, + subject_repository, + grade_repository, + } + } +} + +const MAX_TASK_COUNT: usize = 100; + +#[async_trait] +impl<'a> JobService for JobServiceImpl<'a> { + async fn perform_faculty_synchronization(&self) -> anyhow::Result<()> { + info!("performing faculty synchronization"); + let departments = get_departments().await?; + let department_count = departments.len(); + let _chunks_count = min(department_count / MAX_TASK_COUNT, 1000); + info!( + "performing synchronization for {} departments", department_count + ); + + futures::future::join_all(departments + .into_iter() + .map(|department| { + async move { + self.synchronize_single_faculty(department).await.unwrap(); + } + })).await; + info!("synchronization complete"); + Ok(()) + } + + async fn perform_subject_synchronization(&self) -> anyhow::Result<()> { + info!("performing subject synchronization"); + let subjects = get_subjects().await?; + let subject_count = subjects.len(); + info!( + "performing synchronization for {} subjects", + subject_count + ); + + futures::future::join_all(subjects + .into_iter() + .map(|subject| { + async move { + self.synchronize_single_subject(subject).await.unwrap(); + } + })).await; + info!("synchronization complete"); + Ok(()) + } + + async fn perform_grade_synchronization(&self) -> anyhow::Result<()> { + info!("performing grade synchronization"); + let grades = get_grades().await?; + info!( + "performing synchronization for {} grades", + grades.len() + ); + let map = grades.into_iter() + .filter(|grade| grade + .total_candidates + .parse::() + .expect("invalid number for total candidates") > 0 + ) + .into_grouping_map_by(|grade| grade.subject_code.clone()) + // TODO: Maybe there is a way to avoid this collect? + .collect::>(); + futures::future::join_all(map.into_values().map(|grades| { + async move { + for grade in grades { + self.synchronize_single_grade(grade).await.unwrap(); + } + } + })).await; + info!("synchronization complete"); + Ok(()) + } + + async fn synchronize_single_faculty(&self, department: HkdirDepartment) -> anyhow::Result<()> { + // Create the faculty if it doesn't exist. The underlying query performs an on + // conflict update, which means that we do not need to check if the faculty + // exists before creating it. + let faculty = self + .faculty_repository + .create_faculty(department.faculty_code, department.faculty_name) + .await + .unwrap(); + + // Create the department. The same rules apply here as for the faculty. + self.department_repository + .create_department( + department.department_name, + department.department_code, + faculty.id, + ) + .await + .unwrap(); + Ok(()) + } + + async fn synchronize_single_subject(&self, subject: HkdirSubject) -> anyhow::Result<()> { + // Forge a slug from the subject name. A lot of the subject code fields from + // HKDir have a -1 or another number appended to them, which is not useful + // because we're using to seeing 'TDT4120' instead of 'TDT4120-1'. + let re = Regex::new("-[A-Za-z0-9]+$").unwrap(); + let slug = re.replace_all(&subject.subject_code, "").to_lowercase(); + // Find a reference to the department. + let department = self + .department_repository + .get_department_by_ref_id(subject.department_code) + .await + .unwrap(); + + // Create the subject if it doesn't exist. The underlying query performs an on + // conflict update, which means that we do not need to check if the subject + // exists before creating it. + self.subject_repository + .create_subject( + subject.subject_code, + subject.subject_name, + department.id, + slug, + subject.instruction_language, + subject.level_code, + subject.credits.parse().unwrap(), + 0f32, + 0, + 0, + ) + .await + .unwrap(); + Ok(()) + } + + async fn synchronize_single_grade(&self, grade: HkdirGrade) -> anyhow::Result<()> { + // First we find the matching subject, or else we return early. + let subject = match self + .subject_repository + .find_subject_by_ref_id(grade.subject_code) + .await? + { + Some(subject) => subject, + None => return Ok(()), + }; + let year = grade.year.parse::().unwrap(); + let season_key = map_season_index_to(grade.semester.as_str()); + // Then we find a matching grade record. + self.grade_repository + .update_grade_record( + subject.id, + season_key, + year, + grade.grade, + grade + .total_candidates + .parse::() + .expect("invalid number for total candidates"), + ) + .await?; + + Ok(()) + } +} diff --git a/apps/grades-sync/src/json.rs b/apps/grades-sync/src/json.rs new file mode 100644 index 000000000..67aa7ddb2 --- /dev/null +++ b/apps/grades-sync/src/json.rs @@ -0,0 +1,98 @@ +use crate::grade_repository::SubjectGradingKey; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct HkdirDepartment { + #[serde(rename = "Nivå")] + pub level: String, + #[serde(rename = "Nivå_tekst")] + pub level_text: String, + #[serde(rename = "Institusjonskode")] + pub institution_code: String, + #[serde(rename = "Institusjonsnavn")] + pub institution_name: String, + #[serde(rename = "Avdelingskode")] + pub department_code: String, + #[serde(rename = "Avdelingsnavn")] + pub department_name: String, + #[serde(rename = "Gyldig_fra")] + pub valid_from: Option, + #[serde(rename = "Gyldig_til")] + pub valid_to: Option, + #[serde(rename = "fagkode_avdeling")] + pub department_subject_code: Option, + #[serde(rename = "fagnavn_avdeling")] + pub department_subject_name: Option, + #[serde(rename = "Fakultetskode")] + pub faculty_code: String, + #[serde(rename = "Fakultetsnavn")] + pub faculty_name: String, + #[serde(rename = "Avdelingskode (3 siste siffer)")] + pub department_code_3: String, +} + +#[derive(Serialize, Deserialize)] +pub struct HkdirSubject { + #[serde(rename = "Institusjonskode")] + pub institution_code: String, + #[serde(rename = "Institusjonsnavn")] + pub institution_name: String, + #[serde(rename = "Avdelingskode")] + pub department_code: String, + #[serde(rename = "Avdelingsnavn")] + pub department_name: String, + #[serde(rename = "Avdelingskode_SSB")] + pub department_code_ssb: Option, + #[serde(rename = "Årstall")] + pub year: String, + #[serde(rename = "Semester")] + pub semester: String, + #[serde(rename = "Semesternavn")] + pub semester_name: String, + #[serde(rename = "Studieprogramkode")] + pub study_program_code: String, + #[serde(rename = "Studieprogramnavn")] + pub study_program_name: String, + #[serde(rename = "Emnekode")] + pub subject_code: String, + #[serde(rename = "Emnenavn")] + pub subject_name: String, + #[serde(rename = "Nivåkode")] + pub level_code: String, + #[serde(rename = "Nivånavn")] + pub level_name: String, + #[serde(rename = "Studiepoeng")] + pub credits: String, + #[serde(rename = "NUS-kode")] + pub nus_code: String, + #[serde(rename = "Status")] + pub status: String, + #[serde(rename = "Statusnavn")] + pub status_name: String, + #[serde(rename = "Underv.språk")] + pub instruction_language: String, + #[serde(rename = "Navn")] + pub name: String, + #[serde(rename = "Fagkode")] + pub subject_code_department: Option, + #[serde(rename = "Fagnavn")] + pub subject_name_department: Option, + #[serde(rename = "Oppgave (ny fra h2012)")] + pub task: Option, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct HkdirGrade { + #[serde(rename = "Årstall")] + pub year: String, + #[serde(rename = "Semester")] + pub semester: String, + #[serde(rename = "Semesternavn")] + pub semester_name: String, + #[serde(rename = "Karakter")] + pub grade: SubjectGradingKey, + #[serde(rename = "Emnekode")] + pub subject_code: String, + #[serde(rename = "Antall kandidater totalt")] + pub total_candidates: String, +} diff --git a/apps/grades-sync/src/main.rs b/apps/grades-sync/src/main.rs new file mode 100644 index 000000000..4c4342e0b --- /dev/null +++ b/apps/grades-sync/src/main.rs @@ -0,0 +1,41 @@ +use crate::department_repository::DepartmentRepositoryImpl; +use crate::faculty_repository::FacultyRepositoryImpl; +use crate::grade_repository::GradeRepositoryImpl; +use crate::job::{JobService, JobServiceImpl}; +use crate::subject_repository::SubjectRepositoryImpl; + +mod department_repository; +mod faculty_repository; +mod grade_repository; +mod hkdir; +mod job; +mod json; +mod pg; +mod subject_repository; + +fn bootstrap_environment() { + dotenv::dotenv().ok(); + env_logger::init() +} + +#[tokio::main] +async fn main() { + bootstrap_environment(); + let pool = pg::create_postgres_pool().await.unwrap(); + let faculty_repository = FacultyRepositoryImpl::new(&pool); + let department_repository = DepartmentRepositoryImpl::new(&pool); + let subject_repository = SubjectRepositoryImpl::new(&pool); + let grade_repository = GradeRepositoryImpl::new(&pool); + let job_service = JobServiceImpl::new( + &faculty_repository, + &department_repository, + &subject_repository, + &grade_repository, + ); + + // job_service.perform_faculty_synchronization().await.unwrap(); + job_service.perform_subject_synchronization().await.unwrap(); + job_service.perform_grade_synchronization().await.unwrap(); + + pool.close().await; +} diff --git a/apps/grades-sync/src/pg.rs b/apps/grades-sync/src/pg.rs new file mode 100644 index 000000000..799ac090f --- /dev/null +++ b/apps/grades-sync/src/pg.rs @@ -0,0 +1,26 @@ +use log::LevelFilter; +use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; +use sqlx::{ConnectOptions, Pool, Postgres}; +use std::time::Duration; + +pub type Database = Pool; + +pub async fn create_postgres_pool() -> Result, sqlx::Error> { + let database_url = + std::env::var("DATABASE_URL").expect("missing DATABASE_URL environment variable"); + let max_connections = std::env::var("MAX_CONNECTIONS") + .unwrap_or_else(|_| "10".to_string()) + .parse::() + .expect("MAX_CONNECTIONS must be a number"); + + let opts: PgConnectOptions = database_url.parse()?; + let opts = opts + .log_statements(LevelFilter::Debug) + .log_slow_statements(LevelFilter::Warn, Duration::from_secs(1)); + let pool = PgPoolOptions::new() + .max_connections(max_connections) + .acquire_timeout(Duration::from_secs(10 * 60)) + .connect_with(opts) + .await?; + Ok(pool) +} diff --git a/apps/grades-sync/src/subject_repository.rs b/apps/grades-sync/src/subject_repository.rs new file mode 100644 index 000000000..e267007d4 --- /dev/null +++ b/apps/grades-sync/src/subject_repository.rs @@ -0,0 +1,120 @@ +use crate::grade_repository::SubjectGradingKey; +use crate::pg::Database; +use async_trait::async_trait; +use sqlx::types::Uuid; +use sqlx::{Error, FromRow}; + +#[derive(Debug, FromRow)] +pub struct Subject { + pub id: Uuid, + pub ref_id: String, + pub name: String, + pub department_id: Uuid, + pub slug: String, + pub instruction_language: String, + pub educational_level: String, + pub credits: f32, + pub average_grade: f32, + pub total_students: i32, + pub failed_students: i32, +} + +impl Subject { + pub fn get_next_average(&self, key: SubjectGradingKey, count: i32) -> f32 { + if key.is_pass_or_fail_key() { + return self.average_grade; + } + + let complete_factor = self.average_grade * self.total_students as f32 + + count as f32 * key.to_multiplication_factor(); + complete_factor / (self.total_students + count) as f32 + } + + pub fn get_next_failed_students(&self, key: SubjectGradingKey, count: i32) -> i32 { + if key.is_evaluated_as_failed() { + return self.failed_students + count; + } + self.failed_students + } + + pub fn get_next_total_students(&self, count: i32) -> i32 { + self.total_students + count + } +} + +#[async_trait] +pub trait SubjectRepository: Sync { + async fn create_subject( + &self, + ref_id: String, + name: String, + department_id: Uuid, + slug: String, + instruction_language: String, + educational_level: String, + credits: f32, + average_grade: f32, + total_students: i32, + failed_students: i32, + ) -> Result; + async fn find_subject_by_ref_id(&self, ref_id: String) -> Result, sqlx::Error>; +} + +pub struct SubjectRepositoryImpl<'a> { + db: &'a Database, +} + +impl<'a> SubjectRepositoryImpl<'a> { + pub fn new(db: &'a Database) -> Self { + Self { db } + } +} + +#[async_trait] +impl<'a> SubjectRepository for SubjectRepositoryImpl<'a> { + async fn create_subject( + &self, + ref_id: String, + name: String, + department_id: Uuid, + slug: String, + instruction_language: String, + educational_level: String, + credits: f32, + average_grade: f32, + total_students: i32, + failed_students: i32, + ) -> Result { + sqlx::query_as::<_, Subject>( + r#" + INSERT INTO subject (ref_id, name, department_id, slug, instruction_language, educational_level, credits, average_grade, total_students, failed_students) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + ON CONFLICT (ref_id) DO UPDATE SET name = $2, department_id = $3, slug = $4, instruction_language = $5, educational_level = $6, credits = $7 + RETURNING *; + "#, + ) + .bind(ref_id) + .bind(name) + .bind(department_id) + .bind(slug) + .bind(instruction_language) + .bind(educational_level) + .bind(credits) + .bind(average_grade) + .bind(total_students) + .bind(failed_students) + .fetch_one(self.db) + .await + } + + async fn find_subject_by_ref_id(&self, ref_id: String) -> Result, Error> { + sqlx::query_as::<_, Subject>( + r#" + SELECT * FROM subject WHERE ref_id = $1; + "#, + ) + .bind(ref_id) + .fetch_optional(self.db) + .await + } +} diff --git a/apps/grades/.eslintrc.cjs b/apps/grades/.eslintrc.cjs new file mode 100644 index 000000000..cfe9f4a1e --- /dev/null +++ b/apps/grades/.eslintrc.cjs @@ -0,0 +1,6 @@ +const config = require("@dotkomonline/config/eslint-preset") + +module.exports = { + ...config, + extends: [...config.extends, "next"], +} diff --git a/apps/grades/.gitignore b/apps/grades/.gitignore new file mode 100644 index 000000000..fd3dbb571 --- /dev/null +++ b/apps/grades/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/apps/grades/README.md b/apps/grades/README.md new file mode 100644 index 000000000..c4033664f --- /dev/null +++ b/apps/grades/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/apps/grades/next.config.mjs b/apps/grades/next.config.mjs new file mode 100644 index 000000000..ece3de141 --- /dev/null +++ b/apps/grades/next.config.mjs @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + transpilePackages: ['@dotkomonline/logger'] +}; + +export default nextConfig; diff --git a/apps/grades/package.json b/apps/grades/package.json new file mode 100644 index 000000000..9fe06e592 --- /dev/null +++ b/apps/grades/package.json @@ -0,0 +1,35 @@ +{ + "name": "@dotkomonline/grades", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "eslint --max-warnings 0 .", + "lint:fix": "eslint --fix .", + "type-check": "tsc --noEmit", + "generate": "kysely-codegen --dialect postgres --camel-case --out-file src/db.generated.d.ts" + }, + "dependencies": { + "@dotkomonline/logger": "workspace:*", + "kysely": "^0.27.2", + "next": "14.1.0", + "pg": "^8.11.3", + "react": "^18", + "react-dom": "^18", + "zod": "^3.22.4" + }, + "devDependencies": { + "@dotkomonline/config": "workspace:*", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "autoprefixer": "^10.0.1", + "eslint": "^8.56.0", + "kysely-codegen": "^0.11.0", + "postcss": "^8", + "tailwindcss": "^3.3.0", + "typescript": "^5" + } +} diff --git a/apps/grades/postcss.config.js b/apps/grades/postcss.config.js new file mode 100644 index 000000000..33ad091d2 --- /dev/null +++ b/apps/grades/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/apps/grades/public/next.svg b/apps/grades/public/next.svg new file mode 100644 index 000000000..5174b28c5 --- /dev/null +++ b/apps/grades/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/grades/public/vercel.svg b/apps/grades/public/vercel.svg new file mode 100644 index 000000000..d2f842227 --- /dev/null +++ b/apps/grades/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/grades/src/app/favicon.ico b/apps/grades/src/app/favicon.ico new file mode 100644 index 000000000..718d6fea4 Binary files /dev/null and b/apps/grades/src/app/favicon.ico differ diff --git a/apps/grades/src/app/globals.css b/apps/grades/src/app/globals.css new file mode 100644 index 000000000..875c01e81 --- /dev/null +++ b/apps/grades/src/app/globals.css @@ -0,0 +1,33 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + } +} + +body { + color: rgb(var(--foreground-rgb)); + background: linear-gradient( + to bottom, + transparent, + rgb(var(--background-end-rgb)) + ) + rgb(var(--background-start-rgb)); +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} diff --git a/apps/grades/src/app/layout.tsx b/apps/grades/src/app/layout.tsx new file mode 100644 index 000000000..3a62e1caf --- /dev/null +++ b/apps/grades/src/app/layout.tsx @@ -0,0 +1,22 @@ +import { type Metadata } from "next" +import { Inter } from "next/font/google" +import "./globals.css" + +const inter = Inter({ subsets: ["latin"] }) + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + {children} + + ) +} diff --git a/apps/grades/src/app/page.tsx b/apps/grades/src/app/page.tsx new file mode 100644 index 000000000..ddcf1170b --- /dev/null +++ b/apps/grades/src/app/page.tsx @@ -0,0 +1,103 @@ +import Image from "next/image" + +export default function Home() { + return ( +
+
+

+ Get started by editing  + src/app/page.tsx +

+ +
+ +
+ Next.js Logo +
+ + +
+ ) +} diff --git a/apps/grades/src/db.generated.d.ts b/apps/grades/src/db.generated.d.ts new file mode 100644 index 000000000..142da039b --- /dev/null +++ b/apps/grades/src/db.generated.d.ts @@ -0,0 +1,70 @@ +import { type ColumnType } from "kysely" + +export type Generated = T extends ColumnType + ? ColumnType + : ColumnType + +export type Int8 = ColumnType + +export type SubjectGradingSeason = "AUTUMN" | "SPRING" | "SUMMER" | "WINTER" + +export type Timestamp = ColumnType + +export interface _SqlxMigrations { + checksum: Buffer + description: string + executionTime: Int8 + installedOn: Generated + success: boolean + version: Int8 +} + +export interface Department { + facultyId: string + id: Generated + name: string + refId: string +} + +export interface Faculty { + id: Generated + name: string + refId: string +} + +export interface Subject { + averageGrade: Generated + credits: number + departmentId: string + educationalLevel: string + failedStudents: Generated + id: Generated + instructionLanguage: string + name: string + refId: string + slug: string + totalStudents: Generated +} + +export interface SubjectSeasonGrade { + gradedA: number | null + gradedB: number | null + gradedC: number | null + gradedD: number | null + gradedE: number | null + gradedF: number | null + gradedFail: number | null + gradedPass: number | null + id: Generated + season: SubjectGradingSeason + subjectId: string + year: number +} + +export interface DB { + _SqlxMigrations: _SqlxMigrations + department: Department + faculty: Faculty + subject: Subject + subjectSeasonGrade: SubjectSeasonGrade +} diff --git a/apps/grades/src/pages/api/subjects.ts b/apps/grades/src/pages/api/subjects.ts new file mode 100644 index 000000000..c147a65c9 --- /dev/null +++ b/apps/grades/src/pages/api/subjects.ts @@ -0,0 +1,19 @@ +import { type NextApiRequest, type NextApiResponse } from "next" +import { z } from "zod" +import { createKysely } from "@/server/kysely" +import { createServiceLayer } from "@/server/core" + +const QuerySchema = z.object({ + q: z.string().nullish().default(null), + take: z.number().int().positive().default(30), + skip: z.number().int().nonnegative().default(0), +}) + +export default async function route(req: NextApiRequest, res: NextApiResponse) { + const query = QuerySchema.parse(req.query) + const kysely = createKysely() + const core = createServiceLayer({ fetch, db: kysely }) + + const subjects = await core.subjectService.search(query.q, query.take, query.skip) + res.status(200).json(subjects) +} diff --git a/apps/grades/src/server/core.ts b/apps/grades/src/server/core.ts new file mode 100644 index 000000000..5ac44a9e8 --- /dev/null +++ b/apps/grades/src/server/core.ts @@ -0,0 +1,25 @@ +import { type Database } from "@/server/kysely" +import { type FacultyRepository, FacultyRepositoryImpl } from "@/server/faculty-repository" +import { type DepartmentRepository, DepartmentRepositoryImpl } from "@/server/department-repository" +import { type SubjectRepository, SubjectRepositoryImpl } from "@/server/subject-repository" +import { type GradeRepository, GradeRepositoryImpl } from "@/server/grade-repository" +import { type SubjectService, SubjectServiceImpl } from "@/server/subject-service" + +export interface CreateServiceLayerOptions { + fetch: WindowOrWorkerGlobalScope["fetch"] + db: Database +} + +export type ServiceLayer = Awaited> + +export const createServiceLayer = (opts: CreateServiceLayerOptions) => { + const _facultyRepository: FacultyRepository = new FacultyRepositoryImpl(opts.db) + const _departmentRepository: DepartmentRepository = new DepartmentRepositoryImpl(opts.db) + const subjectRepository: SubjectRepository = new SubjectRepositoryImpl(opts.db) + const _gradeRepository: GradeRepository = new GradeRepositoryImpl(opts.db) + const subjectService: SubjectService = new SubjectServiceImpl(subjectRepository) + + return { + subjectService, + } +} diff --git a/apps/grades/src/server/department-repository.ts b/apps/grades/src/server/department-repository.ts new file mode 100644 index 000000000..f831f16b9 --- /dev/null +++ b/apps/grades/src/server/department-repository.ts @@ -0,0 +1,23 @@ +import { z } from "zod" +import { type Database } from "@/server/kysely" + +export type Department = z.infer +export const Department = z.object({ + id: z.string().uuid(), + refId: z.string(), + facultyId: z.string().uuid(), + name: z.string(), +}) + +export interface DepartmentRepository { + getDepartmentById(id: string): Promise +} + +export class DepartmentRepositoryImpl implements DepartmentRepository { + constructor(private readonly db: Database) {} + + async getDepartmentById(id: string): Promise { + const department = await this.db.selectFrom("department").selectAll().where("id", "=", id).executeTakeFirst() + return department ? Department.parse(department) : null + } +} diff --git a/apps/grades/src/server/faculty-repository.ts b/apps/grades/src/server/faculty-repository.ts new file mode 100644 index 000000000..ac246e09c --- /dev/null +++ b/apps/grades/src/server/faculty-repository.ts @@ -0,0 +1,22 @@ +import { z } from "zod" +import { type Database } from "@/server/kysely" + +export type Faculty = z.infer +export const Faculty = z.object({ + id: z.string().uuid(), + refId: z.string(), + name: z.string(), +}) + +export interface FacultyRepository { + getFacultyById(id: string): Promise +} + +export class FacultyRepositoryImpl implements FacultyRepository { + constructor(private readonly db: Database) {} + + async getFacultyById(id: string): Promise { + const faculty = await this.db.selectFrom("faculty").selectAll().where("id", "=", id).executeTakeFirst() + return faculty ? Faculty.parse(faculty) : null + } +} diff --git a/apps/grades/src/server/grade-repository.ts b/apps/grades/src/server/grade-repository.ts new file mode 100644 index 000000000..23747830a --- /dev/null +++ b/apps/grades/src/server/grade-repository.ts @@ -0,0 +1,40 @@ +import { z } from "zod" +import { type Database } from "@/server/kysely" + +export type Grade = z.infer +export type Season = Grade["season"] +export const Grade = z.object({ + id: z.string().uuid(), + subjectId: z.string().uuid(), + season: z.enum(["SPRING", "AUTUMN", "WINTER", "SUMMER"]), + year: z.number(), + + gradedA: z.number().int().nullable().default(null), + gradedB: z.number().int().nullable().default(null), + gradedC: z.number().int().nullable().default(null), + gradedD: z.number().int().nullable().default(null), + gradedE: z.number().int().nullable().default(null), + gradedF: z.number().int().nullable().default(null), + + gradedFail: z.number().int().nullable().default(null), + gradedPass: z.number().int().nullable().default(null), +}) + +export interface GradeRepository { + getGradeBySemester(subjectId: string, season: Season, year: number): Promise +} + +export class GradeRepositoryImpl implements GradeRepository { + constructor(private readonly db: Database) {} + + async getGradeBySemester(subjectId: string, season: Season, year: number): Promise { + const grade = await this.db + .selectFrom("subjectSeasonGrade") + .selectAll() + .where("subjectId", "=", subjectId) + .where("season", "=", season) + .where("year", "=", year) + .executeTakeFirst() + return grade ? Grade.parse(grade) : null + } +} diff --git a/apps/grades/src/server/kysely.ts b/apps/grades/src/server/kysely.ts new file mode 100644 index 000000000..4bf874517 --- /dev/null +++ b/apps/grades/src/server/kysely.ts @@ -0,0 +1,18 @@ +import process from "node:process" +import pg from "pg" +import { CamelCasePlugin, Kysely, PostgresDialect } from "kysely" +import { type DB } from "@/db.generated" + +export type Database = Awaited> + +export const createKysely = () => { + const conn = new pg.Pool({ + connectionString: process.env.DATABASE_URL ?? "__MISSING_DATABASE_URL__", + }) + return new Kysely({ + dialect: new PostgresDialect({ + pool: conn, + }), + plugins: [new CamelCasePlugin()], + }) +} diff --git a/apps/grades/src/server/subject-repository.ts b/apps/grades/src/server/subject-repository.ts new file mode 100644 index 000000000..3c82fd2f7 --- /dev/null +++ b/apps/grades/src/server/subject-repository.ts @@ -0,0 +1,84 @@ +import { z } from "zod" +import { sql } from "kysely" +import { type Database } from "@/server/kysely" + +export type Subject = z.infer +export const Subject = z.object({ + id: z.string().uuid(), + refId: z.string(), + departmentId: z.string().uuid(), + name: z.string(), + slug: z.string(), + instructionLanguage: z.string(), + educationalLevel: z.string(), + credits: z.number(), + averageGrade: z.number().nonnegative(), + totalStudents: z.number().int().nonnegative(), + failedStudents: z.number().int().nonnegative(), +}) + +export interface SubjectRepository { + getSubjectByReferenceId(refId: string): Promise + getSubjectById(id: string): Promise + getSubjectsBySearchExpression(expression: string, take: number, skip: number): Promise + getSubjectsByPopularity(take: number, skip: number): Promise + getSubjectsByAverageGrade(take: number, skip: number): Promise +} + +export class SubjectRepositoryImpl implements SubjectRepository { + constructor(private readonly db: Database) {} + + async getSubjectByReferenceId(refId: string): Promise { + const subject = await this.db.selectFrom("subject").selectAll().where("refId", "=", refId).executeTakeFirst() + return subject ? Subject.parse(subject) : null + } + + async getSubjectById(id: string): Promise { + const subject = await this.db.selectFrom("subject").selectAll().where("id", "=", id).executeTakeFirst() + return subject ? Subject.parse(subject) : null + } + + async getSubjectsBySearchExpression(expression: string, take: number, skip: number): Promise { + // We want to match the expression case-insensitively, and we will also attempt to match the slug for the subject. + // We should also find close matches using levenstein distance + const subjects = await this.db + .selectFrom("subject") + .selectAll() + .where((eb) => + eb.or([ + eb("name", "ilike", `%${expression}%`), + eb("slug", "ilike", `%${expression}%`), + eb(sql`levenshtein(subject.name, ${expression}::text)`, "<=", 1), + eb(sql`levenshtein(subject.slug, ${expression}::text)`, "<=", 1), + ]) + ) + .limit(take) + .offset(skip) + .execute() + return subjects.map((x) => Subject.parse(x)) + } + + async getSubjectsByPopularity(take: number, skip: number): Promise { + // We determine popularity by the number of grades given to the subject + const subjects = await this.db + .selectFrom("subject") + .selectAll() + .orderBy("totalStudents", "desc") + .limit(take) + .offset(skip) + .execute() + return subjects.map((x) => Subject.parse(x)) + } + + async getSubjectsByAverageGrade(take: number, skip: number): Promise { + // We determine popularity by the number of grades given to the subject + const subjects = await this.db + .selectFrom("subject") + .selectAll() + .orderBy("averageGrade", "desc") + .limit(take) + .offset(skip) + .execute() + return subjects.map((x) => Subject.parse(x)) + } +} diff --git a/apps/grades/src/server/subject-service.ts b/apps/grades/src/server/subject-service.ts new file mode 100644 index 000000000..77be62b80 --- /dev/null +++ b/apps/grades/src/server/subject-service.ts @@ -0,0 +1,16 @@ +import { type Subject, type SubjectRepository } from "@/server/subject-repository" + +export interface SubjectService { + search(expression: string | null, take: number, skip: number): Promise +} + +export class SubjectServiceImpl implements SubjectService { + constructor(private readonly subjectRepository: SubjectRepository) {} + + async search(expression: string | null, take: number, skip: number): Promise { + if (expression !== null) { + return this.subjectRepository.getSubjectsBySearchExpression(expression.trim(), take, skip) + } + return this.subjectRepository.getSubjectsByPopularity(take, skip) + } +} diff --git a/apps/grades/tailwind.config.ts b/apps/grades/tailwind.config.ts new file mode 100644 index 000000000..0768354a9 --- /dev/null +++ b/apps/grades/tailwind.config.ts @@ -0,0 +1,19 @@ +import { type Config } from "tailwindcss" + +const config: Config = { + content: [ + "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", + "./src/components/**/*.{js,ts,jsx,tsx,mdx}", + "./src/app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: { + backgroundImage: { + "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", + "gradient-conic": "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", + }, + }, + }, + plugins: [], +} +export default config diff --git a/apps/grades/tsconfig.json b/apps/grades/tsconfig.json new file mode 100644 index 000000000..7b2858930 --- /dev/null +++ b/apps/grades/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62f6cc5f8..03f8b95df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -234,6 +234,61 @@ importers: specifier: 5.2.2 version: 5.2.2 + apps/grades: + dependencies: + '@dotkomonline/logger': + specifier: workspace:* + version: link:../../packages/logger + kysely: + specifier: ^0.27.2 + version: 0.27.2 + next: + specifier: 14.1.0 + version: 14.1.0(react-dom@18.2.0)(react@18.2.0) + pg: + specifier: ^8.11.3 + version: 8.11.3 + react: + specifier: ^18 + version: 18.2.0 + react-dom: + specifier: ^18 + version: 18.2.0(react@18.2.0) + zod: + specifier: ^3.22.4 + version: 3.22.4 + devDependencies: + '@dotkomonline/config': + specifier: workspace:* + version: link:../../packages/config + '@types/node': + specifier: ^20 + version: 20.11.16 + '@types/react': + specifier: ^18 + version: 18.2.38 + '@types/react-dom': + specifier: ^18 + version: 18.2.17 + autoprefixer: + specifier: ^10.0.1 + version: 10.4.16(postcss@8.4.32) + eslint: + specifier: ^8.56.0 + version: 8.56.0 + kysely-codegen: + specifier: ^0.11.0 + version: 0.11.0(kysely@0.27.2)(pg@8.11.3) + postcss: + specifier: ^8 + version: 8.4.32 + tailwindcss: + specifier: ^3.3.0 + version: 3.3.5(ts-node@10.9.1) + typescript: + specifier: ^5 + version: 5.2.2 + apps/rif: dependencies: '@dotkomonline/env': @@ -4125,6 +4180,16 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.56.0 + eslint-visitor-keys: 3.4.3 + dev: true + /@eslint-community/regexpp@4.6.2: resolution: {integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -4147,11 +4212,33 @@ packages: - supports-color dev: true + /@eslint/eslintrc@2.1.4: + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.20.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + /@eslint/js@8.54.0: resolution: {integrity: sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@eslint/js@8.56.0: + resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + /@floating-ui/core@1.4.1: resolution: {integrity: sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==} dependencies: @@ -4575,6 +4662,10 @@ packages: resolution: {integrity: sha512-7xRqh9nMvP5xrW4/+L0jgRRX+HoNRGnfJpD+5Wq6/13j3dsdzxO3BCXn7D3hMqsDb+vjZnJq+vI7+EtgrYZTeA==} dev: false + /@next/env@14.1.0: + resolution: {integrity: sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==} + dev: false + /@next/eslint-plugin-next@14.0.3: resolution: {integrity: sha512-j4K0n+DcmQYCVnSAM+UByTVfIHnYQy2ODozfQP+4RdwtRDfobrIvKq1K4Exb2koJ79HSSa7s6B2SA8T/1YR3RA==} dependencies: @@ -4590,6 +4681,15 @@ packages: dev: false optional: true + /@next/swc-darwin-arm64@14.1.0: + resolution: {integrity: sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@next/swc-darwin-x64@14.0.3: resolution: {integrity: sha512-RkTf+KbAD0SgYdVn1XzqE/+sIxYGB7NLMZRn9I4Z24afrhUpVJx6L8hsRnIwxz3ERE2NFURNliPjJ2QNfnWicQ==} engines: {node: '>= 10'} @@ -4599,6 +4699,15 @@ packages: dev: false optional: true + /@next/swc-darwin-x64@14.1.0: + resolution: {integrity: sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-arm64-gnu@14.0.3: resolution: {integrity: sha512-3tBWGgz7M9RKLO6sPWC6c4pAw4geujSwQ7q7Si4d6bo0l6cLs4tmO+lnSwFp1Tm3lxwfMk0SgkJT7EdwYSJvcg==} engines: {node: '>= 10'} @@ -4608,6 +4717,15 @@ packages: dev: false optional: true + /@next/swc-linux-arm64-gnu@14.1.0: + resolution: {integrity: sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-arm64-musl@14.0.3: resolution: {integrity: sha512-v0v8Kb8j8T23jvVUWZeA2D8+izWspeyeDGNaT2/mTHWp7+37fiNfL8bmBWiOmeumXkacM/AB0XOUQvEbncSnHA==} engines: {node: '>= 10'} @@ -4617,6 +4735,15 @@ packages: dev: false optional: true + /@next/swc-linux-arm64-musl@14.1.0: + resolution: {integrity: sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-x64-gnu@14.0.3: resolution: {integrity: sha512-VM1aE1tJKLBwMGtyBR21yy+STfl0MapMQnNrXkxeyLs0GFv/kZqXS5Jw/TQ3TSUnbv0QPDf/X8sDXuMtSgG6eg==} engines: {node: '>= 10'} @@ -4626,6 +4753,15 @@ packages: dev: false optional: true + /@next/swc-linux-x64-gnu@14.1.0: + resolution: {integrity: sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-x64-musl@14.0.3: resolution: {integrity: sha512-64EnmKy18MYFL5CzLaSuUn561hbO1Gk16jM/KHznYP3iCIfF9e3yULtHaMy0D8zbHfxset9LTOv6cuYKJgcOxg==} engines: {node: '>= 10'} @@ -4635,6 +4771,15 @@ packages: dev: false optional: true + /@next/swc-linux-x64-musl@14.1.0: + resolution: {integrity: sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-arm64-msvc@14.0.3: resolution: {integrity: sha512-WRDp8QrmsL1bbGtsh5GqQ/KWulmrnMBgbnb+59qNTW1kVi1nG/2ndZLkcbs2GX7NpFLlToLRMWSQXmPzQm4tog==} engines: {node: '>= 10'} @@ -4644,6 +4789,15 @@ packages: dev: false optional: true + /@next/swc-win32-arm64-msvc@14.1.0: + resolution: {integrity: sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-ia32-msvc@14.0.3: resolution: {integrity: sha512-EKffQeqCrj+t6qFFhIFTRoqb2QwX1mU7iTOvMyLbYw3QtqTw9sMwjykyiMlZlrfm2a4fA84+/aeW+PMg1MjuTg==} engines: {node: '>= 10'} @@ -4653,6 +4807,15 @@ packages: dev: false optional: true + /@next/swc-win32-ia32-msvc@14.1.0: + resolution: {integrity: sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-x64-msvc@14.0.3: resolution: {integrity: sha512-ERhKPSJ1vQrPiwrs15Pjz/rvDHZmkmvbf/BjPN/UCOI++ODftT0GtasDPi0j+y6PPJi5HsXw+dpRaXUaw4vjuQ==} engines: {node: '>= 10'} @@ -4662,6 +4825,15 @@ packages: dev: false optional: true + /@next/swc-win32-x64-msvc@14.1.0: + resolution: {integrity: sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1: resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} dependencies: @@ -6126,7 +6298,7 @@ packages: /@sanity/bifur-client@0.3.1: resolution: {integrity: sha512-GlY9+tUmM0Vye64BHwIYLOivuRL37ucW/sj/D9MYqBmjgBnTRrjfmg8NR7qoodZuJ5nYJ5qpGMsVIBLP4Plvnw==} dependencies: - nanoid: 3.3.6 + nanoid: 3.3.7 rxjs: 7.8.1 dev: false @@ -7328,7 +7500,7 @@ packages: resolution: {integrity: sha512-Lz5J+NFgZS4cEVhquwjIGH4oQwlVn2h7LXD3boitujBnzOE5o7s9H8hchEjoDK2SlRsJTogdKnQeiJgPPKLIEw==} dependencies: '@types/docker-modem': 3.0.6 - '@types/node': 18.18.9 + '@types/node': 18.18.13 dev: true /@types/eslint@8.44.7: @@ -7378,7 +7550,7 @@ packages: /@types/ignore-walk@4.0.3: resolution: {integrity: sha512-6V7wDsk0nz8LtRC7qeC0GfXadFLT4FdCtVbXhxoIGRdkn2kLr20iMLupRGiBhlZ79WWWqaObIdR3nkXfUrBPdQ==} dependencies: - '@types/node': 18.18.9 + '@types/node': 18.18.13 dev: true /@types/is-hotkey@0.1.10: @@ -7438,12 +7610,8 @@ packages: dependencies: undici-types: 5.26.5 - /@types/node@18.18.4: - resolution: {integrity: sha512-t3rNFBgJRugIhackit2mVcLfF6IRc0JE4oeizPQL8Zrm8n2WY/0wOdpOPhdtG0V9Q2TlW/axbF1MJ6z+Yj/kKQ==} - dev: true - - /@types/node@18.18.9: - resolution: {integrity: sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==} + /@types/node@20.11.16: + resolution: {integrity: sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==} dependencies: undici-types: 5.26.5 dev: true @@ -7454,7 +7622,7 @@ packages: /@types/pg@8.10.9: resolution: {integrity: sha512-UksbANNE/f8w0wOMxVKKIrLCbEMV+oM1uKejmwXr39olg4xqcfBDbXxObJAt6XxHbDa4XTKOlUEcEltXDX+XLQ==} dependencies: - '@types/node': 18.18.4 + '@types/node': 18.18.13 pg-protocol: 1.6.0 pg-types: 4.0.1 dev: true @@ -8266,6 +8434,22 @@ packages: postcss-value-parser: 4.2.0 dev: true + /autoprefixer@10.4.16(postcss@8.4.32): + resolution: {integrity: sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + dependencies: + browserslist: 4.21.11 + caniuse-lite: 1.0.30001538 + fraction.js: 4.3.6 + normalize-range: 0.1.2 + picocolors: 1.0.0 + postcss: 8.4.32 + postcss-value-parser: 4.2.0 + dev: true + /available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} @@ -8552,6 +8736,10 @@ packages: /caniuse-lite@1.0.30001538: resolution: {integrity: sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==} + /caniuse-lite@1.0.30001585: + resolution: {integrity: sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==} + dev: false + /ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} dev: true @@ -10139,6 +10327,53 @@ packages: - supports-color dev: true + /eslint@8.56.0: + resolution: {integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/regexpp': 4.6.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.56.0 + '@humanwhocodes/config-array': 0.11.13 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.20.0 + graphemer: 1.4.0 + ignore: 5.2.4 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + /espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10347,7 +10582,6 @@ packages: glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.5 - dev: true /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -12077,10 +12311,42 @@ packages: pg: 8.11.3 dev: true + /kysely-codegen@0.11.0(kysely@0.27.2)(pg@8.11.3): + resolution: {integrity: sha512-8aklzXygjANshk5BoGSQ0BWukKIoPL4/k1iFWyteGUQ/VtB1GlyrELBZv1GglydjLGECSSVDpsOgEXyWQmuksg==} + hasBin: true + peerDependencies: + '@libsql/kysely-libsql': ^0.3.0 + better-sqlite3: '>=7.6.2' + kysely: '>=0.19.12' + mysql2: ^2.3.3 || ^3.0.0 + pg: ^8.8.0 + peerDependenciesMeta: + '@libsql/kysely-libsql': + optional: true + better-sqlite3: + optional: true + mysql2: + optional: true + pg: + optional: true + dependencies: + chalk: 4.1.2 + dotenv: 16.3.1 + git-diff: 2.0.6 + kysely: 0.27.2 + micromatch: 4.0.5 + minimist: 1.2.8 + pg: 8.11.3 + dev: true + /kysely@0.26.3: resolution: {integrity: sha512-yWSgGi9bY13b/W06DD2OCDDHQmq1kwTGYlQ4wpZkMOJqMGCstVCFIvxCCVG4KfY1/3G0MhDAcZsip/Lw8/vJWw==} engines: {node: '>=14.0.0'} + /kysely@0.27.2: + resolution: {integrity: sha512-DmRvEfiR/NLpgsTbSxma2ldekhsdcd65+MNiKXyd/qj7w7X5e3cLkXxcj+MypsRDjPhHQ/CD5u3Eq1sBYzX0bw==} + engines: {node: '>=14.0.0'} + /language-subtag-registry@0.3.22: resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} dev: true @@ -13021,7 +13287,6 @@ packages: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - dev: true /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} @@ -13123,6 +13388,45 @@ packages: - babel-plugin-macros dev: false + /next@14.1.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==} + engines: {node: '>=18.17.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + sass: + optional: true + dependencies: + '@next/env': 14.1.0 + '@swc/helpers': 0.5.2 + busboy: 1.6.0 + caniuse-lite: 1.0.30001585 + graceful-fs: 4.2.11 + postcss: 8.4.31 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + styled-jsx: 5.1.1(react@18.2.0) + optionalDependencies: + '@next/swc-darwin-arm64': 14.1.0 + '@next/swc-darwin-x64': 14.1.0 + '@next/swc-linux-arm64-gnu': 14.1.0 + '@next/swc-linux-arm64-musl': 14.1.0 + '@next/swc-linux-x64-gnu': 14.1.0 + '@next/swc-linux-x64-musl': 14.1.0 + '@next/swc-win32-arm64-msvc': 14.1.0 + '@next/swc-win32-ia32-msvc': 14.1.0 + '@next/swc-win32-x64-msvc': 14.1.0 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: false + /node-cleanup@2.1.2: resolution: {integrity: sha512-qN8v/s2PAJwGUtr1/hYTpNKlD6Y9rc4p8KSmJXyGdYGZsDGKXrGThikLFP9OCHFeLeEpQzPwiAtdIvBLqm//Hw==} dev: true @@ -13766,6 +14070,18 @@ packages: read-cache: 1.0.0 resolve: 1.22.2 + /postcss-import@15.1.0(postcss@8.4.32): + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + dependencies: + postcss: 8.4.32 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.2 + dev: false + /postcss-js@4.0.1(postcss@8.4.31): resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} engines: {node: ^12 || ^14 || >= 16} @@ -13775,6 +14091,16 @@ packages: camelcase-css: 2.0.1 postcss: 8.4.31 + /postcss-js@4.0.1(postcss@8.4.32): + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.32 + dev: false + /postcss-load-config@4.0.1(postcss@8.4.31)(ts-node@10.9.1): resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} engines: {node: '>= 14'} @@ -13792,6 +14118,23 @@ packages: ts-node: 10.9.1(@types/node@18.18.13)(typescript@5.2.2) yaml: 2.3.1 + /postcss-load-config@4.0.1(postcss@8.4.32)(ts-node@10.9.1): + resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.1.0 + postcss: 8.4.32 + ts-node: 10.9.1(@types/node@18.18.13)(typescript@5.2.2) + yaml: 2.3.1 + /postcss-mixins@9.0.4(postcss@8.4.31): resolution: {integrity: sha512-XVq5jwQJDRu5M1XGkdpgASqLk37OqkH4JCFDXl/Dn7janOJjCTEKL+36cnRVy7bMtoBzALfO7bV7nTIsFnUWLA==} engines: {node: '>=14.0'} @@ -13814,6 +14157,16 @@ packages: postcss: 8.4.31 postcss-selector-parser: 6.0.13 + /postcss-nested@6.0.1(postcss@8.4.32): + resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.32 + postcss-selector-parser: 6.0.13 + dev: false + /postcss-preset-mantine@1.11.0(postcss@8.4.31): resolution: {integrity: sha512-drhCJAd8Rrn5ulRX5cP6DM6nMLrACAsBU73MfXNI3c77p4dodO36KcfEMY0WqaQkd/E2oODkTm7gVYOzjs45Gw==} peerDependencies: @@ -13865,7 +14218,6 @@ packages: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.0.2 - dev: true /postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} @@ -15468,7 +15820,7 @@ packages: '@types/stylis': 4.2.1 css-to-react-native: 3.2.0 csstype: 3.1.2 - postcss: 8.4.31 + postcss: 8.4.32 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) shallowequal: 1.1.0 @@ -15593,7 +15945,7 @@ packages: chokidar: 3.5.3 didyoumean: 1.2.2 dlv: 1.1.3 - fast-glob: 3.3.1 + fast-glob: 3.3.2 glob-parent: 6.0.2 is-glob: 4.0.3 jiti: 1.19.1 @@ -15602,11 +15954,11 @@ packages: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.0 - postcss: 8.4.31 - postcss-import: 15.1.0(postcss@8.4.31) - postcss-js: 4.0.1(postcss@8.4.31) - postcss-load-config: 4.0.1(postcss@8.4.31)(ts-node@10.9.1) - postcss-nested: 6.0.1(postcss@8.4.31) + postcss: 8.4.32 + postcss-import: 15.1.0(postcss@8.4.32) + postcss-js: 4.0.1(postcss@8.4.32) + postcss-load-config: 4.0.1(postcss@8.4.32)(ts-node@10.9.1) + postcss-nested: 6.0.1(postcss@8.4.32) postcss-selector-parser: 6.0.13 postcss-value-parser: 4.2.0 resolve: 1.22.8 @@ -16031,7 +16383,7 @@ packages: execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss-load-config: 4.0.1(postcss@8.4.31)(ts-node@10.9.1) + postcss-load-config: 4.0.1(postcss@8.4.32)(ts-node@10.9.1) resolve-from: 5.0.0 rollup: 3.29.2 source-map: 0.8.0-beta.0 @@ -16617,7 +16969,7 @@ packages: vfile-message: 4.0.2 dev: true - /vite-node@0.34.6(@types/node@18.18.4): + /vite-node@0.34.6(@types/node@18.18.13): resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} engines: {node: '>=v14.18.0'} hasBin: true @@ -16627,7 +16979,7 @@ packages: mlly: 1.4.0 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.5.0(@types/node@18.18.4) + vite: 4.5.0(@types/node@18.18.13) transitivePeerDependencies: - '@types/node' - less @@ -16656,7 +17008,7 @@ packages: - typescript dev: true - /vite@4.4.11(@types/node@18.18.4): + /vite@4.4.11(@types/node@18.18.13): resolution: {integrity: sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -16684,9 +17036,9 @@ packages: terser: optional: true dependencies: - '@types/node': 18.18.4 + '@types/node': 18.18.13 esbuild: 0.18.20 - postcss: 8.4.31 + postcss: 8.4.32 rollup: 3.29.2 optionalDependencies: fsevents: 2.3.3 @@ -16722,46 +17074,10 @@ packages: dependencies: '@types/node': 18.18.13 esbuild: 0.18.20 - postcss: 8.4.31 - rollup: 3.29.2 - optionalDependencies: - fsevents: 2.3.3 - - /vite@4.5.0(@types/node@18.18.4): - resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} - engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true - peerDependencies: - '@types/node': '>= 14' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - dependencies: - '@types/node': 18.18.4 - esbuild: 0.18.20 - postcss: 8.4.31 + postcss: 8.4.32 rollup: 3.29.2 optionalDependencies: fsevents: 2.3.3 - dev: true /vite@5.0.7(@types/node@18.18.13): resolution: {integrity: sha512-B4T4rJCDPihrQo2B+h1MbeGL/k/GMAHzhQ8S0LjQ142s6/+l3hHTT095ORvsshj4QCkoWu3Xtmob5mazvakaOw==} @@ -16832,7 +17148,7 @@ packages: dependencies: '@types/chai': 4.3.5 '@types/chai-subset': 1.3.3 - '@types/node': 18.18.4 + '@types/node': 18.18.13 '@vitest/expect': 0.34.6 '@vitest/runner': 0.34.6 '@vitest/snapshot': 0.34.6 @@ -16852,8 +17168,8 @@ packages: strip-literal: 1.0.1 tinybench: 2.5.0 tinypool: 0.7.0 - vite: 4.4.11(@types/node@18.18.4) - vite-node: 0.34.6(@types/node@18.18.4) + vite: 4.4.11(@types/node@18.18.13) + vite-node: 0.34.6(@types/node@18.18.13) why-is-node-running: 2.2.2 transitivePeerDependencies: - less diff --git a/turbo.json b/turbo.json index d3b3e3802..080a0ad49 100644 --- a/turbo.json +++ b/turbo.json @@ -69,6 +69,7 @@ "EMAIL_TOKEN", "EMAIL_ENDPOINT", "INTEREST_FORM_SERVICE_ACCOUNT", - "INTEREST_FORM_SPREADSHEET_ID" + "INTEREST_FORM_SPREADSHEET_ID", + "MAX_CONCURRENCY" ] }