merge better member aliasing

This commit is contained in:
Suya1671 2025-06-18 14:42:50 +02:00
commit 561d07efe2
No known key found for this signature in database
18 changed files with 930 additions and 499 deletions

234
Cargo.lock generated Executable file → Normal file
View file

@ -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",
]

View file

@ -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"]

View file

@ -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;

212
src/commands/alias.rs Normal file
View file

@ -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<Untrusted>,
},
/// Lists all of your systems aliases
List {
/// If specified, lists the aliases for the given member.
member: Option<MemberRef>,
},
/// Edit an alias
Edit {
/// The alias to edit. Use the alias ID from /alias list
alias: alias::Id<Untrusted>,
/// 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<SlackCommandEventResponse, CommandError> {
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<SlackCommandEventResponse, CommandError> {
debug!("Creating alias");
let states = state.read().await;
let user_state = states.get_user_state::<user::State>().unwrap();
fetch_system!(event, user_state => system_id);
fetch_member!(member, user_state, system_id => member_id);
if alias.parse::<i64>().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<Untrusted>,
) -> Result<SlackCommandEventResponse, CommandError> {
debug!("Deleting alias");
let states = state.read().await;
let user_state = states.get_user_state::<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<MemberRef>,
) -> Result<SlackCommandEventResponse, CommandError> {
debug!("Listing aliases");
let states = state.read().await;
let user_state = states.get_user_state::<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<Untrusted>,
new_alias: String,
) -> Result<SlackCommandEventResponse, CommandError> {
debug!("Editing alias");
let states = state.read().await;
let user_state = states.get_user_state::<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()),
))
}
}

View file

@ -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<i64>,
member_id: Option<MemberRef>,
/// 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<i64>,
member_ref: Option<MemberRef>,
base: bool,
) -> Result<SlackCommandEventResponse, CommandError> {
trace!("Switching member");
let states = state.read().await;
let user_state = states.get_user_state::<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<SlackCommandEventResponse, CommandError> {
trace!("Running member info command");
let states = state.read().await;
let user_state = states.get_user_state::<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 <name>`".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<SlackCommandEventResponse, CommandError> {
trace!("Running member edit command");
let states = state.read().await;
let user_state = states.get_user_state::<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 <name>`".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");
};
}

View file

@ -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

View file

@ -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::<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::<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 <name>`".into(),
),
));
};
$crate::fields!(system_id = %$system_var_name);
::tracing::debug!("Fetched system");
};
}

View file

@ -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<Untrusted>,
},
/// Lists all of your triggers
List {
/// If specified, lists the triggers for the given member. Use the member id from /member list
member: Option<i64>,
/// If specified, lists the triggers for the given member.
member: Option<MemberRef>,
},
/// Edit a trigger
Edit {
/// The trigger to edit. Use the trigger id from /trigger list
id: i64,
id: trigger::Id<Untrusted>,
},
}
@ -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<SlackCommandEventResponse, CommandError> {
let states = state.read().await;
let user_state = states.get_user_state::<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 <name>`".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<Untrusted>,
) -> Result<SlackCommandEventResponse, CommandError> {
let states = state.read().await;
let user_state = states.get_user_state::<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<i64>,
member_ref: Option<MemberRef>,
) -> Result<SlackCommandEventResponse, CommandError> {
let states = state.read().await;
let user_state = states.get_user_state::<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 <name>`".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<Untrusted>,
) -> Result<SlackCommandEventResponse, CommandError> {
let states = state.read().await;
let user_state = states.get_user_state::<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 <name>`".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)?;

View file

@ -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::<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))

View file

@ -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,

View file

@ -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;

159
src/models/alias.rs Normal file
View file

@ -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<Untrusted> {
#[tracing::instrument(skip(db))]
pub async fn validate_by_system(
self,
system_id: system::Id<Trusted>,
db: &SqlitePool,
) -> Result<Option<Id<Trusted>>, sqlx::Error> {
sqlx::query!(
"SELECT
id as 'id: Id<Trusted>'
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<Trusted> {
#[tracing::instrument(skip(db))]
pub async fn delete(self, db: &SqlitePool) -> Result<SqliteQueryResult, sqlx::Error> {
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<SqliteQueryResult, sqlx::Error> {
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<Trusted>,
pub member_id: member::Id<Trusted>,
pub system_id: system::Id<Trusted>,
#[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<Trusted>,
db: &SqlitePool,
) -> Result<Vec<Self>, sqlx::Error> {
sqlx::query_as!(
Alias,
r#"
SELECT
id as "id: Id<Trusted>",
member_id as "member_id: member::Id<Trusted>",
system_id as "system_id: system::Id<Trusted>",
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<Trusted>,
db: &SqlitePool,
) -> error_stack::Result<Vec<Self>, sqlx::Error> {
sqlx::query_as!(
Self,
r#"
SELECT
id as "id: Id<Trusted>",
member_id as "member_id: member::Id<Trusted>",
system_id as "system_id: system::Id<Trusted>",
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<Trusted>,
system_id: system::Id<Trusted>,
alias: String,
db: &SqlitePool,
) -> error_stack::Result<Self, sqlx::Error> {
sqlx::query_as!(
Self,
r#"
INSERT INTO aliases (member_id, system_id, alias)
VALUES ($1, $2, $3)
RETURNING
id as "id: Id<Trusted>",
member_id as "member_id: member::Id<Trusted>",
system_id as "system_id: system::Id<Trusted>",
alias
"#,
member_id,
system_id,
alias,
)
.fetch_one(db)
.await
.attach_printable("Failed to insert alias into database")
}
}

View file

@ -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<Untrusted> {
}
}
#[tracing::instrument(skip(db))]
pub async fn validate_by_system(
self,
system_id: system::Id<Trusted>,
db: &SqlitePool,
) -> Result<Id<Trusted>, Self> {
let exists = sqlx::query!(
"SELECT EXISTS(SELECT 1 FROM members WHERE id = $1 AND system_id = $2) AS 'exists: bool'",
) -> Result<Option<Id<Trusted>>, sqlx::Error> {
sqlx::query!(
"SELECT
id as 'id: Id<Trusted>'
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<Trusted>,
db: &SqlitePool,
) -> Result<Id<Trusted>, Self> {
let exists = sqlx::query!(
"SELECT EXISTS(
SELECT 1
) -> Result<Option<Id<Trusted>>, sqlx::Error> {
sqlx::query!(
"
SELECT
members.id as 'id: Id<Trusted>'
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<Trusted>,
db: &SqlitePool,
) -> Result<Option<Id<Trusted>>, sqlx::Error> {
sqlx::query!(
"SELECT
member_id AS 'id: Id<Trusted>'
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<Trusted> {
pub async fn fetch_triggers(
self,
#[tracing::instrument(skip(db))]
pub async fn fetch_triggers(self, db: &SqlitePool) -> Result<Vec<Trigger>, 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<Untrusted>),
/// We were given a [`super::Alias`]
Alias(String),
}
impl FromStr for MemberRef {
type Err = Infallible;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
s.parse::<i64>().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<Trusted>,
db: &SqlitePool,
) -> error_stack::Result<Vec<Trigger>, trigger::Error> {
Trigger::fetch_by_member_id(db, self).await
) -> Result<Option<Id<Trusted>>, 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<Trusted>,
member_id: Id<Untrusted>,
db: &SqlitePool,
) -> Result<Option<Self>, sqlx::Error> {
sqlx::query_as!(
Member,
r#"
SELECT
id as "id: Id<Trusted>",
system_id as "system_id: system::Id<Trusted>",
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<Trusted>,
db: &SqlitePool,
) -> Result<Option<Self>, sqlx::Error> {
#[tracing::instrument(skip(db))]
pub async fn fetch_by_id(member_id: Id<Trusted>, db: &SqlitePool) -> Result<Self, sqlx::Error> {
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<Member> 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<Trusted>,
db: &SqlitePool,
) -> error_stack::Result<i64, Error> {
) -> error_stack::Result<i64, sqlx::Error> {
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<Trusted>,
db: &SqlitePool,
) -> error_stack::Result<Option<SqliteQueryResult>, Error> {
) -> error_stack::Result<SqliteQueryResult, sqlx::Error> {
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<SlackViewState> 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<Self, Self::Error> {
impl TryFrom<SlackViewState> for View {
type Error = MissingFieldError;
fn try_from(value: SlackViewState) -> std::result::Result<Self, Self::Error> {
let mut view = Self::default();
for (_id, values) in value.values {
for (id, content) in values {
@ -377,12 +387,12 @@ impl TryFrom<SlackViewState> 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<SlackViewState> 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)

View file

@ -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<SqliteQueryResult, sqlx::Error> {
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<Trusted>,
db: &SqlitePool,
) -> Result<Vec<Self>, 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<Trusted>,
message_id: SlackTs,
@ -108,5 +112,6 @@ impl MessageLog {
)
.fetch_one(db)
.await
.attach_printable("Failed to insert message log")
}
}

View file

@ -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<T> {
pub struct Id<T: $crate::models::Trustability> {
pub id: i64,
trusted: ::std::marker::PhantomData<T>,
}
impl ::std::str::FromStr for Id<$crate::models::Untrusted> {
type Err = ::std::num::ParseIntError;
fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
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 <DB as ::sqlx::Database>::ArgumentBuffer<'q>,
) -> Result<::sqlx::encode::IsNull, ::sqlx::error::BoxDynError> {
) -> ::std::result::Result<::sqlx::encode::IsNull, ::sqlx::error::BoxDynError> {
<i64 as ::sqlx::Encode<'_, DB>>::encode_by_ref(&self.id, buf)
}
@ -55,7 +73,7 @@ macro_rules! id {
{
fn decode(
value: <DB as ::sqlx::Database>::ValueRef<'q>,
) -> Result<Self, ::sqlx::error::BoxDynError> {
) -> ::std::result::Result<Self, ::sqlx::error::BoxDynError> {
let id = <i64 as ::sqlx::Decode<'_, DB>>::decode(value)?;
Ok(Id {
id,

View file

@ -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<Trusted> {
#[tracing::instrument(skip(db))]
pub async fn list_triggers(self, db: &SqlitePool) -> Result<Vec<Trigger>, 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<Trusted> {
Ok(())
}
#[tracing::instrument(skip(db))]
pub async fn change_active_member(
self,
new_active_member_id: Option<member::Id<Trusted>>,
db: &SqlitePool,
) -> Result<Option<Member>, 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<T>(
@ -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<Option<Member>, 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<member::Id<Trusted>>,
db: &SqlitePool,
) -> Result<Option<Member>, 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<Option<Member>, 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")
}
}

View file

@ -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<Untrusted> {
}
}
#[tracing::instrument(skip(db))]
pub async fn validate_by_system(
self,
system_id: system::Id<Trusted>,
db: &SqlitePool,
) -> Result<Id<Trusted>, Self> {
let exists = sqlx::query!(
"SELECT EXISTS(SELECT 1 FROM triggers WHERE id = $1 AND system_id = $2) AS 'exists: bool'",
) -> Result<Id<Trusted>, sqlx::Error> {
sqlx::query!(
"SELECT
id as 'id: Id<Trusted>'
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<Trusted> {
pub async fn delete(self, db_pool: &SqlitePool) -> Result<(), sqlx::Error> {
#[tracing::instrument(skip(db))]
pub async fn delete(self, db: &SqlitePool) -> Result<SqliteQueryResult, sqlx::Error> {
sqlx::query!(
r#"
DELETE FROM triggers
@ -59,9 +55,9 @@ impl Id<Trusted> {
"#,
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<Self, Self::Err> {
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
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<Trusted>,
db: &SqlitePool,
) -> Result<Vec<Self>, 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<Trusted>,
) -> error_stack::Result<Vec<Self>, Error> {
db: &SqlitePool,
) -> error_stack::Result<Vec<Self>, 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<Trusted>,
member_id: member::Id<Trusted>,
db_pool: &SqlitePool,
db: &SqlitePool,
) -> error_stack::Result<Id<Trusted>, 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<Trusted>,
db: &SqlitePool,
) -> error_stack::Result<(), Error> {
) -> Result<SqliteQueryResult, sqlx::Error> {
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<Trusted>) -> SlackView {

View file

@ -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
() => {}
}