mirror of
https://github.com/System-End/plura.git
synced 2026-04-19 16:28:21 +00:00
merge better member aliasing
This commit is contained in:
commit
561d07efe2
18 changed files with 930 additions and 499 deletions
234
Cargo.lock
generated
Executable file → Normal file
234
Cargo.lock
generated
Executable file → Normal 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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
11
migrations/20250618124409_member_aliasing.sql
Normal file
11
migrations/20250618124409_member_aliasing.sql
Normal 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
212
src/commands/alias.rs
Normal 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()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
};
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
159
src/models/alias.rs
Normal 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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
15
src/util.rs
15
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
|
||||
() => {}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue