diff --git a/Cargo.lock b/Cargo.lock old mode 100755 new mode 100644 index 2671afc..0db1fa5 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" @@ -111,7 +111,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -122,7 +122,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -142,9 +142,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" @@ -269,7 +269,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.101", + "syn 2.0.103", "which", ] @@ -311,9 +311,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.26" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", @@ -331,9 +331,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -398,14 +398,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cmake" @@ -533,7 +533,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -544,7 +544,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -574,6 +574,26 @@ dependencies = [ "serde", ] +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "digest" version = "0.10.7" @@ -594,7 +614,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -618,7 +638,7 @@ source = "git+https://github.com/allan2/dotenvy#86c0d6dd2938e615135813df9e3274bf dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -627,6 +647,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + [[package]] name = "either" version = "1.15.0" @@ -814,7 +840,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -866,7 +892,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -923,9 +949,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -938,7 +964,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.3", + "hashbrown 0.15.4", ] [[package]] @@ -1251,7 +1277,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "serde", ] @@ -1329,9 +1355,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libloading" @@ -1340,7 +1366,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.0", + "windows-targets 0.53.2", ] [[package]] @@ -1422,9 +1448,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "menv" @@ -1465,9 +1491,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -1479,7 +1505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -1730,12 +1756,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" dependencies = [ "proc-macro2", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1813,9 +1839,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" @@ -1884,13 +1910,33 @@ checksum = "0020ec469b096d56edb1ed0f0f141d957863302170f8d9c4bfda1a12969e5969" [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "regex" version = "1.11.1" @@ -1937,9 +1983,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.19" +version = "0.12.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119" +checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" dependencies = [ "base64", "bytes", @@ -1950,11 +1996,8 @@ dependencies = [ "hyper", "hyper-rustls", "hyper-util", - "ipnet", "js-sys", "log", - "mime", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", @@ -2023,9 +2066,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -2152,6 +2195,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2204,7 +2259,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2243,15 +2298,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", "indexmap 2.9.0", + "schemars", "serde", "serde_derive", "serde_json", @@ -2261,14 +2317,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "81679d9ed988d5e9a5e6531dc3f2c28efbd639cbd1dfb628df08edea6004da77" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2352,12 +2408,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "slack-morphism" @@ -2409,10 +2462,10 @@ version = "0.1.0" dependencies = [ "axum", "clap", + "derive_more", "displaydoc", "dotenvy 0.15.7 (git+https://github.com/allan2/dotenvy)", "error-stack", - "eyre", "http-body-util", "libsqlite3-sys", "menv", @@ -2500,7 +2553,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "hashlink", "indexmap 2.9.0", "log", @@ -2529,7 +2582,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2552,7 +2605,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.101", + "syn 2.0.103", "tokio", "url", ] @@ -2705,9 +2758,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" dependencies = [ "proc-macro2", "quote", @@ -2731,7 +2784,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2760,7 +2813,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2771,17 +2824,16 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -2866,7 +2918,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2980,13 +3032,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3167,9 +3219,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -3208,7 +3260,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "wasm-bindgen-shared", ] @@ -3243,7 +3295,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3351,7 +3403,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3362,14 +3414,14 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-result" @@ -3449,9 +3501,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", @@ -3636,28 +3688,28 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3677,7 +3729,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "synstructure", ] @@ -3717,5 +3769,5 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] diff --git a/Cargo.toml b/Cargo.toml index 9dea266..ecd4b4c 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,10 @@ name = "slack-system-bot" version = "0.1.0" edition = "2024" license = "MIT" +categories = ["command-line-utilities", "web-programming::http-server"] +description = "Bot for Slack workspaces to make the lives for plural systems a bit easier" +repository = "https://github.com/Suya1671/slack-system-bot.git" +keywords = ["plural", "slack-bot", "cli", "web-server"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,7 +20,6 @@ error-stack = { version = "0.5.0", features = [ "serde", "spantrace", ] } -eyre = "0.6.12" http-body-util = "0.1.3" menv = "0.2.7" oauth2 = "5.0.0" @@ -42,6 +45,7 @@ dotenvy = { git = "https://github.com/allan2/dotenvy", features = ["macros"] } url = "2.5.4" serde_json = "1.0.140" tower-http = { version = "0.6.6", features = ["trace"] } +derive_more = { version = "2.0.1", features = ["from"] } [features] encrypt = ["libsqlite3-sys/bundled-sqlcipher"] diff --git a/migrations/20250618124409_member_aliasing.sql b/migrations/20250618124409_member_aliasing.sql new file mode 100644 index 0000000..bee6c13 --- /dev/null +++ b/migrations/20250618124409_member_aliasing.sql @@ -0,0 +1,11 @@ +-- Add migration script here +CREATE TABLE aliases ( + id INTEGER NOT NULL PRIMARY KEY, + system_id INTEGER NOT NULL REFERENCES systems (id), + member_id INTEGER NOT NULL REFERENCES members (id), + alias TEXT NOT NULL, + -- A system cannot have multiple aliases that are the same + UNIQUE (system_id, alias), + -- A member cannot have multiple aliases that are the same + UNIQUE (member_id, alias) +) STRICT; diff --git a/src/commands/alias.rs b/src/commands/alias.rs new file mode 100644 index 0000000..341fb6f --- /dev/null +++ b/src/commands/alias.rs @@ -0,0 +1,212 @@ +use error_stack::{Result, ResultExt}; +use slack_morphism::prelude::*; +use tracing::debug; + +use crate::{ + fetch_member, fetch_system, + models::{self, Untrusted, alias, member::MemberRef, user}, +}; + +#[derive(clap::Subcommand, Debug)] +pub enum Alias { + /// Adds a new alias for a member. + Add { + /// The member to add the alias for. Use either an existing alias or member ID + member: MemberRef, + /// The alias to add. Must be unique for the system. Cannot be just a number + alias: String, + }, + /// Deletes an alias + Delete { + /// The alias to delete. Use the alias ID from /alias list + alias: alias::Id, + }, + /// Lists all of your systems aliases + List { + /// If specified, lists the aliases for the given member. + member: Option, + }, + /// Edit an alias + Edit { + /// The alias to edit. Use the alias ID from /alias list + alias: alias::Id, + /// The new alias to set. Must be unique for the system. Cannot be just a number + new_alias: String, + }, +} + +#[derive(thiserror::Error, displaydoc::Display, Debug)] +pub enum CommandError { + /// Error while calling the database + Sqlx, +} + +impl Alias { + #[tracing::instrument(skip_all)] + pub async fn run( + self, + event: SlackCommandEvent, + state: SlackClientEventsUserState, + ) -> Result { + match self { + Self::Add { member, alias } => Self::create_alias(event, &state, member, alias).await, + Self::Delete { alias } => Self::delete_alias(event, &state, alias).await, + Self::List { member } => Self::list_aliases(event, &state, member).await, + Self::Edit { alias, new_alias } => { + Self::edit_alias(event, &state, alias, new_alias).await + } + } + } + + #[tracing::instrument(skip(event, state), fields(system_id))] + async fn create_alias( + event: SlackCommandEvent, + state: &SlackClientEventsUserState, + member: MemberRef, + alias: String, + ) -> Result { + debug!("Creating alias"); + let states = state.read().await; + let user_state = states.get_user_state::().unwrap(); + + fetch_system!(event, user_state => system_id); + + fetch_member!(member, user_state, system_id => member_id); + + if alias.parse::().is_ok() { + return Ok(SlackCommandEventResponse::new( + SlackMessageContent::new().with_text( + "Alias cannot be a valid integer, as it could be mistaken for a member ID." + .to_string(), + ), + )); + } + + models::Alias::insert(member_id, system_id, alias, &user_state.db) + .await + .change_context(CommandError::Sqlx)?; + + Ok(SlackCommandEventResponse::new( + SlackMessageContent::new().with_text("Alias created successfully.".to_string()), + )) + } + + #[tracing::instrument(skip(event, state), fields(system_id))] + async fn delete_alias( + event: SlackCommandEvent, + state: &SlackClientEventsUserState, + alias: alias::Id, + ) -> Result { + debug!("Deleting alias"); + let states = state.read().await; + let user_state = states.get_user_state::().unwrap(); + + fetch_system!(event, user_state => system_id); + + let Some(alias) = alias + .validate_by_system(system_id, &user_state.db) + .await + .change_context(CommandError::Sqlx)? + else { + return Ok(SlackCommandEventResponse::new( + SlackMessageContent::new().with_text("Alias not found.".to_string()), + )); + }; + + alias + .delete(&user_state.db) + .await + .change_context(CommandError::Sqlx)?; + + Ok(SlackCommandEventResponse::new( + SlackMessageContent::new().with_text("Alias deleted successfully.".to_string()), + )) + } + + #[tracing::instrument(skip(event, state), fields(system_id))] + async fn list_aliases( + event: SlackCommandEvent, + state: &SlackClientEventsUserState, + member: Option, + ) -> Result { + debug!("Listing aliases"); + let states = state.read().await; + let user_state = states.get_user_state::().unwrap(); + + fetch_system!(event, user_state => system_id); + + let aliases = if let Some(member) = member { + debug!("Fetching aliases by member"); + fetch_member!(member, user_state, system_id => member_id); + + models::Alias::fetch_by_member_id(member_id, &user_state.db) + .await + .change_context(CommandError::Sqlx)? + } else { + models::Alias::fetch_by_system_id(system_id, &user_state.db) + .await + .change_context(CommandError::Sqlx)? + }; + + if aliases.is_empty() { + debug!("No aliases found"); + return Ok(SlackCommandEventResponse::new( + SlackMessageContent::new().with_text("No aliases found.".into()), + )); + } + + debug!(len = aliases.len(), "Found aliases"); + + let alias_blocks = aliases + .into_iter() + .map(|alias| { + let fields = vec![ + md!("Member ID: {}", alias.member_id), + md!("Alias: {}", alias.alias), + ]; + + SlackSectionBlock::new() + .with_text(md!("*Alias {}*", alias.id)) + .with_fields(fields) + }) + .map(Into::into) + .collect(); + + Ok(SlackCommandEventResponse::new( + SlackMessageContent::new().with_blocks(alias_blocks), + )) + } + + #[tracing::instrument(skip(event, state), fields(system_id))] + async fn edit_alias( + event: SlackCommandEvent, + state: &SlackClientEventsUserState, + alias: alias::Id, + new_alias: String, + ) -> Result { + debug!("Editing alias"); + let states = state.read().await; + let user_state = states.get_user_state::().unwrap(); + + fetch_system!(event, user_state => system_id); + + let Some(alias) = alias + .validate_by_system(system_id, &user_state.db) + .await + .change_context(CommandError::Sqlx)? + else { + return Ok(SlackCommandEventResponse::new( + SlackMessageContent::new().with_text("Alias not found.".to_string()), + )); + }; + + alias + .change_alias(new_alias, &user_state.db) + .await + .change_context(CommandError::Sqlx)?; + + Ok(SlackCommandEventResponse::new( + SlackMessageContent::new().with_text("Alias updated successfully.".to_string()), + )) + } +} diff --git a/src/commands/members.rs b/src/commands/member.rs similarity index 71% rename from src/commands/members.rs rename to src/commands/member.rs index 7f529ae..274679e 100755 --- a/src/commands/members.rs +++ b/src/commands/member.rs @@ -1,33 +1,31 @@ use std::sync::Arc; -use error_stack::{Result, ResultExt, report}; +use error_stack::{Result, ResultExt}; use slack_morphism::prelude::*; use tracing::{debug, info, trace}; use crate::{ - BOT_TOKEN, - commands::members, - fields, + BOT_TOKEN, fetch_member, fetch_system, fields, models::{ - member::{self, Member, View}, - system::{ChangeActiveMemberError, System}, + self, + member::{self, MemberRef, View}, user, }, }; #[derive(clap::Subcommand, Debug)] -pub enum Members { +pub enum Member { /// Adds a new member to your system. Expect a popup to fill in the member info! Add, - /// Deletes a member from your system. Use the member id from /member list + /// Deletes a member from your system. Delete { /// The member to delete - member: i64, + member: MemberRef, }, /// Gets info about a member Info { - /// The member to get info about. Use the member id from /member list - member_id: i64, + /// The member to get info about. + member_id: MemberRef, }, /// Lists all members in a system List { @@ -36,15 +34,15 @@ pub enum Members { }, /// Edits a member's info Edit { - /// The member to edit. Use the member id from /member list. Expect a popup to edit the info! - member_id: i64, + /// The member to edit. Expect a popup to edit the info! + member_id: MemberRef, }, /// Switch to a different member #[group(required = true)] Switch { - /// The member to switch to. Use the member id from /member list + /// The member to switch to. #[clap(group = "member")] - member_id: Option, + member_id: Option, /// Don't switch to another member, just message with the base account #[clap(long, short, action, group = "member", alias = "none")] base: bool, @@ -59,7 +57,7 @@ pub enum CommandError { Sqlx, } -impl Members { +impl Member { #[tracing::instrument(skip_all)] pub async fn run( self, @@ -75,7 +73,7 @@ impl Members { Self::create_member(event, session).await } Self::Delete { member } => { - debug!(member_id = member, "Delete member command not implemented"); + debug!(member_id = ?member, "Delete member command not implemented"); Ok(SlackCommandEventResponse::new( SlackMessageContent::new().with_text("Working on it".into()), )) @@ -95,42 +93,26 @@ impl Members { async fn switch_member( event: SlackCommandEvent, state: SlackClientEventsUserState, - member_id: Option, + member_ref: Option, base: bool, ) -> Result { trace!("Switching member"); let states = state.read().await; let user_state = states.get_user_state::().unwrap(); - let Some(mut system) = System::fetch_by_user_id(&user_state.db, &event.user_id.into()) - .await - .change_context(CommandError::Sqlx)? - else { - debug!("User has no system configured"); - return Ok(SlackCommandEventResponse::new( - SlackMessageContent::new().with_text("You don't have a system yet!".into()), - )); - }; - - fields!(system_id = %system.id); - debug!("Found user system"); + fetch_system!(event, user_state => system_id); let new_active_member_id = if base { None } else { - let member_id = - member_id.expect("member_id to be Some, as the clap rules require it to be."); - debug!(requested_member_id = member_id, "Validating member ID"); - - member::Id::new(member_id) - .validate_by_system(system.id, &user_state.db) - .await - .ok() + debug!(requested_member_id = ?&member_ref, "Validating member ID"); + fetch_member!(member_ref.as_ref().unwrap(), user_state, system_id => member_id); + Some(member_id) }; debug!(target_member_id = ?new_active_member_id, "Changing active member"); - let new_member = system + let new_member = system_id .change_active_member(new_active_member_id, &user_state.db) .await; @@ -143,13 +125,7 @@ impl Members { info!("Successfully switched to base account"); "Switched to base account".into() } - Err(ChangeActiveMemberError::MemberNotFound) => { - debug!("Requested member not found in system"); - "The member you gave doesn't exist!".into() - } - Err(ChangeActiveMemberError::Sqlx(err)) => { - return Err(report!(err).change_context(CommandError::Sqlx)); - } + Err(e) => return Err(e.change_context(CommandError::Sqlx)), }; Ok(SlackCommandEventResponse::new( @@ -183,7 +159,7 @@ impl Members { fields!(user_id = %user_id.clone()); - let Some(system) = System::fetch_by_user_id(&user_state.db, &user_id) + let Some(system) = models::System::fetch_by_user_id(&user_state.db, &user_id) .await .change_context(CommandError::Sqlx)? else { @@ -212,8 +188,8 @@ impl Members { .into_iter() .map(|member| { let fields = [ - Some(md!("Display Name: {}", member.display_name)), Some(md!("Member ID: {}", member.id)), + Some(md!("Display Name: {}", member.display_name)), member.title.as_ref().map(|title| md!("Title: {}", title)), member .pronouns @@ -247,46 +223,26 @@ impl Members { async fn member_info( event: SlackCommandEvent, state: &SlackClientEventsUserState, - member_id: i64, + member_ref: MemberRef, ) -> Result { trace!("Running member info command"); let states = state.read().await; let user_state = states.get_user_state::().unwrap(); - let member_id = member::Id::new(member_id); - let Some(system_id) = System::fetch_by_user_id(&user_state.db, &event.user_id.into()) + fetch_system!(event, user_state => system_id); + + fetch_member!(member_ref, user_state, system_id => member_id); + + let member = models::Member::fetch_by_id(member_id, &user_state.db) .await - .change_context(CommandError::Sqlx)? - .map(|system| system.id) - else { - debug!("User has no system configured"); - return Ok(SlackCommandEventResponse::new( - SlackMessageContent::new().with_text( - "You don't have a system yet! Make one with `/system create `".into(), - ), - )); - }; + .change_context(CommandError::Sqlx)?; - fields!(system_id = %system_id); - - let Some(member) = Member::fetch_by_and_trust_id(system_id, member_id, &user_state.db) - .await - .change_context(CommandError::Sqlx)? - else { - debug!("Member not found"); - return Ok(SlackCommandEventResponse::new( - SlackMessageContent::new() - .with_text("Member not found. Make sure you used the correct ID".into()), - )); - }; - - fields!(member_id = %member.id); debug!("Member found"); let fields = [ - Some(md!("Display Name: {}", member.display_name)), Some(md!("Member ID: {}", member.id)), + Some(md!("Display Name: {}", member.display_name)), member.title.as_ref().map(|title| md!("Title: {}", title)), member .pronouns @@ -336,41 +292,22 @@ impl Members { event: SlackCommandEvent, session: SlackClientSession<'_, SlackClientHyperHttpsConnector>, state: &SlackClientEventsUserState, - member_id: i64, + member_ref: MemberRef, ) -> Result { trace!("Running member edit command"); let states = state.read().await; let user_state = states.get_user_state::().unwrap(); - let user_id = user::Id::new(event.user_id); - let member_id = member::Id::new(member_id); - let Some(system_id) = System::fetch_by_user_id(&user_state.db, &user_id) + fetch_system!(event, user_state => system_id); + + fetch_member!(member_ref, user_state, system_id => member_id); + + let member = models::Member::fetch_by_id(member_id, &user_state.db) .await - .change_context(CommandError::Sqlx)? - .map(|system| system.id) - else { - debug!("User has no system configured"); - return Ok(SlackCommandEventResponse::new( - SlackMessageContent::new().with_text( - "You don't have a system yet! Make one with `/system create `".into(), - ), - )); - }; + .change_context(CommandError::Sqlx)?; - let Some(member) = Member::fetch_by_and_trust_id(system_id, member_id, &user_state.db) - .await - .change_context(CommandError::Sqlx)? - else { - return Ok(SlackCommandEventResponse::new( - SlackMessageContent::new() - .with_text("Member not found. Make sure you used the correct ID".into()), - )); - }; - - let member_id = member.id; - - let view = members::View::from(member).create_edit_view(member_id); + let view = member::View::from(member).create_edit_view(member_id); let view = session .views_open(&SlackApiViewsOpenRequest::new( @@ -386,3 +323,28 @@ impl Members { Ok(SlackCommandEventResponse::new(SlackMessageContent::new())) } } + +#[macro_export] +/// Fetches the member ID associated with the +/// Also attaches the member ID to context +/// +/// Else, returns early with a warning message +macro_rules! fetch_member { + ($member_ref:expr, $user_state:expr, $system_id:expr => $member_var_name:ident) => { + let Some($member_var_name) = $member_ref + .validate_by_system($system_id, &$user_state.db) + .await + .change_context(CommandError::Sqlx)? + else { + use slack_morphism::prelude::*; + ::tracing::debug!("User does not have a member with alias {:?} that is associated with the system", $member_ref); + return Ok(SlackCommandEventResponse::new( + SlackMessageContent::new() + .with_text("The member does not exist! Make sure you spelt the alias correctly or used the correct ID.".to_string()), + )); + }; + + $crate::fields!(member_id = %$member_var_name); + ::tracing::debug!("Fetched member"); + }; +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index a3bb97c..ac53ee8 100755 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,17 +1,20 @@ use std::sync::Arc; -mod members; +mod alias; +mod member; mod system; -mod triggers; +mod trigger; + +use alias::Alias; use axum::{Extension, Json}; use clap::{Parser, error::ErrorKind}; use error_stack::ResultExt; -use members::Members; - use slack_morphism::prelude::*; -use system::System; use tracing::{Level, debug, error, trace}; -use triggers::Triggers; + +use member::Member; +use system::System; +use trigger::Trigger; use crate::fields; @@ -19,11 +22,13 @@ use crate::fields; #[command(color(clap::ColorChoice::Never))] enum Command { #[clap(subcommand)] - Members(Members), + Members(Member), #[clap(subcommand)] System(System), #[clap(subcommand)] - Triggers(Triggers), + Triggers(Trigger), + #[clap(subcommand)] + Aliases(Alias), } impl Command { @@ -47,6 +52,10 @@ impl Command { .run(event, client, state) .await .change_context(CommandError::Triggers), + Self::Aliases(aliases) => aliases + .run(event, state) + .await + .change_context(CommandError::Aliases), } } } @@ -59,6 +68,8 @@ enum CommandError { Triggers, /// Error running the system command System, + /// Error running the aliases command + Aliases, } // TO-DO: figure out error handling diff --git a/src/commands/system.rs b/src/commands/system.rs index 8bf95c7..dd74b4b 100755 --- a/src/commands/system.rs +++ b/src/commands/system.rs @@ -8,7 +8,7 @@ use tracing::{debug, trace}; use crate::{ fields, - models::{system, user}, + models::{self, user}, oauth::create_oauth_client, }; @@ -88,7 +88,7 @@ impl System { fields!(user_id = %&user_id); trace!("Mapped user ID"); - let system = system::System::fetch_by_user_id(&user_state.db, &user_id) + let system = models::System::fetch_by_user_id(&user_state.db, &user_id) .await .change_context(CommandError::Sqlx)?; @@ -137,7 +137,7 @@ impl System { let user_state = states.get_user_state::().unwrap(); let Some(system_id) = - system::System::fetch_by_user_id(&user_state.db, &event.user_id.into()) + models::System::fetch_by_user_id(&user_state.db, &event.user_id.into()) .await .change_context(CommandError::Sqlx)? .map(|s| s.id) @@ -175,7 +175,7 @@ impl System { let user_state = states.get_user_state::().unwrap(); let user_id = user::Id::new(event.user_id); - if let Some(system) = system::System::fetch_by_user_id(&user_state.db, &user_id) + if let Some(system) = models::System::fetch_by_user_id(&user_state.db, &user_id) .await .change_context(CommandError::Sqlx)? { @@ -219,3 +219,32 @@ impl System { )) } } + +#[macro_export] +/// Fetches the system ID associated with the user who triggered the command. +/// Also attaches the system ID to context +/// +/// Else, returns early with a warning message +macro_rules! fetch_system { + ($event:expr, $user_state:expr => $system_var_name:ident) => { + let Some($system_var_name) = $crate::models::System::fetch_by_user_id( + &$user_state.db, + &$crate::models::user::Id::new($event.user_id), + ) + .await + .change_context(CommandError::Sqlx)? + .map(|system| system.id) else { + use slack_morphism::prelude::*; + + ::tracing::debug!("User does not have a system"); + return Ok(SlackCommandEventResponse::new( + SlackMessageContent::new().with_text( + "You don't have a system yet! Make one with `/system create `".into(), + ), + )); + }; + + $crate::fields!(system_id = %$system_var_name); + ::tracing::debug!("Fetched system"); + }; +} diff --git a/src/commands/triggers.rs b/src/commands/trigger.rs similarity index 69% rename from src/commands/triggers.rs rename to src/commands/trigger.rs index 6556fb9..1b4b8b2 100755 --- a/src/commands/triggers.rs +++ b/src/commands/trigger.rs @@ -5,35 +5,31 @@ use slack_morphism::prelude::*; use tracing::debug; use crate::{ - BOT_TOKEN, fields, - models::{ - member::{self, Member}, - system::System, - trigger, user, - }, + BOT_TOKEN, fetch_member, fetch_system, fields, + models::{self, Untrusted, member::MemberRef, trigger, user}, }; #[derive(clap::Subcommand, Debug)] -pub enum Triggers { +pub enum Trigger { /// Adds a new trigger for a member. Expect a popup to fill in the info! Add { - /// The member to add the trigger for. Use the member id from /member list - member: i64, + /// The member to add the trigger for. + member: MemberRef, }, /// Deletes a trigger Delete { - /// The trigger to delete. Use the trigger id from /trigger list - id: i64, + /// The trigger to delete. + id: trigger::Id, }, /// Lists all of your triggers List { - /// If specified, lists the triggers for the given member. Use the member id from /member list - member: Option, + /// If specified, lists the triggers for the given member. + member: Option, }, /// Edit a trigger Edit { /// The trigger to edit. Use the trigger id from /trigger list - id: i64, + id: trigger::Id, }, } @@ -45,7 +41,7 @@ pub enum CommandError { Sqlx, } -impl Triggers { +impl Trigger { #[tracing::instrument(skip_all)] pub async fn run( self, @@ -74,32 +70,17 @@ impl Triggers { event: SlackCommandEvent, state: &SlackClientEventsUserState, session: SlackClientSession<'_, SlackClientHyperHttpsConnector>, - member_id: i64, + member_id: MemberRef, ) -> Result { let states = state.read().await; let user_state = states.get_user_state::().unwrap(); - let member_id = member::Id::new(member_id); - let Some(system_id) = - System::fetch_by_user_id(&user_state.db, &user::Id::new(event.user_id)) - .await - .change_context(CommandError::Sqlx)? - .map(|system| system.id) - else { - debug!("User does not have a system"); - return Ok(SlackCommandEventResponse::new( - SlackMessageContent::new().with_text( - "You don't have a system yet! Make one with `/system create `".into(), - ), - )); - }; + fetch_system!(event, user_state => system_id); - fields!(system_id = %system_id); - - let Some(member_id) = Member::fetch_by_and_trust_id(system_id, member_id, &user_state.db) + let Some(member_id) = member_id + .validate_by_system(system_id, &user_state.db) .await .change_context(CommandError::Sqlx)? - .map(|member| member.id) else { debug!("Member not found"); return Ok(SlackCommandEventResponse::new( @@ -129,14 +110,13 @@ impl Triggers { pub async fn delete_trigger( event: SlackCommandEvent, state: &SlackClientEventsUserState, - trigger_id: i64, + trigger_id: trigger::Id, ) -> Result { let states = state.read().await; let user_state = states.get_user_state::().unwrap(); - let trigger_id = trigger::Id::new(trigger_id); let Some(system_id) = - System::fetch_by_user_id(&user_state.db, &user::Id::new(event.user_id)) + models::System::fetch_by_user_id(&user_state.db, &user::Id::new(event.user_id)) .await .change_context(CommandError::Sqlx)? .map(|system| system.id) @@ -177,43 +157,15 @@ impl Triggers { pub async fn list_triggers( event: SlackCommandEvent, state: &SlackClientEventsUserState, - member_id: Option, + member_ref: Option, ) -> Result { let states = state.read().await; let user_state = states.get_user_state::().unwrap(); - let Some(system_id) = - System::fetch_by_user_id(&user_state.db, &user::Id::new(event.user_id)) - .await - .change_context(CommandError::Sqlx)? - .map(|system| system.id) - else { - debug!("User doesn't have a system"); - return Ok(SlackCommandEventResponse::new( - SlackMessageContent::new().with_text( - "You don't have a system yet! Make one with `/system create `".into(), - ), - )); - }; + fetch_system!(event, user_state => system_id); - fields!(system_id = %system_id); - - let triggers = if let Some(member_id) = member_id { - let member_id = member::Id::new(member_id); - - // Validate the member belongs to the user's system - let Ok(member_id) = member_id - .validate_by_system(system_id, &user_state.db) - .await - else { - debug!("Member not found"); - return Ok(SlackCommandEventResponse::new( - SlackMessageContent::new() - .with_text("Member not found. Make sure you used the correct ID".into()), - )); - }; - - fields!(member_id = %member_id); + let triggers = if let Some(member_ref) = member_ref { + fetch_member!(member_ref, user_state, system_id => member_id); member_id .fetch_triggers(&user_state.db) @@ -261,27 +213,12 @@ impl Triggers { event: SlackCommandEvent, state: &SlackClientEventsUserState, session: SlackClientSession<'_, SlackClientHyperHttpsConnector>, - trigger_id: i64, + trigger_id: trigger::Id, ) -> Result { let states = state.read().await; let user_state = states.get_user_state::().unwrap(); - let trigger_id = trigger::Id::new(trigger_id); - let Some(system_id) = - System::fetch_by_user_id(&user_state.db, &user::Id::new(event.user_id)) - .await - .change_context(CommandError::Sqlx)? - .map(|system| system.id) - else { - debug!("User does not have a system"); - return Ok(SlackCommandEventResponse::new( - SlackMessageContent::new().with_text( - "You don't have a system yet! Make one with `/system create `".into(), - ), - )); - }; - - fields!(system_id = %system_id); + fetch_system!(event, user_state => system_id); // Validate the trigger belongs to the user's system let Ok(trigger_id) = trigger_id @@ -297,7 +234,7 @@ impl Triggers { fields!(trigger_id = %trigger_id); // Fetch the trigger to edit - let trigger = trigger::Trigger::fetch_by_id(trigger_id, &user_state.db) + let trigger = models::Trigger::fetch_by_id(trigger_id, &user_state.db) .await .change_context(CommandError::Sqlx)?; diff --git a/src/events/mod.rs b/src/events/mod.rs index c511dc8..c0b9a24 100755 --- a/src/events/mod.rs +++ b/src/events/mod.rs @@ -9,13 +9,7 @@ use tracing::{debug, error, trace}; use crate::{ BOT_TOKEN, fields, - models::{ - member::{Member, TriggeredMember}, - message::MessageLog, - system::System, - trigger::Type, - user, - }, + models::{self, trigger, user}, }; #[derive(thiserror::Error, displaydoc::Display, Debug)] @@ -58,7 +52,9 @@ pub async fn process_push_event( SlackPushEvent::EventCallback(event) => { let client = environment.client.clone(); let state = environment.user_state.clone(); - if let Err(e) = push_event_callback(event, client, state).await { + // https://rust-lang.github.io/rust-clippy/master/index.html#large_futures + // Into the box you go + if let Err(e) = Box::pin(push_event_callback(event, client, state)).await { error!("Error processing push event: {:#?}", e); } @@ -84,14 +80,21 @@ async fn push_event_callback( .as_ref() .is_some_and(|subtype| *subtype == SlackMessageEventType::MessageDeleted) => { - fields!(event_type = ?SlackMessageEventType::MessageDeleted); + fields!(event_type = ?SlackMessageEventType::MessageDeleted, message_id = ?&message_event.deleted_ts, user = ?message_event.sender); let states = state.read().await; let user_state = states.get_user_state::().unwrap(); - MessageLog::delete_by_message_id(message_event.deleted_ts.unwrap().0, &user_state.db) - .await - .change_context(PushEventError::SlackApi) - .attach_printable("Failed to delete message log") + models::MessageLog::delete_by_message_id( + message_event.deleted_ts.unwrap().0, + &user_state.db, + ) + .await + .change_context(PushEventError::SlackApi) + .attach_printable("Failed to delete message log") + .map(|_| ())?; + + debug!("Message log deleted"); + Ok(()) } SlackEventCallbackBody::Message(message_event) if message_event.subtype.is_none() @@ -125,7 +128,7 @@ async fn handle_message( fields!(user_id = ?&user_id); - let Some(mut system) = System::fetch_by_user_id(&user_state.db, &user_id) + let Some(mut system) = models::System::fetch_by_user_id(&user_state.db, &user_id) .await .change_context(PushEventError::SystemFetch)? else { @@ -185,13 +188,9 @@ async fn handle_message( // No triggers ran, so check if there's any actively fronting member if let Some(member_id) = system.active_member_id { fields!(member = %&member_id); - let Some(member) = Member::fetch_by_id(member_id, &user_state.db) + let member = models::Member::fetch_by_id(member_id, &user_state.db) .await - .change_context(PushEventError::MemberFetch)? - else { - error!("Active member not found. This should not happen."); - return Ok(()); - }; + .change_context(PushEventError::MemberFetch)?; fields!(member = ?&member); rewrite_message( @@ -216,8 +215,8 @@ async fn rewrite_message( channel_id: &SlackChannelId, message_id: SlackTs, mut content: SlackMessageContent, - member: TriggeredMember, - system: &System, + member: models::TriggeredMember, + system: &models::System, db: &SqlitePool, ) -> error_stack::Result<(), RewriteMessageError> { let token = SlackApiToken::new(system.slack_oauth_token.expose().into()) @@ -303,7 +302,7 @@ async fn rewrite_message( .await .change_context(RewriteMessageError::PostMessage)?; - MessageLog::insert(member.id, res.ts, db) + models::MessageLog::insert(member.id, res.ts, db) .await .change_context(RewriteMessageError::MessageLog)?; @@ -317,17 +316,17 @@ async fn rewrite_message( Ok(()) } -fn rewrite_content(content: &mut SlackMessageContent, member: &TriggeredMember) { +fn rewrite_content(content: &mut SlackMessageContent, member: &models::TriggeredMember) { debug!("Rewriting message content"); if let Some(text) = &mut content.text { match member.typ { - Type::Prefix => { + trigger::Type::Prefix => { if let Some(new_text) = text.strip_prefix(&member.trigger_text) { *text = new_text.to_string(); } } - Type::Suffix => { + trigger::Type::Suffix => { if let Some(new_text) = text.strip_suffix(&member.trigger_text) { *text = new_text.to_string(); } @@ -344,7 +343,7 @@ fn rewrite_content(content: &mut SlackMessageContent, member: &TriggeredMember) let first = elements.get_mut(0).unwrap(); if let Some(first_text) = first.pointer_mut("/elements/0/text") { - if member.typ == Type::Prefix { + if member.typ == trigger::Type::Prefix { if let Some(new_text) = first_text .as_str() .and_then(|text| text.strip_prefix(&member.trigger_text)) @@ -358,7 +357,7 @@ fn rewrite_content(content: &mut SlackMessageContent, member: &TriggeredMember) let last = elements.get_mut(len - 1).unwrap(); if let Some(last_text) = last.pointer_mut("/elements/0/text") { - if member.typ == Type::Suffix { + if member.typ == trigger::Type::Suffix { if let Some(new_text) = last_text .as_str() .and_then(|text| text.strip_suffix(&member.trigger_text)) diff --git a/src/interactions/mod.rs b/src/interactions/mod.rs index 33fcbe5..89e5cd7 100755 --- a/src/interactions/mod.rs +++ b/src/interactions/mod.rs @@ -114,7 +114,8 @@ async fn handle_modal_view( return Ok(()); }; - let Ok(trusted_member_id) = member_id.validate_by_user(&user_id, &user_state.db).await + let Some(trusted_member_id) = + member_id.validate_by_user(&user_id, &user_state.db).await? else { error!( id, @@ -137,7 +138,8 @@ async fn handle_modal_view( .map(models::member::Id::new) .expect("Failed to parse member id from external id"); - let Ok(trusted_member_id) = member_id.validate_by_user(&user_id, &user_state.db).await + let Some(trusted_member_id) = + member_id.validate_by_user(&user_id, &user_state.db).await? else { error!( id, diff --git a/src/main.rs b/src/main.rs index dd688cd..742cfd2 100755 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #![doc = include_str!("../README.md")] -#![warn(clippy::pedantic, clippy::nursery, missing_docs)] +#![warn(clippy::pedantic, clippy::nursery, missing_docs, clippy::cargo)] +#![allow(clippy::multiple_crate_versions)] mod commands; mod env; diff --git a/src/models/alias.rs b/src/models/alias.rs new file mode 100644 index 0000000..9486258 --- /dev/null +++ b/src/models/alias.rs @@ -0,0 +1,159 @@ +use crate::id; + +use super::{Trusted, Untrusted, member, system}; +use error_stack::{Result, ResultExt}; +use sqlx::{SqlitePool, prelude::*, sqlite::SqliteQueryResult}; + +id!( + /// For an ID to be trusted, it must + /// + /// - Be a valid ID in the database + /// - Be associated with a valid member and system + => Alias +); + +impl Id { + #[tracing::instrument(skip(db))] + pub async fn validate_by_system( + self, + system_id: system::Id, + db: &SqlitePool, + ) -> Result>, sqlx::Error> { + sqlx::query!( + "SELECT + id as 'id: Id' + FROM aliases + WHERE id = $1 AND system_id = $2", + self.id, + system_id.id + ) + .fetch_optional(db) + .await + .map(|res| res.map(|res| res.id)) + .attach_printable("Failed to fetch alias id from database") + } +} + +impl Id { + #[tracing::instrument(skip(db))] + pub async fn delete(self, db: &SqlitePool) -> Result { + sqlx::query!( + r#" + DELETE FROM aliases + WHERE id = $1 + "#, + self.id + ) + .execute(db) + .await + .attach_printable("Failed to delete alias from database") + } + + #[tracing::instrument(skip(db))] + pub async fn change_alias( + self, + new_alias: String, + db: &SqlitePool, + ) -> Result { + sqlx::query!( + r#" + UPDATE aliases + SET alias = $2 + WHERE id = $1 + "#, + self.id, + new_alias + ) + .execute(db) + .await + .attach_printable("Failed to change alias in database") + } +} + +#[derive(FromRow, Debug)] +#[allow(dead_code)] +pub struct Alias { + pub id: Id, + pub member_id: member::Id, + pub system_id: system::Id, + #[allow(clippy::struct_field_names)] + pub alias: String, +} + +impl Alias { + #[tracing::instrument(skip(db))] + pub async fn fetch_by_system_id( + system_id: system::Id, + db: &SqlitePool, + ) -> Result, sqlx::Error> { + sqlx::query_as!( + Alias, + r#" + SELECT + id as "id: Id", + member_id as "member_id: member::Id", + system_id as "system_id: system::Id", + alias + FROM + aliases + WHERE + system_id = $1 + "#, + system_id + ) + .fetch_all(db) + .await + .attach_printable("Failed to fetch aliases from database") + } + + #[tracing::instrument(skip(db))] + pub async fn fetch_by_member_id( + member_id: member::Id, + db: &SqlitePool, + ) -> error_stack::Result, sqlx::Error> { + sqlx::query_as!( + Self, + r#" + SELECT + id as "id: Id", + member_id as "member_id: member::Id", + system_id as "system_id: system::Id", + alias + FROM + aliases + WHERE member_id = $1 + "#, + member_id, + ) + .fetch_all(db) + .await + .attach_printable("Failed to fetch aliases from database") + } + + #[tracing::instrument(skip(db))] + pub async fn insert( + member_id: member::Id, + system_id: system::Id, + alias: String, + db: &SqlitePool, + ) -> error_stack::Result { + sqlx::query_as!( + Self, + r#" + INSERT INTO aliases (member_id, system_id, alias) + VALUES ($1, $2, $3) + RETURNING + id as "id: Id", + member_id as "member_id: member::Id", + system_id as "system_id: system::Id", + alias + "#, + member_id, + system_id, + alias, + ) + .fetch_one(db) + .await + .attach_printable("Failed to insert alias into database") + } +} diff --git a/src/models/member.rs b/src/models/member.rs index db85464..48137fc 100755 --- a/src/models/member.rs +++ b/src/models/member.rs @@ -1,4 +1,6 @@ -use error_stack::ResultExt; +use std::{convert::Infallible, str::FromStr}; + +use error_stack::{Result, ResultExt}; use slack_morphism::prelude::*; use sqlx::{SqlitePool, prelude::*, sqlite::SqliteQueryResult}; use tracing::{debug, warn}; @@ -7,18 +9,10 @@ use crate::id; use super::{ Trusted, Untrusted, system, - trigger::{self, Trigger, Type}, + trigger::{Trigger, Type}, user, }; -#[derive(thiserror::Error, displaydoc::Display, Debug)] -pub enum Error { - /// Error while calling the database - Sqlx, - /// A field was missing from the view - MissingField(String), -} - id!( /// For an ID to be trusted, it must /// @@ -35,68 +29,112 @@ impl Id { } } + #[tracing::instrument(skip(db))] pub async fn validate_by_system( self, system_id: system::Id, db: &SqlitePool, - ) -> Result, Self> { - let exists = sqlx::query!( - "SELECT EXISTS(SELECT 1 FROM members WHERE id = $1 AND system_id = $2) AS 'exists: bool'", + ) -> Result>, sqlx::Error> { + sqlx::query!( + "SELECT + id as 'id: Id' + FROM members + WHERE id = $1 AND system_id = $2", self.id, system_id.id ) - .fetch_one(db) + .fetch_optional(db) .await - .ok() - .is_some_and(|record| record.exists); - - if exists { - Ok(Id { - id: self.id, - trusted: std::marker::PhantomData, - }) - } else { - Err(self) - } + .attach_printable("Failed to validate member by system") + .map(|res| res.map(|res| res.id)) } + #[tracing::instrument(skip(db))] pub async fn validate_by_user( self, user_id: &user::Id, db: &SqlitePool, - ) -> Result, Self> { - let exists = sqlx::query!( - "SELECT EXISTS( - SELECT 1 + ) -> Result>, sqlx::Error> { + sqlx::query!( + " + SELECT + members.id as 'id: Id' FROM members JOIN systems ON members.system_id = systems.id WHERE members.id = $1 AND systems.owner_id = $2 - ) AS 'exists: bool'", + ", self.id, user_id ) - .fetch_one(db) + .fetch_optional(db) .await - .ok() - .is_some_and(|record| record.exists); + .attach_printable("Failed to validate member by user") + .map(|res| res.map(|res| res.id)) + } - if exists { - Ok(Id { - id: self.id, - trusted: std::marker::PhantomData, - }) - } else { - Err(self) - } + #[tracing::instrument(skip(db))] + pub async fn fetch_by_alias( + alias: &str, + system_id: system::Id, + db: &SqlitePool, + ) -> Result>, sqlx::Error> { + sqlx::query!( + "SELECT + member_id AS 'id: Id' + FROM aliases + WHERE alias = $1 AND system_id = $2", + alias, + system_id + ) + .fetch_optional(db) + .await + .attach_printable("Failed to fetch member id by alias") + .map(|res| res.map(|res| res.id)) } } impl Id { - pub async fn fetch_triggers( - self, + #[tracing::instrument(skip(db))] + pub async fn fetch_triggers(self, db: &SqlitePool) -> Result, sqlx::Error> { + Trigger::fetch_by_member_id(self, db).await + } +} + +#[derive(Debug, Clone)] +/// An untrusted member reference from an external source +pub enum MemberRef { + Id(Id), + /// We were given a [`super::Alias`] + Alias(String), +} + +impl FromStr for MemberRef { + type Err = Infallible; + + fn from_str(s: &str) -> std::result::Result { + s.parse::().map_or_else( + |_| Ok(Self::Alias(s.to_string())), + |id| Ok(Self::Id(Id::new(id))), + ) + } +} + +impl MemberRef { + #[tracing::instrument(skip(db))] + pub async fn validate_by_system( + &self, + system_id: system::Id, db: &SqlitePool, - ) -> error_stack::Result, trigger::Error> { - Trigger::fetch_by_member_id(db, self).await + ) -> Result>, sqlx::Error> { + match self { + Self::Id(id) => id + .validate_by_system(system_id, db) + .await + .attach_printable("Failed to validate member reference via id and system"), + Self::Alias(alias) => Id::fetch_by_alias(alias, system_id, db) + .await + .attach_printable("Failed to validate member reference via alias and system"), + } } } @@ -121,41 +159,9 @@ pub struct Member { } impl Member { - pub async fn fetch_by_and_trust_id( - system_id: system::Id, - member_id: Id, - db: &SqlitePool, - ) -> Result, sqlx::Error> { - sqlx::query_as!( - Member, - r#" - SELECT - id as "id: Id", - system_id as "system_id: system::Id", - full_name, - display_name, - profile_picture_url, - title, - pronouns, - name_pronunciation, - name_recording_url, - created_at as "created_at: time::PrimitiveDateTime" - FROM members - WHERE system_id = $1 AND id = $2 - "#, - system_id, - // Safe because this query also checks if the ID is trusted - member_id.id - ) - .fetch_optional(db) - .await - } - /// Fetch a member by their id - pub async fn fetch_by_id( - member_id: Id, - db: &SqlitePool, - ) -> Result, sqlx::Error> { + #[tracing::instrument(skip(db))] + pub async fn fetch_by_id(member_id: Id, db: &SqlitePool) -> Result { sqlx::query_as!( Member, r#" @@ -171,12 +177,13 @@ impl Member { name_recording_url, created_at as "created_at: time::PrimitiveDateTime" FROM members - WHERE id = $2 + WHERE id = $1 "#, member_id ) - .fetch_optional(db) + .fetch_one(db) .await + .attach_printable("Failed to fetch member by id") } } @@ -207,7 +214,7 @@ impl From for TriggeredMember { } } -#[derive(Default, Clone)] +#[derive(Debug, Default, Clone)] pub struct View { pub full_name: String, pub display_name: String, @@ -311,11 +318,12 @@ impl View { /// Add a member to the database /// /// Returns the id of the new member + #[tracing::instrument(skip(db))] pub async fn add( &self, system_id: system::Id, db: &SqlitePool, - ) -> error_stack::Result { + ) -> error_stack::Result { debug!("Adding member {} to database", self.display_name); sqlx::query!(" INSERT INTO members (full_name, display_name, profile_picture_url, title, pronouns, name_pronunciation, name_recording_url, system_id) @@ -334,18 +342,18 @@ impl View { .fetch_one(db) .await .attach_printable("Error adding member to database") - .change_context(Error::Sqlx) .map(|row| row.id) } /// Update a member in the database to match this view /// /// Returns None if the member does not exist + #[tracing::instrument(skip(db))] pub async fn update( &self, member_id: Id, db: &SqlitePool, - ) -> error_stack::Result, Error> { + ) -> error_stack::Result { sqlx::query!(" UPDATE members SET full_name = $1, display_name = $2, profile_picture_url = $3, title = $4, pronouns = $5, name_pronunciation = $6, name_recording_url = $7 @@ -361,15 +369,17 @@ impl View { member_id, ).execute(db).await .attach_printable("Error editing member in database") - .change_context(Error::Sqlx) - .map(Some) } } -impl TryFrom for View { - type Error = Error; +#[derive(thiserror::Error, displaydoc::Display, Debug)] +/// A field was missing from the view +pub struct MissingFieldError(String); - fn try_from(value: SlackViewState) -> Result { +impl TryFrom for View { + type Error = MissingFieldError; + + fn try_from(value: SlackViewState) -> std::result::Result { let mut view = Self::default(); for (_id, values) in value.values { for (id, content) in values { @@ -377,12 +387,12 @@ impl TryFrom for View { "full_name" => { view.full_name = content .value - .ok_or_else(|| Error::MissingField("display_name".to_string()))?; + .ok_or_else(|| MissingFieldError("display_name".to_string()))?; } "display_name" => { view.display_name = content .value - .ok_or_else(|| Error::MissingField("display_name".to_string()))?; + .ok_or_else(|| MissingFieldError("display_name".to_string()))?; } "profile_picture_url" => view.profile_picture_url = content.value, "title" => view.title = content.value, @@ -397,11 +407,11 @@ impl TryFrom for View { } if view.full_name.is_empty() { - return Err(Error::MissingField("full_name".to_string())); + return Err(MissingFieldError("full_name".to_string())); } if view.display_name.is_empty() { - return Err(Error::MissingField("display_name".to_string())); + return Err(MissingFieldError("display_name".to_string())); } Ok(view) diff --git a/src/models/message.rs b/src/models/message.rs index 62df896..15fce34 100644 --- a/src/models/message.rs +++ b/src/models/message.rs @@ -1,8 +1,9 @@ use crate::id; use super::{Trusted, member}; +use error_stack::{Result, ResultExt}; use slack_morphism::SlackTs; -use sqlx::{SqlitePool, prelude::*}; +use sqlx::{SqlitePool, prelude::*, sqlite::SqliteQueryResult}; id!( /// You cannot create a message id, as it is internal generated-only. @@ -28,7 +29,7 @@ impl MessageLog { pub async fn delete_by_message_id( message_id: String, db: &SqlitePool, - ) -> Result<(), sqlx::Error> { + ) -> Result { sqlx::query!( r#" DELETE FROM message_logs @@ -38,7 +39,7 @@ impl MessageLog { ) .execute(db) .await - .map(|_| ()) + .attach_printable("Failed to delete message log") } /// Fetches a message log by the slack message ID. @@ -62,13 +63,14 @@ impl MessageLog { ) .fetch_optional(db) .await + .attach_printable("Failed to fetch message log") } /// Fetches all message logs by the member ID. #[tracing::instrument(skip(db))] pub async fn fetch_all_by_member_id( - db: &SqlitePool, member_id: member::Id, + db: &SqlitePool, ) -> Result, sqlx::Error> { sqlx::query_as!( MessageLog, @@ -86,8 +88,10 @@ impl MessageLog { ) .fetch_all(db) .await + .attach_printable("Failed to fetch message logs") } + #[tracing::instrument(skip(db))] pub async fn insert( member_id: member::Id, message_id: SlackTs, @@ -108,5 +112,6 @@ impl MessageLog { ) .fetch_one(db) .await + .attach_printable("Failed to insert message log") } } diff --git a/src/models/mod.rs b/src/models/mod.rs index 18a92e3..45ea150 100755 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,11 +1,18 @@ use std::fmt::Debug; +pub mod alias; pub mod member; pub mod message; pub mod system; pub mod trigger; pub mod user; +pub use alias::Alias; +pub use member::{Member, TriggeredMember}; +pub use message::MessageLog; +pub use system::System; +pub use trigger::Trigger; + pub trait Trustability: Send + Sync + Debug {} /// A trusted/valid ID @@ -26,11 +33,22 @@ macro_rules! id { ($(#[$attr:meta])* => $name:ident) => { #[derive(::sqlx::Type, Debug, PartialEq, Eq, Clone, Copy)] $(#[$attr])* - pub struct Id { + pub struct Id { pub id: i64, trusted: ::std::marker::PhantomData, } + impl ::std::str::FromStr for Id<$crate::models::Untrusted> { + type Err = ::std::num::ParseIntError; + + fn from_str(s: &str) -> ::std::result::Result { + Ok(Id { + id: s.parse()?, + trusted: ::std::marker::PhantomData, + }) + } + } + impl<'q, DB> Encode<'q, DB> for Id<$crate::models::Trusted> where DB: ::sqlx::Database, @@ -39,7 +57,7 @@ macro_rules! id { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer<'q>, - ) -> Result<::sqlx::encode::IsNull, ::sqlx::error::BoxDynError> { + ) -> ::std::result::Result<::sqlx::encode::IsNull, ::sqlx::error::BoxDynError> { >::encode_by_ref(&self.id, buf) } @@ -55,7 +73,7 @@ macro_rules! id { { fn decode( value: ::ValueRef<'q>, - ) -> Result { + ) -> ::std::result::Result { let id = >::decode(value)?; Ok(Id { id, diff --git a/src/models/system.rs b/src/models/system.rs index fd72481..5929a9a 100755 --- a/src/models/system.rs +++ b/src/models/system.rs @@ -1,5 +1,5 @@ use crate::{ - id, + fields, id, models::member::{Member, TriggeredMember}, }; @@ -9,6 +9,7 @@ use super::{ trigger::Trigger, user, }; +use error_stack::{Result, ResultExt}; use redact::Secret; use sqlx::{SqlitePool, prelude::*}; use tracing::debug; @@ -22,10 +23,12 @@ id!( ); impl Id { + #[tracing::instrument(skip(db))] pub async fn list_triggers(self, db: &SqlitePool) -> Result, sqlx::Error> { - Trigger::fetch_by_system_id(db, self).await + Trigger::fetch_by_system_id(self, db).await } + #[tracing::instrument(skip(db))] pub async fn rename(self, new_name: &str, db: &SqlitePool) -> Result<(), sqlx::Error> { sqlx::query!( "UPDATE systems SET name = ? WHERE id = ?", @@ -37,6 +40,45 @@ impl Id { Ok(()) } + + #[tracing::instrument(skip(db))] + pub async fn change_active_member( + self, + new_active_member_id: Option>, + db: &SqlitePool, + ) -> Result, sqlx::Error> { + debug!( + "Changing active member for {} to {:?}", + self, new_active_member_id + ); + + let mut new_active_member = None; + + if let Some(new_active_member_id) = new_active_member_id { + new_active_member = Some( + Member::fetch_by_id(new_active_member_id, db) + .await + .attach_printable("Failed to fetch member")?, + ); + } + + fields!(new_active_member = ?&new_active_member); + + sqlx::query!( + r#" + UPDATE systems + SET active_member_id = $1 + WHERE id = $2 + "#, + new_active_member_id, + self.id + ) + .execute(db) + .await + .attach_printable("Failed to update system active member")?; + + Ok(new_active_member) + } } #[derive(Debug, FromRow, PartialEq, Eq, Clone)] @@ -68,15 +110,6 @@ pub struct System { pub created_at: time::PrimitiveDateTime, } -#[derive(Debug, thiserror::Error, displaydoc::Display)] -/// Error while changing the active member -pub enum ChangeActiveMemberError { - /// Error while calling the database - Sqlx(#[from] sqlx::Error), - /// The member is not part of the system - MemberNotFound, -} - impl System { #[tracing::instrument(skip(db))] pub async fn fetch_by_user_id( @@ -106,11 +139,13 @@ impl System { ) .fetch_optional(db) .await + .attach_printable("Error fetching system") } + #[tracing::instrument(skip(db))] pub async fn active_member(&self, db: &SqlitePool) -> Result, sqlx::Error> { match self.active_member_id { - Some(id) => Member::fetch_by_id(id, db).await, + Some(id) => Ok(Some(Member::fetch_by_id(id, db).await?)), None => Ok(None), } } @@ -120,32 +155,11 @@ impl System { &mut self, new_active_member_id: Option>, db: &SqlitePool, - ) -> Result, ChangeActiveMemberError> { - debug!( - "Changing active member for {} to {:?}", - self.id, new_active_member_id - ); - let mut new_active_member = None; - - if let Some(new_active_member_id) = new_active_member_id { - let Some(member) = Member::fetch_by_id(new_active_member_id, db).await? else { - return Err(ChangeActiveMemberError::MemberNotFound); - }; - - new_active_member = Some(member); - } - - sqlx::query!( - r#" - UPDATE systems - SET active_member_id = $1 - WHERE id = $2 - "#, - new_active_member_id, - self.id - ) - .execute(db) - .await?; + ) -> Result, sqlx::Error> { + let new_active_member = self + .id + .change_active_member(new_active_member_id, db) + .await?; self.active_member_id = new_active_member_id; Ok(new_active_member) @@ -174,6 +188,7 @@ impl System { ) .fetch_all(db) .await + .attach_printable("Failed to fetch members") } pub async fn fetch_triggered_member( @@ -203,5 +218,6 @@ impl System { ) .fetch_optional(db) .await + .attach_printable("Failed to fetch triggered member") } } diff --git a/src/models/trigger.rs b/src/models/trigger.rs index be1e803..9b48171 100755 --- a/src/models/trigger.rs +++ b/src/models/trigger.rs @@ -3,9 +3,9 @@ use std::str::FromStr; use crate::id; use super::{Trustability, Trusted, Untrusted, member, system}; -use error_stack::ResultExt; +use error_stack::{Result, ResultExt}; use slack_morphism::prelude::*; -use sqlx::{SqlitePool, prelude::*}; +use sqlx::{SqlitePool, prelude::*, sqlite::SqliteQueryResult}; use tracing::{debug, warn}; id!( @@ -24,34 +24,30 @@ impl Id { } } + #[tracing::instrument(skip(db))] pub async fn validate_by_system( self, system_id: system::Id, db: &SqlitePool, - ) -> Result, Self> { - let exists = sqlx::query!( - "SELECT EXISTS(SELECT 1 FROM triggers WHERE id = $1 AND system_id = $2) AS 'exists: bool'", + ) -> Result, sqlx::Error> { + sqlx::query!( + "SELECT + id as 'id: Id' + FROM triggers + WHERE id = $1 AND system_id = $2", self.id, system_id.id ) .fetch_one(db) .await - .ok() - .is_some_and(|record| record.exists); - - if exists { - Ok(Id { - id: self.id, - trusted: std::marker::PhantomData, - }) - } else { - Err(self) - } + .map(|record| record.id) + .attach_printable("Error validating trigger") } } impl Id { - pub async fn delete(self, db_pool: &SqlitePool) -> Result<(), sqlx::Error> { + #[tracing::instrument(skip(db))] + pub async fn delete(self, db: &SqlitePool) -> Result { sqlx::query!( r#" DELETE FROM triggers @@ -59,9 +55,9 @@ impl Id { "#, self.id ) - .execute(db_pool) + .execute(db) .await - .map(|_| ()) + .attach_printable("Error deleting trigger") } } @@ -99,7 +95,7 @@ pub struct UnknownType(String); impl FromStr for Type { type Err = UnknownType; - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> std::result::Result { match s { "suffix" => Ok(Self::Suffix), "prefix" => Ok(Self::Prefix), @@ -140,11 +136,13 @@ impl Trigger { ) .fetch_optional(db) .await + .attach_printable("Error fetching trigger") } + #[tracing::instrument(skip(db))] pub async fn fetch_by_system_id( - db: &SqlitePool, system_id: system::Id, + db: &SqlitePool, ) -> Result, sqlx::Error> { sqlx::query_as!( Trigger, @@ -164,12 +162,14 @@ impl Trigger { ) .fetch_all(db) .await + .attach_printable("Error fetching triggers") } + #[tracing::instrument(skip(db))] pub async fn fetch_by_member_id( - db: &SqlitePool, member_id: member::Id, - ) -> error_stack::Result, Error> { + db: &SqlitePool, + ) -> error_stack::Result, sqlx::Error> { sqlx::query_as!( Trigger, r#" @@ -187,7 +187,7 @@ impl Trigger { ) .fetch_all(db) .await - .change_context(Error::Sqlx) + .attach_printable("Error fetching triggers") } } @@ -252,11 +252,12 @@ impl View { /// Add a trigger to the database /// /// Returns the id of the new trigger + #[tracing::instrument(skip(db))] pub async fn add( &self, system_id: system::Id, member_id: member::Id, - db_pool: &SqlitePool, + db: &SqlitePool, ) -> error_stack::Result, Error> { debug!( "Adding trigger for {} (Member ID {}) to database", @@ -274,7 +275,7 @@ impl View { self.text, self.typ ) - .fetch_one(db_pool) + .fetch_one(db) .await .attach_printable("Error adding trigger to database") .change_context(Error::Sqlx) @@ -285,11 +286,12 @@ impl View { } /// Update a trigger in the database to match this view + #[tracing::instrument(skip(db))] pub async fn update( &self, trigger_id: Id, db: &SqlitePool, - ) -> error_stack::Result<(), Error> { + ) -> Result { sqlx::query!( r#" UPDATE triggers @@ -303,8 +305,6 @@ impl View { .execute(db) .await .attach_printable("Error updating trigger in database") - .change_context(Error::Sqlx) - .map(|_| ()) } pub fn create_add_view(self, member_id: member::Id) -> SlackView { diff --git a/src/util.rs b/src/util.rs index 467a33d..f935e5e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -12,25 +12,25 @@ #[macro_export] macro_rules! fields { // recursive cases - ($name:tt = %$value:expr, $($rest:tt)?) => { + ($name:tt = %$value:expr, $($rest:tt)*) => { ::tracing::span::Span::current() .record(::std::stringify!($name), ::tracing::field::display($value)); - fields!($($rest)+); + fields!($($rest)*); }; - ($name:tt = ?$value:expr, $($rest:tt)?) => { + ($name:tt = ?$value:expr, $($rest:tt)*) => { ::tracing::span::Span::current() .record(::std::stringify!($name), ::tracing::field::debug($value)); - fields!($($rest)+); + fields!($($rest)*); }; - ($name:tt = $value:expr, $($rest:tt)?) => { + ($name:tt = $value:expr, $($rest:tt)*) => { ::tracing::span::Span::current() .record(::std::stringify!($name), $value); - fields!($($rest)+); + fields!($($rest)*); }; // base cases @@ -48,4 +48,7 @@ macro_rules! fields { ::tracing::span::Span::current() .record(::std::stringify!($name), $value); }; + + // end + () => {} }