diff --git a/Cargo.lock b/Cargo.lock index 815a2b74..fe00578f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.20.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" dependencies = [ "gimli", ] @@ -48,12 +48,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9" - [[package]] name = "android-tzdata" version = "0.1.1" @@ -80,9 +74,9 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "async-channel" @@ -95,15 +89,49 @@ dependencies = [ "futures-core", ] +[[package]] +name = "async-nats" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e3e851ddf3b62be8a8085e1e453968df9cdbf990a37bbb589b5b4f587c68d7" +dependencies = [ + "base64 0.21.2", + "bytes", + "futures", + "http", + "itoa 1.0.6", + "memchr", + "nkeys", + "nuid 0.3.2", + "once_cell", + "rand", + "regex", + "ring", + "rustls-native-certs", + "rustls-pemfile", + "rustls-webpki", + "serde", + "serde_json", + "serde_nanos", + "serde_repr", + "thiserror", + "time 0.3.21", + "tokio", + "tokio-retry", + "tokio-rustls 0.24.1", + "tracing", + "url 2.4.0", +] + [[package]] name = "async-trait" -version = "0.1.69" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2d0f03b3640e3a630367e40c468cb7f309529c708ed1d88597047b0e7c6ef7" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", - "quote 1.0.29", - "syn 2.0.23", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -129,14 +157,14 @@ checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" dependencies = [ "async-trait", "axum-core", - "bitflags 1.3.2", + "bitflags", "bytes", "futures-util", "headers", "http", "http-body", "hyper", - "itoa 1.0.8", + "itoa 1.0.6", "matchit", "memchr", "mime", @@ -173,9 +201,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ "addr2line", "cc", @@ -204,6 +232,12 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +[[package]] +name = "base64ct" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b4d9b1225d28d360ec6a231d65af1fd99a2a095154c8040689617290569c5c" + [[package]] name = "bigdecimal" version = "0.3.1" @@ -221,12 +255,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" - [[package]] name = "bitvec" version = "1.0.1" @@ -239,6 +267,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -278,7 +315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" dependencies = [ "proc-macro2", - "quote 1.0.29", + "quote 1.0.28", "syn 1.0.109", ] @@ -289,7 +326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" dependencies = [ "proc-macro2", - "quote 1.0.29", + "quote 1.0.28", "syn 1.0.109", ] @@ -317,7 +354,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" dependencies = [ "proc-macro2", - "quote 1.0.29", + "quote 1.0.28", "syn 1.0.109", ] @@ -332,6 +369,9 @@ name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +dependencies = [ + "serde", +] [[package]] name = "cc" @@ -394,6 +434,12 @@ dependencies = [ "toml", ] +[[package]] +name = "const-oid" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" + [[package]] name = "core-foundation" version = "0.9.3" @@ -412,9 +458,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.8" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] @@ -456,9 +502,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if", ] @@ -473,6 +519,25 @@ dependencies = [ "typenum", ] +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + [[package]] name = "debug_stub_derive" version = "0.3.0" @@ -490,30 +555,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ "serde", - "uuid 1.4.0", -] - -[[package]] -name = "diesel" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b28135ecf6b7d446b43e27e225622a038cc4e2930a1022f51cdb97ada19b8e4d" -dependencies = [ - "bitflags 1.3.2", - "byteorder", - "diesel_derives", - "pq-sys", + "uuid 1.3.3", ] [[package]] -name = "diesel_derives" -version = "1.4.1" +name = "der" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" +checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" dependencies = [ - "proc-macro2", - "quote 1.0.29", - "syn 1.0.109", + "const-oid", ] [[package]] @@ -522,13 +573,22 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "crypto-common", "subtle", ] @@ -592,7 +652,9 @@ dependencies = [ "svc-agent", "svc-authn", "svc-authz", - "svc-error", + "svc-error 0.6.0", + "svc-events", + "svc-nats-client", "svc-utils", "tokio", "tower", @@ -604,7 +666,7 @@ dependencies = [ "tracing-log", "tracing-subscriber", "url 2.4.0", - "uuid 1.4.0", + "uuid 1.3.3", "vec1", ] @@ -632,6 +694,27 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek", + "ed25519", + "sha2 0.9.9", + "zeroize", +] + [[package]] name = "either" version = "1.8.1" @@ -658,7 +741,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -815,8 +898,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", - "quote 1.0.29", - "syn 2.0.23", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -861,9 +944,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", @@ -872,15 +955,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.3" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" [[package]] name = "h2" -version = "0.3.20" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ "bytes", "fnv", @@ -913,23 +996,13 @@ dependencies = [ "ahash 0.8.3", ] -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" -dependencies = [ - "ahash 0.8.3", - "allocator-api2", -] - [[package]] name = "hashlink" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" +checksum = "0761a1b9491c4f2e3d66aa0f62d0fba0af9a0e2852e4d48ea506632a4b56e6aa" dependencies = [ - "hashbrown 0.14.0", + "hashbrown 0.13.2", ] [[package]] @@ -948,7 +1021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ "base64 0.13.1", - "bitflags 1.3.2", + "bitflags", "bytes", "headers-core", "http", @@ -977,9 +1050,18 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "hex" @@ -1002,7 +1084,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1024,7 +1106,7 @@ checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", - "itoa 1.0.8", + "itoa 1.0.6", ] [[package]] @@ -1074,9 +1156,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", @@ -1087,7 +1169,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.8", + "itoa 1.0.6", "pin-project-lite", "socket2", "tokio", @@ -1111,9 +1193,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1178,16 +1260,16 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.1", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "ipnet" -version = "2.8.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" [[package]] name = "itertools" @@ -1206,15 +1288,15 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.8" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] @@ -1241,9 +1323,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "fc86cde3ff845662b8f4ef6cb50ea0e20c524eb3d29ae048287e06a1b3fa6a81" [[package]] name = "linux-raw-sys" @@ -1263,9 +1345,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.19" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" [[package]] name = "match_cfg" @@ -1300,7 +1382,7 @@ version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1338,9 +1420,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] @@ -1353,7 +1435,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1379,7 +1461,7 @@ checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" dependencies = [ "cfg-if", "proc-macro2", - "quote 1.0.29", + "quote 1.0.28", "syn 1.0.109", ] @@ -1410,6 +1492,21 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nkeys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d151f6ece2f3d1077f6c779268de2516653d8344ddde65addd785cce764fe5" +dependencies = [ + "byteorder", + "data-encoding", + "ed25519-dalek", + "getrandom", + "log", + "rand", + "signatory", +] + [[package]] name = "nom" version = "7.1.3" @@ -1436,6 +1533,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "nuid" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c1bb65186718d348306bf1afdeb20d9ab45b2ab80fb793c0fdcf59ffbb4f38" +dependencies = [ + "lazy_static", + "rand", +] + +[[package]] +name = "nuid" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b61b1710432e483e6a67b20b6c60c6afe0e2fad67aabba3bdb912f3f70ff6ae" +dependencies = [ + "once_cell", + "rand", +] + [[package]] name = "num-bigint" version = "0.2.6" @@ -1479,19 +1596,19 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "object" -version = "0.31.1" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" dependencies = [ "memchr", ] @@ -1502,13 +1619,19 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl" -version = "0.10.55" +version = "0.10.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -1524,8 +1647,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", - "quote 1.0.29", - "syn 2.0.23", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -1536,9 +1659,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.90" +version = "0.9.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" dependencies = [ "cc", "libc", @@ -1608,14 +1731,14 @@ dependencies = [ "libc", "redox_syscall 0.3.5", "smallvec", - "windows-targets", + "windows-targets 0.48.0", ] [[package]] name = "paste" -version = "1.0.13" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "pathdiff" @@ -1634,6 +1757,15 @@ dependencies = [ "regex", ] +[[package]] +name = "pem-rfc7468" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e93a3b1cc0510b03020f33f21e62acdde3dcaef432edc95bea377fbd4c2cd4" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "1.0.1" @@ -1648,29 +1780,29 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" -version = "1.1.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", - "quote 1.0.29", - "syn 2.0.23", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -1678,6 +1810,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" +dependencies = [ + "der", + "pem-rfc7468", + "spki", + "zeroize", +] + [[package]] name = "pkg-config" version = "0.3.27" @@ -1696,15 +1840,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "pq-sys" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0052426df997c0cbd30789eb44ca097e3541717a7b8fa36b1c464ee7edebd" -dependencies = [ - "vcpkg", -] - [[package]] name = "predicates" version = "2.1.5" @@ -1746,9 +1881,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] @@ -1776,7 +1911,7 @@ checksum = "f8f30cdb09c39930b8fa5e0f23cbb895ab3f766b187403a0ba0956fc1ef4f0e5" dependencies = [ "lazy_static", "proc-macro2", - "quote 1.0.29", + "quote 1.0.28", "syn 1.0.109", ] @@ -1802,7 +1937,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", - "quote 1.0.29", + "quote 1.0.28", "syn 1.0.109", ] @@ -1834,9 +1969,9 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" [[package]] name = "quote" -version = "1.0.29" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -1876,7 +2011,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1886,9 +2021,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", ] +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + [[package]] name = "rand_core" version = "0.6.4" @@ -1919,7 +2060,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] @@ -1928,7 +2069,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] @@ -2049,7 +2190,7 @@ dependencies = [ "rkyv_derive", "seahash", "tinyvec", - "uuid 1.4.0", + "uuid 1.3.3", ] [[package]] @@ -2059,7 +2200,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" dependencies = [ "proc-macro2", - "quote 1.0.29", + "quote 1.0.28", "syn 1.0.109", ] @@ -2077,15 +2218,15 @@ dependencies = [ "pollster", "thiserror", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "webpki", ] [[package]] name = "rust_decimal" -version = "1.30.0" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0446843641c69436765a35a5a77088e28c2e6a12da93e84aa3ab1cd4aa5a042" +checksum = "26bd36b60561ee1fb5ec2817f198b6fd09fa571c897a5e86d1487cfc2b096dfc" dependencies = [ "arrayvec", "borsh", @@ -2116,16 +2257,16 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.22" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8818fa822adcc98b18fedbb3632a6a33213c070556b5aa7c4c8cc21cff565c4c" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ - "bitflags 1.3.2", + "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2137,29 +2278,72 @@ dependencies = [ "base64 0.13.1", "log", "ring", - "sct", + "sct 0.6.1", "webpki", ] +[[package]] +name = "rustls" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct 0.7.0", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.2", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustversion" -version = "1.0.13" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "ryu" -version = "1.0.14" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -2187,6 +2371,16 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "seahash" version = "4.1.0" @@ -2199,7 +2393,7 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -2224,9 +2418,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "sentry" -version = "0.31.5" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b0ad16faa5d12372f914ed40d00bda21a6d1bdcc99264c5e5e1c9495cf3654" +checksum = "de31c6e03322af2175d3c850c5b5e11efcadc01948cd1fb7b5ad0a7c7b6c7ff2" dependencies = [ "httpdate", "native-tls", @@ -2244,9 +2438,9 @@ dependencies = [ [[package]] name = "sentry-anyhow" -version = "0.31.5" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3a571f02f9982af445af829c4837fe4857568a431bd2bed9f7cf88de4a6c44" +checksum = "4df1501f58a7821af9d3bd11435d1c0c74121a6e6931f7615592fdfa85a5fb84" dependencies = [ "anyhow", "sentry-backtrace", @@ -2255,9 +2449,9 @@ dependencies = [ [[package]] name = "sentry-backtrace" -version = "0.31.5" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f2ee8f147bb5f22ac59b5c35754a759b9a6f6722402e2a14750b2a63fc59bd" +checksum = "264e3ad27da3d1ad81b499dbcceae0a50e0e6ffc4b65b93f47d5180d46827644" dependencies = [ "backtrace", "once_cell", @@ -2267,9 +2461,9 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.31.5" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcd133362c745151eeba0ac61e3ba8350f034e9fe7509877d08059fe1d7720c6" +checksum = "7144590f7950647e4df5bd95f234c3aa29124729c54bd2457e1224d701d1a91c" dependencies = [ "hostname", "libc", @@ -2281,9 +2475,9 @@ dependencies = [ [[package]] name = "sentry-core" -version = "0.31.5" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7163491708804a74446642ff2c80b3acd668d4b9e9f497f85621f3d250fd012b" +checksum = "35614ecf115f55d93583baa02a85cb63acb6567cf91b17690d1147bac1739ca4" dependencies = [ "once_cell", "rand", @@ -2294,9 +2488,9 @@ dependencies = [ [[package]] name = "sentry-debug-images" -version = "0.31.5" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a5003d7ff08aa3b2b76994080b183e8cfa06c083e280737c9cee02ca1c70f5e" +checksum = "53c4288a1b255e6ff55f111d2e14a48d369da76e86fae15a00ee26a371d82ad4" dependencies = [ "findshlibs", "once_cell", @@ -2305,9 +2499,9 @@ dependencies = [ [[package]] name = "sentry-panic" -version = "0.31.5" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4dfe8371c9b2e126a8b64f6fefa54cef716ff2a50e63b5558a48b899265bccd" +checksum = "0a941028a24baf0a5a994d8a39670cecc72a61971bb0155f771537447a46211a" dependencies = [ "sentry-backtrace", "sentry-core", @@ -2315,9 +2509,9 @@ dependencies = [ [[package]] name = "sentry-tracing" -version = "0.31.5" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aca8b88978677a27ee1a91beafe4052306c474c06f582321fde72d2e2cc2f7f" +checksum = "eec56ebafd7cfc1175bccdf277be582ccc3308b8c353dca5831261a967a6e28c" dependencies = [ "sentry-backtrace", "sentry-core", @@ -2337,49 +2531,57 @@ dependencies = [ "serde", "serde_json", "thiserror", - "time 0.3.22", + "time 0.3.21", "url 2.4.0", - "uuid 1.4.0", + "uuid 1.3.3", ] [[package]] name = "serde" -version = "1.0.166" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.166" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", - "quote 1.0.29", - "syn 2.0.23", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] name = "serde_json" -version = "1.0.99" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ - "itoa 1.0.8", + "itoa 1.0.6", "ryu", "serde", ] +[[package]] +name = "serde_nanos" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae801b7733ca8d6a2b580debe99f67f36826a0f5b8a36055dc6bc40f8d6bc71" +dependencies = [ + "serde", +] + [[package]] name = "serde_path_to_error" -version = "0.1.12" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1b6471d7496b051e03f1958802a73f88b947866f5146f329e47e36554f4e55" +checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0" dependencies = [ - "itoa 1.0.8", "serde", ] @@ -2394,6 +2596,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "serde_repr" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +dependencies = [ + "proc-macro2", + "quote 1.0.28", + "syn 2.0.18", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2401,7 +2614,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.8", + "itoa 1.0.6", "ryu", "serde", ] @@ -2423,7 +2636,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -2434,13 +2647,26 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" [[package]] name = "sha2" -version = "0.10.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ + "block-buffer 0.9.0", "cfg-if", "cpufeatures", - "digest", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", ] [[package]] @@ -2483,6 +2709,24 @@ dependencies = [ "tokio", ] +[[package]] +name = "signatory" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfecc059e81632eef1dd9b79e22fc28b8fe69b30d3357512a77a0ad8ee3c782" +dependencies = [ + "pkcs8", + "rand_core 0.6.4", + "signature", + "zeroize", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + [[package]] name = "simdutf8" version = "0.1.4" @@ -2537,6 +2781,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spki" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" +dependencies = [ + "der", +] + [[package]] name = "sqlformat" version = "0.2.1" @@ -2568,7 +2821,7 @@ dependencies = [ "atoi", "base64 0.13.1", "bigdecimal", - "bitflags 1.3.2", + "bitflags", "byteorder", "bytes", "chrono", @@ -2587,7 +2840,7 @@ dependencies = [ "hkdf", "hmac", "indexmap", - "itoa 1.0.8", + "itoa 1.0.6", "libc", "log", "md-5", @@ -2600,7 +2853,7 @@ dependencies = [ "serde", "serde_json", "sha1 0.10.5", - "sha2", + "sha2 0.10.6", "smallvec", "sqlformat", "sqlx-rt", @@ -2608,7 +2861,7 @@ dependencies = [ "thiserror", "tokio-stream", "url 2.4.0", - "uuid 1.4.0", + "uuid 1.3.3", "whoami", ] @@ -2624,10 +2877,10 @@ dependencies = [ "hex", "once_cell", "proc-macro2", - "quote 1.0.29", + "quote 1.0.28", "serde", "serde_json", - "sha2", + "sha2 0.10.6", "sqlx-core", "sqlx-rt", "syn 1.0.109", @@ -2664,9 +2917,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "svc-agent" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c90205083c87c6bc25990d5cdd26f98d8c431441e86dbd4e7421b28c428e04d" +checksum = "bef9c610b65f24bab61b52f5a7264983d0e014858e8496282aec7d688baf7869" dependencies = [ "async-channel", "base64 0.21.2", @@ -2679,7 +2932,7 @@ dependencies = [ "sqlx", "svc-authn", "tokio", - "uuid 1.4.0", + "uuid 1.3.3", ] [[package]] @@ -2689,7 +2942,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb5cf659f78c8fff863c17ac4e674829517919716eeecab602e8d2941e89c111" dependencies = [ "chrono", - "diesel", "http", "jsonwebtoken", "serde", @@ -2723,6 +2975,19 @@ name = "svc-error" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f841d7fd45d6f179e9f3765491fcb5eea100a5bbe50ea47faf3f262031966d9" +dependencies = [ + "anyhow", + "crossbeam-channel", + "http", + "serde", + "serde_derive", +] + +[[package]] +name = "svc-error" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad220c6bc89bc2e7b8af01db6dcfa4a513e18d78e7cf2f778e623ac22577eadf" dependencies = [ "anyhow", "crossbeam-channel", @@ -2738,11 +3003,49 @@ dependencies = [ "svc-authz", ] +[[package]] +name = "svc-events" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad84bd15a598b693df7dd08ca832c3414d59d6847f134f479ff547264669735" +dependencies = [ + "serde", + "serde_json", + "svc-agent", + "svc-authn", + "thiserror", + "uuid 1.3.3", +] + +[[package]] +name = "svc-nats-client" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf7705838936003cae1b79e726be255ea9702b8aad516dec9c998c7c93ef6f8d" +dependencies = [ + "anyhow", + "async-nats", + "async-trait", + "futures", + "futures-util", + "humantime-serde", + "nuid 0.4.1", + "reqwest", + "serde", + "svc-agent", + "svc-error 0.6.0", + "svc-events", + "thiserror", + "tokio", + "tracing", + "uuid 1.3.3", +] + [[package]] name = "svc-utils" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb63daf177825295faa99773d6fb6de26be0d3e9fbe427e3cac794358c000e99" +checksum = "b8443737f4d2444dc9e6a140a83d720685290d1ad763e75b59421958fa4a1a96" dependencies = [ "axum", "futures", @@ -2752,7 +3055,7 @@ dependencies = [ "prometheus", "svc-agent", "svc-authn", - "svc-error", + "svc-error 0.5.0", "tokio", "tower", "tower-http", @@ -2778,18 +3081,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", - "quote 1.0.29", + "quote 1.0.28", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.23" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", - "quote 1.0.29", + "quote 1.0.28", "unicode-ident", ] @@ -2816,16 +3119,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.6.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ - "autocfg", "cfg-if", "fastrand", "redox_syscall 0.3.5", "rustix", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -2850,8 +3152,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", - "quote 1.0.29", - "syn 2.0.23", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -2877,11 +3179,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.22" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ - "itoa 1.0.8", + "itoa 1.0.6", "serde", "time-core", "time-macros", @@ -2919,12 +3221,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", - "backtrace", "bytes", "libc", "mio", @@ -2934,7 +3235,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2944,8 +3245,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", - "quote 1.0.29", - "syn 2.0.23", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -2958,17 +3259,38 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-retry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" +dependencies = [ + "pin-project", + "rand", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ - "rustls", + "rustls 0.19.1", "tokio", "webpki", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.5", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -3021,11 +3343,11 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8bd22a874a2d0b70452d5597b12c537331d49060824a95f49f108994f94aa4c" +checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658" dependencies = [ - "bitflags 2.3.3", + "bitflags", "bytes", "futures-core", "futures-util", @@ -3070,19 +3392,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" dependencies = [ "crossbeam-channel", - "time 0.3.22", + "time 0.3.21", "tracing-subscriber", ] [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", - "quote 1.0.29", - "syn 2.0.23", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -3186,9 +3508,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -3225,11 +3547,11 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "ureq" -version = "2.7.1" +version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9" +checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d" dependencies = [ - "base64 0.21.2", + "base64 0.13.1", "log", "native-tls", "once_cell", @@ -3267,9 +3589,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "uuid" -version = "1.4.0" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" +checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" dependencies = [ "getrandom", "serde", @@ -3325,9 +3647,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3335,24 +3657,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", - "quote 1.0.29", - "syn 2.0.23", + "quote 1.0.28", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ "cfg-if", "js-sys", @@ -3362,38 +3684,38 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ - "quote 1.0.29", + "quote 1.0.28", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", - "quote 1.0.29", - "syn 2.0.23", + "quote 1.0.28", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -3411,9 +3733,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +checksum = "2c70234412ca409cc04e864e89523cb0fc37f5e1344ebed5a3ebf4192b6b9f68" dependencies = [ "wasm-bindgen", "web-sys", @@ -3447,7 +3769,31 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", ] [[package]] @@ -3456,60 +3802,117 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.0" @@ -3533,3 +3936,23 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote 1.0.28", + "syn 2.0.18", +] diff --git a/Cargo.toml b/Cargo.toml index 9800964b..f9076619 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,17 +46,19 @@ sqlx = { version = "0.6", features = [ "bigdecimal", "runtime-tokio-native-tls", ] } -svc-agent = { version = "0.20", features = ["sqlx"] } +svc-agent = { version = "0.21", features = ["sqlx"] } svc-authn = { version = "0.8", features = ["jose", "sqlx"] } svc-authz = "0.12" -svc-error = { version = "0.5", features = [ +svc-error = { version = "0.6", features = [ "svc-agent", "svc-authn", "svc-authz", "sentry-extension", "sqlx", ] } -svc-utils = { version = "0.7", features = ["log-middleware", "metrics-middleware", "cors-middleware", "authn-extractor"] } +svc-events = "0.11" +svc-nats-client = "0.8" +svc-utils = { version = "0.8", features = ["log-middleware", "metrics-middleware", "cors-middleware", "authn-extractor"] } tokio = { version = "1.28", features = ["full"] } tower = { version = "0.4", features = [ "timeout" ] } tracing = "0.1" diff --git a/chart/templates/app-cm.yaml b/chart/templates/app-cm.yaml index f6ad2f65..71fceaa7 100644 --- a/chart/templates/app-cm.yaml +++ b/chart/templates/app-cm.yaml @@ -89,6 +89,26 @@ data: {{- end }} {{- end }} + {{- with .Values.nats }} + {{- println "" }} + [nats] + url = {{ .url | quote }} + creds = {{ .creds | quote }} + subscribe_durable.stream = {{ .subscribe_durable.stream | quote }} + subscribe_durable.consumer = {{ .subscribe_durable.consumer | quote }} + subscribe_durable.batch = {{ .subscribe_durable.batch }} + subscribe_durable.idle_heartbeat = {{ .subscribe_durable.idle_heartbeat | quote }} + {{- end }} + + {{- with .Values.nats_consumer }} + {{- println "" }} + [nats_consumer] + suspend_interval = {{ .suspend_interval | quote }} + max_suspend_interval = {{ .max_suspend_interval | quote }} + suspend_sentry_interval = {{ .suspend_sentry_interval | quote }} + resubscribe_interval = {{ .resubscribe_interval | quote }} + {{- end }} + [storage] {{- if .Values.app.storage }} base_url = {{ .Values.app.storage.url | quote }} diff --git a/chart/values.yaml b/chart/values.yaml index 36a26f53..e9cb7deb 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -44,6 +44,9 @@ clusterService: http: 8080 metrics: 8888 +nats: {} +nats_consumer: {} + tls: secretName: tls-certificates diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 8613b757..02b244a9 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -16,3 +16,4 @@ - [API](minigroups/api.md) - [Classes API](classes/api.md) - [Transcoding utils](utils/transcoding.md) + - [Ban](utils/ban.md) diff --git a/docs/src/utils/ban.md b/docs/src/utils/ban.md new file mode 100644 index 00000000..cd4c06a0 --- /dev/null +++ b/docs/src/utils/ban.md @@ -0,0 +1,42 @@ +# API + +All routes expect json payloads. + +1. Get last ban operation id. +2. Send it in POST request along with other data. +If there's an error, return to 1. + +### Routes +Route | Method | Short description +------------------------------- | ------ | ---------- +/api/v1/account/:account_id/ban | POST | [Bans](#ban) media stream and collaboration for user +/api/v1/account/:account_id/ban | GET | [Gets](#get-last-ban-operation) last ban operation id for user + +### Ban + +Path variable | Type | Optional | Description +---------------------- | ----------- | -------- | -------------- +account_id | string | | Account id we want to ban + +JSON payload is required with following attributes: + +Attribute | Type | Optional | Description +---------------------- | ----------- | -------- | -------------- +last_seen_op_id | integer | | Last seen operation id which can be found by [GET request](#get-last-ban-operation) +ban | bool | | Ban/unban +class_id | uuid | | User should be banned in this particular classroom + +Response: status 200 and empty payload or json object with an error description. + +### Get last ban operation + +Path variable | Type | Optional | Description +---------------------- | ----------- | -------- | -------------- +account_id | string | | Account id we want to ban + +No JSON payload is expected. + +Response: status 200 and following payload or json object with an error description. + +Attribute | Type | Optional | Description +last_seen_op_id | integer | | Last seen operation id which can be used later in POST request diff --git a/migrations/20230517104549_add_ban_account_op_table.sql b/migrations/20230517104549_add_ban_account_op_table.sql new file mode 100644 index 00000000..07f11996 --- /dev/null +++ b/migrations/20230517104549_add_ban_account_op_table.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS ban_account_op ( + user_account account_id PRIMARY KEY, + last_op_id bigint NOT NULL, + is_video_streaming_banned boolean NOT NULL DEFAULT false, + is_collaboration_banned boolean NOT NULL DEFAULT false +); + +CREATE SEQUENCE IF NOT EXISTS ban_entity_seq_id; diff --git a/migrations/20230714081909_add_ban_history.sql b/migrations/20230714081909_add_ban_history.sql new file mode 100644 index 00000000..ea4d055b --- /dev/null +++ b/migrations/20230714081909_add_ban_history.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS ban_history ( + target_account account_id NOT NULL, + class_id uuid NOT NULL, + banned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + banned_operation_id BIGINT NOT NULL, + unbanned_at TIMESTAMPTZ, + unbanned_operation_id BIGINT, + + FOREIGN KEY (class_id) REFERENCES class (id) ON DELETE CASCADE, + PRIMARY KEY (banned_operation_id) +); diff --git a/sqlx-data.json b/sqlx-data.json index 4e3929fd..af4f610f 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -781,6 +781,60 @@ }, "query": "DELETE FROM recording WHERE class_id = $1 AND rtc_id = $2" }, + "59afd2ec969f3fd188d888784ff22b0f1d810bf75c58f2488cd3b8b263853a27": { + "describe": { + "columns": [ + { + "name": "user_account: _", + "ordinal": 0, + "type_info": { + "Custom": { + "kind": { + "Composite": [ + [ + "label", + "Text" + ], + [ + "audience", + "Text" + ] + ] + }, + "name": "account_id" + } + } + }, + { + "name": "last_op_id: _", + "ordinal": 1, + "type_info": "Int8" + }, + { + "name": "is_video_streaming_banned", + "ordinal": 2, + "type_info": "Bool" + }, + { + "name": "is_collaboration_banned", + "ordinal": 3, + "type_info": "Bool" + } + ], + "nullable": [ + false, + false, + false, + false + ], + "parameters": { + "Left": [ + "Record" + ] + } + }, + "query": "\n SELECT\n user_account AS \"user_account: _\",\n last_op_id AS \"last_op_id: _\",\n is_video_streaming_banned,\n is_collaboration_banned\n FROM ban_account_op\n WHERE\n user_account = $1\n LIMIT 1;\n " + }, "6016a361f9fb26d49797872a086033a34a01b4f1038125486a47c0cc8713e209": { "describe": { "columns": [ @@ -1195,123 +1249,134 @@ }, "query": "\n UPDATE recording\n SET modified_segments =\n CASE\n WHEN created_by = $3 THEN $2\n ELSE segments\n END,\n adjusted_at = NOW()\n WHERE class_id = $1 AND deleted_at IS NULL\n RETURNING\n id,\n class_id,\n rtc_id,\n stream_uri,\n segments AS \"segments!: Option\",\n started_at,\n modified_segments AS \"modified_segments!: Option\",\n created_at,\n adjusted_at,\n transcoded_at,\n created_by AS \"created_by: AgentId\",\n deleted_at\n " }, - "7296ad8d156f41d717c88c4f20c012bc010531d460776813fd70b3141485aabe": { + "7027e1415842b3bd10d9c5efb3e691341a7dfd7b51705cbf2dbed9cba3544a03": { "describe": { "columns": [ { - "name": "id", + "name": "user_account: _", "ordinal": 0, - "type_info": "Uuid" + "type_info": { + "Custom": { + "kind": { + "Composite": [ + [ + "label", + "Text" + ], + [ + "audience", + "Text" + ] + ] + }, + "name": "account_id" + } + } }, { - "name": "class_id", + "name": "last_op_id", "ordinal": 1, - "type_info": "Uuid" + "type_info": "Int8" }, { - "name": "rtc_id", + "name": "is_video_streaming_banned", "ordinal": 2, - "type_info": "Uuid" + "type_info": "Bool" }, { - "name": "stream_uri", + "name": "is_collaboration_banned", "ordinal": 3, - "type_info": "Text" - }, - { - "name": "segments!: Option", - "ordinal": 4, - "type_info": "Int8RangeArray" - }, - { - "name": "modified_segments!: Option", - "ordinal": 5, - "type_info": "Int8RangeArray" - }, - { - "name": "started_at", - "ordinal": 6, - "type_info": "Timestamptz" - }, - { - "name": "created_at", - "ordinal": 7, - "type_info": "Timestamptz" - }, - { - "name": "adjusted_at", - "ordinal": 8, - "type_info": "Timestamptz" - }, - { - "name": "transcoded_at", - "ordinal": 9, - "type_info": "Timestamptz" - }, + "type_info": "Bool" + } + ], + "nullable": [ + false, + false, + false, + false + ], + "parameters": { + "Left": [ + { + "Custom": { + "kind": { + "Composite": [ + [ + "label", + "Text" + ], + [ + "audience", + "Text" + ] + ] + }, + "name": "account_id" + } + }, + "Int8", + "Int8" + ] + } + }, + "query": "\n INSERT INTO ban_account_op (user_account, last_op_id)\n VALUES ($1, $2)\n ON CONFLICT (user_account) DO UPDATE\n SET\n -- to reset sub-operation trackers only if op_id has changed\n is_video_streaming_banned = ($2 = $3 AND ban_account_op.is_video_streaming_banned),\n is_collaboration_banned = ($2 = $3 AND ban_account_op.is_collaboration_banned),\n last_op_id = EXCLUDED.last_op_id\n WHERE\n -- allow to 'complete' operation if there's no change in last_op_id\n -- or allow to do upsert without real changes so we can process\n -- the same message twice\n ban_account_op.last_op_id = EXCLUDED.last_op_id OR\n -- allow change op id if the previous operation is completed\n (\n ban_account_op.last_op_id = $3 AND\n ban_account_op.is_video_streaming_banned = true AND\n ban_account_op.is_collaboration_banned = true\n )\n RETURNING\n user_account AS \"user_account: _\",\n last_op_id,\n is_video_streaming_banned,\n is_collaboration_banned\n " + }, + "7174ed47333151e6ef71b45f2f2fe9d15d70ba860e5dc52e1dcff3aea12c4c1d": { + "describe": { + "columns": [ { - "name": "created_by: AgentId", - "ordinal": 10, + "name": "user_account: _", + "ordinal": 0, "type_info": { "Custom": { "kind": { "Composite": [ [ - "account_id", - { - "Custom": { - "kind": { - "Composite": [ - [ - "label", - "Text" - ], - [ - "audience", - "Text" - ] - ] - }, - "name": "account_id" - } - } + "label", + "Text" ], [ - "label", + "audience", "Text" ] ] }, - "name": "agent_id" + "name": "account_id" } } }, { - "name": "deleted_at", - "ordinal": 11, - "type_info": "Timestamptz" + "name": "last_op_id", + "ordinal": 1, + "type_info": "Int8" + }, + { + "name": "is_video_streaming_banned", + "ordinal": 2, + "type_info": "Bool" + }, + { + "name": "is_collaboration_banned", + "ordinal": 3, + "type_info": "Bool" } ], "nullable": [ false, false, false, - true, - true, - true, - true, - false, - true, - true, - false, - true + false ], "parameters": { "Left": [ - "Uuid" + "Record", + "Bool", + "Bool", + "Int8" ] } }, - "query": "\n SELECT\n id,\n class_id,\n rtc_id,\n stream_uri,\n segments AS \"segments!: Option\",\n modified_segments AS \"modified_segments!: Option\",\n started_at,\n created_at,\n adjusted_at,\n transcoded_at,\n created_by AS \"created_by: AgentId\",\n deleted_at\n FROM recording\n WHERE class_id = $1 AND deleted_at IS NULL\n " + "query": "\n UPDATE ban_account_op\n SET\n is_video_streaming_banned = COALESCE($2, ban_account_op.is_video_streaming_banned),\n is_collaboration_banned = COALESCE($3, ban_account_op.is_collaboration_banned)\n WHERE\n ban_account_op.last_op_id = $4\n AND ban_account_op.user_account = $1\n RETURNING\n user_account AS \"user_account: _\",\n last_op_id,\n is_video_streaming_banned,\n is_collaboration_banned\n " }, "73a64697ca7ad2d67c7b2a77cc1fd6c0fb7a2b262a30d8386e83f8cbd8be5a54": { "describe": { @@ -1526,6 +1591,24 @@ }, "query": "\n SELECT\n id::text AS \"id!: String\"\n FROM class\n WHERE conference_room_id = $1\n " }, + "a4fc28b7fa8a6a5f4051750a2447784a10a221632dbea080fe1d4c980de076fd": { + "describe": { + "columns": [ + { + "name": "value!: i64", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + null + ], + "parameters": { + "Left": [] + } + }, + "query": "SELECT nextval('ban_entity_seq_id') as \"value!: i64\";" + }, "ac4ac9431173165543dbc459bb3b2b9b4461f7016aa6d6967cc9f5f832f208bb": { "describe": { "columns": [ @@ -1961,6 +2044,74 @@ }, "query": "\n INSERT INTO recording (\n class_id, rtc_id, stream_uri, segments, modified_segments, started_at, adjusted_at,\n transcoded_at, created_by\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ON CONFLICT (class_id, created_by)\n WHERE deleted_at IS NULL\n DO UPDATE\n SET (rtc_id, stream_uri, segments, modified_segments,\n started_at, adjusted_at, transcoded_at, created_by, created_at) =\n (EXCLUDED.rtc_id, EXCLUDED.stream_uri, EXCLUDED.segments, EXCLUDED.modified_segments, EXCLUDED.started_at, EXCLUDED.adjusted_at,\n EXCLUDED.transcoded_at, EXCLUDED.created_by, NOW())\n RETURNING\n id,\n class_id,\n rtc_id,\n stream_uri,\n segments AS \"segments!: Option\",\n started_at,\n modified_segments AS \"modified_segments!: Option\",\n created_at,\n adjusted_at,\n transcoded_at,\n created_by AS \"created_by: AgentId\",\n deleted_at\n " }, + "cd5e1f8fb8afcf43963183bbe0f1c481e5d65a49f9d4356c9b6bf2dbabc1644c": { + "describe": { + "columns": [ + { + "name": "target_account: AccountId", + "ordinal": 0, + "type_info": { + "Custom": { + "kind": { + "Composite": [ + [ + "label", + "Text" + ], + [ + "audience", + "Text" + ] + ] + }, + "name": "account_id" + } + } + }, + { + "name": "class_id", + "ordinal": 1, + "type_info": "Uuid" + }, + { + "name": "banned_at", + "ordinal": 2, + "type_info": "Timestamptz" + }, + { + "name": "banned_operation_id", + "ordinal": 3, + "type_info": "Int8" + }, + { + "name": "unbanned_at", + "ordinal": 4, + "type_info": "Timestamptz" + }, + { + "name": "unbanned_operation_id", + "ordinal": 5, + "type_info": "Int8" + } + ], + "nullable": [ + false, + false, + false, + false, + true, + true + ], + "parameters": { + "Left": [ + "Uuid", + "Record", + "Int8" + ] + } + }, + "query": "\n UPDATE ban_history\n SET\n unbanned_at = NOW(),\n unbanned_operation_id = $3\n WHERE\n class_id = $1\n AND target_account = $2\n AND unbanned_at IS NULL\n RETURNING\n target_account AS \"target_account: AccountId\",\n class_id,\n banned_at,\n banned_operation_id,\n unbanned_at,\n unbanned_operation_id\n " + }, "ce3fc3fce308f42126eaba1bde5ba12d340cce38b9f06fad4b6622b02de62cd9": { "describe": { "columns": [ @@ -2479,6 +2630,124 @@ }, "query": "\n UPDATE recording\n SET deleted_at = NOW()\n WHERE class_id = $1 AND deleted_at IS NULL\n " }, + "d5327cbe86cd24eef0fa68dff1ea943cb4f86d10663bbd9ad453342012a4e978": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Uuid" + }, + { + "name": "class_id", + "ordinal": 1, + "type_info": "Uuid" + }, + { + "name": "rtc_id", + "ordinal": 2, + "type_info": "Uuid" + }, + { + "name": "stream_uri", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "segments!: Option", + "ordinal": 4, + "type_info": "Int8RangeArray" + }, + { + "name": "modified_segments!: Option", + "ordinal": 5, + "type_info": "Int8RangeArray" + }, + { + "name": "started_at", + "ordinal": 6, + "type_info": "Timestamptz" + }, + { + "name": "created_at", + "ordinal": 7, + "type_info": "Timestamptz" + }, + { + "name": "adjusted_at", + "ordinal": 8, + "type_info": "Timestamptz" + }, + { + "name": "transcoded_at", + "ordinal": 9, + "type_info": "Timestamptz" + }, + { + "name": "created_by: AgentId", + "ordinal": 10, + "type_info": { + "Custom": { + "kind": { + "Composite": [ + [ + "account_id", + { + "Custom": { + "kind": { + "Composite": [ + [ + "label", + "Text" + ], + [ + "audience", + "Text" + ] + ] + }, + "name": "account_id" + } + } + ], + [ + "label", + "Text" + ] + ] + }, + "name": "agent_id" + } + } + }, + { + "name": "deleted_at", + "ordinal": 11, + "type_info": "Timestamptz" + } + ], + "nullable": [ + false, + false, + false, + true, + true, + true, + true, + false, + true, + true, + false, + true + ], + "parameters": { + "Left": [ + "Uuid" + ] + } + }, + "query": "\n SELECT\n r.id,\n r.class_id,\n r.rtc_id,\n r.stream_uri,\n r.segments AS \"segments!: Option\",\n r.modified_segments AS \"modified_segments!: Option\",\n r.started_at,\n r.created_at,\n r.adjusted_at,\n r.transcoded_at,\n r.created_by AS \"created_by: AgentId\",\n r.deleted_at\n FROM recording AS r\n LEFT JOIN ban_history AS bh\n ON r.class_id = bh.class_id\n AND bh.target_account = (r.created_by).account_id\n WHERE\n r.class_id = $1\n AND r.deleted_at IS NULL\n AND bh.banned_operation_id IS NULL\n " + }, "d7263981e0de43bae99ecbd69405e87e614ff3aa84a1379a01a25be6f13f6687": { "describe": { "columns": [ @@ -3103,6 +3372,90 @@ }, "query": "\n INSERT INTO class (\n scope, audience, time, tags, preserve_history, kind,\n conference_room_id, event_room_id, properties\n )\n VALUES ($1, $2, $3, $4, $5, $6::class_type, $7, $8, $9)\n RETURNING\n id,\n scope,\n kind AS \"kind!: ClassType\",\n audience,\n time AS \"time!: Time\",\n tags,\n properties AS \"properties: _\",\n preserve_history,\n created_at,\n event_room_id AS \"event_room_id!: Uuid\",\n conference_room_id AS \"conference_room_id!: Uuid\",\n original_event_room_id,\n modified_event_room_id,\n reserve,\n room_events_uri,\n host AS \"host: AgentId\",\n timed_out,\n original_class_id,\n content_id\n " }, + "f9aeac4c3a1e1747314db755f8e0790b01a0d2d603f20cdd069b7b57268ccdbe": { + "describe": { + "columns": [ + { + "name": "target_account!: AccountId", + "ordinal": 0, + "type_info": { + "Custom": { + "kind": { + "Composite": [ + [ + "label", + "Text" + ], + [ + "audience", + "Text" + ] + ] + }, + "name": "account_id" + } + } + }, + { + "name": "class_id!: _", + "ordinal": 1, + "type_info": "Uuid" + }, + { + "name": "banned_at!: _", + "ordinal": 2, + "type_info": "Timestamptz" + }, + { + "name": "banned_operation_id!: _", + "ordinal": 3, + "type_info": "Int8" + }, + { + "name": "unbanned_at", + "ordinal": 4, + "type_info": "Timestamptz" + }, + { + "name": "unbanned_operation_id", + "ordinal": 5, + "type_info": "Int8" + } + ], + "nullable": [ + null, + null, + null, + null, + null, + null + ], + "parameters": { + "Left": [ + "Uuid", + { + "Custom": { + "kind": { + "Composite": [ + [ + "label", + "Text" + ], + [ + "audience", + "Text" + ] + ] + }, + "name": "account_id" + } + }, + "Int8" + ] + } + }, + "query": "\n WITH i AS (\n INSERT INTO ban_history (class_id, target_account, banned_operation_id)\n VALUES ($1, $2, $3)\n -- this allows us to run this query many times idempotently\n ON CONFLICT (banned_operation_id) DO NOTHING\n RETURNING *\n )\n -- gets row if there was no conflict\n SELECT\n target_account AS \"target_account!: AccountId\",\n class_id AS \"class_id!: _\",\n banned_at AS \"banned_at!: _\",\n banned_operation_id AS \"banned_operation_id!: _\",\n unbanned_at,\n unbanned_operation_id\n FROM i\n -- or selects original row if there was a conflict\n UNION\n SELECT\n target_account AS \"target_account: AccountId\",\n class_id,\n banned_at,\n banned_operation_id,\n unbanned_at,\n unbanned_operation_id\n FROM ban_history\n WHERE\n banned_operation_id = $3\n " + }, "fe7779aca18f7e0fe8dcee465db39f3669a6b1c80064bab63e82f8f5aa99d7d1": { "describe": { "columns": [ diff --git a/src/app/api/v1/account/ban.rs b/src/app/api/v1/account/ban.rs new file mode 100644 index 00000000..c8d2e485 --- /dev/null +++ b/src/app/api/v1/account/ban.rs @@ -0,0 +1,112 @@ +use std::sync::Arc; + +use axum::{extract::Path, response::Response, Extension, Json}; +use hyper::Body; +use serde_derive::{Deserialize, Serialize}; +use svc_authn::{AccountId, Authenticable}; +use uuid::Uuid; + +use svc_utils::extractors::AgentIdExtractor; + +use crate::{ + app::{ + api::{v1::find_class, IntoJsonResponse}, + error::{ErrorExt, ErrorKind as AppErrorKind}, + metrics::AuthorizeMetrics, + stage, AppContext, AuthzObject, + }, + db::ban_account_op, +}; + +use super::AppResult; + +#[derive(Deserialize)] +pub struct BanPayload { + last_seen_op_id: i64, + ban: bool, + class_id: Uuid, +} + +pub async fn ban( + Extension(ctx): Extension>, + Path(account_to_ban): Path, + AgentIdExtractor(agent_id): AgentIdExtractor, + Json(payload): Json, +) -> AppResult { + let class = find_class(ctx.as_ref(), payload.class_id) + .await + .error(AppErrorKind::ClassNotFound)?; + + let object = AuthzObject::new(&["classrooms", &class.id().to_string()]).into(); + ctx.authz() + .authorize( + class.audience().to_owned(), + agent_id.as_account_id().clone(), + object, + "update".into(), + ) + .await + .measure()?; + + let mut conn = ctx + .get_conn() + .await + .error(AppErrorKind::DbConnAcquisitionFailed)?; + + let last_ban_account_op = ban_account_op::ReadQuery::by_account_id(&account_to_ban) + .execute(&mut conn) + .await + .error(AppErrorKind::DbQueryFailed)?; + + if let Some(op) = last_ban_account_op { + if op.last_op_id != payload.last_seen_op_id { + return Err(AppErrorKind::OperationIdObsolete.into()); + } + + if !op.is_completed() { + return Err(AppErrorKind::OperationInProgress.into()); + } + } + + stage::ban::save_intent( + ctx.as_ref(), + &mut conn, + payload.ban, + &class, + agent_id, + account_to_ban, + payload.last_seen_op_id, + ) + .await?; + + Ok(Response::builder().status(200).body(Body::empty()).unwrap()) +} + +#[derive(Serialize)] +pub struct GetLastBanOperationResponse { + last_seen_op_id: i64, +} + +pub async fn get_last_ban_operation( + Extension(ctx): Extension>, + Path(account_to_ban): Path, +) -> AppResult { + let mut conn = ctx + .get_conn() + .await + .error(AppErrorKind::DbConnAcquisitionFailed)?; + + let last_ban_account_op = ban_account_op::ReadQuery::by_account_id(&account_to_ban) + .execute(&mut conn) + .await + .error(AppErrorKind::DbQueryFailed)?; + + let r = GetLastBanOperationResponse { + last_seen_op_id: last_ban_account_op.map(|l| l.last_op_id).unwrap_or(0), + }; + + r.into_json_response( + "failed to serialize last ban operation", + http::StatusCode::OK, + ) +} diff --git a/src/app/api/v1/account.rs b/src/app/api/v1/account/mod.rs similarity index 94% rename from src/app/api/v1/account.rs rename to src/app/api/v1/account/mod.rs index 6d47b694..f0f7f1d4 100644 --- a/src/app/api/v1/account.rs +++ b/src/app/api/v1/account/mod.rs @@ -4,6 +4,8 @@ use axum::{extract::Path, Extension}; use svc_agent::AccountId; use svc_utils::extractors::AccountIdExtractor; +pub mod ban; + use crate::{ app::{ api::IntoJsonResponse, @@ -124,11 +126,11 @@ mod tests { use super::*; use crate::test_helpers::prelude::*; - #[tokio::test] - async fn update_property() { + #[sqlx::test] + async fn update_property(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let authz = TestAuthz::new(); let state = TestState::new_with_pool(db_pool, authz); let state = Arc::new(state); @@ -149,11 +151,11 @@ mod tests { assert!(props.contains_key(&property_id)); } - #[tokio::test] - async fn read_property() { + #[sqlx::test] + async fn read_property(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user2", USR_AUDIENCE); - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let authz = TestAuthz::new(); let state = TestState::new_with_pool(db_pool, authz); let state = Arc::new(state); @@ -183,11 +185,11 @@ mod tests { assert_eq!(property_value, expected_property_value); } - #[tokio::test] - async fn merge_properties() { + #[sqlx::test] + async fn merge_properties(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user3", USR_AUDIENCE); - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let authz = TestAuthz::new(); let state = TestState::new_with_pool(db_pool, authz); let state = Arc::new(state); @@ -241,11 +243,11 @@ mod tests { assert_eq!(property_value, second_prop_value); } - #[tokio::test] - async fn overwrite_property() { + #[sqlx::test] + async fn overwrite_property(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user4", USR_AUDIENCE); - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let authz = TestAuthz::new(); let state = TestState::new_with_pool(db_pool, authz); let state = Arc::new(state); @@ -298,11 +300,11 @@ mod tests { assert_eq!(property_value, second_prop_value); } - #[tokio::test] - async fn overwrite_property_complex() { + #[sqlx::test] + async fn overwrite_property_complex(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user5", USR_AUDIENCE); - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let authz = TestAuthz::new(); let state = TestState::new_with_pool(db_pool, authz); let state = Arc::new(state); diff --git a/src/app/api/v1/authz/mod.rs b/src/app/api/v1/authz/mod.rs index 5fe038af..a7d5f0eb 100644 --- a/src/app/api/v1/authz/mod.rs +++ b/src/app/api/v1/authz/mod.rs @@ -489,12 +489,12 @@ fn test_extract_rtc_id() { assert_eq!(r, "14aa9730-26e1-487c-9153-bc8cb28d8eb0"); } -#[tokio::test] -async fn test_transform_tq_authz_request() { +#[sqlx::test] +async fn test_transform_tq_authz_request(pool: sqlx::PgPool) { use crate::test_helpers::prelude::TestAuthz; use crate::test_helpers::state::TestState; - let test_state = TestState::new(TestAuthz::new()).await; + let test_state = TestState::new(pool, TestAuthz::new()).await; // ["classrooms", CLASSROOM_ID, "priorities", priority]::* // becomes ["classrooms", CLASSROOM_ID]::* let mut authz_req: AuthzRequest = serde_json::from_str( @@ -617,12 +617,12 @@ fn test_transform_nats_gatekeeper_authz_request() { ); } -#[tokio::test] -async fn test_transform_storage_v1_authz_request() { +#[sqlx::test] +async fn test_transform_storage_v1_authz_request(pool: sqlx::PgPool) { use crate::test_helpers::prelude::TestAuthz; use crate::test_helpers::state::TestState; - let test_state = TestState::new(TestAuthz::new()).await; + let test_state = TestState::new(pool, TestAuthz::new()).await; let mut authz_req: AuthzRequest = serde_json::from_str( r#" diff --git a/src/app/api/v1/class/create_timestamp.rs b/src/app/api/v1/class/create_timestamp.rs index db9998b8..3746855c 100644 --- a/src/app/api/v1/class/create_timestamp.rs +++ b/src/app/api/v1/class/create_timestamp.rs @@ -74,11 +74,10 @@ mod create_timestamp_tests { use std::ops::Bound; use uuid::Uuid; - #[tokio::test] - async fn create_timestamp_unauthorized() { + #[sqlx::test] + async fn create_timestamp_unauthorized(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let webinar = { let mut conn = db_pool.get_conn().await; @@ -111,11 +110,10 @@ mod create_timestamp_tests { .expect_err("Unexpected success, should fail due to authz"); } - #[tokio::test] - async fn create_webinar_timestamp() { + #[sqlx::test] + async fn create_webinar_timestamp(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let webinar = { let mut conn = db_pool.get_conn().await; @@ -175,7 +173,7 @@ mod create_timestamp_tests { let body = response.into_body(); let body = hyper::body::to_bytes(body).await.unwrap(); let body = std::str::from_utf8(&body).unwrap(); - let body = serde_json::from_str::(&body).unwrap(); + let body = serde_json::from_str::(body).unwrap(); let position = body.get("position").unwrap().as_i64().unwrap(); assert_eq!(position, 50); } diff --git a/src/app/api/v1/class/properties.rs b/src/app/api/v1/class/properties.rs index e8535d8c..ec4a1c18 100644 --- a/src/app/api/v1/class/properties.rs +++ b/src/app/api/v1/class/properties.rs @@ -164,13 +164,13 @@ mod tests { use mockall::predicate as pred; use uuid::Uuid; - #[tokio::test] - async fn read_property_unauthorized() { + #[sqlx::test] + async fn read_property_unauthorized(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); - let state = TestState::new(TestAuthz::new()).await; + let state = TestState::new(pool, TestAuthz::new()).await; let webinar = { let mut conn = state.get_conn().await.expect("Failed to fetch connection"); @@ -204,13 +204,13 @@ mod tests { .expect_err("Unexpectedly succeeded"); } - #[tokio::test] - async fn read_property() { + #[sqlx::test] + async fn read_property(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let webinar = { let mut conn = db_pool.get_conn().await; @@ -256,13 +256,13 @@ mod tests { assert_eq!(property_value, serde_json::json!("test2")); } - #[tokio::test] - async fn update_property_unauthorized() { + #[sqlx::test] + async fn update_property_unauthorized(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); - let state = TestState::new(TestAuthz::new()).await; + let state = TestState::new(pool, TestAuthz::new()).await; let webinar = { let mut conn = state.get_conn().await.expect("Failed to fetch connection"); @@ -297,13 +297,13 @@ mod tests { .expect_err("Unexpectedly succeeded"); } - #[tokio::test] - async fn update_property() { + #[sqlx::test] + async fn update_property(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let webinar = { let mut conn = db_pool.get_conn().await; diff --git a/src/app/api/v1/class/read.rs b/src/app/api/v1/class/read.rs index e36e4ecf..ccd394e9 100644 --- a/src/app/api/v1/class/read.rs +++ b/src/app/api/v1/class/read.rs @@ -219,11 +219,10 @@ mod tests { }; use serde_json::Value; - #[tokio::test] - async fn read_webinar_unauthorized() { + #[sqlx::test] + async fn read_webinar_unauthorized(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - - let state = TestState::new(TestAuthz::new()).await; + let state = TestState::new(pool, TestAuthz::new()).await; let state = Arc::new(state); @@ -237,14 +236,15 @@ mod tests { .expect_err("Unexpectedly succeeded"); } - #[tokio::test] - async fn read_webinar() { + #[sqlx::test] + async fn read_webinar(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let webinar = { let mut conn = db_pool.get_conn().await; - let webinar = factory::Webinar::new( + + factory::Webinar::new( random_string(), USR_AUDIENCE.to_string(), (Bound::Unbounded, Bound::Unbounded).into(), @@ -252,9 +252,7 @@ mod tests { Uuid::new_v4(), ) .insert(&mut conn) - .await; - - webinar + .await }; let mut authz = TestAuthz::new(); @@ -282,23 +280,22 @@ mod tests { assert_eq!(v.get("turn_host").unwrap().as_str(), Some("turn0")); } - #[tokio::test] - async fn read_p2p() { + #[sqlx::test] + async fn read_p2p(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let p2p = { let mut conn = db_pool.get_conn().await; - let p2p = factory::P2P::new( + + factory::P2P::new( random_string(), USR_AUDIENCE.to_string(), Uuid::new_v4(), Uuid::new_v4(), ) .insert(&mut conn) - .await; - - p2p + .await }; let mut authz = TestAuthz::new(); @@ -331,17 +328,18 @@ mod tests { assert_eq!(turns.into_iter().collect::>().len(), 1); } - #[tokio::test] - async fn read_class_properties() { + #[sqlx::test] + async fn read_class_properties(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let mut properties = KeyValueProperties::new(); properties.insert("test".to_owned(), serde_json::json!("test")); let webinar = { let mut conn = db_pool.get_conn().await; - let webinar = factory::Webinar::new( + + factory::Webinar::new( random_string(), USR_AUDIENCE.to_string(), (Bound::Unbounded, Bound::Unbounded).into(), @@ -350,9 +348,7 @@ mod tests { ) .properties(properties) .insert(&mut conn) - .await; - - webinar + .await }; let mut authz = TestAuthz::new(); @@ -413,10 +409,10 @@ mod tests { ) } - #[tokio::test] - async fn read_account_properties() { + #[sqlx::test] + async fn read_account_properties(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let mut properties = KeyValueProperties::new(); properties.insert("test".to_owned(), serde_json::json!("test")); diff --git a/src/app/api/v1/class/recreate.rs b/src/app/api/v1/class/recreate.rs index 415562a6..b1f423d9 100644 --- a/src/app/api/v1/class/recreate.rs +++ b/src/app/api/v1/class/recreate.rs @@ -140,11 +140,11 @@ mod tests { use mockall::predicate as pred; use uuid::Uuid; - #[tokio::test] - async fn recreate_webinar_unauthorized() { + #[sqlx::test] + async fn recreate_webinar_unauthorized(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let webinar = { let mut conn = db_pool.get_conn().await; @@ -172,14 +172,14 @@ mod tests { .expect_err("Unexpected success, should fail due to authz"); } - #[tokio::test] - async fn recreate_webinar() { + #[sqlx::test] + async fn recreate_webinar(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let recreated_event_room_id = Uuid::new_v4(); let recreated_conference_room_id = Uuid::new_v4(); - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let webinar = { let mut conn = db_pool.get_conn().await; @@ -221,7 +221,7 @@ mod tests { // Assert DB changes. let mut conn = state.get_conn().await.expect("Failed to get conn"); - let new_webinar = WebinarReadQuery::by_scope(USR_AUDIENCE, &webinar.scope()) + let new_webinar = WebinarReadQuery::by_scope(USR_AUDIENCE, webinar.scope()) .execute(&mut conn) .await .expect("Failed to fetch webinar") diff --git a/src/app/api/v1/class/update.rs b/src/app/api/v1/class/update.rs index da11c6b3..ca8e4fed 100644 --- a/src/app/api/v1/class/update.rs +++ b/src/app/api/v1/class/update.rs @@ -185,13 +185,13 @@ mod tests { let _update: ClassUpdate = serde_json::from_str(update).unwrap(); } - #[tokio::test] - async fn update_webinar_unauthorized() { + #[sqlx::test] + async fn update_webinar_unauthorized(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); - let state = TestState::new(TestAuthz::new()).await; + let state = TestState::new(pool, TestAuthz::new()).await; let webinar = { let mut conn = state.get_conn().await.expect("Failed to fetch connection"); factory::Webinar::new( @@ -220,13 +220,13 @@ mod tests { .expect_err("Unexpectedly succeeded"); } - #[tokio::test] - async fn update_webinar() { + #[sqlx::test] + async fn update_webinar(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let webinar = { let mut conn = db_pool.get_conn().await; diff --git a/src/app/api/v1/minigroup/mod.rs b/src/app/api/v1/minigroup/mod.rs index 3551b76e..c5ae2da8 100644 --- a/src/app/api/v1/minigroup/mod.rs +++ b/src/app/api/v1/minigroup/mod.rs @@ -247,14 +247,14 @@ mod tests { use mockall::predicate as pred; use uuid::Uuid; - #[tokio::test] - async fn create_minigroup_no_time() { + #[sqlx::test] + async fn create_minigroup_no_time(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let mut authz = TestAuthz::new(); authz.allow(agent.account_id(), vec!["classrooms"], "create"); - let mut state = TestState::new(authz).await; + let mut state = TestState::new(pool, authz).await; let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); @@ -289,14 +289,14 @@ mod tests { assert_eq!(new_minigroup.reserve(), Some(10),); } - #[tokio::test] - async fn create_minigroup_with_time() { + #[sqlx::test] + async fn create_minigroup_with_time(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let mut authz = TestAuthz::new(); authz.allow(agent.account_id(), vec!["classrooms"], "create"); - let mut state = TestState::new(authz).await; + let mut state = TestState::new(pool, authz).await; let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); @@ -337,11 +337,11 @@ mod tests { assert_eq!(new_minigroup.reserve(), Some(10),); } - #[tokio::test] - async fn create_minigroup_unauthorized() { + #[sqlx::test] + async fn create_minigroup_unauthorized(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - let state = TestState::new(TestAuthz::new()).await; + let state = TestState::new(pool, TestAuthz::new()).await; let scope = random_string(); @@ -362,14 +362,14 @@ mod tests { .expect_err("Unexpectedly succeeded"); } - #[tokio::test] - async fn create_minigroup_with_properties() { + #[sqlx::test] + async fn create_minigroup_with_properties(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let mut authz = TestAuthz::new(); authz.allow(agent.account_id(), vec!["classrooms"], "create"); - let mut state = TestState::new(authz).await; + let mut state = TestState::new(pool, authz).await; let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); diff --git a/src/app/api/v1/p2p/mod.rs b/src/app/api/v1/p2p/mod.rs index 3c9ae843..a05607f9 100644 --- a/src/app/api/v1/p2p/mod.rs +++ b/src/app/api/v1/p2p/mod.rs @@ -242,14 +242,14 @@ mod tests { use mockall::predicate as pred; use uuid::Uuid; - #[tokio::test] - async fn create_p2p() { + #[sqlx::test] + async fn create_p2p(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let mut authz = TestAuthz::new(); authz.allow(agent.account_id(), vec!["classrooms"], "create"); - let mut state = TestState::new(authz).await; + let mut state = TestState::new(pool, authz).await; let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); @@ -279,11 +279,11 @@ mod tests { .expect("p2p not found"); } - #[tokio::test] - async fn create_p2p_unauthorized() { + #[sqlx::test] + async fn create_p2p_unauthorized(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - let state = TestState::new(TestAuthz::new()).await; + let state = TestState::new(pool, TestAuthz::new()).await; let scope = random_string(); @@ -301,14 +301,14 @@ mod tests { .expect_err("Unexpectedly succeeded"); } - #[tokio::test] - async fn create_p2p_with_properties() { + #[sqlx::test] + async fn create_p2p_with_properties(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let mut authz = TestAuthz::new(); authz.allow(agent.account_id(), vec!["classrooms"], "create"); - let mut state = TestState::new(authz).await; + let mut state = TestState::new(pool, authz).await; let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); diff --git a/src/app/api/v1/tests/mod.rs b/src/app/api/v1/tests/mod.rs index f189daf6..43addf16 100644 --- a/src/app/api/v1/tests/mod.rs +++ b/src/app/api/v1/tests/mod.rs @@ -8,9 +8,9 @@ use super::*; use crate::app::http; use crate::test_helpers::prelude::*; -#[tokio::test] -async fn test_healthz() { - let state = TestState::new(TestAuthz::new()).await; +#[sqlx::test] +async fn test_healthz(pool: sqlx::PgPool) { + let state = TestState::new(pool, TestAuthz::new()).await; let state = Arc::new(state) as Arc; let app = http::router(state, HashMap::new()); @@ -29,15 +29,15 @@ async fn test_healthz() { assert_eq!(&body[..], b"Ok"); } -#[tokio::test] -async fn test_api_rollback() { +#[sqlx::test] +async fn test_api_rollback(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user123", USR_AUDIENCE); let token = agent.token(); let mut authz = TestAuthz::new(); authz.set_audience(SVC_AUDIENCE); authz.allow(agent.account_id(), vec!["scopes"], "rollback"); - let state = TestState::new(authz).await; + let state = TestState::new(pool, authz).await; let state = Arc::new(state) as Arc; let app = crate::app::http::router(state.clone(), make_authn()); diff --git a/src/app/api/v1/webinar/convert.rs b/src/app/api/v1/webinar/convert.rs index c6042abd..d3bcaaad 100644 --- a/src/app/api/v1/webinar/convert.rs +++ b/src/app/api/v1/webinar/convert.rs @@ -240,11 +240,11 @@ mod tests { use mockall::predicate as pred; use serde_json::{json, Value}; - #[tokio::test] - async fn convert_webinar_unauthorized() { + #[sqlx::test] + async fn convert_webinar_unauthorized(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - let state = TestState::new(TestAuthz::new()).await; + let state = TestState::new(pool, TestAuthz::new()).await; let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); @@ -269,14 +269,14 @@ mod tests { .expect_err("Unexpectedly succeeded"); } - #[tokio::test] - async fn convert_webinar() { + #[sqlx::test] + async fn convert_webinar(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let mut authz = TestAuthz::new(); authz.allow(agent.account_id(), vec!["classrooms"], "convert"); - let mut state = TestState::new(authz).await; + let mut state = TestState::new(pool, authz).await; let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); convert_webinar_mocks(&mut state, event_room_id, conference_room_id); @@ -313,14 +313,14 @@ mod tests { ); } - #[tokio::test] - async fn convert_webinar_with_recording() { + #[sqlx::test] + async fn convert_webinar_with_recording(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let mut authz = TestAuthz::new(); authz.allow(agent.account_id(), vec!["classrooms"], "convert"); - let mut state = TestState::new(authz).await; + let mut state = TestState::new(pool, authz).await; let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); convert_webinar_mocks(&mut state, event_room_id, conference_room_id); @@ -365,14 +365,14 @@ mod tests { ); } - #[tokio::test] - async fn convert_webinar_unspecified_time() { + #[sqlx::test] + async fn convert_webinar_unspecified_time(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let mut authz = TestAuthz::new(); authz.allow(agent.account_id(), vec!["classrooms"], "convert"); - let mut state = TestState::new(authz).await; + let mut state = TestState::new(pool, authz).await; let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); convert_unspecified_time_webinar_mocks(&mut state, event_room_id, conference_room_id); diff --git a/src/app/api/v1/webinar/create/replica.rs b/src/app/api/v1/webinar/create/replica.rs index eeeed083..cdf354a4 100644 --- a/src/app/api/v1/webinar/create/replica.rs +++ b/src/app/api/v1/webinar/create/replica.rs @@ -171,10 +171,10 @@ mod tests { use mockall::predicate as pred; use uuid::Uuid; - #[tokio::test] - async fn create_replica_unauthorized() { + #[sqlx::test] + async fn create_replica_unauthorized(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - let state = TestState::new(TestAuthz::new()).await; + let state = TestState::new(pool, TestAuthz::new()).await; let scope = random_string(); let class_id = Uuid::new_v4(); @@ -189,14 +189,14 @@ mod tests { .expect_err("Unexpectedly succeeded"); } - #[tokio::test] - async fn create_replica_original_webinar_not_found() { + #[sqlx::test] + async fn create_replica_original_webinar_not_found(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let mut authz = TestAuthz::new(); authz.allow(agent.account_id(), vec!["classrooms"], "create"); - let mut state = TestState::new(authz).await; + let mut state = TestState::new(pool, authz).await; let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); let class_id = Uuid::new_v4(); @@ -216,14 +216,14 @@ mod tests { .expect_err("Unexpectedly succeeded"); } - #[tokio::test] - async fn create_replica_from_original_webinar() { + #[sqlx::test] + async fn create_replica_from_original_webinar(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let mut authz = TestAuthz::new(); authz.allow(agent.account_id(), vec!["classrooms"], "create"); - let mut state = TestState::new(authz).await; + let mut state = TestState::new(pool, authz).await; let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); diff --git a/src/app/api/v1/webinar/create/webinar.rs b/src/app/api/v1/webinar/create/webinar.rs index 35af28d5..1b08315a 100644 --- a/src/app/api/v1/webinar/create/webinar.rs +++ b/src/app/api/v1/webinar/create/webinar.rs @@ -178,14 +178,14 @@ mod tests { use mockall::predicate as pred; use uuid::Uuid; - #[tokio::test] - async fn create_webinar_no_time() { + #[sqlx::test] + async fn create_webinar_no_time(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let mut authz = TestAuthz::new(); authz.allow(agent.account_id(), vec!["classrooms"], "create"); - let mut state = TestState::new(authz).await; + let mut state = TestState::new(pool, authz).await; let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); @@ -220,14 +220,14 @@ mod tests { assert_eq!(new_webinar.reserve(), Some(10)); } - #[tokio::test] - async fn create_webinar_with_time() { + #[sqlx::test] + async fn create_webinar_with_time(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let mut authz = TestAuthz::new(); authz.allow(agent.account_id(), vec!["classrooms"], "create"); - let mut state = TestState::new(authz).await; + let mut state = TestState::new(pool, authz).await; let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); @@ -268,11 +268,10 @@ mod tests { assert_eq!(new_webinar.reserve(), Some(10)); } - #[tokio::test] - async fn create_webinar_unauthorized() { + #[sqlx::test] + async fn create_webinar_unauthorized(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - - let state = TestState::new(TestAuthz::new()).await; + let state = TestState::new(pool, TestAuthz::new()).await; let scope = random_string(); @@ -293,14 +292,14 @@ mod tests { .expect_err("Unexpectedly succeeded"); } - #[tokio::test] - async fn create_webinar_with_properties() { + #[sqlx::test] + async fn create_webinar_with_properties(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); let mut authz = TestAuthz::new(); authz.allow(agent.account_id(), vec!["classrooms"], "create"); - let mut state = TestState::new(authz).await; + let mut state = TestState::new(pool, authz).await; let event_room_id = Uuid::new_v4(); let conference_room_id = Uuid::new_v4(); diff --git a/src/app/api/v1/webinar/download.rs b/src/app/api/v1/webinar/download.rs index 249e59a8..eb3ddcc5 100644 --- a/src/app/api/v1/webinar/download.rs +++ b/src/app/api/v1/webinar/download.rs @@ -79,11 +79,11 @@ mod tests { use chrono::{Duration, Utc}; use hyper::body::to_bytes; - #[tokio::test] - async fn create_webinar_timestamp() { + #[sqlx::test] + async fn create_webinar_timestamp(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - let db_pool = TestDb::new().await; + let db_pool = TestDb::new(pool); let webinar = { let mut conn = db_pool.get_conn().await; diff --git a/src/app/error.rs b/src/app/error.rs index 7e7b8a1e..2e8da560 100644 --- a/src/app/error.rs +++ b/src/app/error.rs @@ -16,7 +16,7 @@ struct ErrorKindProperties { is_notify_sentry: bool, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ErrorKind { AccessDenied, AuthorizationFailed, @@ -40,6 +40,11 @@ pub enum ErrorKind { InternalFailure, CreationWhiteboardFailed, ClassAlreadyEstablished, + OperationIdObsolete, + OperationInProgress, + OperationFailed, + NatsPublishFailed, + NatsClientNotFound, MissingTenant, } @@ -192,6 +197,36 @@ impl From for ErrorKindProperties { title: "Class already established", is_notify_sentry: false, }, + ErrorKind::OperationIdObsolete => ErrorKindProperties { + status: ResponseStatus::CONFLICT, + kind: "operation_id_obsolete", + title: "Operation id obsolete, should fetch latest state", + is_notify_sentry: false, + }, + ErrorKind::OperationInProgress => ErrorKindProperties { + status: ResponseStatus::CONFLICT, + kind: "operation_in_progress", + title: "Operation is not completed yet, retry later", + is_notify_sentry: false, + }, + ErrorKind::OperationFailed => ErrorKindProperties { + status: ResponseStatus::INTERNAL_SERVER_ERROR, + kind: "operation_failed", + title: "Operation failed really bad", + is_notify_sentry: true, + }, + ErrorKind::NatsPublishFailed => ErrorKindProperties { + status: ResponseStatus::UNPROCESSABLE_ENTITY, + kind: "nats_publish_failed", + title: "Nats publish failed", + is_notify_sentry: true, + }, + ErrorKind::NatsClientNotFound => ErrorKindProperties { + status: ResponseStatus::FAILED_DEPENDENCY, + kind: "nats_client_not_found", + title: "Nats client not found", + is_notify_sentry: true, + }, ErrorKind::MissingTenant => ErrorKindProperties { status: ResponseStatus::BAD_REQUEST, kind: "missing_tenant", @@ -241,6 +276,11 @@ impl Error { } } } + + #[cfg(test)] + pub fn kind(&self) -> ErrorKind { + self.kind + } } impl IntoResponse for Error { diff --git a/src/app/http.rs b/src/app/http.rs index c055997b..3b79668e 100644 --- a/src/app/http.rs +++ b/src/app/http.rs @@ -150,6 +150,10 @@ fn utils_router() -> Router { "/api/v1/account/properties/:property_id", get(account::read_property).put(account::update_property), ) + .metered_route( + "/api/v1/account/:id/ban", + get(account::ban::get_last_ban_operation).post(account::ban::ban), + ) .metered_route( "/api/v1/transcoding/minigroup/:id/restart", post(restart_transcoding_minigroup), diff --git a/src/app/mod.rs b/src/app/mod.rs index df7b98cd..6eecff5a 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,5 +1,5 @@ -use std::sync::Arc; use std::time::Duration; +use std::{future, sync::Arc}; use anyhow::{Context, Result}; use futures::StreamExt; @@ -67,9 +67,40 @@ pub async fn run(db: PgPool, authz_cache: Option>) -> Result agent.clone(), authz, ); + + let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(()); + + let nats_client = match &config.nats { + Some(cfg) => { + let nats_client = svc_nats_client::Client::new(cfg.clone()) + .await + .context("nats client")?; + info!("Connected to nats"); + + Some(nats_client) + } + None => None, + }; + + let state = match nats_client.clone() { + Some(nats_client) => state.add_nats_client(nats_client), + None => state, + }; + let state = Arc::new(state) as Arc; let state_ = state.clone(); + let nats_consumer = match (nats_client, &config.nats_consumer) { + (Some(nats_client), Some(cfg)) => { + svc_nats_client::consumer::run(nats_client, cfg.clone(), shutdown_rx, move |msg| { + crate::app::stage::route_message(state_.clone(), msg) + }) + } + _ => tokio::spawn(future::ready(Ok(()))), + }; + + let state_ = state.clone(); + let message_handler = Arc::new(MessageHandler::new(state_, dispatcher)); tokio::task::spawn(async move { while let Some(message) = rx.recv().await { @@ -134,6 +165,11 @@ pub async fn run(db: PgPool, authz_cache: Option>) -> Result let signals = signals_stream.next(); futures::future::select(app_future, signals).await; + shutdown_tx.send(()).ok(); + + if let Err(err) = nats_consumer.await { + tracing::error!(%err, "nats consumer failed"); + } metrics_server.shutdown().await; @@ -240,11 +276,12 @@ fn build_tq_client(config: &Config, token: &str) -> Arc { mod api; mod authz; -mod error; +pub mod error; mod http; mod info; mod metrics; mod postprocessing_strategy; pub mod services; +mod stage; mod tide_state; pub mod turn_host; diff --git a/src/app/postprocessing_strategy/minigroup/tests.rs b/src/app/postprocessing_strategy/minigroup/tests.rs index edbc5c11..2209a7af 100644 --- a/src/app/postprocessing_strategy/minigroup/tests.rs +++ b/src/app/postprocessing_strategy/minigroup/tests.rs @@ -14,10 +14,10 @@ mod handle_upload { use super::super::super::PostprocessingStrategy; use super::super::*; - #[tokio::test] - async fn handle_upload_stream() { + #[sqlx::test] + async fn handle_upload_stream(pool: sqlx::PgPool) { let now = Utc::now(); - let mut state = TestState::new(TestAuthz::new()).await; + let mut state = TestState::new(pool, TestAuthz::new()).await; let conference_room_id = Uuid::new_v4(); let event_room_id = Uuid::new_v4(); let agent1 = TestAgent::new("web", "user1", USR_AUDIENCE); @@ -221,10 +221,10 @@ mod handle_upload { assert_eq!(&recording2.created_by, agent2.agent_id()); } - #[tokio::test] - async fn handle_upload_mjr() { + #[sqlx::test] + async fn handle_upload_mjr(pool: sqlx::PgPool) { let now = Utc::now(); - let mut state = TestState::new(TestAuthz::new()).await; + let mut state = TestState::new(pool, TestAuthz::new()).await; let conference_room_id = Uuid::new_v4(); let event_room_id = Uuid::new_v4(); @@ -342,12 +342,12 @@ mod handle_adjust { use super::super::super::PostprocessingStrategy; use super::super::*; - #[tokio::test] - async fn handle_adjust() { + #[sqlx::test] + async fn handle_adjust(pool: sqlx::PgPool) { let now = Utc::now(); let agent1 = TestAgent::new("web", "user1", USR_AUDIENCE); let agent2 = TestAgent::new("web", "user2", USR_AUDIENCE); - let mut state = TestState::new(TestAuthz::new()).await; + let mut state = TestState::new(pool, TestAuthz::new()).await; let event_room_id = Uuid::new_v4(); let original_event_room_id = Uuid::new_v4(); let modified_event_room_id = Uuid::new_v4(); @@ -582,12 +582,12 @@ mod handle_adjust { } } - #[tokio::test] - async fn handle_adjust_with_pin_and_unpin() { + #[sqlx::test] + async fn handle_adjust_with_pin_and_unpin(pool: sqlx::PgPool) { let now = Utc::now(); let agent1 = TestAgent::new("web", "user1", USR_AUDIENCE); let agent2 = TestAgent::new("web", "user2", USR_AUDIENCE); - let mut state = TestState::new(TestAuthz::new()).await; + let mut state = TestState::new(pool, TestAuthz::new()).await; let event_room_id = Uuid::new_v4(); let original_event_room_id = Uuid::new_v4(); let modified_event_room_id = Uuid::new_v4(); @@ -822,12 +822,12 @@ mod handle_transcoding_completion { use super::super::super::PostprocessingStrategy; use super::super::*; - #[tokio::test] - async fn handle_transcoding_completion() { + #[sqlx::test] + async fn handle_transcoding_completion(pool: sqlx::PgPool) { let now = Utc::now(); let agent1 = TestAgent::new("web", "user1", USR_AUDIENCE); let agent2 = TestAgent::new("web", "user2", USR_AUDIENCE); - let state = TestState::new(TestAuthz::new()).await; + let state = TestState::new(pool, TestAuthz::new()).await; // Insert a minigroup with recordings. let (minigroup, recording1, recording2) = { @@ -969,19 +969,19 @@ mod collect_pinned_events { .room_id(event_room_id) .set(PIN_EVENT_TYPE.to_string()) .data(EventData::Pin(PinEventData::new(agent_id.to_owned()))) - .occurred_at(2041237_463_815) + .occurred_at(2_041_237_463_815) .build(), EventBuilder::new() .room_id(event_room_id) .set(PIN_EVENT_TYPE.to_string()) .data(EventData::Pin(PinEventData::new(agent_id.to_owned()))) - .occurred_at(2041238_581_600) + .occurred_at(2_041_238_581_600) .build(), EventBuilder::new() .room_id(event_room_id) .set(PIN_EVENT_TYPE.to_string()) .data(EventData::Pin(PinEventData::null())) - .occurred_at(2093817_792_770) + .occurred_at(2_093_817_792_770) .build(), ]; @@ -1013,13 +1013,13 @@ mod collect_pinned_events { .room_id(event_room_id) .set(PIN_EVENT_TYPE.to_string()) .data(EventData::Pin(PinEventData::new(agent_id.to_owned()))) - .occurred_at(3312020_000_001) + .occurred_at(3_312_020_000_001) .build(), EventBuilder::new() .room_id(event_room_id) .set(PIN_EVENT_TYPE.to_string()) .data(EventData::Pin(PinEventData::null())) - .occurred_at(3312020_000_003) + .occurred_at(3_312_020_000_003) .build(), ]; diff --git a/src/app/stage/ban.rs b/src/app/stage/ban.rs new file mode 100644 index 00000000..6735b49b --- /dev/null +++ b/src/app/stage/ban.rs @@ -0,0 +1,634 @@ +use sqlx::PgConnection; +use svc_agent::AgentId; +use svc_authn::AccountId; +use svc_events::{ + ban::{ + BanAcceptedV1, BanCollaborationCompletedV1, BanCompletedV1, BanIntentV1, BanRejectedV1, + BanVideoStreamingCompletedV1, + }, + EventId, +}; + +use crate::{ + app::{ + error::{Error, ErrorExt, ErrorKind as AppErrorKind}, + AppContext, + }, + clients::nats, + db::{self, ban_account_op, ban_history}, +}; + +use super::{FailureKind, HandleMessageFailure}; + +const ENTITY_TYPE: &str = "ban"; + +const INTENT_OP: &str = "intent"; +const ACCEPTED_OP: &str = "accepted"; +const REJECTED_OP: &str = "rejected"; +const COMPLETED_OP: &str = "completed"; + +pub async fn save_intent( + ctx: &dyn AppContext, + conn: &mut PgConnection, + ban: bool, + class: &db::class::Object, + sender: AgentId, + target_account: AccountId, + last_operation_id: i64, +) -> Result<(), Error> { + let event_id = get_next_event_id(conn).await?; + let event = BanIntentV1 { + ban, + classroom_id: class.id(), + sender, + target_account, + last_operation_id, + }; + nats::publish_event( + ctx, + class.id(), + &event_id, + event.into(), + nats::Options::default(), + ) + .await +} + +async fn get_next_event_id(conn: &mut PgConnection) -> Result { + let next_seq_id = ban_account_op::get_next_seq_id(conn) + .await + .error(AppErrorKind::DbQueryFailed)?; + + let event_id = ( + ENTITY_TYPE.to_owned(), + INTENT_OP.to_owned(), + next_seq_id.value, + ) + .into(); + + Ok(event_id) +} + +pub async fn handle_intent( + ctx: &dyn AppContext, + intent: BanIntentV1, + intent_id: EventId, +) -> Result<(), HandleMessageFailure> { + let mut conn = ctx + .get_conn() + .await + .error(AppErrorKind::DbConnAcquisitionFailed) + .transient()?; + + // We need to update db here first, then schedule next stage, then acknowledge + // current message. In case if we fail somewhere in between, we can continue + // processing as if nothing happened -- subsequent upserts will be successful, + // attempts to schedule the same message will fail (dedup). + + let op = ban_account_op::UpsertQuery::new_operation( + intent.target_account.clone(), + intent.last_operation_id, + intent_id.sequence_id(), + ) + .execute(&mut conn) + .await + .error(AppErrorKind::DbQueryFailed) + .transient()?; + + match op { + Some(op) => { + if intent.ban { + ban_history::InsertQuery::new( + intent.classroom_id, + &intent.target_account, + op.last_op_id, + ) + .execute(&mut conn) + .await + .error(AppErrorKind::DbQueryFailed) + .transient()?; + } else { + ban_history::FinishBanQuery::new( + intent.classroom_id, + &intent.target_account, + op.last_op_id, + ) + .execute(&mut conn) + .await + .error(AppErrorKind::DbQueryFailed) + .transient()?; + } + + super::ban::accept(ctx, intent, intent_id) + .await + .transient()?; + } + // failed to upsert -- we've lost the race + None => { + reject(ctx, intent, &intent_id).await.transient()?; + } + } + + Ok(()) +} + +pub async fn accept( + ctx: &dyn AppContext, + intent: BanIntentV1, + intent_id: EventId, +) -> Result<(), Error> { + let event_id = ( + ENTITY_TYPE.to_owned(), + ACCEPTED_OP.to_owned(), + intent_id.sequence_id(), + ) + .into(); + + let event = BanAcceptedV1 { + ban: intent.ban, + classroom_id: intent.classroom_id, + target_account: intent.target_account, + operation_id: intent_id.sequence_id(), + }; + + nats::publish_event( + ctx, + event.classroom_id, + &event_id, + event.into(), + nats::Options::default(), + ) + .await +} + +async fn reject( + ctx: &dyn AppContext, + intent: BanIntentV1, + intent_id: &EventId, +) -> Result<(), Error> { + let event = BanRejectedV1 { + ban: intent.ban, + classroom_id: intent.classroom_id, + target_account: intent.target_account, + operation_id: intent_id.sequence_id(), + }; + let event_id = ( + ENTITY_TYPE.to_owned(), + REJECTED_OP.to_owned(), + intent_id.sequence_id(), + ) + .into(); + nats::publish_event( + ctx, + event.classroom_id, + &event_id, + event.into(), + nats::Options::default().receiver_id(intent.sender), + ) + .await +} + +pub async fn handle_video_streaming_banned( + ctx: &dyn AppContext, + video_streaming_banned: BanVideoStreamingCompletedV1, +) -> Result<(), HandleMessageFailure> { + let mut conn = ctx + .get_conn() + .await + .error(AppErrorKind::DbConnAcquisitionFailed) + .transient()?; + + let op = ban_account_op::UpdateQuery::new_video_streaming_banned( + video_streaming_banned.target_account.clone(), + video_streaming_banned.operation_id, + ) + .execute(&mut conn) + .await + .error(AppErrorKind::DbQueryFailed) + .transient()? + .ok_or(Error::from(AppErrorKind::OperationFailed)) + .permanent()?; + + if op.is_completed() { + let original_event_id = video_streaming_banned.parent.clone(); + finish(ctx, video_streaming_banned, original_event_id) + .await + .transient()?; + } + + Ok(()) +} + +pub async fn handle_collaboration_banned( + ctx: &dyn AppContext, + collaboration_banned: BanCollaborationCompletedV1, +) -> Result<(), HandleMessageFailure> { + let mut conn = ctx + .get_conn() + .await + .error(AppErrorKind::DbConnAcquisitionFailed) + .transient()?; + + let op = ban_account_op::UpdateQuery::new_collaboration_banned( + collaboration_banned.target_account.clone(), + collaboration_banned.operation_id, + ) + .execute(&mut conn) + .await + .error(AppErrorKind::DbQueryFailed) + .transient()? + .ok_or(Error::from(AppErrorKind::OperationFailed)) + .permanent()?; + + if op.is_completed() { + let original_event_id = collaboration_banned.parent.clone(); + finish(ctx, collaboration_banned, original_event_id) + .await + .transient()?; + } + + Ok(()) +} + +async fn finish( + ctx: &dyn AppContext, + event: impl Into, + original_event_id: EventId, +) -> Result<(), Error> { + let event_id = ( + ENTITY_TYPE.to_owned(), + COMPLETED_OP.to_owned(), + original_event_id.sequence_id(), + ) + .into(); + let event: BanCompletedV1 = event.into(); + nats::publish_event( + ctx, + event.classroom_id, + &event_id, + event.into(), + nats::Options::default(), + ) + .await +} + +#[cfg(test)] +mod tests { + use svc_events::{Event, EventV1}; + + use super::*; + + use crate::app::AppContext; + use crate::test_helpers::prelude::*; + + #[sqlx::test] + async fn handles_intents(pool: sqlx::PgPool) { + let state = TestState::new(pool, TestAuthz::new()).await; + + let minigroup = { + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + factory::Minigroup::new( + random_string(), + USR_AUDIENCE.to_string(), + (Bound::Unbounded, Bound::Unbounded).into(), + Uuid::new_v4(), + Uuid::new_v4(), + ) + .insert(&mut conn) + .await + }; + + let agent1 = TestAgent::new("web", "user1", USR_AUDIENCE); + + let intent = BanIntentV1 { + classroom_id: minigroup.id(), + ban: true, + sender: agent1.agent_id().clone(), + last_operation_id: 0, + target_account: agent1.account_id().clone(), + }; + let intent_id: EventId = ("ban".to_string(), "intent".to_string(), 0).into(); + + handle_intent(&state, intent.clone(), intent_id) + .await + .expect("failed to handle intent"); + + { + let pub_reqs = state.inspect_nats_client().get_publish_requests(); + assert_eq!(pub_reqs.len(), 1); + + let payload = serde_json::from_slice::(&pub_reqs[0].payload) + .expect("failed to parse event"); + assert!(matches!(payload, Event::V1(EventV1::BanAccepted(..)))); + } + + // should fail b/c we already started an operation with another sequence id + let intent_id: EventId = ("ban".to_string(), "intent".to_string(), 1).into(); + + handle_intent(&state, intent.clone(), intent_id) + .await + .expect("failed to handle intent"); + + { + let pub_reqs = state.inspect_nats_client().get_publish_requests(); + assert_eq!(pub_reqs.len(), 2); + + let payload = serde_json::from_slice::(&pub_reqs[1].payload) + .expect("failed to parse event"); + assert!(matches!(payload, Event::V1(EventV1::BanRejected(..)))); + } + } + + #[sqlx::test] + async fn fails_to_handle_video_streaming_banned_if_there_was_no_intent(pool: sqlx::PgPool) { + let state = TestState::new(pool, TestAuthz::new()).await; + let agent1 = TestAgent::new("web", "user1", USR_AUDIENCE); + + let video_streaming_banned = BanVideoStreamingCompletedV1 { + ban: true, + classroom_id: Uuid::new_v4(), + target_account: agent1.account_id().clone(), + operation_id: 0, + parent: ("ban".to_owned(), "accepted".to_owned(), 0).into(), + }; + + let r = handle_video_streaming_banned(&state, video_streaming_banned).await; + assert!(matches!( + r, + Err(HandleMessageFailure::Permanent( + e @ crate::app::error::Error { .. } + )) if e.kind() == AppErrorKind::OperationFailed + )); + + { + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + let r = db::ban_account_op::ReadQuery::by_account_id(agent1.account_id()) + .execute(&mut conn) + .await + .expect("failed to fetch ban account op entry"); + assert!(r.is_none()); + } + + { + let pub_reqs = state.inspect_nats_client().get_publish_requests(); + assert_eq!(pub_reqs.len(), 0); + } + } + + #[sqlx::test] + async fn fails_to_handle_collaboration_banned_if_there_was_no_intent(pool: sqlx::PgPool) { + let state = TestState::new(pool, TestAuthz::new()).await; + let agent1 = TestAgent::new("web", "user1", USR_AUDIENCE); + + let collaboration_banned = BanCollaborationCompletedV1 { + ban: true, + classroom_id: Uuid::new_v4(), + target_account: agent1.account_id().clone(), + operation_id: 0, + parent: ("ban".to_owned(), "accepted".to_owned(), 0).into(), + }; + + let r = handle_collaboration_banned(&state, collaboration_banned).await; + assert!(matches!( + r, + Err(HandleMessageFailure::Permanent( + e @ crate::app::error::Error { .. } + )) if e.kind() == AppErrorKind::OperationFailed + )); + + { + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + let r = db::ban_account_op::ReadQuery::by_account_id(agent1.account_id()) + .execute(&mut conn) + .await + .expect("failed to fetch ban account op entry"); + assert!(r.is_none()); + } + + { + let pub_reqs = state.inspect_nats_client().get_publish_requests(); + assert_eq!(pub_reqs.len(), 0); + } + } + + #[sqlx::test] + async fn handles_video_streaming_banned(pool: sqlx::PgPool) { + let state = TestState::new(pool, TestAuthz::new()).await; + + let minigroup = { + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + factory::Minigroup::new( + random_string(), + USR_AUDIENCE.to_string(), + (Bound::Unbounded, Bound::Unbounded).into(), + Uuid::new_v4(), + Uuid::new_v4(), + ) + .insert(&mut conn) + .await + }; + + let agent1 = TestAgent::new("web", "user-video", USR_AUDIENCE); + + { + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + db::ban_account_op::UpsertQuery::new_operation(agent1.account_id().clone(), 0, 0) + .execute(&mut conn) + .await + .expect("failed to run upsert query"); + }; + + let video_streaming_banned = BanVideoStreamingCompletedV1 { + ban: true, + classroom_id: minigroup.id(), + target_account: agent1.account_id().clone(), + operation_id: 0, + parent: ("ban".to_owned(), "accepted".to_owned(), 0).into(), + }; + + handle_video_streaming_banned(&state, video_streaming_banned) + .await + .expect("failed to handle video streaming banned"); + + { + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + let r = db::ban_account_op::ReadQuery::by_account_id(agent1.account_id()) + .execute(&mut conn) + .await + .expect("failed to fetch ban account op entry"); + assert!(matches!( + r, + Some(db::ban_account_op::Object { + last_op_id: 0, + is_video_streaming_banned: true, + .. + }) + )); + } + + { + let pub_reqs = state.inspect_nats_client().get_publish_requests(); + assert_eq!(pub_reqs.len(), 0); + } + } + + #[sqlx::test] + async fn handles_collaboration_banned(pool: sqlx::PgPool) { + let state = TestState::new(pool, TestAuthz::new()).await; + + let minigroup = { + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + factory::Minigroup::new( + random_string(), + USR_AUDIENCE.to_string(), + (Bound::Unbounded, Bound::Unbounded).into(), + Uuid::new_v4(), + Uuid::new_v4(), + ) + .insert(&mut conn) + .await + }; + + let agent1 = TestAgent::new("web", "user-collab", USR_AUDIENCE); + + { + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + db::ban_account_op::UpsertQuery::new_operation(agent1.account_id().clone(), 0, 0) + .execute(&mut conn) + .await + .expect("failed to run upsert query"); + }; + + let collaboration_banned = BanCollaborationCompletedV1 { + ban: true, + classroom_id: minigroup.id(), + target_account: agent1.account_id().clone(), + operation_id: 0, + parent: ("ban".to_owned(), "accepted".to_owned(), 0).into(), + }; + + handle_collaboration_banned(&state, collaboration_banned) + .await + .expect("failed to handle collaboration banned"); + + { + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + let r = db::ban_account_op::ReadQuery::by_account_id(agent1.account_id()) + .execute(&mut conn) + .await + .expect("failed to fetch ban account op entry"); + assert!(matches!( + r, + Some(db::ban_account_op::Object { + last_op_id: 0, + is_collaboration_banned: true, + .. + }) + )); + } + + { + let pub_reqs = state.inspect_nats_client().get_publish_requests(); + assert_eq!(pub_reqs.len(), 0); + } + } + + #[sqlx::test] + async fn finishes_operation(pool: sqlx::PgPool) { + let state = TestState::new(pool, TestAuthz::new()).await; + + let minigroup = { + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + factory::Minigroup::new( + random_string(), + USR_AUDIENCE.to_string(), + (Bound::Unbounded, Bound::Unbounded).into(), + Uuid::new_v4(), + Uuid::new_v4(), + ) + .insert(&mut conn) + .await + }; + + let agent1 = TestAgent::new("web", "user-finish", USR_AUDIENCE); + + { + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + db::ban_account_op::UpsertQuery::new_operation(agent1.account_id().clone(), 0, 0) + .execute(&mut conn) + .await + .expect("failed to run upsert query"); + }; + + let video_streaming_banned = BanVideoStreamingCompletedV1 { + ban: true, + classroom_id: minigroup.id(), + target_account: agent1.account_id().clone(), + operation_id: 0, + parent: ("ban".to_owned(), "accepted".to_owned(), 0).into(), + }; + + handle_video_streaming_banned(&state, video_streaming_banned) + .await + .expect("failed to handle video streaming banned"); + + { + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + let r = db::ban_account_op::ReadQuery::by_account_id(agent1.account_id()) + .execute(&mut conn) + .await + .expect("failed to fetch ban account op entry"); + assert!(matches!( + r, + Some(db::ban_account_op::Object { + last_op_id: 0, + is_video_streaming_banned: true, + .. + }) + )); + } + + { + let pub_reqs = state.inspect_nats_client().get_publish_requests(); + assert_eq!(pub_reqs.len(), 0); + } + + let collaboration_banned = BanCollaborationCompletedV1 { + ban: true, + classroom_id: minigroup.id(), + target_account: agent1.account_id().clone(), + operation_id: 0, + parent: ("ban".to_owned(), "accepted".to_owned(), 0).into(), + }; + + handle_collaboration_banned(&state, collaboration_banned) + .await + .expect("failed to handle collaboration banned"); + + { + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + let r = db::ban_account_op::ReadQuery::by_account_id(agent1.account_id()) + .execute(&mut conn) + .await + .expect("failed to fetch ban account op entry"); + assert!(matches!( + r, + Some(db::ban_account_op::Object { + last_op_id: 0, + is_collaboration_banned: true, + is_video_streaming_banned: true, + .. + }) + )); + } + + { + let pub_reqs = state.inspect_nats_client().get_publish_requests(); + assert_eq!(pub_reqs.len(), 1); + + let payload = serde_json::from_slice::(&pub_reqs[0].payload) + .expect("failed to parse event"); + assert!(matches!(payload, Event::V1(EventV1::BanCompleted(..)))); + } + } +} diff --git a/src/app/stage/mod.rs b/src/app/stage/mod.rs new file mode 100644 index 00000000..fc5a9d76 --- /dev/null +++ b/src/app/stage/mod.rs @@ -0,0 +1,72 @@ +use std::{convert::TryFrom, str::FromStr, sync::Arc}; + +use anyhow::Context; +use svc_events::{Event, EventV1}; +use svc_nats_client::{ + consumer::{FailureKind, FailureKindExt, HandleMessageFailure}, + Subject, +}; + +use crate::db; + +use super::AppContext; + +pub mod ban; + +pub async fn route_message( + ctx: Arc, + msg: Arc, +) -> Result<(), HandleMessageFailure> { + let subject = Subject::from_str(&msg.subject) + .context("parse nats subject") + .permanent()?; + + let event = serde_json::from_slice::(msg.payload.as_ref()) + .context("parse nats payload") + .permanent()?; + + let classroom_id = subject.classroom_id(); + let _room = { + let mut conn = ctx + .get_conn() + .await + .map_err(|e| anyhow::anyhow!(e)) + .transient()?; + + db::class::ReadQuery::by_id(classroom_id) + .execute(&mut conn) + .await + .context("find room by classroom_id") + .transient()? + .ok_or(anyhow!( + "failed to get room by classroom_id: {}", + classroom_id + )) + .permanent()? + }; + + tracing::info!(?event, class_id = %classroom_id); + + let headers = svc_nats_client::Headers::try_from(msg.headers.clone().unwrap_or_default()) + .context("parse nats headers") + .permanent()?; + let _agent_id = headers.sender_id(); + let event_id = headers.event_id(); + + let r = match event { + Event::V1(e) => match e { + EventV1::BanIntent(intent) => { + ban::handle_intent(ctx.as_ref(), intent, event_id.clone()).await + } + EventV1::BanVideoStreamingCompleted(event) => { + ban::handle_video_streaming_banned(ctx.as_ref(), event).await + } + EventV1::BanCollaborationCompleted(event) => { + ban::handle_collaboration_banned(ctx.as_ref(), event).await + } + _ => Ok(()), + }, + }; + + FailureKindExt::map_err(r, |e| anyhow::anyhow!(e)) +} diff --git a/src/app/tide_state/mod.rs b/src/app/tide_state/mod.rs index 2a0c3661..52ece2fa 100644 --- a/src/app/tide_state/mod.rs +++ b/src/app/tide_state/mod.rs @@ -8,6 +8,7 @@ use svc_agent::error::Error as AgentError; use svc_agent::mqtt::{Agent, IntoPublishableMessage}; use svc_agent::AgentId; use svc_authz::ClientMap as Authz; +use svc_nats_client::NatsClient; use url::Url; use crate::clients::conference::ConferenceClient; @@ -32,6 +33,7 @@ pub trait AppContext: Sync + Send { fn config(&self) -> &Config; fn agent(&self) -> Option<&Agent>; fn turn_host_selector(&self) -> &TurnHostSelector; + fn nats_client(&self) -> Option>; fn get_preroll_offset(&self, audience: &str) -> i64 { self.config() @@ -63,6 +65,7 @@ pub struct TideState { tq_client: Arc, authz: Authz, turn_host_selector: TurnHostSelector, + nats_client: Option>, } impl TideState { @@ -86,6 +89,14 @@ impl TideState { tq_client, authz, turn_host_selector, + nats_client: None, + } + } + + pub fn add_nats_client(self, nats_client: impl NatsClient + 'static) -> Self { + Self { + nats_client: Some(Arc::new(nats_client)), + ..self } } } @@ -146,6 +157,10 @@ impl AppContext for TideState { fn turn_host_selector(&self) -> &TurnHostSelector { &self.turn_host_selector } + + fn nats_client(&self) -> Option> { + self.nats_client.clone() + } } pub mod message_handler; diff --git a/src/clients/mod.rs b/src/clients/mod.rs index b0058a01..59894cae 100644 --- a/src/clients/mod.rs +++ b/src/clients/mod.rs @@ -53,4 +53,5 @@ fn generate_correlation_data() -> String { pub mod conference; pub mod event; +pub mod nats; pub mod tq; diff --git a/src/clients/nats.rs b/src/clients/nats.rs new file mode 100644 index 00000000..156d4719 --- /dev/null +++ b/src/clients/nats.rs @@ -0,0 +1,61 @@ +use svc_agent::AgentId; +use uuid::Uuid; + +use svc_events::{Event, EventId}; + +use crate::app::{ + error::{Error, ErrorExt, ErrorKind as AppErrorKind}, + AppContext, +}; + +const SUBJECT_PREFIX: &str = "classroom"; + +#[derive(Default)] +pub struct Options { + receiver_id: Option, +} + +impl Options { + pub fn receiver_id(self, r_id: AgentId) -> Self { + Self { + receiver_id: Some(r_id), + } + } +} + +pub async fn publish_event( + ctx: &dyn AppContext, + classroom_id: Uuid, + id: &EventId, + event: Event, + opts: Options, +) -> Result<(), Error> { + let subject = svc_nats_client::Subject::new( + SUBJECT_PREFIX.to_string(), + classroom_id, + id.entity_type().to_string(), + ); + + let payload = serde_json::to_vec(&event).error(AppErrorKind::InternalFailure)?; + + let event_b = svc_nats_client::event::Builder::new( + subject, + payload, + id.to_owned(), + ctx.agent_id().to_owned(), + ); + + let event_b = match opts.receiver_id { + Some(r_id) => event_b.receiver_id(r_id), + None => event_b, + }; + + let event = event_b.build(); + + ctx.nats_client() + .ok_or_else(|| anyhow!("nats client not found")) + .error(AppErrorKind::NatsClientNotFound)? + .publish(&event) + .await + .error(AppErrorKind::NatsPublishFailed) +} diff --git a/src/config.rs b/src/config.rs index a756091f..cca50cc0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -29,6 +29,8 @@ pub struct Config { pub retry_delay: Duration, pub turn_hosts: vec1::Vec1, pub frontend: HashMap, + pub nats: Option, + pub nats_consumer: Option, } #[derive(Clone, Debug, Deserialize)] diff --git a/src/db/ban_account_op.rs b/src/db/ban_account_op.rs new file mode 100644 index 00000000..bebe4254 --- /dev/null +++ b/src/db/ban_account_op.rs @@ -0,0 +1,295 @@ +use sqlx::PgConnection; +use svc_authn::AccountId; + +#[derive(Debug)] +pub struct Object { + pub user_account: AccountId, + pub last_op_id: i64, + pub is_video_streaming_banned: bool, + pub is_collaboration_banned: bool, +} + +impl Object { + pub fn is_completed(&self) -> bool { + self.is_video_streaming_banned && self.is_collaboration_banned + } +} + +pub struct ReadQuery<'a> { + user_account: &'a AccountId, +} + +impl<'a> ReadQuery<'a> { + pub fn by_account_id(user_account: &'a AccountId) -> Self { + Self { user_account } + } + + pub async fn execute(self, conn: &mut PgConnection) -> sqlx::Result> { + sqlx::query_as!( + Object, + r#" + SELECT + user_account AS "user_account: _", + last_op_id AS "last_op_id: _", + is_video_streaming_banned, + is_collaboration_banned + FROM ban_account_op + WHERE + user_account = $1 + LIMIT 1; + "#, + self.user_account as &AccountId, + ) + .fetch_optional(conn) + .await + } +} + +pub struct NextSeqId { + pub value: i64, +} + +pub async fn get_next_seq_id(conn: &mut PgConnection) -> sqlx::Result { + sqlx::query_as!( + NextSeqId, + r#"SELECT nextval('ban_entity_seq_id') as "value!: i64";"# + ) + .fetch_one(conn) + .await +} + +/// Upsert works only if provided `last_op_id` equals to the one stored +/// in database or `last_op_id` equals to `new_op_id`. +/// Returns `None` if `last_op_id` in database differs. +pub struct UpsertQuery { + user_account: AccountId, + last_op_id: i64, + new_op_id: i64, +} + +impl UpsertQuery { + pub fn new_operation(user_account: AccountId, last_op_id: i64, new_op_id: i64) -> Self { + Self { + user_account, + last_op_id, + new_op_id, + } + } + + pub async fn execute(self, conn: &mut PgConnection) -> sqlx::Result> { + sqlx::query_as!( + Object, + r#" + INSERT INTO ban_account_op (user_account, last_op_id) + VALUES ($1, $2) + ON CONFLICT (user_account) DO UPDATE + SET + -- to reset sub-operation trackers only if op_id has changed + is_video_streaming_banned = ($2 = $3 AND ban_account_op.is_video_streaming_banned), + is_collaboration_banned = ($2 = $3 AND ban_account_op.is_collaboration_banned), + last_op_id = EXCLUDED.last_op_id + WHERE + -- allow to 'complete' operation if there's no change in last_op_id + -- or allow to do upsert without real changes so we can process + -- the same message twice + ban_account_op.last_op_id = EXCLUDED.last_op_id OR + -- allow change op id if the previous operation is completed + ( + ban_account_op.last_op_id = $3 AND + ban_account_op.is_video_streaming_banned = true AND + ban_account_op.is_collaboration_banned = true + ) + RETURNING + user_account AS "user_account: _", + last_op_id, + is_video_streaming_banned, + is_collaboration_banned + "#, + self.user_account as AccountId, + self.new_op_id, + self.last_op_id + ) + .fetch_optional(conn) + .await + } +} + +pub struct UpdateQuery { + user_account: AccountId, + is_video_streaming_banned: Option, + is_collaboration_banned: Option, + last_op_id: i64, +} + +impl UpdateQuery { + pub fn new_video_streaming_banned(user_account: AccountId, op_id: i64) -> Self { + Self { + user_account, + is_video_streaming_banned: Some(true), + is_collaboration_banned: None, + last_op_id: op_id, + } + } + + pub fn new_collaboration_banned(user_account: AccountId, op_id: i64) -> Self { + Self { + user_account, + is_video_streaming_banned: None, + is_collaboration_banned: Some(true), + last_op_id: op_id, + } + } + + pub async fn execute(self, conn: &mut PgConnection) -> sqlx::Result> { + sqlx::query_as!( + Object, + r#" + UPDATE ban_account_op + SET + is_video_streaming_banned = COALESCE($2, ban_account_op.is_video_streaming_banned), + is_collaboration_banned = COALESCE($3, ban_account_op.is_collaboration_banned) + WHERE + ban_account_op.last_op_id = $4 + AND ban_account_op.user_account = $1 + RETURNING + user_account AS "user_account: _", + last_op_id, + is_video_streaming_banned, + is_collaboration_banned + "#, + self.user_account as AccountId, + self.is_video_streaming_banned, + self.is_collaboration_banned, + self.last_op_id + ) + .fetch_optional(conn) + .await + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::app::AppContext; + use crate::test_helpers::prelude::*; + + #[sqlx::test] + async fn doesnt_allow_to_change_op_id_until_operation_is_completed(pool: sqlx::PgPool) { + let state = TestState::new(pool, TestAuthz::new()).await; + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + + let agent1 = TestAgent::new("web", "user1", USR_AUDIENCE); + + let r = UpsertQuery::new_operation(agent1.account_id().clone(), 0, 0) + .execute(&mut conn) + .await + .expect("failed to start new ban operation"); + assert!(r.is_some()); + + async fn test_possible_failure_states(agent: &TestAgent, conn: &mut sqlx::PgConnection) { + // should fail b/c previous operation is not completed yet + let r = UpsertQuery::new_operation(agent.account_id().clone(), 0, 100) + .execute(conn) + .await + .expect("failed to start new ban operation"); + assert!(r.is_none()); + + // should fail b/c we passed the wrong previous operation id + let r = UpsertQuery::new_operation(agent.account_id().clone(), 50, 100) + .execute(conn) + .await + .expect("failed to start new ban operation"); + assert!(r.is_none()); + + // should be ok since we didn't change anything (idempotency) + let r = UpsertQuery::new_operation(agent.account_id().clone(), 0, 0) + .execute(conn) + .await + .expect("failed to start new ban operation"); + assert!(r.is_some()); + } + + test_possible_failure_states(&agent1, &mut conn).await; + + // should fail b/c we passed to wrong operation id + let r = UpdateQuery::new_collaboration_banned(agent1.account_id().clone(), 10) + .execute(&mut conn) + .await + .expect("failed to complete collaboration ban"); + assert!(r.is_none()); + + let r = UpdateQuery::new_collaboration_banned(agent1.account_id().clone(), 0) + .execute(&mut conn) + .await + .expect("failed to complete collaboration ban"); + assert!(r.is_some()); + assert!(matches!( + r, + Some(Object { + is_collaboration_banned: true, + last_op_id: 0, + .. + }) + )); + + // should be ok to run twice (idempotency) + let r = UpdateQuery::new_collaboration_banned(agent1.account_id().clone(), 0) + .execute(&mut conn) + .await + .expect("failed to complete collaboration ban"); + assert!(r.is_some()); + + test_possible_failure_states(&agent1, &mut conn).await; + + // should fail b/c we passed to wrong operation id + let r = UpdateQuery::new_video_streaming_banned(agent1.account_id().clone(), 10) + .execute(&mut conn) + .await + .expect("failed to complete video streaming ban"); + assert!(r.is_none()); + + let r = UpdateQuery::new_video_streaming_banned(agent1.account_id().clone(), 0) + .execute(&mut conn) + .await + .expect("failed to complete video streaming ban"); + assert!(r.is_some()); + assert!(matches!( + r, + Some(Object { + is_video_streaming_banned: true, + is_collaboration_banned: true, + last_op_id: 0, + .. + }) + )); + + // should be ok to run twice (idempotency) + let r = UpdateQuery::new_video_streaming_banned(agent1.account_id().clone(), 0) + .execute(&mut conn) + .await + .expect("failed to complete video streaming ban"); + assert!(r.is_some()); + + // should fail b/c we passed the wrong previous operation id + let r = UpsertQuery::new_operation(agent1.account_id().clone(), 50, 100) + .execute(&mut conn) + .await + .expect("failed to start new ban operation"); + assert!(r.is_none()); + + // should be ok since we didn't change anything (idempotency) + let r = UpsertQuery::new_operation(agent1.account_id().clone(), 0, 0) + .execute(&mut conn) + .await + .expect("failed to start new ban operation"); + assert!(r.is_some()); + + // should be ok to start new operation afterwards + let r = UpsertQuery::new_operation(agent1.account_id().clone(), 0, 1) + .execute(&mut conn) + .await + .expect("failed to complete video streaming ban"); + assert!(r.is_some()); + } +} diff --git a/src/db/ban_history.rs b/src/db/ban_history.rs new file mode 100644 index 00000000..13cab186 --- /dev/null +++ b/src/db/ban_history.rs @@ -0,0 +1,207 @@ +use chrono::{DateTime, Utc}; +use svc_authn::AccountId; +use uuid::Uuid; + +#[derive(Debug, PartialEq, Eq)] +pub struct Object { + #[allow(unused)] + target_account: AccountId, + #[allow(unused)] + class_id: Uuid, + #[allow(unused)] + banned_at: DateTime, + #[allow(unused)] + banned_operation_id: i64, + #[allow(unused)] + unbanned_at: Option>, + #[allow(unused)] + unbanned_operation_id: Option, +} + +pub struct InsertQuery<'a> { + class_id: Uuid, + target_account: &'a AccountId, + operation_id: i64, +} + +impl<'a> InsertQuery<'a> { + pub fn new(class_id: Uuid, target_account: &'a AccountId, operation_id: i64) -> Self { + Self { + class_id, + target_account, + operation_id, + } + } + + pub async fn execute(&self, conn: &mut sqlx::PgConnection) -> sqlx::Result { + sqlx::query_as!( + Object, + r#" + WITH i AS ( + INSERT INTO ban_history (class_id, target_account, banned_operation_id) + VALUES ($1, $2, $3) + -- this allows us to run this query many times idempotently + ON CONFLICT (banned_operation_id) DO NOTHING + RETURNING * + ) + -- gets row if there was no conflict + SELECT + target_account AS "target_account!: AccountId", + class_id AS "class_id!: _", + banned_at AS "banned_at!: _", + banned_operation_id AS "banned_operation_id!: _", + unbanned_at, + unbanned_operation_id + FROM i + -- or selects original row if there was a conflict + UNION + SELECT + target_account AS "target_account: AccountId", + class_id, + banned_at, + banned_operation_id, + unbanned_at, + unbanned_operation_id + FROM ban_history + WHERE + banned_operation_id = $3 + "#, + self.class_id, + self.target_account as &AccountId, + self.operation_id, + ) + .fetch_one(conn) + .await + } +} + +pub struct FinishBanQuery<'a> { + class_id: Uuid, + target_account: &'a AccountId, + operation_id: i64, +} + +impl<'a> FinishBanQuery<'a> { + pub fn new(class_id: Uuid, target_account: &'a AccountId, operation_id: i64) -> Self { + Self { + class_id, + target_account, + operation_id, + } + } + + pub async fn execute(&self, conn: &mut sqlx::PgConnection) -> sqlx::Result> { + sqlx::query_as!( + Object, + r#" + UPDATE ban_history + SET + unbanned_at = NOW(), + unbanned_operation_id = $3 + WHERE + class_id = $1 + AND target_account = $2 + AND unbanned_at IS NULL + RETURNING + target_account AS "target_account: AccountId", + class_id, + banned_at, + banned_operation_id, + unbanned_at, + unbanned_operation_id + "#, + self.class_id, + self.target_account as &AccountId, + self.operation_id, + ) + .fetch_optional(conn) + .await + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::app::AppContext; + use crate::test_helpers::prelude::*; + + #[sqlx::test] + async fn finishes_ban(pool: sqlx::PgPool) { + let state = TestState::new(pool, TestAuthz::new()).await; + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + + let minigroup = factory::Minigroup::new( + random_string(), + USR_AUDIENCE.to_string(), + (Bound::Unbounded, Bound::Unbounded).into(), + Uuid::new_v4(), + Uuid::new_v4(), + ) + .insert(&mut conn) + .await; + + let entry_start = InsertQuery::new(minigroup.id(), &AccountId::new("test", "test"), 0) + .execute(&mut conn) + .await + .expect("failed to insert ban history entry"); + + let entry_finish = FinishBanQuery::new(minigroup.id(), &entry_start.target_account, 1) + .execute(&mut conn) + .await + .expect("failed to run finish ban query") + .expect("failed to find entry"); + + assert_eq!(entry_start.target_account, entry_finish.target_account); + assert_eq!(entry_start.class_id, entry_finish.class_id); + assert_eq!(entry_start.banned_at, entry_finish.banned_at); + assert_eq!( + entry_start.banned_operation_id, + entry_finish.banned_operation_id + ); + assert_eq!(entry_finish.unbanned_operation_id, Some(1)); + } + + #[sqlx::test] + async fn inserts_idempotently(pool: sqlx::PgPool) { + let state = TestState::new(pool, TestAuthz::new()).await; + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + + let minigroup1 = factory::Minigroup::new( + random_string(), + USR_AUDIENCE.to_string(), + (Bound::Unbounded, Bound::Unbounded).into(), + Uuid::new_v4(), + Uuid::new_v4(), + ) + .insert(&mut conn) + .await; + + let entry1 = InsertQuery::new(minigroup1.id(), &AccountId::new("test", "test"), 0) + .execute(&mut conn) + .await + .expect("failed to insert ban history entry"); + + let minigroup2 = factory::Minigroup::new( + random_string(), + USR_AUDIENCE.to_string(), + (Bound::Unbounded, Bound::Unbounded).into(), + Uuid::new_v4(), + Uuid::new_v4(), + ) + .insert(&mut conn) + .await; + + let entry2 = InsertQuery::new( + minigroup2.id(), + &AccountId::new("test-another", "test-another"), + 0, + ) + .execute(&mut conn) + .await + .expect("failed to insert ban history entry"); + + // so we just got original entry back + assert_eq!(entry1, entry2); + } +} diff --git a/src/db/class/insert_query.rs b/src/db/class/insert_query.rs index 47829646..28f97b53 100644 --- a/src/db/class/insert_query.rs +++ b/src/db/class/insert_query.rs @@ -214,9 +214,9 @@ mod tests { use super::*; use crate::test_helpers::prelude::*; - #[tokio::test] - async fn insert_already_established_webinar() { - let db = TestDb::new().await; + #[sqlx::test] + async fn insert_already_established_webinar(pool: sqlx::PgPool) { + let db = TestDb::new(pool); let mut conn = db.get_conn().await; let webinar = { @@ -255,9 +255,9 @@ mod tests { assert_eq!(time.0, Bound::Unbounded); } - #[tokio::test] - async fn insert_not_established_webinar() { - let db = TestDb::new().await; + #[sqlx::test] + async fn insert_not_established_webinar(pool: sqlx::PgPool) { + let db = TestDb::new(pool); let mut conn = db.get_conn().await; let dummy = InsertQuery::new( diff --git a/src/db/class/mod.rs b/src/db/class/mod.rs index 9cbef5c0..d57fdfdd 100644 --- a/src/db/class/mod.rs +++ b/src/db/class/mod.rs @@ -47,7 +47,7 @@ impl std::ops::DerefMut for KeyValueProperties { impl sqlx::Encode<'_, sqlx::Postgres> for KeyValueProperties { fn encode_by_ref( &self, - buf: &mut ::ArgumentBuffer, + buf: &mut >::ArgumentBuffer, ) -> sqlx::encode::IsNull { self.clone().into_json().encode_by_ref(buf) } diff --git a/src/db/mod.rs b/src/db/mod.rs index 3b378551..829c79b9 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -2,7 +2,7 @@ use std::time::Duration; use sqlx::postgres::{PgPool, PgPoolOptions}; -pub(crate) async fn create_pool( +pub async fn create_pool( url: &str, size: u32, idle_size: Option, @@ -19,10 +19,12 @@ pub(crate) async fn create_pool( .expect("Failed to create sqlx database pool") } -pub(crate) mod account; -pub(crate) mod authz; -pub(crate) mod class; -pub(crate) mod frontend; -pub(crate) mod record_timestamp; -pub(crate) mod recording; -pub(crate) mod scope; +pub mod account; +pub mod authz; +pub mod ban_account_op; +pub mod ban_history; +pub mod class; +pub mod frontend; +pub mod record_timestamp; +pub mod recording; +pub mod scope; diff --git a/src/db/recording.rs b/src/db/recording.rs index cf6f7a30..660c7eaf 100644 --- a/src/db/recording.rs +++ b/src/db/recording.rs @@ -10,7 +10,7 @@ use serde_derive::{Deserialize, Serialize}; //////////////////////////////////////////////////////////////////////////////// #[allow(dead_code)] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Object { id: Uuid, class_id: Uuid, @@ -123,20 +123,26 @@ impl RecordingListQuery { Object, r#" SELECT - id, - class_id, - rtc_id, - stream_uri, - segments AS "segments!: Option", - modified_segments AS "modified_segments!: Option", - started_at, - created_at, - adjusted_at, - transcoded_at, - created_by AS "created_by: AgentId", - deleted_at - FROM recording - WHERE class_id = $1 AND deleted_at IS NULL + r.id, + r.class_id, + r.rtc_id, + r.stream_uri, + r.segments AS "segments!: Option", + r.modified_segments AS "modified_segments!: Option", + r.started_at, + r.created_at, + r.adjusted_at, + r.transcoded_at, + r.created_by AS "created_by: AgentId", + r.deleted_at + FROM recording AS r + LEFT JOIN ban_history AS bh + ON r.class_id = bh.class_id + AND bh.target_account = (r.created_by).account_id + WHERE + r.class_id = $1 + AND r.deleted_at IS NULL + AND bh.banned_operation_id IS NULL "#, self.class_id ) @@ -567,11 +573,11 @@ pub mod tests { use crate::app::AppContext; use crate::test_helpers::prelude::*; - #[tokio::test] - async fn test_minigroup_adjust_not_using_deleted_recordings() { + #[sqlx::test] + async fn test_minigroup_adjust_not_using_deleted_recordings(pool: sqlx::PgPool) { let agent = TestAgent::new("web", "user1", USR_AUDIENCE); - let state = TestState::new(TestAuthz::new()).await; + let state = TestState::new(pool, TestAuthz::new()).await; let mut conn = state.get_conn().await.expect("Failed to fetch connection"); let minigroup = factory::Minigroup::new( random_string(), @@ -608,6 +614,49 @@ pub mod tests { assert_eq!(recordings[0].rtc_id(), recording.rtc_id()); } + #[sqlx::test] + async fn list_recording_skips_records_from_banned_users(pool: sqlx::PgPool) { + let agent1 = TestAgent::new("web", "user1", USR_AUDIENCE); + let agent2 = TestAgent::new("web", "user2", USR_AUDIENCE); + + let state = TestState::new(pool, TestAuthz::new()).await; + let mut conn = state.get_conn().await.expect("Failed to fetch connection"); + let minigroup = factory::Minigroup::new( + random_string(), + USR_AUDIENCE.to_string(), + (Bound::Unbounded, Bound::Unbounded).into(), + Uuid::new_v4(), + Uuid::new_v4(), + ) + .insert(&mut conn) + .await; + + // Recording of banned agent + let _rec1 = + factory::Recording::new(minigroup.id(), Uuid::new_v4(), agent1.agent_id().to_owned()) + .insert(&mut conn) + .await; + + crate::db::ban_history::InsertQuery::new(minigroup.id(), agent1.account_id(), 0) + .execute(&mut conn) + .await + .expect("failed to insert ban history entry"); + + // Recording of good agent + let rec2 = + factory::Recording::new(minigroup.id(), Uuid::new_v4(), agent2.agent_id().to_owned()) + .insert(&mut conn) + .await; + + let recs = RecordingListQuery::new(minigroup.id()) + .execute(&mut conn) + .await + .expect("failed to run list query"); + + assert_eq!(recs.len(), 1); + assert_eq!(recs[0], rec2); + } + pub struct RecordingInsertQuery { class_id: Uuid, rtc_id: Uuid, diff --git a/src/test_helpers/agent.rs b/src/test_helpers/agent.rs index 9f7a8b11..306aac9b 100644 --- a/src/test_helpers/agent.rs +++ b/src/test_helpers/agent.rs @@ -21,7 +21,7 @@ pub struct TestAgent { impl TestAgent { pub fn new(agent_label: &str, account_label: &str, audience: &str) -> Self { let account_id = AccountId::new(account_label, audience); - let agent_id = AgentId::new(agent_label, account_id.clone()); + let agent_id = AgentId::new(agent_label, account_id); let address = Address::new(agent_id, API_VERSION); Self { address } } @@ -31,11 +31,11 @@ impl TestAgent { } pub fn agent_id(&self) -> &AgentId { - &self.address.id() + self.address.id() } pub fn account_id(&self) -> &AccountId { - &self.address.id().as_account_id() + self.address.id().as_account_id() } pub fn token(&self) -> String { diff --git a/src/test_helpers/db.rs b/src/test_helpers/db.rs index 94aad054..98384e1a 100644 --- a/src/test_helpers/db.rs +++ b/src/test_helpers/db.rs @@ -1,41 +1,13 @@ -use std::env::var; +use sqlx::pool::PoolConnection; +use sqlx::postgres::{PgPool, Postgres}; -use sqlx::postgres::{PgPool, PgPoolOptions, Postgres}; -use sqlx::{pool::PoolConnection, Executor}; -use tokio::sync::OnceCell; - -static DB_TRUNCATE: OnceCell = OnceCell::const_new(); #[derive(Clone)] pub struct TestDb { pool: PgPool, } impl TestDb { - pub async fn new() -> Self { - #[cfg(feature = "dotenv")] - dotenv::dotenv().ok(); - - let url = var("DATABASE_URL").expect("DATABASE_URL must be specified"); - - let pool = PgPoolOptions::new() - .min_connections(1) - .max_connections(1) - .connect(&url) - .await - .expect("Failed to connect to the DB"); - - // todo: we should actually run every test in transaction, but thats not possible for now, maybe in sqlx 0.6 - DB_TRUNCATE - .get_or_init(|| async { - let mut conn = pool.acquire().await.expect("Failed to get DB connection"); - - conn.execute("TRUNCATE class CASCADE;") - .await - .expect("Failed to truncate class table"); - - true - }) - .await; + pub fn new(pool: PgPool) -> Self { Self { pool } } diff --git a/src/test_helpers/mod.rs b/src/test_helpers/mod.rs index 145932b3..b924e1ad 100644 --- a/src/test_helpers/mod.rs +++ b/src/test_helpers/mod.rs @@ -1,6 +1,6 @@ -pub const SVC_AUDIENCE: &'static str = "dev.svc.example.org"; -pub const USR_AUDIENCE: &'static str = "dev.usr.example.com"; -pub const TOKEN_ISSUER: &'static str = "iam.svc.example.com"; +pub const SVC_AUDIENCE: &str = "dev.svc.example.org"; +pub const USR_AUDIENCE: &str = "dev.usr.example.com"; +pub const TOKEN_ISSUER: &str = "iam.svc.example.com"; pub const PUBKEY_PATH: &str = "data/keys/svc.public_key.p8.der.sample"; pub mod prelude { diff --git a/src/test_helpers/state.rs b/src/test_helpers/state.rs index 455d8580..45e2743a 100644 --- a/src/test_helpers/state.rs +++ b/src/test_helpers/state.rs @@ -1,5 +1,6 @@ use parking_lot::Mutex; use std::sync::Arc; +use svc_nats_client::test_helpers::TestNatsClient; use anyhow::Result; use async_trait::async_trait; @@ -12,6 +13,7 @@ use svc_agent::{ AgentId, }; use svc_authz::ClientMap as Authz; +use svc_nats_client::NatsClient; use url::Url; use vec1::{vec1, Vec1}; @@ -41,6 +43,7 @@ pub struct TestState { tq_client: Arc, authz: Authz, turn_host_selector: TurnHostSelector, + nats_client: Arc, } fn build_config() -> Config { @@ -98,7 +101,7 @@ fn build_config() -> Config { } impl TestState { - pub async fn new(authz: TestAuthz) -> Self { + pub async fn new(db: sqlx::PgPool, authz: TestAuthz) -> Self { let config = build_config(); let agent = TestAgent::new(&config.agent_label, config.id.label(), config.id.audience()); @@ -106,7 +109,7 @@ impl TestState { let address = agent.address().to_owned(); Self { - db_pool: TestDb::new().await, + db_pool: TestDb::new(db), turn_host_selector: TurnHostSelector::new(&config.turn_hosts), config, agent, @@ -115,6 +118,7 @@ impl TestState { event_client: Arc::new(MockEventClient::new()), tq_client: Arc::new(MockTqClient::new()), authz: authz.into(), + nats_client: Arc::new(TestNatsClient::new()), } } @@ -135,6 +139,7 @@ impl TestState { tq_client: Arc::new(MockTqClient::new()), authz: authz.into(), turn_host_selector: TurnHostSelector::new(&vec1!["turn.example.org".into()]), + nats_client: Arc::new(TestNatsClient::new()), } } @@ -151,10 +156,14 @@ impl TestState { } pub fn set_turn_hosts(&mut self, hosts: &[&str]) { - let hosts = hosts.into_iter().map(|c| (*c).into()).collect::>(); + let hosts = hosts.iter().map(|c| (*c).into()).collect::>(); let hosts = Vec1::try_from_vec(hosts).unwrap(); self.turn_host_selector = TurnHostSelector::new(&hosts); } + + pub fn inspect_nats_client(&self) -> &TestNatsClient { + &self.nats_client + } } impl TestState { @@ -229,6 +238,10 @@ impl AppContext for TestState { fn turn_host_selector(&self) -> &TurnHostSelector { &self.turn_host_selector } + + fn nats_client(&self) -> Option> { + Some(self.nats_client.clone()) + } } ////////////////////////////////////////////////////////////////////////////////