diff --git a/Cargo.lock b/Cargo.lock index 52779597..98fb375c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,7 +32,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cipher", "cpufeatures", "opaque-debug 0.3.0", @@ -69,7 +69,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "once_cell", "version_check", ] @@ -95,7 +95,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -190,7 +190,7 @@ dependencies = [ "slab", "socket2 0.4.4", "waker-fn", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -210,13 +210,13 @@ checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" dependencies = [ "async-io", "blocking", - "cfg-if 1.0.0", + "cfg-if", "event-listener", "futures-lite", "libc", "once_cell", "signal-hook", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -348,7 +348,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -414,7 +414,7 @@ checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" dependencies = [ "addr2line", "cc", - "cfg-if 1.0.0", + "cfg-if", "libc", "miniz_oxide", "object", @@ -433,16 +433,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" -[[package]] -name = "base64" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5032d51da2741729bfdaeb2664d9b8c6d9fd1e2b90715c660b6def36628499c2" -dependencies = [ - "byteorder", - "safemem", -] - [[package]] name = "base64" version = "0.13.0" @@ -665,12 +655,6 @@ dependencies = [ "jobserver", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -689,7 +673,7 @@ dependencies = [ "num-traits", "time 0.1.44", "wasm-bindgen", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -843,7 +827,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", ] @@ -853,7 +837,7 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "lazy_static", ] @@ -916,7 +900,7 @@ dependencies = [ "openssl-sys", "schannel", "socket2 0.4.4", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -932,7 +916,7 @@ dependencies = [ "openssl-sys", "pkg-config", "vcpkg", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1011,7 +995,7 @@ version = "5.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "hashbrown 0.12.1", "lock_api", "parking_lot_core", @@ -1248,7 +1232,7 @@ version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -1373,7 +1357,7 @@ dependencies = [ "cc", "lazy_static", "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1448,22 +1432,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - [[package]] name = "futures" version = "0.3.21" @@ -1595,7 +1563,7 @@ dependencies = [ "libc", "log", "rustversion", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1623,7 +1591,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "wasi 0.10.0+wasi-snapshot-preview1", ] @@ -1698,26 +1666,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "h2" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" -dependencies = [ - "bytes 0.5.6", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio 0.2.25", - "tokio-util 0.3.1", - "tracing", - "tracing-futures", -] - [[package]] name = "h2" version = "0.3.13" @@ -1856,7 +1804,7 @@ checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ "libc", "match_cfg", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1867,17 +1815,7 @@ checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" dependencies = [ "bytes 1.5.0", "fnv", - "itoa 1.0.2", -] - -[[package]] -name = "http-body" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" -dependencies = [ - "bytes 0.5.6", - "http", + "itoa", ] [[package]] @@ -1897,12 +1835,6 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" -[[package]] -name = "httpdate" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" - [[package]] name = "httpdate" version = "1.0.2" @@ -1918,30 +1850,6 @@ dependencies = [ "quick-error", ] -[[package]] -name = "hyper" -version = "0.13.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a6f157065790a3ed2f88679250419b5cdd96e714a0d65f7797fd337186e96bb" -dependencies = [ - "bytes 0.5.6", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.2.7", - "http", - "http-body 0.3.1", - "httparse", - "httpdate 0.3.2", - "itoa 0.4.8", - "pin-project", - "socket2 0.3.19", - "tokio 0.2.25", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "0.14.19" @@ -1952,12 +1860,12 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.3.13", + "h2", "http", - "http-body 0.4.5", + "http-body", "httparse", - "httpdate 1.0.2", - "itoa 1.0.2", + "httpdate", + "itoa", "pin-project-lite 0.2.13", "socket2 0.4.4", "tokio 1.35.1", @@ -1966,19 +1874,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-tls" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d979acc56dcb5b8dddba3917601745e877576475aa046df3226eabdecef78eed" -dependencies = [ - "bytes 0.5.6", - "hyper 0.13.10", - "native-tls", - "tokio 0.2.25", - "tokio-tls", -] - [[package]] name = "hyper-tls" version = "0.5.0" @@ -1986,7 +1881,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes 1.5.0", - "hyper 0.14.19", + "hyper", "native-tls", "tokio 1.35.1", "tokio-native-tls", @@ -2054,16 +1949,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", + "cfg-if", ] [[package]] @@ -2074,7 +1960,7 @@ checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" dependencies = [ "socket2 0.4.4", "widestring", - "winapi 0.3.9", + "winapi", "winreg 0.7.0", ] @@ -2120,7 +2006,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56480a7792bfa284a4c7d1781f711de58e357dbb669c1b1a50c37c1777c9be92" dependencies = [ "generic-array 0.14.5", - "schemars", "serde", "time 0.3.17", ] @@ -2137,12 +2022,6 @@ dependencies = [ "time 0.3.17", ] -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.2" @@ -2210,22 +2089,12 @@ version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "ecdsa", "elliptic-curve", "sha2", ] -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "kv-log-macro" version = "1.0.7" @@ -2255,7 +2124,7 @@ dependencies = [ "fastrand", "futures-util", "hostname", - "httpdate 1.0.2", + "httpdate", "idna 0.2.3", "mime", "native-tls", @@ -2351,7 +2220,7 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "value-bag", ] @@ -2361,7 +2230,7 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "generator", "scoped-tls", "serde", @@ -2481,25 +2350,6 @@ dependencies = [ "adler", ] -[[package]] -name = "mio" -version = "0.6.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" -dependencies = [ - "cfg-if 0.1.10", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log", - "miow", - "net2", - "slab", - "winapi 0.2.8", -] - [[package]] name = "mio" version = "0.8.10" @@ -2511,18 +2361,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "miow" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", -] - [[package]] name = "mobc" version = "0.8.1" @@ -2647,17 +2485,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "net2" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.9", -] - [[package]] name = "nom" version = "7.1.1" @@ -2674,7 +2501,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2811,7 +2638,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" dependencies = [ "bitflags", - "cfg-if 1.0.0", + "cfg-if", "foreign-types", "libc", "once_cell", @@ -2867,7 +2694,7 @@ checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" dependencies = [ "log", "serde", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2914,7 +2741,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall", "smallvec", @@ -3115,11 +2942,11 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "log", "wepoll-ffi", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -3128,7 +2955,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "opaque-debug 0.3.0", "universal-hash", @@ -3212,7 +3039,7 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fnv", "lazy_static", "memchr", @@ -3269,7 +3096,7 @@ dependencies = [ "rand_os", "rand_pcg", "rand_xorshift", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -3353,7 +3180,7 @@ checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" dependencies = [ "libc", "rand_core 0.4.2", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -3368,7 +3195,7 @@ dependencies = [ "rand_core 0.4.2", "rdrand", "wasm-bindgen", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -3409,7 +3236,7 @@ dependencies = [ "bytes 1.5.0", "combine", "futures-util", - "itoa 1.0.2", + "itoa", "percent-encoding", "pin-project-lite 0.2.13", "ryu", @@ -3421,15 +3248,16 @@ dependencies = [ [[package]] name = "redis" -version = "0.23.1" -source = "git+https://github.com/revoltchat/redis-rs?rev=f8ca28ab85da59d2ccde526b4d2fb390eff5a5f9#f8ca28ab85da59d2ccde526b4d2fb390eff5a5f9" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba" dependencies = [ "async-std", "async-trait", "bytes 1.5.0", "combine", "futures-util", - "itoa 1.0.2", + "itoa", "percent-encoding", "pin-project-lite 0.2.13", "ryu", @@ -3448,7 +3276,7 @@ dependencies = [ "lazy_static", "mobc", "mobc-redis", - "redis 0.23.1", + "redis 0.23.3", "rmp-serde", "serde", "serde_json", @@ -3529,7 +3357,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -3543,11 +3371,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.3.13", + "h2", "http", - "http-body 0.4.5", - "hyper 0.14.19", - "hyper-tls 0.5.0", + "http-body", + "hyper", + "hyper-tls", "ipnet", "js-sys", "lazy_static", @@ -3580,7 +3408,7 @@ dependencies = [ [[package]] name = "revolt-bonfire" -version = "0.7.0" +version = "0.7.1" dependencies = [ "async-std", "async-tungstenite", @@ -3607,7 +3435,7 @@ dependencies = [ [[package]] name = "revolt-config" -version = "0.7.0" +version = "0.7.1" dependencies = [ "async-std", "cached", @@ -3623,7 +3451,7 @@ dependencies = [ [[package]] name = "revolt-database" -version = "0.7.0" +version = "0.7.1" dependencies = [ "async-lock", "async-recursion", @@ -3664,12 +3492,12 @@ dependencies = [ "unicode-segmentation", "url-escape", "validator 0.16.0", - "web-push 0.10.0", + "web-push", ] [[package]] name = "revolt-delta" -version = "0.7.0" +version = "0.7.1" dependencies = [ "async-channel", "async-std", @@ -3715,7 +3543,7 @@ dependencies = [ [[package]] name = "revolt-models" -version = "0.7.0" +version = "0.7.1" dependencies = [ "indexmap", "iso8601-timestamp 0.2.11", @@ -3732,7 +3560,7 @@ dependencies = [ [[package]] name = "revolt-permissions" -version = "0.7.0" +version = "0.7.1" dependencies = [ "async-std", "async-trait", @@ -3747,7 +3575,7 @@ dependencies = [ [[package]] name = "revolt-presence" -version = "0.7.0" +version = "0.7.1" dependencies = [ "async-std", "log", @@ -3756,62 +3584,9 @@ dependencies = [ "redis-kiss", ] -[[package]] -name = "revolt-quark" -version = "0.7.0" -dependencies = [ - "async-lock", - "async-recursion", - "async-std", - "async-trait", - "authifier", - "base64 0.13.0", - "bincode", - "bitfield", - "bson", - "dashmap", - "deadqueue", - "decancer", - "dotenv", - "futures", - "impl_ops", - "indexmap", - "iso8601-timestamp 0.1.10", - "linkify 0.8.1", - "log", - "lru 0.7.6", - "mongodb", - "nanoid", - "num_enum 0.5.7", - "once_cell", - "pretty_env_logger", - "rand 0.8.5", - "redis-kiss", - "regex", - "reqwest", - "revolt-database", - "revolt-models", - "revolt-presence", - "revolt-result", - "revolt_okapi", - "revolt_optional_struct", - "revolt_rocket_okapi", - "rocket", - "rocket_cors", - "rocket_empty", - "rocket_http", - "schemars", - "sentry", - "serde", - "serde_json", - "ulid 0.5.0", - "validator 0.16.0", - "web-push 0.7.2", -] - [[package]] name = "revolt-result" -version = "0.7.0" +version = "0.7.1" dependencies = [ "revolt_okapi", "revolt_rocket_okapi", @@ -3893,9 +3668,9 @@ dependencies = [ "libc", "once_cell", "spin 0.5.2", - "untrusted 0.7.1", + "untrusted", "web-sys", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -4028,7 +3803,7 @@ dependencies = [ "either", "futures", "http", - "hyper 0.14.19", + "hyper", "indexmap", "log", "memchr", @@ -4105,7 +3880,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "ordered-multimap", ] @@ -4176,12 +3951,6 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" -[[package]] -name = "safemem" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" - [[package]] name = "schannel" version = "0.1.20" @@ -4236,7 +4005,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ "ring", - "untrusted 0.7.1", + "untrusted", ] [[package]] @@ -4314,7 +4083,7 @@ version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01b0ad16faa5d12372f914ed40d00bda21a6d1bdcc99264c5e5e1c9495cf3654" dependencies = [ - "httpdate 1.0.2", + "httpdate", "native-tls", "reqwest", "sentry-backtrace", @@ -4463,7 +4232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "indexmap", - "itoa 1.0.2", + "itoa", "ryu", "serde", ] @@ -4475,7 +4244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.2", + "itoa", "ryu", "serde", ] @@ -4520,7 +4289,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.10.7", ] @@ -4537,7 +4306,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.10.7", ] @@ -4603,17 +4372,6 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" -[[package]] -name = "socket2" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "winapi 0.3.9", -] - [[package]] name = "socket2" version = "0.4.4" @@ -4621,7 +4379,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -4756,12 +4514,12 @@ version = "0.27.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c215311383d25d03753375c53ab9fad8fc0cf46953c409211e065edeabf577ee" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "core-foundation-sys", "libc", "ntapi", "once_cell", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -4776,12 +4534,12 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fastrand", "libc", "redox_syscall", "remove_dir_all", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -4830,7 +4588,7 @@ checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -4839,7 +4597,7 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "itoa 1.0.2", + "itoa", "serde", "time-core", "time-macros", @@ -4882,12 +4640,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" dependencies = [ "bytes 0.5.6", - "fnv", - "futures-core", - "iovec", - "lazy_static", - "memchr", - "mio 0.6.23", "pin-project-lite 0.1.12", "slab", ] @@ -4901,7 +4653,7 @@ dependencies = [ "backtrace", "bytes 1.5.0", "libc", - "mio 0.8.10", + "mio", "num_cpus", "parking_lot", "pin-project-lite 0.2.13", @@ -4954,30 +4706,6 @@ dependencies = [ "tokio 1.35.1", ] -[[package]] -name = "tokio-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" -dependencies = [ - "native-tls", - "tokio 0.2.25", -] - -[[package]] -name = "tokio-util" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" -dependencies = [ - "bytes 0.5.6", - "futures-core", - "futures-sink", - "log", - "pin-project-lite 0.1.12", - "tokio 0.2.25", -] - [[package]] name = "tokio-util" version = "0.6.10" @@ -5040,7 +4768,7 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "log", "pin-project-lite 0.2.13", "tracing-attributes", @@ -5114,7 +4842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d" dependencies = [ "async-trait", - "cfg-if 1.0.0", + "cfg-if", "data-encoding", "enum-as-inner", "futures-channel", @@ -5138,7 +4866,7 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "futures-util", "ipconfig", "lazy_static", @@ -5326,12 +5054,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "untrusted" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" - [[package]] name = "untrusted" version = "0.7.1" @@ -5490,7 +5212,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "571b69f690c855821462709b6f41d42ceccc316fbd17b60bd06d06928cfe6a99" dependencies = [ "anyhow", - "cfg-if 1.0.0", + "cfg-if", "enum-iterator", "getset", "git2", @@ -5541,7 +5263,7 @@ version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen-macro", ] @@ -5566,7 +5288,7 @@ version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "wasm-bindgen", "web-sys", @@ -5601,31 +5323,6 @@ version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" -[[package]] -name = "web-push" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8353dd6c7cfb9a02737fd6dc9a66a80dca2a93fb690f6ad264d2a7672e6f1c0" -dependencies = [ - "base64 0.7.0", - "chrono", - "erased-serde", - "futures", - "http", - "hyper 0.13.10", - "hyper-tls 0.4.3", - "lazy_static", - "log", - "native-tls", - "openssl", - "ring", - "serde", - "serde_derive", - "serde_json", - "time 0.1.44", - "untrusted 0.6.2", -] - [[package]] name = "web-push" version = "0.10.0" @@ -5665,7 +5362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ "ring", - "untrusted 0.7.1", + "untrusted", ] [[package]] @@ -5692,12 +5389,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - [[package]] name = "winapi" version = "0.3.9" @@ -5708,12 +5399,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -5726,7 +5411,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -5850,7 +5535,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -5859,17 +5544,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", + "winapi", ] [[package]] @@ -5892,3 +5567,8 @@ name = "zeroize" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" + +[[patch.unused]] +name = "redis" +version = "0.23.1" +source = "git+https://github.com/revoltchat/redis-rs?rev=f8ca28ab85da59d2ccde526b4d2fb390eff5a5f9#f8ca28ab85da59d2ccde526b4d2fb390eff5a5f9" diff --git a/Cargo.toml b/Cargo.toml index 2609e364..b5532716 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["crates/delta", "crates/bonfire", "crates/quark", "crates/core/*"] +members = ["crates/delta", "crates/bonfire", "crates/core/*"] [patch.crates-io] # mobc-redis = { git = "https://github.com/insertish/mobc", rev = "8b880bb59f2ba80b4c7bc40c649c113d8857a186" } diff --git a/crates/bonfire/Cargo.toml b/crates/bonfire/Cargo.toml index d87f549e..485ec527 100644 --- a/crates/bonfire/Cargo.toml +++ b/crates/bonfire/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-bonfire" -version = "0.7.0" +version = "0.7.1" license = "AGPL-3.0-or-later" edition = "2021" @@ -38,7 +38,7 @@ revolt-result = { path = "../core/result" } revolt-models = { path = "../core/models" } revolt-config = { path = "../core/config" } revolt-database = { path = "../core/database" } -revolt-permissions = { version = "0.7.0", path = "../core/permissions" } +revolt-permissions = { version = "0.7.1", path = "../core/permissions" } revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] } # redis diff --git a/crates/core/config/Cargo.toml b/crates/core/config/Cargo.toml index b0e60c9b..69986f48 100644 --- a/crates/core/config/Cargo.toml +++ b/crates/core/config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-config" -version = "0.7.0" +version = "0.7.1" edition = "2021" license = "AGPL-3.0-or-later" authors = ["Paul Makles "] diff --git a/crates/core/database/Cargo.toml b/crates/core/database/Cargo.toml index c1af007b..2e6bbcab 100644 --- a/crates/core/database/Cargo.toml +++ b/crates/core/database/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-database" -version = "0.7.0" +version = "0.7.1" edition = "2021" license = "AGPL-3.0-or-later" authors = ["Paul Makles "] @@ -23,13 +23,13 @@ default = ["mongodb", "async-std-runtime", "tasks"] [dependencies] # Core -revolt-config = { version = "0.7.0", path = "../config" } -revolt-result = { version = "0.7.0", path = "../result" } -revolt-models = { version = "0.7.0", path = "../models", features = [ +revolt-config = { version = "0.7.1", path = "../config" } +revolt-result = { version = "0.7.1", path = "../result" } +revolt-models = { version = "0.7.1", path = "../models", features = [ "validator", ] } -revolt-presence = { version = "0.7.0", path = "../presence" } -revolt-permissions = { version = "0.7.0", path = "../permissions", features = [ +revolt-presence = { version = "0.7.1", path = "../presence" } +revolt-permissions = { version = "0.7.1", path = "../permissions", features = [ "serde", "bson", ] } diff --git a/crates/core/models/Cargo.toml b/crates/core/models/Cargo.toml index 83b57671..5b41b771 100644 --- a/crates/core/models/Cargo.toml +++ b/crates/core/models/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-models" -version = "0.7.0" +version = "0.7.1" edition = "2021" license = "AGPL-3.0-or-later" authors = ["Paul Makles "] @@ -19,8 +19,8 @@ default = ["serde", "partials", "rocket"] [dependencies] # Core -revolt-config = { version = "0.7.0", path = "../config" } -revolt-permissions = { version = "0.7.0", path = "../permissions" } +revolt-config = { version = "0.7.1", path = "../config" } +revolt-permissions = { version = "0.7.1", path = "../permissions" } # Utility regex = "1" diff --git a/crates/core/permissions/Cargo.toml b/crates/core/permissions/Cargo.toml index 69b8ae3b..7ee44d2c 100644 --- a/crates/core/permissions/Cargo.toml +++ b/crates/core/permissions/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-permissions" -version = "0.7.0" +version = "0.7.1" edition = "2021" license = "AGPL-3.0-or-later" authors = ["Paul Makles "] @@ -21,7 +21,7 @@ async-std = { version = "1.8.0", features = ["attributes"] } [dependencies] # Core -revolt-result = { version = "0.7.0", path = "../result" } +revolt-result = { version = "0.7.1", path = "../result" } # Utility auto_ops = "0.3.0" diff --git a/crates/core/presence/Cargo.toml b/crates/core/presence/Cargo.toml index 5634ed5c..c09d5010 100644 --- a/crates/core/presence/Cargo.toml +++ b/crates/core/presence/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-presence" -version = "0.7.0" +version = "0.7.1" edition = "2021" license = "AGPL-3.0-or-later" authors = ["Paul Makles "] diff --git a/crates/core/result/Cargo.toml b/crates/core/result/Cargo.toml index 583842d7..c7f1ec04 100644 --- a/crates/core/result/Cargo.toml +++ b/crates/core/result/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-result" -version = "0.7.0" +version = "0.7.1" edition = "2021" license = "AGPL-3.0-or-later" authors = ["Paul Makles "] @@ -12,7 +12,7 @@ description = "Revolt Backend: Result and Error types" serde = ["dep:serde"] schemas = ["dep:schemars"] rocket = ["dep:rocket", "dep:serde_json"] -okapi = ["dep:revolt_rocket_okapi", "dep:revolt_okapi"] +okapi = ["dep:revolt_rocket_okapi", "dep:revolt_okapi", "schemas"] default = ["serde"] diff --git a/crates/delta/Cargo.toml b/crates/delta/Cargo.toml index 2c7a4cfa..cc4d0d39 100644 --- a/crates/delta/Cargo.toml +++ b/crates/delta/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-delta" -version = "0.7.0" +version = "0.7.1" license = "AGPL-3.0-or-later" authors = ["Paul Makles "] edition = "2018" diff --git a/crates/delta/src/util/test.rs b/crates/delta/src/util/test.rs index 428f8bbb..467e945b 100644 --- a/crates/delta/src/util/test.rs +++ b/crates/delta/src/util/test.rs @@ -101,7 +101,6 @@ impl TestHarness { let mut stream = self.sub.on_message(); while let Some(item) = stream.next().await { - let item = item.unwrap(); let msg_topic = item.get_channel_name(); let payload: EventV1 = redis_kiss::decode_payload(&item).unwrap(); diff --git a/crates/quark/.gitignore b/crates/quark/.gitignore deleted file mode 100644 index 01dfec2c..00000000 --- a/crates/quark/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# Database -.minio -.data* diff --git a/crates/quark/Cargo.toml b/crates/quark/Cargo.toml deleted file mode 100644 index 09fa10af..00000000 --- a/crates/quark/Cargo.toml +++ /dev/null @@ -1,100 +0,0 @@ -[package] -name = "revolt-quark" -version = "0.7.0" -edition = "2021" -license = "AGPL-3.0-or-later" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[features] -mongo = ["mongodb"] -rocket_impl = [ - "rocket", - "rocket_empty", - "rocket_cors", - - "lru", - "dashmap", - - "authifier/database-mongodb", - "authifier/rocket_impl", - "authifier/okapi_impl", -] - -test = ["async-std", "mongo", "mongodb/async-std-runtime", "rocket_impl"] -default = ["test"] - -[dependencies] -# Serialisation -revolt_optional_struct = "0.2.0" -serde = { version = "1", features = ["derive"] } -validator = { version = "0.16", features = ["derive"] } -iso8601-timestamp = { version = "0.1.8", features = ["schema", "bson"] } - -# Formats -bincode = "1.3.3" -serde_json = "1.0.78" -bson = { version = "2.1.0", features = ["chrono-0_4"] } - -# Spec Generation -schemars = "0.8.8" -revolt_okapi = "0.9.1" -revolt_rocket_okapi = { version = "0.9.1", features = ["swagger"] } - -# Databases -redis-kiss = { version = "0.1.4" } -mongodb = { optional = true, version = "2.1.0", default-features = false } - -# Async -futures = "0.3.19" -deadqueue = "0.2.1" -async-trait = "0.1.51" -async-recursion = "1.0.0" -async-std = { version = "1.8.0", features = ["attributes"], optional = true } - -# Logging -log = "0.4.14" -pretty_env_logger = "0.4.0" - -# Util -rand = "0.8.5" -ulid = "0.5.0" -regex = "1.5.5" -nanoid = "0.4.0" -linkify = "0.8.1" -dotenv = "0.15.0" -indexmap = "1.9.1" -decancer = "1.6.2" -impl_ops = "0.1.1" -num_enum = "0.5.6" -reqwest = "0.11.10" -bitfield = "0.13.2" -once_cell = "1.17.1" -async-lock = "2.6.0" - -lru = { version = "0.7.6", optional = true } -dashmap = { version = "5.2.0", optional = true } - -# Web Push -base64 = "0.13.0" -web-push = "0.7.2" - -# Implementations -rocket_http = { optional = true, version = "0.5.0-rc.2" } -rocket = { optional = true, version = "0.5.0-rc.2", default-features = false, features = [ - "json", -] } -rocket_empty = { version = "0.1.1", optional = true, features = ["schema"] } -rocket_cors = { optional = true, git = "https://github.com/lawliet89/rocket_cors", rev = "c17e8145baa4790319fdb6a473e465b960f55e7c" } - -# Authifier -authifier = { version = "1.0.8", features = ["async-std-runtime"] } - -# Sentry -sentry = "0.31.5" - -# Core -revolt-result = { path = "../core/result", features = ["serde", "schemas"] } -revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] } -revolt-database = { path = "../core/database" } -revolt-models = { path = "../core/models" } diff --git a/crates/quark/LICENSE b/crates/quark/LICENSE deleted file mode 120000 index 30cff740..00000000 --- a/crates/quark/LICENSE +++ /dev/null @@ -1 +0,0 @@ -../../LICENSE \ No newline at end of file diff --git a/crates/quark/assets/emojis.txt b/crates/quark/assets/emojis.txt deleted file mode 100644 index bc8036e3..00000000 --- a/crates/quark/assets/emojis.txt +++ /dev/null @@ -1,1850 +0,0 @@ -๐Ÿ’ฏ -๐Ÿ”ข -๐Ÿ˜€ -๐Ÿ˜ƒ -๐Ÿ˜„ -๐Ÿ˜ -๐Ÿ˜† -๐Ÿ˜† -๐Ÿ˜… -๐Ÿคฃ -๐Ÿ˜‚ -๐Ÿ™‚ -๐Ÿ™ƒ -๐Ÿ˜‰ -๐Ÿ˜Š -๐Ÿ˜‡ -๐Ÿฅฐ -๐Ÿ˜ -๐Ÿคฉ -๐Ÿ˜˜ -๐Ÿ˜— -โ˜บ๏ธ -๐Ÿ˜š -๐Ÿ˜™ -๐Ÿฅฒ -๐Ÿ˜‹ -๐Ÿ˜› -๐Ÿ˜œ -๐Ÿคช -๐Ÿ˜ -๐Ÿค‘ -๐Ÿค— -๐Ÿคญ -๐Ÿคซ -๐Ÿค” -๐Ÿค -๐Ÿคจ -๐Ÿ˜ -๐Ÿ˜‘ -๐Ÿ˜ถ -๐Ÿ˜ -๐Ÿ˜’ -๐Ÿ™„ -๐Ÿ˜ฌ -๐Ÿคฅ -๐Ÿ˜Œ -๐Ÿ˜” -๐Ÿ˜ช -๐Ÿคค -๐Ÿ˜ด -๐Ÿ˜ท -๐Ÿค’ -๐Ÿค• -๐Ÿคข -๐Ÿคฎ -๐Ÿคง -๐Ÿฅต -๐Ÿฅถ -๐Ÿฅด -๐Ÿ˜ต -๐Ÿคฏ -๐Ÿค  -๐Ÿฅณ -๐Ÿฅธ -๐Ÿ˜Ž -๐Ÿค“ -๐Ÿง -๐Ÿ˜• -๐Ÿ˜Ÿ -๐Ÿ™ -โ˜น๏ธ -๐Ÿ˜ฎ -๐Ÿ˜ฏ -๐Ÿ˜ฒ -๐Ÿ˜ณ -๐Ÿฅบ -๐Ÿ˜ฆ -๐Ÿ˜ง -๐Ÿ˜จ -๐Ÿ˜ฐ -๐Ÿ˜ฅ -๐Ÿ˜ข -๐Ÿ˜ญ -๐Ÿ˜ฑ -๐Ÿ˜– -๐Ÿ˜ฃ -๐Ÿ˜ž -๐Ÿ˜“ -๐Ÿ˜ฉ -๐Ÿ˜ซ -๐Ÿฅฑ -๐Ÿ˜ค -๐Ÿ˜ก -๐Ÿ˜ก -๐Ÿ˜  -๐Ÿคฌ -๐Ÿ˜ˆ -๐Ÿ‘ฟ -๐Ÿ’€ -โ˜ ๏ธ -๐Ÿ’ฉ -๐Ÿ’ฉ -๐Ÿ’ฉ -๐Ÿคก -๐Ÿ‘น -๐Ÿ‘บ -๐Ÿ‘ป -๐Ÿ‘ฝ -๐Ÿ‘พ -๐Ÿค– -๐Ÿ˜บ -๐Ÿ˜ธ -๐Ÿ˜น -๐Ÿ˜ป -๐Ÿ˜ผ -๐Ÿ˜ฝ -๐Ÿ™€ -๐Ÿ˜ฟ -๐Ÿ˜พ -๐Ÿ™ˆ -๐Ÿ™‰ -๐Ÿ™Š -๐Ÿ’‹ -๐Ÿ’Œ -๐Ÿ’˜ -๐Ÿ’ -๐Ÿ’– -๐Ÿ’— -๐Ÿ’“ -๐Ÿ’ž -๐Ÿ’• -๐Ÿ’Ÿ -โฃ๏ธ -๐Ÿ’” -โค๏ธ -๐Ÿงก -๐Ÿ’› -๐Ÿ’š -๐Ÿ’™ -๐Ÿ’œ -๐ŸคŽ -๐Ÿ–ค -๐Ÿค -๐Ÿ’ข -๐Ÿ’ฅ -๐Ÿ’ฅ -๐Ÿ’ซ -๐Ÿ’ฆ -๐Ÿ’จ -๐Ÿ•ณ๏ธ -๐Ÿ’ฃ -๐Ÿ’ฌ -๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ -๐Ÿ—จ๏ธ -๐Ÿ—ฏ๏ธ -๐Ÿ’ญ -๐Ÿ’ค -๐Ÿ‘‹ -๐Ÿคš -๐Ÿ–๏ธ -โœ‹ -โœ‹ -๐Ÿ–– -๐Ÿ‘Œ -๐ŸคŒ -๐Ÿค -โœŒ๏ธ -๐Ÿคž -๐ŸคŸ -๐Ÿค˜ -๐Ÿค™ -๐Ÿ‘ˆ -๐Ÿ‘‰ -๐Ÿ‘† -๐Ÿ–• -๐Ÿ–• -๐Ÿ‘‡ -โ˜๏ธ -๐Ÿ‘ -๐Ÿ‘ -๐Ÿ‘Ž -๐Ÿ‘Ž -โœŠ -โœŠ -๐Ÿ‘Š -๐Ÿ‘Š -๐Ÿ‘Š -๐Ÿค› -๐Ÿคœ -๐Ÿ‘ -๐Ÿ™Œ -๐Ÿ‘ -๐Ÿคฒ -๐Ÿค -๐Ÿ™ -โœ๏ธ -๐Ÿ’… -๐Ÿคณ -๐Ÿ’ช -๐Ÿฆพ -๐Ÿฆฟ -๐Ÿฆต -๐Ÿฆถ -๐Ÿ‘‚ -๐Ÿฆป -๐Ÿ‘ƒ -๐Ÿง  -๐Ÿซ€ -๐Ÿซ -๐Ÿฆท -๐Ÿฆด -๐Ÿ‘€ -๐Ÿ‘๏ธ -๐Ÿ‘… -๐Ÿ‘„ -๐Ÿ‘ถ -๐Ÿง’ -๐Ÿ‘ฆ -๐Ÿ‘ง -๐Ÿง‘ -๐Ÿ‘ฑ -๐Ÿ‘จ -๐Ÿง” -๐Ÿ‘จโ€๐Ÿฆฐ -๐Ÿ‘จโ€๐Ÿฆฑ -๐Ÿ‘จโ€๐Ÿฆณ -๐Ÿ‘จโ€๐Ÿฆฒ -๐Ÿ‘ฉ -๐Ÿ‘ฉโ€๐Ÿฆฐ -๐Ÿง‘โ€๐Ÿฆฐ -๐Ÿ‘ฉโ€๐Ÿฆฑ -๐Ÿง‘โ€๐Ÿฆฑ -๐Ÿ‘ฉโ€๐Ÿฆณ -๐Ÿง‘โ€๐Ÿฆณ -๐Ÿ‘ฉโ€๐Ÿฆฒ -๐Ÿง‘โ€๐Ÿฆฒ -๐Ÿ‘ฑโ€โ™€๏ธ -๐Ÿ‘ฑโ€โ™€๏ธ -๐Ÿ‘ฑโ€โ™‚๏ธ -๐Ÿง“ -๐Ÿ‘ด -๐Ÿ‘ต -๐Ÿ™ -๐Ÿ™โ€โ™‚๏ธ -๐Ÿ™โ€โ™€๏ธ -๐Ÿ™Ž -๐Ÿ™Žโ€โ™‚๏ธ -๐Ÿ™Žโ€โ™€๏ธ -๐Ÿ™… -๐Ÿ™…โ€โ™‚๏ธ -๐Ÿ™…โ€โ™‚๏ธ -๐Ÿ™…โ€โ™€๏ธ -๐Ÿ™…โ€โ™€๏ธ -๐Ÿ™† -๐Ÿ™†โ€โ™‚๏ธ -๐Ÿ™†โ€โ™€๏ธ -๐Ÿ’ -๐Ÿ’ -๐Ÿ’โ€โ™‚๏ธ -๐Ÿ’โ€โ™‚๏ธ -๐Ÿ’โ€โ™€๏ธ -๐Ÿ’โ€โ™€๏ธ -๐Ÿ™‹ -๐Ÿ™‹โ€โ™‚๏ธ -๐Ÿ™‹โ€โ™€๏ธ -๐Ÿง -๐Ÿงโ€โ™‚๏ธ -๐Ÿงโ€โ™€๏ธ -๐Ÿ™‡ -๐Ÿ™‡โ€โ™‚๏ธ -๐Ÿ™‡โ€โ™€๏ธ -๐Ÿคฆ -๐Ÿคฆโ€โ™‚๏ธ -๐Ÿคฆโ€โ™€๏ธ -๐Ÿคท -๐Ÿคทโ€โ™‚๏ธ -๐Ÿคทโ€โ™€๏ธ -๐Ÿง‘โ€โš•๏ธ -๐Ÿ‘จโ€โš•๏ธ -๐Ÿ‘ฉโ€โš•๏ธ -๐Ÿง‘โ€๐ŸŽ“ -๐Ÿ‘จโ€๐ŸŽ“ -๐Ÿ‘ฉโ€๐ŸŽ“ -๐Ÿง‘โ€๐Ÿซ -๐Ÿ‘จโ€๐Ÿซ -๐Ÿ‘ฉโ€๐Ÿซ -๐Ÿง‘โ€โš–๏ธ -๐Ÿ‘จโ€โš–๏ธ -๐Ÿ‘ฉโ€โš–๏ธ -๐Ÿง‘โ€๐ŸŒพ -๐Ÿ‘จโ€๐ŸŒพ -๐Ÿ‘ฉโ€๐ŸŒพ -๐Ÿง‘โ€๐Ÿณ -๐Ÿ‘จโ€๐Ÿณ -๐Ÿ‘ฉโ€๐Ÿณ -๐Ÿง‘โ€๐Ÿ”ง -๐Ÿ‘จโ€๐Ÿ”ง -๐Ÿ‘ฉโ€๐Ÿ”ง -๐Ÿง‘โ€๐Ÿญ -๐Ÿ‘จโ€๐Ÿญ -๐Ÿ‘ฉโ€๐Ÿญ -๐Ÿง‘โ€๐Ÿ’ผ -๐Ÿ‘จโ€๐Ÿ’ผ -๐Ÿ‘ฉโ€๐Ÿ’ผ -๐Ÿง‘โ€๐Ÿ”ฌ -๐Ÿ‘จโ€๐Ÿ”ฌ -๐Ÿ‘ฉโ€๐Ÿ”ฌ -๐Ÿง‘โ€๐Ÿ’ป -๐Ÿ‘จโ€๐Ÿ’ป -๐Ÿ‘ฉโ€๐Ÿ’ป -๐Ÿง‘โ€๐ŸŽค -๐Ÿ‘จโ€๐ŸŽค -๐Ÿ‘ฉโ€๐ŸŽค -๐Ÿง‘โ€๐ŸŽจ -๐Ÿ‘จโ€๐ŸŽจ -๐Ÿ‘ฉโ€๐ŸŽจ -๐Ÿง‘โ€โœˆ๏ธ -๐Ÿ‘จโ€โœˆ๏ธ -๐Ÿ‘ฉโ€โœˆ๏ธ -๐Ÿง‘โ€๐Ÿš€ -๐Ÿ‘จโ€๐Ÿš€ -๐Ÿ‘ฉโ€๐Ÿš€ -๐Ÿง‘โ€๐Ÿš’ -๐Ÿ‘จโ€๐Ÿš’ -๐Ÿ‘ฉโ€๐Ÿš’ -๐Ÿ‘ฎ -๐Ÿ‘ฎ -๐Ÿ‘ฎโ€โ™‚๏ธ -๐Ÿ‘ฎโ€โ™€๏ธ -๐Ÿ•ต๏ธ -๐Ÿ•ต๏ธโ€โ™‚๏ธ -๐Ÿ•ต๏ธโ€โ™€๏ธ -๐Ÿ’‚ -๐Ÿ’‚โ€โ™‚๏ธ -๐Ÿ’‚โ€โ™€๏ธ -๐Ÿฅท -๐Ÿ‘ท -๐Ÿ‘ทโ€โ™‚๏ธ -๐Ÿ‘ทโ€โ™€๏ธ -๐Ÿคด -๐Ÿ‘ธ -๐Ÿ‘ณ -๐Ÿ‘ณโ€โ™‚๏ธ -๐Ÿ‘ณโ€โ™€๏ธ -๐Ÿ‘ฒ -๐Ÿง• -๐Ÿคต -๐Ÿคตโ€โ™‚๏ธ -๐Ÿคตโ€โ™€๏ธ -๐Ÿ‘ฐ -๐Ÿ‘ฐโ€โ™‚๏ธ -๐Ÿ‘ฐโ€โ™€๏ธ -๐Ÿ‘ฐโ€โ™€๏ธ -๐Ÿคฐ -๐Ÿคฑ -๐Ÿ‘ฉโ€๐Ÿผ -๐Ÿ‘จโ€๐Ÿผ -๐Ÿง‘โ€๐Ÿผ -๐Ÿ‘ผ -๐ŸŽ… -๐Ÿคถ -๐Ÿง‘โ€๐ŸŽ„ -๐Ÿฆธ -๐Ÿฆธโ€โ™‚๏ธ -๐Ÿฆธโ€โ™€๏ธ -๐Ÿฆน -๐Ÿฆนโ€โ™‚๏ธ -๐Ÿฆนโ€โ™€๏ธ -๐Ÿง™ -๐Ÿง™โ€โ™‚๏ธ -๐Ÿง™โ€โ™€๏ธ -๐Ÿงš -๐Ÿงšโ€โ™‚๏ธ -๐Ÿงšโ€โ™€๏ธ -๐Ÿง› -๐Ÿง›โ€โ™‚๏ธ -๐Ÿง›โ€โ™€๏ธ -๐Ÿงœ -๐Ÿงœโ€โ™‚๏ธ -๐Ÿงœโ€โ™€๏ธ -๐Ÿง -๐Ÿงโ€โ™‚๏ธ -๐Ÿงโ€โ™€๏ธ -๐Ÿงž -๐Ÿงžโ€โ™‚๏ธ -๐Ÿงžโ€โ™€๏ธ -๐ŸงŸ -๐ŸงŸโ€โ™‚๏ธ -๐ŸงŸโ€โ™€๏ธ -๐Ÿ’† -๐Ÿ’†โ€โ™‚๏ธ -๐Ÿ’†โ€โ™€๏ธ -๐Ÿ’‡ -๐Ÿ’‡โ€โ™‚๏ธ -๐Ÿ’‡โ€โ™€๏ธ -๐Ÿšถ -๐Ÿšถโ€โ™‚๏ธ -๐Ÿšถโ€โ™€๏ธ -๐Ÿง -๐Ÿงโ€โ™‚๏ธ -๐Ÿงโ€โ™€๏ธ -๐ŸงŽ -๐ŸงŽโ€โ™‚๏ธ -๐ŸงŽโ€โ™€๏ธ -๐Ÿง‘โ€๐Ÿฆฏ -๐Ÿ‘จโ€๐Ÿฆฏ -๐Ÿ‘ฉโ€๐Ÿฆฏ -๐Ÿง‘โ€๐Ÿฆผ -๐Ÿ‘จโ€๐Ÿฆผ -๐Ÿ‘ฉโ€๐Ÿฆผ -๐Ÿง‘โ€๐Ÿฆฝ -๐Ÿ‘จโ€๐Ÿฆฝ -๐Ÿ‘ฉโ€๐Ÿฆฝ -๐Ÿƒ -๐Ÿƒ -๐Ÿƒโ€โ™‚๏ธ -๐Ÿƒโ€โ™€๏ธ -๐Ÿ’ƒ -๐Ÿ’ƒ -๐Ÿ•บ -๐Ÿ•ด๏ธ -๐Ÿ‘ฏ -๐Ÿ‘ฏโ€โ™‚๏ธ -๐Ÿ‘ฏโ€โ™€๏ธ -๐Ÿง– -๐Ÿง–โ€โ™‚๏ธ -๐Ÿง–โ€โ™€๏ธ -๐Ÿง— -๐Ÿง—โ€โ™‚๏ธ -๐Ÿง—โ€โ™€๏ธ -๐Ÿคบ -๐Ÿ‡ -โ›ท๏ธ -๐Ÿ‚ -๐ŸŒ๏ธ -๐ŸŒ๏ธโ€โ™‚๏ธ -๐ŸŒ๏ธโ€โ™€๏ธ -๐Ÿ„ -๐Ÿ„โ€โ™‚๏ธ -๐Ÿ„โ€โ™€๏ธ -๐Ÿšฃ -๐Ÿšฃโ€โ™‚๏ธ -๐Ÿšฃโ€โ™€๏ธ -๐ŸŠ -๐ŸŠโ€โ™‚๏ธ -๐ŸŠโ€โ™€๏ธ -โ›น๏ธ -โ›น๏ธโ€โ™‚๏ธ -โ›น๏ธโ€โ™‚๏ธ -โ›น๏ธโ€โ™€๏ธ -โ›น๏ธโ€โ™€๏ธ -๐Ÿ‹๏ธ -๐Ÿ‹๏ธโ€โ™‚๏ธ -๐Ÿ‹๏ธโ€โ™€๏ธ -๐Ÿšด -๐Ÿšดโ€โ™‚๏ธ -๐Ÿšดโ€โ™€๏ธ -๐Ÿšต -๐Ÿšตโ€โ™‚๏ธ -๐Ÿšตโ€โ™€๏ธ -๐Ÿคธ -๐Ÿคธโ€โ™‚๏ธ -๐Ÿคธโ€โ™€๏ธ -๐Ÿคผ -๐Ÿคผโ€โ™‚๏ธ -๐Ÿคผโ€โ™€๏ธ -๐Ÿคฝ -๐Ÿคฝโ€โ™‚๏ธ -๐Ÿคฝโ€โ™€๏ธ -๐Ÿคพ -๐Ÿคพโ€โ™‚๏ธ -๐Ÿคพโ€โ™€๏ธ -๐Ÿคน -๐Ÿคนโ€โ™‚๏ธ -๐Ÿคนโ€โ™€๏ธ -๐Ÿง˜ -๐Ÿง˜โ€โ™‚๏ธ -๐Ÿง˜โ€โ™€๏ธ -๐Ÿ›€ -๐Ÿ›Œ -๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘ -๐Ÿ‘ญ -๐Ÿ‘ซ -๐Ÿ‘ฌ -๐Ÿ’ -๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ -๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ -๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ -๐Ÿ’‘ -๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘จ -๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ -๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ -๐Ÿ‘ช -๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ -๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง -๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ -๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ -๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง -๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ -๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง -๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ -๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ -๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง -๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ -๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง -๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ -๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ -๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง -๐Ÿ‘จโ€๐Ÿ‘ฆ -๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ -๐Ÿ‘จโ€๐Ÿ‘ง -๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ -๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง -๐Ÿ‘ฉโ€๐Ÿ‘ฆ -๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ -๐Ÿ‘ฉโ€๐Ÿ‘ง -๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ -๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง -๐Ÿ—ฃ๏ธ -๐Ÿ‘ค -๐Ÿ‘ฅ -๐Ÿซ‚ -๐Ÿ‘ฃ -๐Ÿต -๐Ÿ’ -๐Ÿฆ -๐Ÿฆง -๐Ÿถ -๐Ÿ• -๐Ÿฆฎ -๐Ÿ•โ€๐Ÿฆบ -๐Ÿฉ -๐Ÿบ -๐ŸฆŠ -๐Ÿฆ -๐Ÿฑ -๐Ÿˆ -๐Ÿˆโ€โฌ› -๐Ÿฆ -๐Ÿฏ -๐Ÿ… -๐Ÿ† -๐Ÿด -๐ŸŽ -๐Ÿฆ„ -๐Ÿฆ“ -๐ŸฆŒ -๐Ÿฆฌ -๐Ÿฎ -๐Ÿ‚ -๐Ÿƒ -๐Ÿ„ -๐Ÿท -๐Ÿ– -๐Ÿ— -๐Ÿฝ -๐Ÿ -๐Ÿ‘ -๐Ÿ -๐Ÿช -๐Ÿซ -๐Ÿฆ™ -๐Ÿฆ’ -๐Ÿ˜ -๐Ÿฆฃ -๐Ÿฆ -๐Ÿฆ› -๐Ÿญ -๐Ÿ -๐Ÿ€ -๐Ÿน -๐Ÿฐ -๐Ÿ‡ -๐Ÿฟ๏ธ -๐Ÿฆซ -๐Ÿฆ” -๐Ÿฆ‡ -๐Ÿป -๐Ÿปโ€โ„๏ธ -๐Ÿจ -๐Ÿผ -๐Ÿฆฅ -๐Ÿฆฆ -๐Ÿฆจ -๐Ÿฆ˜ -๐Ÿฆก -๐Ÿพ -๐Ÿพ -๐Ÿฆƒ -๐Ÿ” -๐Ÿ“ -๐Ÿฃ -๐Ÿค -๐Ÿฅ -๐Ÿฆ -๐Ÿง -๐Ÿ•Š๏ธ -๐Ÿฆ… -๐Ÿฆ† -๐Ÿฆข -๐Ÿฆ‰ -๐Ÿฆค -๐Ÿชถ -๐Ÿฆฉ -๐Ÿฆš -๐Ÿฆœ -๐Ÿธ -๐ŸŠ -๐Ÿข -๐ŸฆŽ -๐Ÿ -๐Ÿฒ -๐Ÿ‰ -๐Ÿฆ• -๐Ÿฆ– -๐Ÿณ -๐Ÿ‹ -๐Ÿฌ -๐Ÿฌ -๐Ÿฆญ -๐ŸŸ -๐Ÿ  -๐Ÿก -๐Ÿฆˆ -๐Ÿ™ -๐Ÿš -๐ŸŒ -๐Ÿฆ‹ -๐Ÿ› -๐Ÿœ -๐Ÿ -๐Ÿ -๐Ÿชฒ -๐Ÿž -๐Ÿฆ— -๐Ÿชณ -๐Ÿ•ท๏ธ -๐Ÿ•ธ๏ธ -๐Ÿฆ‚ -๐ŸฆŸ -๐Ÿชฐ -๐Ÿชฑ -๐Ÿฆ  -๐Ÿ’ -๐ŸŒธ -๐Ÿ’ฎ -๐Ÿต๏ธ -๐ŸŒน -๐Ÿฅ€ -๐ŸŒบ -๐ŸŒป -๐ŸŒผ -๐ŸŒท -๐ŸŒฑ -๐Ÿชด -๐ŸŒฒ -๐ŸŒณ -๐ŸŒด -๐ŸŒต -๐ŸŒพ -๐ŸŒฟ -โ˜˜๏ธ -๐Ÿ€ -๐Ÿ -๐Ÿ‚ -๐Ÿƒ -๐Ÿ‡ -๐Ÿˆ -๐Ÿ‰ -๐ŸŠ -๐ŸŠ -๐ŸŠ -๐Ÿ‹ -๐ŸŒ -๐ŸŒ -๐Ÿ -๐Ÿฅญ -๐ŸŽ -๐Ÿ -๐Ÿ -๐Ÿ‘ -๐Ÿ’ -๐Ÿ“ -๐Ÿซ -๐Ÿฅ -๐Ÿ… -๐Ÿซ’ -๐Ÿฅฅ -๐Ÿฅ‘ -๐Ÿ† -๐Ÿฅ” -๐Ÿฅ• -๐ŸŒฝ -๐ŸŒถ๏ธ -๐Ÿซ‘ -๐Ÿฅ’ -๐Ÿฅฌ -๐Ÿฅฆ -๐Ÿง„ -๐Ÿง… -๐Ÿ„ -๐Ÿฅœ -๐ŸŒฐ -๐Ÿž -๐Ÿฅ -๐Ÿฅ– -๐Ÿซ“ -๐Ÿฅจ -๐Ÿฅฏ -๐Ÿฅž -๐Ÿง‡ -๐Ÿง€ -๐Ÿ– -๐Ÿ— -๐Ÿฅฉ -๐Ÿฅ“ -๐Ÿ” -๐ŸŸ -๐Ÿ• -๐ŸŒญ -๐Ÿฅช -๐ŸŒฎ -๐ŸŒฏ -๐Ÿซ” -๐Ÿฅ™ -๐Ÿง† -๐Ÿฅš -๐Ÿณ -๐Ÿฅ˜ -๐Ÿฒ -๐Ÿซ• -๐Ÿฅฃ -๐Ÿฅ— -๐Ÿฟ -๐Ÿงˆ -๐Ÿง‚ -๐Ÿฅซ -๐Ÿฑ -๐Ÿ˜ -๐Ÿ™ -๐Ÿš -๐Ÿ› -๐Ÿœ -๐Ÿ -๐Ÿ  -๐Ÿข -๐Ÿฃ -๐Ÿค -๐Ÿฅ -๐Ÿฅฎ -๐Ÿก -๐ŸฅŸ -๐Ÿฅ  -๐Ÿฅก -๐Ÿฆ€ -๐Ÿฆž -๐Ÿฆ -๐Ÿฆ‘ -๐Ÿฆช -๐Ÿฆ -๐Ÿง -๐Ÿจ -๐Ÿฉ -๐Ÿช -๐ŸŽ‚ -๐Ÿฐ -๐Ÿง -๐Ÿฅง -๐Ÿซ -๐Ÿฌ -๐Ÿญ -๐Ÿฎ -๐Ÿฏ -๐Ÿผ -๐Ÿฅ› -โ˜• -๐Ÿซ– -๐Ÿต -๐Ÿถ -๐Ÿพ -๐Ÿท -๐Ÿธ -๐Ÿน -๐Ÿบ -๐Ÿป -๐Ÿฅ‚ -๐Ÿฅƒ -๐Ÿฅค -๐Ÿง‹ -๐Ÿงƒ -๐Ÿง‰ -๐ŸงŠ -๐Ÿฅข -๐Ÿฝ๏ธ -๐Ÿด -๐Ÿฅ„ -๐Ÿ”ช -๐Ÿ”ช -๐Ÿบ -๐ŸŒ -๐ŸŒŽ -๐ŸŒ -๐ŸŒ -๐Ÿ—บ๏ธ -๐Ÿ—พ -๐Ÿงญ -๐Ÿ”๏ธ -โ›ฐ๏ธ -๐ŸŒ‹ -๐Ÿ—ป -๐Ÿ•๏ธ -๐Ÿ–๏ธ -๐Ÿœ๏ธ -๐Ÿ๏ธ -๐Ÿž๏ธ -๐ŸŸ๏ธ -๐Ÿ›๏ธ -๐Ÿ—๏ธ -๐Ÿงฑ -๐Ÿชจ -๐Ÿชต -๐Ÿ›– -๐Ÿ˜๏ธ -๐Ÿš๏ธ -๐Ÿ  -๐Ÿก -๐Ÿข -๐Ÿฃ -๐Ÿค -๐Ÿฅ -๐Ÿฆ -๐Ÿจ -๐Ÿฉ -๐Ÿช -๐Ÿซ -๐Ÿฌ -๐Ÿญ -๐Ÿฏ -๐Ÿฐ -๐Ÿ’’ -๐Ÿ—ผ -๐Ÿ—ฝ -โ›ช -๐Ÿ•Œ -๐Ÿ›• -๐Ÿ• -โ›ฉ๏ธ -๐Ÿ•‹ -โ›ฒ -โ›บ -๐ŸŒ -๐ŸŒƒ -๐Ÿ™๏ธ -๐ŸŒ„ -๐ŸŒ… -๐ŸŒ† -๐ŸŒ‡ -๐ŸŒ‰ -โ™จ๏ธ -๐ŸŽ  -๐ŸŽก -๐ŸŽข -๐Ÿ’ˆ -๐ŸŽช -๐Ÿš‚ -๐Ÿšƒ -๐Ÿš„ -๐Ÿš… -๐Ÿš† -๐Ÿš‡ -๐Ÿšˆ -๐Ÿš‰ -๐ŸšŠ -๐Ÿš -๐Ÿšž -๐Ÿš‹ -๐ŸšŒ -๐Ÿš -๐ŸšŽ -๐ŸšŽ -๐Ÿš -๐Ÿš‘ -๐Ÿš’ -๐Ÿš“ -๐Ÿš” -๐Ÿš• -๐Ÿš– -๐Ÿš— -๐Ÿš— -๐Ÿš˜ -๐Ÿš™ -๐Ÿ›ป -๐Ÿšš -๐Ÿš› -๐Ÿšœ -๐ŸŽ๏ธ -๐Ÿ๏ธ -๐Ÿ›ต -๐Ÿฆฝ -๐Ÿฆผ -๐Ÿ›บ -๐Ÿšฒ -๐Ÿ›ด -๐Ÿ›น -๐Ÿ›ผ -๐Ÿš -๐Ÿ›ฃ๏ธ -๐Ÿ›ค๏ธ -๐Ÿ›ข๏ธ -โ›ฝ -๐Ÿšจ -๐Ÿšฅ -๐Ÿšฆ -๐Ÿ›‘ -๐Ÿšง -โš“ -โ›ต -โ›ต -๐Ÿ›ถ -๐Ÿšค -๐Ÿ›ณ๏ธ -โ›ด๏ธ -๐Ÿ›ฅ๏ธ -๐Ÿšข -โœˆ๏ธ -๐Ÿ›ฉ๏ธ -๐Ÿ›ซ -๐Ÿ›ฌ -๐Ÿช‚ -๐Ÿ’บ -๐Ÿš -๐ŸšŸ -๐Ÿš  -๐Ÿšก -๐Ÿ›ฐ๏ธ -๐Ÿš€ -๐Ÿ›ธ -๐Ÿ›Ž๏ธ -๐Ÿงณ -โŒ› -โณ -โŒš -โฐ -โฑ๏ธ -โฒ๏ธ -๐Ÿ•ฐ๏ธ -๐Ÿ•› -๐Ÿ•ง -๐Ÿ• -๐Ÿ•œ -๐Ÿ•‘ -๐Ÿ• -๐Ÿ•’ -๐Ÿ•ž -๐Ÿ•“ -๐Ÿ•Ÿ -๐Ÿ•” -๐Ÿ•  -๐Ÿ•• -๐Ÿ•ก -๐Ÿ•– -๐Ÿ•ข -๐Ÿ•— -๐Ÿ•ฃ -๐Ÿ•˜ -๐Ÿ•ค -๐Ÿ•™ -๐Ÿ•ฅ -๐Ÿ•š -๐Ÿ•ฆ -๐ŸŒ‘ -๐ŸŒ’ -๐ŸŒ“ -๐ŸŒ” -๐ŸŒ” -๐ŸŒ• -๐ŸŒ– -๐ŸŒ— -๐ŸŒ˜ -๐ŸŒ™ -๐ŸŒš -๐ŸŒ› -๐ŸŒœ -๐ŸŒก๏ธ -โ˜€๏ธ -๐ŸŒ -๐ŸŒž -๐Ÿช -โญ -๐ŸŒŸ -๐ŸŒ  -๐ŸŒŒ -โ˜๏ธ -โ›… -โ›ˆ๏ธ -๐ŸŒค๏ธ -๐ŸŒฅ๏ธ -๐ŸŒฆ๏ธ -๐ŸŒง๏ธ -๐ŸŒจ๏ธ -๐ŸŒฉ๏ธ -๐ŸŒช๏ธ -๐ŸŒซ๏ธ -๐ŸŒฌ๏ธ -๐ŸŒ€ -๐ŸŒˆ -๐ŸŒ‚ -โ˜‚๏ธ -โ˜” -โ›ฑ๏ธ -โšก -โ„๏ธ -โ˜ƒ๏ธ -โ›„ -โ˜„๏ธ -๐Ÿ”ฅ -๐Ÿ’ง -๐ŸŒŠ -๐ŸŽƒ -๐ŸŽ„ -๐ŸŽ† -๐ŸŽ‡ -๐Ÿงจ -โœจ -๐ŸŽˆ -๐ŸŽ‰ -๐ŸŽŠ -๐ŸŽ‹ -๐ŸŽ -๐ŸŽŽ -๐ŸŽ -๐ŸŽ -๐ŸŽ‘ -๐Ÿงง -๐ŸŽ€ -๐ŸŽ -๐ŸŽ—๏ธ -๐ŸŽŸ๏ธ -๐ŸŽซ -๐ŸŽ–๏ธ -๐Ÿ† -๐Ÿ… -๐Ÿฅ‡ -๐Ÿฅˆ -๐Ÿฅ‰ -โšฝ -โšพ -๐ŸฅŽ -๐Ÿ€ -๐Ÿ -๐Ÿˆ -๐Ÿ‰ -๐ŸŽพ -๐Ÿฅ -๐ŸŽณ -๐Ÿ -๐Ÿ‘ -๐Ÿ’ -๐Ÿฅ -๐Ÿ“ -๐Ÿธ -๐ŸฅŠ -๐Ÿฅ‹ -๐Ÿฅ… -โ›ณ -โ›ธ๏ธ -๐ŸŽฃ -๐Ÿคฟ -๐ŸŽฝ -๐ŸŽฟ -๐Ÿ›ท -๐ŸฅŒ -๐ŸŽฏ -๐Ÿช€ -๐Ÿช -๐ŸŽฑ -๐Ÿ”ฎ -๐Ÿช„ -๐Ÿงฟ -๐ŸŽฎ -๐Ÿ•น๏ธ -๐ŸŽฐ -๐ŸŽฒ -๐Ÿงฉ -๐Ÿงธ -๐Ÿช… -๐Ÿช† -โ™ ๏ธ -โ™ฅ๏ธ -โ™ฆ๏ธ -โ™ฃ๏ธ -โ™Ÿ๏ธ -๐Ÿƒ -๐Ÿ€„ -๐ŸŽด -๐ŸŽญ -๐Ÿ–ผ๏ธ -๐ŸŽจ -๐Ÿงต -๐Ÿชก -๐Ÿงถ -๐Ÿชข -๐Ÿ‘“ -๐Ÿ•ถ๏ธ -๐Ÿฅฝ -๐Ÿฅผ -๐Ÿฆบ -๐Ÿ‘” -๐Ÿ‘• -๐Ÿ‘• -๐Ÿ‘– -๐Ÿงฃ -๐Ÿงค -๐Ÿงฅ -๐Ÿงฆ -๐Ÿ‘— -๐Ÿ‘˜ -๐Ÿฅป -๐Ÿฉฑ -๐Ÿฉฒ -๐Ÿฉณ -๐Ÿ‘™ -๐Ÿ‘š -๐Ÿ‘› -๐Ÿ‘œ -๐Ÿ‘ -๐Ÿ›๏ธ -๐ŸŽ’ -๐Ÿฉด -๐Ÿ‘ž -๐Ÿ‘ž -๐Ÿ‘Ÿ -๐Ÿฅพ -๐Ÿฅฟ -๐Ÿ‘  -๐Ÿ‘ก -๐Ÿฉฐ -๐Ÿ‘ข -๐Ÿ‘‘ -๐Ÿ‘’ -๐ŸŽฉ -๐ŸŽ“ -๐Ÿงข -๐Ÿช– -โ›‘๏ธ -๐Ÿ“ฟ -๐Ÿ’„ -๐Ÿ’ -๐Ÿ’Ž -๐Ÿ”‡ -๐Ÿ”ˆ -๐Ÿ”‰ -๐Ÿ”Š -๐Ÿ“ข -๐Ÿ“ฃ -๐Ÿ“ฏ -๐Ÿ”” -๐Ÿ”• -๐ŸŽผ -๐ŸŽต -๐ŸŽถ -๐ŸŽ™๏ธ -๐ŸŽš๏ธ -๐ŸŽ›๏ธ -๐ŸŽค -๐ŸŽง -๐Ÿ“ป -๐ŸŽท -๐Ÿช— -๐ŸŽธ -๐ŸŽน -๐ŸŽบ -๐ŸŽป -๐Ÿช• -๐Ÿฅ -๐Ÿช˜ -๐Ÿ“ฑ -๐Ÿ“ฒ -โ˜Ž๏ธ -โ˜Ž๏ธ -๐Ÿ“ž -๐Ÿ“Ÿ -๐Ÿ“  -๐Ÿ”‹ -๐Ÿ”Œ -๐Ÿ’ป -๐Ÿ–ฅ๏ธ -๐Ÿ–จ๏ธ -โŒจ๏ธ -๐Ÿ–ฑ๏ธ -๐Ÿ–ฒ๏ธ -๐Ÿ’ฝ -๐Ÿ’พ -๐Ÿ’ฟ -๐Ÿ“€ -๐Ÿงฎ -๐ŸŽฅ -๐ŸŽž๏ธ -๐Ÿ“ฝ๏ธ -๐ŸŽฌ -๐Ÿ“บ -๐Ÿ“ท -๐Ÿ“ธ -๐Ÿ“น -๐Ÿ“ผ -๐Ÿ” -๐Ÿ”Ž -๐Ÿ•ฏ๏ธ -๐Ÿ’ก -๐Ÿ”ฆ -๐Ÿฎ -๐Ÿฎ -๐Ÿช” -๐Ÿ“” -๐Ÿ“• -๐Ÿ“– -๐Ÿ“– -๐Ÿ“— -๐Ÿ“˜ -๐Ÿ“™ -๐Ÿ“š -๐Ÿ““ -๐Ÿ“’ -๐Ÿ“ƒ -๐Ÿ“œ -๐Ÿ“„ -๐Ÿ“ฐ -๐Ÿ—ž๏ธ -๐Ÿ“‘ -๐Ÿ”– -๐Ÿท๏ธ -๐Ÿ’ฐ -๐Ÿช™ -๐Ÿ’ด -๐Ÿ’ต -๐Ÿ’ถ -๐Ÿ’ท -๐Ÿ’ธ -๐Ÿ’ณ -๐Ÿงพ -๐Ÿ’น -โœ‰๏ธ -๐Ÿ“ง -๐Ÿ“ง -๐Ÿ“จ -๐Ÿ“ฉ -๐Ÿ“ค -๐Ÿ“ฅ -๐Ÿ“ฆ -๐Ÿ“ซ -๐Ÿ“ช -๐Ÿ“ฌ -๐Ÿ“ญ -๐Ÿ“ฎ -๐Ÿ—ณ๏ธ -โœ๏ธ -โœ’๏ธ -๐Ÿ–‹๏ธ -๐Ÿ–Š๏ธ -๐Ÿ–Œ๏ธ -๐Ÿ–๏ธ -๐Ÿ“ -๐Ÿ“ -๐Ÿ’ผ -๐Ÿ“ -๐Ÿ“‚ -๐Ÿ—‚๏ธ -๐Ÿ“… -๐Ÿ“† -๐Ÿ—’๏ธ -๐Ÿ—“๏ธ -๐Ÿ“‡ -๐Ÿ“ˆ -๐Ÿ“‰ -๐Ÿ“Š -๐Ÿ“‹ -๐Ÿ“Œ -๐Ÿ“ -๐Ÿ“Ž -๐Ÿ–‡๏ธ -๐Ÿ“ -๐Ÿ“ -โœ‚๏ธ -๐Ÿ—ƒ๏ธ -๐Ÿ—„๏ธ -๐Ÿ—‘๏ธ -๐Ÿ”’ -๐Ÿ”“ -๐Ÿ” -๐Ÿ” -๐Ÿ”‘ -๐Ÿ—๏ธ -๐Ÿ”จ -๐Ÿช“ -โ›๏ธ -โš’๏ธ -๐Ÿ› ๏ธ -๐Ÿ—ก๏ธ -โš”๏ธ -๐Ÿ”ซ -๐Ÿชƒ -๐Ÿน -๐Ÿ›ก๏ธ -๐Ÿชš -๐Ÿ”ง -๐Ÿช› -๐Ÿ”ฉ -โš™๏ธ -๐Ÿ—œ๏ธ -โš–๏ธ -๐Ÿฆฏ -๐Ÿ”— -โ›“๏ธ -๐Ÿช -๐Ÿงฐ -๐Ÿงฒ -๐Ÿชœ -โš—๏ธ -๐Ÿงช -๐Ÿงซ -๐Ÿงฌ -๐Ÿ”ฌ -๐Ÿ”ญ -๐Ÿ“ก -๐Ÿ’‰ -๐Ÿฉธ -๐Ÿ’Š -๐Ÿฉน -๐Ÿฉบ -๐Ÿšช -๐Ÿ›— -๐Ÿชž -๐ŸชŸ -๐Ÿ›๏ธ -๐Ÿ›‹๏ธ -๐Ÿช‘ -๐Ÿšฝ -๐Ÿช  -๐Ÿšฟ -๐Ÿ› -๐Ÿชค -๐Ÿช’ -๐Ÿงด -๐Ÿงท -๐Ÿงน -๐Ÿงบ -๐Ÿงป -๐Ÿชฃ -๐Ÿงผ -๐Ÿชฅ -๐Ÿงฝ -๐Ÿงฏ -๐Ÿ›’ -๐Ÿšฌ -โšฐ๏ธ -๐Ÿชฆ -โšฑ๏ธ -๐Ÿ—ฟ -๐Ÿชง -๐Ÿง -๐Ÿšฎ -๐Ÿšฐ -โ™ฟ -๐Ÿšน -๐Ÿšบ -๐Ÿšป -๐Ÿšผ -๐Ÿšพ -๐Ÿ›‚ -๐Ÿ›ƒ -๐Ÿ›„ -๐Ÿ›… -โš ๏ธ -๐Ÿšธ -โ›” -๐Ÿšซ -๐Ÿšณ -๐Ÿšญ -๐Ÿšฏ -๐Ÿšฑ -๐Ÿšท -๐Ÿ“ต -๐Ÿ”ž -โ˜ข๏ธ -โ˜ฃ๏ธ -โฌ†๏ธ -โ†—๏ธ -โžก๏ธ -โ†˜๏ธ -โฌ‡๏ธ -โ†™๏ธ -โฌ…๏ธ -โ†–๏ธ -โ†•๏ธ -โ†”๏ธ -โ†ฉ๏ธ -โ†ช๏ธ -โคด๏ธ -โคต๏ธ -๐Ÿ”ƒ -๐Ÿ”„ -๐Ÿ”™ -๐Ÿ”š -๐Ÿ”› -๐Ÿ”œ -๐Ÿ” -๐Ÿ› -โš›๏ธ -๐Ÿ•‰๏ธ -โœก๏ธ -โ˜ธ๏ธ -โ˜ฏ๏ธ -โœ๏ธ -โ˜ฆ๏ธ -โ˜ช๏ธ -โ˜ฎ๏ธ -๐Ÿ•Ž -๐Ÿ”ฏ -โ™ˆ -โ™‰ -โ™Š -โ™‹ -โ™Œ -โ™ -โ™Ž -โ™ -โ™ -โ™‘ -โ™’ -โ™“ -โ›Ž -๐Ÿ”€ -๐Ÿ” -๐Ÿ”‚ -โ–ถ๏ธ -โฉ -โญ๏ธ -โฏ๏ธ -โ—€๏ธ -โช -โฎ๏ธ -๐Ÿ”ผ -โซ -๐Ÿ”ฝ -โฌ -โธ๏ธ -โน๏ธ -โบ๏ธ -โ๏ธ -๐ŸŽฆ -๐Ÿ”… -๐Ÿ”† -๐Ÿ“ถ -๐Ÿ“ณ -๐Ÿ“ด -โ™€๏ธ -โ™‚๏ธ -โšง๏ธ -โœ–๏ธ -โž• -โž– -โž— -โ™พ๏ธ -โ€ผ๏ธ -โ‰๏ธ -โ“ -โ” -โ• -โ— -โ— -ใ€ฐ๏ธ -๐Ÿ’ฑ -๐Ÿ’ฒ -โš•๏ธ -โ™ป๏ธ -โšœ๏ธ -๐Ÿ”ฑ -๐Ÿ“› -๐Ÿ”ฐ -โญ• -โœ… -โ˜‘๏ธ -โœ”๏ธ -โŒ -โŽ -โžฐ -โžฟ -ใ€ฝ๏ธ -โœณ๏ธ -โœด๏ธ -โ‡๏ธ -ยฉ๏ธ -ยฎ๏ธ -โ„ข๏ธ -#๏ธโƒฃ -*๏ธโƒฃ -0๏ธโƒฃ -1๏ธโƒฃ -2๏ธโƒฃ -3๏ธโƒฃ -4๏ธโƒฃ -5๏ธโƒฃ -6๏ธโƒฃ -7๏ธโƒฃ -8๏ธโƒฃ -9๏ธโƒฃ -๐Ÿ”Ÿ -๐Ÿ”  -๐Ÿ”ก -๐Ÿ”ฃ -๐Ÿ”ค -๐Ÿ…ฐ๏ธ -๐Ÿ†Ž -๐Ÿ…ฑ๏ธ -๐Ÿ†‘ -๐Ÿ†’ -๐Ÿ†“ -โ„น๏ธ -๐Ÿ†” -โ“‚๏ธ -๐Ÿ†• -๐Ÿ†– -๐Ÿ…พ๏ธ -๐Ÿ†— -๐Ÿ…ฟ๏ธ -๐Ÿ†˜ -๐Ÿ†™ -๐Ÿ†š -๐Ÿˆ -๐Ÿˆ‚๏ธ -๐Ÿˆท๏ธ -๐Ÿˆถ -๐Ÿˆฏ -๐Ÿ‰ -๐Ÿˆน -๐Ÿˆš -๐Ÿˆฒ -๐Ÿ‰‘ -๐Ÿˆธ -๐Ÿˆด -๐Ÿˆณ -ใŠ—๏ธ -ใŠ™๏ธ -๐Ÿˆบ -๐Ÿˆต -๐Ÿ”ด -๐ŸŸ  -๐ŸŸก -๐ŸŸข -๐Ÿ”ต -๐ŸŸฃ -๐ŸŸค -โšซ -โšช -๐ŸŸฅ -๐ŸŸง -๐ŸŸจ -๐ŸŸฉ -๐ŸŸฆ -๐ŸŸช -๐ŸŸซ -โฌ› -โฌœ -โ—ผ๏ธ -โ—ป๏ธ -โ—พ -โ—ฝ -โ–ช๏ธ -โ–ซ๏ธ -๐Ÿ”ถ -๐Ÿ”ท -๐Ÿ”ธ -๐Ÿ”น -๐Ÿ”บ -๐Ÿ”ป -๐Ÿ’  -๐Ÿ”˜ -๐Ÿ”ณ -๐Ÿ”ฒ -๐Ÿ -๐Ÿšฉ -๐ŸŽŒ -๐Ÿด -๐Ÿณ๏ธ -๐Ÿณ๏ธโ€๐ŸŒˆ -๐Ÿณ๏ธโ€โšง๏ธ -๐Ÿดโ€โ˜ ๏ธ -๐Ÿ‡ฆ๐Ÿ‡จ -๐Ÿ‡ฆ๐Ÿ‡ฉ -๐Ÿ‡ฆ๐Ÿ‡ช -๐Ÿ‡ฆ๐Ÿ‡ซ -๐Ÿ‡ฆ๐Ÿ‡ฌ -๐Ÿ‡ฆ๐Ÿ‡ฎ -๐Ÿ‡ฆ๐Ÿ‡ฑ -๐Ÿ‡ฆ๐Ÿ‡ฒ -๐Ÿ‡ฆ๐Ÿ‡ด -๐Ÿ‡ฆ๐Ÿ‡ถ -๐Ÿ‡ฆ๐Ÿ‡ท -๐Ÿ‡ฆ๐Ÿ‡ธ -๐Ÿ‡ฆ๐Ÿ‡น -๐Ÿ‡ฆ๐Ÿ‡บ -๐Ÿ‡ฆ๐Ÿ‡ผ -๐Ÿ‡ฆ๐Ÿ‡ฝ -๐Ÿ‡ฆ๐Ÿ‡ฟ -๐Ÿ‡ง๐Ÿ‡ฆ -๐Ÿ‡ง๐Ÿ‡ง -๐Ÿ‡ง๐Ÿ‡ฉ -๐Ÿ‡ง๐Ÿ‡ช -๐Ÿ‡ง๐Ÿ‡ซ -๐Ÿ‡ง๐Ÿ‡ฌ -๐Ÿ‡ง๐Ÿ‡ญ -๐Ÿ‡ง๐Ÿ‡ฎ -๐Ÿ‡ง๐Ÿ‡ฏ -๐Ÿ‡ง๐Ÿ‡ฑ -๐Ÿ‡ง๐Ÿ‡ฒ -๐Ÿ‡ง๐Ÿ‡ณ -๐Ÿ‡ง๐Ÿ‡ด -๐Ÿ‡ง๐Ÿ‡ถ -๐Ÿ‡ง๐Ÿ‡ท -๐Ÿ‡ง๐Ÿ‡ธ -๐Ÿ‡ง๐Ÿ‡น -๐Ÿ‡ง๐Ÿ‡ป -๐Ÿ‡ง๐Ÿ‡ผ -๐Ÿ‡ง๐Ÿ‡พ -๐Ÿ‡ง๐Ÿ‡ฟ -๐Ÿ‡จ๐Ÿ‡ฆ -๐Ÿ‡จ๐Ÿ‡จ -๐Ÿ‡จ๐Ÿ‡ฉ -๐Ÿ‡จ๐Ÿ‡ซ -๐Ÿ‡จ๐Ÿ‡ฌ -๐Ÿ‡จ๐Ÿ‡ญ -๐Ÿ‡จ๐Ÿ‡ฎ -๐Ÿ‡จ๐Ÿ‡ฐ -๐Ÿ‡จ๐Ÿ‡ฑ -๐Ÿ‡จ๐Ÿ‡ฒ -๐Ÿ‡จ๐Ÿ‡ณ -๐Ÿ‡จ๐Ÿ‡ด -๐Ÿ‡จ๐Ÿ‡ต -๐Ÿ‡จ๐Ÿ‡ท -๐Ÿ‡จ๐Ÿ‡บ -๐Ÿ‡จ๐Ÿ‡ป -๐Ÿ‡จ๐Ÿ‡ผ -๐Ÿ‡จ๐Ÿ‡ฝ -๐Ÿ‡จ๐Ÿ‡พ -๐Ÿ‡จ๐Ÿ‡ฟ -๐Ÿ‡ฉ๐Ÿ‡ช -๐Ÿ‡ฉ๐Ÿ‡ฌ -๐Ÿ‡ฉ๐Ÿ‡ฏ -๐Ÿ‡ฉ๐Ÿ‡ฐ -๐Ÿ‡ฉ๐Ÿ‡ฒ -๐Ÿ‡ฉ๐Ÿ‡ด -๐Ÿ‡ฉ๐Ÿ‡ฟ -๐Ÿ‡ช๐Ÿ‡ฆ -๐Ÿ‡ช๐Ÿ‡จ -๐Ÿ‡ช๐Ÿ‡ช -๐Ÿ‡ช๐Ÿ‡ฌ -๐Ÿ‡ช๐Ÿ‡ญ -๐Ÿ‡ช๐Ÿ‡ท -๐Ÿ‡ช๐Ÿ‡ธ -๐Ÿ‡ช๐Ÿ‡น -๐Ÿ‡ช๐Ÿ‡บ -๐Ÿ‡ช๐Ÿ‡บ -๐Ÿ‡ซ๐Ÿ‡ฎ -๐Ÿ‡ซ๐Ÿ‡ฏ -๐Ÿ‡ซ๐Ÿ‡ฐ -๐Ÿ‡ซ๐Ÿ‡ฒ -๐Ÿ‡ซ๐Ÿ‡ด -๐Ÿ‡ซ๐Ÿ‡ท -๐Ÿ‡ฌ๐Ÿ‡ฆ -๐Ÿ‡ฌ๐Ÿ‡ง -๐Ÿ‡ฌ๐Ÿ‡ง -๐Ÿ‡ฌ๐Ÿ‡ฉ -๐Ÿ‡ฌ๐Ÿ‡ช -๐Ÿ‡ฌ๐Ÿ‡ซ -๐Ÿ‡ฌ๐Ÿ‡ฌ -๐Ÿ‡ฌ๐Ÿ‡ญ -๐Ÿ‡ฌ๐Ÿ‡ฎ -๐Ÿ‡ฌ๐Ÿ‡ฑ -๐Ÿ‡ฌ๐Ÿ‡ฒ -๐Ÿ‡ฌ๐Ÿ‡ณ -๐Ÿ‡ฌ๐Ÿ‡ต -๐Ÿ‡ฌ๐Ÿ‡ถ -๐Ÿ‡ฌ๐Ÿ‡ท -๐Ÿ‡ฌ๐Ÿ‡ธ -๐Ÿ‡ฌ๐Ÿ‡น -๐Ÿ‡ฌ๐Ÿ‡บ -๐Ÿ‡ฌ๐Ÿ‡ผ -๐Ÿ‡ฌ๐Ÿ‡พ -๐Ÿ‡ญ๐Ÿ‡ฐ -๐Ÿ‡ญ๐Ÿ‡ฒ -๐Ÿ‡ญ๐Ÿ‡ณ -๐Ÿ‡ญ๐Ÿ‡ท -๐Ÿ‡ญ๐Ÿ‡น -๐Ÿ‡ญ๐Ÿ‡บ -๐Ÿ‡ฎ๐Ÿ‡จ -๐Ÿ‡ฎ๐Ÿ‡ฉ -๐Ÿ‡ฎ๐Ÿ‡ช -๐Ÿ‡ฎ๐Ÿ‡ฑ -๐Ÿ‡ฎ๐Ÿ‡ฒ -๐Ÿ‡ฎ๐Ÿ‡ณ -๐Ÿ‡ฎ๐Ÿ‡ด -๐Ÿ‡ฎ๐Ÿ‡ถ -๐Ÿ‡ฎ๐Ÿ‡ท -๐Ÿ‡ฎ๐Ÿ‡ธ -๐Ÿ‡ฎ๐Ÿ‡น -๐Ÿ‡ฏ๐Ÿ‡ช -๐Ÿ‡ฏ๐Ÿ‡ฒ -๐Ÿ‡ฏ๐Ÿ‡ด -๐Ÿ‡ฏ๐Ÿ‡ต -๐Ÿ‡ฐ๐Ÿ‡ช -๐Ÿ‡ฐ๐Ÿ‡ฌ -๐Ÿ‡ฐ๐Ÿ‡ญ -๐Ÿ‡ฐ๐Ÿ‡ฎ -๐Ÿ‡ฐ๐Ÿ‡ฒ -๐Ÿ‡ฐ๐Ÿ‡ณ -๐Ÿ‡ฐ๐Ÿ‡ต -๐Ÿ‡ฐ๐Ÿ‡ท -๐Ÿ‡ฐ๐Ÿ‡ผ -๐Ÿ‡ฐ๐Ÿ‡พ -๐Ÿ‡ฐ๐Ÿ‡ฟ -๐Ÿ‡ฑ๐Ÿ‡ฆ -๐Ÿ‡ฑ๐Ÿ‡ง -๐Ÿ‡ฑ๐Ÿ‡จ -๐Ÿ‡ฑ๐Ÿ‡ฎ -๐Ÿ‡ฑ๐Ÿ‡ฐ -๐Ÿ‡ฑ๐Ÿ‡ท -๐Ÿ‡ฑ๐Ÿ‡ธ -๐Ÿ‡ฑ๐Ÿ‡น -๐Ÿ‡ฑ๐Ÿ‡บ -๐Ÿ‡ฑ๐Ÿ‡ป -๐Ÿ‡ฑ๐Ÿ‡พ -๐Ÿ‡ฒ๐Ÿ‡ฆ -๐Ÿ‡ฒ๐Ÿ‡จ -๐Ÿ‡ฒ๐Ÿ‡ฉ -๐Ÿ‡ฒ๐Ÿ‡ช -๐Ÿ‡ฒ๐Ÿ‡ซ -๐Ÿ‡ฒ๐Ÿ‡ฌ -๐Ÿ‡ฒ๐Ÿ‡ญ -๐Ÿ‡ฒ๐Ÿ‡ฐ -๐Ÿ‡ฒ๐Ÿ‡ฑ -๐Ÿ‡ฒ๐Ÿ‡ฒ -๐Ÿ‡ฒ๐Ÿ‡ณ -๐Ÿ‡ฒ๐Ÿ‡ด -๐Ÿ‡ฒ๐Ÿ‡ต -๐Ÿ‡ฒ๐Ÿ‡ถ -๐Ÿ‡ฒ๐Ÿ‡ท -๐Ÿ‡ฒ๐Ÿ‡ธ -๐Ÿ‡ฒ๐Ÿ‡น -๐Ÿ‡ฒ๐Ÿ‡บ -๐Ÿ‡ฒ๐Ÿ‡ป -๐Ÿ‡ฒ๐Ÿ‡ผ -๐Ÿ‡ฒ๐Ÿ‡ฝ -๐Ÿ‡ฒ๐Ÿ‡พ -๐Ÿ‡ฒ๐Ÿ‡ฟ -๐Ÿ‡ณ๐Ÿ‡ฆ -๐Ÿ‡ณ๐Ÿ‡จ -๐Ÿ‡ณ๐Ÿ‡ช -๐Ÿ‡ณ๐Ÿ‡ซ -๐Ÿ‡ณ๐Ÿ‡ฌ -๐Ÿ‡ณ๐Ÿ‡ฎ -๐Ÿ‡ณ๐Ÿ‡ฑ -๐Ÿ‡ณ๐Ÿ‡ด -๐Ÿ‡ณ๐Ÿ‡ต -๐Ÿ‡ณ๐Ÿ‡ท -๐Ÿ‡ณ๐Ÿ‡บ -๐Ÿ‡ณ๐Ÿ‡ฟ -๐Ÿ‡ด๐Ÿ‡ฒ -๐Ÿ‡ต๐Ÿ‡ฆ -๐Ÿ‡ต๐Ÿ‡ช -๐Ÿ‡ต๐Ÿ‡ซ -๐Ÿ‡ต๐Ÿ‡ฌ -๐Ÿ‡ต๐Ÿ‡ญ -๐Ÿ‡ต๐Ÿ‡ฐ -๐Ÿ‡ต๐Ÿ‡ฑ -๐Ÿ‡ต๐Ÿ‡ฒ -๐Ÿ‡ต๐Ÿ‡ณ -๐Ÿ‡ต๐Ÿ‡ท -๐Ÿ‡ต๐Ÿ‡ธ -๐Ÿ‡ต๐Ÿ‡น -๐Ÿ‡ต๐Ÿ‡ผ -๐Ÿ‡ต๐Ÿ‡พ -๐Ÿ‡ถ๐Ÿ‡ฆ -๐Ÿ‡ท๐Ÿ‡ช -๐Ÿ‡ท๐Ÿ‡ด -๐Ÿ‡ท๐Ÿ‡ธ -๐Ÿ‡ท๐Ÿ‡บ -๐Ÿ‡ท๐Ÿ‡ผ -๐Ÿ‡ธ๐Ÿ‡ฆ -๐Ÿ‡ธ๐Ÿ‡ง -๐Ÿ‡ธ๐Ÿ‡จ -๐Ÿ‡ธ๐Ÿ‡ฉ -๐Ÿ‡ธ๐Ÿ‡ช -๐Ÿ‡ธ๐Ÿ‡ฌ -๐Ÿ‡ธ๐Ÿ‡ญ -๐Ÿ‡ธ๐Ÿ‡ฎ -๐Ÿ‡ธ๐Ÿ‡ฏ -๐Ÿ‡ธ๐Ÿ‡ฐ -๐Ÿ‡ธ๐Ÿ‡ฑ -๐Ÿ‡ธ๐Ÿ‡ฒ -๐Ÿ‡ธ๐Ÿ‡ณ -๐Ÿ‡ธ๐Ÿ‡ด -๐Ÿ‡ธ๐Ÿ‡ท -๐Ÿ‡ธ๐Ÿ‡ธ -๐Ÿ‡ธ๐Ÿ‡น -๐Ÿ‡ธ๐Ÿ‡ป -๐Ÿ‡ธ๐Ÿ‡ฝ -๐Ÿ‡ธ๐Ÿ‡พ -๐Ÿ‡ธ๐Ÿ‡ฟ -๐Ÿ‡น๐Ÿ‡ฆ -๐Ÿ‡น๐Ÿ‡จ -๐Ÿ‡น๐Ÿ‡ฉ -๐Ÿ‡น๐Ÿ‡ซ -๐Ÿ‡น๐Ÿ‡ฌ -๐Ÿ‡น๐Ÿ‡ญ -๐Ÿ‡น๐Ÿ‡ฏ -๐Ÿ‡น๐Ÿ‡ฐ -๐Ÿ‡น๐Ÿ‡ฑ -๐Ÿ‡น๐Ÿ‡ฒ -๐Ÿ‡น๐Ÿ‡ณ -๐Ÿ‡น๐Ÿ‡ด -๐Ÿ‡น๐Ÿ‡ท -๐Ÿ‡น๐Ÿ‡น -๐Ÿ‡น๐Ÿ‡ป -๐Ÿ‡น๐Ÿ‡ผ -๐Ÿ‡น๐Ÿ‡ฟ -๐Ÿ‡บ๐Ÿ‡ฆ -๐Ÿ‡บ๐Ÿ‡ฌ -๐Ÿ‡บ๐Ÿ‡ฒ -๐Ÿ‡บ๐Ÿ‡ณ -๐Ÿ‡บ๐Ÿ‡ธ -๐Ÿ‡บ๐Ÿ‡พ -๐Ÿ‡บ๐Ÿ‡ฟ -๐Ÿ‡ป๐Ÿ‡ฆ -๐Ÿ‡ป๐Ÿ‡จ -๐Ÿ‡ป๐Ÿ‡ช -๐Ÿ‡ป๐Ÿ‡ฌ -๐Ÿ‡ป๐Ÿ‡ฎ -๐Ÿ‡ป๐Ÿ‡ณ -๐Ÿ‡ป๐Ÿ‡บ -๐Ÿ‡ผ๐Ÿ‡ซ -๐Ÿ‡ผ๐Ÿ‡ธ -๐Ÿ‡ฝ๐Ÿ‡ฐ -๐Ÿ‡พ๐Ÿ‡ช -๐Ÿ‡พ๐Ÿ‡น -๐Ÿ‡ฟ๐Ÿ‡ฆ -๐Ÿ‡ฟ๐Ÿ‡ฒ -๐Ÿ‡ฟ๐Ÿ‡ผ -๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ -๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ -๐Ÿด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ \ No newline at end of file diff --git a/crates/quark/assets/templates/deletion.html b/crates/quark/assets/templates/deletion.html deleted file mode 100644 index e18b1d8a..00000000 --- a/crates/quark/assets/templates/deletion.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/crates/quark/assets/templates/deletion.original.html b/crates/quark/assets/templates/deletion.original.html deleted file mode 100644 index 1028ce49..00000000 --- a/crates/quark/assets/templates/deletion.original.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - -
- Revolt Logo -
-

Account Deletion

-

- You requested to have your account deleted, if you did not perform - this action please take measures to secure your account immediately. -

- Confirm -
-
- This email is intended for {{email}}
- Sent from Revolt
- Made in the EU -
-
- - diff --git a/crates/quark/assets/templates/deletion.txt b/crates/quark/assets/templates/deletion.txt deleted file mode 100644 index 88112b8e..00000000 --- a/crates/quark/assets/templates/deletion.txt +++ /dev/null @@ -1,7 +0,0 @@ -You requested to have your account deleted, if you did not perform this action please take measures to secure your account immediately. - -Please navigate to: {{url}} - -This email is intended for {{email}} -Sent by Revolt -Made in the EU diff --git a/crates/quark/assets/templates/reset.html b/crates/quark/assets/templates/reset.html deleted file mode 100644 index 6c4da8f6..00000000 --- a/crates/quark/assets/templates/reset.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/crates/quark/assets/templates/reset.original.html b/crates/quark/assets/templates/reset.original.html deleted file mode 100644 index f40c7254..00000000 --- a/crates/quark/assets/templates/reset.original.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - -
- -
-

Password Reset

-

You requested a password reset, click below to continue.

- Reset -
-
- This email is intended for {{email}}
- Sent from Revolt
- Made in the EU -
-
- - diff --git a/crates/quark/assets/templates/reset.txt b/crates/quark/assets/templates/reset.txt deleted file mode 100644 index fdc2751f..00000000 --- a/crates/quark/assets/templates/reset.txt +++ /dev/null @@ -1,7 +0,0 @@ -You requested a password reset, if you did not perform this action you can safely ignore this email. - -Please navigate to: {{url}} - -This email is intended for {{email}} -Sent by Revolt -Made in the EU diff --git a/crates/quark/assets/templates/verify.html b/crates/quark/assets/templates/verify.html deleted file mode 100644 index d7016f62..00000000 --- a/crates/quark/assets/templates/verify.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/crates/quark/assets/templates/verify.original.html b/crates/quark/assets/templates/verify.original.html deleted file mode 100644 index e3ce0d5c..00000000 --- a/crates/quark/assets/templates/verify.original.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - -
- Revolt Logo -
-

Almost there!

-

To complete your sign up, we just need to verify your email.

- Confirm -
-
- This email is intended for {{email}}
- Sent from Revolt
- Made in the EU -
-
- - diff --git a/crates/quark/assets/templates/verify.txt b/crates/quark/assets/templates/verify.txt deleted file mode 100644 index 1fdaaa63..00000000 --- a/crates/quark/assets/templates/verify.txt +++ /dev/null @@ -1,8 +0,0 @@ -Almost there! -To complete your sign up, we just need to verify your email. - -Please navigate to: {{url}} - -This email is intended for {{email}} -Sent by Revolt -Made in the EU diff --git a/crates/quark/assets/user/1.png b/crates/quark/assets/user/1.png deleted file mode 100644 index 88d2e28b..00000000 Binary files a/crates/quark/assets/user/1.png and /dev/null differ diff --git a/crates/quark/assets/user/2.png b/crates/quark/assets/user/2.png deleted file mode 100644 index dd0d2967..00000000 Binary files a/crates/quark/assets/user/2.png and /dev/null differ diff --git a/crates/quark/assets/user/3.png b/crates/quark/assets/user/3.png deleted file mode 100644 index c40c1291..00000000 Binary files a/crates/quark/assets/user/3.png and /dev/null differ diff --git a/crates/quark/assets/user/4.png b/crates/quark/assets/user/4.png deleted file mode 100644 index 6db7af60..00000000 Binary files a/crates/quark/assets/user/4.png and /dev/null differ diff --git a/crates/quark/assets/user/5.png b/crates/quark/assets/user/5.png deleted file mode 100644 index 9f32511c..00000000 Binary files a/crates/quark/assets/user/5.png and /dev/null differ diff --git a/crates/quark/assets/user/6.png b/crates/quark/assets/user/6.png deleted file mode 100644 index 533f920d..00000000 Binary files a/crates/quark/assets/user/6.png and /dev/null differ diff --git a/crates/quark/assets/user/7.png b/crates/quark/assets/user/7.png deleted file mode 100644 index 7d9ffd0e..00000000 Binary files a/crates/quark/assets/user/7.png and /dev/null differ diff --git a/crates/quark/src/database.rs b/crates/quark/src/database.rs deleted file mode 100644 index 7229dadc..00000000 --- a/crates/quark/src/database.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::env; -use std::ops::Deref; - -use crate::r#impl::{DummyDb, MongoDb}; -use crate::AbstractDatabase; - -/// Database information to use to create a client -pub enum DatabaseInfo { - /// Auto-detect the database in use - Auto, - /// Use the mock database - Dummy, - /// Connect to MongoDB - MongoDb(String), - /// Use existing MongoDB connection - MongoDbFromClient(mongodb::Client), -} - -/// Database -#[derive(Debug, Clone)] -pub enum Database { - /// Mock database - Dummy(DummyDb), - /// MongoDB database - MongoDb(MongoDb), -} - -impl DatabaseInfo { - /// Create a database client from the given database information - #[async_recursion] - pub async fn connect(self) -> Result { - Ok(match self { - DatabaseInfo::Auto => { - if let Ok(test_db) = env::var("TEST_DB") { - return match test_db.as_str() { - "REFERENCE" => DatabaseInfo::Dummy.connect().await, - "MONGODB" => { - DatabaseInfo::MongoDb(env::var("MONGODB").expect("`MONGODB` env")) - .connect() - .await - } - _ => unreachable!("must specify REFERENCE or MONGODB"), - }; - } - - if let Ok(uri) = env::var("MONGODB") { - return DatabaseInfo::MongoDb(uri).connect().await; - } - - DatabaseInfo::Dummy.connect().await? - } - DatabaseInfo::Dummy => Database::Dummy(DummyDb), - DatabaseInfo::MongoDb(uri) => { - let client = mongodb::Client::with_uri_str(uri) - .await - .map_err(|_| "Failed to init db connection.".to_string())?; - - Database::MongoDb(MongoDb(client)) - } - DatabaseInfo::MongoDbFromClient(client) => Database::MongoDb(MongoDb(client)), - }) - } -} - -impl Deref for Database { - type Target = dyn AbstractDatabase; - - fn deref(&self) -> &Self::Target { - match self { - Database::Dummy(dummy) => dummy, - Database::MongoDb(mongo) => mongo, - } - } -} - -impl From for revolt_database::Database { - fn from(val: Database) -> Self { - match val { - Database::Dummy(_) => revolt_database::Database::Reference(Default::default()), - Database::MongoDb(MongoDb(client)) => revolt_database::Database::MongoDb( - revolt_database::MongoDb(client, "revolt".to_string()), - ), - } - } -} diff --git a/crates/quark/src/events/client.rs b/crates/quark/src/events/client.rs deleted file mode 100644 index e9a08eb7..00000000 --- a/crates/quark/src/events/client.rs +++ /dev/null @@ -1,237 +0,0 @@ -use authifier::AuthifierEvent; -use revolt_models::v0::{FieldsWebhook, PartialWebhook, Webhook}; -use serde::{Deserialize, Serialize}; - -use crate::models::channel::{FieldsChannel, PartialChannel}; -use crate::models::message::{AppendMessage, PartialMessage}; -use crate::models::server::{FieldsRole, FieldsServer, PartialRole, PartialServer}; -use crate::models::server_member::{FieldsMember, MemberCompositeKey, PartialMember}; -use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus}; -use crate::models::{Channel, Emoji, Member, Message, Report, Server, User, UserSettings}; -use crate::Error; - -/// WebSocket Client Errors -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(tag = "error")] -pub enum WebSocketError { - LabelMe, - InternalError { at: String }, - InvalidSession, - OnboardingNotFinished, - AlreadyAuthenticated, - MalformedData { msg: String }, -} - -/// Ping Packet -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(untagged)] -pub enum Ping { - Binary(Vec), - Number(usize), -} - -/// Untagged Error -#[derive(Serialize)] -#[serde(untagged)] -pub enum ErrorEvent { - Error(WebSocketError), - APIError(Error), -} - -/// Protocol Events -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(tag = "type")] -pub enum EventV1 { - /// Multiple events - Bulk { v: Vec }, - - /// Successfully authenticated - Authenticated, - - /// Basic data to cache - Ready { - users: Vec, - servers: Vec, - channels: Vec, - members: Vec, - emojis: Option>, - }, - - /// Ping response - Pong { data: Ping }, - - /// New message - Message(Message), - - /// Update existing message - MessageUpdate { - id: String, - channel: String, - data: PartialMessage, - }, - - /// Append information to existing message - MessageAppend { - id: String, - channel: String, - append: AppendMessage, - }, - - /// Delete message - MessageDelete { id: String, channel: String }, - - /// New reaction to a message - MessageReact { - id: String, - channel_id: String, - user_id: String, - emoji_id: String, - }, - - /// Remove user's reaction from message - MessageUnreact { - id: String, - channel_id: String, - user_id: String, - emoji_id: String, - }, - - /// Remove a reaction from message - MessageRemoveReaction { - id: String, - channel_id: String, - emoji_id: String, - }, - - /// Bulk delete messages - BulkMessageDelete { channel: String, ids: Vec }, - - /// New channel - ChannelCreate(Channel), - - /// Update existing channel - ChannelUpdate { - id: String, - data: PartialChannel, - clear: Vec, - }, - - /// Delete channel - ChannelDelete { id: String }, - - /// User joins a group - ChannelGroupJoin { id: String, user: String }, - - /// User leaves a group - ChannelGroupLeave { id: String, user: String }, - - /// User started typing in a channel - ChannelStartTyping { id: String, user: String }, - - /// User stopped typing in a channel - ChannelStopTyping { id: String, user: String }, - - /// User acknowledged message in channel - ChannelAck { - id: String, - user: String, - message_id: String, - }, - - /// New server - ServerCreate { - id: String, - server: Server, - channels: Vec, - emojis: Vec, - }, - - /// Update existing server - ServerUpdate { - id: String, - data: PartialServer, - clear: Vec, - }, - - /// Delete server - ServerDelete { id: String }, - - /// Update existing server member - ServerMemberUpdate { - id: MemberCompositeKey, - data: PartialMember, - clear: Vec, - }, - - /// User joins server - ServerMemberJoin { id: String, user: String }, - - /// User left server - ServerMemberLeave { id: String, user: String }, - - /// Server role created or updated - ServerRoleUpdate { - id: String, - role_id: String, - data: PartialRole, - clear: Vec, - }, - - /// Server role deleted - ServerRoleDelete { id: String, role_id: String }, - - /// Update existing user - UserUpdate { - id: String, - data: PartialUser, - clear: Vec, - event_id: Option, - }, - - /// Relationship with another user changed - UserRelationship { - id: String, - user: User, - // ! this field can be deprecated - status: RelationshipStatus, - }, - - /// Settings updated remotely - UserSettingsUpdate { id: String, update: UserSettings }, - - /// User has been platform banned or deleted their account - /// - /// Clients should remove the following associated data: - /// - Messages - /// - DM Channels - /// - Relationships - /// - Server Memberships - /// - /// User flags are specified to explain why a wipe is occurring though not all reasons will necessarily ever appear. - UserPlatformWipe { user_id: String, flags: i32 }, - - /// New emoji - EmojiCreate(Emoji), - - /// Delete emoji - EmojiDelete { id: String }, - - /// New webhook - WebhookCreate(Webhook), - - /// Update existing webhook - WebhookUpdate { - id: String, - data: PartialWebhook, - remove: Vec, - }, - - /// Delete webhook - WebhookDelete { id: String }, - - /// New report - ReportCreate(Report), - - /// Auth events - Auth(AuthifierEvent), -} diff --git a/crates/quark/src/events/impl.rs b/crates/quark/src/events/impl.rs deleted file mode 100644 index c157fe11..00000000 --- a/crates/quark/src/events/impl.rs +++ /dev/null @@ -1,589 +0,0 @@ -use std::collections::HashSet; - -use crate::{ - get_relationship, - models::{ - server_member::FieldsMember, - user::{PartialUser, Presence, RelationshipStatus}, - Channel, Member, User, - }, - perms, Database, Permission, Result, -}; - -use revolt_presence::filter_online; - -use super::{ - client::EventV1, - state::{Cache, State}, -}; - -/// Cache Manager -impl Cache { - /// Check whether the current user can view a channel - pub async fn can_view_channel(&self, db: &Database, channel: &Channel) -> bool { - match &channel { - Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => { - let member = self.members.get(server); - let server = self.servers.get(server); - let mut perms = perms(self.users.get(&self.user_id).unwrap()).channel(channel); - - if let Some(member) = member { - perms.member.set_ref(member); - } - - if let Some(server) = server { - perms.server.set_ref(server); - } - - perms - .has_permission(db, Permission::ViewChannel) - .await - .unwrap_or_default() - } - _ => true, - } - } - - /// Filter a given vector of channels to only include the ones we can access - pub async fn filter_accessible_channels( - &self, - db: &Database, - channels: Vec, - ) -> Vec { - let mut viewable_channels = vec![]; - for channel in channels { - if self.can_view_channel(db, &channel).await { - viewable_channels.push(channel); - } - } - - viewable_channels - } - - /// Check whether we can subscribe to another user - pub fn can_subscribe_to_user(&self, user_id: &str) -> bool { - if let Some(user) = self.users.get(&self.user_id) { - match get_relationship(user, user_id) { - RelationshipStatus::Friend - | RelationshipStatus::Incoming - | RelationshipStatus::Outgoing - | RelationshipStatus::User => true, - _ => { - let user_id = &user_id.to_string(); - for channel in self.channels.values() { - match channel { - Channel::DirectMessage { recipients, .. } - | Channel::Group { recipients, .. } => { - if recipients.contains(user_id) { - return true; - } - } - _ => {} - } - } - - false - } - } - } else { - false - } - } -} - -/// State Manager -impl State { - /// Generate a Ready packet for the current user - pub async fn generate_ready_payload(&mut self, db: &Database) -> Result { - let mut user = self.clone_user(); - - // Find all relationships to the user. - let mut user_ids: HashSet = user - .relations - .as_ref() - .map(|arr| arr.iter().map(|x| x.id.to_string()).collect()) - .unwrap_or_default(); - - // Fetch all memberships with their corresponding servers. - let members: Vec = db.fetch_all_memberships(&user.id).await?; - self.cache.members = members - .iter() - .cloned() - .map(|x| (x.id.server.clone(), x)) - .collect(); - - let server_ids: Vec = members.iter().map(|x| x.id.server.clone()).collect(); - let servers = db.fetch_servers(&server_ids).await?; - self.cache.servers = servers.iter().cloned().map(|x| (x.id.clone(), x)).collect(); - - // Collect channel ids from servers. - let mut channel_ids = vec![]; - for server in &servers { - channel_ids.append(&mut server.channels.clone()); - } - - // Fetch DMs and server channels. - let mut channels = db.find_direct_messages(&user.id).await?; - channels.append(&mut db.fetch_channels(&channel_ids).await?); - - // Filter server channels by permission. - let channels = self.cache.filter_accessible_channels(db, channels).await; - - // Append known user IDs from DMs. - for channel in &channels { - match channel { - Channel::DirectMessage { recipients, .. } | Channel::Group { recipients, .. } => { - user_ids.extend(&mut recipients.clone().into_iter()); - } - _ => {} - } - } - - // Fetch presence data for known users. - let online_ids = filter_online(&user_ids.iter().cloned().collect::>()).await; - user.online = Some(true); - - // Fetch user data. - let users = db - .fetch_users( - &user_ids - .into_iter() - .filter(|x| x != &user.id) - .collect::>(), - ) - .await?; - - // Fetch customisations. - let emojis = Some( - db.fetch_emoji_by_parent_ids( - &servers - .iter() - .map(|x| x.id.to_string()) - .collect::>(), - ) - .await?, - ); - - // Copy data into local state cache. - self.cache.users = users.iter().cloned().map(|x| (x.id.clone(), x)).collect(); - self.cache - .users - .insert(self.cache.user_id.clone(), user.clone()); - self.cache.channels = channels - .iter() - .cloned() - .map(|x| (x.id().to_string(), x)) - .collect(); - - // Make all users appear from our perspective. - let mut users: Vec = users - .into_iter() - .map(|mut x| { - x.online = Some(online_ids.contains(&x.id)); - x.with_relationship(&user) - }) - .collect(); - - // Make sure we see our own user correctly. - user.relationship = Some(RelationshipStatus::User); - users.push(user.foreign()); - - // Set subscription state internally. - self.reset_state(); - self.insert_subscription(self.private_topic.clone()); - - for user in &users { - self.insert_subscription(user.id.clone()); - } - - for server in &servers { - self.insert_subscription(server.id.clone()); - } - - for channel in &channels { - self.insert_subscription(channel.id().to_string()); - } - - Ok(EventV1::Ready { - users, - servers, - channels, - members, - emojis, - }) - } - - /// Re-determine the currently accessible server channels - pub async fn recalculate_server(&mut self, db: &Database, id: &str, event: &mut EventV1) { - if let Some(server) = self.cache.servers.get(id) { - let mut channel_ids = HashSet::new(); - let mut added_channels = vec![]; - let mut removed_channels = vec![]; - - let id = &id.to_string(); - for (channel_id, channel) in &self.cache.channels { - match channel { - Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => { - if server == id { - channel_ids.insert(channel_id.clone()); - - if self.cache.can_view_channel(db, channel).await { - added_channels.push(channel_id.clone()); - } else { - removed_channels.push(channel_id.clone()); - } - } - } - _ => {} - } - } - - let known_ids = server.channels.iter().cloned().collect::>(); - - let mut bulk_events = vec![]; - - for id in added_channels { - self.insert_subscription(id); - } - - for id in removed_channels { - self.remove_subscription(&id); - self.cache.channels.remove(&id); - - bulk_events.push(EventV1::ChannelDelete { id }); - } - - // * NOTE: currently all channels should be cached - // * provided that a server was loaded from payload - let unknowns = known_ids - .difference(&channel_ids) - .cloned() - .collect::>(); - - if !unknowns.is_empty() { - if let Ok(channels) = db.fetch_channels(&unknowns).await { - let viewable_channels = - self.cache.filter_accessible_channels(db, channels).await; - - for channel in viewable_channels { - self.cache - .channels - .insert(channel.id().to_string(), channel.clone()); - - self.insert_subscription(channel.id().to_string()); - bulk_events.push(EventV1::ChannelCreate(channel)); - } - } - } - - if !bulk_events.is_empty() { - let mut new_event = EventV1::Bulk { v: bulk_events }; - std::mem::swap(&mut new_event, event); - - if let EventV1::Bulk { v } = event { - v.push(new_event); - } - } - } - } - - /// Push presence change to the user and all associated server topics - pub async fn broadcast_presence_change(&self, target: bool) { - if if let Some(status) = &self.cache.users.get(&self.cache.user_id).unwrap().status { - status.presence != Some(Presence::Invisible) - } else { - true - } { - let event = EventV1::UserUpdate { - id: self.cache.user_id.clone(), - data: PartialUser { - online: Some(target), - ..Default::default() - }, - clear: vec![], - event_id: Some(ulid::Ulid::new().to_string()), - }; - - for server in self.cache.servers.keys() { - event.clone().p(server.clone()).await; - } - - event.p(self.cache.user_id.clone()).await; - } - } - - /// Handle an incoming event for protocol version 1 - pub async fn handle_incoming_event_v1(&mut self, db: &Database, event: &mut EventV1) -> bool { - /* Superseded by private topics. - if match event { - EventV1::UserRelationship { id, .. } - | EventV1::UserSettingsUpdate { id, .. } - | EventV1::ChannelAck { id, .. } => id != &self.cache.user_id, - EventV1::ServerCreate { server, .. } => server.owner != self.cache.user_id, - EventV1::ChannelCreate(channel) => match channel { - Channel::SavedMessages { user, .. } => user != &self.cache.user_id, - Channel::DirectMessage { recipients, .. } | Channel::Group { recipients, .. } => { - !recipients.contains(&self.cache.user_id) - } - _ => false, - }, - _ => false, - } { - return false; - }*/ - - // An event may trigger recalculation of an entire server's permission. - // Keep track of whether we need to do anything. - let mut queue_server = None; - - // It may also need to sub or unsub a single value. - let mut queue_add = None; - let mut queue_remove = None; - - match event { - EventV1::ChannelCreate(channel) => { - let id = channel.id().to_string(); - self.insert_subscription(id.clone()); - self.cache.channels.insert(id, channel.clone()); - } - EventV1::ChannelUpdate { - id, data, clear, .. - } => { - let could_view: bool = if let Some(channel) = self.cache.channels.get(id) { - self.cache.can_view_channel(db, channel).await - } else { - false - }; - - if let Some(channel) = self.cache.channels.get_mut(id) { - for field in clear { - channel.remove(field); - } - - channel.apply_options(data.clone()); - } - - if !self.cache.channels.contains_key(id) { - if let Ok(channel) = db.fetch_channel(id).await { - self.cache.channels.insert(id.clone(), channel); - } - } - - if let Some(channel) = self.cache.channels.get(id) { - let can_view = self.cache.can_view_channel(db, channel).await; - if could_view != can_view { - if can_view { - queue_add = Some(id.clone()); - *event = EventV1::ChannelCreate(channel.clone()); - } else { - queue_remove = Some(id.clone()); - *event = EventV1::ChannelDelete { id: id.clone() }; - } - } - } - } - EventV1::ChannelDelete { id } => { - self.remove_subscription(id); - self.cache.channels.remove(id); - } - EventV1::ChannelGroupJoin { user, .. } => { - self.insert_subscription(user.clone()); - } - EventV1::ChannelGroupLeave { id, user, .. } => { - if user == &self.cache.user_id { - self.remove_subscription(id); - } else if !self.cache.can_subscribe_to_user(user) { - self.remove_subscription(user); - } - } - - EventV1::ServerCreate { - id, - server, - channels, - emojis: _, - } => { - self.insert_subscription(id.clone()); - self.cache.servers.insert(id.clone(), server.clone()); - let member = Member::new(id.clone(), self.cache.user_id.clone()); - self.cache.members.insert(id.clone(), member); - - for channel in channels { - self.cache - .channels - .insert(channel.id().to_string(), channel.clone()); - } - - queue_server = Some(id.clone()); - } - EventV1::ServerUpdate { - id, data, clear, .. - } => { - if let Some(server) = self.cache.servers.get_mut(id) { - for field in clear { - server.remove(field); - } - - server.apply_options(data.clone()); - } - - if data.default_permissions.is_some() { - queue_server = Some(id.clone()); - } - } - EventV1::ServerMemberJoin { .. } => { - // We will always receive ServerCreate when joining a new server. - } - EventV1::ServerMemberLeave { id, user } => { - if user == &self.cache.user_id { - self.remove_subscription(id); - - if let Some(server) = self.cache.servers.remove(id) { - for channel in &server.channels { - self.remove_subscription(channel); - self.cache.channels.remove(channel); - } - } - self.cache.members.remove(id); - } - } - EventV1::ServerDelete { id } => { - self.remove_subscription(id); - - if let Some(server) = self.cache.servers.remove(id) { - for channel in &server.channels { - self.remove_subscription(channel); - self.cache.channels.remove(channel); - } - } - self.cache.members.remove(id); - } - EventV1::ServerMemberUpdate { id, data, clear } => { - if id.user == self.cache.user_id { - if let Some(member) = self.cache.members.get_mut(&id.server) { - for field in &clear.clone() { - member.remove(field); - } - - member.apply_options(data.clone()); - } - - if data.roles.is_some() || clear.contains(&FieldsMember::Roles) { - queue_server = Some(id.server.clone()); - } - } - } - EventV1::ServerRoleUpdate { - id, - role_id, - data, - clear, - .. - } => { - if let Some(server) = self.cache.servers.get_mut(id) { - if let Some(role) = server.roles.get_mut(role_id) { - for field in &clear.clone() { - role.remove(field); - } - - role.apply_options(data.clone()); - } - } - - if data.rank.is_some() || data.permissions.is_some() { - if let Some(member) = self.cache.members.get(id) { - if member.roles.contains(role_id) { - queue_server = Some(id.clone()); - } - } - } - } - EventV1::ServerRoleDelete { id, role_id } => { - if let Some(server) = self.cache.servers.get_mut(id) { - server.roles.remove(role_id); - } - - if let Some(member) = self.cache.members.get(id) { - if member.roles.contains(role_id) { - queue_server = Some(id.clone()); - } - } - } - - EventV1::UserUpdate { event_id, .. } => { - if let Some(id) = event_id { - if self.cache.seen_events.contains(id) { - return false; - } - - self.cache.seen_events.put(id.to_string(), ()); - } - - *event_id = None; - } - EventV1::UserRelationship { id, user, .. } => { - self.cache.users.insert(id.clone(), user.clone()); - - if self.cache.can_subscribe_to_user(id) { - self.insert_subscription(id.clone()); - } else { - self.remove_subscription(id); - } - } - - _ => {} - } - - // Calculate server permissions if requested. - if let Some(server_id) = queue_server { - self.recalculate_server(db, &server_id, event).await; - } - - // Sub / unsub accordingly. - if let Some(id) = queue_add { - self.insert_subscription(id); - } - - if let Some(id) = queue_remove { - self.remove_subscription(&id); - } - - true - } -} - -impl EventV1 { - /// Publish helper wrapper - pub async fn p(self, channel: String) { - #[cfg(not(debug_assertions))] - redis_kiss::p(channel, self).await; - - #[cfg(debug_assertions)] - info!("Publishing event to {channel}: {self:?}"); - - #[cfg(debug_assertions)] - redis_kiss::publish(channel, self).await.unwrap(); - } - - /// Publish user event - pub async fn p_user(self, id: String, db: &Database) { - self.clone().p(id.clone()).await; - - // TODO: this should be captured by member list in the future and not immediately fanned out to users - if let Ok(members) = db.fetch_all_memberships(&id).await { - for member in members { - self.clone().p(member.id.server).await; - } - } - } - - /// Publish private event - pub async fn private(self, id: String) { - self.p(format!("{id}!")).await; - } - - /// Publish internal global event - pub async fn global(self) { - self.p("global".to_string()).await; - } -} diff --git a/crates/quark/src/events/mod.rs b/crates/quark/src/events/mod.rs deleted file mode 100644 index 4669f467..00000000 --- a/crates/quark/src/events/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod client; -pub mod r#impl; -pub mod server; -pub mod state; diff --git a/crates/quark/src/events/server.rs b/crates/quark/src/events/server.rs deleted file mode 100644 index 883b92c3..00000000 --- a/crates/quark/src/events/server.rs +++ /dev/null @@ -1,12 +0,0 @@ -use serde::Deserialize; - -use super::client::Ping; - -#[derive(Deserialize, Debug)] -#[serde(tag = "type")] -pub enum ClientMessage { - Authenticate { token: String }, - BeginTyping { channel: String }, - EndTyping { channel: String }, - Ping { data: Ping, responded: Option<()> }, -} diff --git a/crates/quark/src/events/state.rs b/crates/quark/src/events/state.rs deleted file mode 100644 index 278a18ac..00000000 --- a/crates/quark/src/events/state.rs +++ /dev/null @@ -1,165 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use lru::LruCache; - -use crate::models::{Channel, Member, Server, User}; - -/// Enumeration representing some change in subscriptions -pub enum SubscriptionStateChange { - /// No change - None, - /// Clear all subscriptions - Reset, - /// Append or remove subscriptions - Change { - add: Vec, - remove: Vec, - }, -} - -/// Dumb per-state cache implementation -/// -/// Ideally this would use a global cache that -/// allows for mutations and could use Rc<> to -/// track usage. If Rc<> == 1, then it only -/// remains in global cache, hence should be -/// dropped. -/// -/// ------------------------------------------------ -/// We can strip these objects to core information!! -/// ------------------------------------------------ -#[derive(Debug)] -pub struct Cache { - pub user_id: String, - - pub users: HashMap, - pub channels: HashMap, - pub members: HashMap, - pub servers: HashMap, - - pub seen_events: LruCache, -} - -impl Default for Cache { - fn default() -> Self { - Cache { - user_id: Default::default(), - - users: Default::default(), - channels: Default::default(), - members: Default::default(), - servers: Default::default(), - - seen_events: LruCache::new(20), - } - } -} - -/// Client state -pub struct State { - pub cache: Cache, - - pub private_topic: String, - subscribed: HashSet, - state: SubscriptionStateChange, -} - -impl State { - /// Create state from User - pub fn from(user: User) -> State { - let mut subscribed = HashSet::new(); - let private_topic = format!("{}!", user.id); - subscribed.insert(private_topic.clone()); - subscribed.insert(user.id.clone()); - - let mut cache: Cache = Cache { - user_id: user.id.clone(), - ..Default::default() - }; - - cache.users.insert(user.id.clone(), user); - - State { - cache, - subscribed, - private_topic, - state: SubscriptionStateChange::Reset, - } - } - - /// Apply currently queued state - pub fn apply_state(&mut self) -> SubscriptionStateChange { - let state = std::mem::replace(&mut self.state, SubscriptionStateChange::None); - if let SubscriptionStateChange::Change { add, remove } = &state { - for id in add { - self.subscribed.insert(id.clone()); - } - - for id in remove { - self.subscribed.remove(id); - } - } - - state - } - - /// Clone the active user - pub fn clone_user(&self) -> User { - self.cache.users.get(&self.cache.user_id).unwrap().clone() - } - - /// Iterate through all subscriptions - pub fn iter_subscriptions(&self) -> std::collections::hash_set::Iter<'_, std::string::String> { - self.subscribed.iter() - } - - /// Reset the current state - pub fn reset_state(&mut self) { - self.state = SubscriptionStateChange::Reset; - self.subscribed.clear(); - } - - /// Add a new subscription - pub fn insert_subscription(&mut self, subscription: String) { - if self.subscribed.contains(&subscription) { - return; - } - - match &mut self.state { - SubscriptionStateChange::None => { - self.state = SubscriptionStateChange::Change { - add: vec![subscription.clone()], - remove: vec![], - }; - } - SubscriptionStateChange::Change { add, .. } => { - add.push(subscription.clone()); - } - SubscriptionStateChange::Reset => {} - } - - self.subscribed.insert(subscription); - } - - /// Remove existing subscription - pub fn remove_subscription(&mut self, subscription: &str) { - if !self.subscribed.contains(&subscription.to_string()) { - return; - } - - match &mut self.state { - SubscriptionStateChange::None => { - self.state = SubscriptionStateChange::Change { - add: vec![], - remove: vec![subscription.to_string()], - }; - } - SubscriptionStateChange::Change { remove, .. } => { - remove.push(subscription.to_string()); - } - SubscriptionStateChange::Reset => panic!("Should not remove during a reset!"), - } - - self.subscribed.remove(subscription); - } -} diff --git a/crates/quark/src/impl/dummy/admin/stats.rs b/crates/quark/src/impl/dummy/admin/stats.rs deleted file mode 100644 index a5fc808e..00000000 --- a/crates/quark/src/impl/dummy/admin/stats.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::{models::stats::Stats, AbstractStats, Result}; - -use super::super::DummyDb; - -#[async_trait] -impl AbstractStats for DummyDb { - async fn generate_stats(&self) -> Result { - todo!() - } -} diff --git a/crates/quark/src/impl/dummy/channels/channel.rs b/crates/quark/src/impl/dummy/channels/channel.rs deleted file mode 100644 index ebcbead6..00000000 --- a/crates/quark/src/impl/dummy/channels/channel.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::models::{channel::{Channel, FieldsChannel, PartialChannel}}; -use crate::{AbstractAttachment, AbstractChannel, Error, OverrideField, Result}; - -use super::super::DummyDb; - -#[async_trait] -impl AbstractChannel for DummyDb { - async fn fetch_channel(&self, id: &str) -> Result { - Ok(Channel::Group { - id: id.into(), - - name: "group".into(), - owner: "owner".into(), - description: None, - recipients: vec!["owner".into()], - - icon: Some( - self.find_and_use_attachment("dummy", "dummy", "dummy", "dummy") - .await?, - ), - last_message_id: None, - - permissions: None, - - nsfw: false, - }) - } - - async fn fetch_channels<'a>(&self, _ids: &'a [String]) -> Result> { - Ok(vec![self.fetch_channel("sus").await.unwrap()]) - } - - async fn insert_channel(&self, channel: &Channel) -> Result<()> { - info!("Insert {channel:?}"); - Ok(()) - } - - async fn update_channel( - &self, - id: &str, - channel: &PartialChannel, - remove: Vec, - ) -> Result<()> { - info!("Update {id} with {channel:?} and remove {remove:?}"); - Ok(()) - } - - async fn delete_channel(&self, channel: &Channel) -> Result<()> { - info!("Delete {channel:?}"); - Ok(()) - } - - async fn find_direct_messages(&self, user_id: &str) -> Result> { - Ok(vec![self.fetch_channel(user_id).await?]) - } - - async fn find_saved_messages_channel(&self, user: &str) -> Result { - self.fetch_channel(user).await - } - - async fn find_direct_message_channel(&self, _user_a: &str, _user_b: &str) -> Result { - Err(Error::NotFound) - } - - async fn add_user_to_group(&self, channel: &str, user: &str) -> Result<()> { - info!("Added {user} to {channel}"); - Ok(()) - } - - async fn remove_user_from_group(&self, channel: &str, user: &str) -> Result<()> { - info!("Removed {user} from {channel}"); - Ok(()) - } - - async fn set_channel_role_permission( - &self, - channel: &str, - role: &str, - permissions: OverrideField, - ) -> Result<()> { - info!("Updating permissions for role {role} in {channel} with {permissions:?}"); - Ok(()) - } -} diff --git a/crates/quark/src/impl/dummy/channels/channel_invite.rs b/crates/quark/src/impl/dummy/channels/channel_invite.rs deleted file mode 100644 index 1e292821..00000000 --- a/crates/quark/src/impl/dummy/channels/channel_invite.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::models::Invite; -use crate::{AbstractChannelInvite, Result}; - -use super::super::DummyDb; - -#[async_trait] -impl AbstractChannelInvite for DummyDb { - async fn fetch_invite(&self, code: &str) -> Result { - Ok(Invite::Server { - code: code.into(), - server: "server".into(), - creator: "creator".into(), - channel: "channel".into(), - }) - } - - async fn insert_invite(&self, invite: &Invite) -> Result<()> { - info!("Insert {invite:?}"); - Ok(()) - } - - async fn delete_invite(&self, code: &str) -> Result<()> { - info!("Delete {code}"); - Ok(()) - } - - async fn fetch_invites_for_server(&self, server: &str) -> Result> { - Ok(vec![self.fetch_invite(server).await.unwrap()]) - } -} diff --git a/crates/quark/src/impl/dummy/channels/channel_unread.rs b/crates/quark/src/impl/dummy/channels/channel_unread.rs deleted file mode 100644 index a271fa9e..00000000 --- a/crates/quark/src/impl/dummy/channels/channel_unread.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::models::channel_unread::ChannelUnread; -use crate::{AbstractChannelUnread, Result}; - -use super::super::DummyDb; - -#[async_trait] -impl AbstractChannelUnread for DummyDb { - async fn acknowledge_message(&self, channel: &str, user: &str, message: &str) -> Result<()> { - info!("Acknowledged {message} in {channel} for {user}"); - Ok(()) - } - - async fn acknowledge_channels(&self, user: &str, channels: &[String]) -> Result<()> { - info!("Acknowledged {channels:?} for {user}"); - Ok(()) - } - - async fn add_mention_to_unread<'a>( - &self, - channel: &str, - user: &str, - ids: &[String], - ) -> Result<()> { - info!("Added mentions for {user} in {channel}: {ids:?}"); - Ok(()) - } - - async fn fetch_unreads(&self, _user: &str) -> Result> { - Ok(vec![]) - } -} diff --git a/crates/quark/src/impl/dummy/channels/message.rs b/crates/quark/src/impl/dummy/channels/message.rs deleted file mode 100644 index c88ce11f..00000000 --- a/crates/quark/src/impl/dummy/channels/message.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::models::message::{AppendMessage, Message, MessageQuery, PartialMessage}; -use crate::{AbstractMessage, Result}; - -use super::super::DummyDb; - -#[async_trait] -impl AbstractMessage for DummyDb { - async fn fetch_message(&self, id: &str) -> Result { - Ok(Message { - id: id.into(), - channel: "channel".into(), - author: "author".into(), - content: Some("message content".into()), - - ..Default::default() - }) - } - - async fn insert_message(&self, message: &Message) -> Result<()> { - info!("Insert {message:?}"); - Ok(()) - } - - async fn update_message(&self, id: &str, message: &PartialMessage) -> Result<()> { - info!("Update {id} with {message:?}"); - Ok(()) - } - - async fn append_message(&self, id: &str, append: &AppendMessage) -> Result<()> { - info!("Append {id} with {append:?}"); - Ok(()) - } - - async fn delete_message(&self, id: &str) -> Result<()> { - info!("Delete {id}"); - Ok(()) - } - - async fn delete_messages(&self, channel: &str, ids: Vec) -> Result<()> { - info!("Delete {ids:?} in {channel}"); - Ok(()) - } - - async fn fetch_messages(&self, _query: MessageQuery) -> Result> { - Ok(vec![]) - } - - /// Add a new reaction to a message - async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> { - info!("Add to {id} with {emoji} and {user}"); - Ok(()) - } - - /// Remove a reaction from a message - async fn remove_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> { - info!("Remove {emoji} from {id} for {user}"); - Ok(()) - } - - /// Remove reaction from a message - async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()> { - info!("Clear {emoji} on {id}"); - Ok(()) - } -} diff --git a/crates/quark/src/impl/dummy/media/attachment.rs b/crates/quark/src/impl/dummy/media/attachment.rs deleted file mode 100644 index 6197ad19..00000000 --- a/crates/quark/src/impl/dummy/media/attachment.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::models::attachment::File; -use crate::{AbstractAttachment, Result}; - -use super::super::DummyDb; - -#[async_trait] -impl AbstractAttachment for DummyDb { - async fn find_and_use_attachment( - &self, - attachment_id: &str, - tag: &str, - _parent_type: &str, - parent_id: &str, - ) -> Result { - Ok(File { - id: attachment_id.into(), - tag: tag.into(), - filename: "file.txt".into(), - content_type: "plain/text".into(), - size: 100, - - object_id: Some(parent_id.into()), - - ..Default::default() - }) - } - - async fn insert_attachment(&self, attachment: &File) -> Result<()> { - info!("Insert {attachment:?}"); - Ok(()) - } - - async fn mark_attachment_as_reported(&self, id: &str) -> Result<()> { - info!("Marked {id} as reported"); - Ok(()) - } - - async fn mark_attachment_as_deleted(&self, id: &str) -> Result<()> { - info!("Marked {id} as deleted"); - Ok(()) - } - - async fn mark_attachments_as_deleted(&self, ids: &[String]) -> Result<()> { - info!("Marked {ids:?} as deleted"); - Ok(()) - } -} diff --git a/crates/quark/src/impl/dummy/media/emoji.rs b/crates/quark/src/impl/dummy/media/emoji.rs deleted file mode 100644 index 6695db69..00000000 --- a/crates/quark/src/impl/dummy/media/emoji.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::models::emoji::EmojiParent; -use crate::models::Emoji; -use crate::{AbstractEmoji, Result}; - -use super::super::DummyDb; - -#[async_trait] -impl AbstractEmoji for DummyDb { - /// Fetch an emoji by its id - async fn fetch_emoji(&self, id: &str) -> Result { - Ok(Emoji { - id: id.into(), - name: id.into(), - parent: EmojiParent::Server { id: id.into() }, - creator_id: id.into(), - animated: false, - nsfw: false, - }) - } - - /// Fetch emoji by their ids - async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result> { - Ok(vec![self.fetch_emoji(parent_id).await?]) - } - - /// Fetch emoji by their parent ids - async fn fetch_emoji_by_parent_ids(&self, _parent_ids: &[String]) -> Result> { - Ok(vec![]) - } - - /// Insert emoji into database. - async fn insert_emoji(&self, emoji: &Emoji) -> Result<()> { - info!("Insert {emoji:?}"); - Ok(()) - } - - /// Detach an emoji by its id - async fn detach_emoji(&self, emoji: &Emoji) -> Result<()> { - info!("Detach {emoji:?}"); - Ok(()) - } -} diff --git a/crates/quark/src/impl/dummy/mod.rs b/crates/quark/src/impl/dummy/mod.rs deleted file mode 100644 index cd72470d..00000000 --- a/crates/quark/src/impl/dummy/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::AbstractDatabase; - -pub mod admin { - pub mod stats; -} - -pub mod media { - pub mod attachment; - pub mod emoji; -} - -pub mod channels { - pub mod channel; - pub mod channel_invite; - pub mod channel_unread; - pub mod message; -} - -pub mod servers { - pub mod server; - pub mod server_ban; - pub mod server_member; -} - -pub mod users { - pub mod user; - pub mod user_settings; -} - -pub mod safety { - pub mod report; - pub mod snapshot; -} - -#[derive(Debug, Clone)] -pub struct DummyDb; - -impl AbstractDatabase for DummyDb {} diff --git a/crates/quark/src/impl/dummy/safety/report.rs b/crates/quark/src/impl/dummy/safety/report.rs deleted file mode 100644 index 3286f997..00000000 --- a/crates/quark/src/impl/dummy/safety/report.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::models::report::PartialReport; -use crate::models::Report; -use crate::{AbstractReport, Result}; - -use super::super::DummyDb; - -#[async_trait] -impl AbstractReport for DummyDb { - async fn insert_report(&self, report: &Report) -> Result<()> { - info!("Insert {:?}", report); - Ok(()) - } - - async fn update_report(&self, _id: &str, _report: &PartialReport) -> Result<()> { - todo!() - } - - async fn fetch_report(&self, _report_id: &str) -> Result { - todo!() - } - - async fn fetch_reports(&self) -> Result> { - Ok(vec![]) - } -} diff --git a/crates/quark/src/impl/dummy/safety/snapshot.rs b/crates/quark/src/impl/dummy/safety/snapshot.rs deleted file mode 100644 index 691f28d4..00000000 --- a/crates/quark/src/impl/dummy/safety/snapshot.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::models::Snapshot; -use crate::{AbstractSnapshot, Result}; - -use super::super::DummyDb; - -#[async_trait] -impl AbstractSnapshot for DummyDb { - async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()> { - info!("Insert {:?}", snapshot); - Ok(()) - } - - async fn fetch_snapshots(&self, _report_id: &str) -> Result> { - todo!() - } -} diff --git a/crates/quark/src/impl/dummy/servers/server.rs b/crates/quark/src/impl/dummy/servers/server.rs deleted file mode 100644 index 4d693ad5..00000000 --- a/crates/quark/src/impl/dummy/servers/server.rs +++ /dev/null @@ -1,78 +0,0 @@ -use crate::models::server::{FieldsRole, FieldsServer, PartialRole, PartialServer, Role, Server}; -use crate::{AbstractServer, Result, DEFAULT_PERMISSION_SERVER}; - -use super::super::DummyDb; - -#[async_trait] -impl AbstractServer for DummyDb { - async fn fetch_server(&self, id: &str) -> Result { - Ok(Server { - id: id.into(), - owner: "owner".into(), - - name: "server".into(), - description: Some("server description".into()), - - channels: vec!["channel".into()], - categories: None, - system_messages: None, - - roles: std::collections::HashMap::new(), - default_permissions: *DEFAULT_PERMISSION_SERVER as i64, - - icon: None, - banner: None, - - flags: None, - - nsfw: false, - analytics: true, - discoverable: true, - }) - } - - async fn fetch_servers<'a>(&self, _ids: &'a [String]) -> Result> { - Ok(vec![self.fetch_server("sus").await.unwrap()]) - } - - async fn insert_server(&self, server: &Server) -> Result<()> { - info!("Insert {server:?}"); - Ok(()) - } - - async fn update_server( - &self, - id: &str, - server: &PartialServer, - remove: Vec, - ) -> Result<()> { - info!("Update {id} with {server:?} and remove {remove:?}"); - Ok(()) - } - - async fn delete_server(&self, server: &Server) -> Result<()> { - info!("Delete {server:?}"); - Ok(()) - } - - async fn insert_role(&self, server_id: &str, role_id: &str, role: &Role) -> Result<()> { - info!("Create {role:?} on {server_id} as {role_id}"); - Ok(()) - } - - async fn update_role( - &self, - server_id: &str, - role_id: &str, - role: &PartialRole, - remove: Vec, - ) -> Result<()> { - info!("Update {role_id} on {server_id} with {role:?} and remove {remove:?}"); - Ok(()) - } - - async fn delete_role(&self, server_id: &str, role_id: &str) -> Result<()> { - info!("Delete {role_id} on {server_id}"); - Ok(()) - } -} diff --git a/crates/quark/src/impl/dummy/servers/server_ban.rs b/crates/quark/src/impl/dummy/servers/server_ban.rs deleted file mode 100644 index c07700d5..00000000 --- a/crates/quark/src/impl/dummy/servers/server_ban.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::models::server_member::MemberCompositeKey; -use crate::models::ServerBan; -use crate::{AbstractServerBan, Result}; - -use super::super::DummyDb; - -#[async_trait] -impl AbstractServerBan for DummyDb { - async fn fetch_ban(&self, server: &str, user: &str) -> Result { - Ok(ServerBan { - id: MemberCompositeKey { - server: server.into(), - user: user.into(), - }, - reason: Some("ban reason".into()), - }) - } - - async fn fetch_bans(&self, server: &str) -> Result> { - Ok(vec![self.fetch_ban(server, "user").await.unwrap()]) - } - - async fn insert_ban(&self, ban: &ServerBan) -> Result<()> { - info!("Insert {ban:?}"); - Ok(()) - } - - async fn delete_ban(&self, id: &MemberCompositeKey) -> Result<()> { - info!("Delete {id:?}"); - Ok(()) - } -} diff --git a/crates/quark/src/impl/dummy/servers/server_member.rs b/crates/quark/src/impl/dummy/servers/server_member.rs deleted file mode 100644 index 535e2fb6..00000000 --- a/crates/quark/src/impl/dummy/servers/server_member.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::models::server_member::{ - FieldsMember, Member, MemberCompositeKey, MemberWithRoles, PartialMember, -}; -use crate::{AbstractServerMember, Result}; - -use super::super::DummyDb; - -#[async_trait] -impl AbstractServerMember for DummyDb { - async fn fetch_member(&self, server: &str, user: &str) -> Result { - Ok(Member::new(server.into(), user.into())) - } - async fn fetch_member_with_roles(&self, server: &str, user: &str) -> Result { - Ok(MemberWithRoles::new(server.into(), user.into())) - } - - async fn insert_member(&self, member: &Member) -> Result<()> { - info!("Create {member:?}"); - Ok(()) - } - - async fn update_member( - &self, - id: &MemberCompositeKey, - member: &PartialMember, - remove: Vec, - ) -> Result<()> { - info!("Update {id:?} with {member:?} and remove {remove:?}"); - Ok(()) - } - - async fn delete_member(&self, id: &MemberCompositeKey) -> Result<()> { - info!("Delete {id:?}"); - Ok(()) - } - - async fn fetch_all_members<'a>(&self, server: &str) -> Result> { - Ok(vec![self.fetch_member(server, "member").await.unwrap()]) - } - - async fn fetch_all_memberships<'a>(&self, user: &str) -> Result> { - Ok(vec![self.fetch_member("server", user).await.unwrap()]) - } - - async fn fetch_members<'a>(&self, server: &str, _ids: &'a [String]) -> Result> { - Ok(vec![self.fetch_member(server, "member").await.unwrap()]) - } - - async fn fetch_member_count(&self, _server: &str) -> Result { - Ok(100) - } - - async fn fetch_server_count(&self, _user: &str) -> Result { - Ok(5) - } -} diff --git a/crates/quark/src/impl/dummy/users/user.rs b/crates/quark/src/impl/dummy/users/user.rs deleted file mode 100644 index 8a0c7f6a..00000000 --- a/crates/quark/src/impl/dummy/users/user.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus, User}; -use crate::{AbstractUser, Result}; - -use super::super::DummyDb; - -#[async_trait] -impl AbstractUser for DummyDb { - async fn fetch_user(&self, id: &str) -> Result { - Ok(User { - id: id.into(), - username: "username".into(), - discriminator: "0000".into(), - ..Default::default() - }) - } - - async fn fetch_user_by_username(&self, username: &str, _discriminator: &str) -> Result { - self.fetch_user(username).await - } - - async fn fetch_user_by_token(&self, token: &str) -> Result { - self.fetch_user(token).await - } - - async fn insert_user(&self, user: &User) -> Result<()> { - info!("Insert {:?}", user); - Ok(()) - } - - async fn update_user( - &self, - id: &str, - user: &PartialUser, - remove: Vec, - ) -> Result<()> { - info!("Update {id} with {user:?} and remove {remove:?}"); - Ok(()) - } - - async fn delete_user(&self, id: &str) -> Result<()> { - info!("Delete {id}"); - Ok(()) - } - - async fn fetch_users<'a>(&self, _id: &'a [String]) -> Result> { - Ok(vec![self.fetch_user("id").await.unwrap()]) - } - - async fn fetch_discriminators_in_use(&self, _username: &str) -> Result> { - Ok(vec![]) - } - - async fn fetch_mutual_user_ids(&self, _user_a: &str, _user_b: &str) -> Result> { - Ok(vec!["a".into()]) - } - - async fn fetch_mutual_channel_ids(&self, _user_a: &str, _user_b: &str) -> Result> { - Ok(vec!["b".into()]) - } - - async fn fetch_mutual_server_ids(&self, _user_a: &str, _user_b: &str) -> Result> { - Ok(vec!["c".into()]) - } - - async fn set_relationship( - &self, - user_id: &str, - target_id: &str, - relationship: &RelationshipStatus, - ) -> Result<()> { - info!("Set relationship from {user_id} to {target_id} as {relationship:?}"); - Ok(()) - } - - async fn pull_relationship(&self, user_id: &str, target_id: &str) -> Result<()> { - info!("Removing relationship from {user_id} to {target_id}"); - Ok(()) - } -} diff --git a/crates/quark/src/impl/dummy/users/user_settings.rs b/crates/quark/src/impl/dummy/users/user_settings.rs deleted file mode 100644 index 745892b6..00000000 --- a/crates/quark/src/impl/dummy/users/user_settings.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::models::UserSettings; -use crate::{AbstractUserSettings, Result}; - -use super::super::DummyDb; - -#[async_trait] -impl AbstractUserSettings for DummyDb { - async fn fetch_user_settings( - &'_ self, - _id: &str, - _filter: &'_ [String], - ) -> Result { - Ok(std::collections::HashMap::new()) - } - - async fn set_user_settings(&self, id: &str, settings: &UserSettings) -> Result<()> { - info!("Set {id} to {settings:?}"); - Ok(()) - } - - async fn delete_user_settings(&self, id: &str) -> Result<()> { - info!("Delete {id}"); - Ok(()) - } -} diff --git a/crates/quark/src/impl/generic/channels/channel.rs b/crates/quark/src/impl/generic/channels/channel.rs deleted file mode 100644 index d020585f..00000000 --- a/crates/quark/src/impl/generic/channels/channel.rs +++ /dev/null @@ -1,566 +0,0 @@ -use std::collections::HashSet; - -use revolt_database::util::idempotency::IdempotencyKey; -use ulid::Ulid; - -use crate::{ - events::client::EventV1, - models::{ - channel::{FieldsChannel, PartialChannel}, - message::{DataMessageSend, Message, Reply, SystemMessage, RE_MENTION}, - Channel, - }, - tasks::{ack::AckEvent, process_embeds}, - types::push::MessageAuthor, - variables::delta::{MAX_ATTACHMENT_COUNT, MAX_EMBED_COUNT, MAX_REPLY_COUNT}, - Database, Error, OverrideField, Ref, Result, -}; - -impl Channel { - /// Get a reference to this channel's id - pub fn id(&'_ self) -> &'_ str { - match self { - Channel::DirectMessage { id, .. } - | Channel::Group { id, .. } - | Channel::SavedMessages { id, .. } - | Channel::TextChannel { id, .. } - | Channel::VoiceChannel { id, .. } => id, - } - } - - /// Represent channel as its id - pub fn as_id(self) -> String { - match self { - Channel::DirectMessage { id, .. } - | Channel::Group { id, .. } - | Channel::SavedMessages { id, .. } - | Channel::TextChannel { id, .. } - | Channel::VoiceChannel { id, .. } => id, - } - } - - /// Map out whether it is a direct DM - pub fn is_direct_dm(&self) -> bool { - matches!(self, Channel::DirectMessage { .. }) - } - - /// Create a channel - pub async fn create(&self, db: &Database) -> Result<()> { - db.insert_channel(self).await?; - - let event = EventV1::ChannelCreate(self.clone()); - match self { - Self::SavedMessages { user, .. } => event.private(user.clone()).await, - Self::DirectMessage { recipients, .. } | Self::Group { recipients, .. } => { - for recipient in recipients { - event.clone().private(recipient.clone()).await; - } - } - Self::TextChannel { server, .. } | Self::VoiceChannel { server, .. } => { - event.p(server.clone()).await; - } - } - - Ok(()) - } - - /// Update channel data - pub async fn update<'a>( - &mut self, - db: &Database, - partial: PartialChannel, - remove: Vec, - ) -> Result<()> { - for field in &remove { - self.remove(field); - } - - self.apply_options(partial.clone()); - - let id = self.id().to_string(); - db.update_channel(&id, &partial, remove.clone()).await?; - - EventV1::ChannelUpdate { - id: id.clone(), - data: partial, - clear: remove, - } - .p(match self { - Self::TextChannel { server, .. } | Self::VoiceChannel { server, .. } => server.clone(), - _ => id, - }) - .await; - - Ok(()) - } - - /// Delete a channel - pub async fn delete(self, db: &Database) -> Result<()> { - let id = self.id().to_string(); - EventV1::ChannelDelete { id: id.clone() }.p(id).await; - db.delete_channel(&self).await - } - - /// Remove a field from Channel object - pub fn remove(&mut self, field: &FieldsChannel) { - match field { - FieldsChannel::Description => match self { - Self::Group { description, .. } - | Self::TextChannel { description, .. } - | Self::VoiceChannel { description, .. } => { - description.take(); - } - _ => {} - }, - FieldsChannel::Icon => match self { - Self::Group { icon, .. } - | Self::TextChannel { icon, .. } - | Self::VoiceChannel { icon, .. } => { - icon.take(); - } - _ => {} - }, - FieldsChannel::DefaultPermissions => match self { - Self::TextChannel { - default_permissions, - .. - } - | Self::VoiceChannel { - default_permissions, - .. - } => { - default_permissions.take(); - } - _ => {} - }, - } - } - - /// Apply partial channel to channel - pub fn apply_options(&mut self, partial: PartialChannel) { - // ! FIXME: maybe flatten channel object? - match self { - Self::DirectMessage { active, .. } => { - if let Some(v) = partial.active { - *active = v; - } - } - Self::Group { - name, - owner, - description, - icon, - nsfw, - permissions, - .. - } => { - if let Some(v) = partial.name { - *name = v; - } - - if let Some(v) = partial.owner { - *owner = v; - } - - if let Some(v) = partial.description { - description.replace(v); - } - - if let Some(v) = partial.icon { - icon.replace(v); - } - - if let Some(v) = partial.nsfw { - *nsfw = v; - } - - if let Some(v) = partial.permissions { - permissions.replace(v); - } - } - Self::TextChannel { - name, - description, - icon, - nsfw, - default_permissions, - role_permissions, - .. - } - | Self::VoiceChannel { - name, - description, - icon, - nsfw, - default_permissions, - role_permissions, - .. - } => { - if let Some(v) = partial.name { - *name = v; - } - - if let Some(v) = partial.description { - description.replace(v); - } - - if let Some(v) = partial.icon { - icon.replace(v); - } - - if let Some(v) = partial.nsfw { - *nsfw = v; - } - - if let Some(v) = partial.role_permissions { - *role_permissions = v; - } - - if let Some(v) = partial.default_permissions { - default_permissions.replace(v); - } - } - _ => {} - } - } - - /// Acknowledge a message - pub async fn ack(&self, user: &str, message: &str) -> Result<()> { - EventV1::ChannelAck { - id: self.id().to_string(), - user: user.to_string(), - message_id: message.to_string(), - } - .private(user.to_string()) - .await; - - crate::tasks::ack::queue( - self.id().to_string(), - user.to_string(), - AckEvent::AckMessage { - id: message.to_string(), - }, - ) - .await; - - Ok(()) - } - - /// Add user to a group - pub async fn add_user_to_group(&mut self, db: &Database, user: &str, by: &str) -> Result<()> { - if let Channel::Group { recipients, .. } = self { - let user = user.to_string(); - if recipients.contains(&user) { - return Err(Error::AlreadyInGroup); - } - - recipients.push(user); - } - - match &self { - Channel::Group { id, .. } => { - db.add_user_to_group(id, user).await?; - - EventV1::ChannelGroupJoin { - id: id.to_string(), - user: user.to_string(), - } - .p(id.to_string()) - .await; - - EventV1::ChannelCreate(self.clone()) - .private(user.to_string()) - .await; - - SystemMessage::UserAdded { - id: user.to_string(), - by: by.to_string(), - } - .into_message(id.to_string()) - .create(db, self, None) - .await - .ok(); - - Ok(()) - } - _ => Err(Error::InvalidOperation), - } - } - - /// Remove user from a group - pub async fn remove_user_from_group( - &self, - db: &Database, - user: &str, - by: Option<&str>, - silent: bool, - ) -> Result<()> { - match &self { - Channel::Group { - id, - owner, - recipients, - .. - } => { - if user == owner { - if let Some(new_owner) = recipients.iter().find(|x| *x != user) { - db.update_channel( - id, - &PartialChannel { - owner: Some(new_owner.into()), - ..Default::default() - }, - vec![], - ) - .await?; - - SystemMessage::ChannelOwnershipChanged { - from: owner.to_string(), - to: new_owner.into(), - } - .into_message(id.to_string()) - .create(db, self, None) - .await - .ok(); - } else { - db.delete_channel(self).await?; - return Ok(()); - } - } - - db.remove_user_from_group(id, user).await?; - - EventV1::ChannelGroupLeave { - id: id.to_string(), - user: user.to_string(), - } - .p(id.to_string()) - .await; - - if !silent { - if let Some(by) = by { - SystemMessage::UserRemove { - id: user.to_string(), - by: by.to_string(), - } - } else { - SystemMessage::UserLeft { - id: user.to_string(), - } - } - .into_message(id.to_string()) - .create(db, self, None) - .await - .ok(); - } - - Ok(()) - } - _ => Err(Error::InvalidOperation), - } - } - - /// Set role permission on a channel - pub async fn set_role_permission( - &mut self, - db: &Database, - role: &str, - permissions: OverrideField, - ) -> Result<()> { - match self { - Channel::TextChannel { - id, - server, - role_permissions, - .. - } - | Channel::VoiceChannel { - id, - server, - role_permissions, - .. - } => { - db.set_channel_role_permission(id, role, permissions) - .await?; - - role_permissions.insert(role.to_string(), permissions); - - EventV1::ChannelUpdate { - id: id.clone(), - data: PartialChannel { - role_permissions: Some(role_permissions.clone()), - ..Default::default() - }, - clear: vec![], - } - .p(server.clone()) - .await; - - Ok(()) - } - _ => Err(Error::InvalidOperation), - } - } - - /// Creates a message in a channel - pub async fn send_message( - &self, - db: &Database, - data: DataMessageSend, - author: MessageAuthor<'_>, - mut idempotency: IdempotencyKey, - generate_embeds: bool, - ) -> Result { - Message::validate_sum(&data.content, data.embeds.as_deref().unwrap_or_default())?; - - idempotency - .consume_nonce(data.nonce) - .await - .map_err(|_| Error::InvalidOperation)?; - - // Check the message is not empty - if (data.content.as_ref().map_or(true, |v| v.is_empty())) - && (data.attachments.as_ref().map_or(true, |v| v.is_empty())) - && (data.embeds.as_ref().map_or(true, |v| v.is_empty())) - { - return Err(Error::EmptyMessage); - } - - // Ensure restrict_reactions is not specified without reactions list - if let Some(interactions) = &data.interactions { - if interactions.restrict_reactions { - let disallowed = if let Some(list) = &interactions.reactions { - list.is_empty() - } else { - true - }; - - if disallowed { - return Err(Error::InvalidProperty); - } - } - } - - let (author_id, webhook) = match &author { - MessageAuthor::User(user) => (user.id.clone(), None), - MessageAuthor::Webhook(webhook) => (webhook.id.clone(), Some((*webhook).clone())), - }; - - // Start constructing the message - let message_id = Ulid::new().to_string(); - let mut message = Message { - id: message_id.clone(), - channel: self.id().to_string(), - masquerade: data.masquerade, - interactions: data.interactions.unwrap_or_default(), - author: author_id, - webhook: webhook.map(|w| w.into()), - ..Default::default() - }; - - // Parse mentions in message. - let mut mentions = HashSet::new(); - if let Some(content) = &data.content { - for capture in RE_MENTION.captures_iter(content) { - if let Some(mention) = capture.get(1) { - mentions.insert(mention.as_str().to_string()); - } - } - } - - // Verify replies are valid. - let mut replies = HashSet::new(); - if let Some(entries) = data.replies { - if entries.len() > *MAX_REPLY_COUNT { - return Err(Error::TooManyReplies { - max: *MAX_REPLY_COUNT, - }); - } - - for Reply { id, mention } in entries { - let message = Ref::from_unchecked(id).as_message(db).await?; - - if mention { - mentions.insert(message.author.to_owned()); - } - - replies.insert(message.id); - } - } - - if !mentions.is_empty() { - message.mentions.replace(mentions.into_iter().collect()); - } - - if !replies.is_empty() { - message - .replies - .replace(replies.into_iter().collect::>()); - } - - // Add attachments to message. - let mut attachments = vec![]; - if data - .attachments - .as_ref() - .is_some_and(|v| v.len() > *MAX_ATTACHMENT_COUNT) - { - return Err(Error::TooManyAttachments { - max: *MAX_ATTACHMENT_COUNT, - }); - } - - if data - .embeds - .as_ref() - .is_some_and(|v| v.len() > *MAX_EMBED_COUNT) - { - return Err(Error::TooManyEmbeds { - max: *MAX_EMBED_COUNT, - }); - } - - for attachment_id in data.attachments.as_deref().unwrap_or_default() { - attachments.push( - db.find_and_use_attachment(attachment_id, "attachments", "message", &message_id) - .await?, - ); - } - - if !attachments.is_empty() { - message.attachments.replace(attachments); - } - - // Process included embeds. - let mut embeds = vec![]; - for sendable_embed in data.embeds.unwrap_or_default() { - embeds.push(sendable_embed.into_embed(db, &message_id).await?) - } - - if !embeds.is_empty() { - message.embeds.replace(embeds); - } - - // Set content - message.content = data.content; - - // Pass-through nonce value for clients - message.nonce = Some(idempotency.into_key()); - - message.create(db, self, Some(author)).await?; - - // Queue up a task for processing embeds - if generate_embeds { - if let Some(content) = &message.content { - process_embeds::queue( - self.id().to_string(), - message.id.to_string(), - content.clone(), - ) - .await; - } - } - - Ok(message) - } -} diff --git a/crates/quark/src/impl/generic/channels/channel_invite.rs b/crates/quark/src/impl/generic/channels/channel_invite.rs deleted file mode 100644 index 4193c34d..00000000 --- a/crates/quark/src/impl/generic/channels/channel_invite.rs +++ /dev/null @@ -1,72 +0,0 @@ -use nanoid::nanoid; - -use crate::{ - models::{Channel, Invite, User}, - Database, Error, Result, -}; - -static ALPHABET: [char; 54] = [ - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', - 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z' -]; - -impl Invite { - /// Get the invite code for this invite - pub fn code(&'_ self) -> &'_ str { - match self { - Invite::Server { code, .. } | Invite::Group { code, .. } => code, - } - } - - /// Get the ID of the user who created this invite - pub fn creator(&'_ self) -> &'_ str { - match self { - Invite::Server { creator, .. } | Invite::Group { creator, .. } => creator, - } - } - - /// Create a new invite from given information - pub async fn create(db: &Database, creator: &User, target: &Channel) -> Result { - let code = nanoid!(8, &ALPHABET); - let invite = match &target { - Channel::Group { id, .. } => Ok(Invite::Group { - code, - creator: creator.id.clone(), - channel: id.clone(), - }), - Channel::TextChannel { id, server, .. } | Channel::VoiceChannel { id, server, .. } => { - Ok(Invite::Server { - code, - creator: creator.id.clone(), - server: server.clone(), - channel: id.clone(), - }) - } - _ => Err(Error::InvalidOperation), - }?; - - db.insert_invite(&invite).await?; - Ok(invite) - } - - /// Resolve an invite by its ID or by a public server ID - pub async fn find(db: &Database, code: &str) -> Result { - if let Ok(invite) = db.fetch_invite(code).await { - return Ok(invite); - } else if let Ok(server) = db.fetch_server(code).await { - if server.discoverable { - if let Some(channel) = server.channels.into_iter().next() { - return Ok(Invite::Server { - code: code.to_string(), - server: server.id, - creator: server.owner, - channel, - }); - } - } - } - - Err(Error::NotFound) - } -} diff --git a/crates/quark/src/impl/generic/channels/channel_unread.rs b/crates/quark/src/impl/generic/channels/channel_unread.rs deleted file mode 100644 index 8b137891..00000000 --- a/crates/quark/src/impl/generic/channels/channel_unread.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/quark/src/impl/generic/channels/message.rs b/crates/quark/src/impl/generic/channels/message.rs deleted file mode 100644 index 64ea74e7..00000000 --- a/crates/quark/src/impl/generic/channels/message.rs +++ /dev/null @@ -1,494 +0,0 @@ -use std::collections::HashSet; - -use revolt_presence::filter_online; -use serde_json::json; -use ulid::Ulid; -use validator::Validate; - -use crate::{ - events::client::EventV1, - models::{ - message::{ - AppendMessage, BulkMessageResponse, Interactions, PartialMessage, SendableEmbed, - SystemMessage, DataMessageSend, - }, - Channel, Emoji, Message, User, - }, - permissions::PermissionCalculator, - tasks::ack::AckEvent, - types::{ - january::{Embed, Text}, - push::{MessageAuthor, PushNotification}, - }, - Database, Error, Permission, Result, -}; - -impl Message { - /// Create a message - pub async fn create_no_web_push( - &mut self, - db: &Database, - channel: &str, - is_direct_dm: bool, - ) -> Result<()> { - db.insert_message(self).await?; - - // Fan out events - EventV1::Message(self.clone()).p(channel.to_string()).await; - - // Update last_message_id - crate::tasks::last_message_id::queue( - channel.to_string(), - self.id.to_string(), - is_direct_dm, - ) - .await; - - // Add mentions for affected users - if let Some(mentions) = &self.mentions { - for user in mentions { - crate::tasks::ack::queue( - channel.to_string(), - user.to_string(), - AckEvent::AddMention { - ids: vec![self.id.to_string()], - }, - ) - .await; - } - } - - Ok(()) - } - - /// Create a message and Web Push events - pub async fn create( - &mut self, - db: &Database, - channel: &Channel, - sender: Option>, - ) -> Result<()> { - self.create_no_web_push(db, channel.id(), channel.is_direct_dm()) - .await?; - - // Push out Web Push notifications - crate::tasks::web_push::queue( - { - let mut target_ids = vec![]; - match &channel { - Channel::DirectMessage { recipients, .. } - | Channel::Group { recipients, .. } => { - target_ids = (&recipients.iter().cloned().collect::>() - - &filter_online(recipients).await) - .into_iter() - .collect::>(); - } - Channel::TextChannel { .. } => { - if let Some(mentions) = &self.mentions { - target_ids.append(&mut mentions.clone()); - } - } - _ => {} - }; - target_ids - }, - json!(PushNotification::new(self.clone(), sender, channel.id())).to_string(), - ) - .await; - - Ok(()) - } - - /// Update message data - pub async fn update(&mut self, db: &Database, partial: PartialMessage) -> Result<()> { - self.apply_options(partial.clone()); - db.update_message(&self.id, &partial).await?; - - EventV1::MessageUpdate { - id: self.id.clone(), - channel: self.channel.clone(), - data: partial, - } - .p(self.channel.clone()) - .await; - - Ok(()) - } - - /// Append message data - pub async fn append( - db: &Database, - id: String, - channel: String, - append: AppendMessage, - ) -> Result<()> { - db.append_message(&id, &append).await?; - - EventV1::MessageAppend { - id, - channel: channel.to_string(), - append, - } - .p(channel) - .await; - - Ok(()) - } - - /// Delete a message - pub async fn delete(self, db: &Database) -> Result<()> { - let file_ids: Vec = self - .attachments - .map(|files| files.iter().map(|file| file.id.to_string()).collect()) - .unwrap_or_default(); - - if !file_ids.is_empty() { - db.mark_attachments_as_deleted(&file_ids).await?; - } - - db.delete_message(&self.id).await?; - - EventV1::MessageDelete { - id: self.id, - channel: self.channel.clone(), - } - .p(self.channel) - .await; - Ok(()) - } - - /// Bulk delete messages - pub async fn bulk_delete(db: &Database, channel: &str, ids: Vec) -> Result<()> { - db.delete_messages(channel, ids.clone()).await?; - EventV1::BulkMessageDelete { - channel: channel.to_string(), - ids, - } - .p(channel.to_string()) - .await; - Ok(()) - } - - /// Validate the sum of content of a message is under threshold - pub fn validate_sum(content: &Option, embeds: &[SendableEmbed]) -> Result<()> { - let mut running_total = 0; - if let Some(content) = content { - running_total += content.len(); - } - - for embed in embeds { - if let Some(desc) = &embed.description { - running_total += desc.len(); - } - } - - if running_total <= 2000 { - Ok(()) - } else { - Err(Error::PayloadTooLarge) - } - } - - /// Add a reaction to a message - pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> { - // Check how many reactions are already on the message - if self.reactions.len() >= 20 && !self.reactions.contains_key(emoji) { - return Err(Error::InvalidOperation); - } - - // Check if the emoji is whitelisted - if !self.interactions.can_use(emoji) { - return Err(Error::InvalidOperation); - } - - // Check if the emoji is usable by us - if !Emoji::can_use(db, emoji).await? { - return Err(Error::InvalidOperation); - } - - // Send reaction event - EventV1::MessageReact { - id: self.id.to_string(), - channel_id: self.channel.to_string(), - user_id: user.id.to_string(), - emoji_id: emoji.to_string(), - } - .p(self.channel.to_string()) - .await; - - // Add emoji - db.add_reaction(&self.id, emoji, &user.id).await - } - - /// Remove a reaction from a message - pub async fn remove_reaction(&self, db: &Database, user: &str, emoji: &str) -> Result<()> { - // Check if it actually exists - let empty = if let Some(users) = self.reactions.get(emoji) { - if !users.contains(user) { - return Err(Error::NotFound); - } - - users.len() == 1 - } else { - return Err(Error::NotFound); - }; - - // Send reaction event - EventV1::MessageUnreact { - id: self.id.to_string(), - channel_id: self.channel.to_string(), - user_id: user.to_string(), - emoji_id: emoji.to_string(), - } - .p(self.channel.to_string()) - .await; - - if empty { - // If empty, remove the reaction entirely - db.clear_reaction(&self.id, emoji).await - } else { - // Otherwise only remove that one reaction - db.remove_reaction(&self.id, emoji, user).await - } - } - - /// Remove a reaction from a message - pub async fn clear_reaction(&self, db: &Database, emoji: &str) -> Result<()> { - // Send reaction event - EventV1::MessageRemoveReaction { - id: self.id.to_string(), - channel_id: self.channel.to_string(), - emoji_id: emoji.to_string(), - } - .p(self.channel.to_string()) - .await; - - // Write to database - db.clear_reaction(&self.id, emoji).await - } - - pub fn is_webhook(&self) -> bool { - self.webhook.is_some() - } -} - -pub trait IntoUsers { - fn get_user_ids(&self) -> Vec; -} - -impl IntoUsers for Message { - fn get_user_ids(&self) -> Vec { - let mut ids = Vec::new(); - - if !self.is_webhook() { - ids.push(self.author.clone()); - }; - - if let Some(msg) = &self.system { - match msg { - SystemMessage::UserAdded { id, by, .. } - | SystemMessage::UserRemove { id, by, .. } => { - ids.push(id.clone()); - ids.push(by.clone()); - } - SystemMessage::UserJoined { id, .. } - | SystemMessage::UserLeft { id, .. } - | SystemMessage::UserKicked { id, .. } - | SystemMessage::UserBanned { id, .. } => ids.push(id.clone()), - SystemMessage::ChannelRenamed { by, .. } - | SystemMessage::ChannelDescriptionChanged { by, .. } - | SystemMessage::ChannelIconChanged { by, .. } => ids.push(by.clone()), - _ => {} - } - } - - ids - } -} - -impl IntoUsers for Vec { - fn get_user_ids(&self) -> Vec { - let mut ids = HashSet::new(); - for message in self { - ids.extend(&mut message.get_user_ids().into_iter()); - } - - ids.into_iter().collect() - } -} - -impl SystemMessage { - pub fn into_message(self, channel: String) -> Message { - Message { - id: Ulid::new().to_string(), - channel, - author: "00000000000000000000000000".to_string(), - system: Some(self), - - ..Default::default() - } - } -} - -impl From for String { - fn from(s: SystemMessage) -> String { - match s { - SystemMessage::Text { content } => content, - SystemMessage::UserAdded { .. } => "User added to the channel.".to_string(), - SystemMessage::UserRemove { .. } => "User removed from the channel.".to_string(), - SystemMessage::UserJoined { .. } => "User joined the channel.".to_string(), - SystemMessage::UserLeft { .. } => "User left the channel.".to_string(), - SystemMessage::UserKicked { .. } => "User kicked from the channel.".to_string(), - SystemMessage::UserBanned { .. } => "User banned from the channel.".to_string(), - SystemMessage::ChannelRenamed { .. } => "Channel renamed.".to_string(), - SystemMessage::ChannelDescriptionChanged { .. } => { - "Channel description changed.".to_string() - } - SystemMessage::ChannelIconChanged { .. } => "Channel icon changed.".to_string(), - SystemMessage::ChannelOwnershipChanged { .. } => { - "Channel ownership changed.".to_string() - } - } - } -} - -impl SendableEmbed { - pub async fn into_embed(self, db: &Database, message_id: &str) -> Result { - self.validate() - .map_err(|error| Error::FailedValidation { error })?; - - let media = if let Some(id) = self.media { - Some( - db.find_and_use_attachment(&id, "attachments", "message", message_id) - .await?, - ) - } else { - None - }; - - Ok(Embed::Text(Text { - icon_url: self.icon_url, - url: self.url, - title: self.title, - description: self.description, - media, - colour: self.colour, - })) - } -} - -impl BulkMessageResponse { - pub async fn transform( - db: &Database, - channel: Option<&Channel>, - messages: Vec, - user: &User, - include_users: Option, - ) -> Result { - if let Some(true) = include_users { - let user_ids = messages.get_user_ids(); - let users = User::fetch_foreign_users(db, &user_ids) - .await? - .into_iter() - .map(|x| x.with_relationship(user)) - .collect(); - - Ok(match channel { - Some(Channel::TextChannel { server, .. }) - | Some(Channel::VoiceChannel { server, .. }) => { - BulkMessageResponse::MessagesAndUsers { - messages, - users, - members: Some(db.fetch_members(server, &user_ids).await?), - } - } - _ => BulkMessageResponse::MessagesAndUsers { - messages, - users, - members: None, - }, - }) - } else { - Ok(BulkMessageResponse::JustMessages(messages)) - } - } -} - -impl Interactions { - /// Validate interactions info is correct - pub async fn validate( - &self, - db: &Database, - permissions: &mut PermissionCalculator<'_>, - ) -> Result<()> { - if let Some(reactions) = &self.reactions { - permissions.throw_permission(db, Permission::React).await?; - - if reactions.len() > 20 { - return Err(Error::InvalidOperation); - } - - for reaction in reactions { - if !Emoji::can_use(db, reaction).await? { - return Err(Error::InvalidOperation); - } - } - } - - Ok(()) - } - - /// Check if we can use a given emoji to react - pub fn can_use(&self, emoji: &str) -> bool { - if self.restrict_reactions { - if let Some(reactions) = &self.reactions { - reactions.contains(emoji) - } else { - false - } - } else { - true - } - } - - /// Check if default initialisation of fields - pub fn is_default(&self) -> bool { - !self.restrict_reactions && self.reactions.is_none() - } -} - - -fn throw_permission(permissions: u64, permission: Permission) -> Result<()> { - if (permission as u64) & permissions == (permission as u64) { - Ok(()) - } else { - Err(Error::MissingPermission { permission }) - } -} - -impl DataMessageSend { - pub fn validate_webhook_permissions( - &self, - permissions: u64, - ) -> Result<()> { - throw_permission(permissions, Permission::SendMessage)?; - - if self.attachments.as_ref().map_or(false, |v| !v.is_empty()) { - throw_permission(permissions, Permission::UploadFiles)?; - }; - - if self.embeds.as_ref().map_or(false, |v| !v.is_empty()) { - throw_permission(permissions, Permission::SendEmbeds)?; - }; - - if self.masquerade.is_some() { - throw_permission(permissions, Permission::Masquerade)?; - }; - - if self.interactions.is_some() { - throw_permission(permissions, Permission::React)?; - }; - - Ok(()) - } -} diff --git a/crates/quark/src/impl/generic/media/attachment.rs b/crates/quark/src/impl/generic/media/attachment.rs deleted file mode 100644 index a21a13b8..00000000 --- a/crates/quark/src/impl/generic/media/attachment.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::{models::File, Database, Result}; - -impl File { - pub async fn use_attachment(db: &Database, id: &str, parent: &str) -> Result { - db.find_and_use_attachment(id, "attachments", "message", parent) - .await - } - - pub async fn use_background(db: &Database, id: &str, parent: &str) -> Result { - db.find_and_use_attachment(id, "backgrounds", "user", parent) - .await - } - - pub async fn use_avatar(db: &Database, id: &str, parent: &str) -> Result { - db.find_and_use_attachment(id, "avatars", "user", parent) - .await - } - - pub async fn use_icon(db: &Database, id: &str, parent: &str) -> Result { - db.find_and_use_attachment(id, "icons", "object", parent) - .await - } - - pub async fn use_server_icon(db: &Database, id: &str, parent: &str) -> Result { - db.find_and_use_attachment(id, "icons", "object", parent) - .await - } - - pub async fn use_banner(db: &Database, id: &str, parent: &str) -> Result { - db.find_and_use_attachment(id, "banners", "server", parent) - .await - } - - pub async fn use_emoji(db: &Database, id: &str, parent: &str) -> Result { - db.find_and_use_attachment(id, "emojis", "object", parent) - .await - } -} diff --git a/crates/quark/src/impl/generic/media/emoji.rs b/crates/quark/src/impl/generic/media/emoji.rs deleted file mode 100644 index 77b3677f..00000000 --- a/crates/quark/src/impl/generic/media/emoji.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::{collections::HashSet, str::FromStr}; -use once_cell::sync::Lazy; - -use ulid::Ulid; - -use crate::{ - events::client::EventV1, - models::{emoji::EmojiParent, Emoji}, - Database, Result, -}; - -static PERMISSIBLE_EMOJIS: Lazy> = Lazy::new(|| include_str!(crate::asset!("emojis.txt")) - .split('\n') - .map(|x| x.into()) - .collect()); - -impl Emoji { - /// Get parent id - fn parent(&self) -> &str { - match &self.parent { - EmojiParent::Server { id } => id, - EmojiParent::Detached => "", - } - } - - /// Create an emoji - pub async fn create(&self, db: &Database) -> Result<()> { - db.insert_emoji(self).await?; - EventV1::EmojiCreate(self.clone()) - .p(self.parent().to_string()) - .await; - - Ok(()) - } - - /// Delete an emoji - pub async fn delete(self, db: &Database) -> Result<()> { - EventV1::EmojiDelete { - id: self.id.to_string(), - } - .p(self.parent().to_string()) - .await; - - db.detach_emoji(&self).await - } - - /// Check whether we can use a given emoji - pub async fn can_use(db: &Database, emoji: &str) -> Result { - if Ulid::from_str(emoji).is_ok() { - db.fetch_emoji(emoji).await?; - Ok(true) - } else { - Ok(PERMISSIBLE_EMOJIS.contains(emoji)) - } - } -} diff --git a/crates/quark/src/impl/generic/mod.rs b/crates/quark/src/impl/generic/mod.rs deleted file mode 100644 index cd4d13b3..00000000 --- a/crates/quark/src/impl/generic/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Database agnostic implementations. - -pub mod media { - pub mod attachment; - pub mod emoji; -} - -pub mod channels { - pub mod channel; - pub mod channel_invite; - pub mod channel_unread; - pub mod message; -} - -pub mod servers { - pub mod server; - pub mod server_ban; - pub mod server_member; -} - -pub mod users { - pub mod user; - pub mod user_settings; -} - -pub mod safety { - pub mod report; - pub mod snapshot; -} diff --git a/crates/quark/src/impl/generic/safety/report.rs b/crates/quark/src/impl/generic/safety/report.rs deleted file mode 100644 index daac34d7..00000000 --- a/crates/quark/src/impl/generic/safety/report.rs +++ /dev/null @@ -1,30 +0,0 @@ -use iso8601_timestamp::Timestamp; - -use crate::{ - models::report::PartialReport, - models::{report::ReportStatus, Report}, - Database, Result, -}; - -impl Report { - /// Update report data - pub async fn update(&mut self, db: &Database, partial: PartialReport) -> Result<()> { - self.apply_options(partial.clone()); - - match &mut self.status { - ReportStatus::Created {} => {} - ReportStatus::Rejected { closed_at, .. } => { - if closed_at.is_none() { - closed_at.replace(Timestamp::now_utc()); - } - } - ReportStatus::Resolved { closed_at } => { - if closed_at.is_none() { - closed_at.replace(Timestamp::now_utc()); - } - } - } - - db.update_report(&self.id, &partial).await - } -} diff --git a/crates/quark/src/impl/generic/safety/snapshot.rs b/crates/quark/src/impl/generic/safety/snapshot.rs deleted file mode 100644 index b5e372a9..00000000 --- a/crates/quark/src/impl/generic/safety/snapshot.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::{ - models::{ - message::{MessageFilter, MessageQuery, MessageSort, MessageTimePeriod}, - snapshot::SnapshotContent, - Message, Server, User, - }, - Database, Result, -}; - -impl SnapshotContent { - pub async fn generate_from_message( - db: &Database, - message: Message, - ) -> Result<(SnapshotContent, Vec)> { - // Collect message attachments - let files = message - .attachments - .as_ref() - .map(|attachments| attachments.iter().map(|x| x.id.to_string()).collect()) - .unwrap_or_default(); - - // Collect prior context - let prior_context = db - .fetch_messages(MessageQuery { - filter: MessageFilter { - channel: Some(message.channel.to_string()), - ..Default::default() - }, - limit: Some(15), - time_period: MessageTimePeriod::Absolute { - before: Some(message.id.to_string()), - after: None, - sort: Some(MessageSort::Latest), - }, - }) - .await?; - - // Collect leading context - let leading_context = db - .fetch_messages(MessageQuery { - filter: MessageFilter { - channel: Some(message.channel.to_string()), - ..Default::default() - }, - limit: Some(15), - time_period: MessageTimePeriod::Absolute { - before: None, - after: Some(message.id.to_string()), - sort: Some(MessageSort::Oldest), - }, - }) - .await?; - - Ok(( - SnapshotContent::Message { - message, - prior_context, - leading_context, - }, - files, - )) - } - - pub fn generate_from_server(server: Server) -> Result<(SnapshotContent, Vec)> { - // Collect server's icon and banner - let files = [&server.icon, &server.banner] - .iter() - .filter_map(|x| x.as_ref().map(|x| x.id.to_string())) - .collect(); - - Ok((SnapshotContent::Server(server), files)) - } - - pub fn generate_from_user(user: User) -> Result<(SnapshotContent, Vec)> { - // Collect user's avatar and profile background - let files = [ - user.avatar.as_ref(), - user.profile - .as_ref() - .and_then(|profile| profile.background.as_ref()), - ] - .iter() - .filter_map(|x| x.as_ref().map(|x| x.id.to_string())) - .collect(); - - Ok((SnapshotContent::User(user), files)) - } -} diff --git a/crates/quark/src/impl/generic/servers/server.rs b/crates/quark/src/impl/generic/servers/server.rs deleted file mode 100644 index 1a4916f0..00000000 --- a/crates/quark/src/impl/generic/servers/server.rs +++ /dev/null @@ -1,330 +0,0 @@ -use std::collections::HashSet; - -use ulid::Ulid; - -use crate::{ - events::client::EventV1, - models::{ - message::SystemMessage, - server::{ - FieldsRole, FieldsServer, PartialRole, PartialServer, Role, SystemMessageChannels, - }, - server_member::{MemberCompositeKey, RemovalIntention}, - Channel, Member, Server, ServerBan, User, - }, - perms, Database, Error, OverrideField, Permission, Result, -}; - -impl Role { - /// Into optional struct - pub fn into_optional(self) -> PartialRole { - PartialRole { - name: Some(self.name), - permissions: Some(self.permissions), - colour: self.colour, - hoist: Some(self.hoist), - rank: Some(self.rank), - } - } - - /// Create a role - pub async fn create(&self, db: &Database, server_id: &str) -> Result { - let role_id = Ulid::new().to_string(); - db.insert_role(server_id, &role_id, self).await?; - - EventV1::ServerRoleUpdate { - id: server_id.to_string(), - role_id: role_id.to_string(), - data: self.clone().into_optional(), - clear: vec![], - } - .p(server_id.to_string()) - .await; - - Ok(role_id) - } - - /// Update server data - pub async fn update<'a>( - &mut self, - db: &Database, - server_id: &str, - role_id: &str, - partial: PartialRole, - remove: Vec, - ) -> Result<()> { - for field in &remove { - self.remove(field); - } - - self.apply_options(partial.clone()); - - db.update_role(server_id, role_id, &partial, remove.clone()) - .await?; - - EventV1::ServerRoleUpdate { - id: server_id.to_string(), - role_id: role_id.to_string(), - data: partial, - clear: vec![], - } - .p(server_id.to_string()) - .await; - - Ok(()) - } - - /// Delete a role - pub async fn delete(self, db: &Database, server_id: &str, role_id: &str) -> Result<()> { - EventV1::ServerRoleDelete { - id: server_id.to_string(), - role_id: role_id.to_string(), - } - .p(server_id.to_string()) - .await; - - db.delete_role(server_id, role_id).await - } - - /// Remove field from Role - pub fn remove(&mut self, field: &FieldsRole) { - match field { - FieldsRole::Colour => self.colour = None, - } - } -} - -impl Server { - /// Create a server - pub async fn create(&self, db: &Database) -> Result<()> { - db.insert_server(self).await - } - - /// Update server data - pub async fn update<'a>( - &mut self, - db: &Database, - partial: PartialServer, - remove: Vec, - ) -> Result<()> { - for field in &remove { - self.remove(field); - } - - self.apply_options(partial.clone()); - - db.update_server(&self.id, &partial, remove.clone()).await?; - - EventV1::ServerUpdate { - id: self.id.clone(), - data: partial, - clear: remove, - } - .p(self.id.clone()) - .await; - - Ok(()) - } - - /// Delete a server - pub async fn delete(self, db: &Database) -> Result<()> { - EventV1::ServerDelete { - id: self.id.clone(), - } - .p(self.id.clone()) - .await; - - db.delete_server(&self).await - } - - /// Remove a field from Server - pub fn remove(&mut self, field: &FieldsServer) { - match field { - FieldsServer::Description => self.description = None, - FieldsServer::Categories => self.categories = None, - FieldsServer::SystemMessages => self.system_messages = None, - FieldsServer::Icon => self.icon = None, - FieldsServer::Banner => self.banner = None, - } - } - - /// Set role permission on a server - pub async fn set_role_permission( - &mut self, - db: &Database, - role_id: &str, - permissions: OverrideField, - ) -> Result<()> { - if let Some(role) = self.roles.get_mut(role_id) { - role.update( - db, - &self.id, - role_id, - PartialRole { - permissions: Some(permissions), - ..Default::default() - }, - vec![], - ) - .await?; - - Ok(()) - } else { - Err(Error::NotFound) - } - } - - /// Create a new member in a server - pub async fn create_member( - &self, - db: &Database, - user: User, - channels: Option>, - ) -> Result> { - if db.fetch_ban(&self.id, &user.id).await.is_ok() { - return Err(Error::Banned); - } - - let member = Member::new(self.id.clone(), user.id.clone()); - db.insert_member(&member).await?; - - let should_fetch = channels.is_none(); - let mut channels = channels.unwrap_or_default(); - - if should_fetch { - let perm = perms(&user).server(self).member(&member); - let existing_channels = db.fetch_channels(&self.channels).await?; - for channel in existing_channels { - if perm - .clone() - .channel(&channel) - .has_permission(db, Permission::ViewChannel) - .await? - { - channels.push(channel); - } - } - } - - let emojis = db.fetch_emoji_by_parent_id(&self.id).await?; - - EventV1::ServerMemberJoin { - id: self.id.clone(), - user: user.id.clone(), - } - .p(self.id.clone()) - .await; - - EventV1::ServerCreate { - id: self.id.clone(), - server: self.clone(), - channels: channels.clone(), - emojis, - } - .private(user.id.clone()) - .await; - - if let Some(id) = self - .system_messages - .as_ref() - .and_then(|x| x.user_joined.as_ref()) - { - SystemMessage::UserJoined { - id: user.id.clone(), - } - .into_message(id.to_string()) - .create_no_web_push(db, id, false) - .await - .ok(); - } - - Ok(channels) - } - - /// Remove a member from a server - pub async fn remove_member( - &self, - db: &Database, - member: Member, - intention: RemovalIntention, - silent: bool, - ) -> Result<()> { - db.delete_member(&member.id).await?; - - EventV1::ServerMemberLeave { - id: self.id.to_string(), - user: member.id.user.clone(), - } - .p(member.id.server) - .await; - - if !silent { - if let Some(id) = self.system_messages.as_ref().and_then(|x| match intention { - RemovalIntention::Leave => x.user_left.as_ref(), - RemovalIntention::Kick => x.user_kicked.as_ref(), - RemovalIntention::Ban => x.user_banned.as_ref(), - }) { - match intention { - RemovalIntention::Leave => SystemMessage::UserLeft { id: member.id.user }, - RemovalIntention::Kick => SystemMessage::UserKicked { id: member.id.user }, - RemovalIntention::Ban => SystemMessage::UserBanned { id: member.id.user }, - } - .into_message(id.to_string()) - .create_no_web_push(db, id, false) - .await - .ok(); - } - } - - Ok(()) - } - - /// Create ban - pub async fn ban_user( - self, - db: &Database, - id: MemberCompositeKey, - reason: Option, - ) -> Result { - let ban = ServerBan { id, reason }; - db.insert_ban(&ban).await?; - Ok(ban) - } - - /// Ban a member from a server - pub async fn ban_member( - self, - db: &Database, - member: Member, - reason: Option, - ) -> Result { - self.remove_member(db, member.clone(), RemovalIntention::Ban, false) - .await?; - - self.ban_user(db, member.id, reason).await - } -} - -impl SystemMessageChannels { - pub fn into_channel_ids(self) -> HashSet { - let mut ids = HashSet::new(); - - if let Some(id) = self.user_joined { - ids.insert(id); - } - - if let Some(id) = self.user_left { - ids.insert(id); - } - - if let Some(id) = self.user_kicked { - ids.insert(id); - } - - if let Some(id) = self.user_banned { - ids.insert(id); - } - - ids - } -} diff --git a/crates/quark/src/impl/generic/servers/server_ban.rs b/crates/quark/src/impl/generic/servers/server_ban.rs deleted file mode 100644 index 8b137891..00000000 --- a/crates/quark/src/impl/generic/servers/server_ban.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/quark/src/impl/generic/servers/server_member.rs b/crates/quark/src/impl/generic/servers/server_member.rs deleted file mode 100644 index b52e49ff..00000000 --- a/crates/quark/src/impl/generic/servers/server_member.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::collections::HashMap; - -use iso8601_timestamp::Timestamp; - -use crate::{ - events::client::EventV1, - models::{ - server_member::{FieldsMember, MemberCompositeKey, MemberWithRoles, PartialMember}, - Member, Server, - }, - Database, Result, -}; - -impl Member { - pub fn new(server_id: String, user_id: String) -> Self { - Self { - id: MemberCompositeKey { - server: server_id, - user: user_id, - }, - joined_at: Timestamp::now_utc(), - nickname: None, - avatar: None, - roles: vec![], - timeout: None, - } - } - - /// Update member data - pub async fn update<'a>( - &mut self, - db: &Database, - partial: PartialMember, - remove: Vec, - ) -> Result<()> { - for field in &remove { - self.remove(field); - } - - self.apply_options(partial.clone()); - - db.update_member(&self.id, &partial, remove.clone()).await?; - - EventV1::ServerMemberUpdate { - id: self.id.clone(), - data: partial, - clear: remove, - } - .p(self.id.server.clone()) - .await; - - Ok(()) - } - - /// Get this user's current ranking - pub fn get_ranking(&self, server: &Server) -> i64 { - let mut value = i64::MAX; - for role in &self.roles { - if let Some(role) = server.roles.get(role) { - if role.rank < value { - value = role.rank; - } - } - } - - value - } - - /// Check whether this member is in timeout - pub fn in_timeout(&self) -> bool { - if let Some(timeout) = self.timeout { - *timeout > *Timestamp::now_utc() - } else { - false - } - } - - pub fn remove(&mut self, field: &FieldsMember) { - match field { - FieldsMember::Avatar => self.avatar = None, - FieldsMember::Nickname => self.nickname = None, - FieldsMember::Roles => self.roles.clear(), - FieldsMember::Timeout => self.timeout = None, - } - } -} - -impl MemberWithRoles { - pub fn new(server_id: String, user_id: String) -> Self { - Self { - member: Member::new(server_id, user_id), - roles: HashMap::new(), - } - } -} diff --git a/crates/quark/src/impl/generic/users/user.rs b/crates/quark/src/impl/generic/users/user.rs deleted file mode 100644 index e0fb6919..00000000 --- a/crates/quark/src/impl/generic/users/user.rs +++ /dev/null @@ -1,510 +0,0 @@ -use crate::events::client::EventV1; -use crate::models::user::{ - Badges, FieldsUser, PartialUser, Presence, RelationshipStatus, User, UserHint, -}; -use crate::permissions::defn::UserPerms; -use crate::permissions::r#impl::user::get_relationship; -use crate::{perms, Database, Error, Result}; - -use futures::try_join; -use impl_ops::impl_op_ex_commutative; -use once_cell::sync::Lazy; -use rand::seq::SliceRandom; -use revolt_database::RatelimitEventType; -use revolt_presence::filter_online; -use std::collections::HashSet; -use std::ops; -use std::time::Duration; - -impl_op_ex_commutative!(+ |a: &i32, b: &Badges| -> i32 { *a | *b as i32 }); - -impl User { - /// Update user data - pub async fn update<'a>( - &mut self, - db: &Database, - partial: PartialUser, - remove: Vec, - ) -> Result<()> { - for field in &remove { - self.remove(field); - } - - self.apply_options(partial.clone()); - - db.update_user(&self.id, &partial, remove.clone()).await?; - - EventV1::UserUpdate { - id: self.id.clone(), - data: partial, - clear: remove, - event_id: Some(ulid::Ulid::new().to_string()), - } - .p_user(self.id.clone(), db) - .await; - - Ok(()) - } - - /// Remove a field from User object - pub fn remove(&mut self, field: &FieldsUser) { - match field { - FieldsUser::Avatar => self.avatar = None, - FieldsUser::StatusText => { - if let Some(x) = self.status.as_mut() { - x.text = None; - } - } - FieldsUser::StatusPresence => { - if let Some(x) = self.status.as_mut() { - x.presence = None; - } - } - FieldsUser::ProfileContent => { - if let Some(x) = self.profile.as_mut() { - x.content = None; - } - } - FieldsUser::ProfileBackground => { - if let Some(x) = self.profile.as_mut() { - x.background = None; - } - } - FieldsUser::DisplayName => self.display_name = None, - } - } - - /// Mutate the user object to remove redundant information - #[must_use] - pub fn foreign(mut self) -> User { - self.profile = None; - self.relations = None; - - let mut badges = self.badges.unwrap_or(0); - if let Ok(id) = ulid::Ulid::from_string(&self.id) { - // Yes, this is hard-coded - // No, I don't care + ratio - if id.datetime().timestamp_millis() < 1629638578431 { - badges = badges + Badges::EarlyAdopter; - } - } - - self.badges = Some(badges); - - if let Some(status) = &self.status { - if let Some(presence) = &status.presence { - if presence == &Presence::Invisible { - self.status = None; - self.online = Some(false); - } - } - } - - self - } - - /// Fetch foreign users by a list of IDs - pub async fn fetch_foreign_users(db: &Database, user_ids: &[String]) -> Result> { - let online_ids = filter_online(user_ids).await; - - Ok(db - .fetch_users(user_ids) - .await? - .into_iter() - .map(|mut user| { - user.online = Some(online_ids.contains(&user.id)); - user.foreign() - }) - .collect::>()) - } - - /// Mutate the user object to include relationship (if it does not already exist) - #[must_use] - pub fn with_relationship(self, perspective: &User) -> User { - let mut user = self.foreign(); - - if user.relationship.is_none() { - user.relationship = Some(get_relationship(perspective, &user.id)); - } - - user - } - - /// Mutate user object with given permission - #[must_use] - pub fn apply_permission(mut self, permission: &UserPerms) -> User { - if !permission.get_view_profile() { - self.status = None; - } - - self - } - - /// Helper function to apply relationship and permission - #[must_use] - pub fn with_perspective(self, perspective: &User, permission: &UserPerms) -> User { - self.with_relationship(perspective) - .apply_permission(permission) - } - - /// Helper function to calculate perspective - pub async fn with_auto_perspective(self, db: &Database, perspective: &User) -> User { - let user = self.with_relationship(perspective); - let permissions = perms(perspective).user(&user).calc_user(db).await; - user.apply_permission(&permissions) - } - - /// Check whether two users have a mutual connection - /// - /// This will check if user and user_b share a server or a group. - pub async fn has_mutual_connection(&self, db: &Database, user_b: &str) -> Result { - Ok(!db - .fetch_mutual_server_ids(&self.id, user_b) - .await? - .is_empty() - || !db - .fetch_mutual_channel_ids(&self.id, user_b) - .await? - .is_empty()) - } - - /// Check if this user can acquire another server - pub async fn can_acquire_server(&self, db: &Database) -> Result { - // hardcoded max server count - // NB. fixed in new crate - Ok(db.fetch_server_count(&self.id).await? <= 100) - } - - /// Sanitise and validate a username can be used - pub fn validate_username(username: String) -> Result { - // Copy the username for validation - let username_lowercase = username.to_lowercase(); - - // Block homoglyphs - if decancer::cure(&username_lowercase).into_str() != username_lowercase { - return Err(Error::InvalidUsername); - } - - // Ensure the username itself isn't blocked - const BLOCKED_USERNAMES: &[&str] = &["admin", "revolt"]; - - for username in BLOCKED_USERNAMES { - if username_lowercase == *username { - return Err(Error::InvalidUsername); - } - } - - // Ensure none of the following substrings show up in the username - const BLOCKED_SUBSTRINGS: &[&str] = &["```"]; - - for substr in BLOCKED_SUBSTRINGS { - if username_lowercase.contains(substr) { - return Err(Error::InvalidUsername); - } - } - - Ok(username) - } - - // Find a free discriminator for a given username - pub async fn find_discriminator( - db: &Database, - username: &str, - preferred: Option<(String, String)>, - ) -> Result { - let search_space: &HashSet = &DISCRIMINATOR_SEARCH_SPACE_QUARK; - let used_discriminators: HashSet = db - .fetch_discriminators_in_use(username) - .await? - .into_iter() - .collect(); - - let available_discriminators: Vec<&String> = - search_space.difference(&used_discriminators).collect(); - - if available_discriminators.is_empty() { - return Err(Error::UsernameTaken); - } - - if let Some((preferred, target_id)) = preferred { - if available_discriminators.contains(&&preferred) { - return Ok(preferred); - } else { - let rvdb: revolt_database::Database = db.clone().into(); - if rvdb - .has_ratelimited( - &target_id, - RatelimitEventType::DiscriminatorChange, - Duration::from_secs(60 * 60 * 24), - 1, - ) - .await - .map_err(Error::from_core)? - { - return Err(Error::DiscriminatorChangeRatelimited); - } - - // FIXME: don't access directly? - #[allow(clippy::disallowed_methods)] - rvdb.insert_ratelimit_event(&revolt_database::RatelimitEvent { - id: ulid::Ulid::new().to_string(), - target_id, - event_type: RatelimitEventType::DiscriminatorChange, - }) - .await - .map_err(Error::from_core)?; - } - } - - let mut rng = rand::thread_rng(); - Ok(available_discriminators - .choose(&mut rng) - .expect("we can assert this has an element") - .to_string()) - } - - /// Update a user's username - pub async fn update_username(&mut self, db: &Database, username: String) -> Result<()> { - let username = User::validate_username(username)?; - if self.username.to_lowercase() == username.to_lowercase() { - self.update( - db, - PartialUser { - username: Some(username), - ..Default::default() - }, - vec![], - ) - .await - } else { - self.update( - db, - PartialUser { - discriminator: Some( - User::find_discriminator( - db, - &username, - Some((self.discriminator.to_string(), self.id.clone())), - ) - .await?, - ), - username: Some(username), - ..Default::default() - }, - vec![], - ) - .await - } - } - - /// Apply a certain relationship between two users - pub async fn apply_relationship( - &self, - db: &Database, - target: &mut User, - local: RelationshipStatus, - remote: RelationshipStatus, - ) -> Result<()> { - if try_join!( - db.set_relationship(&self.id, &target.id, &local), - db.set_relationship(&target.id, &self.id, &remote) - ) - .is_err() - { - return Err(Error::DatabaseError { - operation: "update_one", - with: "user", - }); - } - - EventV1::UserRelationship { - id: target.id.clone(), - user: self.clone().with_relationship(target), - status: remote, - } - .private(target.id.clone()) - .await; - - EventV1::UserRelationship { - id: self.id.clone(), - user: target.clone().with_relationship(self), - status: local.clone(), - } - .private(self.id.clone()) - .await; - - target.relationship.replace(local); - Ok(()) - } - - /// Add another user as a friend - pub async fn add_friend(&self, db: &Database, target: &mut User) -> Result<()> { - match get_relationship(self, &target.id) { - RelationshipStatus::User => Err(Error::NoEffect), - RelationshipStatus::Friend => Err(Error::AlreadyFriends), - RelationshipStatus::Outgoing => Err(Error::AlreadySentRequest), - RelationshipStatus::Blocked => Err(Error::Blocked), - RelationshipStatus::BlockedOther => Err(Error::BlockedByOther), - RelationshipStatus::Incoming => { - self.apply_relationship( - db, - target, - RelationshipStatus::Friend, - RelationshipStatus::Friend, - ) - .await - } - RelationshipStatus::None => { - self.apply_relationship( - db, - target, - RelationshipStatus::Outgoing, - RelationshipStatus::Incoming, - ) - .await - } - } - } - - /// Remove another user as a friend - pub async fn remove_friend(&self, db: &Database, target: &mut User) -> Result<()> { - match get_relationship(self, &target.id) { - RelationshipStatus::Friend - | RelationshipStatus::Outgoing - | RelationshipStatus::Incoming => { - self.apply_relationship( - db, - target, - RelationshipStatus::None, - RelationshipStatus::None, - ) - .await - } - _ => Err(Error::NoEffect), - } - } - - /// Block another user - pub async fn block_user(&self, db: &Database, target: &mut User) -> Result<()> { - match get_relationship(self, &target.id) { - RelationshipStatus::User | RelationshipStatus::Blocked => Err(Error::NoEffect), - RelationshipStatus::BlockedOther => { - self.apply_relationship( - db, - target, - RelationshipStatus::Blocked, - RelationshipStatus::Blocked, - ) - .await - } - RelationshipStatus::None - | RelationshipStatus::Friend - | RelationshipStatus::Incoming - | RelationshipStatus::Outgoing => { - self.apply_relationship( - db, - target, - RelationshipStatus::Blocked, - RelationshipStatus::BlockedOther, - ) - .await - } - } - } - - /// Unblock another user - pub async fn unblock_user(&self, db: &Database, target: &mut User) -> Result<()> { - match get_relationship(self, &target.id) { - RelationshipStatus::Blocked => match get_relationship(target, &self.id) { - RelationshipStatus::Blocked => { - self.apply_relationship( - db, - target, - RelationshipStatus::BlockedOther, - RelationshipStatus::Blocked, - ) - .await - } - RelationshipStatus::BlockedOther => { - self.apply_relationship( - db, - target, - RelationshipStatus::None, - RelationshipStatus::None, - ) - .await - } - _ => Err(Error::InternalError), - }, - _ => Err(Error::NoEffect), - } - } - - /// Check whether this user has another user blocked - pub fn has_blocked(&self, user: &str) -> bool { - matches!( - get_relationship(self, user), - RelationshipStatus::Blocked | RelationshipStatus::BlockedOther - ) - } - - /// Mark as deleted - pub async fn mark_deleted(&mut self, db: &Database) -> Result<()> { - self.update( - db, - PartialUser { - username: Some(format!("Deleted User {}", self.id)), - flags: Some(2), - ..Default::default() - }, - vec![ - FieldsUser::Avatar, - FieldsUser::StatusText, - FieldsUser::StatusPresence, - FieldsUser::ProfileContent, - FieldsUser::ProfileBackground, - ], - ) - .await - } - - /// Find a user from a given token and hint - #[async_recursion] - pub async fn from_token(db: &Database, token: &str, hint: UserHint) -> Result { - match hint { - UserHint::Bot => { - let rvdb: revolt_database::Database = db.clone().into(); - db.fetch_user( - &rvdb - .fetch_bot_by_token(token) - .await - .map_err(|_| Error::InternalError)? - .id, - ) - .await - } - UserHint::User => db.fetch_user_by_token(token).await, - UserHint::Any => { - if let Ok(user) = User::from_token(db, token, UserHint::User).await { - Ok(user) - } else { - User::from_token(db, token, UserHint::Bot).await - } - } - } - } -} - -pub static DISCRIMINATOR_SEARCH_SPACE_QUARK: Lazy> = Lazy::new(|| { - let mut set = (2..9999) - .map(|v| format!("{:0>4}", v)) - .collect::>(); - - for discrim in [ - 123, 1234, 1111, 2222, 3333, 4444, 5555, 6666, 7777, 8888, 9999, - ] { - set.remove(&format!("{:0>4}", discrim)); - } - - set.into_iter().collect() -}); diff --git a/crates/quark/src/impl/generic/users/user_settings.rs b/crates/quark/src/impl/generic/users/user_settings.rs deleted file mode 100644 index 76e0e92b..00000000 --- a/crates/quark/src/impl/generic/users/user_settings.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::{events::client::EventV1, models::UserSettings, Database, Result}; - -#[async_trait] -pub trait UserSettingsImpl { - async fn set(self, db: &Database, user: &str) -> Result<()>; -} - -#[async_trait] -impl UserSettingsImpl for UserSettings { - async fn set(self, db: &Database, user: &str) -> Result<()> { - db.set_user_settings(user, &self).await?; - - EventV1::UserSettingsUpdate { - id: user.to_string(), - update: self, - } - .private(user.to_string()) - .await; - - Ok(()) - } -} diff --git a/crates/quark/src/impl/mod.rs b/crates/quark/src/impl/mod.rs deleted file mode 100644 index effaf7b8..00000000 --- a/crates/quark/src/impl/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -mod dummy; -mod generic; -mod mongo; - -#[cfg(feature = "rocket_impl")] -mod rocket; - -pub use self::generic::users::user_settings::UserSettingsImpl; -pub use dummy::DummyDb; -pub use mongo::MongoDb; diff --git a/crates/quark/src/impl/mongo/admin/stats.rs b/crates/quark/src/impl/mongo/admin/stats.rs deleted file mode 100644 index 8fa1e2b8..00000000 --- a/crates/quark/src/impl/mongo/admin/stats.rs +++ /dev/null @@ -1,90 +0,0 @@ -use std::collections::HashMap; - -use bson::{from_document, Document}; -use futures::StreamExt; - -use crate::{ - models::stats::{Index, Stats}, - AbstractStats, Error, Result, -}; - -use super::super::MongoDb; - -#[async_trait] -impl AbstractStats for MongoDb { - async fn generate_stats(&self) -> Result { - let mut indices = HashMap::new(); - let mut coll_stats = HashMap::new(); - - let collection_names = - self.db() - .list_collection_names(None) - .await - .map_err(|_| Error::DatabaseError { - operation: "list_collection_names", - with: "database", - })?; - - for collection in collection_names { - indices.insert( - collection.to_string(), - self.col::(&collection) - .aggregate( - vec![doc! { - "$indexStats": { } - }], - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "aggregate", - with: "col", - })? - .filter_map(|s| async { s.ok() }) - .collect::>() - .await - .into_iter() - .filter_map(|doc| from_document(doc).ok()) - .collect::>(), - ); - - coll_stats.insert( - collection.to_string(), - self.col::(&collection) - .aggregate( - vec![doc! { - "$collStats": { - "latencyStats": { - "histograms": true - }, - "storageStats": {}, - "count": {}, - "queryExecStats": {} - } - }], - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "aggregate", - with: "col", - })? - .filter_map(|s| async { s.ok() }) - .collect::>() - .await - .into_iter() - .filter_map(|doc| from_document(doc).ok()) - .next() - .ok_or(Error::DatabaseError { - operation: "next aggregation", - with: "col", - })?, - ); - } - - Ok(Stats { - indices, - coll_stats, - }) - } -} diff --git a/crates/quark/src/impl/mongo/channels/channel.rs b/crates/quark/src/impl/mongo/channels/channel.rs deleted file mode 100644 index 33aaa25f..00000000 --- a/crates/quark/src/impl/mongo/channels/channel.rs +++ /dev/null @@ -1,317 +0,0 @@ -use bson::{Bson, Document}; - -use crate::models::channel::{Channel, FieldsChannel, PartialChannel}; -use crate::r#impl::mongo::IntoDocumentPath; -use crate::{AbstractChannel, AbstractServer, Error, OverrideField, Result}; - -use super::super::MongoDb; - -static COL: &str = "channels"; - -impl MongoDb { - pub async fn delete_associated_channel_objects(&self, id: Bson) -> Result<()> { - // Delete all invites to these channels. - self.col::("channel_invites") - .delete_many( - doc! { - "channel": &id - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "delete_many", - with: "channel_invites", - })?; - - // Delete unread message objects on channels. - self.col::("channel_unreads") - .delete_many( - doc! { - "_id.channel": &id - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "delete_many", - with: "channel_unreads", - }) - .map(|_| ())?; - - // update many attachments with parent id - - // Delete all webhooks on this channel. - self.col::("webhooks") - .delete_many( - doc! { - "channel": &id - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "delete_many", - with: "webhooks", - }) - .map(|_| ()) - } -} - -#[async_trait] -impl AbstractChannel for MongoDb { - async fn fetch_channel(&self, id: &str) -> Result { - self.find_one_by_id(COL, id).await - } - - async fn fetch_channels<'a>(&self, ids: &'a [String]) -> Result> { - self.find( - COL, - doc! { - "_id": { - "$in": ids - } - }, - ) - .await - } - - async fn insert_channel(&self, channel: &Channel) -> Result<()> { - self.insert_one(COL, channel).await.map(|_| ()) - } - - async fn update_channel( - &self, - id: &str, - channel: &PartialChannel, - remove: Vec, - ) -> Result<()> { - self.update_one_by_id( - COL, - id, - channel, - remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(), - None, - ) - .await - .map(|_| ()) - } - - async fn delete_channel(&self, channel: &Channel) -> Result<()> { - let id = channel.id().to_string(); - let server_id = match channel { - Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => { - Some(server) - } - _ => None, - }; - - // Delete invites and unreads. - self.delete_associated_channel_objects(Bson::String(id.to_string())) - .await?; - - // Delete messages. - self.delete_bulk_messages(doc! { - "channel": &id - }) - .await?; - - // Remove from server object. - if let Some(server) = server_id { - let server = self.fetch_server(server).await?; - let mut update = doc! { - "$pull": { - "channels": &id - } - }; - - if let Some(sys) = &server.system_messages { - let mut unset = doc! {}; - - if let Some(cid) = &sys.user_joined { - if &id == cid { - unset.insert("system_messages.user_joined", 1_i32); - } - } - - if let Some(cid) = &sys.user_left { - if &id == cid { - unset.insert("system_messages.user_left", 1_i32); - } - } - - if let Some(cid) = &sys.user_kicked { - if &id == cid { - unset.insert("system_messages.user_kicked", 1_i32); - } - } - - if let Some(cid) = &sys.user_banned { - if &id == cid { - unset.insert("system_messages.user_banned", 1_i32); - } - } - - if !unset.is_empty() { - update.insert("$unset", unset); - } - } - - self.col::("servers") - .update_one( - doc! { - "_id": server.id - }, - update, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "servers", - })?; - } - - // Delete associated attachments - self.delete_many_attachments(doc! { - "object_id": &id - }) - .await?; - - // Delete the channel itself - self.delete_one_by_id(COL, &id).await.map(|_| ()) - } - - async fn find_direct_messages(&self, user_id: &str) -> Result> { - self.find( - COL, - doc! { - "$or": [ - { - "$or": [ - { - "channel_type": "DirectMessage" - }, - { - "channel_type": "Group" - } - ], - "recipients": user_id - }, - { - "channel_type": "SavedMessages", - "user": user_id - } - ] - }, - ) - .await - } - - async fn find_saved_messages_channel(&self, user_id: &str) -> Result { - self.find_one( - COL, - doc! { - "channel_type": "SavedMessages", - "user": user_id - }, - ) - .await - } - - async fn find_direct_message_channel(&self, user_a: &str, user_b: &str) -> Result { - self.find_one( - COL, - if user_a == user_b { - doc! { - "channel_type": "SavedMessages", - "user": user_a - } - } else { - doc! { - "channel_type": "DirectMessage", - "recipients": { - "$all": [ user_a, user_b ] - } - } - }, - ) - .await - } - - async fn add_user_to_group(&self, channel: &str, user: &str) -> Result<()> { - self.col::(COL) - .update_one( - doc! { - "_id": channel - }, - doc! { - "$push": { - "recipients": user - } - }, - None, - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "channel", - }) - } - - async fn remove_user_from_group(&self, channel: &str, user: &str) -> Result<()> { - self.col::(COL) - .update_one( - doc! { - "_id": channel - }, - doc! { - "$pull": { - "recipients": user - } - }, - None, - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "channel", - }) - } - - async fn set_channel_role_permission( - &self, - channel: &str, - role: &str, - permissions: OverrideField, - ) -> Result<()> { - self.col::(COL) - .update_one( - doc! { "_id": channel }, - doc! { - "$set": { - "role_permissions.".to_owned() + role: permissions - } - }, - None, - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "channel", - }) - } -} - -impl IntoDocumentPath for FieldsChannel { - fn as_path(&self) -> Option<&'static str> { - Some(match self { - FieldsChannel::DefaultPermissions => "default_permissions", - FieldsChannel::Description => "description", - FieldsChannel::Icon => "icon", - }) - } -} diff --git a/crates/quark/src/impl/mongo/channels/channel_invite.rs b/crates/quark/src/impl/mongo/channels/channel_invite.rs deleted file mode 100644 index ebe1ce13..00000000 --- a/crates/quark/src/impl/mongo/channels/channel_invite.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::models::Invite; -use crate::{AbstractChannelInvite, Result}; - -use super::super::MongoDb; - -static COL: &str = "channel_invites"; - -#[async_trait] -impl AbstractChannelInvite for MongoDb { - async fn fetch_invite(&self, code: &str) -> Result { - self.find_one_by_id(COL, code).await - } - - async fn insert_invite(&self, invite: &Invite) -> Result<()> { - self.insert_one(COL, invite).await.map(|_| ()) - } - - async fn delete_invite(&self, code: &str) -> Result<()> { - self.delete_one_by_id(COL, code).await.map(|_| ()) - } - - async fn fetch_invites_for_server(&self, server: &str) -> Result> { - self.find( - COL, - doc! { - "server": server - }, - ) - .await - } -} diff --git a/crates/quark/src/impl/mongo/channels/channel_unread.rs b/crates/quark/src/impl/mongo/channels/channel_unread.rs deleted file mode 100644 index 8442f3cc..00000000 --- a/crates/quark/src/impl/mongo/channels/channel_unread.rs +++ /dev/null @@ -1,120 +0,0 @@ -use bson::Document; -use mongodb::options::UpdateOptions; -use ulid::Ulid; - -use crate::models::channel_unread::ChannelUnread; -use crate::{AbstractChannelUnread, Error, Result}; - -use super::super::MongoDb; - -static COL: &str = "channel_unreads"; - -#[async_trait] -impl AbstractChannelUnread for MongoDb { - async fn acknowledge_message(&self, channel: &str, user: &str, message: &str) -> Result<()> { - self.col::(COL) - .update_one( - doc! { - "_id.channel": channel, - "_id.user": user, - }, - doc! { - "$unset": { - "mentions": 1_i32 - }, - "$set": { - "last_id": message - } - }, - UpdateOptions::builder().upsert(true).build(), - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "channel_unread", - }) - } - - async fn acknowledge_channels(&self, user: &str, channels: &[String]) -> Result<()> { - let current_time = Ulid::new().to_string(); - - self.col::(COL) - .delete_many( - doc! { - "_id.channel": { - "$in": channels - }, - "_id.user": user - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "delete_many", - with: "channel_unreads", - })?; - - self.col::(COL) - .insert_many( - channels - .iter() - .map(|channel| { - doc! { - "_id": { - "channel": channel, - "user": user - }, - "last_id": ¤t_time - } - }) - .collect::>(), - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "update_many", - with: "channel_unreads", - }) - .map(|_| ()) - } - - async fn add_mention_to_unread<'a>( - &self, - channel: &str, - user: &str, - ids: &[String], - ) -> Result<()> { - self.col::(COL) - .update_one( - doc! { - "_id.channel": channel, - "_id.user": user, - }, - doc! { - "$push": { - "mentions": { - "$each": ids - } - } - }, - UpdateOptions::builder().upsert(true).build(), - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "channel_unread", - }) - } - - async fn fetch_unreads(&self, user: &str) -> Result> { - self.find( - COL, - doc! { - "_id.user": user - }, - ) - .await - } -} diff --git a/crates/quark/src/impl/mongo/channels/message.rs b/crates/quark/src/impl/mongo/channels/message.rs deleted file mode 100644 index 79ae1916..00000000 --- a/crates/quark/src/impl/mongo/channels/message.rs +++ /dev/null @@ -1,343 +0,0 @@ -use bson::{to_bson, Document}; -use futures::try_join; -use mongodb::options::FindOptions; - -use crate::models::message::{ - AppendMessage, Message, MessageQuery, MessageSort, MessageTimePeriod, PartialMessage, -}; -use crate::r#impl::mongo::DocumentId; -use crate::{AbstractMessage, Error, Result}; - -use super::super::MongoDb; - -static COL: &str = "messages"; - -impl MongoDb { - pub async fn delete_bulk_messages(&self, projection: Document) -> Result<()> { - let mut for_attachments = projection.clone(); - for_attachments.insert( - "attachments", - doc! { - "$exists": 1_i32 - }, - ); - - // Check if there are any attachments we need to delete. - let message_ids_with_attachments = self - .find_with_options::<_, DocumentId>( - COL, - for_attachments, - FindOptions::builder() - .projection(doc! { "_id": 1_i32 }) - .build(), - ) - .await? - .into_iter() - .map(|x| x.id) - .collect::>(); - - // If we found any, mark them as deleted. - if !message_ids_with_attachments.is_empty() { - self.col::("attachments") - .update_many( - doc! { - "message_id": { - "$in": message_ids_with_attachments - } - }, - doc! { - "$set": { - "deleted": true - } - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "update_many", - with: "attachments", - })?; - } - - // And then delete said messages. - self.col::(COL) - .delete_many(projection, None) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "delete_many", - with: "messages", - }) - } -} - -#[async_trait] -impl AbstractMessage for MongoDb { - async fn fetch_message(&self, id: &str) -> Result { - self.find_one_by_id(COL, id).await - } - - async fn insert_message(&self, message: &Message) -> Result<()> { - self.insert_one(COL, message).await.map(|_| ()) - } - - async fn update_message(&self, id: &str, message: &PartialMessage) -> Result<()> { - self.update_one_by_id(COL, id, message, vec![], None) - .await - .map(|_| ()) - } - - async fn append_message(&self, id: &str, append: &AppendMessage) -> Result<()> { - let mut query = doc! {}; - - if let Some(embeds) = &append.embeds { - if !embeds.is_empty() { - query.insert( - "$push", - doc! { - "embeds": { - "$each": to_bson(embeds) - .map_err(|_| Error::DatabaseError { - operation: "to_bson", - with: "embeds" - })? - } - }, - ); - } - } - - if query.is_empty() { - return Ok(()); - } - - self.col::(COL) - .update_one( - doc! { - "_id": id - }, - query, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "message", - }) - .map(|_| ()) - } - - async fn delete_message(&self, id: &str) -> Result<()> { - self.delete_one_by_id(COL, id).await.map(|_| ()) - } - - async fn delete_messages(&self, channel: &str, ids: Vec) -> Result<()> { - self.delete_bulk_messages(doc! { - "channel": channel, - "_id": { - "$in": ids - } - }) - .await - } - - async fn fetch_messages(&self, query: MessageQuery) -> Result> { - let mut filter = doc! {}; - - // 1. Apply message filters - if let Some(channel) = query.filter.channel { - filter.insert("channel", channel); - } - - if let Some(author) = query.filter.author { - filter.insert("author", author); - } - - let is_search_query = if let Some(query) = query.filter.query { - filter.insert( - "$text", - doc! { - "$search": query - }, - ); - - true - } else { - false - }; - - // 2. Find query limit - let limit = query.limit.unwrap_or(50); - - // 3. Apply message time period - match query.time_period { - MessageTimePeriod::Relative { nearby } => { - // 3.1. Prepare filters - let mut older_message_filter = filter.clone(); - let mut newer_message_filter = filter; - - older_message_filter.insert( - "_id", - doc! { - "$lt": &nearby - }, - ); - - newer_message_filter.insert( - "_id", - doc! { - "$gte": &nearby - }, - ); - - // 3.2. Execute in both directions - let (a, b) = try_join!( - self.find_with_options::<_, Message>( - COL, - newer_message_filter, - FindOptions::builder() - .limit(limit / 2 + 1) - .sort(doc! { - "_id": 1_i32 - }) - .build(), - ), - self.find_with_options::<_, Message>( - COL, - older_message_filter, - FindOptions::builder() - .limit(limit / 2) - .sort(doc! { - "_id": -1_i32 - }) - .build(), - ) - )?; - - Ok([a, b].concat()) - } - MessageTimePeriod::Absolute { - before, - after, - sort, - } => { - // 3.1. Apply message ID filter - if let Some(doc) = match (before, after) { - (Some(before), Some(after)) => Some(doc! { - "$lt": before, - "$gt": after - }), - (Some(before), _) => Some(doc! { - "$lt": before - }), - (_, Some(after)) => Some(doc! { - "$gt": after - }), - _ => None, - } { - filter.insert("_id", doc); - } - - // 3.2. Execute with given message sort - self.find_with_options( - COL, - filter, - FindOptions::builder() - .limit(limit) - .sort(match sort.unwrap_or(MessageSort::Latest) { - // Sort by relevance, fallback to latest - MessageSort::Relevance => { - if is_search_query { - doc! { - "score": { - "$meta": "textScore" - } - } - } else { - doc! { - "_id": -1_i32 - } - } - } - // Sort by latest first - MessageSort::Latest => doc! { - "_id": -1_i32 - }, - // Sort by oldest first - MessageSort::Oldest => doc! { - "_id": 1_i32 - }, - }) - .build(), - ) - .await - } - } - } - - /// Add a new reaction to a message - async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> { - self.col::(COL) - .update_one( - doc! { - "_id": id - }, - doc! { - "$addToSet": { - format!("reactions.{emoji}"): user - } - }, - None, - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "message", - }) - } - - /// Remove a reaction from a message - async fn remove_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> { - self.col::(COL) - .update_one( - doc! { - "_id": id - }, - doc! { - "$pull": { - format!("reactions.{emoji}"): user - } - }, - None, - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "message", - }) - } - - /// Remove reaction from a message - async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()> { - self.col::(COL) - .update_one( - doc! { - "_id": id - }, - doc! { - "$unset": { - format!("reactions.{emoji}"): 1 - } - }, - None, - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "message", - }) - } -} diff --git a/crates/quark/src/impl/mongo/media/attachment.rs b/crates/quark/src/impl/mongo/media/attachment.rs deleted file mode 100644 index 204ca712..00000000 --- a/crates/quark/src/impl/mongo/media/attachment.rs +++ /dev/null @@ -1,148 +0,0 @@ -use bson::Document; - -use crate::models::attachment::File; -use crate::{AbstractAttachment, Error, Result}; - -use super::super::MongoDb; - -static COL: &str = "attachments"; - -impl MongoDb { - pub async fn delete_many_attachments(&self, projection: Document) -> Result<()> { - self.col::(COL) - .update_many( - projection, - doc! { - "$set": { - "deleted": true - } - }, - None, - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_many", - with: "attachment", - }) - } -} - -#[async_trait] -impl AbstractAttachment for MongoDb { - async fn find_and_use_attachment( - &self, - attachment_id: &str, - tag: &str, - parent_type: &str, - parent_id: &str, - ) -> Result { - let key = format!("{parent_type}_id"); - match self - .find_one::( - COL, - doc! { - "_id": attachment_id, - "tag": tag, - &key: { - "$exists": false - } - }, - ) - .await - { - Ok(file) => { - self.col::(COL) - .update_one( - doc! { - "_id": &file.id - }, - doc! { - "$set": { - key: parent_id - } - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "attachment", - })?; - - Ok(file) - } - Err(Error::NotFound) => Err(Error::UnknownAttachment), - Err(error) => Err(error), - } - } - - async fn insert_attachment(&self, attachment: &File) -> Result<()> { - self.insert_one(COL, attachment).await.map(|_| ()) - } - - async fn mark_attachment_as_reported(&self, id: &str) -> Result<()> { - self.col::(COL) - .update_one( - doc! { - "_id": id - }, - doc! { - "$set": { - "reported": true - } - }, - None, - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "attachment", - }) - } - - async fn mark_attachment_as_deleted(&self, id: &str) -> Result<()> { - self.col::(COL) - .update_one( - doc! { - "_id": id - }, - doc! { - "$set": { - "deleted": true - } - }, - None, - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "attachment", - }) - } - - async fn mark_attachments_as_deleted(&self, ids: &[String]) -> Result<()> { - self.col::(COL) - .update_many( - doc! { - "_id": { - "$in": ids - } - }, - doc! { - "$set": { - "deleted": true - } - }, - None, - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update", - with: "attachments", - }) - } -} diff --git a/crates/quark/src/impl/mongo/media/emoji.rs b/crates/quark/src/impl/mongo/media/emoji.rs deleted file mode 100644 index 848c006c..00000000 --- a/crates/quark/src/impl/mongo/media/emoji.rs +++ /dev/null @@ -1,69 +0,0 @@ -use bson::Document; - -use crate::models::Emoji; -use crate::{AbstractEmoji, Error, Result}; - -use super::super::MongoDb; - -static COL: &str = "emojis"; - -#[async_trait] -impl AbstractEmoji for MongoDb { - /// Fetch an emoji by its id - async fn fetch_emoji(&self, id: &str) -> Result { - self.find_one_by_id(COL, id).await - } - - /// Fetch emoji by their ids - async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result> { - self.find( - COL, - doc! { - "parent.id": parent_id - }, - ) - .await - } - - /// Fetch emoji by their parent ids - async fn fetch_emoji_by_parent_ids(&self, parent_ids: &[String]) -> Result> { - self.find( - COL, - doc! { - "parent.id": { - "$in": parent_ids - } - }, - ) - .await - } - - /// Insert emoji into database. - async fn insert_emoji(&self, emoji: &Emoji) -> Result<()> { - self.insert_one(COL, emoji).await.map(|_| ()) - } - - /// Delete an emoji by its id - async fn detach_emoji(&self, emoji: &Emoji) -> Result<()> { - self.col::(COL) - .update_one( - doc! { - "_id": &emoji.id - }, - doc! { - "$set": { - "parent": { - "type": "Detached" - } - } - }, - None, - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "emojis", - }) - } -} diff --git a/crates/quark/src/impl/mongo/mod.rs b/crates/quark/src/impl/mongo/mod.rs deleted file mode 100644 index b04cd9f2..00000000 --- a/crates/quark/src/impl/mongo/mod.rs +++ /dev/null @@ -1,263 +0,0 @@ -use std::ops::Deref; - -use bson::{to_document, Document}; -use futures::StreamExt; -use mongodb::{ - options::{FindOneOptions, FindOptions}, - results::{DeleteResult, InsertOneResult, UpdateResult}, -}; -use rocket::serde::DeserializeOwned; -use serde::{Deserialize, Serialize}; - -use crate::{util::manipulation::prefix_keys, AbstractDatabase, Error, Result}; - -pub mod admin { - pub mod stats; -} - -pub mod media { - pub mod attachment; - pub mod emoji; -} - -pub mod channels { - pub mod channel; - pub mod channel_invite; - pub mod channel_unread; - pub mod message; -} - -pub mod servers { - pub mod server; - pub mod server_ban; - pub mod server_member; -} - -pub mod users { - pub mod user; - pub mod user_settings; -} - -pub mod safety { - pub mod report; - pub mod snapshot; -} - -#[derive(Debug, Clone)] -pub struct MongoDb(pub mongodb::Client); - -impl AbstractDatabase for MongoDb {} - -impl Deref for MongoDb { - type Target = mongodb::Client; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl MongoDb { - pub fn db(&self) -> mongodb::Database { - self.database("revolt") - } - - pub fn col(&self, collection: &str) -> mongodb::Collection { - self.db().collection(collection) - } - - async fn insert_one( - &self, - collection: &'static str, - document: T, - ) -> Result { - self.col::(collection) - .insert_one(document, None) - .await - .map_err(|_| Error::DatabaseError { - operation: "insert_one", - with: collection, - }) - } - - async fn find_with_options( - &self, - collection: &'static str, - projection: Document, - options: O, - ) -> Result> - where - O: Into>, - { - let result = self.col::(collection).find(projection, options).await; - Ok(if cfg!(debug_assertions) { - result.unwrap() - } else { - result.map_err(|_| Error::DatabaseError { - operation: "find", - with: collection, - })? - } - .filter_map(|s| async { - if cfg!(debug_assertions) { - // Hard fail on invalid documents - Some(s.unwrap()) - } else { - s.ok() - } - }) - .collect::>() - .await) - } - - async fn find( - &self, - collection: &'static str, - projection: Document, - ) -> Result> { - self.find_with_options(collection, projection, None).await - } - - async fn find_one_with_options( - &self, - collection: &'static str, - projection: Document, - options: O, - ) -> Result - where - O: Into>, - { - self.col::(collection) - .find_one(projection, options) - .await - .map_err(|_| Error::DatabaseError { - operation: "find_one", - with: collection, - })? - .ok_or(Error::NotFound) - } - - async fn find_one( - &self, - collection: &'static str, - projection: Document, - ) -> Result { - self.find_one_with_options(collection, projection, None) - .await - } - - async fn find_one_by_id( - &self, - collection: &'static str, - id: &str, - ) -> Result { - self.find_one( - collection, - doc! { - "_id": id - }, - ) - .await - } - - async fn update_one( - &self, - collection: &'static str, - projection: Document, - partial: T, - remove: Vec<&dyn IntoDocumentPath>, - prefix: P, - ) -> Result - where - P: Into>, - { - let prefix = prefix.into(); - - let mut unset = doc! {}; - for field in remove { - if let Some(path) = field.as_path() { - if let Some(prefix) = &prefix { - unset.insert(prefix.to_owned() + path, 1_i32); - } else { - unset.insert(path, 1_i32); - } - } - } - - let query = doc! { - "$unset": unset, - "$set": if let Some(prefix) = &prefix { - to_document(&prefix_keys(&partial, prefix)) - } else { - to_document(&partial) - }.map_err(|_| Error::DatabaseError { - operation: "to_document", - with: collection - })? - }; - - self.col::(collection) - .update_one(projection, query, None) - .await - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: collection, - }) - } - - async fn update_one_by_id( - &self, - collection: &'static str, - id: &str, - partial: T, - remove: Vec<&dyn IntoDocumentPath>, - prefix: P, - ) -> Result - where - P: Into>, - { - self.update_one( - collection, - doc! { - "_id": id - }, - partial, - remove, - prefix, - ) - .await - } - - async fn delete_one( - &self, - collection: &'static str, - projection: Document, - ) -> Result { - self.col::(collection) - .delete_one(projection, None) - .await - .map_err(|_| Error::DatabaseError { - operation: "delete_one", - with: collection, - }) - } - - async fn delete_one_by_id(&self, collection: &'static str, id: &str) -> Result { - self.delete_one( - collection, - doc! { - "_id": id - }, - ) - .await - } -} - -#[derive(Deserialize)] -pub struct DocumentId { - #[serde(rename = "_id")] - pub id: String, -} - -pub trait IntoDocumentPath: Send + Sync { - fn as_path(&self) -> Option<&'static str>; -} diff --git a/crates/quark/src/impl/mongo/safety/report.rs b/crates/quark/src/impl/mongo/safety/report.rs deleted file mode 100644 index 69888e88..00000000 --- a/crates/quark/src/impl/mongo/safety/report.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::models::report::PartialReport; -use crate::models::Report; -use crate::{AbstractReport, Result}; - -use super::super::MongoDb; - -static COL: &str = "safety_reports"; - -#[async_trait] -impl AbstractReport for MongoDb { - async fn insert_report(&self, report: &Report) -> Result<()> { - self.insert_one(COL, report).await.map(|_| ()) - } - - async fn update_report(&self, id: &str, report: &PartialReport) -> Result<()> { - self.update_one_by_id(COL, id, report, vec![], None) - .await - .map(|_| ()) - } - - async fn fetch_report(&self, report_id: &str) -> Result { - self.find_one_by_id(COL, report_id).await - } - - async fn fetch_reports(&self) -> Result> { - self.find(COL, doc! {}).await - } -} diff --git a/crates/quark/src/impl/mongo/safety/snapshot.rs b/crates/quark/src/impl/mongo/safety/snapshot.rs deleted file mode 100644 index 4b1a934c..00000000 --- a/crates/quark/src/impl/mongo/safety/snapshot.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::models::Snapshot; -use crate::{AbstractSnapshot, Result}; - -use super::super::MongoDb; - -static COL: &str = "safety_snapshots"; - -#[async_trait] -impl AbstractSnapshot for MongoDb { - async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()> { - self.insert_one(COL, snapshot).await.map(|_| ()) - } - - async fn fetch_snapshots(&self, report_id: &str) -> Result> { - self.find( - COL, - doc! { - "report_id": report_id - }, - ) - .await - } -} diff --git a/crates/quark/src/impl/mongo/servers/server.rs b/crates/quark/src/impl/mongo/servers/server.rs deleted file mode 100644 index babf897f..00000000 --- a/crates/quark/src/impl/mongo/servers/server.rs +++ /dev/null @@ -1,242 +0,0 @@ -use bson::{to_document, Bson, Document}; - -use crate::models::server::{FieldsRole, FieldsServer, PartialRole, PartialServer, Role, Server}; -use crate::r#impl::mongo::IntoDocumentPath; -use crate::{AbstractServer, Database, Error, Result}; - -use super::super::MongoDb; - -static COL: &str = "servers"; - -impl MongoDb { - pub async fn delete_associated_server_objects(&self, server: &Server) -> Result<()> { - // Check if there are any attachments we need to delete. - self.delete_bulk_messages(doc! { - "channel": { - "$in": &server.channels - } - }) - .await?; - - // Delete all emoji. - self.col::("emojis") - .delete_many( - doc! { - "parent.id": &server.id - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "delete_many", - with: "emojis", - })?; - - // Delete all channels. - self.col::("channels") - .delete_many( - doc! { - "server": &server.id - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "delete_many", - with: "channels", - })?; - - // Delete any associated objects, e.g. unreads and invites. - self.delete_associated_channel_objects(Bson::Document(doc! { "$in": &server.channels })) - .await?; - - // Delete members and bans. - for with in &["server_members", "server_bans"] { - self.col::(with) - .delete_many( - doc! { - "_id.server": &server.id - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "delete_many", - with, - })?; - } - - // Update many attachments with parent id. - self.delete_many_attachments(doc! { - "object_id": &server.id - }) - .await?; - - Ok(()) - } -} - -#[async_trait] -impl AbstractServer for MongoDb { - async fn fetch_server(&self, id: &str) -> Result { - self.find_one_by_id(COL, id).await - } - - async fn fetch_servers<'a>(&self, ids: &'a [String]) -> Result> { - self.find( - COL, - doc! { - "_id": { - "$in": ids - } - }, - ) - .await - } - - async fn insert_server(&self, server: &Server) -> Result<()> { - self.insert_one(COL, server).await.map(|_| ()) - } - - async fn update_server( - &self, - id: &str, - server: &PartialServer, - remove: Vec, - ) -> Result<()> { - self.update_one_by_id( - COL, - id, - server, - remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(), - None, - ) - .await - .map(|_| ()) - } - - async fn delete_server(&self, server: &Server) -> Result<()> { - self.delete_associated_server_objects(server).await?; - self.delete_one_by_id(COL, &server.id).await.map(|_| ()) - } - - async fn insert_role(&self, server_id: &str, role_id: &str, role: &Role) -> Result<()> { - self.col::(COL) - .update_one( - doc! { - "_id": server_id - }, - doc! { - "$set": { - "roles.".to_owned() + role_id: to_document(role) - .map_err(|_| Error::DatabaseError { - operation: "to_document", - with: "role" - })? - } - }, - None, - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "server", - }) - } - - async fn update_role( - &self, - server_id: &str, - role_id: &str, - role: &PartialRole, - remove: Vec, - ) -> Result<()> { - self.update_one_by_id( - COL, - server_id, - role, - remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(), - "roles.".to_owned() + role_id + ".", - ) - .await - .map(|_| ()) - } - - async fn delete_role(&self, server_id: &str, role_id: &str) -> Result<()> { - self.col::("server_members") - .update_many( - doc! { - "_id.server": server_id - }, - doc! { - "$pull": { - "roles": &role_id - } - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "update_many", - with: "server_members", - })?; - - self.col::("channels") - .update_one( - doc! { - "server": server_id - }, - doc! { - "$unset": { - "role_permissions.".to_owned() + role_id: 1_i32 - } - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "channels", - })?; - - self.col::("servers") - .update_one( - doc! { - "_id": server_id - }, - doc! { - "$unset": { - "roles.".to_owned() + role_id: 1_i32 - } - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "servers", - }) - .map(|_| ()) - } -} - -impl IntoDocumentPath for FieldsServer { - fn as_path(&self) -> Option<&'static str> { - Some(match self { - FieldsServer::Banner => "banner", - FieldsServer::Categories => "categories", - FieldsServer::Description => "description", - FieldsServer::Icon => "icon", - FieldsServer::SystemMessages => "system_messages", - }) - } -} - -impl IntoDocumentPath for FieldsRole { - fn as_path(&self) -> Option<&'static str> { - Some(match self { - FieldsRole::Colour => "colour", - }) - } -} diff --git a/crates/quark/src/impl/mongo/servers/server_ban.rs b/crates/quark/src/impl/mongo/servers/server_ban.rs deleted file mode 100644 index 2b343034..00000000 --- a/crates/quark/src/impl/mongo/servers/server_ban.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::models::server_member::MemberCompositeKey; -use crate::models::ServerBan; -use crate::{AbstractServerBan, Result}; - -use super::super::MongoDb; - -static COL: &str = "server_bans"; - -#[async_trait] -impl AbstractServerBan for MongoDb { - async fn fetch_ban(&self, server: &str, user: &str) -> Result { - self.find_one( - COL, - doc! { - "_id.server": server, - "_id.user": user - }, - ) - .await - } - - async fn fetch_bans(&self, server: &str) -> Result> { - self.find( - COL, - doc! { - "_id.server": server - }, - ) - .await - } - - async fn insert_ban(&self, ban: &ServerBan) -> Result<()> { - self.insert_one(COL, ban).await.map(|_| ()) - } - - async fn delete_ban(&self, id: &MemberCompositeKey) -> Result<()> { - self.delete_one( - COL, - doc! { - "_id.server": &id.server, - "_id.user": &id.user - }, - ) - .await - .map(|_| ()) - } -} diff --git a/crates/quark/src/impl/mongo/servers/server_member.rs b/crates/quark/src/impl/mongo/servers/server_member.rs deleted file mode 100644 index 0edb0046..00000000 --- a/crates/quark/src/impl/mongo/servers/server_member.rs +++ /dev/null @@ -1,146 +0,0 @@ -use bson::Document; - -use super::super::MongoDb; -use crate::models::server_member::MemberWithRoles; -use crate::models::server_member::{FieldsMember, Member, MemberCompositeKey, PartialMember}; -use crate::r#impl::mongo::IntoDocumentPath; -use crate::{AbstractServer, AbstractServerMember, Error, Result}; - -static COL: &str = "server_members"; - -#[async_trait] -impl AbstractServerMember for MongoDb { - async fn fetch_member(&self, server: &str, user: &str) -> Result { - self.find_one( - COL, - doc! { - "_id.server": server, - "_id.user": user - }, - ) - .await - } - - async fn fetch_member_with_roles(&self, server: &str, user: &str) -> Result { - let member = self.fetch_member(server, user).await?; - let server_roles = self.fetch_server(server).await?.roles; - let roles = member - .roles - .iter() - .filter_map(|id| server_roles.get(id).map(|r| (id.clone(), r.clone()))) - .collect(); - Ok(MemberWithRoles { member, roles }) - } - - async fn insert_member(&self, member: &Member) -> Result<()> { - self.insert_one(COL, member).await.map(|_| ()) - } - - async fn update_member( - &self, - id: &MemberCompositeKey, - member: &PartialMember, - remove: Vec, - ) -> Result<()> { - self.update_one( - COL, - doc! { - "_id.server": &id.server, - "_id.user": &id.user - }, - member, - remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(), - None, - ) - .await - .map(|_| ()) - } - - async fn delete_member(&self, id: &MemberCompositeKey) -> Result<()> { - self.delete_one( - COL, - doc! { - "_id.server": &id.server, - "_id.user": &id.user - }, - ) - .await - .map(|_| ()) - } - - async fn fetch_all_members<'a>(&self, server: &str) -> Result> { - self.find( - COL, - doc! { - "_id.server": server - }, - ) - .await - } - - async fn fetch_all_memberships<'a>(&self, user: &str) -> Result> { - self.find( - COL, - doc! { - "_id.user": user - }, - ) - .await - } - - async fn fetch_members<'a>(&self, server: &str, ids: &'a [String]) -> Result> { - self.find( - COL, - doc! { - "_id.server": server, - "_id.user": { - "$in": ids - } - }, - ) - .await - } - - async fn fetch_member_count(&self, server: &str) -> Result { - self.col::(COL) - .count_documents( - doc! { - "_id.server": server - }, - None, - ) - .await - .map(|c| c as usize) - .map_err(|_| Error::DatabaseError { - operation: "count_documents", - with: "server_members", - }) - } - - async fn fetch_server_count(&self, user: &str) -> Result { - self.col::(COL) - .count_documents( - doc! { - "_id.user": user - }, - None, - ) - .await - .map(|c| c as usize) - .map_err(|_| Error::DatabaseError { - operation: "count_documents", - with: "server_members", - }) - } -} - -impl IntoDocumentPath for FieldsMember { - fn as_path(&self) -> Option<&'static str> { - Some(match self { - FieldsMember::Avatar => "avatar", - FieldsMember::Nickname => "nickname", - FieldsMember::Roles => "roles", - FieldsMember::Timeout => "timeout", - }) - } -} diff --git a/crates/quark/src/impl/mongo/users/user.rs b/crates/quark/src/impl/mongo/users/user.rs deleted file mode 100644 index bd1bb329..00000000 --- a/crates/quark/src/impl/mongo/users/user.rs +++ /dev/null @@ -1,343 +0,0 @@ -use bson::Document; -use futures::StreamExt; -use mongodb::options::{Collation, CollationStrength, FindOneOptions, FindOptions}; -use once_cell::sync::Lazy; - -use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus, User}; -use crate::r#impl::mongo::IntoDocumentPath; -use crate::{AbstractUser, Error, Result}; - -use super::super::MongoDb; - -static FIND_USERNAME_OPTIONS: Lazy = Lazy::new(|| { - FindOneOptions::builder() - .collation( - Collation::builder() - .locale("en") - .strength(CollationStrength::Secondary) - .build(), - ) - .build() -}); - -static COL: &str = "users"; - -#[async_trait] -impl AbstractUser for MongoDb { - async fn fetch_user(&self, id: &str) -> Result { - self.find_one_by_id(COL, id).await - } - - async fn fetch_user_by_username(&self, username: &str, discriminator: &str) -> Result { - self.find_one_with_options( - COL, - doc! { - "username": username, - "discriminator": discriminator - }, - FIND_USERNAME_OPTIONS.clone(), - ) - .await - } - - async fn fetch_user_by_token(&self, token: &str) -> Result { - let session = self - .col::("sessions") - .find_one( - doc! { - "token": token - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "find_one", - with: "sessions", - })? - .ok_or(Error::InvalidSession)?; - - self.fetch_user(session.get_str("user_id").unwrap()).await - } - - async fn insert_user(&self, user: &User) -> Result<()> { - self.insert_one(COL, user).await.map(|_| ()) - } - - async fn update_user( - &self, - id: &str, - user: &PartialUser, - remove: Vec, - ) -> Result<()> { - self.update_one_by_id( - COL, - id, - user, - remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(), - None, - ) - .await - .map(|_| ()) - } - - async fn delete_user(&self, id: &str) -> Result<()> { - self.delete_one_by_id(COL, id).await.map(|_| ()) - } - - async fn fetch_users<'a>(&self, ids: &'a [String]) -> Result> { - let mut cursor = self - .col::(COL) - .find( - doc! { - "_id": { - "$in": ids - } - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "find", - with: "users", - })?; - - let mut users = vec![]; - while let Some(Ok(user)) = cursor.next().await { - users.push(user); - } - - Ok(users) - } - - async fn fetch_discriminators_in_use(&self, username: &str) -> Result> { - Ok(self - .col::(COL) - .find( - doc! { - "username": username - }, - FindOptions::builder() - .collation( - Collation::builder() - .locale("en") - .strength(CollationStrength::Secondary) - .build(), - ) - .projection(doc! { "_id": 0, "discriminator": 1 }) - .build(), - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "find", - with: "users", - })? - .filter_map(|s| async { s.ok() }) - .collect::>() - .await - .into_iter() - .filter_map(|x| x.get_str("discriminator").ok().map(|x| x.to_string())) - .collect::>()) - } - - async fn fetch_mutual_user_ids(&self, user_a: &str, user_b: &str) -> Result> { - Ok(self - .col::(COL) - .find( - doc! { - "$and": [ - { "relations": { "$elemMatch": { "_id": &user_a, "status": "Friend" } } }, - { "relations": { "$elemMatch": { "_id": &user_b, "status": "Friend" } } } - ] - }, - FindOptions::builder().projection(doc! { "_id": 1 }).build(), - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "find", - with: "users", - })? - .filter_map(|s| async { s.ok() }) - .collect::>() - .await - .into_iter() - .filter_map(|x| x.get_str("_id").ok().map(|x| x.to_string())) - .collect::>()) - } - - async fn fetch_mutual_channel_ids(&self, user_a: &str, user_b: &str) -> Result> { - Ok(self - .col::("channels") - .find( - doc! { - "channel_type": { - "$in": ["Group", "DirectMessage"] - }, - "recipients": { - "$all": [ user_a, user_b ] - } - }, - FindOptions::builder().projection(doc! { "_id": 1 }).build(), - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "find", - with: "channels", - })? - .filter_map(|s| async { s.ok() }) - .collect::>() - .await - .into_iter() - .filter_map(|x| x.get_str("_id").ok().map(|x| x.to_string())) - .collect::>()) - } - - async fn fetch_mutual_server_ids(&self, user_a: &str, user_b: &str) -> Result> { - Ok(self - .col::("server_members") - .aggregate( - vec![ - doc! { - "$match": { - "_id.user": user_a - } - }, - doc! { - "$lookup": { - "from": "server_members", - "as": "members", - "let": { - "server": "$_id.server" - }, - "pipeline": [ - { - "$match": { - "$expr": { - "$and": [ - { "$eq": [ "$_id.user", user_b ] }, - { "$eq": [ "$_id.server", "$$server" ] } - ] - } - } - } - ] - } - }, - doc! { - "$match": { - "members": { - "$size": 1_i32 - } - } - }, - doc! { - "$project": { - "_id": "$_id.server" - } - }, - ], - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "aggregate", - with: "server_members", - })? - .filter_map(|s| async { s.ok() }) - .collect::>() - .await - .into_iter() - .filter_map(|x| x.get_str("_id").ok().map(|i| i.to_string())) - .collect::>()) - } - - async fn set_relationship( - &self, - user_id: &str, - target_id: &str, - relationship: &RelationshipStatus, - ) -> Result<()> { - if let RelationshipStatus::None = relationship { - return self.pull_relationship(user_id, target_id).await; - } - - self.col::(COL) - .update_one( - doc! { - "_id": user_id - }, - vec![doc! { - "$set": { - "relations": { - "$concatArrays": [ - { - "$ifNull": [ - { - "$filter": { - "input": "$relations", - "cond": { - "$ne": [ - "$$this._id", - target_id - ] - } - } - }, - [] - ] - }, - [ - { - "_id": target_id, - "status": format!("{relationship:?}") - } - ] - ] - } - } - }], - None, - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "user", - }) - } - - async fn pull_relationship(&self, user_id: &str, target_id: &str) -> Result<()> { - self.col::(COL) - .update_one( - doc! { - "_id": user_id - }, - doc! { - "$pull": { - "relations": { - "_id": target_id - } - } - }, - None, - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "user", - }) - } -} - -impl IntoDocumentPath for FieldsUser { - fn as_path(&self) -> Option<&'static str> { - Some(match self { - FieldsUser::Avatar => "avatar", - FieldsUser::ProfileBackground => "profile.background", - FieldsUser::ProfileContent => "profile.content", - FieldsUser::StatusPresence => "status.presence", - FieldsUser::StatusText => "status.text", - FieldsUser::DisplayName => "display_name", - }) - } -} diff --git a/crates/quark/src/impl/mongo/users/user_settings.rs b/crates/quark/src/impl/mongo/users/user_settings.rs deleted file mode 100644 index f73bf5d6..00000000 --- a/crates/quark/src/impl/mongo/users/user_settings.rs +++ /dev/null @@ -1,62 +0,0 @@ -use bson::{to_bson, Document}; -use mongodb::options::{FindOneOptions, UpdateOptions}; - -use crate::models::UserSettings; -use crate::{AbstractUserSettings, Error, Result}; - -use super::super::MongoDb; - -static COL: &str = "user_settings"; - -#[async_trait] -impl AbstractUserSettings for MongoDb { - async fn fetch_user_settings(&'_ self, id: &str, filter: &'_ [String]) -> Result { - let mut projection = doc! { - "_id": 0, - }; - - for key in filter { - projection.insert(key, 1); - } - - self.find_one_with_options( - COL, - doc! { - "_id": id - }, - FindOneOptions::builder().projection(projection).build(), - ) - .await - } - - async fn set_user_settings(&self, id: &str, settings: &UserSettings) -> Result<()> { - let mut set = doc! {}; - for (key, data) in settings { - set.insert( - key, - vec![to_bson(&data.0).unwrap(), to_bson(&data.1).unwrap()], - ); - } - - self.col::(COL) - .update_one( - doc! { - "_id": id - }, - doc! { - "$set": set - }, - UpdateOptions::builder().upsert(true).build(), - ) - .await - .map(|_| ()) - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "user_settings", - }) - } - - async fn delete_user_settings(&self, id: &str) -> Result<()> { - self.delete_one_by_id(COL, id).await.map(|_| ()) - } -} diff --git a/crates/quark/src/impl/rocket.rs b/crates/quark/src/impl/rocket.rs deleted file mode 100644 index 22c1466c..00000000 --- a/crates/quark/src/impl/rocket.rs +++ /dev/null @@ -1,73 +0,0 @@ -use authifier::models::Session; -use revolt_okapi::openapi3::{SecurityScheme, SecuritySchemeData}; -use revolt_rocket_okapi::gen::OpenApiGenerator; -use revolt_rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput}; - -use rocket::http::Status; -use rocket::request::{self, FromRequest, Outcome, Request}; - -use crate::models::user::UserHint; -use crate::models::User; -use crate::Database; - -#[rocket::async_trait] -impl<'r> FromRequest<'r> for User { - type Error = authifier::Error; - - async fn from_request(request: &'r Request<'_>) -> request::Outcome { - let user: &Option = request - .local_cache_async(async { - let db = request.rocket().state::().expect("`Database`"); - - let header_bot_token = request - .headers() - .get("x-bot-token") - .next() - .map(|x| x.to_string()); - - if let Some(bot_token) = header_bot_token { - if let Ok(user) = User::from_token(db, &bot_token, UserHint::Bot).await { - return Some(user); - } - } else if let Outcome::Success(session) = request.guard::().await { - // This uses a guard so can't really easily be refactored into from_token at this stage. - if let Ok(user) = db.fetch_user(&session.user_id).await { - return Some(user); - } - } - - None - }) - .await; - - if let Some(user) = user { - Outcome::Success(user.clone()) - } else { - Outcome::Failure((Status::Unauthorized, authifier::Error::InvalidSession)) - } - } -} - -impl<'r> OpenApiFromRequest<'r> for User { - fn from_request_input( - _gen: &mut OpenApiGenerator, - _name: String, - _required: bool, - ) -> revolt_rocket_okapi::Result { - let mut requirements = schemars::Map::new(); - requirements.insert("Session Token".to_owned(), vec![]); - - Ok(RequestHeaderInput::Security( - "Session Token".to_owned(), - SecurityScheme { - data: SecuritySchemeData::ApiKey { - name: "x-session-token".to_owned(), - location: "header".to_owned(), - }, - description: Some("Used to authenticate as a user.".to_owned()), - extensions: schemars::Map::new(), - }, - requirements, - )) - } -} diff --git a/crates/quark/src/lib.rs b/crates/quark/src/lib.rs deleted file mode 100644 index b95c6583..00000000 --- a/crates/quark/src/lib.rs +++ /dev/null @@ -1,58 +0,0 @@ -#[macro_use] -extern crate async_trait; -#[macro_use] -extern crate schemars; -#[macro_use] -extern crate async_recursion; -#[macro_use] -extern crate log; -#[macro_use] -extern crate impl_ops; -#[macro_use] -extern crate revolt_optional_struct; -#[macro_use] -extern crate bitfield; -#[macro_use] -extern crate bson; - -pub use authifier; -pub use iso8601_timestamp::Timestamp; -pub use redis_kiss; - -pub mod events; -pub mod r#impl; -pub mod models; -pub mod tasks; -pub mod types; -pub mod util; - -#[cfg(feature = "rocket_impl")] -pub mod web; - -mod database; -mod permissions; -mod traits; - -pub use database::*; -pub use traits::*; - -pub use permissions::defn::*; -pub use permissions::{get_relationship, perms}; - -pub use util::{ - r#ref::Ref, - result::{Error, Result}, - variables, -}; - -#[cfg(feature = "rocket_impl")] -pub use web::{Db, EmptyResponse}; - -/// Resolve asset -macro_rules! asset { - ($path:literal) => { - concat!(env!("CARGO_MANIFEST_DIR"), "/assets/", $path) - }; -} - -pub(crate) use asset; diff --git a/crates/quark/src/models/admin/simple.rs b/crates/quark/src/models/admin/simple.rs deleted file mode 100644 index fe9bdedd..00000000 --- a/crates/quark/src/models/admin/simple.rs +++ /dev/null @@ -1,8 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Simple database model for testing -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct SimpleModel { - pub number: i32, - pub value: String, -} diff --git a/crates/quark/src/models/admin/stats.rs b/crates/quark/src/models/admin/stats.rs deleted file mode 100644 index 7866cd8c..00000000 --- a/crates/quark/src/models/admin/stats.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::collections::HashMap; - -use iso8601_timestamp::Timestamp; -use serde::{Deserialize, Serialize}; - -/// Index access information -#[derive(Serialize, Deserialize, JsonSchema, Debug)] -pub struct IndexAccess { - /// Operations since timestamp - ops: i32, - - /// Timestamp at which data keeping begun - since: Timestamp, -} - -/// Collection index -#[derive(Serialize, Deserialize, JsonSchema, Debug)] -pub struct Index { - /// Index name - name: String, - - /// Access information - accesses: IndexAccess, -} - -/// Histogram entry -#[derive(Serialize, Deserialize, JsonSchema, Debug)] -pub struct LatencyHistogramEntry { - /// Time - micros: i64, - - /// Count - count: i64, -} - -/// Collection latency stats -#[derive(Serialize, Deserialize, JsonSchema, Debug)] -pub struct LatencyStats { - /// Total operations - ops: i64, - - /// Timestamp at which data keeping begun - latency: i64, - - /// Histogram representation of latency data - histogram: Vec, -} - -/// Collection storage stats -#[derive(Serialize, Deserialize, JsonSchema, Debug)] -#[serde(rename_all = "camelCase")] -pub struct StorageStats { - /// Uncompressed data size - size: i64, - - /// Data size on disk - storage_size: i64, - - /// Total size of all indexes - total_index_size: i64, - - /// Sum of storage size and total index size - total_size: i64, - - /// Individual index sizes - index_sizes: HashMap, - - /// Number of documents in collection - count: i64, - - /// Average size of each document - avg_obj_size: i64, -} - -/// Query collection scan stats -#[derive(Serialize, Deserialize, JsonSchema, Debug)] -#[serde(rename_all = "camelCase")] -pub struct CollectionScans { - /// Number of total collection scans - total: i64, - - /// Number of total collection scans not using a tailable cursor - non_tailable: i64, -} - -/// Collection query execution stats -#[derive(Serialize, Deserialize, JsonSchema, Debug)] -#[serde(rename_all = "camelCase")] -pub struct QueryExecStats { - /// Stats regarding collection scans - collection_scans: CollectionScans, -} - -/// Collection stats -#[derive(Serialize, Deserialize, JsonSchema, Debug)] -#[serde(rename_all = "camelCase")] -pub struct CollectionStats { - /// Namespace - ns: String, - - /// Local time - local_time: Timestamp, - - /// Latency stats - latency_stats: HashMap, - - /// Query exec stats - query_exec_stats: QueryExecStats, - - /// Number of documents in collection - count: u64, -} - -/// Server Stats -#[derive(Serialize, JsonSchema, Debug)] -pub struct Stats { - /// Index usage information - pub indices: HashMap>, - - /// Collection stats - pub coll_stats: HashMap, -} diff --git a/crates/quark/src/models/channels/channel.rs b/crates/quark/src/models/channels/channel.rs deleted file mode 100644 index 62b05db0..00000000 --- a/crates/quark/src/models/channels/channel.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; - -use crate::{models::attachment::File, OverrideField}; - -/// Utility function to check if a boolean value is false -pub fn if_false(t: &bool) -> bool { - !t -} - -/// Representation of a channel on Revolt -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -#[serde(tag = "channel_type")] -pub enum Channel { - /// Personal "Saved Notes" channel which allows users to save messages - SavedMessages { - /// Unique Id - #[serde(rename = "_id")] - id: String, - /// Id of the user this channel belongs to - user: String, - }, - /// Direct message channel between two users - DirectMessage { - /// Unique Id - #[serde(rename = "_id")] - id: String, - - /// Whether this direct message channel is currently open on both sides - active: bool, - /// 2-tuple of user ids participating in direct message - recipients: Vec, - /// Id of the last message sent in this channel - #[serde(skip_serializing_if = "Option::is_none")] - last_message_id: Option, - }, - /// Group channel between 1 or more participants - Group { - /// Unique Id - #[serde(rename = "_id")] - id: String, - - /// Display name of the channel - name: String, - /// User id of the owner of the group - owner: String, - /// Channel description - #[serde(skip_serializing_if = "Option::is_none")] - description: Option, - /// Array of user ids participating in channel - recipients: Vec, - - /// Custom icon attachment - #[serde(skip_serializing_if = "Option::is_none")] - icon: Option, - /// Id of the last message sent in this channel - #[serde(skip_serializing_if = "Option::is_none")] - last_message_id: Option, - - /// Permissions assigned to members of this group - /// (does not apply to the owner of the group) - #[serde(skip_serializing_if = "Option::is_none")] - permissions: Option, - - /// Whether this group is marked as not safe for work - #[serde(skip_serializing_if = "if_false", default)] - nsfw: bool, - }, - /// Text channel belonging to a server - TextChannel { - /// Unique Id - #[serde(rename = "_id")] - id: String, - /// Id of the server this channel belongs to - server: String, - - /// Display name of the channel - name: String, - /// Channel description - #[serde(skip_serializing_if = "Option::is_none")] - description: Option, - - /// Custom icon attachment - #[serde(skip_serializing_if = "Option::is_none")] - icon: Option, - /// Id of the last message sent in this channel - #[serde(skip_serializing_if = "Option::is_none")] - last_message_id: Option, - - /// Default permissions assigned to users in this channel - #[serde(skip_serializing_if = "Option::is_none")] - default_permissions: Option, - /// Permissions assigned based on role to this channel - #[serde( - default = "HashMap::::new", - skip_serializing_if = "HashMap::::is_empty" - )] - role_permissions: HashMap, - - /// Whether this channel is marked as not safe for work - #[serde(skip_serializing_if = "if_false", default)] - nsfw: bool, - }, - /// Voice channel belonging to a server - VoiceChannel { - /// Unique Id - #[serde(rename = "_id")] - id: String, - /// Id of the server this channel belongs to - server: String, - - /// Display name of the channel - name: String, - #[serde(skip_serializing_if = "Option::is_none")] - /// Channel description - description: Option, - /// Custom icon attachment - #[serde(skip_serializing_if = "Option::is_none")] - icon: Option, - - /// Default permissions assigned to users in this channel - #[serde(skip_serializing_if = "Option::is_none")] - default_permissions: Option, - /// Permissions assigned based on role to this channel - #[serde( - default = "HashMap::::new", - skip_serializing_if = "HashMap::::is_empty" - )] - role_permissions: HashMap, - - /// Whether this channel is marked as not safe for work - #[serde(skip_serializing_if = "if_false", default)] - nsfw: bool, - }, -} - -/// Partial values of [Channel] -#[derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)] -pub struct PartialChannel { - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub owner: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub icon: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub nsfw: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub active: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub permissions: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub role_permissions: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub default_permissions: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub last_message_id: Option, -} - -/// Optional fields on channel object -#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Eq, Clone)] -pub enum FieldsChannel { - Description, - Icon, - DefaultPermissions, -} diff --git a/crates/quark/src/models/channels/channel_invite.rs b/crates/quark/src/models/channels/channel_invite.rs deleted file mode 100644 index 0dc62212..00000000 --- a/crates/quark/src/models/channels/channel_invite.rs +++ /dev/null @@ -1,32 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Representation of an invite to a channel on Revolt -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -#[serde(tag = "type")] -pub enum Invite { - /// Invite to a specific server channel - Server { - /// Invite code - #[serde(rename = "_id")] - code: String, - /// Id of the server this invite points to - server: String, - /// Id of user who created this invite - creator: String, - /// Id of the server channel this invite points to - channel: String, - }, - /// Invite to a group channel - Group { - /// Invite code - #[serde(rename = "_id")] - code: String, - /// Id of user who created this invite - creator: String, - /// Id of the group channel this invite points to - channel: String, - }, /* User { - code: String, - user: String - } */ -} diff --git a/crates/quark/src/models/channels/channel_unread.rs b/crates/quark/src/models/channels/channel_unread.rs deleted file mode 100644 index 6832a014..00000000 --- a/crates/quark/src/models/channels/channel_unread.rs +++ /dev/null @@ -1,25 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Composite primary key consisting of channel and user id -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub struct ChannelCompositeKey { - /// Channel Id - pub channel: String, - /// User Id - pub user: String, -} - -/// Representation of the state of a channel from the perspective of a user -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub struct ChannelUnread { - /// Composite key pointing to a user's view of a channel - #[serde(rename = "_id")] - pub id: ChannelCompositeKey, - - /// Id of the last message read in this channel by a user - #[serde(skip_serializing_if = "Option::is_none")] - pub last_id: Option, - /// Array of message ids that mention the user - #[serde(skip_serializing_if = "Option::is_none")] - pub mentions: Option>, -} diff --git a/crates/quark/src/models/channels/message.rs b/crates/quark/src/models/channels/message.rs deleted file mode 100644 index ec72f687..00000000 --- a/crates/quark/src/models/channels/message.rs +++ /dev/null @@ -1,285 +0,0 @@ -use crate::util::regex::RE_COLOUR; - -use indexmap::{IndexMap, IndexSet}; -use iso8601_timestamp::Timestamp; -use once_cell::sync::Lazy; -use regex::Regex; -use revolt_models::v0::MessageWebhook; -use serde::{Deserialize, Serialize}; -use validator::Validate; - -#[cfg(feature = "rocket_impl")] -use rocket::FromFormField; - -use crate::{ - models::{attachment::File, Member, User}, - types::january::Embed, -}; - -/// Utility function to check if a boolean value is false -pub fn if_false(t: &bool) -> bool { - !t -} - -pub static RE_MENTION: Lazy = - Lazy::new(|| Regex::new(r"<@([0-9A-HJKMNP-TV-Z]{26})>").unwrap()); - -/// # Reply -/// -/// Representation of a message reply before it is sent. -#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] -pub struct Reply { - /// Message Id - pub id: String, - /// Whether this reply should mention the message's author - pub mention: bool, -} - -/// Representation of a text embed before it is sent. -#[derive(Validate, Serialize, Deserialize, JsonSchema, Clone, Debug, Default)] -pub struct SendableEmbed { - #[validate(length(min = 1, max = 128))] - pub icon_url: Option, - #[validate(length(min = 1, max = 256))] - pub url: Option, - #[validate(length(min = 1, max = 100))] - pub title: Option, - #[validate(length(min = 1, max = 2000))] - pub description: Option, - pub media: Option, - #[validate(length(min = 1, max = 128), regex = "RE_COLOUR")] - pub colour: Option, -} - -/// Representation of a system event message -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -#[serde(tag = "type")] -pub enum SystemMessage { - #[serde(rename = "text")] - Text { content: String }, - #[serde(rename = "user_added")] - UserAdded { id: String, by: String }, - #[serde(rename = "user_remove")] - UserRemove { id: String, by: String }, - #[serde(rename = "user_joined")] - UserJoined { id: String }, - #[serde(rename = "user_left")] - UserLeft { id: String }, - #[serde(rename = "user_kicked")] - UserKicked { id: String }, - #[serde(rename = "user_banned")] - UserBanned { id: String }, - #[serde(rename = "channel_renamed")] - ChannelRenamed { name: String, by: String }, - #[serde(rename = "channel_description_changed")] - ChannelDescriptionChanged { by: String }, - #[serde(rename = "channel_icon_changed")] - ChannelIconChanged { by: String }, - #[serde(rename = "channel_ownership_changed")] - ChannelOwnershipChanged { from: String, to: String }, -} - -/// Name and / or avatar override information -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Validate)] -pub struct Masquerade { - /// Replace the display name shown on this message - #[serde(skip_serializing_if = "Option::is_none")] - #[validate(length(min = 1, max = 32))] - pub name: Option, - /// Replace the avatar shown on this message (URL to image file) - #[serde(skip_serializing_if = "Option::is_none")] - #[validate(length(min = 1, max = 256))] - pub avatar: Option, - /// Replace the display role colour shown on this message - /// - /// Must have `ManageRole` permission to use - #[serde(skip_serializing_if = "Option::is_none")] - #[validate(length(min = 1, max = 128), regex = "RE_COLOUR")] - pub colour: Option, -} - -/// Information to guide interactions on this message -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Validate, Default)] -pub struct Interactions { - /// Reactions which should always appear and be distinct - #[serde(skip_serializing_if = "Option::is_none", default)] - pub reactions: Option>, - /// Whether reactions should be restricted to the given list - /// - /// Can only be set to true if reactions list is of at least length 1 - #[serde(skip_serializing_if = "if_false", default)] - pub restrict_reactions: bool, -} - -/// Representation of a Message on Revolt -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct, Default)] -#[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)] -#[optional_name = "PartialMessage"] -#[opt_skip_serializing_none] -#[opt_some_priority] -pub struct Message { - /// Unique Id - #[serde(rename = "_id")] - pub id: String, - /// Unique value generated by client sending this message - #[serde(skip_serializing_if = "Option::is_none")] - pub nonce: Option, - /// Id of the channel this message was sent in - pub channel: String, - /// Id of the user or webhook that sent this message - pub author: String, - /// The webhook that sent this message - #[serde(skip_serializing_if = "Option::is_none")] - pub webhook: Option, - /// Message content - #[serde(skip_serializing_if = "Option::is_none")] - pub content: Option, - /// System message - #[serde(skip_serializing_if = "Option::is_none")] - pub system: Option, - /// Array of attachments - #[serde(skip_serializing_if = "Option::is_none")] - pub attachments: Option>, - /// Time at which this message was last edited - #[serde(skip_serializing_if = "Option::is_none")] - pub edited: Option, - /// Attached embeds to this message - #[serde(skip_serializing_if = "Option::is_none")] - pub embeds: Option>, - /// Array of user ids mentioned in this message - #[serde(skip_serializing_if = "Option::is_none")] - pub mentions: Option>, - /// Array of message ids this message is replying to - #[serde(skip_serializing_if = "Option::is_none")] - pub replies: Option>, - /// Hashmap of emoji IDs to array of user IDs - #[serde(skip_serializing_if = "IndexMap::is_empty", default)] - pub reactions: IndexMap>, - /// Information about how this message should be interacted with - #[serde(skip_serializing_if = "Interactions::is_default", default)] - pub interactions: Interactions, - /// Name and / or avatar overrides for this message - #[serde(skip_serializing_if = "Option::is_none")] - pub masquerade: Option, -} - -/// # Message Sort -/// -/// Sort used for retrieving messages -#[derive(Serialize, Deserialize, JsonSchema, Debug, Default)] -#[cfg_attr(feature = "rocket_impl", derive(FromFormField))] -pub enum MessageSort { - /// Sort by the most relevant messages - #[default] - Relevance, - /// Sort by the newest messages first - Latest, - /// Sort by the oldest messages first - Oldest, -} - -/// # Message Time Period -/// -/// Filter and sort messages by time -#[derive(Serialize, Deserialize, JsonSchema)] -#[serde(untagged)] -pub enum MessageTimePeriod { - Relative { - /// Message id to search around - /// - /// Specifying 'nearby' ignores 'before', 'after' and 'sort'. - /// It will also take half of limit rounded as the limits to each side. - /// It also fetches the message ID specified. - nearby: String, - }, - Absolute { - /// Message id before which messages should be fetched - before: Option, - /// Message id after which messages should be fetched - after: Option, - /// Message sort direction - sort: Option, - }, -} - -/// # Message Filter -#[derive(Serialize, Deserialize, JsonSchema, Default)] -pub struct MessageFilter { - /// Parent channel ID - pub channel: Option, - /// Message author ID - pub author: Option, - /// Search query - pub query: Option, -} - -/// # Message Query -#[derive(Serialize, Deserialize, JsonSchema)] -pub struct MessageQuery { - /// Maximum number of messages to fetch - /// - /// For fetching nearby messages, this is \`(limit + 1)\`. - pub limit: Option, - /// Filter to apply - #[serde(flatten)] - pub filter: MessageFilter, - /// Time period to fetch - #[serde(flatten)] - pub time_period: MessageTimePeriod, -} - -/// # Bulk Message Response -/// -/// Response used when multiple messages are fetched -#[derive(Serialize, JsonSchema)] -#[serde(untagged)] -pub enum BulkMessageResponse { - JustMessages( - /// List of messages - Vec, - ), - MessagesAndUsers { - /// List of messages - messages: Vec, - /// List of users - users: Vec, - /// List of members - #[serde(skip_serializing_if = "Option::is_none")] - members: Option>, - }, -} - -/// # Appended Information -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AppendMessage { - /// Additional embeds to include in this message - #[serde(skip_serializing_if = "Option::is_none")] - pub embeds: Option>, -} - -#[derive(Validate, Serialize, Deserialize, JsonSchema)] -pub struct DataMessageSend { - /// Unique token to prevent duplicate message sending - /// - /// **This is deprecated and replaced by `Idempotency-Key`!** - #[validate(length(min = 1, max = 64))] - pub nonce: Option, - - /// Message content to send - #[validate(length(min = 0, max = 2000))] - pub content: Option, - /// Attachments to include in message - pub attachments: Option>, - /// Messages to reply to - pub replies: Option>, - /// Embeds to include in message - /// - /// Text embed content contributes to the content length cap - #[validate] - pub embeds: Option>, - /// Masquerade to apply to this message - #[validate] - pub masquerade: Option, - /// Information about how this message should be interacted with - pub interactions: Option, -} diff --git a/crates/quark/src/models/media/attachment.rs b/crates/quark/src/models/media/attachment.rs deleted file mode 100644 index 4cef5dfb..00000000 --- a/crates/quark/src/models/media/attachment.rs +++ /dev/null @@ -1,56 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Metadata associated with file -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Default)] -#[serde(tag = "type")] -pub enum Metadata { - /// File is just a generic uncategorised file - #[default] - File, - /// File contains textual data and should be displayed as such - Text, - /// File is an image with specific dimensions - Image { width: isize, height: isize }, - /// File is a video with specific dimensions - Video { width: isize, height: isize }, - /// File is audio - Audio, -} - -/// Representation of a File on Revolt -/// Generated by Autumn -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Default)] -pub struct File { - /// Unique Id - #[serde(rename = "_id")] - pub id: String, - /// Tag / bucket this file was uploaded to - pub tag: String, - /// Original filename - pub filename: String, - /// Parsed metadata of this file - pub metadata: Metadata, - /// Raw content type of this file - pub content_type: String, - /// Size of this file (in bytes) - pub size: isize, - - /// Whether this file was deleted - #[serde(skip_serializing_if = "Option::is_none")] - pub deleted: Option, - /// Whether this file was reported - #[serde(skip_serializing_if = "Option::is_none")] - pub reported: Option, - - // ! THE FOLLOWING SHOULD BE DEPRECATED - #[serde(skip_serializing_if = "Option::is_none")] - pub message_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub user_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub server_id: Option, - - /// Id of the object this file is associated with - #[serde(skip_serializing_if = "Option::is_none")] - pub object_id: Option, -} diff --git a/crates/quark/src/models/media/emoji.rs b/crates/quark/src/models/media/emoji.rs deleted file mode 100644 index 2d3345dd..00000000 --- a/crates/quark/src/models/media/emoji.rs +++ /dev/null @@ -1,34 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Utility function to check if a boolean value is false -pub fn if_false(t: &bool) -> bool { - !t -} - -/// Information about what owns this emoji -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -#[serde(tag = "type")] -pub enum EmojiParent { - Server { id: String }, - Detached, -} - -/// Representation of an Emoji on Revolt -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub struct Emoji { - /// Unique Id - #[serde(rename = "_id")] - pub id: String, - /// What owns this emoji - pub parent: EmojiParent, - /// Uploader user id - pub creator_id: String, - /// Emoji name - pub name: String, - /// Whether the emoji is animated - #[serde(skip_serializing_if = "if_false", default)] - pub animated: bool, - /// Whether the emoji is marked as nsfw - #[serde(skip_serializing_if = "if_false", default)] - pub nsfw: bool, -} diff --git a/crates/quark/src/models/mod.rs b/crates/quark/src/models/mod.rs deleted file mode 100644 index 4bd26af1..00000000 --- a/crates/quark/src/models/mod.rs +++ /dev/null @@ -1,54 +0,0 @@ -mod admin { - pub mod simple; - pub mod stats; -} - -mod media { - pub mod attachment; - pub mod emoji; -} - -mod channels { - pub mod channel; - pub mod channel_invite; - pub mod channel_unread; - pub mod message; -} - -mod servers { - pub mod server; - pub mod server_ban; - pub mod server_member; -} - -mod users { - pub mod user; - pub mod user_settings; -} - -mod safety { - pub mod report; - pub mod snapshot; -} - -pub use admin::*; -pub use channels::*; -pub use media::*; -pub use safety::*; -pub use servers::*; -pub use users::*; - -pub use attachment::File; -pub use channel::Channel; -pub use channel_invite::Invite; -pub use channel_unread::ChannelUnread; -pub use emoji::Emoji; -pub use message::Message; -pub use report::Report; -pub use server::Server; -pub use server_ban::ServerBan; -pub use server_member::Member; -pub use simple::SimpleModel; -pub use snapshot::Snapshot; -pub use user::User; -pub use user_settings::UserSettings; diff --git a/crates/quark/src/models/safety/report.rs b/crates/quark/src/models/safety/report.rs deleted file mode 100644 index 186fa77a..00000000 --- a/crates/quark/src/models/safety/report.rs +++ /dev/null @@ -1,158 +0,0 @@ -use iso8601_timestamp::Timestamp; -use rocket::FromFormField; -use serde::{Deserialize, Serialize}; - -/// Reason for reporting content (message or server) -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub enum ContentReportReason { - /// No reason has been specified - NoneSpecified, - - /// Illegal content catch-all reason - Illegal, - - /// Selling or facilitating use of drugs or other illegal goods - IllegalGoods, - - /// Extortion or blackmail - IllegalExtortion, - - /// Revenge or child pornography - IllegalPornography, - - /// Illegal hacking activity - IllegalHacking, - - /// Extreme violence, gore, or animal cruelty - /// With exception to violence potrayed in media / creative arts - ExtremeViolence, - - /// Content that promotes harm to others / self - PromotesHarm, - - /// Unsolicited advertisements - UnsolicitedSpam, - - /// This is a raid - Raid, - - /// Spam or platform abuse - SpamAbuse, - - /// Scams or fraud - ScamsFraud, - - /// Distribution of malware or malicious links - Malware, - - /// Harassment or abuse targeted at another user - Harassment, -} - -/// Reason for reporting a user -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub enum UserReportReason { - /// No reason has been specified - NoneSpecified, - - /// Unsolicited advertisements - UnsolicitedSpam, - - /// User is sending spam or otherwise abusing the platform - SpamAbuse, - - /// User's profile contains inappropriate content for a general audience - InappropriateProfile, - - /// User is impersonating another user - Impersonation, - - /// User is evading a ban - BanEvasion, - - /// User is not of minimum age to use the platform - Underage, -} - -/// The content being reported -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -#[serde(tag = "type")] -pub enum ReportedContent { - /// Report a message - Message { - /// ID of the message - id: String, - /// Reason for reporting message - report_reason: ContentReportReason, - }, - /// Report a server - Server { - /// ID of the server - id: String, - /// Reason for reporting server - report_reason: ContentReportReason, - }, - /// Report a user - User { - /// ID of the user - id: String, - /// Reason for reporting a user - report_reason: UserReportReason, - /// Message context - message_id: Option, - }, -} - -/// Status of the report -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -#[serde(tag = "status")] -pub enum ReportStatus { - /// Report is waiting for triage / action - Created {}, - - /// Report was rejected - Rejected { - rejection_reason: String, - closed_at: Option, - }, - - /// Report was actioned and resolved - Resolved { closed_at: Option }, -} - -/// Just the status of the report -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, FromFormField)] -pub enum ReportStatusString { - /// Report is waiting for triage / action - Created, - - /// Report was rejected - Rejected, - - /// Report was actioned and resolved - Resolved, -} - -/// User-generated platform moderation report. -#[derive(Serialize, Deserialize, JsonSchema, Debug, OptionalStruct, Clone)] -#[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)] -#[optional_name = "PartialReport"] -#[opt_skip_serializing_none] -pub struct Report { - /// Unique Id - #[serde(rename = "_id")] - pub id: String, - /// Id of the user creating this report - pub author_id: String, - /// Reported content - pub content: ReportedContent, - /// Additional report context - pub additional_context: String, - /// Status of the report - #[opt_passthrough] - #[serde(flatten)] - pub status: ReportStatus, - /// Additional notes included on the report - #[serde(default)] - pub notes: String, -} diff --git a/crates/quark/src/models/safety/snapshot.rs b/crates/quark/src/models/safety/snapshot.rs deleted file mode 100644 index 90eb7687..00000000 --- a/crates/quark/src/models/safety/snapshot.rs +++ /dev/null @@ -1,54 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::models::{Channel, Message, Server, User}; - -/// Enum to map into different models -/// that can be saved in a snapshot -#[derive(Serialize, Deserialize, JsonSchema, Debug)] -#[serde(tag = "_type")] -pub enum SnapshotContent { - Message { - /// Context before the message - #[serde(rename = "_prior_context", default)] - prior_context: Vec, - - /// Context after the message - #[serde(rename = "_leading_context", default)] - leading_context: Vec, - - /// Message - #[serde(flatten)] - message: Message, - }, - Server(Server), - User(User), -} - -/// Snapshot of some content -#[derive(Serialize, Deserialize, JsonSchema, Debug)] -pub struct Snapshot { - /// Unique Id - #[serde(rename = "_id")] - pub id: String, - /// Report parent Id - pub report_id: String, - /// Snapshot of content - pub content: SnapshotContent, -} - -/// Snapshot of some content with required data to render -#[derive(Serialize, JsonSchema, Debug)] -pub struct SnapshotWithContext { - /// Snapshot itself - #[serde(flatten)] - pub snapshot: Snapshot, - /// Users involved in snapshot - #[serde(rename = "_users", skip_serializing_if = "Vec::is_empty")] - pub users: Vec, - /// Channels involved in snapshot - #[serde(rename = "_channels", skip_serializing_if = "Vec::is_empty")] - pub channels: Vec, - /// Server involved in snapshot - #[serde(rename = "_server", skip_serializing_if = "Option::is_none")] - pub server: Option, -} diff --git a/crates/quark/src/models/servers/server.rs b/crates/quark/src/models/servers/server.rs deleted file mode 100644 index aa9e9d77..00000000 --- a/crates/quark/src/models/servers/server.rs +++ /dev/null @@ -1,149 +0,0 @@ -use std::collections::HashMap; - -use num_enum::TryFromPrimitive; -use serde::{Deserialize, Serialize}; -use validator::Validate; - -use crate::{models::attachment::File, OverrideField}; - -/// Utility function to check if a boolean value is false -pub fn if_false(t: &bool) -> bool { - !t -} - -/// Representation of a server role -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct, Default)] -#[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Default)] -#[optional_name = "PartialRole"] -#[opt_skip_serializing_none] -#[opt_some_priority] -pub struct Role { - /// Role name - pub name: String, - /// Permissions available to this role - pub permissions: OverrideField, - /// Colour used for this role - /// - /// This can be any valid CSS colour - #[serde(skip_serializing_if = "Option::is_none")] - pub colour: Option, - /// Whether this role should be shown separately on the member sidebar - #[serde(skip_serializing_if = "if_false", default)] - pub hoist: bool, - /// Ranking of this role - #[serde(default)] - pub rank: i64, -} - -/// Channel category -#[derive(Validate, Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub struct Category { - /// Unique ID for this category - #[validate(length(min = 1, max = 32))] - pub id: String, - /// Title for this category - #[validate(length(min = 1, max = 32))] - pub title: String, - /// Channels in this category - pub channels: Vec, -} - -/// System message channel assignments -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub struct SystemMessageChannels { - /// ID of channel to send user join messages in - #[serde(skip_serializing_if = "Option::is_none")] - pub user_joined: Option, - /// ID of channel to send user left messages in - #[serde(skip_serializing_if = "Option::is_none")] - pub user_left: Option, - /// ID of channel to send user kicked messages in - #[serde(skip_serializing_if = "Option::is_none")] - pub user_kicked: Option, - /// ID of channel to send user banned messages in - #[serde(skip_serializing_if = "Option::is_none")] - pub user_banned: Option, -} - -/// Server flag enum -#[derive(Debug, PartialEq, Eq, TryFromPrimitive, Copy, Clone)] -#[repr(i32)] -pub enum ServerFlags { - Verified = 1, - Official = 2, -} - -/// Representation of a server on Revolt -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct, Default)] -#[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)] -#[optional_name = "PartialServer"] -#[opt_skip_serializing_none] -#[opt_some_priority] -pub struct Server { - /// Unique Id - #[serde(rename = "_id")] - pub id: String, - /// User id of the owner - pub owner: String, - - /// Name of the server - pub name: String, - /// Description for the server - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - - /// Channels within this server - pub channels: Vec, - /// Categories for this server - #[serde(skip_serializing_if = "Option::is_none")] - pub categories: Option>, - /// Configuration for sending system event messages - #[serde(skip_serializing_if = "Option::is_none")] - pub system_messages: Option, - - /// Roles for this server - #[serde( - default = "HashMap::::new", - skip_serializing_if = "HashMap::::is_empty" - )] - pub roles: HashMap, - /// Default set of server and channel permissions - pub default_permissions: i64, - - /// Icon attachment - #[serde(skip_serializing_if = "Option::is_none")] - pub icon: Option, - /// Banner attachment - #[serde(skip_serializing_if = "Option::is_none")] - pub banner: Option, - - /// Bitfield of server flags - #[serde(skip_serializing_if = "Option::is_none")] - pub flags: Option, - - /// Whether this server is flagged as not safe for work - #[serde(skip_serializing_if = "if_false", default)] - pub nsfw: bool, - /// Whether to enable analytics - #[serde(skip_serializing_if = "if_false", default)] - pub analytics: bool, - /// Whether this server should be publicly discoverable - #[serde(skip_serializing_if = "if_false", default)] - pub discoverable: bool, -} - -/// Optional fields on server object -#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Eq, Clone)] -pub enum FieldsServer { - Description, - Categories, - SystemMessages, - Icon, - Banner, -} - -/// Optional fields on server object -#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Eq, Clone)] -pub enum FieldsRole { - Colour, -} diff --git a/crates/quark/src/models/servers/server_ban.rs b/crates/quark/src/models/servers/server_ban.rs deleted file mode 100644 index a99adc2a..00000000 --- a/crates/quark/src/models/servers/server_ban.rs +++ /dev/null @@ -1,13 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use super::server_member::MemberCompositeKey; - -/// Representation of a server ban on Revolt -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub struct ServerBan { - /// Unique member id - #[serde(rename = "_id")] - pub id: MemberCompositeKey, - /// Reason for ban creation - pub reason: Option, -} diff --git a/crates/quark/src/models/servers/server_member.rs b/crates/quark/src/models/servers/server_member.rs deleted file mode 100644 index 5f41aaf6..00000000 --- a/crates/quark/src/models/servers/server_member.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::collections::HashMap; - -use iso8601_timestamp::Timestamp; - -use serde::{Deserialize, Serialize}; - -use crate::models::attachment::File; - -use super::server::Role; - -/// Composite primary key consisting of server and user id -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Default)] -pub struct MemberCompositeKey { - /// Server Id - pub server: String, - /// User Id - pub user: String, -} - -/// Representation of a member of a server on Revolt -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct)] -#[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)] -#[optional_name = "PartialMember"] -#[opt_skip_serializing_none] -#[opt_some_priority] -pub struct Member { - /// Unique member id - #[serde(rename = "_id")] - pub id: MemberCompositeKey, - - /// Time at which this user joined the server - pub joined_at: Timestamp, - - /// Member's nickname - #[serde(skip_serializing_if = "Option::is_none")] - pub nickname: Option, - /// Avatar attachment - #[serde(skip_serializing_if = "Option::is_none")] - pub avatar: Option, - - /// Member's roles - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub roles: Vec, - /// Timestamp this member is timed out until - #[serde(skip_serializing_if = "Option::is_none")] - pub timeout: Option, -} - -/// Representation of a member of a server on Revolt With Role Data -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub struct MemberWithRoles { - pub member: Member, - pub roles: HashMap, -} - -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -#[serde(untagged)] -pub enum MemberResponse { - Member(Member), - MemberWithRoles(MemberWithRoles), -} - -impl From for MemberResponse { - fn from(value: Member) -> Self { - Self::Member(value) - } -} -impl From for MemberResponse { - fn from(value: MemberWithRoles) -> Self { - Self::MemberWithRoles(value) - } -} -/// Optional fields on server member object -#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Eq, Clone)] -pub enum FieldsMember { - Nickname, - Avatar, - Roles, - Timeout, -} - -/// Member removal intention -pub enum RemovalIntention { - Leave, - Kick, - Ban, -} diff --git a/crates/quark/src/models/users/bot.rs b/crates/quark/src/models/users/bot.rs deleted file mode 100644 index 9fccb30d..00000000 --- a/crates/quark/src/models/users/bot.rs +++ /dev/null @@ -1,63 +0,0 @@ -use num_enum::TryFromPrimitive; -use serde::{Deserialize, Serialize}; - -/// Utility function to check if a boolean value is false -pub fn if_false(t: &bool) -> bool { - !t -} - -/// Bot flag enum -#[derive(Debug, PartialEq, Eq, TryFromPrimitive, Copy, Clone)] -#[repr(i32)] -pub enum BotFlags { - Verified = 1, - Official = 2, -} - -/// Representation of a bot on Revolt -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct, Default)] -#[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)] -#[optional_name = "PartialBot"] -#[opt_skip_serializing_none] -#[opt_some_priority] -pub struct Bot { - /// Bot Id - /// - /// This equals the associated bot user's id. - #[serde(rename = "_id")] - pub id: String, - /// User Id of the bot owner - pub owner: String, - /// Token used to authenticate requests for this bot - pub token: String, - /// Whether the bot is public - /// (may be invited by anyone) - pub public: bool, - - /// Whether to enable analytics - #[serde(skip_serializing_if = "if_false", default)] - pub analytics: bool, - /// Whether this bot should be publicly discoverable - #[serde(skip_serializing_if = "if_false", default)] - pub discoverable: bool, - /// Reserved; URL for handling interactions - #[serde(skip_serializing_if = "Option::is_none")] - pub interactions_url: Option, - /// URL for terms of service - #[serde(skip_serializing_if = "Option::is_none")] - pub terms_of_service_url: Option, - /// URL for privacy policy - #[serde(skip_serializing_if = "Option::is_none")] - pub privacy_policy_url: Option, - - /// Enum of bot flags - #[serde(skip_serializing_if = "Option::is_none")] - pub flags: Option, -} - -/// Optional fields on bot object -#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Eq)] -pub enum FieldsBot { - Token, - InteractionsURL, -} diff --git a/crates/quark/src/models/users/user.rs b/crates/quark/src/models/users/user.rs deleted file mode 100644 index 6c5e2c80..00000000 --- a/crates/quark/src/models/users/user.rs +++ /dev/null @@ -1,191 +0,0 @@ -use num_enum::TryFromPrimitive; -use serde::{Deserialize, Serialize}; -use validator::Validate; - -use crate::models::attachment::File; - -/// Utility function to check if a boolean value is false -pub fn if_false(t: &bool) -> bool { - !t -} - -/// User's relationship with another user (or themselves) -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq, Eq)] -pub enum RelationshipStatus { - None, - User, - Friend, - Outgoing, - Incoming, - Blocked, - BlockedOther, -} - -/// Relationship entry indicating current status with other user -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub struct Relationship { - #[serde(rename = "_id")] - pub id: String, - pub status: RelationshipStatus, -} - -/// Presence status -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq, Eq)] -pub enum Presence { - /// User is online - Online, - /// User is not currently available - Idle, - /// User is focusing / will only receive mentions - Focus, - /// User is busy / will not receive any notifications - Busy, - /// User appears to be offline - Invisible, -} - -/// User's active status -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Validate, Default)] -pub struct UserStatus { - /// Custom status text - #[validate(length(min = 1, max = 128))] - #[serde(skip_serializing_if = "Option::is_none")] - pub text: Option, - /// Current presence option - #[serde(skip_serializing_if = "Option::is_none")] - pub presence: Option, -} - -/// User's profile -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Default)] -pub struct UserProfile { - /// Text content on user's profile - #[serde(skip_serializing_if = "Option::is_none")] - pub content: Option, - /// Background visible on user's profile - #[serde(skip_serializing_if = "Option::is_none")] - pub background: Option, -} - -/// User badge bitfield -#[derive(Debug, PartialEq, Eq, TryFromPrimitive, Copy, Clone)] -#[repr(i32)] -pub enum Badges { - /// Revolt Developer - Developer = 1, - /// Helped translate Revolt - Translator = 2, - /// Monetarily supported Revolt - Supporter = 4, - /// Responsibly disclosed a security issue - ResponsibleDisclosure = 8, - /// Revolt Founder - Founder = 16, - /// Platform moderator - PlatformModeration = 32, - /// Active monetary supporter - ActiveSupporter = 64, - /// ๐ŸฆŠ๐Ÿฆ - Paw = 128, - /// Joined as one of the first 1000 users in 2021 - EarlyAdopter = 256, - /// Amogus - ReservedRelevantJokeBadge1 = 512, - /// Low resolution troll face - ReservedRelevantJokeBadge2 = 1024, -} - -/// User flag enum -#[derive(Debug, PartialEq, Eq, TryFromPrimitive, Copy, Clone)] -#[repr(i32)] -pub enum Flags { - /// User has been suspended from the platform - Suspended = 1, - /// User has deleted their account - Deleted = 2, - /// User was banned off the platform - Banned = 4, - /// User was marked as spam and removed from platform - Spam = 8, -} - -/// Bot information for if the user is a bot -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub struct BotInformation { - /// Id of the owner of this bot - pub owner: String, -} - -/// Representiation of a User on Revolt. -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct, Default)] -#[optional_derive(Serialize, Deserialize, Debug, Default, Clone)] -#[optional_name = "PartialUser"] -#[opt_skip_serializing_none] -#[opt_some_priority] -pub struct User { - /// Unique Id - #[serde(rename = "_id")] - pub id: String, - /// Username - pub username: String, - /// Discriminator - pub discriminator: String, - /// Display name - #[serde(skip_serializing_if = "Option::is_none")] - pub display_name: Option, - #[serde(skip_serializing_if = "Option::is_none")] - /// Avatar attachment - pub avatar: Option, - /// Relationships with other users - #[serde(skip_serializing_if = "Option::is_none")] - pub relations: Option>, - - /// Bitfield of user badges - #[serde(skip_serializing_if = "Option::is_none")] - pub badges: Option, - /// User's current status - #[serde(skip_serializing_if = "Option::is_none")] - pub status: Option, - /// User's profile page - #[serde(skip_serializing_if = "Option::is_none")] - pub profile: Option, - - /// Enum of user flags - #[serde(skip_serializing_if = "Option::is_none")] - pub flags: Option, - /// Whether this user is privileged - #[serde(skip_serializing_if = "if_false", default)] - pub privileged: bool, - /// Bot information - #[serde(skip_serializing_if = "Option::is_none")] - pub bot: Option, - - // ? Entries below should never be pushed to the database - /// Current session user's relationship with this user - #[serde(skip_serializing_if = "Option::is_none")] - pub relationship: Option, - /// Whether this user is currently online - #[serde(skip_serializing_if = "Option::is_none")] - pub online: Option, -} - -/// Optional fields on user object -#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Eq, Clone)] -pub enum FieldsUser { - Avatar, - StatusText, - StatusPresence, - ProfileContent, - ProfileBackground, - DisplayName, -} - -/// Enumeration providing a hint to the type of user we are handling -pub enum UserHint { - /// Could be either a user or a bot - Any, - /// Only match bots - Bot, - /// Only match users - User, -} diff --git a/crates/quark/src/models/users/user_settings.rs b/crates/quark/src/models/users/user_settings.rs deleted file mode 100644 index 21efab9a..00000000 --- a/crates/quark/src/models/users/user_settings.rs +++ /dev/null @@ -1,6 +0,0 @@ -use std::collections::HashMap; - -/// HashMap of user settings -/// Each key is mapped to a tuple consisting of the -/// revision timestamp and serialised data (in JSON format) -pub type UserSettings = HashMap; diff --git a/crates/quark/src/permissions/defn/mod.rs b/crates/quark/src/permissions/defn/mod.rs deleted file mode 100644 index a665b903..00000000 --- a/crates/quark/src/permissions/defn/mod.rs +++ /dev/null @@ -1,108 +0,0 @@ -mod permission; -mod user; - -use bson::Bson; -pub use permission::*; -pub use user::*; - -use serde::{Deserialize, Serialize}; - -/// Holds a permission value to manipulate. -#[derive(Debug)] -pub struct PermissionValue(u64); - -/// Representation of a single permission override -#[derive(Deserialize, JsonSchema, Debug, Clone, Copy)] -pub struct Override { - /// Allow bit flags - allow: u64, - /// Disallow bit flags - deny: u64, -} - -/// Representation of a single permission override -/// as it appears on models and in the database -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Copy, Default)] -pub struct OverrideField { - /// Allow bit flags - a: i64, - /// Disallow bit flags - d: i64, -} - -impl Override { - /// Into allows - pub fn allows(&self) -> u64 { - self.allow - } - - /// Into denies - pub fn denies(&self) -> u64 { - self.deny - } -} - -impl PermissionValue { - /// Apply a given override to this value - pub fn apply(&mut self, v: Override) { - self.allow(v.allow); - self.revoke(v.deny); - } - - /// Allow given permissions - pub fn allow(&mut self, v: u64) { - self.0 |= v; - } - - /// Revoke given permissions - pub fn revoke(&mut self, v: u64) { - self.0 &= !v; - } - - /// Restrict to given permissions - pub fn restrict(&mut self, v: u64) { - self.0 &= v; - } -} - -impl From for OverrideField { - fn from(v: Override) -> Self { - Self { - a: v.allow as i64, - d: v.deny as i64, - } - } -} - -impl From for Override { - fn from(v: OverrideField) -> Self { - Self { - allow: v.a as u64, - deny: v.d as u64, - } - } -} - -impl From for Bson { - fn from(v: OverrideField) -> Self { - Self::Document(bson::to_document(&v).unwrap()) - } -} - -impl From for PermissionValue { - fn from(v: i64) -> Self { - Self(v as u64) - } -} - -impl From for PermissionValue { - fn from(v: u64) -> Self { - Self(v) - } -} - -impl From for u64 { - fn from(v: PermissionValue) -> Self { - v.0 - } -} diff --git a/crates/quark/src/permissions/defn/permission.rs b/crates/quark/src/permissions/defn/permission.rs deleted file mode 100644 index 144cadd1..00000000 --- a/crates/quark/src/permissions/defn/permission.rs +++ /dev/null @@ -1,157 +0,0 @@ -use num_enum::TryFromPrimitive; -use serde::{Deserialize, Serialize}; -use once_cell::sync::Lazy; -use std::ops::{self, Add}; - -/// Permission value on Revolt -/// -/// This should be restricted to the lower 52 bits to prevent any -/// potential issues with Javascript. Also leave empty spaces for -/// future permission flags to be added. -#[derive( - Serialize, Deserialize, Debug, PartialEq, Eq, TryFromPrimitive, Copy, Clone, JsonSchema, -)] -#[repr(u64)] -pub enum Permission { - // * Generic permissions - /// Manage the channel or channels on the server - ManageChannel = 1 << 0, - /// Manage the server - ManageServer = 1 << 1, - /// Manage permissions on servers or channels - ManagePermissions = 1 << 2, - /// Manage roles on server - ManageRole = 1 << 3, - /// Manage server customisation (includes emoji) - ManageCustomisation = 1 << 4, - - // % 1 bit reserved - - // * Member permissions - /// Kick other members below their ranking - KickMembers = 1 << 6, - /// Ban other members below their ranking - BanMembers = 1 << 7, - /// Timeout other members below their ranking - TimeoutMembers = 1 << 8, - /// Assign roles to members below their ranking - AssignRoles = 1 << 9, - /// Change own nickname - ChangeNickname = 1 << 10, - /// Change or remove other's nicknames below their ranking - ManageNicknames = 1 << 11, - /// Change own avatar - ChangeAvatar = 1 << 12, - /// Remove other's avatars below their ranking - RemoveAvatars = 1 << 13, - - // % 7 bits reserved - - // * Channel permissions - /// View a channel - ViewChannel = 1 << 20, - /// Read a channel's past message history - ReadMessageHistory = 1 << 21, - /// Send a message in a channel - SendMessage = 1 << 22, - /// Delete messages in a channel - ManageMessages = 1 << 23, - /// Manage webhook entries on a channel - ManageWebhooks = 1 << 24, - /// Create invites to this channel - InviteOthers = 1 << 25, - /// Send embedded content in this channel - SendEmbeds = 1 << 26, - /// Send attachments and media in this channel - UploadFiles = 1 << 27, - /// Masquerade messages using custom nickname and avatar - Masquerade = 1 << 28, - /// React to messages with emojis - React = 1 << 29, - - // * Voice permissions - /// Connect to a voice channel - Connect = 1 << 30, - /// Speak in a voice call - Speak = 1 << 31, - /// Share video in a voice call - Video = 1 << 32, - /// Mute other members with lower ranking in a voice call - MuteMembers = 1 << 33, - /// Deafen other members with lower ranking in a voice call - DeafenMembers = 1 << 34, - /// Move members between voice channels - MoveMembers = 1 << 35, - - // * Misc. permissions - // % Bits 36 to 52: free area - // % Bits 53 to 64: do not use - - // * Grant all permissions - /// Safely grant all permissions - GrantAllSafe = 0x000F_FFFF_FFFF_FFFF, - - /// Grant all permissions - GrantAll = u64::MAX, -} - -impl_op_ex!(+ |a: &Permission, b: &Permission| -> u64 { *a as u64 | *b as u64 }); -impl_op_ex_commutative!(+ |a: &u64, b: &Permission| -> u64 { *a | *b as u64 }); - -pub static ALLOW_IN_TIMEOUT: Lazy = Lazy::new(|| Permission::ViewChannel + Permission::ReadMessageHistory); -pub static DEFAULT_PERMISSION_VIEW_ONLY: Lazy = Lazy::new(|| Permission::ViewChannel + Permission::ReadMessageHistory); -pub static DEFAULT_PERMISSION: Lazy = Lazy::new(|| DEFAULT_PERMISSION_VIEW_ONLY.add( - Permission::SendMessage - + Permission::InviteOthers - + Permission::SendEmbeds - + Permission::UploadFiles - + Permission::Connect - + Permission::Speak)); -pub static DEFAULT_PERMISSION_SAVED_MESSAGES: u64 = Permission::GrantAllSafe as u64; -pub static DEFAULT_PERMISSION_DIRECT_MESSAGE: Lazy = Lazy::new(|| DEFAULT_PERMISSION.add(Permission::ManageChannel + Permission::React)); -pub static DEFAULT_PERMISSION_SERVER: Lazy = Lazy::new(|| DEFAULT_PERMISSION.add(Permission::React + Permission::ChangeNickname + Permission::ChangeAvatar)); -pub static DEFAULT_WEBHOOK_PERMISSIONS: Lazy = Lazy::new(|| Permission::SendMessage + Permission::SendEmbeds + Permission::Masquerade + Permission::React); - -bitfield! { - #[derive(Default)] - pub struct Permissions(MSB0 [u64]); - u64; - - // * Server permissions - pub can_manage_channel, _: 63; - pub can_manage_server, _: 62; - pub can_manage_permissions, _: 61; - pub can_manage_roles, _: 60; - pub can_manage_customisation, _: 59; - - // * Member permissions - pub can_kick_members, _: 57; - pub can_ban_members, _: 56; - pub can_timeout_members, _: 55; - pub can_assign_roles, _: 54; - pub can_change_nickname, _: 53; - pub can_manage_nicknames, _: 52; - pub can_change_avatar, _: 51; - pub can_remove_avatars, _: 50; - - // * Channel permissions - pub can_view_channel, _: 42; - pub can_read_message_history, _: 41; - pub can_send_message, _: 40; - pub can_manage_messages, _: 39; - pub can_manage_webhooks, _: 38; - pub can_invite_others, _: 37; - pub can_send_embeds, _: 36; - pub can_upload_files, _: 35; - pub can_masquerade, _: 34; - - // * Voice permissions - pub can_connect, _: 32; - pub can_speak, _: 31; - pub can_share_video, _: 30; - pub can_mute_members, _: 29; - pub can_deafen_members, _: 28; - pub can_move_members, _: 27; -} - -pub type Perms = Permissions<[u64; 1]>; diff --git a/crates/quark/src/permissions/defn/user.rs b/crates/quark/src/permissions/defn/user.rs deleted file mode 100644 index faed0e5f..00000000 --- a/crates/quark/src/permissions/defn/user.rs +++ /dev/null @@ -1,29 +0,0 @@ -use num_enum::TryFromPrimitive; -use serde::{Deserialize, Serialize}; -use std::ops; - -/// User permission definitions -#[derive( - Serialize, Deserialize, Debug, PartialEq, Eq, TryFromPrimitive, Copy, Clone, JsonSchema, -)] -#[repr(u32)] -pub enum UserPermission { - Access = 1 << 0, - ViewProfile = 1 << 1, - SendMessage = 1 << 2, - Invite = 1 << 3, -} - -impl_op_ex!(+ |a: &UserPermission, b: &UserPermission| -> u32 { *a as u32 | *b as u32 }); -impl_op_ex_commutative!(+ |a: &u32, b: &UserPermission| -> u32 { *a | *b as u32 }); - -bitfield! { - pub struct UserPermissions(MSB0 [u32]); - u32; - pub get_access, _: 31; - pub get_view_profile, _: 30; - pub get_send_message, _: 29; - pub get_invite, _: 28; -} - -pub type UserPerms = UserPermissions<[u32; 1]>; diff --git a/crates/quark/src/permissions/impl/mod.rs b/crates/quark/src/permissions/impl/mod.rs deleted file mode 100644 index 30506541..00000000 --- a/crates/quark/src/permissions/impl/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod permission; -pub mod user; diff --git a/crates/quark/src/permissions/impl/permission.rs b/crates/quark/src/permissions/impl/permission.rs deleted file mode 100644 index 7ff36616..00000000 --- a/crates/quark/src/permissions/impl/permission.rs +++ /dev/null @@ -1,232 +0,0 @@ -use std::collections::HashSet; - -use crate::{ - models::Channel, permissions::PermissionCalculator, Override, Permission, PermissionValue, - Permissions, Perms, Result, ALLOW_IN_TIMEOUT, DEFAULT_PERMISSION_DIRECT_MESSAGE, - DEFAULT_PERMISSION_SAVED_MESSAGES, DEFAULT_PERMISSION_VIEW_ONLY, -}; - -use super::super::Permission::GrantAllSafe; - -impl PermissionCalculator<'_> { - /// Calculate the permissions from our perspective to the given server or channel - /// - /// Refer to https://developers.revolt.chat/stack/delta/permissions#flow-chart for more information - pub async fn calc(&mut self, db: &crate::Database) -> Result { - if self.perspective.privileged { - return Ok(Permissions([GrantAllSafe as u64])); - } - - let value = if self.channel.has() { - calculate_channel_permission(self, db).await? - } else if self.server.has() { - calculate_server_permission(self, db).await? - } else { - panic!("Expected `PermissionCalculator.(user|server) to exist."); - } - .into(); - - self.cached_permission = Some(value); - Ok(Permissions([value])) - } -} - -/// Internal helper function for calculating server permission -async fn calculate_server_permission( - data: &mut PermissionCalculator<'_>, - db: &crate::Database, -) -> Result { - let server = data.server.get().unwrap(); - - // 1. Check if owner. - if data.perspective.id == server.owner { - return Ok((Permission::GrantAllSafe as u64).into()); - } - - // 2. Fetch member. - if !data.member.has() { - data.member - .set(db.fetch_member(&server.id, &data.perspective.id).await?); - } - - let member = data.member.get().expect("Member should be present by now."); - - // 3. Apply allows from default_permissions. - let mut permissions: PermissionValue = server.default_permissions.into(); - - // 4. Resolve each role in order. - let member_roles: HashSet<&String> = member.roles.iter().collect(); - - if !member_roles.is_empty() { - let mut roles = server - .roles - .iter() - .filter(|(id, _)| member_roles.contains(id)) - .map(|(_, role)| { - let v: Override = role.permissions.into(); - (role.rank, v) - }) - .collect::>(); - - roles.sort_by(|a, b| b.0.cmp(&a.0)); - - // 5. Apply allows and denies from roles. - for (_, v) in roles { - permissions.apply(v); - } - } - - // 5. Revoke permissions if member is timed out. - if member.in_timeout() { - permissions.restrict(*ALLOW_IN_TIMEOUT); - } - - Ok(permissions) -} - -/// Internal helper function for calculating channel permission -async fn calculate_channel_permission( - data: &mut PermissionCalculator<'_>, - db: &crate::Database, -) -> Result { - // Pre-calculate server permissions if applicable. - // We do this to satisfy the borrow checker. - let server_id = match data.channel.get().unwrap() { - Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => Some(server), - _ => None, - }; - - let mut permissions = if let Some(server) = server_id { - if !data.server.has() { - data.server.set(db.fetch_server(server).await?); - } - - calculate_server_permission(data, db).await? - } else { - 0_u64.into() - }; - - // Borrow the channel now and continue as normal. - let channel = data.channel.get().unwrap(); - - // 1. Check channel type. - let value: PermissionValue = match channel { - Channel::SavedMessages { user, .. } => { - if user == &data.perspective.id { - DEFAULT_PERMISSION_SAVED_MESSAGES.into() - } else { - 0_u64.into() - } - } - Channel::DirectMessage { recipients, .. } => { - // 2. Ensure we are a recipient. - if recipients.contains(&data.perspective.id) { - // 3. Fetch user. - let other_user = recipients - .iter() - .find(|x| x != &&data.perspective.id) - .unwrap(); - - let user = db.fetch_user(other_user).await?; - data.user.set(user); - - // 4. Calculate user permissions. - let perms = data.calc_user(db).await; - - // 5. Check if the user can send messages. - if perms.get_send_message() { - (*DEFAULT_PERMISSION_DIRECT_MESSAGE).into() - } else { - (*DEFAULT_PERMISSION_VIEW_ONLY).into() - } - } else { - 0_u64.into() - } - } - Channel::Group { - owner, - permissions, - recipients, - .. - } => { - // 2. Check if user is owner. - if &data.perspective.id == owner { - (Permission::GrantAllSafe as u64).into() - } else { - // 3. Check that we are actually in the group. - if recipients.contains(&data.perspective.id) { - // 4. Pull out group permissions. - permissions - .map(|x| x as u64) - .unwrap_or(*DEFAULT_PERMISSION_DIRECT_MESSAGE) - .into() - } else { - 0_u64.into() - } - } - } - Channel::TextChannel { - default_permissions, - role_permissions, - .. - } - | Channel::VoiceChannel { - default_permissions, - role_permissions, - .. - } => { - // 2. If server owner, just grant all permissions. - // - // Member may be present and we need to check or - // we can just grant all if member is not present. - // - // In the case member isn't present, the previous - // step did not fetch member as we are the server owner. - if let Some(member) = data.member.get() { - let server = data.server.get().unwrap(); - if server.owner == member.id.user { - return Ok((Permission::GrantAllSafe as u64).into()); - } - - // 3. Apply default allows and denies for channel. - if let Some(default) = default_permissions { - permissions.apply((*default).into()); - } - - // 4. Resolve each role in order. - let member_roles: HashSet<&String> = member.roles.iter().collect(); - - if !member_roles.is_empty() { - let mut roles = role_permissions - .iter() - .filter(|(id, _)| member_roles.contains(id)) - .filter_map(|(id, permission)| { - server.roles.get(id).map(|role| { - let v: Override = (*permission).into(); - (role.rank, v) - }) - }) - .collect::>(); - - roles.sort_by(|a, b| b.0.cmp(&a.0)); - - // 5. Apply allows and denies from roles. - for (_, v) in roles { - permissions.apply(v); - } - } - - // 5. Revoke permissions if member is timed out. - if member.in_timeout() { - permissions.restrict(*ALLOW_IN_TIMEOUT); - } - - permissions - } else { - (Permission::GrantAllSafe as u64).into() - } - } - }; - - Ok(value) -} diff --git a/crates/quark/src/permissions/impl/user.rs b/crates/quark/src/permissions/impl/user.rs deleted file mode 100644 index f2b37048..00000000 --- a/crates/quark/src/permissions/impl/user.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::{ - models::{user::RelationshipStatus, User}, - permissions::PermissionCalculator, - UserPermission, UserPermissions, UserPerms, -}; - -impl PermissionCalculator<'_> { - /// Calculate the permissions from our perspective to the given user - /// - /// How the permission is calculated: - /// 1. Are we the target? - /// - If so: return maximum permissions - /// 2. Do we have a relationship with the target? - /// - If we are friends: return maximum permissions - /// - If either user blocked each other: return only `Access` - /// - If incoming / outgoing request: add `Access` to the list - /// 3. Determine whether there is a mutual connection: - /// 1. Check if the "mutual connection" flag is set. - /// 2. Check if we share any servers with the target. - /// 3. Check if we share any DMs or groups with the target. - /// 4. Do we have a mutual connection with the target? - /// - If so: return `Access` + `ViewProfile` - /// 5. Return no permissions - pub async fn calc_user(&mut self, db: &crate::Database) -> UserPerms { - if self.user.has() { - let v = calculate_permission(self, db).await; - self.cached_user_permission = Some(v); - UserPermissions([v]) - } else { - panic!("Expected `PermissionCalculator.user` to exist.") - } - } -} - -/// Find the relationship between two users -pub fn get_relationship(a: &User, b: &str) -> RelationshipStatus { - if a.id == b { - return RelationshipStatus::User; - } - - if let Some(relations) = &a.relations { - if let Some(relationship) = relations.iter().find(|x| x.id == b) { - return relationship.status.clone(); - } - } - - RelationshipStatus::None -} - -/// Internal helper function for calculating permission -async fn calculate_permission(data: &mut PermissionCalculator<'_>, db: &crate::Database) -> u32 { - let user = data.user.get().unwrap(); - - if data.perspective.privileged { - return u32::MAX; - } - - if data.perspective.id == user.id { - return u32::MAX; - } - - let relationship = data.flag_known_relationship.cloned().unwrap_or_else(|| { - user.relationship - .as_ref() - .cloned() - .unwrap_or_else(|| get_relationship(data.perspective, &user.id)) - }); - - let mut permissions: u32 = 0; - match relationship { - RelationshipStatus::Friend => return u32::MAX, - RelationshipStatus::Blocked | RelationshipStatus::BlockedOther => { - return UserPermission::Access as u32 - } - RelationshipStatus::Incoming | RelationshipStatus::Outgoing => { - permissions = UserPermission::Access as u32; - } - _ => {} - } - - if data.flag_has_mutual_connection - || data - .perspective - .has_mutual_connection(db, &user.id) - .await - .unwrap_or(false) - { - permissions = UserPermission::Access + UserPermission::ViewProfile; - - if user.bot.is_some() || data.perspective.bot.is_some() { - permissions += UserPermission::SendMessage as u32; - } - - return permissions; - } - - permissions -} diff --git a/crates/quark/src/permissions/mod.rs b/crates/quark/src/permissions/mod.rs deleted file mode 100644 index 81a15ce6..00000000 --- a/crates/quark/src/permissions/mod.rs +++ /dev/null @@ -1,171 +0,0 @@ -use crate::{ - models::{user::RelationshipStatus, Channel, Member, Server, User}, - util::value::Value, - Database, Error, Override, Permission, Result, -}; - -pub mod defn; -pub mod r#impl; - -pub use r#impl::user::get_relationship; - -/// Permissions calculator -#[derive(Clone)] -pub struct PermissionCalculator<'a> { - perspective: &'a User, - - pub user: Value<'a, User>, - pub channel: Value<'a, Channel>, - pub server: Value<'a, Server>, - pub member: Value<'a, Member>, - - flag_known_relationship: Option<&'a RelationshipStatus>, - flag_has_mutual_connection: bool, - - cached_user_permission: Option, - cached_permission: Option, -} - -impl<'a> PermissionCalculator<'a> { - /// Create a new permission calculator - pub fn new(perspective: &'a User) -> PermissionCalculator { - PermissionCalculator { - perspective, - - user: Value::None, - channel: Value::None, - server: Value::None, - member: Value::None, - - flag_known_relationship: None, - flag_has_mutual_connection: false, - - cached_user_permission: None, - cached_permission: None, - } - } - - /// Use user by ref - pub fn user(self, user: &'a User) -> PermissionCalculator { - PermissionCalculator { - user: Value::Ref(user), - ..self - } - } - - /// Use channel by ref - pub fn channel(self, channel: &'a Channel) -> PermissionCalculator { - PermissionCalculator { - channel: Value::Ref(channel), - ..self - } - } - - /// Use server by ref - pub fn server(self, server: &'a Server) -> PermissionCalculator { - PermissionCalculator { - server: Value::Ref(server), - ..self - } - } - - /// Use member by ref - pub fn member(self, member: &'a Member) -> PermissionCalculator { - PermissionCalculator { - member: Value::Ref(member), - ..self - } - } - - /// Use existing relationship by ref - pub fn with_relationship(self, relationship: &'a RelationshipStatus) -> PermissionCalculator { - PermissionCalculator { - flag_known_relationship: Some(relationship), - ..self - } - } - - /// Check whether the calculated permission contains a given value - pub async fn has_permission_value(&mut self, db: &Database, value: u64) -> Result { - let perms = if let Some(perms) = self.cached_permission { - perms - } else { - self.calc(db).await?.0[0] - }; - - Ok((value) & perms == (value)) - } - - /// Check whether we have a given permission - pub async fn has_permission(&mut self, db: &Database, permission: Permission) -> Result { - self.has_permission_value(db, permission as u64).await - } - - /// Check whether we have a given permission, otherwise throw an error - pub async fn throw_permission_value(&mut self, db: &Database, value: u64) -> Result<()> { - if self.has_permission_value(db, value).await? { - Ok(()) - } else { - Err(Error::CannotGiveMissingPermissions) - } - } - - /// Check whether we have a given permission, otherwise throw an error - pub async fn throw_permission(&mut self, db: &Database, permission: Permission) -> Result<()> { - if self.has_permission(db, permission).await? { - Ok(()) - } else { - Error::from_permission(permission) - } - } - - /// Throw an error if we cannot grant permissions on either allows or denies - /// going from the previous given value to the next given value. - /// - /// We need to check any: - /// - allows added (permissions now granted) - /// - denies removed (permissions now neutral or granted) - pub async fn throw_permission_override( - &mut self, - db: &Database, - current_value: C, - next_value: Override, - ) -> Result<()> - where - C: Into>, - { - let current_value = current_value.into(); - - if let Some(current_value) = current_value { - self.throw_permission_value(db, !current_value.allows() & next_value.allows()) - .await?; - - self.throw_permission_value(db, current_value.denies() & !next_value.denies()) - .await - } else { - self.throw_permission_value(db, next_value.allows()).await - } - } - - /// Check whether we has the ViewChannel and another given permission, otherwise throw an error - pub async fn throw_permission_and_view_channel( - &mut self, - db: &Database, - permission: Permission, - ) -> Result<()> { - self.throw_permission(db, Permission::ViewChannel).await?; - self.throw_permission(db, permission).await - } - - /// Get the known member's current ranking - pub fn get_member_rank(&self) -> Option { - self.member - .get() - .map(|member| member.get_ranking(self.server.get().unwrap())) - } -} - -/// Short-hand for creating a permission calculator -pub fn perms(perspective: &'_ User) -> PermissionCalculator<'_> { - PermissionCalculator::new(perspective) -} diff --git a/crates/quark/src/tasks/ack.rs b/crates/quark/src/tasks/ack.rs deleted file mode 100644 index 6bca0886..00000000 --- a/crates/quark/src/tasks/ack.rs +++ /dev/null @@ -1,121 +0,0 @@ -// Queue Type: Debounced -use crate::Database; - -use deadqueue::limited::Queue; -use once_cell::sync::Lazy; -use std::{collections::HashMap, time::Duration}; - -use super::DelayedTask; - -/// Enumeration of possible events -#[derive(Debug, Eq, PartialEq)] -pub enum AckEvent { - /// Add mentions for a user in a channel - AddMention { - /// Message IDs - ids: Vec, - }, - - /// Acknowledge message in a channel for a user - AckMessage { - /// Message ID - id: String, - }, -} - -/// Task information -struct Data { - /// Channel to ack - channel: String, - /// User to ack for - user: String, - /// Event - event: AckEvent, -} - -#[derive(Debug)] -struct Task { - event: AckEvent, -} - -static Q: Lazy> = Lazy::new(|| Queue::new(10_000)); - -/// Queue a new task for a worker -pub async fn queue(channel: String, user: String, event: AckEvent) { - Q.try_push(Data { - channel, - user, - event, - }) - .ok(); - - info!("Queue is using {} slots from {}.", Q.len(), Q.capacity()); -} - -/// Start a new worker -pub async fn worker(db: Database) { - let mut tasks = HashMap::<(String, String), DelayedTask>::new(); - let mut keys = vec![]; - - loop { - // Find due tasks. - for (key, task) in &tasks { - if task.should_run() { - keys.push(key.clone()); - } - } - - // Commit any due tasks to the database. - for key in &keys { - if let Some(task) = tasks.remove(key) { - let Task { event } = task.data; - let (user, channel) = key; - - if let Err(err) = match &event { - AckEvent::AckMessage { id } => db.acknowledge_message(channel, user, id).await, - AckEvent::AddMention { ids } => { - db.add_mention_to_unread(channel, user, ids).await - } - } { - error!("{err:?} for {event:?}. ({user}, {channel})"); - } else { - info!("User {user} ack in {channel} with {event:?}"); - } - } - } - - // Clear keys - keys.clear(); - - // Queue incoming tasks. - while let Some(Data { - channel, - user, - mut event, - }) = Q.try_pop() - { - let key = (user, channel); - if let Some(task) = tasks.get_mut(&key) { - task.delay(); - - match &mut event { - AckEvent::AddMention { ids } => { - if let AckEvent::AddMention { ids: existing } = &mut task.data.event { - existing.append(ids); - } else { - task.data.event = event; - } - } - AckEvent::AckMessage { .. } => { - task.data.event = event; - } - } - } else { - tasks.insert(key, DelayedTask::new(Task { event })); - } - } - - // Sleep for an arbitrary amount of time. - async_std::task::sleep(Duration::from_secs(1)).await; - } -} diff --git a/crates/quark/src/tasks/last_message_id.rs b/crates/quark/src/tasks/last_message_id.rs deleted file mode 100644 index acc3cbc4..00000000 --- a/crates/quark/src/tasks/last_message_id.rs +++ /dev/null @@ -1,87 +0,0 @@ -// Queue Type: Debounced -use crate::{models::channel::PartialChannel, Database}; - -use deadqueue::limited::Queue; -use once_cell::sync::Lazy; -use std::{collections::HashMap, time::Duration}; - -use super::DelayedTask; - -/// Task information -struct Data { - /// Channel to update - channel: String, - /// Latest message ID - id: String, - /// Whether the channel is a DM - is_dm: bool, -} - -/// Task information -#[derive(Debug)] -struct Task { - /// Latest message ID - id: String, - /// Whether the channel is a DM - is_dm: bool, -} - -static Q: Lazy> = Lazy::new(|| Queue::new(10_000)); - -/// Queue a new task for a worker -pub async fn queue(channel: String, id: String, is_dm: bool) { - Q.try_push(Data { channel, id, is_dm }).ok(); - info!("Queue is using {} slots from {}.", Q.len(), Q.capacity()); -} - -/// Start a new worker -pub async fn worker(db: Database) { - let mut tasks = HashMap::>::new(); - let mut keys = vec![]; - - loop { - // Find due tasks. - for (key, task) in &tasks { - if task.should_run() { - keys.push(key.clone()); - } - } - - // Commit any due tasks to the database. - for key in &keys { - if let Some(task) = tasks.remove(key) { - let Task { id, is_dm, .. } = task.data; - - let mut channel = PartialChannel { - last_message_id: Some(id.to_string()), - ..Default::default() - }; - - if is_dm { - channel.active = Some(true); - } - - match db.update_channel(key, &channel, vec![]).await { - Ok(_) => info!("Updated last_message_id for {key} to {id}."), - Err(err) => error!("Failed to update last_message_id with {err:?}!"), - } - } - } - - // Clear keys - keys.clear(); - - // Queue incoming tasks. - while let Some(Data { channel, id, is_dm }) = Q.try_pop() { - if let Some(task) = tasks.get_mut(&channel) { - task.data.id = id; - task.delay(); - } else { - tasks.insert(channel, DelayedTask::new(Task { id, is_dm })); - } - } - - // Sleep for an arbitrary amount of time. - async_std::task::sleep(Duration::from_secs(1)).await; - } -} diff --git a/crates/quark/src/tasks/mod.rs b/crates/quark/src/tasks/mod.rs deleted file mode 100644 index 12a5d122..00000000 --- a/crates/quark/src/tasks/mod.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! Semi-important background task management - -use crate::Database; - -use async_std::task; -use std::time::Instant; - -const WORKER_COUNT: usize = 5; - -pub mod ack; -pub mod last_message_id; -pub mod process_embeds; -pub mod web_push; - -/// Spawn background workers -pub async fn start_workers(db: Database, authifier_db: authifier::Database) { - for _ in 0..WORKER_COUNT { - task::spawn(ack::worker(db.clone())); - task::spawn(last_message_id::worker(db.clone())); - task::spawn(process_embeds::worker(db.clone())); - task::spawn(web_push::worker(authifier_db.clone())); - } -} - -/// Task with additional information on when it should run -pub struct DelayedTask { - pub data: T, - last_updated: Instant, - first_seen: Instant, -} - -/// Commit to database every 30 seconds if the task is particularly active. -static EXPIRE_CONSTANT: u64 = 30; - -/// Otherwise, commit to database after 5 seconds. -static SAVE_CONSTANT: u64 = 5; - -impl DelayedTask { - /// Create a new delayed task - pub fn new(data: T) -> Self { - DelayedTask { - data, - last_updated: Instant::now(), - first_seen: Instant::now(), - } - } - - /// Push a task further back in time - pub fn delay(&mut self) { - self.last_updated = Instant::now() - } - - /// Check if a task should run yet - pub fn should_run(&self) -> bool { - self.first_seen.elapsed().as_secs() > EXPIRE_CONSTANT - || self.last_updated.elapsed().as_secs() > SAVE_CONSTANT - } -} diff --git a/crates/quark/src/tasks/process_embeds.rs b/crates/quark/src/tasks/process_embeds.rs deleted file mode 100644 index aef165fb..00000000 --- a/crates/quark/src/tasks/process_embeds.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::util::variables::delta::{JANUARY_URL, MAX_EMBED_COUNT, JANUARY_CONCURRENT_CONNECTIONS}; -use crate::{ - models::{message::AppendMessage, Message}, - types::january::Embed, - Database, -}; - -use async_lock::Semaphore; -use async_std::task::spawn; -use deadqueue::limited::Queue; -use std::sync::Arc; -use once_cell::sync::Lazy; - -/// Task information -#[derive(Debug)] -struct EmbedTask { - /// Channel we're processing the event in - channel: String, - /// ID of the message we're processing - id: String, - /// Content of the message - content: String, -} - -static Q: Lazy> = Lazy::new(|| Queue::new(10_000)); - - -/// Queue a new task for a worker -pub async fn queue(channel: String, id: String, content: String) { - Q.try_push(EmbedTask { - channel, - id, - content, - }) - .ok(); - - info!("Queue is using {} slots from {}.", Q.len(), Q.capacity()); -} - -/// Start a new worker -pub async fn worker(db: Database) { - let semaphore = Arc::new(Semaphore::new(*JANUARY_CONCURRENT_CONNECTIONS)); - - loop { - let task = Q.pop().await; - let db = db.clone(); - let semaphore = semaphore.clone(); - - spawn(async move { - let embeds = Embed::generate(task.content, &JANUARY_URL, *MAX_EMBED_COUNT, semaphore).await; - - if let Ok(embeds) = embeds { - if let Err(err) = Message::append( - &db, - task.id, - task.channel, - AppendMessage { - embeds: Some(embeds), - }, - ) - .await - { - error!("Encountered an error appending to message: {:?}", err); - } - } - }); - } -} diff --git a/crates/quark/src/tasks/web_push.rs b/crates/quark/src/tasks/web_push.rs deleted file mode 100644 index b0c5acb1..00000000 --- a/crates/quark/src/tasks/web_push.rs +++ /dev/null @@ -1,112 +0,0 @@ -use crate::util::variables::delta::VAPID_PRIVATE_KEY; - -use authifier::Database; -use deadqueue::limited::Queue; -use once_cell::sync::Lazy; -use web_push::{ - ContentEncoding, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, WebPushClient, - WebPushMessageBuilder, -}; - -/// Task information -#[derive(Debug)] -struct PushTask { - /// User IDs of the targets that are to receive this notification - recipients: Vec, - /// Raw JSON payload to send to clients - payload: String, -} - -static Q: Lazy> = Lazy::new(|| Queue::new(10_000)); - - -/// Queue a new task for a worker -pub async fn queue(recipients: Vec, payload: String) { - if recipients.is_empty() { - return; - } - - Q.try_push(PushTask { - recipients, - payload, - }) - .ok(); - - info!("Queue is using {} slots from {}.", Q.len(), Q.capacity()); -} - -/// Start a new worker -pub async fn worker(db: Database) { - let client = WebPushClient::new(); - let key = base64::decode_config(VAPID_PRIVATE_KEY.clone(), base64::URL_SAFE) - .expect("valid `VAPID_PRIVATE_KEY`"); - - loop { - let task = Q.pop().await; - - if let Ok(sessions) = db.find_sessions_with_subscription(&task.recipients).await { - for session in sessions { - if let Some(sub) = session.subscription { - let subscription = SubscriptionInfo { - endpoint: sub.endpoint, - keys: SubscriptionKeys { - auth: sub.auth, - p256dh: sub.p256dh, - }, - }; - - match WebPushMessageBuilder::new(&subscription) { - Ok(mut builder) => { - match VapidSignatureBuilder::from_pem( - std::io::Cursor::new(&key), - &subscription, - ) { - Ok(sig_builder) => match sig_builder.build() { - Ok(signature) => { - builder.set_vapid_signature(signature); - builder.set_payload( - ContentEncoding::AesGcm, - task.payload.as_bytes(), - ); - - match builder.build() { - Ok(msg) => match client.send(msg).await { - Ok(_) => { - info!( - "Sent Web Push notification to {:?}.", - session.id - ) - } - Err(err) => { - error!("Hit error sending Web Push! {:?}", err) - } - }, - Err(err) => { - error!( - "Failed to build message for {}! {:?}", - session.user_id, err - ) - } - } - } - Err(err) => error!( - "Failed to build signature for {}! {:?}", - session.user_id, err - ), - }, - Err(err) => error!( - "Failed to create signature builder for {}! {:?}", - session.user_id, err - ), - } - } - Err(err) => error!( - "Invalid subscription information for {}! {:?}", - session.user_id, err - ), - } - } - } - } - } -} diff --git a/crates/quark/src/traits/admin/stats.rs b/crates/quark/src/traits/admin/stats.rs deleted file mode 100644 index 99e10819..00000000 --- a/crates/quark/src/traits/admin/stats.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::{models::stats::Stats, Result}; - -#[async_trait] -pub trait AbstractStats: Sync + Send { - async fn generate_stats(&self) -> Result; -} diff --git a/crates/quark/src/traits/channels/channel.rs b/crates/quark/src/traits/channels/channel.rs deleted file mode 100644 index 895ec981..00000000 --- a/crates/quark/src/traits/channels/channel.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::models::channel::{Channel, FieldsChannel, PartialChannel}; -use crate::{OverrideField, Result}; - -#[async_trait] -pub trait AbstractChannel: Sync + Send { - /// Fetch a channel by its id - async fn fetch_channel(&self, id: &str) -> Result; - - /// Fetch channels by their ids - async fn fetch_channels<'a>(&self, ids: &'a [String]) -> Result>; - - /// Insert a new channel into the database - async fn insert_channel(&self, channel: &Channel) -> Result<()>; - - /// Update an existing channel using some data - async fn update_channel( - &self, - id: &str, - channel: &PartialChannel, - remove: Vec, - ) -> Result<()>; - - /// Delete a channel by its id - /// - /// This will also delete all associated messages and files. - async fn delete_channel(&self, channel: &Channel) -> Result<()>; - - /// Find all direct messages that a user is involved in - /// - /// Returns group DMs, any DMs marked as "active" and saved messages. - async fn find_direct_messages(&self, user_id: &str) -> Result>; - - /// Find a direct message channel between two users - async fn find_direct_message_channel(&self, user_a: &str, user_b: &str) -> Result; - - /// Find a saved message channel owned by a user - async fn find_saved_messages_channel(&self, user_id: &str) -> Result; - - /// Add user to a group - async fn add_user_to_group(&self, channel: &str, user: &str) -> Result<()>; - - /// Remove a user from a group - async fn remove_user_from_group(&self, channel: &str, user: &str) -> Result<()>; - - /// Set role permission for a channel - /// TODO: may want to refactor to just use normal updates - async fn set_channel_role_permission( - &self, - channel: &str, - role: &str, - permissions: OverrideField, - ) -> Result<()>; -} diff --git a/crates/quark/src/traits/channels/channel_invite.rs b/crates/quark/src/traits/channels/channel_invite.rs deleted file mode 100644 index e571233a..00000000 --- a/crates/quark/src/traits/channels/channel_invite.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::models::Invite; -use crate::Result; - -#[async_trait] -pub trait AbstractChannelInvite: Sync + Send { - /// Fetch an invite by its id - async fn fetch_invite(&self, code: &str) -> Result; - - /// Insert a new invite into the database - async fn insert_invite(&self, invite: &Invite) -> Result<()>; - - /// Delete an invite by its id - async fn delete_invite(&self, code: &str) -> Result<()>; - - /// Fetch all invites for a server - async fn fetch_invites_for_server(&self, server: &str) -> Result>; -} diff --git a/crates/quark/src/traits/channels/channel_unread.rs b/crates/quark/src/traits/channels/channel_unread.rs deleted file mode 100644 index 24ba8bed..00000000 --- a/crates/quark/src/traits/channels/channel_unread.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::models::channel_unread::ChannelUnread; -use crate::Result; - -#[async_trait] -pub trait AbstractChannelUnread: Sync + Send { - /// Acknowledge a message. - async fn acknowledge_message(&self, channel: &str, user: &str, message: &str) -> Result<()>; - - /// Acknowledge many channels. - async fn acknowledge_channels(&self, user: &str, channels: &[String]) -> Result<()>; - - /// Add a mention. - async fn add_mention_to_unread<'a>( - &self, - channel: &str, - user: &str, - ids: &[String], - ) -> Result<()>; - - /// Fetch all channel unreads for a user. - async fn fetch_unreads(&self, user: &str) -> Result>; -} diff --git a/crates/quark/src/traits/channels/message.rs b/crates/quark/src/traits/channels/message.rs deleted file mode 100644 index c408de73..00000000 --- a/crates/quark/src/traits/channels/message.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::models::message::{AppendMessage, Message, MessageQuery, PartialMessage}; -use crate::Result; - -#[async_trait] -pub trait AbstractMessage: Sync + Send { - /// Fetch a message by its id - async fn fetch_message(&self, id: &str) -> Result; - - /// Insert a new message into the database - async fn insert_message(&self, message: &Message) -> Result<()>; - - /// Update a given message with new information - async fn update_message(&self, id: &str, message: &PartialMessage) -> Result<()>; - - /// Append information to a given message - async fn append_message(&self, id: &str, append: &AppendMessage) -> Result<()>; - - /// Delete a message from the database by its id - async fn delete_message(&self, id: &str) -> Result<()>; - - /// Delete messages from a channel by their ids and corresponding channel id - async fn delete_messages(&self, channel: &str, ids: Vec) -> Result<()>; - - /// Fetch multiple messages by given query - async fn fetch_messages(&self, query: MessageQuery) -> Result>; - - /// Add a new reaction to a message - async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>; - - /// Remove a reaction from a message - async fn remove_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>; - - /// Remove reaction from a message - async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()>; -} diff --git a/crates/quark/src/traits/media/attachment.rs b/crates/quark/src/traits/media/attachment.rs deleted file mode 100644 index 36918635..00000000 --- a/crates/quark/src/traits/media/attachment.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::models::attachment::File; -use crate::Result; - -#[async_trait] -pub trait AbstractAttachment: Sync + Send { - /// Find an attachment by its details and mark it as used by a given parent. - async fn find_and_use_attachment( - &self, - id: &str, - tag: &str, - parent_type: &str, - parent_id: &str, - ) -> Result; - - /// Insert attachment into database. - async fn insert_attachment(&self, attachment: &File) -> Result<()>; - - /// Mark an attachment as having been reported. - async fn mark_attachment_as_reported(&self, id: &str) -> Result<()>; - - /// Mark an attachment as having been deleted. - async fn mark_attachment_as_deleted(&self, id: &str) -> Result<()>; - - /// Mark multiple attachments as having been deleted. - async fn mark_attachments_as_deleted(&self, ids: &[String]) -> Result<()>; -} diff --git a/crates/quark/src/traits/media/emoji.rs b/crates/quark/src/traits/media/emoji.rs deleted file mode 100644 index fc873e34..00000000 --- a/crates/quark/src/traits/media/emoji.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::models::Emoji; -use crate::Result; - -#[async_trait] -pub trait AbstractEmoji: Sync + Send { - /// Fetch an emoji by its id - async fn fetch_emoji(&self, id: &str) -> Result; - - /// Fetch emoji by their parent id - async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result>; - - /// Fetch emoji by their parent ids - async fn fetch_emoji_by_parent_ids(&self, parent_ids: &[String]) -> Result>; - - /// Insert emoji into database. - async fn insert_emoji(&self, emoji: &Emoji) -> Result<()>; - - /// Detach an emoji by its id - async fn detach_emoji(&self, emoji: &Emoji) -> Result<()>; -} diff --git a/crates/quark/src/traits/mod.rs b/crates/quark/src/traits/mod.rs deleted file mode 100644 index bda97615..00000000 --- a/crates/quark/src/traits/mod.rs +++ /dev/null @@ -1,71 +0,0 @@ -mod admin { - pub mod stats; -} - -mod media { - pub mod attachment; - pub mod emoji; -} - -mod channels { - pub mod channel; - pub mod channel_invite; - pub mod channel_unread; - pub mod message; -} - -mod servers { - pub mod server; - pub mod server_ban; - pub mod server_member; -} - -mod users { - pub mod user; - pub mod user_settings; -} - -mod safety { - pub mod report; - pub mod snapshot; -} - -pub use admin::stats::AbstractStats; - -pub use media::attachment::AbstractAttachment; -pub use media::emoji::AbstractEmoji; - -pub use channels::channel::AbstractChannel; -pub use channels::channel_invite::AbstractChannelInvite; -pub use channels::channel_unread::AbstractChannelUnread; -pub use channels::message::AbstractMessage; - -pub use servers::server::AbstractServer; -pub use servers::server_ban::AbstractServerBan; -pub use servers::server_member::AbstractServerMember; - -pub use users::user::AbstractUser; -pub use users::user_settings::AbstractUserSettings; - -pub use safety::report::AbstractReport; -pub use safety::snapshot::AbstractSnapshot; - -pub trait AbstractDatabase: - Sync - + Send - + AbstractStats - + AbstractAttachment - + AbstractEmoji - + AbstractChannel - + AbstractChannelInvite - + AbstractChannelUnread - + AbstractMessage - + AbstractServer - + AbstractServerBan - + AbstractServerMember - + AbstractUser - + AbstractUserSettings - + AbstractReport - + AbstractSnapshot -{ -} diff --git a/crates/quark/src/traits/safety/report.rs b/crates/quark/src/traits/safety/report.rs deleted file mode 100644 index 34752613..00000000 --- a/crates/quark/src/traits/safety/report.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::models::report::PartialReport; -use crate::models::Report; -use crate::Result; - -#[async_trait] -pub trait AbstractReport: Sync + Send { - /// Insert a new report into the database - async fn insert_report(&self, report: &Report) -> Result<()>; - - /// Update a given report with new information - async fn update_report(&self, id: &str, message: &PartialReport) -> Result<()>; - - /// Fetch report - async fn fetch_report(&self, report_id: &str) -> Result; - - /// Fetch reports - async fn fetch_reports(&self) -> Result>; -} diff --git a/crates/quark/src/traits/safety/snapshot.rs b/crates/quark/src/traits/safety/snapshot.rs deleted file mode 100644 index 516ef87d..00000000 --- a/crates/quark/src/traits/safety/snapshot.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::models::Snapshot; -use crate::Result; - -#[async_trait] -pub trait AbstractSnapshot: Sync + Send { - /// Insert a new snapshot into the database - async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()>; - - /// Fetch a snapshots by a report's id - async fn fetch_snapshots(&self, report_id: &str) -> Result>; -} diff --git a/crates/quark/src/traits/servers/server.rs b/crates/quark/src/traits/servers/server.rs deleted file mode 100644 index 94a34353..00000000 --- a/crates/quark/src/traits/servers/server.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::models::server::{FieldsRole, FieldsServer, PartialRole, PartialServer, Role, Server}; -use crate::Result; - -#[async_trait] -pub trait AbstractServer: Sync + Send { - /// Fetch a server by its id - async fn fetch_server(&self, id: &str) -> Result; - - /// Fetch a servers by their ids - async fn fetch_servers<'a>(&self, ids: &'a [String]) -> Result>; - - /// Insert a new server into database - async fn insert_server(&self, server: &Server) -> Result<()>; - - /// Update a server with new information - async fn update_server( - &self, - id: &str, - server: &PartialServer, - remove: Vec, - ) -> Result<()>; - - /// Delete a server by its id - async fn delete_server(&self, server: &Server) -> Result<()>; - - /// Insert a new role into server object - async fn insert_role(&self, server_id: &str, role_id: &str, role: &Role) -> Result<()>; - - /// Update an existing role on a server - async fn update_role( - &self, - server_id: &str, - role_id: &str, - role: &PartialRole, - remove: Vec, - ) -> Result<()>; - - /// Delete a role from a server - /// - /// Also updates channels and members. - async fn delete_role(&self, server_id: &str, role_id: &str) -> Result<()>; -} diff --git a/crates/quark/src/traits/servers/server_ban.rs b/crates/quark/src/traits/servers/server_ban.rs deleted file mode 100644 index ad25a295..00000000 --- a/crates/quark/src/traits/servers/server_ban.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::models::server_member::MemberCompositeKey; -use crate::models::ServerBan; -use crate::Result; - -#[async_trait] -pub trait AbstractServerBan: Sync + Send { - /// Fetch a server ban by server and user id - async fn fetch_ban(&self, server: &str, user: &str) -> Result; - - /// Fetch all bans in a server - async fn fetch_bans(&self, server: &str) -> Result>; - - /// Insert new ban into database - async fn insert_ban(&self, ban: &ServerBan) -> Result<()>; - - /// Delete a ban from the database - async fn delete_ban(&self, id: &MemberCompositeKey) -> Result<()>; -} diff --git a/crates/quark/src/traits/servers/server_member.rs b/crates/quark/src/traits/servers/server_member.rs deleted file mode 100644 index 419663a1..00000000 --- a/crates/quark/src/traits/servers/server_member.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::models::server_member::{ - FieldsMember, Member, MemberCompositeKey, MemberWithRoles, PartialMember, -}; -use crate::Result; - -#[async_trait] -pub trait AbstractServerMember: Sync + Send { - /// Fetch a server member by their id - async fn fetch_member(&self, server: &str, user: &str) -> Result; - - async fn fetch_member_with_roles(&self, server: &str, user: &str) -> Result; - - /// Insert a new server member into the database - async fn insert_member(&self, member: &Member) -> Result<()>; - - /// Update information for a server member - async fn update_member( - &self, - id: &MemberCompositeKey, - member: &PartialMember, - remove: Vec, - ) -> Result<()>; - - /// Delete a server member by their id - async fn delete_member(&self, id: &MemberCompositeKey) -> Result<()>; - - /// Fetch all members in a server - async fn fetch_all_members<'a>(&self, server: &str) -> Result>; - - /// Fetch all memberships for a user - async fn fetch_all_memberships<'a>(&self, user: &str) -> Result>; - - /// Fetch multiple members by their ids - async fn fetch_members<'a>(&self, server: &str, ids: &'a [String]) -> Result>; - - /// Fetch member count of a server - async fn fetch_member_count(&self, server: &str) -> Result; - - /// Fetch server count of a user - async fn fetch_server_count(&self, user: &str) -> Result; -} diff --git a/crates/quark/src/traits/users/user.rs b/crates/quark/src/traits/users/user.rs deleted file mode 100644 index b0b5f8c8..00000000 --- a/crates/quark/src/traits/users/user.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus, User}; -use crate::Result; - -#[async_trait] -pub trait AbstractUser: Sync + Send { - /// Fetch a user from the database - async fn fetch_user(&self, id: &str) -> Result; - - /// Fetch a user from the database by their username - async fn fetch_user_by_username(&self, username: &str, discriminator: &str) -> Result; - - /// Fetch a user from the database by their session token - async fn fetch_user_by_token(&self, token: &str) -> Result; - - /// Insert a new user into the database - async fn insert_user(&self, user: &User) -> Result<()>; - - /// Update a user by their id given some data - async fn update_user( - &self, - id: &str, - user: &PartialUser, - remove: Vec, - ) -> Result<()>; - - /// Delete a user by their id - async fn delete_user(&self, id: &str) -> Result<()>; - - /// Fetch multiple users by their ids - async fn fetch_users<'a>(&self, ids: &'a [String]) -> Result>; - - /// Fetch all discriminators in use for a username - async fn fetch_discriminators_in_use(&self, username: &str) -> Result>; - - /// Fetch ids of users that both users are friends with - async fn fetch_mutual_user_ids(&self, user_a: &str, user_b: &str) -> Result>; - - /// Fetch ids of channels that both users are in - async fn fetch_mutual_channel_ids(&self, user_a: &str, user_b: &str) -> Result>; - - /// Fetch ids of servers that both users share - async fn fetch_mutual_server_ids(&self, user_a: &str, user_b: &str) -> Result>; - - /// Set relationship with another user - /// - /// This should use pull_relationship if relationship is None. - async fn set_relationship( - &self, - user_id: &str, - target_id: &str, - relationship: &RelationshipStatus, - ) -> Result<()>; - - /// Remove relationship with another user - async fn pull_relationship(&self, user_id: &str, target_id: &str) -> Result<()>; -} diff --git a/crates/quark/src/traits/users/user_settings.rs b/crates/quark/src/traits/users/user_settings.rs deleted file mode 100644 index c6634cfd..00000000 --- a/crates/quark/src/traits/users/user_settings.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::models::UserSettings; -use crate::Result; - -#[async_trait] -pub trait AbstractUserSettings: Sync + Send { - /// Fetch a subset of user settings - async fn fetch_user_settings(&'_ self, id: &str, filter: &'_ [String]) -> Result; - - /// Update a subset of user settings - async fn set_user_settings(&self, id: &str, settings: &UserSettings) -> Result<()>; - - /// Delete all user settings - async fn delete_user_settings(&self, id: &str) -> Result<()>; -} diff --git a/crates/quark/src/types/january.rs b/crates/quark/src/types/january.rs deleted file mode 100644 index e42f3b23..00000000 --- a/crates/quark/src/types/january.rs +++ /dev/null @@ -1,272 +0,0 @@ -use async_lock::Semaphore; -use async_std::task::spawn; -use futures::future::join_all; -use linkify::{LinkFinder, LinkKind}; -use once_cell::sync::Lazy; -use regex::Regex; -use serde::{Deserialize, Serialize}; -use std::{collections::HashSet, sync::Arc}; - -use crate::{models::attachment::File, Error, Result}; - -/// Image positioning and size -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub enum ImageSize { - /// Show large preview at the bottom of the embed - Large, - /// Show small preview to the side of the embed - Preview, -} - -/// Image -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub struct Image { - /// URL to the original image - pub url: String, - /// Width of the image - pub width: isize, - /// Height of the image - pub height: isize, - /// Positioning and size - pub size: ImageSize, -} - -/// Video -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub struct Video { - /// URL to the original video - pub url: String, - /// Width of the video - pub width: isize, - /// Height of the video - pub height: isize, -} - -/// Type of remote Twitch content -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub enum TwitchType { - Channel, - Video, - Clip, -} - -/// Type of remote Lightspeed.tv content -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub enum LightspeedType { - Channel, -} - -/// Type of remote Bandcamp content -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub enum BandcampType { - Album, - Track, -} - -/// Information about special remote content -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -#[serde(tag = "type")] -pub enum Special { - /// No remote content - None, - /// Content hint that this contains a GIF - /// - /// Use metadata to find video or image to play - GIF, - /// YouTube video - YouTube { - id: String, - - #[serde(skip_serializing_if = "Option::is_none")] - timestamp: Option, - }, - /// Lightspeed.tv stream - Lightspeed { - content_type: LightspeedType, - id: String, - }, - /// Twitch stream or clip - Twitch { - content_type: TwitchType, - id: String, - }, - /// Spotify track - Spotify { content_type: String, id: String }, - /// Soundcloud track - Soundcloud, - /// Bandcamp track - Bandcamp { - content_type: BandcampType, - id: String, - }, - /// Streamable Video - Streamable { id: String }, -} - -/// Website metadata -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] -pub struct Metadata { - /// Direct URL to web page - #[serde(skip_serializing_if = "Option::is_none")] - url: Option, - /// Original direct URL - #[serde(skip_serializing_if = "Option::is_none")] - original_url: Option, - /// Remote content - #[serde(skip_serializing_if = "Option::is_none")] - special: Option, - - /// Title of website - #[serde(skip_serializing_if = "Option::is_none")] - title: Option, - /// Description of website - #[serde(skip_serializing_if = "Option::is_none")] - description: Option, - /// Embedded image - #[serde(skip_serializing_if = "Option::is_none")] - image: Option, - /// Embedded video - #[serde(skip_serializing_if = "Option::is_none")] - video: Option