From 3065e153d5ab40a38aa6539cd34e70be6d43341d Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Mon, 8 Apr 2024 21:56:35 +0100 Subject: [PATCH] refactor: delete quark crate (closes #239) --- Cargo.lock | 510 +---- Cargo.toml | 2 +- crates/bonfire/Cargo.toml | 4 +- crates/core/config/Cargo.toml | 2 +- crates/core/database/Cargo.toml | 12 +- crates/core/models/Cargo.toml | 6 +- crates/core/permissions/Cargo.toml | 4 +- crates/core/presence/Cargo.toml | 2 +- crates/core/result/Cargo.toml | 4 +- crates/delta/Cargo.toml | 2 +- crates/delta/src/util/test.rs | 1 - crates/quark/.gitignore | 14 - crates/quark/Cargo.toml | 100 - crates/quark/LICENSE | 1 - crates/quark/assets/emojis.txt | 1850 ----------------- crates/quark/assets/templates/deletion.html | 138 -- .../assets/templates/deletion.original.html | 30 - crates/quark/assets/templates/deletion.txt | 7 - crates/quark/assets/templates/reset.html | 138 -- .../assets/templates/reset.original.html | 26 - crates/quark/assets/templates/reset.txt | 7 - crates/quark/assets/templates/verify.html | 138 -- .../assets/templates/verify.original.html | 27 - crates/quark/assets/templates/verify.txt | 8 - crates/quark/assets/user/1.png | Bin 6890 -> 0 bytes crates/quark/assets/user/2.png | Bin 6402 -> 0 bytes crates/quark/assets/user/3.png | Bin 6569 -> 0 bytes crates/quark/assets/user/4.png | Bin 6405 -> 0 bytes crates/quark/assets/user/5.png | Bin 6800 -> 0 bytes crates/quark/assets/user/6.png | Bin 6785 -> 0 bytes crates/quark/assets/user/7.png | Bin 5653 -> 0 bytes crates/quark/src/database.rs | 85 - crates/quark/src/events/client.rs | 237 --- crates/quark/src/events/impl.rs | 589 ------ crates/quark/src/events/mod.rs | 4 - crates/quark/src/events/server.rs | 12 - crates/quark/src/events/state.rs | 165 -- crates/quark/src/impl/dummy/admin/stats.rs | 10 - .../quark/src/impl/dummy/channels/channel.rs | 84 - .../src/impl/dummy/channels/channel_invite.rs | 30 - .../src/impl/dummy/channels/channel_unread.rs | 31 - .../quark/src/impl/dummy/channels/message.rs | 65 - .../quark/src/impl/dummy/media/attachment.rs | 47 - crates/quark/src/impl/dummy/media/emoji.rs | 42 - crates/quark/src/impl/dummy/mod.rs | 38 - crates/quark/src/impl/dummy/safety/report.rs | 25 - .../quark/src/impl/dummy/safety/snapshot.rs | 16 - crates/quark/src/impl/dummy/servers/server.rs | 78 - .../src/impl/dummy/servers/server_ban.rs | 32 - .../src/impl/dummy/servers/server_member.rs | 56 - crates/quark/src/impl/dummy/users/user.rs | 79 - .../src/impl/dummy/users/user_settings.rs | 25 - .../src/impl/generic/channels/channel.rs | 566 ----- .../impl/generic/channels/channel_invite.rs | 72 - .../impl/generic/channels/channel_unread.rs | 1 - .../src/impl/generic/channels/message.rs | 494 ----- .../src/impl/generic/media/attachment.rs | 38 - crates/quark/src/impl/generic/media/emoji.rs | 56 - crates/quark/src/impl/generic/mod.rs | 29 - .../quark/src/impl/generic/safety/report.rs | 30 - .../quark/src/impl/generic/safety/snapshot.rs | 88 - .../quark/src/impl/generic/servers/server.rs | 330 --- .../src/impl/generic/servers/server_ban.rs | 1 - .../src/impl/generic/servers/server_member.rs | 95 - crates/quark/src/impl/generic/users/user.rs | 510 ----- .../src/impl/generic/users/user_settings.rs | 22 - crates/quark/src/impl/mod.rs | 10 - crates/quark/src/impl/mongo/admin/stats.rs | 90 - .../quark/src/impl/mongo/channels/channel.rs | 317 --- .../src/impl/mongo/channels/channel_invite.rs | 31 - .../src/impl/mongo/channels/channel_unread.rs | 120 -- .../quark/src/impl/mongo/channels/message.rs | 343 --- .../quark/src/impl/mongo/media/attachment.rs | 148 -- crates/quark/src/impl/mongo/media/emoji.rs | 69 - crates/quark/src/impl/mongo/mod.rs | 263 --- crates/quark/src/impl/mongo/safety/report.rs | 28 - .../quark/src/impl/mongo/safety/snapshot.rs | 23 - crates/quark/src/impl/mongo/servers/server.rs | 242 --- .../src/impl/mongo/servers/server_ban.rs | 47 - .../src/impl/mongo/servers/server_member.rs | 146 -- crates/quark/src/impl/mongo/users/user.rs | 343 --- .../src/impl/mongo/users/user_settings.rs | 62 - crates/quark/src/impl/rocket.rs | 73 - crates/quark/src/lib.rs | 58 - crates/quark/src/models/admin/simple.rs | 8 - crates/quark/src/models/admin/stats.rs | 122 -- crates/quark/src/models/channels/channel.rs | 169 -- .../src/models/channels/channel_invite.rs | 32 - .../src/models/channels/channel_unread.rs | 25 - crates/quark/src/models/channels/message.rs | 285 --- crates/quark/src/models/media/attachment.rs | 56 - crates/quark/src/models/media/emoji.rs | 34 - crates/quark/src/models/mod.rs | 54 - crates/quark/src/models/safety/report.rs | 158 -- crates/quark/src/models/safety/snapshot.rs | 54 - crates/quark/src/models/servers/server.rs | 149 -- crates/quark/src/models/servers/server_ban.rs | 13 - .../quark/src/models/servers/server_member.rs | 87 - crates/quark/src/models/users/bot.rs | 63 - crates/quark/src/models/users/user.rs | 191 -- .../quark/src/models/users/user_settings.rs | 6 - crates/quark/src/permissions/defn/mod.rs | 108 - .../quark/src/permissions/defn/permission.rs | 157 -- crates/quark/src/permissions/defn/user.rs | 29 - crates/quark/src/permissions/impl/mod.rs | 2 - .../quark/src/permissions/impl/permission.rs | 232 --- crates/quark/src/permissions/impl/user.rs | 98 - crates/quark/src/permissions/mod.rs | 171 -- crates/quark/src/tasks/ack.rs | 121 -- crates/quark/src/tasks/last_message_id.rs | 87 - crates/quark/src/tasks/mod.rs | 58 - crates/quark/src/tasks/process_embeds.rs | 68 - crates/quark/src/tasks/web_push.rs | 112 - crates/quark/src/traits/admin/stats.rs | 6 - crates/quark/src/traits/channels/channel.rs | 53 - .../src/traits/channels/channel_invite.rs | 17 - .../src/traits/channels/channel_unread.rs | 22 - crates/quark/src/traits/channels/message.rs | 35 - crates/quark/src/traits/media/attachment.rs | 26 - crates/quark/src/traits/media/emoji.rs | 20 - crates/quark/src/traits/mod.rs | 71 - crates/quark/src/traits/safety/report.rs | 18 - crates/quark/src/traits/safety/snapshot.rs | 11 - crates/quark/src/traits/servers/server.rs | 42 - crates/quark/src/traits/servers/server_ban.rs | 18 - .../quark/src/traits/servers/server_member.rs | 41 - crates/quark/src/traits/users/user.rs | 56 - .../quark/src/traits/users/user_settings.rs | 14 - crates/quark/src/types/january.rs | 272 --- crates/quark/src/types/mod.rs | 2 - crates/quark/src/types/push.rs | 101 - crates/quark/src/util/authifier.rs | 79 - crates/quark/src/util/log.rs | 38 - crates/quark/src/util/manipulation.rs | 13 - crates/quark/src/util/mod.rs | 9 - crates/quark/src/util/pfp.rs | 13 - crates/quark/src/util/ref.rs | 111 - crates/quark/src/util/regex.rs | 21 - crates/quark/src/util/result.rs | 281 --- crates/quark/src/util/value.rs | 38 - crates/quark/src/util/variables/delta.rs | 171 -- crates/quark/src/util/variables/mod.rs | 1 - crates/quark/src/web/mod.rs | 5 - 143 files changed, 114 insertions(+), 13768 deletions(-) delete mode 100644 crates/quark/.gitignore delete mode 100644 crates/quark/Cargo.toml delete mode 120000 crates/quark/LICENSE delete mode 100644 crates/quark/assets/emojis.txt delete mode 100644 crates/quark/assets/templates/deletion.html delete mode 100644 crates/quark/assets/templates/deletion.original.html delete mode 100644 crates/quark/assets/templates/deletion.txt delete mode 100644 crates/quark/assets/templates/reset.html delete mode 100644 crates/quark/assets/templates/reset.original.html delete mode 100644 crates/quark/assets/templates/reset.txt delete mode 100644 crates/quark/assets/templates/verify.html delete mode 100644 crates/quark/assets/templates/verify.original.html delete mode 100644 crates/quark/assets/templates/verify.txt delete mode 100644 crates/quark/assets/user/1.png delete mode 100644 crates/quark/assets/user/2.png delete mode 100644 crates/quark/assets/user/3.png delete mode 100644 crates/quark/assets/user/4.png delete mode 100644 crates/quark/assets/user/5.png delete mode 100644 crates/quark/assets/user/6.png delete mode 100644 crates/quark/assets/user/7.png delete mode 100644 crates/quark/src/database.rs delete mode 100644 crates/quark/src/events/client.rs delete mode 100644 crates/quark/src/events/impl.rs delete mode 100644 crates/quark/src/events/mod.rs delete mode 100644 crates/quark/src/events/server.rs delete mode 100644 crates/quark/src/events/state.rs delete mode 100644 crates/quark/src/impl/dummy/admin/stats.rs delete mode 100644 crates/quark/src/impl/dummy/channels/channel.rs delete mode 100644 crates/quark/src/impl/dummy/channels/channel_invite.rs delete mode 100644 crates/quark/src/impl/dummy/channels/channel_unread.rs delete mode 100644 crates/quark/src/impl/dummy/channels/message.rs delete mode 100644 crates/quark/src/impl/dummy/media/attachment.rs delete mode 100644 crates/quark/src/impl/dummy/media/emoji.rs delete mode 100644 crates/quark/src/impl/dummy/mod.rs delete mode 100644 crates/quark/src/impl/dummy/safety/report.rs delete mode 100644 crates/quark/src/impl/dummy/safety/snapshot.rs delete mode 100644 crates/quark/src/impl/dummy/servers/server.rs delete mode 100644 crates/quark/src/impl/dummy/servers/server_ban.rs delete mode 100644 crates/quark/src/impl/dummy/servers/server_member.rs delete mode 100644 crates/quark/src/impl/dummy/users/user.rs delete mode 100644 crates/quark/src/impl/dummy/users/user_settings.rs delete mode 100644 crates/quark/src/impl/generic/channels/channel.rs delete mode 100644 crates/quark/src/impl/generic/channels/channel_invite.rs delete mode 100644 crates/quark/src/impl/generic/channels/channel_unread.rs delete mode 100644 crates/quark/src/impl/generic/channels/message.rs delete mode 100644 crates/quark/src/impl/generic/media/attachment.rs delete mode 100644 crates/quark/src/impl/generic/media/emoji.rs delete mode 100644 crates/quark/src/impl/generic/mod.rs delete mode 100644 crates/quark/src/impl/generic/safety/report.rs delete mode 100644 crates/quark/src/impl/generic/safety/snapshot.rs delete mode 100644 crates/quark/src/impl/generic/servers/server.rs delete mode 100644 crates/quark/src/impl/generic/servers/server_ban.rs delete mode 100644 crates/quark/src/impl/generic/servers/server_member.rs delete mode 100644 crates/quark/src/impl/generic/users/user.rs delete mode 100644 crates/quark/src/impl/generic/users/user_settings.rs delete mode 100644 crates/quark/src/impl/mod.rs delete mode 100644 crates/quark/src/impl/mongo/admin/stats.rs delete mode 100644 crates/quark/src/impl/mongo/channels/channel.rs delete mode 100644 crates/quark/src/impl/mongo/channels/channel_invite.rs delete mode 100644 crates/quark/src/impl/mongo/channels/channel_unread.rs delete mode 100644 crates/quark/src/impl/mongo/channels/message.rs delete mode 100644 crates/quark/src/impl/mongo/media/attachment.rs delete mode 100644 crates/quark/src/impl/mongo/media/emoji.rs delete mode 100644 crates/quark/src/impl/mongo/mod.rs delete mode 100644 crates/quark/src/impl/mongo/safety/report.rs delete mode 100644 crates/quark/src/impl/mongo/safety/snapshot.rs delete mode 100644 crates/quark/src/impl/mongo/servers/server.rs delete mode 100644 crates/quark/src/impl/mongo/servers/server_ban.rs delete mode 100644 crates/quark/src/impl/mongo/servers/server_member.rs delete mode 100644 crates/quark/src/impl/mongo/users/user.rs delete mode 100644 crates/quark/src/impl/mongo/users/user_settings.rs delete mode 100644 crates/quark/src/impl/rocket.rs delete mode 100644 crates/quark/src/lib.rs delete mode 100644 crates/quark/src/models/admin/simple.rs delete mode 100644 crates/quark/src/models/admin/stats.rs delete mode 100644 crates/quark/src/models/channels/channel.rs delete mode 100644 crates/quark/src/models/channels/channel_invite.rs delete mode 100644 crates/quark/src/models/channels/channel_unread.rs delete mode 100644 crates/quark/src/models/channels/message.rs delete mode 100644 crates/quark/src/models/media/attachment.rs delete mode 100644 crates/quark/src/models/media/emoji.rs delete mode 100644 crates/quark/src/models/mod.rs delete mode 100644 crates/quark/src/models/safety/report.rs delete mode 100644 crates/quark/src/models/safety/snapshot.rs delete mode 100644 crates/quark/src/models/servers/server.rs delete mode 100644 crates/quark/src/models/servers/server_ban.rs delete mode 100644 crates/quark/src/models/servers/server_member.rs delete mode 100644 crates/quark/src/models/users/bot.rs delete mode 100644 crates/quark/src/models/users/user.rs delete mode 100644 crates/quark/src/models/users/user_settings.rs delete mode 100644 crates/quark/src/permissions/defn/mod.rs delete mode 100644 crates/quark/src/permissions/defn/permission.rs delete mode 100644 crates/quark/src/permissions/defn/user.rs delete mode 100644 crates/quark/src/permissions/impl/mod.rs delete mode 100644 crates/quark/src/permissions/impl/permission.rs delete mode 100644 crates/quark/src/permissions/impl/user.rs delete mode 100644 crates/quark/src/permissions/mod.rs delete mode 100644 crates/quark/src/tasks/ack.rs delete mode 100644 crates/quark/src/tasks/last_message_id.rs delete mode 100644 crates/quark/src/tasks/mod.rs delete mode 100644 crates/quark/src/tasks/process_embeds.rs delete mode 100644 crates/quark/src/tasks/web_push.rs delete mode 100644 crates/quark/src/traits/admin/stats.rs delete mode 100644 crates/quark/src/traits/channels/channel.rs delete mode 100644 crates/quark/src/traits/channels/channel_invite.rs delete mode 100644 crates/quark/src/traits/channels/channel_unread.rs delete mode 100644 crates/quark/src/traits/channels/message.rs delete mode 100644 crates/quark/src/traits/media/attachment.rs delete mode 100644 crates/quark/src/traits/media/emoji.rs delete mode 100644 crates/quark/src/traits/mod.rs delete mode 100644 crates/quark/src/traits/safety/report.rs delete mode 100644 crates/quark/src/traits/safety/snapshot.rs delete mode 100644 crates/quark/src/traits/servers/server.rs delete mode 100644 crates/quark/src/traits/servers/server_ban.rs delete mode 100644 crates/quark/src/traits/servers/server_member.rs delete mode 100644 crates/quark/src/traits/users/user.rs delete mode 100644 crates/quark/src/traits/users/user_settings.rs delete mode 100644 crates/quark/src/types/january.rs delete mode 100644 crates/quark/src/types/mod.rs delete mode 100644 crates/quark/src/types/push.rs delete mode 100644 crates/quark/src/util/authifier.rs delete mode 100644 crates/quark/src/util/log.rs delete mode 100644 crates/quark/src/util/manipulation.rs delete mode 100644 crates/quark/src/util/mod.rs delete mode 100644 crates/quark/src/util/pfp.rs delete mode 100644 crates/quark/src/util/ref.rs delete mode 100644 crates/quark/src/util/regex.rs delete mode 100644 crates/quark/src/util/result.rs delete mode 100644 crates/quark/src/util/value.rs delete mode 100644 crates/quark/src/util/variables/delta.rs delete mode 100644 crates/quark/src/util/variables/mod.rs delete mode 100644 crates/quark/src/web/mod.rs 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 88d2e28bf499553944843ead1682bbc5ef1c3089..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6890 zcmeHM`9IWa-@j&zU6>Gxu@#ldmaM~+VUlH1B6nFb+LN+3gBiKYzC1@|DHN6Ls&k0R zQdv)yGa(bl7RNe_VvI7(bB*WRuh;Vj+&`Y5=DNN<*XQ~y@9VRPKkiJ}BC%Zp0I48fJ_On;*^e?w{h z{rlx5AK6a0x}n6Fvvoc7>nR1s3_h5S&goiSi{-PA{Kmp|HJFj18Tf)G7S~f zuih1cV?&8ouIieHvF$?G!^=z6-a<>Z0C%O%>!7H#C6i&|;K10YN05pw(xGV1ulL1< z2k&$ZTd)+n-BosmQFl|Tc(D4S#m+?1kinKyaFnW1;&A{xYrnZhV{&j6OIXh4))+Nsr&54)m>5F#A z$fu4S2`=|?!YZ@OkO1R;$~JQPxZ|Ni`Bs5`U|kkcG3$^{bjcy(1M~*5#1*Wm>>%4=FXR)=c>Ze)s!TQnILUOd0+dZ1h$5^1dcA z=r=Te=1~_~Y!+n^0L@q!Q5#qEifjKieqivUQ}vGYsl3$orEa2(OvFmonj}sPNc5|i z$_`%awtHQlOP0Sdx-k~r)%lRYU$oJ{F;Kwvio2Wd8tcmo88v6Dg!n;SU4x%P?EIkl ze@Sg@aA)?%J<&(f@_%)3c)9+`!9~p)@|eU0E&5zrACqC!1@942i+pV4JqHT>QFJ)wq=C}>13zHY5tayK%L%h-gZG`Hzp}>Z< z<<~Z|$;ofRCfdSch1KPdw0#0~$>Uo-uCr32tgF1E%(MxqEsH~8Wg=<90qM-a;=L_; z^-5=`t1&s&^m30gi4-c2)JsTxjuFnyQUEKZq31O0rn)JoWnLRAHBEP#6mb8_FjyUp ziK}(SqAe8=jk&IB7TXF&c-r|RM6AS}Y)gIw#J2&jfu9IpTTyGY&ZI1$bgh(Bv`i

m?GW{R&)>RK_gnr)L4@~mh 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 diff --git a/crates/quark/assets/user/2.png b/crates/quark/assets/user/2.png deleted file mode 100644 index dd0d2967cdd1456550d36f5d9cbc095b02d39513..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6402 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 Py#UXHz6Yw^La+P}c2kv} diff --git a/crates/quark/assets/user/4.png b/crates/quark/assets/user/4.png deleted file mode 100644 index 6db7af6099351f5d233830c987f68e9fb9251d81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6405 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 diff --git a/crates/quark/assets/user/5.png b/crates/quark/assets/user/5.png deleted file mode 100644 index 9f32511c055dbbc1b877b34e48df3c9c921424a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6800 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 Lni~|HL*M!zf6y<{ diff --git a/crates/quark/assets/user/6.png b/crates/quark/assets/user/6.png deleted file mode 100644 index 533f920dfcd42cbfd8315ad73dcfcd96726a86f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6785 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 diff --git a/crates/quark/assets/user/7.png b/crates/quark/assets/user/7.png deleted file mode 100644 index 7d9ffd0ed14791394445a7768d4eeb0cfdd94394..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5653 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 PykK>RaIj#%+m-(U&MoaJ 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