diff options
author | Nathan Ringo <nathan@remexre.com> | 2024-01-16 00:57:41 -0600 |
---|---|---|
committer | Nathan Ringo <nathan@remexre.com> | 2024-01-16 00:57:41 -0600 |
commit | ed778ab2060c6131caf98231a97873d7ea490d5a (patch) | |
tree | bfa6ceca8fe2e209562c1e995c598d80be0e4501 | |
parent | 54f497163f57dacd8d621a2a3c89e1f06ac370d0 (diff) |
The start of database functionality.
-rw-r--r-- | .envrc | 1 | ||||
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | Cargo.lock | 728 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | build.rs | 5 | ||||
-rw-r--r-- | flake.nix | 2 | ||||
-rw-r--r-- | migrations/20240116060938_init_x500s.sql | 27 | ||||
-rw-r--r-- | src/bin/add-student-x500s.rs | 144 | ||||
-rw-r--r-- | src/bin/lambo.rs (renamed from src/main.rs) | 60 | ||||
-rw-r--r-- | src/config.rs | 17 | ||||
-rw-r--r-- | src/handlers/x500_mapper.rs | 21 | ||||
-rw-r--r-- | src/lib.rs | 2 |
12 files changed, 975 insertions, 40 deletions
@@ -1 +1,2 @@ use flake +export DATABASE_URL=sqlite:lambo.db @@ -12,5 +12,8 @@ result-* # Non-Nix outputs target/ -# Configuration files +# Configuration and state files +lambo.db +lambo.db-shm +lambo.db-wal lambo.toml @@ -18,6 +18,25 @@ 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 = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -110,6 +129,25 @@ dependencies = [ ] [[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 = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -148,6 +186,12 @@ 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" @@ -158,6 +202,9 @@ name = "bitflags" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" @@ -311,6 +358,12 @@ dependencies = [ ] [[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" @@ -336,6 +389,21 @@ dependencies = [ ] [[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 = "crc32fast" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -354,6 +422,15 @@ dependencies = [ ] [[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" @@ -370,6 +447,27 @@ dependencies = [ ] [[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + +[[package]] name = "dashmap" version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -390,6 +488,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[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 = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -406,7 +515,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", +] + +[[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]] @@ -444,12 +570,35 @@ dependencies = [ ] [[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 = "flate2" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -460,6 +609,17 @@ dependencies = [ ] [[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" @@ -517,6 +677,17 @@ dependencies = [ ] [[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" @@ -629,12 +800,28 @@ 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" @@ -652,6 +839,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[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" @@ -773,6 +993,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + +[[package]] name = "itoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -793,16 +1022,28 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "csv", "futures", "log", "serde", "serenity", + "sqlx", "stderrlog", + "time", "tokio", "toml", ] [[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 = "levenshtein" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -815,6 +1056,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[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.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -837,6 +1095,16 @@ 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" @@ -874,6 +1142,12 @@ dependencies = [ ] [[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.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -894,12 +1168,72 @@ dependencies = [ ] [[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.1", + "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.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] name = "num-traits" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -951,6 +1285,21 @@ dependencies = [ ] [[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" @@ -969,6 +1318,33 @@ 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.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" + +[[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1100,12 +1476,32 @@ dependencies = [ "cc", "getrandom", "libc", - "spin", + "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" @@ -1305,6 +1701,27 @@ dependencies = [ ] [[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 = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] name = "skeptic" version = "0.13.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1346,9 +1763,240 @@ dependencies = [ [[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", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[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.1", + "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", + "time", + "tracing", + "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.1", + "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", + "time", + "tracing", + "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", + "time", + "tracing", + "url", + "urlencoding", +] [[package]] name = "static_assertions" @@ -1370,12 +2018,29 @@ dependencies = [ ] [[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 = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[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" @@ -1559,6 +2224,17 @@ dependencies = [ ] [[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-tungstenite" version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1763,6 +2439,18 @@ dependencies = [ ] [[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[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" @@ -1781,6 +2469,12 @@ dependencies = [ ] [[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1799,6 +2493,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" [[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" @@ -1925,6 +2625,12 @@ 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 = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2116,6 +2822,26 @@ dependencies = [ ] [[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" @@ -6,10 +6,13 @@ edition = "2021" [dependencies] anyhow = { version = "1.0.79", features = ["backtrace"] } clap = { version = "4.4.17", features = ["derive"] } +csv = "1.3.0" futures = "0.3.30" log = "0.4.20" serde = { version = "1.0.195", features = ["derive"] } serenity = "0.12.0" +sqlx = { version = "0.7.3", features = ["runtime-tokio", "sqlite", "time"] } stderrlog = "0.5.4" +time = "0.3.31" tokio = { version = "1.35.1", features = ["rt-multi-thread"] } toml = "0.8.8" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..d506869 --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +// generated by `sqlx migrate build-script` +fn main() { + // trigger recompilation when a new migration is added + println!("cargo:rerun-if-changed=migrations"); +} @@ -20,7 +20,7 @@ in rec { devShells.default = pkgs.mkShell { inputsFrom = builtins.attrValues packages; - nativeBuildInputs = [ pkgs.cargo-watch ]; + nativeBuildInputs = [ pkgs.cargo-watch pkgs.sqlite pkgs.sqlx-cli ]; }; packages.default = diff --git a/migrations/20240116060938_init_x500s.sql b/migrations/20240116060938_init_x500s.sql new file mode 100644 index 0000000..3d5ca68 --- /dev/null +++ b/migrations/20240116060938_init_x500s.sql @@ -0,0 +1,27 @@ +-- We know all the X.500s of students we expect to have. +CREATE TABLE IF NOT EXISTS student_x500s + ( x500 TEXT NOT NULL + , UNIQUE(x500) + ); + +-- In the common case, we can just map users to their X.500s. +CREATE TABLE IF NOT EXISTS uids_to_x500s_clean + ( uid TEXT NOT NULL + , x500 TEXT NOT NULL + , FOREIGN KEY(x500) REFERENCES student_x500s(x500) + , UNIQUE(uid) + , UNIQUE(x500) + ); + +-- ...until we see someone change X.500, or try to take someone else's X.500, +-- or use an unenrolled student's X.500. +-- +-- These show up as a violation of one of the constraints in +-- uids_to_x500s_clean. In that case, we still add the failing tuple to +-- all_seen_uids_to_x500s. This lets us get a list of "suspicious tuples" by +-- subtracting uids_to_x500s_clean from all_seen_uids_to_x500s. +CREATE TABLE IF NOT EXISTS all_seen_uids_to_x500s + ( uid TEXT NOT NULL + , x500 TEXT NOT NULL + , UNIQUE(uid, x500) + ); diff --git a/src/bin/add-student-x500s.rs b/src/bin/add-student-x500s.rs new file mode 100644 index 0000000..14023b1 --- /dev/null +++ b/src/bin/add-student-x500s.rs @@ -0,0 +1,144 @@ +use anyhow::{bail, Context, Result}; +use clap::{value_parser, ArgAction, Parser}; +use futures::{stream, FutureExt, StreamExt}; +use lambo::config::Config; +use sqlx::sqlite::SqlitePoolOptions; +use std::{fs, path::PathBuf}; +use stderrlog::StdErrLog; + +#[derive(Debug, Parser)] +struct Args { + /// The path to the lambo configuration file. + config_path: PathBuf, + + /// The path to the CSV file, as exported from Canvas's gradebook. + csv_path: PathBuf, + + /// Decreases the log level. + #[clap( + short, + long, + conflicts_with("verbose"), + action = ArgAction::Count, + value_parser = value_parser!(u8).range(..=2) + )] + quiet: u8, + + /// Increases the log level. + #[clap( + short, + long, + conflicts_with("quiet"), + action = ArgAction::Count, + value_parser = value_parser!(u8).range(..=3) + )] + verbose: u8, +} + +#[tokio::main] +async fn main() -> Result<()> { + // Parse the arguments. + let args = Args::parse(); + + // Set up logging. + { + let mut logger = StdErrLog::new(); + match args.quiet { + 0 => logger.verbosity(1 + args.verbose as usize), + 1 => logger.verbosity(0), + 2 => logger.quiet(true), + // UNREACHABLE: A maximum of two occurrences of quiet are allowed. + _ => unreachable!(), + }; + // UNWRAP: No other logger should be set up. + logger.show_module_names(true).init().unwrap() + } + + // Parse the config file. + let config = Config::read_from_file(&args.config_path)?; + + // Connect to the database. + let db = SqlitePoolOptions::new() + .connect(&config.database_url) + .await + .with_context(|| format!("failed to connect to database at {:?}", config.database_url))?; + + // Run any necessary migrations. + sqlx::migrate!().run(&db).await.with_context(|| { + format!( + "failed to run migrations on database at {:?}", + config.database_url + ) + })?; + + // Read the CSV file. + let csv_string = fs::read_to_string(&args.csv_path) + .with_context(|| format!("failed to read {:?}", args.csv_path))?; + + // Skip the first two lines; Canvas puts two lines of headers in... + let (i, _) = csv_string + .match_indices('\n') + .nth(1) + .context("invalid CSV file (not enough lines)")?; + let csv_string = &csv_string[i..]; + + // Parse the CSV file. + let mut csv = csv::ReaderBuilder::new() + .has_headers(false) + .from_reader(csv_string.as_bytes()) + .records() + .collect::<Result<Vec<_>, _>>() + .with_context(|| format!("failed to parse {:?}", args.csv_path))?; + + // Remove the test student. + csv.retain(|record| &record[0] != "Student, Test"); + + // Collect all the X.500s, checking that they were of the right form. + let x500s = csv + .into_iter() + .map(|record| { + let email = &record[3]; + if let Some(x500) = email.strip_suffix("@umn.edu") { + Ok(x500.to_string()) + } else { + bail!("not a valid UMN email: {:?}", email) + } + }) + .collect::<Result<Vec<_>>>()?; + + // Insert them all in the database. + // + // Looks like sqlx doesn't actually have bulk insert? WTF? + // + // https://github.com/launchbadge/sqlx/issues/294 + let db = &db; + let errors = stream::iter(x500s) + .map(|x500| async move { + sqlx::query!( + "INSERT OR IGNORE INTO student_x500s (x500) VALUES (?)", + x500 + ) + .execute(db) + .await + .context("failed to insert X.500s") + }) + .filter_map(|future| future.map(|r| r.err())) + .collect::<Vec<_>>() + .await; + if !errors.is_empty() { + log::error!("encountered {} errors:", errors.len()); + for error in errors { + log::error!("{:?}", error); + } + bail!("failed to insert X.500s") + } + + // Count the number of X.500s. + let x500_count = sqlx::query!("SELECT COUNT(x500) as count from student_x500s") + .fetch_one(db) + .await + .context("failed to get a count of X.500s")?; + log::info!("We now have {} student X.500s", x500_count.count); + + Ok(()) +} diff --git a/src/main.rs b/src/bin/lambo.rs index c9dbd7a..e3e81a6 100644 --- a/src/main.rs +++ b/src/bin/lambo.rs @@ -1,26 +1,14 @@ -mod handlers; - -use crate::handlers::*; use anyhow::{Context as _, Result}; use clap::{value_parser, ArgAction, Parser}; -use serde::Deserialize; -use serenity::{ - all::{ActivityData, GatewayIntents, GuildMemberUpdateEvent, Member, Ready}, - async_trait, - client::{Context, EventHandler}, - Client, -}; -use std::{fs, path::PathBuf}; +use lambo::{config::Config, handlers::*}; +use serenity::{all::GatewayIntents, Client}; +use sqlx::sqlite::SqlitePoolOptions; +use std::path::PathBuf; use stderrlog::StdErrLog; -#[derive(Debug, Deserialize)] -struct Config { - discord_token: String, -} - #[derive(Debug, Parser)] struct Args { - /// The path to the configuration file. + /// The path to the lambo configuration file. config_path: PathBuf, /// Decreases the log level. @@ -46,6 +34,7 @@ struct Args { #[tokio::main] async fn main() -> Result<()> { + // Parse the arguments. let args = Args::parse(); // Set up logging. @@ -62,23 +51,32 @@ async fn main() -> Result<()> { logger.show_module_names(true).init().unwrap() } - let config_str = fs::read_to_string(&args.config_path) - .with_context(|| format!("failed to read {}", args.config_path.display()))?; - let config: Config = toml::from_str(&config_str) - .with_context(|| format!("failed to parse {}", args.config_path.display()))?; - drop(config_str); + // Parse the config file. + let config = Config::read_from_file(&args.config_path)?; - let handler = MultiHandler(vec![Box::new(PresenceSetter), Box::new(X500Mapper)]); + // Connect to the database. + let db = SqlitePoolOptions::new() + .connect(&config.database_url) + .await + .with_context(|| format!("failed to connect to database at {:?}", config.database_url))?; + + // Run any necessary migrations. + sqlx::migrate!().run(&db).await.with_context(|| { + format!( + "failed to run migrations on database at {:?}", + config.database_url + ) + })?; - let mut client = Client::builder( - &config.discord_token, - GatewayIntents::default() | GatewayIntents::GUILD_MEMBERS, - ) - .event_handler(handler) - .await - .context("failed to create Discord client")?; + // Create the handlers. + let handler = MultiHandler(vec![Box::new(PresenceSetter), Box::new(X500Mapper(db))]); - client + // Start up the client. + let intents = GatewayIntents::default() | GatewayIntents::GUILD_MEMBERS; + Client::builder(&config.discord_token, intents) + .event_handler(handler) + .await + .context("failed to create Discord client")? .start() .await .context("failed to start Discord client")?; diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..14a4c19 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,17 @@ +use anyhow::{Context, Result}; +use serde::Deserialize; +use std::{fs, path::Path}; + +#[derive(Debug, Deserialize)] +pub struct Config { + pub database_url: String, + pub discord_token: String, +} + +impl Config { + pub fn read_from_file(path: &Path) -> Result<Config> { + let config_str = fs::read_to_string(path) + .with_context(|| format!("failed to read {}", path.display()))?; + toml::from_str(&config_str).with_context(|| format!("failed to parse {}", path.display())) + } +} diff --git a/src/handlers/x500_mapper.rs b/src/handlers/x500_mapper.rs index f477e97..1dd8955 100644 --- a/src/handlers/x500_mapper.rs +++ b/src/handlers/x500_mapper.rs @@ -1,22 +1,31 @@ use serenity::{ - all::{GuildMemberUpdateEvent, Member}, + all::{GuildMemberUpdateEvent, Member, UserId}, async_trait, client::{Context, EventHandler}, }; +use sqlx::{Database, Pool}; /// A handler that notices people with an X.500 in their nicknames that matches a student's, and /// records it in the database. -pub struct X500Mapper; +pub struct X500Mapper<DB: Database>(pub Pool<DB>); + +impl<DB: Database> X500Mapper<DB> { + async fn notice_member(&self, nick: &str, uid: UserId) { + dbg!((nick, uid)); + } +} #[async_trait] -impl EventHandler for X500Mapper { +impl<DB: Database> EventHandler for X500Mapper<DB> { async fn guild_member_update( &self, _ctx: Context, - old_if_available: Option<Member>, - new: Option<Member>, + _old_if_available: Option<Member>, + _new: Option<Member>, event: GuildMemberUpdateEvent, ) { - dbg!((old_if_available, new, event)); + if let Some(nick) = event.nick { + self.notice_member(&nick, event.user.id).await + } } } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..61d095d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod config; +pub mod handlers; |