From 2ce610e1e7d5dfc2ee927977a5d7b63ea067db9a Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Thu, 2 Jun 2022 00:04:22 +0100 Subject: [PATCH] chore(monorepo): delta, january, quark --- .gitignore | 8 +- .vscode/launch.json | 34 - Cargo.lock | 575 +- Cargo.toml | 70 +- LICENSE | 316 +- crates/bonfire/.github/FUNDING.yml | 2 + crates/bonfire/.github/workflows/docker.yml | 98 + crates/bonfire/.github/workflows/rust.yaml | 33 + .../.github/workflows/triage_issue.yml | 49 + .../bonfire/.github/workflows/triage_pr.yml | 72 + crates/bonfire/.gitignore | 1 + crates/bonfire/Cargo.lock | 4680 +++++++++++++++++ crates/bonfire/Cargo.toml | 27 + crates/bonfire/Dockerfile | 19 + crates/bonfire/LICENSE | 661 +++ crates/bonfire/src/config.rs | 150 + crates/bonfire/src/database.rs | 19 + crates/bonfire/src/main.rs | 34 + crates/bonfire/src/websocket.rs | 253 + crates/delta/Cargo.toml | 64 + Dockerfile => crates/delta/Dockerfile | 1 + crates/delta/LICENSE | 661 +++ .../delta/assets}/templates/reset.html | 0 .../delta/assets}/templates/reset.txt | 0 .../delta/assets}/templates/reset_plain.html | 0 .../delta/assets}/templates/verify.html | 0 .../delta/assets}/templates/verify.txt | 0 .../delta/assets}/templates/verify_plain.html | 0 {assets => crates/delta/assets}/user/1.png | Bin {assets => crates/delta/assets}/user/2.png | Bin {assets => crates/delta/assets}/user/3.png | Bin {assets => crates/delta/assets}/user/4.png | Bin {assets => crates/delta/assets}/user/5.png | Bin {assets => crates/delta/assets}/user/6.png | Bin {assets => crates/delta/assets}/user/7.png | Bin {src => crates/delta/src}/main.rs | 0 .../delta/src}/routes/bots/create.rs | 0 .../delta/src}/routes/bots/delete.rs | 0 {src => crates/delta/src}/routes/bots/edit.rs | 0 .../delta/src}/routes/bots/fetch.rs | 0 .../delta/src}/routes/bots/fetch_owned.rs | 0 .../delta/src}/routes/bots/fetch_public.rs | 0 .../delta/src}/routes/bots/invite.rs | 0 {src => crates/delta/src}/routes/bots/mod.rs | 0 .../delta/src}/routes/channels/channel_ack.rs | 0 .../src}/routes/channels/channel_delete.rs | 0 .../src}/routes/channels/channel_edit.rs | 0 .../src}/routes/channels/channel_fetch.rs | 0 .../src}/routes/channels/group_add_member.rs | 0 .../src}/routes/channels/group_create.rs | 0 .../routes/channels/group_remove_member.rs | 0 .../src}/routes/channels/invite_create.rs | 0 .../src}/routes/channels/members_fetch.rs | 0 .../routes/channels/message_bulk_delete.rs | 0 .../src}/routes/channels/message_delete.rs | 0 .../src}/routes/channels/message_edit.rs | 0 .../src}/routes/channels/message_fetch.rs | 0 .../src}/routes/channels/message_query.rs | 0 .../routes/channels/message_query_stale.rs | 0 .../src}/routes/channels/message_search.rs | 0 .../src}/routes/channels/message_send.rs | 0 .../delta/src}/routes/channels/mod.rs | 0 .../src}/routes/channels/permissions_set.rs | 0 .../channels/permissions_set_default.rs | 0 .../delta/src}/routes/channels/voice_join.rs | 0 .../src}/routes/invites/invite_delete.rs | 0 .../delta/src}/routes/invites/invite_fetch.rs | 0 .../delta/src}/routes/invites/invite_join.rs | 0 .../delta/src}/routes/invites/mod.rs | 0 {src => crates/delta/src}/routes/mod.rs | 0 .../delta/src}/routes/onboard/complete.rs | 0 .../delta/src}/routes/onboard/hello.rs | 0 .../delta/src}/routes/onboard/mod.rs | 0 {src => crates/delta/src}/routes/push/mod.rs | 0 .../delta/src}/routes/push/subscribe.rs | 0 .../delta/src}/routes/push/unsubscribe.rs | 0 {src => crates/delta/src}/routes/root.rs | 0 .../delta/src}/routes/servers/ban_create.rs | 0 .../delta/src}/routes/servers/ban_list.rs | 0 .../delta/src}/routes/servers/ban_remove.rs | 0 .../src}/routes/servers/channel_create.rs | 0 .../src}/routes/servers/invites_fetch.rs | 0 .../delta/src}/routes/servers/member_edit.rs | 0 .../delta/src}/routes/servers/member_fetch.rs | 0 .../src}/routes/servers/member_fetch_all.rs | 0 .../src}/routes/servers/member_remove.rs | 0 .../delta/src}/routes/servers/mod.rs | 0 .../src}/routes/servers/permissions_set.rs | 0 .../routes/servers/permissions_set_default.rs | 0 .../delta/src}/routes/servers/roles_create.rs | 0 .../delta/src}/routes/servers/roles_delete.rs | 0 .../delta/src}/routes/servers/roles_edit.rs | 0 .../delta/src}/routes/servers/server_ack.rs | 0 .../src}/routes/servers/server_create.rs | 0 .../src}/routes/servers/server_delete.rs | 0 .../delta/src}/routes/servers/server_edit.rs | 0 .../delta/src}/routes/servers/server_fetch.rs | 0 .../delta/src}/routes/sync/get_settings.rs | 0 .../delta/src}/routes/sync/get_unreads.rs | 0 {src => crates/delta/src}/routes/sync/mod.rs | 0 .../delta/src}/routes/sync/set_settings.rs | 0 .../delta/src}/routes/users/add_friend.rs | 0 .../delta/src}/routes/users/block_user.rs | 0 .../src}/routes/users/change_username.rs | 0 .../delta/src}/routes/users/edit_user.rs | 0 .../delta/src}/routes/users/fetch_dms.rs | 0 .../delta/src}/routes/users/fetch_profile.rs | 0 .../delta/src}/routes/users/fetch_self.rs | 0 .../delta/src}/routes/users/fetch_user.rs | 0 .../delta/src}/routes/users/find_mutual.rs | 0 .../src}/routes/users/get_default_avatar.rs | 0 {src => crates/delta/src}/routes/users/mod.rs | 0 .../delta/src}/routes/users/open_dm.rs | 0 .../delta/src}/routes/users/remove_friend.rs | 0 .../delta/src}/routes/users/unblock_user.rs | 0 {src => crates/delta/src}/util/idempotency.rs | 0 {src => crates/delta/src}/util/mod.rs | 0 {src => crates/delta/src}/util/ratelimiter.rs | 0 {src => crates/delta/src}/util/regex.rs | 0 {src => crates/delta/src}/version.rs | 0 crates/quark/.github/FUNDING.yml | 2 + crates/quark/.github/workflows/rust.yaml | 33 + .../quark/.github/workflows/triage_issue.yml | 49 + crates/quark/.github/workflows/triage_pr.yml | 72 + crates/quark/.gitignore | 14 + crates/quark/Cargo.toml | 70 + crates/quark/LICENSE | 661 +++ crates/quark/examples/test.rs | 34 + crates/quark/src/database.rs | 63 + crates/quark/src/events/client.rs | 173 + crates/quark/src/events/impl.rs | 557 ++ crates/quark/src/events/mod.rs | 4 + crates/quark/src/events/server.rs | 12 + crates/quark/src/events/state.rs | 146 + .../quark/src/impl/dummy/admin/migrations.rs | 11 + .../quark/src/impl/dummy/autumn/attachment.rs | 42 + .../quark/src/impl/dummy/channels/channel.rs | 90 + .../src/impl/dummy/channels/channel_invite.rs | 30 + .../src/impl/dummy/channels/channel_unread.rs | 31 + .../quark/src/impl/dummy/channels/message.rs | 67 + crates/quark/src/impl/dummy/mod.rs | 33 + crates/quark/src/impl/dummy/servers/server.rs | 78 + .../src/impl/dummy/servers/server_ban.rs | 32 + .../src/impl/dummy/servers/server_member.rs | 59 + crates/quark/src/impl/dummy/users/bot.rs | 46 + crates/quark/src/impl/dummy/users/user.rs | 78 + .../src/impl/dummy/users/user_settings.rs | 25 + .../src/impl/generic/admin/migrations.rs | 1 + .../src/impl/generic/autumn/attachment.rs | 33 + .../src/impl/generic/channels/channel.rs | 380 ++ .../impl/generic/channels/channel_invite.rs | 74 + .../impl/generic/channels/channel_unread.rs | 1 + .../src/impl/generic/channels/message.rs | 284 + crates/quark/src/impl/generic/mod.rs | 28 + .../quark/src/impl/generic/servers/server.rs | 323 ++ .../src/impl/generic/servers/server_ban.rs | 1 + .../src/impl/generic/servers/server_member.rs | 62 + crates/quark/src/impl/generic/users/bot.rs | 24 + crates/quark/src/impl/generic/users/user.rs | 430 ++ .../src/impl/generic/users/user_settings.rs | 22 + crates/quark/src/impl/mod.rs | 3 + .../quark/src/impl/mongo/admin/migrations.rs | 26 + .../src/impl/mongo/admin/migrations/init.rs | 187 + .../impl/mongo/admin/migrations/scripts.rs | 610 +++ .../quark/src/impl/mongo/autumn/attachment.rs | 125 + .../quark/src/impl/mongo/channels/channel.rs | 323 ++ .../src/impl/mongo/channels/channel_invite.rs | 31 + .../src/impl/mongo/channels/channel_unread.rs | 104 + .../quark/src/impl/mongo/channels/message.rs | 285 + crates/quark/src/impl/mongo/mod.rs | 250 + crates/quark/src/impl/mongo/servers/server.rs | 228 + .../src/impl/mongo/servers/server_ban.rs | 47 + .../src/impl/mongo/servers/server_member.rs | 134 + crates/quark/src/impl/mongo/users/bot.rs | 68 + crates/quark/src/impl/mongo/users/user.rs | 319 ++ .../src/impl/mongo/users/user_settings.rs | 62 + crates/quark/src/lib.rs | 72 + crates/quark/src/models/admin/migrations.rs | 11 + crates/quark/src/models/admin/simple.rs | 8 + crates/quark/src/models/autumn/attachment.rs | 61 + 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 | 170 + crates/quark/src/models/mod.rs | 47 + crates/quark/src/models/servers/server.rs | 150 + crates/quark/src/models/servers/server_ban.rs | 13 + .../quark/src/models/servers/server_member.rs | 50 + crates/quark/src/models/users/bot.rs | 63 + crates/quark/src/models/users/user.rs | 177 + .../quark/src/models/users/user_settings.rs | 6 + crates/quark/src/permissions/defn/mod.rs | 93 + .../quark/src/permissions/defn/permission.rs | 156 + crates/quark/src/permissions/defn/user.rs | 29 + crates/quark/src/permissions/impl/mod.rs | 2 + .../quark/src/permissions/impl/permission.rs | 219 + crates/quark/src/permissions/impl/user.rs | 100 + crates/quark/src/permissions/mod.rs | 171 + crates/quark/src/presence/entry.rs | 64 + crates/quark/src/presence/mod.rs | 162 + crates/quark/src/presence/operations.rs | 57 + crates/quark/src/tasks/ack.rs | 123 + crates/quark/src/tasks/last_message_id.rs | 90 + crates/quark/src/tasks/mod.rs | 58 + crates/quark/src/tasks/process_embeds.rs | 57 + crates/quark/src/tasks/web_push.rs | 135 + crates/quark/src/traits/admin/migrations.rs | 6 + crates/quark/src/traits/autumn/attachment.rs | 16 + crates/quark/src/traits/channels/channel.rs | 61 + .../src/traits/channels/channel_invite.rs | 17 + .../src/traits/channels/channel_unread.rs | 22 + crates/quark/src/traits/channels/message.rs | 45 + crates/quark/src/traits/mod.rs | 64 + crates/quark/src/traits/servers/server.rs | 42 + crates/quark/src/traits/servers/server_ban.rs | 18 + .../quark/src/traits/servers/server_member.rs | 37 + crates/quark/src/traits/users/bot.rs | 26 + crates/quark/src/traits/users/user.rs | 56 + .../quark/src/traits/users/user_settings.rs | 14 + crates/quark/src/types/january.rs | 243 + crates/quark/src/types/mod.rs | 2 + crates/quark/src/types/push.rs | 72 + crates/quark/src/util/manipulation.rs | 13 + crates/quark/src/util/mod.rs | 5 + crates/quark/src/util/ref.rs | 87 + crates/quark/src/util/result.rs | 264 + crates/quark/src/util/value.rs | 38 + crates/quark/src/util/variables/delta.rs | 98 + crates/quark/src/util/variables/mod.rs | 1 + docker-compose.yml | 17 - publish.sh | 7 - set_version.sh | 3 - 232 files changed, 18094 insertions(+), 554 deletions(-) delete mode 100644 .vscode/launch.json create mode 100644 crates/bonfire/.github/FUNDING.yml create mode 100644 crates/bonfire/.github/workflows/docker.yml create mode 100644 crates/bonfire/.github/workflows/rust.yaml create mode 100644 crates/bonfire/.github/workflows/triage_issue.yml create mode 100644 crates/bonfire/.github/workflows/triage_pr.yml create mode 100644 crates/bonfire/.gitignore create mode 100644 crates/bonfire/Cargo.lock create mode 100644 crates/bonfire/Cargo.toml create mode 100644 crates/bonfire/Dockerfile create mode 100644 crates/bonfire/LICENSE create mode 100644 crates/bonfire/src/config.rs create mode 100644 crates/bonfire/src/database.rs create mode 100644 crates/bonfire/src/main.rs create mode 100644 crates/bonfire/src/websocket.rs create mode 100644 crates/delta/Cargo.toml rename Dockerfile => crates/delta/Dockerfile (99%) create mode 100644 crates/delta/LICENSE rename {assets => crates/delta/assets}/templates/reset.html (100%) rename {assets => crates/delta/assets}/templates/reset.txt (100%) rename {assets => crates/delta/assets}/templates/reset_plain.html (100%) rename {assets => crates/delta/assets}/templates/verify.html (100%) rename {assets => crates/delta/assets}/templates/verify.txt (100%) rename {assets => crates/delta/assets}/templates/verify_plain.html (100%) rename {assets => crates/delta/assets}/user/1.png (100%) rename {assets => crates/delta/assets}/user/2.png (100%) rename {assets => crates/delta/assets}/user/3.png (100%) rename {assets => crates/delta/assets}/user/4.png (100%) rename {assets => crates/delta/assets}/user/5.png (100%) rename {assets => crates/delta/assets}/user/6.png (100%) rename {assets => crates/delta/assets}/user/7.png (100%) rename {src => crates/delta/src}/main.rs (100%) rename {src => crates/delta/src}/routes/bots/create.rs (100%) rename {src => crates/delta/src}/routes/bots/delete.rs (100%) rename {src => crates/delta/src}/routes/bots/edit.rs (100%) rename {src => crates/delta/src}/routes/bots/fetch.rs (100%) rename {src => crates/delta/src}/routes/bots/fetch_owned.rs (100%) rename {src => crates/delta/src}/routes/bots/fetch_public.rs (100%) rename {src => crates/delta/src}/routes/bots/invite.rs (100%) rename {src => crates/delta/src}/routes/bots/mod.rs (100%) rename {src => crates/delta/src}/routes/channels/channel_ack.rs (100%) rename {src => crates/delta/src}/routes/channels/channel_delete.rs (100%) rename {src => crates/delta/src}/routes/channels/channel_edit.rs (100%) rename {src => crates/delta/src}/routes/channels/channel_fetch.rs (100%) rename {src => crates/delta/src}/routes/channels/group_add_member.rs (100%) rename {src => crates/delta/src}/routes/channels/group_create.rs (100%) rename {src => crates/delta/src}/routes/channels/group_remove_member.rs (100%) rename {src => crates/delta/src}/routes/channels/invite_create.rs (100%) rename {src => crates/delta/src}/routes/channels/members_fetch.rs (100%) rename {src => crates/delta/src}/routes/channels/message_bulk_delete.rs (100%) rename {src => crates/delta/src}/routes/channels/message_delete.rs (100%) rename {src => crates/delta/src}/routes/channels/message_edit.rs (100%) rename {src => crates/delta/src}/routes/channels/message_fetch.rs (100%) rename {src => crates/delta/src}/routes/channels/message_query.rs (100%) rename {src => crates/delta/src}/routes/channels/message_query_stale.rs (100%) rename {src => crates/delta/src}/routes/channels/message_search.rs (100%) rename {src => crates/delta/src}/routes/channels/message_send.rs (100%) rename {src => crates/delta/src}/routes/channels/mod.rs (100%) rename {src => crates/delta/src}/routes/channels/permissions_set.rs (100%) rename {src => crates/delta/src}/routes/channels/permissions_set_default.rs (100%) rename {src => crates/delta/src}/routes/channels/voice_join.rs (100%) rename {src => crates/delta/src}/routes/invites/invite_delete.rs (100%) rename {src => crates/delta/src}/routes/invites/invite_fetch.rs (100%) rename {src => crates/delta/src}/routes/invites/invite_join.rs (100%) rename {src => crates/delta/src}/routes/invites/mod.rs (100%) rename {src => crates/delta/src}/routes/mod.rs (100%) rename {src => crates/delta/src}/routes/onboard/complete.rs (100%) rename {src => crates/delta/src}/routes/onboard/hello.rs (100%) rename {src => crates/delta/src}/routes/onboard/mod.rs (100%) rename {src => crates/delta/src}/routes/push/mod.rs (100%) rename {src => crates/delta/src}/routes/push/subscribe.rs (100%) rename {src => crates/delta/src}/routes/push/unsubscribe.rs (100%) rename {src => crates/delta/src}/routes/root.rs (100%) rename {src => crates/delta/src}/routes/servers/ban_create.rs (100%) rename {src => crates/delta/src}/routes/servers/ban_list.rs (100%) rename {src => crates/delta/src}/routes/servers/ban_remove.rs (100%) rename {src => crates/delta/src}/routes/servers/channel_create.rs (100%) rename {src => crates/delta/src}/routes/servers/invites_fetch.rs (100%) rename {src => crates/delta/src}/routes/servers/member_edit.rs (100%) rename {src => crates/delta/src}/routes/servers/member_fetch.rs (100%) rename {src => crates/delta/src}/routes/servers/member_fetch_all.rs (100%) rename {src => crates/delta/src}/routes/servers/member_remove.rs (100%) rename {src => crates/delta/src}/routes/servers/mod.rs (100%) rename {src => crates/delta/src}/routes/servers/permissions_set.rs (100%) rename {src => crates/delta/src}/routes/servers/permissions_set_default.rs (100%) rename {src => crates/delta/src}/routes/servers/roles_create.rs (100%) rename {src => crates/delta/src}/routes/servers/roles_delete.rs (100%) rename {src => crates/delta/src}/routes/servers/roles_edit.rs (100%) rename {src => crates/delta/src}/routes/servers/server_ack.rs (100%) rename {src => crates/delta/src}/routes/servers/server_create.rs (100%) rename {src => crates/delta/src}/routes/servers/server_delete.rs (100%) rename {src => crates/delta/src}/routes/servers/server_edit.rs (100%) rename {src => crates/delta/src}/routes/servers/server_fetch.rs (100%) rename {src => crates/delta/src}/routes/sync/get_settings.rs (100%) rename {src => crates/delta/src}/routes/sync/get_unreads.rs (100%) rename {src => crates/delta/src}/routes/sync/mod.rs (100%) rename {src => crates/delta/src}/routes/sync/set_settings.rs (100%) rename {src => crates/delta/src}/routes/users/add_friend.rs (100%) rename {src => crates/delta/src}/routes/users/block_user.rs (100%) rename {src => crates/delta/src}/routes/users/change_username.rs (100%) rename {src => crates/delta/src}/routes/users/edit_user.rs (100%) rename {src => crates/delta/src}/routes/users/fetch_dms.rs (100%) rename {src => crates/delta/src}/routes/users/fetch_profile.rs (100%) rename {src => crates/delta/src}/routes/users/fetch_self.rs (100%) rename {src => crates/delta/src}/routes/users/fetch_user.rs (100%) rename {src => crates/delta/src}/routes/users/find_mutual.rs (100%) rename {src => crates/delta/src}/routes/users/get_default_avatar.rs (100%) rename {src => crates/delta/src}/routes/users/mod.rs (100%) rename {src => crates/delta/src}/routes/users/open_dm.rs (100%) rename {src => crates/delta/src}/routes/users/remove_friend.rs (100%) rename {src => crates/delta/src}/routes/users/unblock_user.rs (100%) rename {src => crates/delta/src}/util/idempotency.rs (100%) rename {src => crates/delta/src}/util/mod.rs (100%) rename {src => crates/delta/src}/util/ratelimiter.rs (100%) rename {src => crates/delta/src}/util/regex.rs (100%) rename {src => crates/delta/src}/version.rs (100%) create mode 100644 crates/quark/.github/FUNDING.yml create mode 100644 crates/quark/.github/workflows/rust.yaml create mode 100644 crates/quark/.github/workflows/triage_issue.yml create mode 100644 crates/quark/.github/workflows/triage_pr.yml create mode 100644 crates/quark/.gitignore create mode 100644 crates/quark/Cargo.toml create mode 100644 crates/quark/LICENSE create mode 100644 crates/quark/examples/test.rs create mode 100644 crates/quark/src/database.rs create mode 100644 crates/quark/src/events/client.rs create mode 100644 crates/quark/src/events/impl.rs create mode 100644 crates/quark/src/events/mod.rs create mode 100644 crates/quark/src/events/server.rs create mode 100644 crates/quark/src/events/state.rs create mode 100644 crates/quark/src/impl/dummy/admin/migrations.rs create mode 100644 crates/quark/src/impl/dummy/autumn/attachment.rs create mode 100644 crates/quark/src/impl/dummy/channels/channel.rs create mode 100644 crates/quark/src/impl/dummy/channels/channel_invite.rs create mode 100644 crates/quark/src/impl/dummy/channels/channel_unread.rs create mode 100644 crates/quark/src/impl/dummy/channels/message.rs create mode 100644 crates/quark/src/impl/dummy/mod.rs create mode 100644 crates/quark/src/impl/dummy/servers/server.rs create mode 100644 crates/quark/src/impl/dummy/servers/server_ban.rs create mode 100644 crates/quark/src/impl/dummy/servers/server_member.rs create mode 100644 crates/quark/src/impl/dummy/users/bot.rs create mode 100644 crates/quark/src/impl/dummy/users/user.rs create mode 100644 crates/quark/src/impl/dummy/users/user_settings.rs create mode 100644 crates/quark/src/impl/generic/admin/migrations.rs create mode 100644 crates/quark/src/impl/generic/autumn/attachment.rs create mode 100644 crates/quark/src/impl/generic/channels/channel.rs create mode 100644 crates/quark/src/impl/generic/channels/channel_invite.rs create mode 100644 crates/quark/src/impl/generic/channels/channel_unread.rs create mode 100644 crates/quark/src/impl/generic/channels/message.rs create mode 100644 crates/quark/src/impl/generic/mod.rs create mode 100644 crates/quark/src/impl/generic/servers/server.rs create mode 100644 crates/quark/src/impl/generic/servers/server_ban.rs create mode 100644 crates/quark/src/impl/generic/servers/server_member.rs create mode 100644 crates/quark/src/impl/generic/users/bot.rs create mode 100644 crates/quark/src/impl/generic/users/user.rs create mode 100644 crates/quark/src/impl/generic/users/user_settings.rs create mode 100644 crates/quark/src/impl/mod.rs create mode 100644 crates/quark/src/impl/mongo/admin/migrations.rs create mode 100644 crates/quark/src/impl/mongo/admin/migrations/init.rs create mode 100644 crates/quark/src/impl/mongo/admin/migrations/scripts.rs create mode 100644 crates/quark/src/impl/mongo/autumn/attachment.rs create mode 100644 crates/quark/src/impl/mongo/channels/channel.rs create mode 100644 crates/quark/src/impl/mongo/channels/channel_invite.rs create mode 100644 crates/quark/src/impl/mongo/channels/channel_unread.rs create mode 100644 crates/quark/src/impl/mongo/channels/message.rs create mode 100644 crates/quark/src/impl/mongo/mod.rs create mode 100644 crates/quark/src/impl/mongo/servers/server.rs create mode 100644 crates/quark/src/impl/mongo/servers/server_ban.rs create mode 100644 crates/quark/src/impl/mongo/servers/server_member.rs create mode 100644 crates/quark/src/impl/mongo/users/bot.rs create mode 100644 crates/quark/src/impl/mongo/users/user.rs create mode 100644 crates/quark/src/impl/mongo/users/user_settings.rs create mode 100644 crates/quark/src/lib.rs create mode 100644 crates/quark/src/models/admin/migrations.rs create mode 100644 crates/quark/src/models/admin/simple.rs create mode 100644 crates/quark/src/models/autumn/attachment.rs create mode 100644 crates/quark/src/models/channels/channel.rs create mode 100644 crates/quark/src/models/channels/channel_invite.rs create mode 100644 crates/quark/src/models/channels/channel_unread.rs create mode 100644 crates/quark/src/models/channels/message.rs create mode 100644 crates/quark/src/models/mod.rs create mode 100644 crates/quark/src/models/servers/server.rs create mode 100644 crates/quark/src/models/servers/server_ban.rs create mode 100644 crates/quark/src/models/servers/server_member.rs create mode 100644 crates/quark/src/models/users/bot.rs create mode 100644 crates/quark/src/models/users/user.rs create mode 100644 crates/quark/src/models/users/user_settings.rs create mode 100644 crates/quark/src/permissions/defn/mod.rs create mode 100644 crates/quark/src/permissions/defn/permission.rs create mode 100644 crates/quark/src/permissions/defn/user.rs create mode 100644 crates/quark/src/permissions/impl/mod.rs create mode 100644 crates/quark/src/permissions/impl/permission.rs create mode 100644 crates/quark/src/permissions/impl/user.rs create mode 100644 crates/quark/src/permissions/mod.rs create mode 100644 crates/quark/src/presence/entry.rs create mode 100644 crates/quark/src/presence/mod.rs create mode 100644 crates/quark/src/presence/operations.rs create mode 100644 crates/quark/src/tasks/ack.rs create mode 100644 crates/quark/src/tasks/last_message_id.rs create mode 100644 crates/quark/src/tasks/mod.rs create mode 100644 crates/quark/src/tasks/process_embeds.rs create mode 100644 crates/quark/src/tasks/web_push.rs create mode 100644 crates/quark/src/traits/admin/migrations.rs create mode 100644 crates/quark/src/traits/autumn/attachment.rs create mode 100644 crates/quark/src/traits/channels/channel.rs create mode 100644 crates/quark/src/traits/channels/channel_invite.rs create mode 100644 crates/quark/src/traits/channels/channel_unread.rs create mode 100644 crates/quark/src/traits/channels/message.rs create mode 100644 crates/quark/src/traits/mod.rs create mode 100644 crates/quark/src/traits/servers/server.rs create mode 100644 crates/quark/src/traits/servers/server_ban.rs create mode 100644 crates/quark/src/traits/servers/server_member.rs create mode 100644 crates/quark/src/traits/users/bot.rs create mode 100644 crates/quark/src/traits/users/user.rs create mode 100644 crates/quark/src/traits/users/user_settings.rs create mode 100644 crates/quark/src/types/january.rs create mode 100644 crates/quark/src/types/mod.rs create mode 100644 crates/quark/src/types/push.rs create mode 100644 crates/quark/src/util/manipulation.rs create mode 100644 crates/quark/src/util/mod.rs create mode 100644 crates/quark/src/util/ref.rs create mode 100644 crates/quark/src/util/result.rs create mode 100644 crates/quark/src/util/value.rs create mode 100644 crates/quark/src/util/variables/delta.rs create mode 100644 crates/quark/src/util/variables/mod.rs delete mode 100644 docker-compose.yml delete mode 100755 publish.sh delete mode 100755 set_version.sh diff --git a/.gitignore b/.gitignore index 1e079469..4152308b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,2 @@ Rocket.toml -/target -/target_backup -**/*.rs.bk -.mongo -.data -.env -avatar.png +target diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index b4455fd0..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "configurations": [ - { - "name": "(gdb) Launch", - "type": "cppdbg", - "request": "launch", - "program": "${workspaceFolder}/target/debug/revolt", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}", - "environment": [{ - "name": "ROCKET_ADDRESS", - "value": "0.0.0.0" - }, { - "name": "MONGODB", - "value": "mongodb://localhost" - }], - "externalConsole": false, - "MIMode": "gdb", - "setupCommands": [ - { - "description": "Enable pretty-printing for gdb", - "text": "-enable-pretty-printing", - "ignoreFailures": true - }, - { - "description": "Set Disassembly Flavor to Intel", - "text": "-gdb-set disassembly-flavor intel", - "ignoreFailures": true - } - ] - } - ] -} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index d9b409f4..3ed2037a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,7 +75,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -105,27 +105,26 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c290043c9a95b05d45e952fb6383c67bcb61471f60cfa21e890dba6654234f43" +checksum = "fd8b508d585e01084059b60f06ade4cb7415cd2e4084b71dd1cb44e7d3fb9880" dependencies = [ "async-channel", "async-executor", "async-io", - "async-mutex", + "async-lock", "blocking", "futures-lite", - "num_cpus", "once_cell", "tokio 0.2.25", - "tokio 1.18.0", + "tokio 1.18.2", ] [[package]] name = "async-io" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" +checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" dependencies = [ "concurrent-queue", "futures-lite", @@ -150,19 +149,10 @@ dependencies = [ ] [[package]] -name = "async-mutex" +name = "async-process" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-process" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6" +checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" dependencies = [ "async-io", "blocking", @@ -183,7 +173,7 @@ checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -248,7 +238,7 @@ checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -265,7 +255,21 @@ checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", +] + +[[package]] +name = "async-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" +dependencies = [ + "async-std", + "futures-io", + "futures-util", + "log", + "pin-project-lite 0.2.9", + "tungstenite", ] [[package]] @@ -487,9 +491,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] name = "byte-tools" @@ -574,8 +578,8 @@ dependencies = [ "futures-core", "memchr", "pin-project-lite 0.2.9", - "tokio 1.18.0", - "tokio-util 0.7.1", + "tokio 1.18.2", + "tokio-util 0.7.2", ] [[package]] @@ -682,7 +686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" dependencies = [ "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -726,7 +730,7 @@ dependencies = [ "proc-macro2", "quote 1.0.18", "strsim 0.9.3", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -740,7 +744,7 @@ dependencies = [ "proc-macro2", "quote 1.0.18", "strsim 0.10.0", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -751,7 +755,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core 0.10.2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -762,18 +766,19 @@ checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core 0.13.4", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] name = "dashmap" -version = "5.2.0" +version = "5.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8858831f7781322e539ea39e72449c46b059638250c14344fec8d0aa6e539c" +checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f" dependencies = [ "cfg-if 1.0.0", - "num_cpus", - "parking_lot 0.12.0", + "hashbrown 0.12.1", + "lock_api", + "parking_lot_core 0.9.3", ] [[package]] @@ -784,12 +789,12 @@ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "deadqueue" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f13842c4b01ed03514527b625ae001f43b74158c7b0402a5369010cc38c3c8cf" +checksum = "455d501abc72bae6432dbe500059887cab4700ba83303045db9ce2e824177d1c" dependencies = [ "crossbeam-queue", - "tokio 1.18.0", + "tokio 1.18.2", ] [[package]] @@ -810,7 +815,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -843,7 +848,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -932,7 +937,7 @@ dependencies = [ "heck", "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -944,7 +949,7 @@ dependencies = [ "heck", "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -979,7 +984,7 @@ dependencies = [ "proc-macro2", "quote 1.0.18", "rustversion", - "syn 1.0.91", + "syn 1.0.95", "synstructure", ] @@ -1142,7 +1147,7 @@ checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -1293,8 +1298,8 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 1.18.0", - "tokio-util 0.7.1", + "tokio 1.18.2", + "tokio-util 0.7.2", "tracing", ] @@ -1321,6 +1326,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" + [[package]] name = "heck" version = "0.4.0" @@ -1374,13 +1385,13 @@ dependencies = [ [[package]] name = "http" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" dependencies = [ "bytes 1.1.0", "fnv", - "itoa 1.0.1", + "itoa 1.0.2", ] [[package]] @@ -1395,9 +1406,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes 1.1.0", "http", @@ -1457,9 +1468,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.18" +version = "0.14.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" +checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" dependencies = [ "bytes 1.1.0", "futures-channel", @@ -1467,13 +1478,13 @@ dependencies = [ "futures-util", "h2 0.3.13", "http", - "http-body 0.4.4", + "http-body 0.4.5", "httparse", "httpdate 1.0.2", - "itoa 1.0.1", + "itoa 1.0.2", "pin-project-lite 0.2.9", "socket2 0.4.4", - "tokio 1.18.0", + "tokio 1.18.2", "tower-service", "tracing", "want", @@ -1499,9 +1510,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes 1.1.0", - "hyper 0.14.18", + "hyper 0.14.19", "native-tls", - "tokio 1.18.0", + "tokio 1.18.2", "tokio-native-tls", ] @@ -1536,12 +1547,12 @@ checksum = "90f97a5f38dd3ccfbe7aa80f4a0c00930f21b922c74195be0201c51028f22dcf" [[package]] name = "indexmap" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" dependencies = [ "autocfg 1.1.0", - "hashbrown", + "hashbrown 0.11.2", "serde", ] @@ -1601,9 +1612,9 @@ checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" [[package]] name = "iso8601-timestamp" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b415c6f5289fa0f0fcaaf15653931eaf947e584a9a58c1d15756e8bcbb7dd19a" +checksum = "56480a7792bfa284a4c7d1781f711de58e357dbb669c1b1a50c37c1777c9be92" dependencies = [ "generic-array 0.14.5", "schemars", @@ -1619,9 +1630,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" [[package]] name = "js-sys" @@ -1665,9 +1676,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lettre" -version = "0.10.0-rc.5" +version = "0.10.0-rc.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5144148f337be14dabfc0f0d85b691a68ac6c77ef22a5c47c5504b70a7c9fcf3" +checksum = "2f6c70001f7ee6c93b6687a06607c7a38f9a7ae460139a496c23da21e95bc289" dependencies = [ "base64 0.13.0", "email-encoding", @@ -1686,9 +1697,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.124" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] name = "linked-hash-map" @@ -1726,9 +1737,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", "value-bag", @@ -1736,9 +1747,9 @@ dependencies = [ [[package]] name = "loom" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5c7d328e32cc4954e8e01193d7f0ef5ab257b5090b70a964e099a36034309" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" dependencies = [ "cfg-if 1.0.0", "generator", @@ -1751,11 +1762,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32613e41de4c47ab04970c348ca7ae7382cf116625755af070b008a15516a889" +checksum = "8015d95cb7b2ddd3c0d32ca38283ceb1eea09b4713ee380bceb942d85a244228" dependencies = [ - "hashbrown", + "hashbrown 0.11.2", ] [[package]] @@ -1816,9 +1827,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "mime" @@ -1834,9 +1845,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" dependencies = [ "adler", ] @@ -1854,7 +1865,7 @@ dependencies = [ "kernel32-sys", "libc", "log", - "miow 0.2.2", + "miow", "net2", "slab", "winapi 0.2.8", @@ -1862,16 +1873,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" dependencies = [ "libc", "log", - "miow 0.3.7", - "ntapi", "wasi 0.11.0+wasi-snapshot-preview1", - "winapi 0.3.9", + "windows-sys", ] [[package]] @@ -1886,15 +1895,6 @@ dependencies = [ "ws2_32-sys", ] -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "mobc" version = "0.7.3" @@ -1908,7 +1908,7 @@ dependencies = [ "futures-timer", "futures-util", "log", - "tokio 1.18.0", + "tokio 1.18.2", ] [[package]] @@ -1993,7 +1993,7 @@ dependencies = [ "percent-encoding", "rand 0.8.5", "rustc_version_runtime", - "rustls 0.20.4", + "rustls 0.20.6", "rustls-pemfile 0.3.0", "serde", "serde_with", @@ -2004,9 +2004,9 @@ dependencies = [ "strsim 0.10.0", "take_mut", "thiserror", - "tokio 1.18.0", - "tokio-rustls 0.23.3", - "tokio-util 0.7.1", + "tokio 1.18.2", + "tokio-rustls 0.23.4", + "tokio-util 0.7.2", "trust-dns-proto 0.21.2", "trust-dns-resolver 0.21.2", "typed-builder 0.10.0", @@ -2029,8 +2029,8 @@ dependencies = [ "memchr", "mime", "spin 0.9.3", - "tokio 1.18.0", - "tokio-util 0.6.9", + "tokio 1.18.2", + "tokio-util 0.6.10", "version_check", ] @@ -2093,20 +2093,11 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg 1.1.0", "num-traits", @@ -2114,9 +2105,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg 1.1.0", ] @@ -2149,23 +2140,23 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] name = "num_threads" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ "libc", ] [[package]] name = "object" -version = "0.28.3" +version = "0.28.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ "memchr", ] @@ -2183,9 +2174,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" [[package]] name = "opaque-debug" @@ -2201,18 +2192,30 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.38" +version = "0.10.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" dependencies = [ "bitflags", "cfg-if 1.0.0", "foreign-types", "libc", "once_cell", + "openssl-macros", "openssl-sys", ] +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote 1.0.18", + "syn 1.0.95", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -2221,9 +2224,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.72" +version = "0.9.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +checksum = "9d5fd19fb3e0a8191c1e34935718976a3e70c112ab9a24af6d7cadccd9d90bc0" dependencies = [ "autocfg 1.1.0", "cc", @@ -2243,9 +2246,9 @@ dependencies = [ [[package]] name = "os_info" -version = "3.2.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "023df84d545ef479cf67fd2f4459a613585c9db4852c2fad12ab70587859d340" +checksum = "0eca3ecae1481e12c3d9379ec541b238a16f0b75c9a409942daa8ec20dbfdb62" dependencies = [ "log", "winapi 0.3.9", @@ -2270,12 +2273,12 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.2", + "parking_lot_core 0.9.3", ] [[package]] @@ -2294,9 +2297,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if 1.0.0", "libc", @@ -2349,7 +2352,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -2387,7 +2390,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -2418,7 +2421,7 @@ checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -2493,7 +2496,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", "version_check", ] @@ -2516,11 +2519,11 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.37" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" dependencies = [ - "unicode-xid 0.2.2", + "unicode-ident", ] [[package]] @@ -2531,11 +2534,17 @@ checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", "version_check", "yansi", ] +[[package]] +name = "querystring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9318ead08c799aad12a55a3e78b82e0b6167271ffd1f627b758891282f739187" + [[package]] name = "quick-error" version = "1.2.3" @@ -2813,8 +2822,8 @@ dependencies = [ "percent-encoding", "pin-project-lite 0.2.9", "sha1", - "tokio 1.18.0", - "tokio-util 0.6.9", + "tokio 1.18.2", + "tokio-util 0.6.10", "url", ] @@ -2834,8 +2843,8 @@ dependencies = [ "percent-encoding", "pin-project-lite 0.2.9", "sha1", - "tokio 1.18.0", - "tokio-util 0.6.9", + "tokio 1.18.2", + "tokio-util 0.6.10", "url", ] @@ -2881,14 +2890,14 @@ checksum = "a043824e29c94169374ac5183ac0ed43f5724dc4556b19568007486bd840fa1f" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] name = "regex" -version = "1.5.5" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" dependencies = [ "aho-corasick", "memchr", @@ -2906,9 +2915,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" [[package]] name = "remove_dir_all" @@ -2932,8 +2941,8 @@ dependencies = [ "futures-util", "h2 0.3.13", "http", - "http-body 0.4.4", - "hyper 0.14.18", + "http-body 0.4.5", + "hyper 0.14.19", "hyper-tls 0.5.0", "ipnet", "js-sys", @@ -2946,7 +2955,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "tokio 1.18.0", + "tokio 1.18.2", "tokio-native-tls", "url", "wasm-bindgen", @@ -2966,8 +2975,24 @@ dependencies = [ ] [[package]] -name = "revolt" -version = "0.0.0" +name = "revolt-bonfire" +version = "1.0.6-patch.2" +dependencies = [ + "async-std", + "async-tungstenite", + "futures", + "log", + "once_cell", + "querystring", + "revolt-quark", + "rmp-serde", + "serde", + "serde_json", +] + +[[package]] +name = "revolt-delta" +version = "0.5.3-5-patch.2" dependencies = [ "async-channel", "async-std", @@ -3009,7 +3034,6 @@ dependencies = [ [[package]] name = "revolt-quark" version = "0.1.0" -source = "git+https://github.com/revoltchat/quark?rev=076755e513aa6b1e955cf917f60aad9e62a73883#076755e513aa6b1e955cf917f60aad9e62a73883" dependencies = [ "async-recursion", "async-std", @@ -3037,6 +3061,7 @@ dependencies = [ "regex", "reqwest", "rocket", + "rocket_http", "rocket_okapi", "schemars", "sentry", @@ -3115,9 +3140,9 @@ dependencies = [ "state", "tempfile", "time 0.2.27", - "tokio 1.18.0", + "tokio 1.18.2", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util 0.6.10", "ubyte", "version_check", "yansi", @@ -3135,8 +3160,8 @@ dependencies = [ "proc-macro2", "quote 1.0.18", "rocket_http", - "syn 1.0.91", - "unicode-xid 0.2.2", + "syn 1.0.95", + "unicode-xid 0.2.3", ] [[package]] @@ -3163,7 +3188,7 @@ dependencies = [ "cookie", "either", "http", - "hyper 0.14.18", + "hyper 0.14.19", "indexmap", "log", "memchr", @@ -3178,7 +3203,7 @@ dependencies = [ "stable-pattern", "state", "time 0.2.27", - "tokio 1.18.0", + "tokio 1.18.2", "uncased", ] @@ -3206,7 +3231,7 @@ dependencies = [ "proc-macro2", "quote 1.0.18", "rocket_http", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -3270,9 +3295,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.4" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" dependencies = [ "log", "ring", @@ -3306,9 +3331,9 @@ checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" [[package]] name = "safemem" @@ -3318,19 +3343,19 @@ checksum = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" [[package]] name = "schannel" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ "lazy_static", - "winapi 0.3.9", + "windows-sys", ] [[package]] name = "schemars" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6b5a3c80cea1ab61f4260238409510e814e38b4b563c06044edf91e7dc070e3" +checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" dependencies = [ "dyn-clone", "indexmap", @@ -3341,14 +3366,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ae4dce13e8614c46ac3c38ef1c0d668b101df6ac39817aebdaa26642ddae9b" +checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" dependencies = [ "proc-macro2", "quote 1.0.18", "serde_derive_internals", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -3439,7 +3464,7 @@ dependencies = [ "sentry-contexts", "sentry-core", "sentry-panic", - "tokio 1.18.0", + "tokio 1.18.2", ] [[package]] @@ -3509,52 +3534,52 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] name = "serde_derive_internals" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" dependencies = [ "indexmap", - "itoa 1.0.1", + "itoa 1.0.2", "ryu", "serde", ] @@ -3566,18 +3591,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.1", + "itoa 1.0.2", "ryu", "serde", ] [[package]] name = "serde_with" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b827f2113224f3f19a665136f006709194bdfdcb1fdc1e4b2b5cbac8e0cced54" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" dependencies = [ - "rustversion", "serde", "serde_with_macros", ] @@ -3591,7 +3615,7 @@ dependencies = [ "darling 0.13.4", "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -3680,9 +3704,9 @@ dependencies = [ [[package]] name = "signal-hook" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d" +checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" dependencies = [ "libc", "signal-hook-registry", @@ -3762,9 +3786,9 @@ dependencies = [ [[package]] name = "state" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cf4f5369e6d3044b5e365c9690f451516ac8f0954084622b49ea3fde2f6de5" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" dependencies = [ "loom", ] @@ -3793,7 +3817,7 @@ dependencies = [ "quote 1.0.18", "serde", "serde_derive", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -3809,7 +3833,7 @@ dependencies = [ "serde_derive", "serde_json", "sha1", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -3859,13 +3883,13 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.91" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" dependencies = [ "proc-macro2", "quote 1.0.18", - "unicode-xid 0.2.2", + "unicode-ident", ] [[package]] @@ -3885,8 +3909,8 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", - "unicode-xid 0.2.2", + "syn 1.0.95", + "unicode-xid 0.2.3", ] [[package]] @@ -3920,22 +3944,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -3979,7 +4003,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" dependencies = [ - "itoa 1.0.1", + "itoa 1.0.2", "libc", "num_threads", "time-macros 0.2.4", @@ -4011,7 +4035,7 @@ dependencies = [ "proc-macro2", "quote 1.0.18", "standback", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -4049,14 +4073,14 @@ dependencies = [ [[package]] name = "tokio" -version = "1.18.0" +version = "1.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f48b6d60512a392e34dbf7fd456249fd2de3c83669ab642e021903f4015185b" +checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" dependencies = [ "bytes 1.1.0", "libc", "memchr", - "mio 0.8.2", + "mio 0.8.3", "num_cpus", "once_cell", "pin-project-lite 0.2.9", @@ -4074,7 +4098,7 @@ checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -4085,7 +4109,7 @@ checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -4095,7 +4119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" dependencies = [ "native-tls", - "tokio 1.18.0", + "tokio 1.18.2", ] [[package]] @@ -4112,12 +4136,12 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.3" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4151fda0cf2798550ad0b34bcfc9b9dcc2a9d2471c895c68f3a8818e54f2389e" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls 0.20.4", - "tokio 1.18.0", + "rustls 0.20.6", + "tokio 1.18.2", "webpki 0.22.0", ] @@ -4129,7 +4153,7 @@ checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", "pin-project-lite 0.2.9", - "tokio 1.18.0", + "tokio 1.18.2", ] [[package]] @@ -4158,30 +4182,30 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ "bytes 1.1.0", "futures-core", "futures-sink", "log", "pin-project-lite 0.2.9", - "tokio 1.18.0", + "tokio 1.18.2", ] [[package]] name = "tokio-util" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" dependencies = [ "bytes 1.1.0", "futures-core", "futures-io", "futures-sink", "pin-project-lite 0.2.9", - "tokio 1.18.0", + "tokio 1.18.2", "tracing", ] @@ -4233,7 +4257,7 @@ checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -4327,7 +4351,7 @@ dependencies = [ "smallvec", "thiserror", "tinyvec", - "tokio 1.18.0", + "tokio 1.18.2", "url", ] @@ -4362,11 +4386,11 @@ dependencies = [ "lazy_static", "log", "lru-cache", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "resolv-conf", "smallvec", "thiserror", - "tokio 1.18.0", + "tokio 1.18.2", "trust-dns-proto 0.21.2", ] @@ -4376,6 +4400,25 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" +dependencies = [ + "base64 0.13.0", + "byteorder", + "bytes 1.1.0", + "http", + "httparse", + "log", + "rand 0.8.5", + "sha-1 0.10.0", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "typed-builder" version = "0.4.1" @@ -4384,7 +4427,7 @@ checksum = "dfc955f27acc7a547f328f52f4a5a568986a31efec2fc6de865279f3995787b9" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -4395,7 +4438,7 @@ checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -4406,9 +4449,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ubyte" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42756bb9e708855de2f8a98195643dff31a97f0485d90d8467b39dc24be9e8fe" +checksum = "a58e29f263341a29bb79e14ad7fda5f63b1c7e48929bad4c685d7876b1d04e94" dependencies = [ "serde", ] @@ -4451,9 +4494,9 @@ dependencies = [ [[package]] name = "uncased" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" dependencies = [ "serde", "version_check", @@ -4484,6 +4527,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + [[package]] name = "unicode-normalization" version = "0.1.19" @@ -4501,9 +4550,9 @@ checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "untrusted" @@ -4530,6 +4579,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "uuid" version = "0.8.2" @@ -4569,7 +4624,7 @@ dependencies = [ "proc-macro2", "quote 1.0.18", "regex", - "syn 1.0.91", + "syn 1.0.95", "validator_types", ] @@ -4580,7 +4635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded9d97e1d42327632f5f3bae6403c04886e2de3036261ef42deebd931a6a291" dependencies = [ "proc-macro2", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] @@ -4591,9 +4646,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.0.0-alpha.8" +version = "1.0.0-alpha.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" dependencies = [ "ctor", "version_check", @@ -4666,7 +4721,7 @@ dependencies = [ "log", "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", "wasm-bindgen-shared", ] @@ -4700,7 +4755,7 @@ checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ "proc-macro2", "quote 1.0.18", - "syn 1.0.91", + "syn 1.0.95", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4850,9 +4905,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ "windows_aarch64_msvc", "windows_i686_gnu", @@ -4863,33 +4918,33 @@ dependencies = [ [[package]] name = "windows_aarch64_msvc" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_i686_gnu" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_msvc" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_x86_64_gnu" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_msvc" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "winreg" @@ -4946,7 +5001,7 @@ dependencies = [ "proc-macro2", "quote 1.0.18", "serde", - "syn 1.0.91", + "syn 1.0.95", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 022eed90..c66a4d73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,68 +1,2 @@ -[package] -name = "revolt" -# To help optimise CI and Docker builds. -# Version here is left as 0.0.0, please -# adjust and run ./set_version.sh instead. -version = "0.0.0" -authors = ["Paul Makles "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -# Utility -lru = "0.7.0" -url = "2.2.2" -log = "0.4.11" -dotenv = "0.15.0" -dashmap = "5.2.0" -linkify = "0.6.0" -once_cell = "1.4.1" -env_logger = "0.7.1" -lazy_static = "1.4.0" -ctrlc = { version = "3.0", features = ["termination"] } - -# Lang. Utilities -regex = "1" -num_enum = "0.5.1" -impl_ops = "0.1.1" -bitfield = "0.13.2" - -# ID / key generation -ulid = "0.4.1" -nanoid = "0.4.0" - -# serde -serde_json = "1.0.57" -serde = { version = "1.0.115", features = ["derive"] } -validator = { version = "0.14", features = ["derive"] } - -# async -futures = "0.3.8" -chrono = "0.4.15" -async-channel = "1.6.1" -reqwest = { version = "0.11.4", features = ["json"] } -async-std = { version = "1.8.0", features = ["tokio1", "tokio02", "attributes"] } - -# internal util -lettre = "0.10.0-alpha.4" -rauth = { git = "https://github.com/insertish/rauth", rev = "001a9698c56cea79e69e4ae71d7bc2cb48aec1a6" } - -# redis -redis = { version = "0.21.2", features = ["async-std-comp"] } -mobc = { version = "0.7.3" } -mobc-redis = { version = "0.7.0", default-features = false, features = ["async-std-comp"] } - -# web -rocket = { version = "0.5.0-rc.1", default-features = false, features = ["json"] } -mongodb = { version = "1.2.2", features = ["async-std-runtime"], default-features = false } -rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "5843861a88958c16bfaa0b40f0d8910772bcd2f6" } - -# spec generation -schemars = "0.8.8" -# rocket_okapi = "0.8.0-rc.1" -rocket_okapi = { git = "https://github.com/insertish/okapi", rev = "dcf0df115596ee07a587a7a543cddf3d7944645b", features = [ "swagger" ] } - -# quark -revolt-quark = { git = "https://github.com/revoltchat/quark", rev = "076755e513aa6b1e955cf917f60aad9e62a73883" } -# revolt-quark = { path = "../quark" } +[workspace] +members = ["crates/*"] diff --git a/LICENSE b/LICENSE index 8bd70cb8..eaeac67c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,178 +1,178 @@ GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 -Copyright (C) 2007 Free Software Foundation, Inc. -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. Preamble -The GNU Affero General Public License is a free, copyleft license for + The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. -Developers that use our General Public Licenses protect your rights + Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. -A secondary benefit of defending all users' freedom is that + A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. -The GNU Affero General Public License is designed specifically to + The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to +to the community. It requires the operator of a network server to provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on +users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. -An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. -The precise terms and conditions for copying, distribution and + The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS -0. Definitions. + 0. Definitions. -"This License" refers to version 3 of the GNU Affero General Public License. + "This License" refers to version 3 of the GNU Affero General Public License. -"Copyright" also means copyright-like laws that apply to other kinds of + "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. -To "modify" a work means to copy from or adapt all or part of the work + To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the +exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. -A "covered work" means either the unmodified Program or a work based + A "covered work" means either the unmodified Program or a work based on the Program. -To "propagate" a work means to do anything with it that, without + To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, +computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. -An interactive user interface displays "Appropriate Legal Notices" + An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If +work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. -1. Source Code. + 1. Source Code. -The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source form of a work. -A "Standard Interface" means an interface that either is an official + A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. -The "System Libraries" of an executable work include anything, other + The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A +implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. -The "Corresponding Source" for a work in object code form means all + The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's +control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source +which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. -The Corresponding Source need not include anything that users + The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. -The Corresponding Source for a work in source code form is that + The Corresponding Source for a work in source code form is that same work. -2. Basic Permissions. + 2. Basic Permissions. -All rights granted under this License are granted for the term of + All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your +content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. -You may make, run and propagate covered works that you do not + You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose +in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works +not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. -Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. -3. Protecting Users' Legal Rights From Anti-Circumvention Law. + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. -No covered work shall be deemed part of an effective technological + No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. -When you convey a covered work, you waive any legal power to forbid + When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or @@ -180,9 +180,9 @@ modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. -4. Conveying Verbatim Copies. + 4. Conveying Verbatim Copies. -You may convey verbatim copies of the Program's source code as you + You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any @@ -190,12 +190,12 @@ non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. -You may charge any price or no price for each copy that you convey, + You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. -5. Conveying Modified Source Versions. + 5. Conveying Modified Source Versions. -You may convey a work based on the Program, or the modifications to + You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: @@ -220,19 +220,19 @@ terms of section 4, provided that you also meet all of these conditions: interfaces that do not display Appropriate Legal Notices, your work need not make them do so. -A compilation of a covered work with other separate and independent + A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work +beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. -6. Conveying Non-Source Forms. + 6. Conveying Non-Source Forms. -You may convey a covered work in object code form under the terms + You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: @@ -278,75 +278,75 @@ in one of these ways: Source of the work are being offered to the general public at no charge under subsection 6d. -A separable portion of the object code, whose source code is excluded + A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. -A "User Product" is either (1) a "consumer product", which means any + A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product +actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. -"Installation Information" for a User Product means any methods, + "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must +a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. -If you convey an object code work under this section in, or with, or + If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply +by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). -The requirement to provide Installation Information does not include a + The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a +the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. -Corresponding Source conveyed, and Installation Information provided, + Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. -7. Additional Terms. + 7. Additional Terms. -"Additional permissions" are terms that supplement the terms of this + "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions +that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. -When you convey a copy of a covered work, you may at your option + When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. -Notwithstanding any other provision of this License, for material you + Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: @@ -373,74 +373,74 @@ that material) supplement the terms of this License with terms: any liability that these contractual assumptions directly impose on those licensors and authors. -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains +restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. -If you add terms to a covered work in accord with this section, you + If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. -Additional terms, permissive or non-permissive, may be stated in the + Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. -8. Termination. + 8. Termination. -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). -However, if you cease all violation of this License, then your + However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. -Moreover, your license from a particular copyright holder is + Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. -Termination of your rights under this section does not terminate the + Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently +this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. -9. Acceptance Not Required for Having Copies. + 9. Acceptance Not Required for Having Copies. -You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, +to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. -10. Automatic Licensing of Downstream Recipients. + 10. Automatic Licensing of Downstream Recipients. -Each time you convey a covered work, the recipient automatically + Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible +propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. -An "entity transaction" is a transaction transferring control of an + An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered +organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could @@ -448,43 +448,43 @@ give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. -11. Patents. + 11. Patents. -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". -A contributor's "essential patent claims" are all patent claims + A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For +consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. -Each contributor grants you a non-exclusive, worldwide, royalty-free + Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. -In the following three paragraphs, a "patent license" is any express + In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a +sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. -If you convey a covered work, knowingly relying on a patent license, + If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, @@ -492,13 +492,13 @@ then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have +license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. -If, pursuant to or in connection with a single transaction or + If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify @@ -506,10 +506,10 @@ or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. -A patent license is "discriminatory" if it does not include within + A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered +specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying @@ -521,83 +521,83 @@ for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. -Nothing in this License shall be construed as excluding or limiting + Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. -12. No Surrender of Others' Freedom. + 12. No Surrender of Others' Freedom. -If conditions are imposed on you (whether by court order, agreement or + If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a +excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you +not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. -13. Remote Network Interaction; Use with the GNU General Public License. + 13. Remote Network Interaction; Use with the GNU General Public License. -Notwithstanding any other provision of this License, if you modify the + Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source +means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. -Notwithstanding any other provision of this License, you have + Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this +combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. -14. Revised Versions of this License. + 14. Revised Versions of this License. -The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. -Each version is given a distinguishing version number. If the + Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the +Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. -If the Program specifies that a proxy can decide which future + If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. -15. Disclaimer of Warranty. + 15. Disclaimer of Warranty. -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. -16. Limitation of Liability. + 16. Limitation of Liability. -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE @@ -607,9 +607,9 @@ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -17. Interpretation of Sections 15 and 16. + 17. Interpretation of Sections 15 and 16. -If the disclaimer of warranty and limitation of liability provided + If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the @@ -620,16 +620,16 @@ copy of the Program in return for a fee. How to Apply These Terms to Your New Programs -If you develop a new program, and you want it to be of the greatest + If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. -To do so, attach the following notices to the program. It is safest + To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - Revolt Delta + Revolt Project Copyright (C) 2022 Pawel Makles This program is free software: you can redistribute it and/or modify @@ -643,19 +643,19 @@ the "copyright" line and a pointer to where the full notice is found. GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. -If your software can interact with users remotely through a computer + If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its +get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different +of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. -You should also get your employer (if you work as a programmer) or school, + You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see -. +. diff --git a/crates/bonfire/.github/FUNDING.yml b/crates/bonfire/.github/FUNDING.yml new file mode 100644 index 00000000..dcf3dd90 --- /dev/null +++ b/crates/bonfire/.github/FUNDING.yml @@ -0,0 +1,2 @@ +ko_fi: insertish +custom: https://insrt.uk/donate diff --git a/crates/bonfire/.github/workflows/docker.yml b/crates/bonfire/.github/workflows/docker.yml new file mode 100644 index 00000000..77cdbc0b --- /dev/null +++ b/crates/bonfire/.github/workflows/docker.yml @@ -0,0 +1,98 @@ +name: Docker + +on: + push: + branches: + - "master" + tags: + - "*" + paths-ignore: + - ".github/**" + - "!.github/workflows/docker.yml" + - ".vscode/**" + - ".gitignore" + - ".gitlab-ci.yml" + - "LICENSE" + - "README" + pull_request: + branches: + - "master" + paths: + - "Dockerfile" + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + architecture: [linux/amd64] + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: "recursive" + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Cache Docker layers + uses: actions/cache@v2 + with: + path: /tmp/.buildx-cache/${{ matrix.architecture }} + key: ${{ runner.os }}-buildx-${{ matrix.architecture }}-${{ github.sha }} + - name: Build + uses: docker/build-push-action@v2 + with: + context: . + platforms: ${{ matrix.architecture }} + cache-from: type=local,src=/tmp/.buildx-cache/${{ matrix.architecture }} + cache-to: type=local,dest=/tmp/.buildx-cache-new/${{ matrix.architecture }},mode=max + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache/${{ matrix.architecture }} + mv /tmp/.buildx-cache-new/${{ matrix.architecture }} /tmp/.buildx-cache/${{ matrix.architecture }} + + publish_amd64: + needs: [test] + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: "recursive" + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Cache amd64 Docker layers + uses: actions/cache@v2 + with: + path: /tmp/.buildx-cache/linux/amd64 + key: ${{ runner.os }}-buildx-linux/amd64-${{ github.sha }} + - name: Docker meta + id: meta + uses: docker/metadata-action@v3 + with: + images: ghcr.io/revoltchat/bonfire + - name: Login to Github Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and publish + uses: docker/build-push-action@v2 + with: + context: . + push: true + platforms: linux/amd64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=local,src=/tmp/.buildx-cache/linux/amd64 + cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache diff --git a/crates/bonfire/.github/workflows/rust.yaml b/crates/bonfire/.github/workflows/rust.yaml new file mode 100644 index 00000000..4386b19f --- /dev/null +++ b/crates/bonfire/.github/workflows/rust.yaml @@ -0,0 +1,33 @@ +name: Rust build and test + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + check: + name: Rust project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install latest nightly + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + components: rustfmt, clippy + + - name: Run cargo build + uses: actions-rs/cargo@v1 + with: + command: build + + - name: Run cargo test + uses: actions-rs/cargo@v1 + with: + command: test diff --git a/crates/bonfire/.github/workflows/triage_issue.yml b/crates/bonfire/.github/workflows/triage_issue.yml new file mode 100644 index 00000000..b625a98b --- /dev/null +++ b/crates/bonfire/.github/workflows/triage_issue.yml @@ -0,0 +1,49 @@ +name: Add Issue to Board + +on: + issues: + types: [opened] + +jobs: + track_issue: + runs-on: ubuntu-latest + steps: + - name: Get project data + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + run: | + gh api graphql -f query=' + query { + organization(login: "revoltchat"){ + projectNext(number: 3) { + id + fields(first:20) { + nodes { + id + name + settings + } + } + } + } + }' > project_data.json + + echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV + echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV + echo 'TODO_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV + + - name: Add issue to project + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + ISSUE_ID: ${{ github.event.issue.node_id }} + run: | + item_id="$( gh api graphql -f query=' + mutation($project:ID!, $issue:ID!) { + addProjectNextItem(input: {projectId: $project, contentId: $issue}) { + projectNextItem { + id + } + } + }' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')" + + echo 'ITEM_ID='$item_id >> $GITHUB_ENV diff --git a/crates/bonfire/.github/workflows/triage_pr.yml b/crates/bonfire/.github/workflows/triage_pr.yml new file mode 100644 index 00000000..df07fe88 --- /dev/null +++ b/crates/bonfire/.github/workflows/triage_pr.yml @@ -0,0 +1,72 @@ +name: Add PR to Board + +on: + pull_request_target: + types: [opened, synchronize, ready_for_review, review_requested] + +jobs: + track_pr: + runs-on: ubuntu-latest + steps: + - name: Get project data + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + run: | + gh api graphql -f query=' + query { + organization(login: "revoltchat"){ + projectNext(number: 3) { + id + fields(first:20) { + nodes { + id + name + settings + } + } + } + } + }' > project_data.json + + echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV + echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV + echo 'INCOMING_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Incoming PRs") |.id' project_data.json) >> $GITHUB_ENV + + - name: Add PR to project + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + PR_ID: ${{ github.event.pull_request.node_id }} + run: | + item_id="$( gh api graphql -f query=' + mutation($project:ID!, $pr:ID!) { + addProjectNextItem(input: {projectId: $project, contentId: $pr}) { + projectNextItem { + id + } + } + }' -f project=$PROJECT_ID -f pr=$PR_ID --jq '.data.addProjectNextItem.projectNextItem.id')" + + echo 'ITEM_ID='$item_id >> $GITHUB_ENV + + - name: Set fields + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + run: | + gh api graphql -f query=' + mutation ( + $project: ID! + $item: ID! + $status_field: ID! + $status_value: String! + ) { + set_status: updateProjectNextItemField(input: { + projectId: $project + itemId: $item + fieldId: $status_field + value: $status_value + }) { + projectNextItem { + id + } + } + }' -f project=$PROJECT_ID -f item=$ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.INCOMING_OPTION_ID }} --silent diff --git a/crates/bonfire/.gitignore b/crates/bonfire/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/crates/bonfire/.gitignore @@ -0,0 +1 @@ +/target diff --git a/crates/bonfire/Cargo.lock b/crates/bonfire/Cargo.lock new file mode 100644 index 00000000..401b6367 --- /dev/null +++ b/crates/bonfire/Cargo.lock @@ -0,0 +1,4680 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.3", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "async-channel" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-mutex", + "blocking", + "futures-lite", + "num_cpus", + "once_cell", + "tokio 0.2.25", + "tokio 1.15.0", +] + +[[package]] +name = "async-io" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" +dependencies = [ + "concurrent-queue", + "futures-lite", + "libc", + "log", + "once_cell", + "parking", + "polling", + "slab", + "socket2 0.4.2", + "waker-fn", + "winapi 0.3.9", +] + +[[package]] +name = "async-lock" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-process" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6" +dependencies = [ + "async-io", + "blocking", + "cfg-if 1.0.0", + "event-listener", + "futures-lite", + "libc", + "once_cell", + "signal-hook", + "winapi 0.3.9", +] + +[[package]] +name = "async-recursion" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "async-std" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952" +dependencies = [ + "async-attributes", + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "num_cpus", + "once_cell", + "pin-project-lite 0.2.8", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-std-resolver" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf3e776afdf3a2477ef4854b85ba0dff3bd85792f685fb3c68948b4d304e4f0" +dependencies = [ + "async-std", + "async-trait", + "futures-io", + "futures-util", + "pin-utils", + "trust-dns-resolver 0.20.4", +] + +[[package]] +name = "async-stream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "async-task" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" + +[[package]] +name = "async-trait" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "async-tungstenite" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fe458e5f0c283bd6315a884d30f90b654c612b2c16d676730ba71f23cf160a" +dependencies = [ + "async-std", + "futures-io", + "futures-util", + "log", + "pin-project-lite 0.2.8", + "tungstenite", +] + +[[package]] +name = "atomic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" +dependencies = [ + "autocfg 1.1.0", +] + +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "autocfg" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.1.0", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + +[[package]] +name = "base32" +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.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "binascii" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "blocking" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046e47d4b2d391b1f6f8b407b1deb8dee56c1852ccd868becf2710f601b5f427" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "once_cell", +] + +[[package]] +name = "bonfire" +version = "1.0.6-patch.2" +dependencies = [ + "async-std", + "async-tungstenite", + "futures", + "log", + "once_cell", + "querystring", + "revolt-quark", + "rmp-serde", + "serde", + "serde_json", +] + +[[package]] +name = "bson" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0aa578035b938855a710ba58d43cfb4d435f3619f99236fb35922a574d6cb1" +dependencies = [ + "base64 0.13.0", + "chrono", + "hex", + "lazy_static", + "linked-hash-map", + "rand 0.7.3", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "bson" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41539b5c502b7c4e7b8af8ef07e5c442fe79ceba62a2aad8e62bd589b9454745" +dependencies = [ + "ahash", + "base64 0.13.0", + "chrono", + "hex", + "indexmap", + "lazy_static", + "rand 0.8.4", + "serde", + "serde_bytes", + "serde_json", + "uuid", +] + +[[package]] +name = "bumpalo" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "js-sys", + "libc", + "num-integer", + "num-traits", + "serde", + "time 0.1.44", + "wasm-bindgen", + "winapi 0.3.9", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "combine" +version = "4.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062" +dependencies = [ + "bytes 1.1.0", + "futures-core", + "memchr", + "pin-project-lite 0.2.8", + "tokio 1.15.0", + "tokio-util 0.6.9", +] + +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "const_fn" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "cookie" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d" +dependencies = [ + "percent-encoding", + "time 0.2.27", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array 0.14.4", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "ctor" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +dependencies = [ + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core 0.10.2", + "darling_macro 0.10.2", +] + +[[package]] +name = "darling" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" +dependencies = [ + "darling_core 0.13.1", + "darling_macro 0.13.1", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote 1.0.14", + "strsim 0.9.3", + "syn 1.0.84", +] + +[[package]] +name = "darling_core" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote 1.0.14", + "strsim 0.10.0", + "syn 1.0.84", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core 0.10.2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "darling_macro" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" +dependencies = [ + "darling_core 0.13.1", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + +[[package]] +name = "deadqueue" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f13842c4b01ed03514527b625ae001f43b74158c7b0402a5369010cc38c3c8cf" +dependencies = [ + "crossbeam-queue", + "tokio 1.15.0", +] + +[[package]] +name = "debugid" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ee87af31d84ef885378aebca32be3d682b0e0dc119d5b4860a2c5bb5046730" +dependencies = [ + "serde", + "uuid", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "devise" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595" +dependencies = [ + "devise_codegen", + "devise_core", +] + +[[package]] +name = "devise_codegen" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2" +dependencies = [ + "devise_core", + "quote 1.0.14", +] + +[[package]] +name = "devise_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0" +dependencies = [ + "bitflags", + "proc-macro2", + "proc-macro2-diagnostics", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", +] + +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "dyn-clone" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "encoding_rs" +version = "0.8.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "enum-as-inner" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" +dependencies = [ + "heck", + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "erased-serde" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad132dd8d0d0b546348d7d86cb3191aad14b34e5f979781fc005c80d4ac67ffd" +dependencies = [ + "serde", +] + +[[package]] +name = "err-derive" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22deed3a8124cff5fa835713fa105621e43bbdc46690c3a6b68328a012d350d4" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote 1.0.14", + "rustversion", + "syn 1.0.84", + "synstructure", +] + +[[package]] +name = "event-listener" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "fastrand" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2" +dependencies = [ + "instant", +] + +[[package]] +name = "figment" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df" +dependencies = [ + "atomic", + "pear", + "serde", + "toml", + "uncased", + "version_check", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "fuchsia-cprng" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite 0.2.8", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite 0.2.8", + "pin-utils", + "slab", +] + +[[package]] +name = "generator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "winapi 0.3.9", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "gloo-timers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f16c88aa13d2656ef20d1c042086b8767bbe2bdb62526894275a1b062161b2e" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" +dependencies = [ + "bytes 1.1.0", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio 1.15.0", + "tokio-util 0.6.9", + "tracing", +] + +[[package]] +name = "handlebars" +version = "3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3" +dependencies = [ + "log", + "pest", + "pest_derive", + "quick-error 2.0.1", + "serde", + "serde_json", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi 0.3.9", +] + +[[package]] +name = "http" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +dependencies = [ + "bytes 1.1.0", + "fnv", + "itoa 1.0.1", +] + +[[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", +] + +[[package]] +name = "http-body" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +dependencies = [ + "bytes 1.1.0", + "http", + "pin-project-lite 0.2.8", +] + +[[package]] +name = "httparse" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error 1.2.3", +] + +[[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.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" +dependencies = [ + "bytes 1.1.0", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.11", + "http", + "http-body 0.4.4", + "httparse", + "httpdate 1.0.2", + "itoa 0.4.8", + "pin-project-lite 0.2.8", + "socket2 0.4.2", + "tokio 1.15.0", + "tower-service", + "tracing", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes 1.1.0", + "hyper 0.14.16", + "native-tls", + "tokio 1.15.0", + "tokio-native-tls", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + +[[package]] +name = "impl_ops" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f97a5f38dd3ccfbe7aa80f4a0c00930f21b922c74195be0201c51028f22dcf" + +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg 1.1.0", + "hashbrown", + "serde", +] + +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + +[[package]] +name = "instant" +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", +] + +[[package]] +name = "ipconfig" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +dependencies = [ + "socket2 0.3.19", + "widestring", + "winapi 0.3.9", + "winreg 0.6.2", +] + +[[package]] +name = "ipnet" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" + +[[package]] +name = "iso8601-timestamp" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acc02e0ca75f26cafde38a8483b47b3f2474bc214e4f51e33d32896ef210c918" +dependencies = [ + "generic-array 0.14.4", + "schemars", + "serde", + "time 0.3.9", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lettre" +version = "0.10.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d8da8f34d086b081c9cc3b57d3bb3b51d16fc06b5c848a188e2f14d58ac2a5" +dependencies = [ + "base64 0.13.0", + "fastrand", + "futures-util", + "hostname", + "httpdate 1.0.2", + "idna", + "mime", + "native-tls", + "nom", + "once_cell", + "quoted_printable", + "regex", +] + +[[package]] +name = "libc" +version = "0.2.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "linkify" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d9967eb7d0bc31c39c6f52e8fce42991c0cd1f7a2078326f0b7a399a584c8d" +dependencies = [ + "memchr", +] + +[[package]] +name = "lock_api" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", + "value-bag", +] + +[[package]] +name = "loom" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5c7d328e32cc4954e8e01193d7f0ef5ab257b5090b70a964e099a36034309" +dependencies = [ + "cfg-if 1.0.0", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "md-5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg 1.1.0", +] + +[[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 0.2.2", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log", + "miow 0.3.7", + "ntapi", + "winapi 0.3.9", +] + +[[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 = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "mobc" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f76d2f2e2dcbb00a8d3b2b09f026a74a82693ea52cd071647aa6cfa7f1ff37e" +dependencies = [ + "async-std", + "async-trait", + "futures-channel", + "futures-core", + "futures-timer", + "futures-util", + "log", + "tokio 1.15.0", +] + +[[package]] +name = "mobc-redis" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7b5db77b37c9224d5b9949b214041ea3e1c15b6b1e5dd24a5acb8e73975d6d6" +dependencies = [ + "mobc", + "redis 0.19.0", +] + +[[package]] +name = "mongodb" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88543485b334691ccd284ea2d6033a5d9b0e5e77db6a0aa7a2da4caaa458f4f9" +dependencies = [ + "async-std", + "async-trait", + "base64 0.11.0", + "bitflags", + "bson 1.2.4", + "chrono", + "derivative", + "err-derive", + "futures", + "hex", + "hmac", + "lazy_static", + "md-5", + "os_info", + "pbkdf2", + "percent-encoding", + "rand 0.7.3", + "rustls 0.17.0", + "rustls-pemfile", + "serde", + "serde_with", + "sha-1 0.9.8", + "sha2", + "socket2 0.3.19", + "stringprep", + "strsim 0.10.0", + "take_mut", + "time 0.1.44", + "tokio 0.2.25", + "tokio-rustls 0.13.1", + "trust-dns-proto 0.19.7", + "trust-dns-resolver 0.19.7", + "typed-builder 0.4.1", + "uuid", + "version_check", + "webpki", + "webpki-roots", +] + +[[package]] +name = "mongodb" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bacb6f8cee6bf010d7bc57550d859f6a4ffe255eb8c9a7014637fe988eaece64" +dependencies = [ + "async-std", + "async-std-resolver", + "async-trait", + "base64 0.13.0", + "bitflags", + "bson 2.1.0", + "chrono", + "derivative", + "futures-core", + "futures-executor", + "futures-io", + "futures-util", + "hex", + "hmac", + "lazy_static", + "md-5", + "os_info", + "pbkdf2", + "percent-encoding", + "rand 0.8.4", + "rustls 0.19.1", + "rustls-pemfile", + "serde", + "serde_with", + "sha-1 0.9.8", + "sha2", + "socket2 0.4.2", + "stringprep", + "strsim 0.10.0", + "take_mut", + "thiserror", + "tokio 1.15.0", + "tokio-rustls 0.22.0", + "tokio-util 0.6.9", + "trust-dns-proto 0.20.4", + "trust-dns-resolver 0.20.4", + "typed-builder 0.9.1", + "uuid", + "version_check", + "webpki", + "webpki-roots", +] + +[[package]] +name = "multer" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f8f35e687561d5c1667590911e6698a8cb714a134a7505718a182e7bc9d3836" +dependencies = [ + "bytes 1.1.0", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log", + "memchr", + "mime", + "spin 0.9.2", + "tokio 1.15.0", + "tokio-util 0.6.9", + "version_check", +] + +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand 0.8.4", +] + +[[package]] +name = "native-tls" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +dependencies = [ + "memchr", + "minimal-lexical", + "version_check", +] + +[[package]] +name = "ntapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg 1.1.0", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg 1.1.0", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "720d3ea1055e4e4574c0c0b0f8c3fd4f24c4cdaf465948206dea090b57b526ad" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "num_threads" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" +dependencies = [ + "libc", +] + +[[package]] +name = "object" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +dependencies = [ + "memchr", +] + +[[package]] +name = "okapi" +version = "0.7.0-rc.1" +source = "git+https://github.com/insertish/okapi?rev=dcf0df115596ee07a587a7a543cddf3d7944645b#dcf0df115596ee07a587a7a543cddf3d7944645b" +dependencies = [ + "log", + "schemars", + "serde", + "serde_json", +] + +[[package]] +name = "once_cell" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +dependencies = [ + "autocfg 1.1.0", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "optional_struct" +version = "0.2.0" +source = "git+https://github.com/insertish/OptionalStruct?rev=e275d2726595474632485934aa0887fa52281f70#e275d2726595474632485934aa0887fa52281f70" +dependencies = [ + "quote 0.3.15", + "syn 0.11.11", +] + +[[package]] +name = "os_info" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023df84d545ef479cf67fd2f4459a613585c9db4852c2fad12ab70587859d340" +dependencies = [ + "log", + "winapi 0.3.9", +] + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "pbkdf2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +dependencies = [ + "crypto-mac", +] + +[[package]] +name = "pear" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1 0.8.2", +] + +[[package]] +name = "pin-project" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" + +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" + +[[package]] +name = "polling" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "log", + "wepoll-ffi", + "winapi 0.3.9", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "proc-macro-crate" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dada8c9981fcf32929c3c0f0cd796a9284aca335565227ed88c83babb1d43dc" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid 0.2.2", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", + "version_check", + "yansi", +] + +[[package]] +name = "querystring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9318ead08c799aad12a55a3e78b82e0b6167271ffd1f627b758891282f739187" + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" + +[[package]] +name = "quote" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "quoted_printable" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fee2dce59f7a43418e3382c766554c614e06a552d53a8f07ef499ea4b332c0f" + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.8", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc 0.1.0", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg 0.1.2", + "rand_xorshift", + "winapi 0.3.9", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", + "rand_pcg 0.2.1", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", + "rand_hc 0.3.1", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.3.1", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.3", +] + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core 0.6.3", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi 0.3.9", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "wasm-bindgen", + "winapi 0.3.9", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rauth" +version = "1.0.0" +source = "git+https://github.com/insertish/rauth?rev=001a9698c56cea79e69e4ae71d7bc2cb48aec1a6#001a9698c56cea79e69e4ae71d7bc2cb48aec1a6" +dependencies = [ + "async-std", + "base32", + "chrono", + "futures", + "handlebars", + "json", + "lazy_static", + "lettre", + "mongodb 1.2.5", + "nanoid", + "okapi", + "rand 0.8.4", + "regex", + "reqwest", + "rocket", + "rocket_okapi", + "rust-argon2", + "schemars", + "serde", + "serde_json", + "totp-lite", + "ulid 0.4.1", + "validator", + "wither", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redis" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a6ddfecac9391fed21cce10e83c65fa4abafd77df05c98b1c647c65374ce9b3" +dependencies = [ + "async-std", + "async-trait", + "bytes 1.1.0", + "combine", + "dtoa", + "futures-util", + "itoa 0.4.8", + "percent-encoding", + "pin-project-lite 0.2.8", + "sha1", + "tokio 1.15.0", + "tokio-util 0.6.9", + "url", +] + +[[package]] +name = "redis" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80b5f38d7f5a020856a0e16e40a9cfabf88ae8f0e4c2dcd8a3114c1e470852" +dependencies = [ + "async-std", + "async-trait", + "bytes 1.1.0", + "combine", + "dtoa", + "futures-util", + "itoa 0.4.8", + "percent-encoding", + "pin-project-lite 0.2.8", + "tokio 1.15.0", + "tokio-util 0.6.9", + "url", +] + +[[package]] +name = "redis-kiss" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4f2f48fb776a308331c4a1ecb3a7b7b99f4d46c6beb2fc03db2da0f63fcad6" +dependencies = [ + "bincode", + "lazy_static", + "mobc", + "mobc-redis", + "redis 0.21.5", + "rmp-serde", + "serde", + "serde_json", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "regex" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "reqwest" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" +dependencies = [ + "base64 0.13.0", + "bytes 1.1.0", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.11", + "http", + "http-body 0.4.4", + "hyper 0.14.16", + "hyper-tls 0.5.0", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite 0.2.8", + "serde", + "serde_json", + "serde_urlencoded", + "tokio 1.15.0", + "tokio-native-tls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.10.1", +] + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error 1.2.3", +] + +[[package]] +name = "revolt-quark" +version = "0.1.0" +source = "git+https://github.com/revoltchat/quark?rev=076755e513aa6b1e955cf917f60aad9e62a73883#076755e513aa6b1e955cf917f60aad9e62a73883" +dependencies = [ + "async-recursion", + "async-std", + "async-trait", + "base64 0.13.0", + "bincode", + "bitfield", + "bson 2.1.0", + "deadqueue", + "dotenv", + "futures", + "impl_ops", + "iso8601-timestamp", + "lazy_static", + "linkify", + "log", + "mongodb 2.1.0", + "nanoid", + "num_enum", + "okapi", + "optional_struct", + "pretty_env_logger", + "rauth", + "redis-kiss", + "regex", + "reqwest", + "rocket", + "rocket_okapi", + "schemars", + "sentry", + "serde", + "serde_json", + "ulid 0.5.0", + "validator", + "web-push", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi 0.3.9", +] + +[[package]] +name = "rmp" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f55e5fa1446c4d5dd1f5daeed2a4fe193071771a2636274d0d7a3b082aa7ad6" +dependencies = [ + "byteorder", + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3eedffbfcc6a428f230c04baf8f59bd73c1781361e4286111fe900849aaddaf" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "rocket" +version = "0.5.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a71c18c42a0eb15bf3816831caf0dad11e7966f2a41aaf486a701979c4dd1f2" +dependencies = [ + "async-stream", + "async-trait", + "atomic", + "atty", + "binascii", + "bytes 1.1.0", + "either", + "figment", + "futures", + "indexmap", + "log", + "memchr", + "multer", + "num_cpus", + "parking_lot", + "pin-project-lite 0.2.8", + "rand 0.8.4", + "ref-cast", + "rocket_codegen", + "rocket_http", + "serde", + "serde_json", + "state", + "tempfile", + "time 0.2.27", + "tokio 1.15.0", + "tokio-stream", + "tokio-util 0.6.9", + "ubyte", + "version_check", + "yansi", +] + +[[package]] +name = "rocket_codegen" +version = "0.5.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66f5fa462f7eb958bba8710c17c5d774bbbd59809fa76fb1957af7e545aea8bb" +dependencies = [ + "devise", + "glob", + "indexmap", + "proc-macro2", + "quote 1.0.14", + "rocket_http", + "syn 1.0.84", + "unicode-xid 0.2.2", +] + +[[package]] +name = "rocket_http" +version = "0.5.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c8b7d512d2fcac2316ebe590cde67573844b99e6cc9ee0f53375fa16e25ebd" +dependencies = [ + "cookie", + "either", + "http", + "hyper 0.14.16", + "indexmap", + "log", + "memchr", + "mime", + "parking_lot", + "pear", + "percent-encoding", + "pin-project-lite 0.2.8", + "ref-cast", + "serde", + "smallvec", + "stable-pattern", + "state", + "time 0.2.27", + "tokio 1.15.0", + "uncased", +] + +[[package]] +name = "rocket_okapi" +version = "0.8.0-rc.1" +source = "git+https://github.com/insertish/okapi?rev=dcf0df115596ee07a587a7a543cddf3d7944645b#dcf0df115596ee07a587a7a543cddf3d7944645b" +dependencies = [ + "either", + "log", + "okapi", + "rocket", + "rocket_okapi_codegen", + "schemars", + "serde", + "serde_json", +] + +[[package]] +name = "rocket_okapi_codegen" +version = "0.8.0-rc.1" +source = "git+https://github.com/insertish/okapi?rev=dcf0df115596ee07a587a7a543cddf3d7944645b#dcf0df115596ee07a587a7a543cddf3d7944645b" +dependencies = [ + "darling 0.13.1", + "proc-macro2", + "quote 1.0.14", + "rocket_http", + "syn 1.0.84", +] + +[[package]] +name = "rust-argon2" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" +dependencies = [ + "base64 0.13.0", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.9", +] + +[[package]] +name = "rustls" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1" +dependencies = [ + "base64 0.11.0", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64 0.13.0", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64 0.13.0", +] + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi 0.3.9", +] + +[[package]] +name = "schemars" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6b5a3c80cea1ab61f4260238409510e814e38b4b563c06044edf91e7dc070e3" +dependencies = [ + "dyn-clone", + "indexmap", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ae4dce13e8614c46ac3c38ef1c0d668b101df6ac39817aebdaa26642ddae9b" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "serde_derive_internals", + "syn 1.0.84", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted 0.7.1", +] + +[[package]] +name = "security-framework" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "sentry" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d23af89cf3e40dffb53f974e9a21653353b3e21cf51633aa58006f2a0caf8a" +dependencies = [ + "httpdate 1.0.2", + "reqwest", + "sentry-backtrace", + "sentry-contexts", + "sentry-core", + "sentry-panic", + "tokio 1.15.0", +] + +[[package]] +name = "sentry-backtrace" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8158a446429420acdf6a4f75192ee8929da16a0c41c89a1c34b2e0f1eaebcc02" +dependencies = [ + "backtrace", + "lazy_static", + "regex", + "sentry-core", +] + +[[package]] +name = "sentry-contexts" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3bda8a1e3213f1944da2d42f3081ea9f3717105bb2a6b0a8fe4f5e603010a3" +dependencies = [ + "hostname", + "libc", + "rustc_version 0.4.0", + "sentry-core", + "uname", +] + +[[package]] +name = "sentry-core" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56333f11be3a78131c67637f7611339df8af7ad9af831226585a457df75f9e3b" +dependencies = [ + "lazy_static", + "rand 0.8.4", + "sentry-types", + "serde", + "serde_json", +] + +[[package]] +name = "sentry-panic" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b957b1965c450acd220a27806fe1f2dec998d393973ebae797936b12df1c7416" +dependencies = [ + "sentry-backtrace", + "sentry-core", +] + +[[package]] +name = "sentry-types" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825fd3382e2397007499a910e0184e55f7837cb0df4af30ae62bd2123e2ebcd6" +dependencies = [ + "debugid", + "getrandom 0.2.3", + "hex", + "serde", + "serde_json", + "thiserror", + "time 0.3.9", + "url", + "uuid", +] + +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "serde_derive_internals" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "serde_json" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +dependencies = [ + "indexmap", + "itoa 1.0.1", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.1", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1e6ec4d8950e5b1e894eac0d360742f3b1407a6078a604a731c4b3f49cefbc" +dependencies = [ + "rustversion", + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12e47be9471c72889ebafb5e14d5ff930d89ae7a67bbdb5f8abb564f845a927e" +dependencies = [ + "darling 0.13.1", + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" + +[[package]] +name = "stable-pattern" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" +dependencies = [ + "memchr", +] + +[[package]] +name = "standback" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" +dependencies = [ + "version_check", +] + +[[package]] +name = "state" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cf4f5369e6d3044b5e365c9690f451516ac8f0954084622b49ea3fde2f6de5" +dependencies = [ + "loom", +] + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version 0.2.3", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "serde", + "serde_derive", + "syn 1.0.84", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote 1.0.14", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn 1.0.84", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + +[[package]] +name = "stringprep" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +dependencies = [ + "quote 0.3.15", + "synom", + "unicode-xid 0.0.4", +] + +[[package]] +name = "syn" +version = "1.0.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "unicode-xid 0.2.2", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +dependencies = [ + "unicode-xid 0.0.4", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", + "unicode-xid 0.2.2", +] + +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi 0.3.9", +] + +[[package]] +name = "time" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros 0.1.1", + "version_check", + "winapi 0.3.9", +] + +[[package]] +name = "time" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +dependencies = [ + "itoa 1.0.1", + "libc", + "num_threads", + "time-macros 0.2.4", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + +[[package]] +name = "time-macros-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote 1.0.14", + "standback", + "syn 1.0.84", +] + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "0.2.25" +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", + "tokio-macros 0.2.6", +] + +[[package]] +name = "tokio" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" +dependencies = [ + "bytes 1.1.0", + "libc", + "memchr", + "mio 0.7.14", + "num_cpus", + "once_cell", + "pin-project-lite 0.2.8", + "signal-hook-registry", + "tokio-macros 1.7.0", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio 1.15.0", +] + +[[package]] +name = "tokio-rustls" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15cb62a0d2770787abc96e99c1cd98fcf17f94959f3af63ca85bdfb203f051b4" +dependencies = [ + "futures-core", + "rustls 0.17.0", + "tokio 0.2.25", + "webpki", +] + +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls 0.19.1", + "tokio 1.15.0", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +dependencies = [ + "futures-core", + "pin-project-lite 0.2.8", + "tokio 1.15.0", +] + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +dependencies = [ + "bytes 1.1.0", + "futures-core", + "futures-io", + "futures-sink", + "log", + "pin-project-lite 0.2.8", + "tokio 1.15.0", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "totp-lite" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18009e8be74bfb2e2cc59a63d078d95c042858a1ca1128a294e1f9ce225148b" +dependencies = [ + "digest 0.9.0", + "hmac", + "sha-1 0.9.8", + "sha2", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +dependencies = [ + "cfg-if 1.0.0", + "log", + "pin-project-lite 0.2.8", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "tracing-core" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +dependencies = [ + "lazy_static", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-log" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" +dependencies = [ + "ansi_term", + "lazy_static", + "matchers", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trust-dns-proto" +version = "0.19.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cad71a0c0d68ab9941d2fb6e82f8fb2e86d9945b94e1661dd0aaea2b88215a9" +dependencies = [ + "async-trait", + "backtrace", + "cfg-if 1.0.0", + "enum-as-inner", + "futures", + "idna", + "lazy_static", + "log", + "rand 0.7.3", + "smallvec", + "thiserror", + "tokio 0.2.25", + "url", +] + +[[package]] +name = "trust-dns-proto" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca94d4e9feb6a181c690c4040d7a24ef34018d8313ac5044a61d21222ae24e31" +dependencies = [ + "async-trait", + "cfg-if 1.0.0", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "lazy_static", + "log", + "rand 0.8.4", + "smallvec", + "thiserror", + "tinyvec", + "tokio 1.15.0", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.19.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f593b371175db53a26d0b38ed2978fafb9e9e8d3868b1acd753ea18df0ceb" +dependencies = [ + "cfg-if 0.1.10", + "futures", + "ipconfig", + "lazy_static", + "log", + "lru-cache", + "resolv-conf", + "smallvec", + "thiserror", + "tokio 0.2.25", + "trust-dns-proto 0.19.7", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecae383baad9995efaa34ce8e57d12c3f305e545887472a492b838f4b5cfb77a" +dependencies = [ + "cfg-if 1.0.0", + "futures-util", + "ipconfig", + "lazy_static", + "log", + "lru-cache", + "parking_lot", + "resolv-conf", + "smallvec", + "thiserror", + "tokio 1.15.0", + "trust-dns-proto 0.20.4", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a5198d211a468fa9573edf4919aa88a17515723c766b3a6b3a10536eb7e1ee0" +dependencies = [ + "base64 0.13.0", + "byteorder", + "bytes 1.1.0", + "http", + "httparse", + "log", + "rand 0.8.4", + "sha-1 0.10.0", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typed-builder" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc955f27acc7a547f328f52f4a5a568986a31efec2fc6de865279f3995787b9" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "typed-builder" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a46ee5bd706ff79131be9c94e7edcb82b703c487766a114434e5790361cf08c5" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "ubyte" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42756bb9e708855de2f8a98195643dff31a97f0485d90d8467b39dc24be9e8fe" +dependencies = [ + "serde", +] + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "ulid" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e95a59b292ca0cf9b45be2e52294d1ca6cb24eb11b08ef4376f73f1a00c549" +dependencies = [ + "chrono", + "lazy_static", + "rand 0.6.5", +] + +[[package]] +name = "ulid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "220b18413e1fe5e85a5580b22f44241f82404a66c792c9f3c9eda74c52d9a22e" +dependencies = [ + "chrono", + "rand 0.8.4", +] + +[[package]] +name = "uname" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" +dependencies = [ + "libc", +] + +[[package]] +name = "uncased" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.3", + "serde", +] + +[[package]] +name = "validator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0f08911ab0fee2c5009580f04615fa868898ee57de10692a45da0c3bcc3e5e" +dependencies = [ + "idna", + "lazy_static", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", + "validator_types", +] + +[[package]] +name = "validator_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85135714dba11a1bd0b3eb1744169266f1a38977bf4e3ff5e2e1acb8c2b7eee" +dependencies = [ + "if_chain", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote 1.0.14", + "regex", + "syn 1.0.84", + "validator_types", +] + +[[package]] +name = "validator_types" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded9d97e1d42327632f5f3bae6403c04886e2de3036261ef42deebd931a6a291" +dependencies = [ + "proc-macro2", + "syn 1.0.84", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "value-bag" +version = "1.0.0-alpha.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" +dependencies = [ + "ctor", + "version_check", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote 1.0.14", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn 1.0.84", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[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-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted 0.7.1", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki", +] + +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + +[[package]] +name = "widestring" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "wither" +version = "0.9.0" +source = "git+https://github.com/insertish/wither?rev=25226946ff9a2cafac390968b317df83eaafe31f#25226946ff9a2cafac390968b317df83eaafe31f" +dependencies = [ + "async-trait", + "chrono", + "futures", + "log", + "mongodb 1.2.5", + "serde", + "thiserror", + "ulid 0.4.1", + "wither_derive", +] + +[[package]] +name = "wither_derive" +version = "0.9.0" +source = "git+https://github.com/insertish/wither?rev=25226946ff9a2cafac390968b317df83eaafe31f#25226946ff9a2cafac390968b317df83eaafe31f" +dependencies = [ + "Inflector", + "async-trait", + "darling 0.10.2", + "proc-macro-error", + "proc-macro2", + "quote 1.0.14", + "serde", + "syn 1.0.84", +] + +[[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", +] + +[[package]] +name = "yansi" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" diff --git a/crates/bonfire/Cargo.toml b/crates/bonfire/Cargo.toml new file mode 100644 index 00000000..04e3940f --- /dev/null +++ b/crates/bonfire/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "revolt-bonfire" +version = "1.0.6-patch.2" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# util +log = "*" +once_cell = "1.9.0" + +# parsing +querystring = "1.1.0" + +# quark +revolt-quark = { path = "../quark" } + +# serde +serde_json = "1.0.79" +rmp-serde = "1.0.0" +serde = "1.0.136" + +# async +futures = "0.3.21" +async-tungstenite = { version = "0.17.0", features = ["async-std-runtime"] } +async-std = { version = "1.8.0", features = ["tokio1", "tokio02", "attributes"] } diff --git a/crates/bonfire/Dockerfile b/crates/bonfire/Dockerfile new file mode 100644 index 00000000..97aaab97 --- /dev/null +++ b/crates/bonfire/Dockerfile @@ -0,0 +1,19 @@ +# Build Stage +FROM rustlang/rust:nightly-slim AS builder +USER 0:0 +WORKDIR /home/rust/src + +RUN USER=root cargo new --bin bonfire +WORKDIR /home/rust/src/bonfire +RUN apt-get update && apt-get install -y libssl-dev pkg-config + +COPY Cargo.toml Cargo.lock ./ +COPY src ./src +RUN cargo install --locked --path . + +# Bundle Stage +FROM debian:buster-slim +RUN apt-get update && apt-get install -y ca-certificates +COPY --from=builder /usr/local/cargo/bin/bonfire ./ +EXPOSE 9000 +CMD ["./bonfire"] diff --git a/crates/bonfire/LICENSE b/crates/bonfire/LICENSE new file mode 100644 index 00000000..4481b50d --- /dev/null +++ b/crates/bonfire/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + Revolt Bonfire + Copyright (C) 2022 Pawel Makles + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/crates/bonfire/src/config.rs b/crates/bonfire/src/config.rs new file mode 100644 index 00000000..e9e4370d --- /dev/null +++ b/crates/bonfire/src/config.rs @@ -0,0 +1,150 @@ +use async_tungstenite::tungstenite::{handshake, Message}; +use futures::channel::oneshot::Sender; +use revolt_quark::{Error, Result}; +use serde::{Deserialize, Serialize}; + +/// Enumeration of supported protocol formats +#[derive(Debug)] +pub enum ProtocolFormat { + Json, + Msgpack, +} + +/// User-provided protocol configuration +#[derive(Debug)] +pub struct ProtocolConfiguration { + protocol_version: i32, + format: ProtocolFormat, + session_token: Option, +} + +impl ProtocolConfiguration { + /// Create a new protocol configuration object from provided data + pub fn from( + protocol_version: i32, + format: ProtocolFormat, + session_token: Option, + ) -> Self { + Self { + protocol_version, + format, + session_token, + } + } + + /// Decode some WebSocket message into a T: Deserialize using the client's specified protocol format + pub fn decode<'a, T: Deserialize<'a>>(&self, msg: &'a Message) -> Result { + match self.format { + ProtocolFormat::Json => { + if let Message::Text(text) = msg { + serde_json::from_str(text).map_err(|_| Error::InternalError) + } else { + Err(Error::InternalError) + } + } + ProtocolFormat::Msgpack => { + if let Message::Binary(buf) = msg { + rmp_serde::from_slice(buf).map_err(|_| Error::InternalError) + } else { + Err(Error::InternalError) + } + } + } + } + + /// Encode T: Serialize into a WebSocket message using the client's specified protocol format + pub fn encode(&self, data: &T) -> Message { + match self.format { + ProtocolFormat::Json => { + Message::Text(serde_json::to_string(data).expect("Failed to serialise (as json).")) + } + ProtocolFormat::Msgpack => Message::Binary( + rmp_serde::to_vec_named(data).expect("Failed to serialise (as msgpack)."), + ), + } + } + + /// Set the current session token + pub fn set_session_token(&mut self, token: String) { + self.session_token.replace(token); + } + + /// Get the current session token + pub fn get_session_token(&self) -> &Option { + &self.session_token + } + + /// Get the protocol version specified + pub fn get_protocol_version(&self) -> i32 { + self.protocol_version + } + + /// Get the protocol format specified + pub fn get_protocol_format(&self) -> &ProtocolFormat { + &self.format + } +} + +/// Object holding one side of a channel for receiving the parsed information +pub struct WebsocketHandshakeCallback { + sender: Sender, +} + +impl WebsocketHandshakeCallback { + /// Create a callback using a given sender + pub fn from(sender: Sender) -> Self { + Self { sender } + } +} + +impl handshake::server::Callback for WebsocketHandshakeCallback { + /// Handle request to create a new WebSocket connection + fn on_request( + self, + request: &handshake::server::Request, + response: handshake::server::Response, + ) -> Result { + // Take and parse query parameters from the URI. + let query = request.uri().query().unwrap_or_default(); + let params = querystring::querify(query); + + // Set default values for the protocol. + let mut protocol_version = 1; + let mut format = ProtocolFormat::Json; + let mut session_token = None; + + // Parse and map parameters from key-value to known variables. + for (key, value) in params { + match key { + "version" => { + if let Ok(version) = value.parse() { + protocol_version = version; + } + } + "format" => match value { + "json" => format = ProtocolFormat::Json, + "msgpack" => format = ProtocolFormat::Msgpack, + _ => {} + }, + "token" => session_token = Some(value.into()), + _ => {} + } + } + + // Send configuration information back from this callback. + // We have to use a channel as this function does not borrow mutably. + if self + .sender + .send(ProtocolConfiguration { + protocol_version, + format, + session_token, + }) + .is_ok() + { + Ok(response) + } else { + Err(handshake::server::ErrorResponse::new(None)) + } + } +} diff --git a/crates/bonfire/src/database.rs b/crates/bonfire/src/database.rs new file mode 100644 index 00000000..fe191a0b --- /dev/null +++ b/crates/bonfire/src/database.rs @@ -0,0 +1,19 @@ +use once_cell::sync::OnceCell; +use revolt_quark::{Database, DatabaseInfo}; + +static DBCONN: OnceCell = OnceCell::new(); + +/// Connect Bonfire to the database. +pub async fn connect() { + let database = DatabaseInfo::Auto + .connect() + .await + .expect("Failed to connect to the database."); + + DBCONN.set(database).expect("Setting `Database`"); +} + +/// Get a reference to the current database. +pub fn get_db() -> &'static Database { + DBCONN.get().expect("Valid `Database`") +} diff --git a/crates/bonfire/src/main.rs b/crates/bonfire/src/main.rs new file mode 100644 index 00000000..caf7ac95 --- /dev/null +++ b/crates/bonfire/src/main.rs @@ -0,0 +1,34 @@ +use std::env; + +use async_std::net::TcpListener; +use revolt_quark::presence::presence_clear_region; + +#[macro_use] +extern crate log; + +pub mod config; + +mod database; +mod websocket; + +#[async_std::main] +async fn main() { + // Configure requirements for Bonfire. + let _guard = revolt_quark::setup_logging(); + database::connect().await; + + // Clean up the current region information. + presence_clear_region(None).await; + + // Setup a TCP listener to accept WebSocket connections on. + // By default, we bind to port 9000 on all interfaces. + let bind = env::var("HOST").unwrap_or_else(|_| "0.0.0.0:9000".into()); + info!("Listening on host {bind}"); + let try_socket = TcpListener::bind(bind).await; + let listener = try_socket.expect("Failed to bind"); + + // Start accepting new connections and spawn a client for each connection. + while let Ok((stream, addr)) = listener.accept().await { + websocket::spawn_client(database::get_db(), stream, addr); + } +} diff --git a/crates/bonfire/src/websocket.rs b/crates/bonfire/src/websocket.rs new file mode 100644 index 00000000..e298db3e --- /dev/null +++ b/crates/bonfire/src/websocket.rs @@ -0,0 +1,253 @@ +use std::net::SocketAddr; + +use futures::{channel::oneshot, pin_mut, select, FutureExt, SinkExt, StreamExt, TryStreamExt}; +use revolt_quark::{ + events::{ + client::EventV1, + server::ClientMessage, + state::{State, SubscriptionStateChange}, + }, + models::{user::UserHint, User}, + presence::{presence_create_session, presence_delete_session}, + redis_kiss, Database, +}; + +use async_std::{net::TcpStream, sync::Mutex, task}; + +use crate::config::WebsocketHandshakeCallback; + +/// Spawn a new WebSocket client worker given access to the database, +/// the relevant TCP stream and the remote address of the client. +pub fn spawn_client(db: &'static Database, stream: TcpStream, addr: SocketAddr) { + // Spawn a new Async task to work on. + task::spawn(async move { + info!("User connected from {addr:?}"); + + // Upgrade the TCP connection to a WebSocket connection. + // In this process, we also parse any additional parameters given. + // e.g. wss://example.com?format=json&version=1 + let (sender, receiver) = oneshot::channel(); + if let Ok(ws) = async_tungstenite::accept_hdr_async_with_config( + stream, + WebsocketHandshakeCallback::from(sender), + None, + ) + .await + { + // Verify we've received a valid config, otherwise we should just drop the connection. + if let Ok(mut config) = receiver.await { + info!( + "User {addr:?} provided protocol configuration (version = {}, format = {:?})", + config.get_protocol_version(), + config.get_protocol_format() + ); + + // Split the socket for simultaneously read and write. + let (write, mut read) = ws.split(); + let write = Mutex::new(write); + + // If the user has not provided authentication, request information. + if config.get_session_token().is_none() { + 'outer: while let Ok(message) = read.try_next().await { + if let Ok(ClientMessage::Authenticate { token }) = + config.decode(message.as_ref().unwrap()) + { + config.set_session_token(token); + break 'outer; + } + } + } + + // Try to authenticate the user. + if let Some(token) = config.get_session_token().as_ref() { + match User::from_token(db, token, UserHint::Any).await { + Ok(user) => { + info!("User {addr:?} authenticated as @{}", user.username); + + // Create local state. + let mut state = State::from(user); + let user_id = state.cache.user_id.clone(); + + // Create presence session. + let (first_session, session_id) = + presence_create_session(&user_id, 0).await; + + // Notify socket we have authenticated. + write + .lock() + .await + .send(config.encode(&EventV1::Authenticated)) + .await + .ok(); + + // Download required data to local cache and send Ready payload. + if let Ok(ready_payload) = state.generate_ready_payload(db).await { + write + .lock() + .await + .send(config.encode(&ready_payload)) + .await + .ok(); + + // If this was the first session, notify other users that we just went online. + if first_session { + state.broadcast_presence_change(true).await; + } + + // Create a PubSub connection to poll on. + let listener = async { + if let Ok(mut conn) = redis_kiss::open_pubsub_connection().await + { + loop { + // Check for state changes for subscriptions. + match state.apply_state() { + SubscriptionStateChange::Reset => { + for id in state.iter_subscriptions() { + conn.subscribe(id).await.unwrap(); + } + + #[cfg(debug_assertions)] + info!("{addr:?} has reset their subscriptions"); + } + SubscriptionStateChange::Change { add, remove } => { + for id in remove { + #[cfg(debug_assertions)] + info!("{addr:?} unsubscribing from {id}"); + + conn.unsubscribe(id).await.unwrap(); + } + + for id in add { + #[cfg(debug_assertions)] + info!("{addr:?} subscribing to {id}"); + + conn.subscribe(id).await.unwrap(); + } + } + SubscriptionStateChange::None => {} + } + + // * Debug logging of current subscriptions. + /*#[cfg(debug_assertions)] + info!( + "User {addr:?} is subscribed to {:?}", + state + .iter_subscriptions() + .collect::>() + );*/ + + // Handle incoming events. + match conn.on_message().next().await.map(|item| { + ( + item.get_channel_name().to_string(), + redis_kiss::decode_payload::(&item), + ) + }) { + Some((channel, item)) => { + if let Ok(mut event) = item { + if state + .handle_incoming_event_v1( + db, &mut event, + ) + .await + && write.lock().await + .send(config.encode(&event)) + .await + .is_err() + { + break; + } + } else { + warn!("Failed to deserialise an event for {channel}!"); + } + } + // No more data, assume we disconnected or otherwise + // something bad occurred, so disconnect user. + None => break, + } + } + } + } + .fuse(); + + // Read from WebSocket stream. + let worker = + async { + while let Ok(Some(msg)) = read.try_next().await { + if let Ok(payload) = config.decode(&msg) { + match payload { + ClientMessage::BeginTyping { channel } => { + EventV1::ChannelStartTyping { + id: channel.clone(), + user: user_id.clone(), + } + .p(channel.clone()) + .await; + } + ClientMessage::EndTyping { channel } => { + EventV1::ChannelStopTyping { + id: channel.clone(), + user: user_id.clone(), + } + .p(channel.clone()) + .await; + } + ClientMessage::Ping { data, responded } => { + if responded.is_none() { + write + .lock() + .await + .send(config.encode( + &EventV1::Pong { data }, + )) + .await + .ok(); + } + } + _ => {} + } + } + } + } + .fuse(); + + // Pin both tasks. + pin_mut!(listener, worker); + + // Wait for either disconnect or for listener to die. + select!( + () = listener => {}, + () = worker => {} + ); + + // * Combine the streams back once we are ready to disconnect. + /* ws = read.reunite(write).unwrap(); */ + } + + // Clean up presence session. + let last_session = presence_delete_session(&user_id, session_id).await; + + // If this was the last session, notify other users that we just went offline. + if last_session { + state.broadcast_presence_change(false).await; + } + } + Err(err) => { + write.lock().await.send(config.encode(&err)).await.ok(); + } + } + } + } + + // * Disconnect the WebSocket if it isn't already. + /*ws.close(Some(CloseFrame { + code: CloseCode::Normal, + reason: std::borrow::Cow::from(""), + })) + .await + .unwrap();*/ + } + + info!("User disconnected from {addr:?}"); + }); +} diff --git a/crates/delta/Cargo.toml b/crates/delta/Cargo.toml new file mode 100644 index 00000000..5265f6a3 --- /dev/null +++ b/crates/delta/Cargo.toml @@ -0,0 +1,64 @@ +[package] +name = "revolt-delta" +version = "0.5.3-5-patch.2" +authors = ["Paul Makles "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# Utility +lru = "0.7.0" +url = "2.2.2" +log = "0.4.11" +dotenv = "0.15.0" +dashmap = "5.2.0" +linkify = "0.6.0" +once_cell = "1.4.1" +env_logger = "0.7.1" +lazy_static = "1.4.0" +ctrlc = { version = "3.0", features = ["termination"] } + +# Lang. Utilities +regex = "1" +num_enum = "0.5.1" +impl_ops = "0.1.1" +bitfield = "0.13.2" + +# ID / key generation +ulid = "0.4.1" +nanoid = "0.4.0" + +# serde +serde_json = "1.0.57" +serde = { version = "1.0.115", features = ["derive"] } +validator = { version = "0.14", features = ["derive"] } + +# async +futures = "0.3.8" +chrono = "0.4.15" +async-channel = "1.6.1" +reqwest = { version = "0.11.4", features = ["json"] } +async-std = { version = "1.8.0", features = ["tokio1", "tokio02", "attributes"] } + +# internal util +lettre = "0.10.0-alpha.4" +rauth = { git = "https://github.com/insertish/rauth", rev = "001a9698c56cea79e69e4ae71d7bc2cb48aec1a6" } + +# redis +redis = { version = "0.21.2", features = ["async-std-comp"] } +mobc = { version = "0.7.3" } +mobc-redis = { version = "0.7.0", default-features = false, features = ["async-std-comp"] } + +# web +rocket = { version = "0.5.0-rc.1", default-features = false, features = ["json"] } +mongodb = { version = "1.2.2", features = ["async-std-runtime"], default-features = false } +rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "5843861a88958c16bfaa0b40f0d8910772bcd2f6" } + +# spec generation +schemars = "0.8.8" +# rocket_okapi = "0.8.0-rc.1" +rocket_okapi = { git = "https://github.com/insertish/okapi", rev = "dcf0df115596ee07a587a7a543cddf3d7944645b", features = [ "swagger" ] } + +# quark +revolt-quark = { path = "../quark" } diff --git a/Dockerfile b/crates/delta/Dockerfile similarity index 99% rename from Dockerfile rename to crates/delta/Dockerfile index 1db63a9e..e4d8bd1b 100644 --- a/Dockerfile +++ b/crates/delta/Dockerfile @@ -16,6 +16,7 @@ RUN cargo install --locked --path . FROM debian:buster-slim RUN apt-get update && apt-get install -y ca-certificates COPY --from=builder /usr/local/cargo/bin/revolt ./ + EXPOSE 8000 ENV ROCKET_ADDRESS 0.0.0.0 ENV ROCKET_PORT 8000 diff --git a/crates/delta/LICENSE b/crates/delta/LICENSE new file mode 100644 index 00000000..21f82f17 --- /dev/null +++ b/crates/delta/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + Revolt Delta + Copyright (C) 2022 Pawel Makles + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/assets/templates/reset.html b/crates/delta/assets/templates/reset.html similarity index 100% rename from assets/templates/reset.html rename to crates/delta/assets/templates/reset.html diff --git a/assets/templates/reset.txt b/crates/delta/assets/templates/reset.txt similarity index 100% rename from assets/templates/reset.txt rename to crates/delta/assets/templates/reset.txt diff --git a/assets/templates/reset_plain.html b/crates/delta/assets/templates/reset_plain.html similarity index 100% rename from assets/templates/reset_plain.html rename to crates/delta/assets/templates/reset_plain.html diff --git a/assets/templates/verify.html b/crates/delta/assets/templates/verify.html similarity index 100% rename from assets/templates/verify.html rename to crates/delta/assets/templates/verify.html diff --git a/assets/templates/verify.txt b/crates/delta/assets/templates/verify.txt similarity index 100% rename from assets/templates/verify.txt rename to crates/delta/assets/templates/verify.txt diff --git a/assets/templates/verify_plain.html b/crates/delta/assets/templates/verify_plain.html similarity index 100% rename from assets/templates/verify_plain.html rename to crates/delta/assets/templates/verify_plain.html diff --git a/assets/user/1.png b/crates/delta/assets/user/1.png similarity index 100% rename from assets/user/1.png rename to crates/delta/assets/user/1.png diff --git a/assets/user/2.png b/crates/delta/assets/user/2.png similarity index 100% rename from assets/user/2.png rename to crates/delta/assets/user/2.png diff --git a/assets/user/3.png b/crates/delta/assets/user/3.png similarity index 100% rename from assets/user/3.png rename to crates/delta/assets/user/3.png diff --git a/assets/user/4.png b/crates/delta/assets/user/4.png similarity index 100% rename from assets/user/4.png rename to crates/delta/assets/user/4.png diff --git a/assets/user/5.png b/crates/delta/assets/user/5.png similarity index 100% rename from assets/user/5.png rename to crates/delta/assets/user/5.png diff --git a/assets/user/6.png b/crates/delta/assets/user/6.png similarity index 100% rename from assets/user/6.png rename to crates/delta/assets/user/6.png diff --git a/assets/user/7.png b/crates/delta/assets/user/7.png similarity index 100% rename from assets/user/7.png rename to crates/delta/assets/user/7.png diff --git a/src/main.rs b/crates/delta/src/main.rs similarity index 100% rename from src/main.rs rename to crates/delta/src/main.rs diff --git a/src/routes/bots/create.rs b/crates/delta/src/routes/bots/create.rs similarity index 100% rename from src/routes/bots/create.rs rename to crates/delta/src/routes/bots/create.rs diff --git a/src/routes/bots/delete.rs b/crates/delta/src/routes/bots/delete.rs similarity index 100% rename from src/routes/bots/delete.rs rename to crates/delta/src/routes/bots/delete.rs diff --git a/src/routes/bots/edit.rs b/crates/delta/src/routes/bots/edit.rs similarity index 100% rename from src/routes/bots/edit.rs rename to crates/delta/src/routes/bots/edit.rs diff --git a/src/routes/bots/fetch.rs b/crates/delta/src/routes/bots/fetch.rs similarity index 100% rename from src/routes/bots/fetch.rs rename to crates/delta/src/routes/bots/fetch.rs diff --git a/src/routes/bots/fetch_owned.rs b/crates/delta/src/routes/bots/fetch_owned.rs similarity index 100% rename from src/routes/bots/fetch_owned.rs rename to crates/delta/src/routes/bots/fetch_owned.rs diff --git a/src/routes/bots/fetch_public.rs b/crates/delta/src/routes/bots/fetch_public.rs similarity index 100% rename from src/routes/bots/fetch_public.rs rename to crates/delta/src/routes/bots/fetch_public.rs diff --git a/src/routes/bots/invite.rs b/crates/delta/src/routes/bots/invite.rs similarity index 100% rename from src/routes/bots/invite.rs rename to crates/delta/src/routes/bots/invite.rs diff --git a/src/routes/bots/mod.rs b/crates/delta/src/routes/bots/mod.rs similarity index 100% rename from src/routes/bots/mod.rs rename to crates/delta/src/routes/bots/mod.rs diff --git a/src/routes/channels/channel_ack.rs b/crates/delta/src/routes/channels/channel_ack.rs similarity index 100% rename from src/routes/channels/channel_ack.rs rename to crates/delta/src/routes/channels/channel_ack.rs diff --git a/src/routes/channels/channel_delete.rs b/crates/delta/src/routes/channels/channel_delete.rs similarity index 100% rename from src/routes/channels/channel_delete.rs rename to crates/delta/src/routes/channels/channel_delete.rs diff --git a/src/routes/channels/channel_edit.rs b/crates/delta/src/routes/channels/channel_edit.rs similarity index 100% rename from src/routes/channels/channel_edit.rs rename to crates/delta/src/routes/channels/channel_edit.rs diff --git a/src/routes/channels/channel_fetch.rs b/crates/delta/src/routes/channels/channel_fetch.rs similarity index 100% rename from src/routes/channels/channel_fetch.rs rename to crates/delta/src/routes/channels/channel_fetch.rs diff --git a/src/routes/channels/group_add_member.rs b/crates/delta/src/routes/channels/group_add_member.rs similarity index 100% rename from src/routes/channels/group_add_member.rs rename to crates/delta/src/routes/channels/group_add_member.rs diff --git a/src/routes/channels/group_create.rs b/crates/delta/src/routes/channels/group_create.rs similarity index 100% rename from src/routes/channels/group_create.rs rename to crates/delta/src/routes/channels/group_create.rs diff --git a/src/routes/channels/group_remove_member.rs b/crates/delta/src/routes/channels/group_remove_member.rs similarity index 100% rename from src/routes/channels/group_remove_member.rs rename to crates/delta/src/routes/channels/group_remove_member.rs diff --git a/src/routes/channels/invite_create.rs b/crates/delta/src/routes/channels/invite_create.rs similarity index 100% rename from src/routes/channels/invite_create.rs rename to crates/delta/src/routes/channels/invite_create.rs diff --git a/src/routes/channels/members_fetch.rs b/crates/delta/src/routes/channels/members_fetch.rs similarity index 100% rename from src/routes/channels/members_fetch.rs rename to crates/delta/src/routes/channels/members_fetch.rs diff --git a/src/routes/channels/message_bulk_delete.rs b/crates/delta/src/routes/channels/message_bulk_delete.rs similarity index 100% rename from src/routes/channels/message_bulk_delete.rs rename to crates/delta/src/routes/channels/message_bulk_delete.rs diff --git a/src/routes/channels/message_delete.rs b/crates/delta/src/routes/channels/message_delete.rs similarity index 100% rename from src/routes/channels/message_delete.rs rename to crates/delta/src/routes/channels/message_delete.rs diff --git a/src/routes/channels/message_edit.rs b/crates/delta/src/routes/channels/message_edit.rs similarity index 100% rename from src/routes/channels/message_edit.rs rename to crates/delta/src/routes/channels/message_edit.rs diff --git a/src/routes/channels/message_fetch.rs b/crates/delta/src/routes/channels/message_fetch.rs similarity index 100% rename from src/routes/channels/message_fetch.rs rename to crates/delta/src/routes/channels/message_fetch.rs diff --git a/src/routes/channels/message_query.rs b/crates/delta/src/routes/channels/message_query.rs similarity index 100% rename from src/routes/channels/message_query.rs rename to crates/delta/src/routes/channels/message_query.rs diff --git a/src/routes/channels/message_query_stale.rs b/crates/delta/src/routes/channels/message_query_stale.rs similarity index 100% rename from src/routes/channels/message_query_stale.rs rename to crates/delta/src/routes/channels/message_query_stale.rs diff --git a/src/routes/channels/message_search.rs b/crates/delta/src/routes/channels/message_search.rs similarity index 100% rename from src/routes/channels/message_search.rs rename to crates/delta/src/routes/channels/message_search.rs diff --git a/src/routes/channels/message_send.rs b/crates/delta/src/routes/channels/message_send.rs similarity index 100% rename from src/routes/channels/message_send.rs rename to crates/delta/src/routes/channels/message_send.rs diff --git a/src/routes/channels/mod.rs b/crates/delta/src/routes/channels/mod.rs similarity index 100% rename from src/routes/channels/mod.rs rename to crates/delta/src/routes/channels/mod.rs diff --git a/src/routes/channels/permissions_set.rs b/crates/delta/src/routes/channels/permissions_set.rs similarity index 100% rename from src/routes/channels/permissions_set.rs rename to crates/delta/src/routes/channels/permissions_set.rs diff --git a/src/routes/channels/permissions_set_default.rs b/crates/delta/src/routes/channels/permissions_set_default.rs similarity index 100% rename from src/routes/channels/permissions_set_default.rs rename to crates/delta/src/routes/channels/permissions_set_default.rs diff --git a/src/routes/channels/voice_join.rs b/crates/delta/src/routes/channels/voice_join.rs similarity index 100% rename from src/routes/channels/voice_join.rs rename to crates/delta/src/routes/channels/voice_join.rs diff --git a/src/routes/invites/invite_delete.rs b/crates/delta/src/routes/invites/invite_delete.rs similarity index 100% rename from src/routes/invites/invite_delete.rs rename to crates/delta/src/routes/invites/invite_delete.rs diff --git a/src/routes/invites/invite_fetch.rs b/crates/delta/src/routes/invites/invite_fetch.rs similarity index 100% rename from src/routes/invites/invite_fetch.rs rename to crates/delta/src/routes/invites/invite_fetch.rs diff --git a/src/routes/invites/invite_join.rs b/crates/delta/src/routes/invites/invite_join.rs similarity index 100% rename from src/routes/invites/invite_join.rs rename to crates/delta/src/routes/invites/invite_join.rs diff --git a/src/routes/invites/mod.rs b/crates/delta/src/routes/invites/mod.rs similarity index 100% rename from src/routes/invites/mod.rs rename to crates/delta/src/routes/invites/mod.rs diff --git a/src/routes/mod.rs b/crates/delta/src/routes/mod.rs similarity index 100% rename from src/routes/mod.rs rename to crates/delta/src/routes/mod.rs diff --git a/src/routes/onboard/complete.rs b/crates/delta/src/routes/onboard/complete.rs similarity index 100% rename from src/routes/onboard/complete.rs rename to crates/delta/src/routes/onboard/complete.rs diff --git a/src/routes/onboard/hello.rs b/crates/delta/src/routes/onboard/hello.rs similarity index 100% rename from src/routes/onboard/hello.rs rename to crates/delta/src/routes/onboard/hello.rs diff --git a/src/routes/onboard/mod.rs b/crates/delta/src/routes/onboard/mod.rs similarity index 100% rename from src/routes/onboard/mod.rs rename to crates/delta/src/routes/onboard/mod.rs diff --git a/src/routes/push/mod.rs b/crates/delta/src/routes/push/mod.rs similarity index 100% rename from src/routes/push/mod.rs rename to crates/delta/src/routes/push/mod.rs diff --git a/src/routes/push/subscribe.rs b/crates/delta/src/routes/push/subscribe.rs similarity index 100% rename from src/routes/push/subscribe.rs rename to crates/delta/src/routes/push/subscribe.rs diff --git a/src/routes/push/unsubscribe.rs b/crates/delta/src/routes/push/unsubscribe.rs similarity index 100% rename from src/routes/push/unsubscribe.rs rename to crates/delta/src/routes/push/unsubscribe.rs diff --git a/src/routes/root.rs b/crates/delta/src/routes/root.rs similarity index 100% rename from src/routes/root.rs rename to crates/delta/src/routes/root.rs diff --git a/src/routes/servers/ban_create.rs b/crates/delta/src/routes/servers/ban_create.rs similarity index 100% rename from src/routes/servers/ban_create.rs rename to crates/delta/src/routes/servers/ban_create.rs diff --git a/src/routes/servers/ban_list.rs b/crates/delta/src/routes/servers/ban_list.rs similarity index 100% rename from src/routes/servers/ban_list.rs rename to crates/delta/src/routes/servers/ban_list.rs diff --git a/src/routes/servers/ban_remove.rs b/crates/delta/src/routes/servers/ban_remove.rs similarity index 100% rename from src/routes/servers/ban_remove.rs rename to crates/delta/src/routes/servers/ban_remove.rs diff --git a/src/routes/servers/channel_create.rs b/crates/delta/src/routes/servers/channel_create.rs similarity index 100% rename from src/routes/servers/channel_create.rs rename to crates/delta/src/routes/servers/channel_create.rs diff --git a/src/routes/servers/invites_fetch.rs b/crates/delta/src/routes/servers/invites_fetch.rs similarity index 100% rename from src/routes/servers/invites_fetch.rs rename to crates/delta/src/routes/servers/invites_fetch.rs diff --git a/src/routes/servers/member_edit.rs b/crates/delta/src/routes/servers/member_edit.rs similarity index 100% rename from src/routes/servers/member_edit.rs rename to crates/delta/src/routes/servers/member_edit.rs diff --git a/src/routes/servers/member_fetch.rs b/crates/delta/src/routes/servers/member_fetch.rs similarity index 100% rename from src/routes/servers/member_fetch.rs rename to crates/delta/src/routes/servers/member_fetch.rs diff --git a/src/routes/servers/member_fetch_all.rs b/crates/delta/src/routes/servers/member_fetch_all.rs similarity index 100% rename from src/routes/servers/member_fetch_all.rs rename to crates/delta/src/routes/servers/member_fetch_all.rs diff --git a/src/routes/servers/member_remove.rs b/crates/delta/src/routes/servers/member_remove.rs similarity index 100% rename from src/routes/servers/member_remove.rs rename to crates/delta/src/routes/servers/member_remove.rs diff --git a/src/routes/servers/mod.rs b/crates/delta/src/routes/servers/mod.rs similarity index 100% rename from src/routes/servers/mod.rs rename to crates/delta/src/routes/servers/mod.rs diff --git a/src/routes/servers/permissions_set.rs b/crates/delta/src/routes/servers/permissions_set.rs similarity index 100% rename from src/routes/servers/permissions_set.rs rename to crates/delta/src/routes/servers/permissions_set.rs diff --git a/src/routes/servers/permissions_set_default.rs b/crates/delta/src/routes/servers/permissions_set_default.rs similarity index 100% rename from src/routes/servers/permissions_set_default.rs rename to crates/delta/src/routes/servers/permissions_set_default.rs diff --git a/src/routes/servers/roles_create.rs b/crates/delta/src/routes/servers/roles_create.rs similarity index 100% rename from src/routes/servers/roles_create.rs rename to crates/delta/src/routes/servers/roles_create.rs diff --git a/src/routes/servers/roles_delete.rs b/crates/delta/src/routes/servers/roles_delete.rs similarity index 100% rename from src/routes/servers/roles_delete.rs rename to crates/delta/src/routes/servers/roles_delete.rs diff --git a/src/routes/servers/roles_edit.rs b/crates/delta/src/routes/servers/roles_edit.rs similarity index 100% rename from src/routes/servers/roles_edit.rs rename to crates/delta/src/routes/servers/roles_edit.rs diff --git a/src/routes/servers/server_ack.rs b/crates/delta/src/routes/servers/server_ack.rs similarity index 100% rename from src/routes/servers/server_ack.rs rename to crates/delta/src/routes/servers/server_ack.rs diff --git a/src/routes/servers/server_create.rs b/crates/delta/src/routes/servers/server_create.rs similarity index 100% rename from src/routes/servers/server_create.rs rename to crates/delta/src/routes/servers/server_create.rs diff --git a/src/routes/servers/server_delete.rs b/crates/delta/src/routes/servers/server_delete.rs similarity index 100% rename from src/routes/servers/server_delete.rs rename to crates/delta/src/routes/servers/server_delete.rs diff --git a/src/routes/servers/server_edit.rs b/crates/delta/src/routes/servers/server_edit.rs similarity index 100% rename from src/routes/servers/server_edit.rs rename to crates/delta/src/routes/servers/server_edit.rs diff --git a/src/routes/servers/server_fetch.rs b/crates/delta/src/routes/servers/server_fetch.rs similarity index 100% rename from src/routes/servers/server_fetch.rs rename to crates/delta/src/routes/servers/server_fetch.rs diff --git a/src/routes/sync/get_settings.rs b/crates/delta/src/routes/sync/get_settings.rs similarity index 100% rename from src/routes/sync/get_settings.rs rename to crates/delta/src/routes/sync/get_settings.rs diff --git a/src/routes/sync/get_unreads.rs b/crates/delta/src/routes/sync/get_unreads.rs similarity index 100% rename from src/routes/sync/get_unreads.rs rename to crates/delta/src/routes/sync/get_unreads.rs diff --git a/src/routes/sync/mod.rs b/crates/delta/src/routes/sync/mod.rs similarity index 100% rename from src/routes/sync/mod.rs rename to crates/delta/src/routes/sync/mod.rs diff --git a/src/routes/sync/set_settings.rs b/crates/delta/src/routes/sync/set_settings.rs similarity index 100% rename from src/routes/sync/set_settings.rs rename to crates/delta/src/routes/sync/set_settings.rs diff --git a/src/routes/users/add_friend.rs b/crates/delta/src/routes/users/add_friend.rs similarity index 100% rename from src/routes/users/add_friend.rs rename to crates/delta/src/routes/users/add_friend.rs diff --git a/src/routes/users/block_user.rs b/crates/delta/src/routes/users/block_user.rs similarity index 100% rename from src/routes/users/block_user.rs rename to crates/delta/src/routes/users/block_user.rs diff --git a/src/routes/users/change_username.rs b/crates/delta/src/routes/users/change_username.rs similarity index 100% rename from src/routes/users/change_username.rs rename to crates/delta/src/routes/users/change_username.rs diff --git a/src/routes/users/edit_user.rs b/crates/delta/src/routes/users/edit_user.rs similarity index 100% rename from src/routes/users/edit_user.rs rename to crates/delta/src/routes/users/edit_user.rs diff --git a/src/routes/users/fetch_dms.rs b/crates/delta/src/routes/users/fetch_dms.rs similarity index 100% rename from src/routes/users/fetch_dms.rs rename to crates/delta/src/routes/users/fetch_dms.rs diff --git a/src/routes/users/fetch_profile.rs b/crates/delta/src/routes/users/fetch_profile.rs similarity index 100% rename from src/routes/users/fetch_profile.rs rename to crates/delta/src/routes/users/fetch_profile.rs diff --git a/src/routes/users/fetch_self.rs b/crates/delta/src/routes/users/fetch_self.rs similarity index 100% rename from src/routes/users/fetch_self.rs rename to crates/delta/src/routes/users/fetch_self.rs diff --git a/src/routes/users/fetch_user.rs b/crates/delta/src/routes/users/fetch_user.rs similarity index 100% rename from src/routes/users/fetch_user.rs rename to crates/delta/src/routes/users/fetch_user.rs diff --git a/src/routes/users/find_mutual.rs b/crates/delta/src/routes/users/find_mutual.rs similarity index 100% rename from src/routes/users/find_mutual.rs rename to crates/delta/src/routes/users/find_mutual.rs diff --git a/src/routes/users/get_default_avatar.rs b/crates/delta/src/routes/users/get_default_avatar.rs similarity index 100% rename from src/routes/users/get_default_avatar.rs rename to crates/delta/src/routes/users/get_default_avatar.rs diff --git a/src/routes/users/mod.rs b/crates/delta/src/routes/users/mod.rs similarity index 100% rename from src/routes/users/mod.rs rename to crates/delta/src/routes/users/mod.rs diff --git a/src/routes/users/open_dm.rs b/crates/delta/src/routes/users/open_dm.rs similarity index 100% rename from src/routes/users/open_dm.rs rename to crates/delta/src/routes/users/open_dm.rs diff --git a/src/routes/users/remove_friend.rs b/crates/delta/src/routes/users/remove_friend.rs similarity index 100% rename from src/routes/users/remove_friend.rs rename to crates/delta/src/routes/users/remove_friend.rs diff --git a/src/routes/users/unblock_user.rs b/crates/delta/src/routes/users/unblock_user.rs similarity index 100% rename from src/routes/users/unblock_user.rs rename to crates/delta/src/routes/users/unblock_user.rs diff --git a/src/util/idempotency.rs b/crates/delta/src/util/idempotency.rs similarity index 100% rename from src/util/idempotency.rs rename to crates/delta/src/util/idempotency.rs diff --git a/src/util/mod.rs b/crates/delta/src/util/mod.rs similarity index 100% rename from src/util/mod.rs rename to crates/delta/src/util/mod.rs diff --git a/src/util/ratelimiter.rs b/crates/delta/src/util/ratelimiter.rs similarity index 100% rename from src/util/ratelimiter.rs rename to crates/delta/src/util/ratelimiter.rs diff --git a/src/util/regex.rs b/crates/delta/src/util/regex.rs similarity index 100% rename from src/util/regex.rs rename to crates/delta/src/util/regex.rs diff --git a/src/version.rs b/crates/delta/src/version.rs similarity index 100% rename from src/version.rs rename to crates/delta/src/version.rs diff --git a/crates/quark/.github/FUNDING.yml b/crates/quark/.github/FUNDING.yml new file mode 100644 index 00000000..dcf3dd90 --- /dev/null +++ b/crates/quark/.github/FUNDING.yml @@ -0,0 +1,2 @@ +ko_fi: insertish +custom: https://insrt.uk/donate diff --git a/crates/quark/.github/workflows/rust.yaml b/crates/quark/.github/workflows/rust.yaml new file mode 100644 index 00000000..4386b19f --- /dev/null +++ b/crates/quark/.github/workflows/rust.yaml @@ -0,0 +1,33 @@ +name: Rust build and test + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + check: + name: Rust project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install latest nightly + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + components: rustfmt, clippy + + - name: Run cargo build + uses: actions-rs/cargo@v1 + with: + command: build + + - name: Run cargo test + uses: actions-rs/cargo@v1 + with: + command: test diff --git a/crates/quark/.github/workflows/triage_issue.yml b/crates/quark/.github/workflows/triage_issue.yml new file mode 100644 index 00000000..b625a98b --- /dev/null +++ b/crates/quark/.github/workflows/triage_issue.yml @@ -0,0 +1,49 @@ +name: Add Issue to Board + +on: + issues: + types: [opened] + +jobs: + track_issue: + runs-on: ubuntu-latest + steps: + - name: Get project data + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + run: | + gh api graphql -f query=' + query { + organization(login: "revoltchat"){ + projectNext(number: 3) { + id + fields(first:20) { + nodes { + id + name + settings + } + } + } + } + }' > project_data.json + + echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV + echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV + echo 'TODO_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV + + - name: Add issue to project + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + ISSUE_ID: ${{ github.event.issue.node_id }} + run: | + item_id="$( gh api graphql -f query=' + mutation($project:ID!, $issue:ID!) { + addProjectNextItem(input: {projectId: $project, contentId: $issue}) { + projectNextItem { + id + } + } + }' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')" + + echo 'ITEM_ID='$item_id >> $GITHUB_ENV diff --git a/crates/quark/.github/workflows/triage_pr.yml b/crates/quark/.github/workflows/triage_pr.yml new file mode 100644 index 00000000..df07fe88 --- /dev/null +++ b/crates/quark/.github/workflows/triage_pr.yml @@ -0,0 +1,72 @@ +name: Add PR to Board + +on: + pull_request_target: + types: [opened, synchronize, ready_for_review, review_requested] + +jobs: + track_pr: + runs-on: ubuntu-latest + steps: + - name: Get project data + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + run: | + gh api graphql -f query=' + query { + organization(login: "revoltchat"){ + projectNext(number: 3) { + id + fields(first:20) { + nodes { + id + name + settings + } + } + } + } + }' > project_data.json + + echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV + echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV + echo 'INCOMING_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Incoming PRs") |.id' project_data.json) >> $GITHUB_ENV + + - name: Add PR to project + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + PR_ID: ${{ github.event.pull_request.node_id }} + run: | + item_id="$( gh api graphql -f query=' + mutation($project:ID!, $pr:ID!) { + addProjectNextItem(input: {projectId: $project, contentId: $pr}) { + projectNextItem { + id + } + } + }' -f project=$PROJECT_ID -f pr=$PR_ID --jq '.data.addProjectNextItem.projectNextItem.id')" + + echo 'ITEM_ID='$item_id >> $GITHUB_ENV + + - name: Set fields + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + run: | + gh api graphql -f query=' + mutation ( + $project: ID! + $item: ID! + $status_field: ID! + $status_value: String! + ) { + set_status: updateProjectNextItemField(input: { + projectId: $project + itemId: $item + fieldId: $status_field + value: $status_value + }) { + projectNextItem { + id + } + } + }' -f project=$PROJECT_ID -f item=$ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.INCOMING_OPTION_ID }} --silent diff --git a/crates/quark/.gitignore b/crates/quark/.gitignore new file mode 100644 index 00000000..01dfec2c --- /dev/null +++ b/crates/quark/.gitignore @@ -0,0 +1,14 @@ +# 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 new file mode 100644 index 00000000..4803ac6b --- /dev/null +++ b/crates/quark/Cargo.toml @@ -0,0 +1,70 @@ +[package] +name = "revolt-quark" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +mongo = [ "mongodb" ] +rocket_impl = [ "rocket" ] +test = [ "async-std", "mongo", "mongodb/async-std-runtime", "rocket_impl", "rauth" ] +default = [ "test" ] + +[dependencies] +# Serialisation +serde = { version = "1", features = ["derive"] } +validator = { version = "0.14", features = ["derive"] } +iso8601-timestamp = { version = "0.1.8", features = ["schema", "bson"] } +optional_struct = { git = "https://github.com/insertish/OptionalStruct", rev = "e275d2726595474632485934aa0887fa52281f70" } + +# 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" +okapi = { git = "https://github.com/insertish/okapi", rev = "dcf0df115596ee07a587a7a543cddf3d7944645b" } +rocket_okapi = { git = "https://github.com/insertish/okapi", rev = "dcf0df115596ee07a587a7a543cddf3d7944645b" } +# okapi = "0.7.0-rc.1" +# rocket_okapi = "0.8.0-rc.1" + +# Databases +redis-kiss = { version = "0.1.3" } +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 +ulid = "0.5.0" +regex = "1.5.5" +nanoid = "0.4.0" +linkify = "0.8.1" +dotenv = "0.15.0" +impl_ops = "0.1.1" +num_enum = "0.5.6" +reqwest = "0.11.10" +bitfield = "0.13.2" +lazy_static = "1.4.0" + +# Web Push +base64 = "0.13.0" +web-push = "0.7.2" + +# Implementations +rauth = { optional = true, git = "https://github.com/insertish/rauth", rev = "001a9698c56cea79e69e4ae71d7bc2cb48aec1a6" } +rocket = { optional = true, version = "=0.5.0-rc.1", default-features = false, features = ["json"] } +rocket_http = { optional = true, version = "=0.5.0-rc.1" } + +# Sentry +sentry = "0.25.0" diff --git a/crates/quark/LICENSE b/crates/quark/LICENSE new file mode 100644 index 00000000..5638cdfb --- /dev/null +++ b/crates/quark/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + Revolt Quark + Copyright (C) 2022 Pawel Makles + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/crates/quark/examples/test.rs b/crates/quark/examples/test.rs new file mode 100644 index 00000000..d09220c3 --- /dev/null +++ b/crates/quark/examples/test.rs @@ -0,0 +1,34 @@ +use revolt_quark::models::user::PartialUser; +use revolt_quark::presence::{ + presence_create_session, presence_delete_session, presence_filter_online, presence_is_online, +}; +use revolt_quark::*; + +#[async_std::main] +async fn main() { + let db = DatabaseInfo::Dummy.connect().await.unwrap(); + + let sus = PartialUser { + username: Some("neat".into()), + ..Default::default() + }; + + db.update_user("user id", &sus, vec![]).await.unwrap(); + + dbg!(presence_create_session("entry", 0).await); + dbg!(presence_is_online("entry").await); + dbg!(presence_filter_online(&["a".into(), "b".into(), "entry".into()]).await); + + dbg!(presence_delete_session("entry", 0).await); + dbg!(presence_is_online("entry").await); + + dbg!(presence_create_session("entry", 0).await); + dbg!(presence_create_session("entry", 0).await); + dbg!(presence_delete_session("entry", 0).await); + dbg!(presence_is_online("entry").await); + dbg!(presence_delete_session("entry", 1).await); + dbg!(presence_is_online("entry").await); + + // __set_key("dietz", vec![0xFF]).await; + // dbg!(presence_filter_online(&["dietz".into(), "nuts".into()]).await); +} diff --git a/crates/quark/src/database.rs b/crates/quark/src/database.rs new file mode 100644 index 00000000..ecdf13c2 --- /dev/null +++ b/crates/quark/src/database.rs @@ -0,0 +1,63 @@ +use std::env; +use std::ops::Deref; + +use crate::r#impl::dummy::DummyDb; +use crate::r#impl::mongo::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(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, + } + } +} diff --git a/crates/quark/src/events/client.rs b/crates/quark/src/events/client.rs new file mode 100644 index 00000000..3dd2d027 --- /dev/null +++ b/crates/quark/src/events/client.rs @@ -0,0 +1,173 @@ +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, Member, Message, 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, + }, + + /// 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 }, + + /// 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, + }, + + /// 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, + }, + + /// 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 }, +} diff --git a/crates/quark/src/events/impl.rs b/crates/quark/src/events/impl.rs new file mode 100644 index 00000000..60dabb60 --- /dev/null +++ b/crates/quark/src/events/impl.rs @@ -0,0 +1,557 @@ +use std::collections::HashSet; + +use crate::{ + get_relationship, + models::{ + server_member::FieldsMember, + user::{PartialUser, Presence, RelationshipStatus}, + Channel, Member, User, + }, + perms, + presence::presence_filter_online, + Database, Permission, Result, +}; + +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 + .iter() + .map(|(_, x)| x) + .find(|x| &x.id.server == 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: Vec = 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?; + let server_ids: Vec = members.iter().map(|x| x.id.server.clone()).collect(); + let servers = db.fetch_servers(&server_ids).await?; + + // 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.append(&mut recipients.clone()); + } + _ => {} + } + } + + // Fetch presence data for known users. + let online_ids = presence_filter_online(&user_ids).await; + user.online = Some(true); + + // Fetch user data. + let users = db + .fetch_users( + &user_ids + .into_iter() + .filter(|x| x != &user.id) + .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.servers = servers.iter().cloned().map(|x| (x.id.clone(), x)).collect(); + self.cache.channels = channels + .iter() + .cloned() + .map(|x| (x.id().to_string(), x)) + .collect(); + self.cache.members = members + .iter() + .cloned() + .map(|x| (x.id.server.clone(), 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, + }) + } + + /// 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![], + }; + + 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 { + true + }; + + if let Some(channel) = self.cache.channels.get_mut(id) { + for field in clear { + channel.remove(field); + } + + channel.apply_options(data.clone()); + } + + 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, + } => { + self.insert_subscription(id.clone()); + self.cache.servers.insert(id.to_string(), server.clone()); + + 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); + } + } + } + } + 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); + } + } + } + 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 let Some(roles) = &member.roles { + if 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 let Some(roles) = &member.roles { + if roles.contains(role_id) { + queue_server = Some(id.clone()); + } + } + } + } + + 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; + + // ! FIXME: 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; + } +} diff --git a/crates/quark/src/events/mod.rs b/crates/quark/src/events/mod.rs new file mode 100644 index 00000000..4669f467 --- /dev/null +++ b/crates/quark/src/events/mod.rs @@ -0,0 +1,4 @@ +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 new file mode 100644 index 00000000..883b92c3 --- /dev/null +++ b/crates/quark/src/events/server.rs @@ -0,0 +1,12 @@ +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 new file mode 100644 index 00000000..8b502d22 --- /dev/null +++ b/crates/quark/src/events/state.rs @@ -0,0 +1,146 @@ +use std::collections::{HashMap, HashSet}; + +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, Default)] +pub struct Cache { + pub user_id: String, + + pub users: HashMap, + pub channels: HashMap, + pub members: HashMap, + pub servers: HashMap, +} + +/// 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/migrations.rs b/crates/quark/src/impl/dummy/admin/migrations.rs new file mode 100644 index 00000000..d8694453 --- /dev/null +++ b/crates/quark/src/impl/dummy/admin/migrations.rs @@ -0,0 +1,11 @@ +use crate::{AbstractMigrations, Result}; + +use super::super::DummyDb; + +#[async_trait] +impl AbstractMigrations for DummyDb { + async fn migrate_database(&self) -> Result<()> { + info!("Migrating the database."); + Ok(()) + } +} diff --git a/crates/quark/src/impl/dummy/autumn/attachment.rs b/crates/quark/src/impl/dummy/autumn/attachment.rs new file mode 100644 index 00000000..329ed562 --- /dev/null +++ b/crates/quark/src/impl/dummy/autumn/attachment.rs @@ -0,0 +1,42 @@ +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(()) + } +} diff --git a/crates/quark/src/impl/dummy/channels/channel.rs b/crates/quark/src/impl/dummy/channels/channel.rs new file mode 100644 index 00000000..b88914ff --- /dev/null +++ b/crates/quark/src/impl/dummy/channels/channel.rs @@ -0,0 +1,90 @@ +use std::collections::HashSet; + +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(()) + } + + async fn check_channels_exist(&self, _channels: &HashSet) -> Result { + Ok(true) + } +} diff --git a/crates/quark/src/impl/dummy/channels/channel_invite.rs b/crates/quark/src/impl/dummy/channels/channel_invite.rs new file mode 100644 index 00000000..1e292821 --- /dev/null +++ b/crates/quark/src/impl/dummy/channels/channel_invite.rs @@ -0,0 +1,30 @@ +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 new file mode 100644 index 00000000..a271fa9e --- /dev/null +++ b/crates/quark/src/impl/dummy/channels/channel_unread.rs @@ -0,0 +1,31 @@ +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 new file mode 100644 index 00000000..efee2c36 --- /dev/null +++ b/crates/quark/src/impl/dummy/channels/message.rs @@ -0,0 +1,67 @@ +use crate::models::message::{AppendMessage, Message, MessageSort, 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, + channel: &str, + _limit: Option, + _before: Option, + _after: Option, + _sort: Option, + _nearby: Option, + ) -> Result> { + Ok(vec![self.fetch_message(channel).await.unwrap()]) + } + + async fn search_messages( + &self, + channel: &str, + _query: &str, + _limit: Option, + _before: Option, + _after: Option, + _sort: MessageSort, + ) -> Result> { + Ok(vec![self.fetch_message(channel).await.unwrap()]) + } +} diff --git a/crates/quark/src/impl/dummy/mod.rs b/crates/quark/src/impl/dummy/mod.rs new file mode 100644 index 00000000..5b6e30e2 --- /dev/null +++ b/crates/quark/src/impl/dummy/mod.rs @@ -0,0 +1,33 @@ +use crate::AbstractDatabase; + +pub mod admin { + pub mod migrations; +} + +pub mod autumn { + pub mod attachment; +} + +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 bot; + pub mod user; + pub mod user_settings; +} + +#[derive(Debug, Clone)] +pub struct DummyDb; + +impl AbstractDatabase for DummyDb {} diff --git a/crates/quark/src/impl/dummy/servers/server.rs b/crates/quark/src/impl/dummy/servers/server.rs new file mode 100644 index 00000000..4d693ad5 --- /dev/null +++ b/crates/quark/src/impl/dummy/servers/server.rs @@ -0,0 +1,78 @@ +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 new file mode 100644 index 00000000..c07700d5 --- /dev/null +++ b/crates/quark/src/impl/dummy/servers/server_ban.rs @@ -0,0 +1,32 @@ +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 new file mode 100644 index 00000000..77db7406 --- /dev/null +++ b/crates/quark/src/impl/dummy/servers/server_member.rs @@ -0,0 +1,59 @@ +use crate::models::server_member::{FieldsMember, Member, MemberCompositeKey, 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 { + id: MemberCompositeKey { + server: server.into(), + user: user.into(), + }, + nickname: None, + avatar: None, + roles: None, + }) + } + + 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/bot.rs b/crates/quark/src/impl/dummy/users/bot.rs new file mode 100644 index 00000000..e2e36c7e --- /dev/null +++ b/crates/quark/src/impl/dummy/users/bot.rs @@ -0,0 +1,46 @@ +use crate::models::bot::{Bot, FieldsBot, PartialBot}; +use crate::{AbstractBot, Result}; + +use super::super::DummyDb; + +#[async_trait] +impl AbstractBot for DummyDb { + async fn fetch_bot(&self, id: &str) -> Result { + Ok(Bot { + id: id.into(), + owner: "user".into(), + token: "token".into(), + public: true, + analytics: true, + discoverable: true, + ..Default::default() + }) + } + + async fn fetch_bot_by_token(&self, _token: &str) -> Result { + self.fetch_bot("bot").await + } + + async fn insert_bot(&self, bot: &Bot) -> Result<()> { + info!("Insert {bot:?}"); + Ok(()) + } + + async fn update_bot(&self, id: &str, bot: &PartialBot, remove: Vec) -> Result<()> { + info!("Update {id} with {bot:?} and remove {remove:?}"); + Ok(()) + } + + async fn delete_bot(&self, id: &str) -> Result<()> { + info!("Delete {id}"); + Ok(()) + } + + async fn fetch_bots_by_user(&self, user_id: &str) -> Result> { + Ok(vec![self.fetch_bot(user_id).await.unwrap()]) + } + + async fn get_number_of_bots_by_user(&self, _user_id: &str) -> Result { + Ok(1) + } +} diff --git a/crates/quark/src/impl/dummy/users/user.rs b/crates/quark/src/impl/dummy/users/user.rs new file mode 100644 index 00000000..9b3bfd2a --- /dev/null +++ b/crates/quark/src/impl/dummy/users/user.rs @@ -0,0 +1,78 @@ +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(), + ..Default::default() + }) + } + + async fn fetch_user_by_username(&self, username: &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 is_username_taken(&self, _username: &str) -> Result { + Ok(false) + } + + 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 new file mode 100644 index 00000000..745892b6 --- /dev/null +++ b/crates/quark/src/impl/dummy/users/user_settings.rs @@ -0,0 +1,25 @@ +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/admin/migrations.rs b/crates/quark/src/impl/generic/admin/migrations.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/quark/src/impl/generic/admin/migrations.rs @@ -0,0 +1 @@ + diff --git a/crates/quark/src/impl/generic/autumn/attachment.rs b/crates/quark/src/impl/generic/autumn/attachment.rs new file mode 100644 index 00000000..11b7dd9d --- /dev/null +++ b/crates/quark/src/impl/generic/autumn/attachment.rs @@ -0,0 +1,33 @@ +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 + } +} diff --git a/crates/quark/src/impl/generic/channels/channel.rs b/crates/quark/src/impl/generic/channels/channel.rs new file mode 100644 index 00000000..68461f02 --- /dev/null +++ b/crates/quark/src/impl/generic/channels/channel.rs @@ -0,0 +1,380 @@ +use crate::{ + events::client::EventV1, + models::{ + channel::{FieldsChannel, PartialChannel}, + message::SystemMessage, + Channel, + }, + tasks::ack::AckEvent, + Database, Error, OverrideField, 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 { + recipients.push(user.to_string()); + } + + 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>, + ) -> 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?; + } 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 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), + } + } +} diff --git a/crates/quark/src/impl/generic/channels/channel_invite.rs b/crates/quark/src/impl/generic/channels/channel_invite.rs new file mode 100644 index 00000000..ac9f645b --- /dev/null +++ b/crates/quark/src/impl/generic/channels/channel_invite.rs @@ -0,0 +1,74 @@ +use nanoid::nanoid; + +use crate::{ + models::{Channel, Invite, User}, + Database, Error, Result, +}; + +lazy_static! { + static ref 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 new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/quark/src/impl/generic/channels/channel_unread.rs @@ -0,0 +1 @@ + diff --git a/crates/quark/src/impl/generic/channels/message.rs b/crates/quark/src/impl/generic/channels/message.rs new file mode 100644 index 00000000..710fecea --- /dev/null +++ b/crates/quark/src/impl/generic/channels/message.rs @@ -0,0 +1,284 @@ +use std::collections::HashSet; + +use serde_json::json; +use ulid::Ulid; + +use crate::{ + events::client::EventV1, + models::{ + message::{ + AppendMessage, BulkMessageResponse, PartialMessage, SendableEmbed, SystemMessage, + }, + Channel, Message, User, + }, + presence::presence_filter_online, + tasks::ack::AckEvent, + types::{ + january::{Embed, Text}, + push::PushNotification, + }, + Database, 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<&User>, + ) -> 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::>() + - &presence_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<()> { + 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(()) + } +} + +pub trait IntoUsers { + fn get_user_ids(&self) -> Vec; +} + +impl IntoUsers for Message { + fn get_user_ids(&self) -> Vec { + let mut ids = vec![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 = vec![]; + for message in self { + ids.append(&mut message.get_user_ids()); + } + + ids + } +} + +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(), + } + } +} + +impl SendableEmbed { + pub async fn into_embed(self, db: &Database, message_id: String) -> Result { + 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: &Channel, + messages: Vec, + include_users: Option, + ) -> Result { + if let Some(true) = include_users { + let user_ids = messages.get_user_ids(); + let users = db.fetch_users(&user_ids).await?; + + Ok(match channel { + Channel::TextChannel { server, .. } | 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)) + } + } +} diff --git a/crates/quark/src/impl/generic/mod.rs b/crates/quark/src/impl/generic/mod.rs new file mode 100644 index 00000000..6092ef59 --- /dev/null +++ b/crates/quark/src/impl/generic/mod.rs @@ -0,0 +1,28 @@ +//! Database agnostic implementations. + +pub mod admin { + pub mod migrations; +} + +pub mod autumn { + pub mod attachment; +} + +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 bot; + pub mod user; + pub mod user_settings; +} diff --git a/crates/quark/src/impl/generic/servers/server.rs b/crates/quark/src/impl/generic/servers/server.rs new file mode 100644 index 00000000..ce3b7f09 --- /dev/null +++ b/crates/quark/src/impl/generic/servers/server.rs @@ -0,0 +1,323 @@ +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 { + id: MemberCompositeKey { + server: self.id.clone(), + user: user.id.clone(), + }, + ..Default::default() + }; + + 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); + } + } + } + + 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(), + } + .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, + ) -> 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 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(()) + } + + /// Ban a member from a server + pub async fn ban_member( + self, + db: &Database, + member: Member, + reason: Option, + ) -> Result { + let ban = ServerBan { + id: member.id.clone(), + reason, + }; + + self.remove_member(db, member, RemovalIntention::Ban) + .await?; + + db.insert_ban(&ban).await?; + Ok(ban) + } +} + +impl SystemMessageChannels { + pub fn into_channel_ids(self) -> Vec { + let mut ids = vec![]; + + if let Some(id) = self.user_joined { + ids.push(id); + } + + if let Some(id) = self.user_left { + ids.push(id); + } + + if let Some(id) = self.user_kicked { + ids.push(id); + } + + if let Some(id) = self.user_banned { + ids.push(id); + } + + ids + } +} diff --git a/crates/quark/src/impl/generic/servers/server_ban.rs b/crates/quark/src/impl/generic/servers/server_ban.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/quark/src/impl/generic/servers/server_ban.rs @@ -0,0 +1 @@ + diff --git a/crates/quark/src/impl/generic/servers/server_member.rs b/crates/quark/src/impl/generic/servers/server_member.rs new file mode 100644 index 00000000..79c5d8fc --- /dev/null +++ b/crates/quark/src/impl/generic/servers/server_member.rs @@ -0,0 +1,62 @@ +use crate::{ + events::client::EventV1, + models::{ + server_member::{FieldsMember, PartialMember}, + Member, Server, + }, + Database, Result, +}; + +impl Member { + /// 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 { + if let Some(roles) = &self.roles { + let mut value = i64::MAX; + for role in roles { + if let Some(role) = server.roles.get(role) { + if role.rank < value { + value = role.rank; + } + } + } + + value + } else { + i64::MAX + } + } + + pub fn remove(&mut self, field: &FieldsMember) { + match field { + FieldsMember::Avatar => self.avatar = None, + FieldsMember::Nickname => self.nickname = None, + FieldsMember::Roles => self.roles = None, + } + } +} diff --git a/crates/quark/src/impl/generic/users/bot.rs b/crates/quark/src/impl/generic/users/bot.rs new file mode 100644 index 00000000..2a327735 --- /dev/null +++ b/crates/quark/src/impl/generic/users/bot.rs @@ -0,0 +1,24 @@ +use nanoid::nanoid; + +use crate::{ + models::{bot::FieldsBot, Bot}, + Database, Result, +}; + +impl Bot { + /// Remove a field from this object + pub fn remove(&mut self, field: &FieldsBot) { + match field { + FieldsBot::Token => self.token = nanoid!(64), + FieldsBot::InteractionsURL => { + self.interactions_url.take(); + } + } + } + + /// Delete this bot + pub async fn delete(&self, db: &Database) -> Result<()> { + db.fetch_user(&self.id).await?.mark_deleted(db).await?; + db.delete_bot(&self.id).await + } +} diff --git a/crates/quark/src/impl/generic/users/user.rs b/crates/quark/src/impl/generic/users/user.rs new file mode 100644 index 00000000..21f4972f --- /dev/null +++ b/crates/quark/src/impl/generic/users/user.rs @@ -0,0 +1,430 @@ +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 okapi::openapi3::{SecurityScheme, SecuritySchemeData}; +use rocket_okapi::gen::OpenApiGenerator; +use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput}; +use std::ops; + +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, + } + .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; + } + } + } + } + + /// Mutate the user object to remove redundant information + 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 + } + + /// 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 { + // ! FIXME: hardcoded max server count + Ok(db.fetch_server_count(&self.id).await? <= 100) + } + + /// Update a user's username + pub async fn update_username(&mut self, db: &Database, username: String) -> Result<()> { + let username = username.trim().to_string(); + + if db.is_username_taken(&username).await? { + return Err(Error::UsernameTaken); + } + + self.update( + db, + PartialUser { + 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 => db.fetch_user(&db.fetch_bot_by_token(token).await?.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 + } + } + } + } +} + +use rauth::entities::Session; +use rocket::http::Status; +use rocket::request::{self, FromRequest, Outcome, Request}; + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for User { + type Error = rauth::util::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 state not reachable!"); + + 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, rauth::util::Error::InvalidSession)) + } + } +} + +impl<'r> OpenApiFromRequest<'r> for User { + fn from_request_input( + _gen: &mut OpenApiGenerator, + _name: String, + _required: bool, + ) -> rocket_okapi::Result { + let mut requirements = schemars::Map::new(); + requirements.insert("Api Key".to_owned(), vec![]); + + Ok(RequestHeaderInput::Security( + "Api Key".to_owned(), + SecurityScheme { + data: SecuritySchemeData::ApiKey { + name: "x-session-token".to_owned(), + location: "header".to_owned(), + }, + description: Some("Session Token".to_owned()), + extensions: schemars::Map::new(), + }, + requirements, + )) + } +} diff --git a/crates/quark/src/impl/generic/users/user_settings.rs b/crates/quark/src/impl/generic/users/user_settings.rs new file mode 100644 index 00000000..76e0e92b --- /dev/null +++ b/crates/quark/src/impl/generic/users/user_settings.rs @@ -0,0 +1,22 @@ +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 new file mode 100644 index 00000000..fdc7bfed --- /dev/null +++ b/crates/quark/src/impl/mod.rs @@ -0,0 +1,3 @@ +pub mod dummy; +pub mod generic; +pub mod mongo; diff --git a/crates/quark/src/impl/mongo/admin/migrations.rs b/crates/quark/src/impl/mongo/admin/migrations.rs new file mode 100644 index 00000000..943ca78f --- /dev/null +++ b/crates/quark/src/impl/mongo/admin/migrations.rs @@ -0,0 +1,26 @@ +use crate::{AbstractMigrations, Result}; + +use super::super::MongoDb; + +mod init; +mod scripts; + +#[async_trait] +impl AbstractMigrations for MongoDb { + async fn migrate_database(&self) -> Result<()> { + info!("Migrating the database."); + + let list = self + .list_database_names(None, None) + .await + .expect("Failed to fetch database names."); + + if list.iter().any(|x| x == "revolt") { + scripts::migrate_database(self).await; + } else { + init::create_database(self).await; + } + + Ok(()) + } +} diff --git a/crates/quark/src/impl/mongo/admin/migrations/init.rs b/crates/quark/src/impl/mongo/admin/migrations/init.rs new file mode 100644 index 00000000..c24ab16a --- /dev/null +++ b/crates/quark/src/impl/mongo/admin/migrations/init.rs @@ -0,0 +1,187 @@ +use crate::r#impl::mongo::MongoDb; + +use super::scripts::LATEST_REVISION; + +use log::info; +use mongodb::bson::doc; +use mongodb::options::CreateCollectionOptions; + +pub async fn create_database(db: &MongoDb) { + info!("Creating database."); + let db = db.db(); + + db.create_collection("accounts", None) + .await + .expect("Failed to create accounts collection."); + + db.create_collection("users", None) + .await + .expect("Failed to create users collection."); + + db.create_collection("channels", None) + .await + .expect("Failed to create channels collection."); + + db.create_collection("messages", None) + .await + .expect("Failed to create messages collection."); + + db.create_collection("servers", None) + .await + .expect("Failed to create servers collection."); + + db.create_collection("server_members", None) + .await + .expect("Failed to create server_members collection."); + + db.create_collection("server_bans", None) + .await + .expect("Failed to create server_bans collection."); + + db.create_collection("channel_invites", None) + .await + .expect("Failed to create channel_invites collection."); + + db.create_collection("channel_unreads", None) + .await + .expect("Failed to create channel_unreads collection."); + + db.create_collection("migrations", None) + .await + .expect("Failed to create migrations collection."); + + db.create_collection("attachments", None) + .await + .expect("Failed to create attachments collection."); + + db.create_collection("user_settings", None) + .await + .expect("Failed to create user_settings collection."); + + db.create_collection("bots", None) + .await + .expect("Failed to create bots collection."); + + db.create_collection( + "pubsub", + CreateCollectionOptions::builder() + .capped(true) + .size(1_000_000) + .build(), + ) + .await + .expect("Failed to create pubsub collection."); + + db.run_command( + doc! { + "createIndexes": "users", + "indexes": [ + { + "key": { + "username": 1_i32 + }, + "name": "username", + "unique": true, + "collation": { + "locale": "en", + "strength": 2_i32 + } + } + ] + }, + None, + ) + .await + .expect("Failed to create username index."); + + db.run_command( + doc! { + "createIndexes": "messages", + "indexes": [ + { + "key": { + "content": "text" + }, + "name": "content" + }, + { + "key": { + "channel": 1_i32 + }, + "name": "channel" + }, + { + "key": { + "channel": 1_i32, + "_id": 1_i32 + }, + "name": "channel_id_compound" + } + ] + }, + None, + ) + .await + .expect("Failed to create message index."); + + db.run_command( + doc! { + "createIndexes": "channel_unreads", + "indexes": [ + { + "key": { + "_id.channel": 1_i32, + "_id.user": 1_i32, + }, + "name": "compound_id" + }, + { + "key": { + "_id.user": 1_i32, + }, + "name": "user_id" + } + ] + }, + None, + ) + .await + .expect("Failed to create channel_unreads index."); + + db.run_command( + doc! { + "createIndexes": "server_members", + "indexes": [ + { + "key": { + "_id.server": 1_i32, + "_id.user": 1_i32, + }, + "name": "compound_id" + }, + { + "key": { + "_id.user": 1_i32, + }, + "name": "user_id" + } + ] + }, + None, + ) + .await + .expect("Failed to create server_members index."); + + db.collection("migrations") + .insert_one( + doc! { + "_id": 0_i32, + "revision": LATEST_REVISION + }, + None, + ) + .await + .expect("Failed to save migration info."); + + info!("Created database."); +} diff --git a/crates/quark/src/impl/mongo/admin/migrations/scripts.rs b/crates/quark/src/impl/mongo/admin/migrations/scripts.rs new file mode 100644 index 00000000..792c0297 --- /dev/null +++ b/crates/quark/src/impl/mongo/admin/migrations/scripts.rs @@ -0,0 +1,610 @@ +use std::time::Duration; + +use bson::Bson; +use futures::StreamExt; +use log::info; +use mongodb::{ + bson::{doc, from_bson, from_document, to_document, Document}, + options::FindOptions, +}; +use serde::{Deserialize, Serialize}; + +use crate::{r#impl::mongo::MongoDb, Permission, DEFAULT_PERMISSION_SERVER}; + +#[derive(Serialize, Deserialize)] +struct MigrationInfo { + _id: i32, + revision: i32, +} + +pub const LATEST_REVISION: i32 = 15; + +pub async fn migrate_database(db: &MongoDb) { + let migrations = db.col::("migrations"); + let data = migrations + .find_one(None, None) + .await + .expect("Failed to fetch migration data."); + + if let Some(doc) = data { + let info: MigrationInfo = + from_document(doc).expect("Failed to read migration information."); + + let revision = run_migrations(db, info.revision).await; + + migrations + .update_one( + doc! { + "_id": info._id + }, + doc! { + "$set": { + "revision": revision + } + }, + None, + ) + .await + .expect("Failed to commit migration information."); + + info!("Migration complete. Currently at revision {}.", revision); + } else { + panic!("Database was configured incorrectly, possibly because initalization failed.") + } +} + +pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 { + info!("Starting database migration."); + + if revision <= 0 { + info!("Running migration [revision 0]: Test migration system."); + } + + if revision <= 1 { + info!("Running migration [revision 1 / 2021-04-24]: Migrate to Autumn v1.0.0."); + + let messages = db.col::("messages"); + let attachments = db.col::("attachments"); + + messages + .update_many( + doc! { "attachment": { "$exists": 1_i32 } }, + doc! { "$set": { "attachment.tag": "attachments", "attachment.size": 0_i32 } }, + None, + ) + .await + .expect("Failed to update messages."); + + attachments + .update_many( + doc! {}, + doc! { "$set": { "tag": "attachments", "size": 0_i32 } }, + None, + ) + .await + .expect("Failed to update attachments."); + } + + if revision <= 2 { + info!("Running migration [revision 2 / 2021-05-08]: Add servers collection."); + + db.db() + .create_collection("servers", None) + .await + .expect("Failed to create servers collection."); + } + + if revision <= 3 { + info!("Running migration [revision 3 / 2021-05-25]: Support multiple file uploads, add channel_unreads and user_settings."); + + let messages = db.col::("messages"); + let mut cursor = messages + .find( + doc! { + "attachment": { + "$exists": 1_i32 + } + }, + FindOptions::builder() + .projection(doc! { + "_id": 1_i32, + "attachments": [ "$attachment" ] + }) + .build(), + ) + .await + .expect("Failed to fetch messages."); + + while let Some(result) = cursor.next().await { + let doc = result.unwrap(); + let id = doc.get_str("_id").unwrap(); + let attachments = doc.get_array("attachments").unwrap(); + + messages + .update_one( + doc! { "_id": id }, + doc! { "$unset": { "attachment": 1_i32 }, "$set": { "attachments": attachments } }, + None, + ) + .await + .unwrap(); + } + + db.db() + .create_collection("channel_unreads", None) + .await + .expect("Failed to create channel_unreads collection."); + + db.db() + .create_collection("user_settings", None) + .await + .expect("Failed to create user_settings collection."); + } + + if revision <= 4 { + info!("Running migration [revision 4 / 2021-06-01]: Add more server collections."); + + db.db() + .create_collection("server_members", None) + .await + .expect("Failed to create server_members collection."); + + db.db() + .create_collection("server_bans", None) + .await + .expect("Failed to create server_bans collection."); + + db.db() + .create_collection("channel_invites", None) + .await + .expect("Failed to create channel_invites collection."); + } + + if revision <= 5 { + info!("Running migration [revision 5 / 2021-06-26]: Add permissions."); + + #[derive(Serialize)] + struct Server { + pub default_permissions: (i32, i32), + } + + let server = Server { + default_permissions: (0_i32, 0_i32), + }; + + db.col::("servers") + .update_many( + doc! {}, + doc! { + "$set": to_document(&server).unwrap() + }, + None, + ) + .await + .expect("Failed to migrate servers."); + } + + if revision <= 6 { + info!("Running migration [revision 6 / 2021-07-09]: Add message text index."); + + db.db() + .run_command( + doc! { + "createIndexes": "messages", + "indexes": [ + { + "key": { + "content": "text" + }, + "name": "content" + } + ] + }, + None, + ) + .await + .expect("Failed to create message index."); + } + + if revision <= 7 { + info!("Running migration [revision 7 / 2021-08-11]: Add message text index."); + + db.db() + .create_collection("bots", None) + .await + .expect("Failed to create bots collection."); + } + + if revision <= 8 { + info!("Running migration [revision 8 / 2021-09-10]: Update to rAuth version 1."); + + db.db() + .run_command( + doc! { + "dropIndexes": "accounts", + "index": ["email", "email_normalised"] + }, + None, + ) + .await + .expect("Failed to delete legacy account indexes."); + + let col = db.col::("sessions"); + let mut cursor = db + .col::("accounts") + .find(doc! {}, None) + .await + .unwrap(); + + while let Some(doc) = cursor.next().await { + if let Ok(account) = doc { + let id = account.get_str("_id").unwrap(); + if let Some(sessions) = account.get("sessions") { + #[derive(Deserialize)] + struct Session { + id: String, + token: String, + friendly_name: String, + subscription: Option, + } + + let sessions = from_bson::>(sessions.clone()).unwrap(); + for session in sessions { + info!("Converting session {} to new format.", &session.id); + + let mut doc = doc! { + "_id": session.id, + "token": session.token, + "user_id": id, + "name": session.friendly_name, + }; + + if let Some(sub) = session.subscription { + doc.insert("subscription", sub); + } + + col.insert_one(doc, None).await.ok(); + } + } else { + info!("Account doesn't have any sessions!"); + } + } + } + + db.col::("accounts") + .update_many( + doc! {}, + doc! { + "$unset": { + "sessions": 1_i32, + }, + "$set": { + "mfa": { + "recovery_codes": [] + } + } + }, + None, + ) + .await + .unwrap(); + } + + if revision <= 9 { + info!("Running migration [revision 9 / 2021-09-14]: Switch from last_message to last_message_id."); + + let mut cursor = db + .col::("channels") + .find(doc! {}, None) + .await + .unwrap(); + + while let Some(doc) = cursor.next().await { + if let Ok(channel) = doc { + let channel_id = channel.get_str("_id").unwrap(); + if let Some(last_message) = channel.get("last_message") { + #[derive(Serialize, Deserialize, Debug, Clone)] + pub struct Obj { + #[serde(rename = "_id")] + id: String, + } + + #[derive(Serialize, Deserialize, Debug, Clone)] + #[serde(untagged)] + pub enum LastMessage { + Obj(Obj), + Id(String), + } + + let lm = from_bson::(last_message.clone()).unwrap(); + let id = match lm { + LastMessage::Obj(Obj { id }) => id, + LastMessage::Id(id) => id, + }; + + info!("Converting session {} to new format.", &channel_id); + db.col::("channels") + .update_one( + doc! { + "_id": channel_id + }, + doc! { + "$set": { + "last_message_id": id + }, + "$unset": { + "last_message": 1_i32, + } + }, + None, + ) + .await + .unwrap(); + } else { + info!("{} has no last_message.", &channel_id); + } + } + } + } + + if revision <= 10 { + info!("Running migration [revision 10 / 2021-11-01]: Remove nonce values on channels and servers."); + + db.col::("servers") + .update_many( + doc! {}, + doc! { + "$unset": { + "nonce": 1_i32, + } + }, + None, + ) + .await + .unwrap(); + + db.col::("channels") + .update_many( + doc! {}, + doc! { + "$unset": { + "nonce": 1_i32, + } + }, + None, + ) + .await + .unwrap(); + } + + if revision <= 11 { + info!("Running migration [revision 11 / 2021-11-14]: Add indexes to database."); + + db.db() + .run_command( + doc! { + "createIndexes": "messages", + "indexes": [ + { + "key": { + "channel": 1_i32 + }, + "name": "channel" + } + ] + }, + None, + ) + .await + .expect("Failed to create message index."); + + db.db() + .run_command( + doc! { + "createIndexes": "channel_unreads", + "indexes": [ + { + "key": { + "_id.channel": 1_i32, + "_id.user": 1_i32, + }, + "name": "compound_id" + }, + { + "key": { + "_id.user": 1_i32, + }, + "name": "user_id" + } + ] + }, + None, + ) + .await + .expect("Failed to create channel_unreads index."); + + db.db() + .run_command( + doc! { + "createIndexes": "server_members", + "indexes": [ + { + "key": { + "_id.server": 1_i32, + "_id.user": 1_i32, + }, + "name": "compound_id" + }, + { + "key": { + "_id.user": 1_i32, + }, + "name": "user_id" + } + ] + }, + None, + ) + .await + .expect("Failed to create server_members index."); + } + + if revision <= 12 { + info!("Running migration [revision 12 / 2021-11-21]: Add indexes to database."); + + db.db() + .run_command( + doc! { + "createIndexes": "messages", + "indexes": [ + { + "key": { + "channel": 1_i32, + "_id": 1_i32 + }, + "name": "channel_id_compound" + } + ] + }, + None, + ) + .await + .expect("Failed to create message index."); + } + + if revision <= 13 { + info!("Running migration [revision 13 / 22-02-2022]: Wipe legacy permission values."); + + warn!("This is a destructive operation and will wipe existing permission data (excl. defaults for SendMessage)."); + warn!("Taking a backup is advised."); + warn!("Continuing in 10 seconds..."); + async_std::task::sleep(Duration::from_secs(10)).await; + + let servers = db.col::("servers"); + let mut cursor = servers.find(doc! {}, None).await.unwrap(); + + while let Some(Ok(mut document)) = cursor.next().await { + let id = document.get_str("_id").unwrap().to_string(); + info!("Updating server {id}"); + + let mut update = doc! {}; + + // Try to pluck channel permission SendMessage (0x2) + // Structure of default_permissions used to be [server, channel] + let has_send = document + .get_array("default_permissions") + .map(|x| { + x.get(1) + .map(|x| x.as_i32().map(|x| (x as u32 & 0x2) == 0x2)) + }) + .ok() + .flatten() + .flatten() + .unwrap_or_default(); + + update.insert( + "default_permissions", + (*DEFAULT_PERMISSION_SERVER + // Remove Send Message permission if it wasn't originally granted + ^ (if has_send { + 0 + } else { + Permission::SendMessage as u64 + })) as i64, + ); + + if let Some(Bson::Document(mut roles)) = document.remove("roles") { + for role in roles.keys().cloned().collect::>() { + if let Some(Bson::Document(role)) = roles.get_mut(role) { + role.insert( + "permissions", + doc! { + "a": 0_i64, + "d": 0_i64, + }, + ); + } + } + + update.insert("roles", roles); + } + + servers + .update_one(doc! { "_id": id }, doc! { "$set": update }, None) + .await + .unwrap(); + } + + let channels = db.col::("channels"); + let mut cursor = channels.find(doc! {}, None).await.unwrap(); + + while let Some(Ok(document)) = cursor.next().await { + let id = document.get_str("_id").unwrap().to_string(); + info!("Updating channel {id}"); + + let mut unset = doc! { + "permissions": 1_i32, + "role_permissions": 1_i32, + }; + + // Try to pluck channel permission SendMessage (0x2) + let has_send = document + .get_i32("default_permissions") + .map(|x| (x as u32 & 0x2) == 0x2) + .unwrap_or(true); + + if has_send { + // Let parent permissions fall through. + unset.insert("default_permissions", 1_i32); + } + + let mut update = doc! { + "$unset": unset + }; + + if !has_send { + // Block send message permission. + update.insert( + "$set", + doc! { + "default_permissions": { + "a": 0_i64, + "d": Permission::SendMessage as i64 + } + }, + ); + } + + channels + .update_one(doc! { "_id": id }, update, None) + .await + .unwrap(); + } + } + + if revision <= 14 { + info!("Running migration [revision 14 / 21-04-2022]: Split content into content and system fields."); + + db.col::("messages") + .update_many( + doc! { + "content": { + "$type": "object" + } + }, + doc! { + "$rename": { + "content": "system" + } + }, + None, + ) + .await + .unwrap(); + } + + // Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`. + + // Reminder to update LATEST_REVISION when adding new migrations. + LATEST_REVISION +} diff --git a/crates/quark/src/impl/mongo/autumn/attachment.rs b/crates/quark/src/impl/mongo/autumn/attachment.rs new file mode 100644 index 00000000..1a35c8b0 --- /dev/null +++ b/crates/quark/src/impl/mongo/autumn/attachment.rs @@ -0,0 +1,125 @@ +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!("{}_id", parent_type); + 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", + }) + } +} diff --git a/crates/quark/src/impl/mongo/channels/channel.rs b/crates/quark/src/impl/mongo/channels/channel.rs new file mode 100644 index 00000000..ede29150 --- /dev/null +++ b/crates/quark/src/impl/mongo/channels/channel.rs @@ -0,0 +1,323 @@ +use std::collections::HashSet; + +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 + } +} + +#[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", + }) + } + + async fn check_channels_exist(&self, channels: &HashSet) -> Result { + let count = channels.len() as u64; + self.col::(COL) + .count_documents( + doc! { + "_id": { + "$in": channels.iter().cloned().collect::>() + } + }, + None, + ) + .await + .map(|x| x == count) + .map_err(|_| Error::DatabaseError { + operation: "count_documents", + 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 new file mode 100644 index 00000000..ebe1ce13 --- /dev/null +++ b/crates/quark/src/impl/mongo/channels/channel_invite.rs @@ -0,0 +1,31 @@ +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 new file mode 100644 index 00000000..0f5fd733 --- /dev/null +++ b/crates/quark/src/impl/mongo/channels/channel_unread.rs @@ -0,0 +1,104 @@ +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<()> { + self.col::(COL) + .update_one( + doc! { + "_id.channel": { + "$in": channels + }, + "_id.user": user, + }, + doc! { + "$unset": { + "mentions": 1_i32 + }, + "$set": { + "last_id": Ulid::new().to_string() + } + }, + UpdateOptions::builder().upsert(true).build(), + ) + .await + .map(|_| ()) + .map_err(|_| Error::DatabaseError { + operation: "update", + with: "channel_unread", + }) + } + + 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 new file mode 100644 index 00000000..ddc423ad --- /dev/null +++ b/crates/quark/src/impl/mongo/channels/message.rs @@ -0,0 +1,285 @@ +use bson::{to_bson, Document}; +use futures::try_join; +use mongodb::options::FindOptions; + +use crate::models::message::{AppendMessage, Message, MessageSort, 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( + "attachment", + 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_bulk_messages(doc! { + "_id": id + }) + .await + } + + 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, + channel: &str, + limit: Option, + before: Option, + after: Option, + sort: Option, + nearby: Option, + ) -> Result> { + let limit = limit.unwrap_or(50); + Ok(if let Some(nearby) = nearby { + let (a, b) = try_join!( + self.find_with_options::<_, Message>( + COL, + doc! { + "channel": channel, + "_id": { + "$gte": &nearby + } + }, + FindOptions::builder() + .limit(limit / 2 + 1) + .sort(doc! { + "_id": 1_i32 + }) + .build(), + ), + self.find_with_options::<_, Message>( + COL, + doc! { + "channel": channel, + "_id": { + "$lt": &nearby + } + }, + FindOptions::builder() + .limit(limit / 2) + .sort(doc! { + "_id": -1_i32 + }) + .build(), + ) + )?; + + [a, b].concat() + } else { + let mut query = doc! { "channel": channel }; + if let Some(before) = before { + query.insert("_id", doc! { "$lt": before }); + } + + if let Some(after) = after { + query.insert("_id", doc! { "$gt": after }); + } + + let sort: i32 = if let MessageSort::Latest = sort.unwrap_or(MessageSort::Latest) { + -1 + } else { + 1 + }; + + self.find_with_options::<_, Message>( + COL, + query, + FindOptions::builder() + .limit(limit) + .sort(doc! { + "_id": sort + }) + .build(), + ) + .await? + }) + } + + async fn search_messages( + &self, + channel: &str, + query: &str, + limit: Option, + before: Option, + after: Option, + sort: MessageSort, + ) -> Result> { + let limit = limit.unwrap_or(50); + + let mut filter = doc! { + "channel": channel, + "$text": { + "$search": query + } + }; + + 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); + } + + self.find_with_options( + COL, + filter, + FindOptions::builder() + .projection(if let MessageSort::Relevance = &sort { + doc! { + "score": { + "$meta": "textScore" + } + } + } else { + doc! {} + }) + .limit(limit) + .sort(match &sort { + MessageSort::Relevance => doc! { + "score": { + "$meta": "textScore" + } + }, + MessageSort::Latest => doc! { + "_id": -1_i32 + }, + MessageSort::Oldest => doc! { + "_id": 1_i32 + }, + }) + .build(), + ) + .await + } +} diff --git a/crates/quark/src/impl/mongo/mod.rs b/crates/quark/src/impl/mongo/mod.rs new file mode 100644 index 00000000..54a933eb --- /dev/null +++ b/crates/quark/src/impl/mongo/mod.rs @@ -0,0 +1,250 @@ +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 migrations; +} + +pub mod autumn { + pub mod attachment; +} + +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 bot; + pub mod user; + pub mod user_settings; +} + +#[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>, + { + Ok(self + .col::(collection) + .find(projection, options) + .await + .map_err(|_| Error::DatabaseError { + operation: "find", + with: collection, + })? + .filter_map(|s| async { 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/servers/server.rs b/crates/quark/src/impl/mongo/servers/server.rs new file mode 100644 index 00000000..b8efabdb --- /dev/null +++ b/crates/quark/src/impl/mongo/servers/server.rs @@ -0,0 +1,228 @@ +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 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 new file mode 100644 index 00000000..2b343034 --- /dev/null +++ b/crates/quark/src/impl/mongo/servers/server_ban.rs @@ -0,0 +1,47 @@ +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 new file mode 100644 index 00000000..bebb7e65 --- /dev/null +++ b/crates/quark/src/impl/mongo/servers/server_member.rs @@ -0,0 +1,134 @@ +use bson::Document; + +use crate::models::server_member::{FieldsMember, Member, MemberCompositeKey, PartialMember}; +use crate::r#impl::mongo::IntoDocumentPath; +use crate::{AbstractServerMember, Error, Result}; + +use super::super::MongoDb; + +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 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", + }) + } +} diff --git a/crates/quark/src/impl/mongo/users/bot.rs b/crates/quark/src/impl/mongo/users/bot.rs new file mode 100644 index 00000000..7dc5ff85 --- /dev/null +++ b/crates/quark/src/impl/mongo/users/bot.rs @@ -0,0 +1,68 @@ +use crate::models::bot::{Bot, FieldsBot, PartialBot}; +use crate::r#impl::mongo::IntoDocumentPath; +use crate::{AbstractBot, Result}; + +use super::super::MongoDb; + +static COL: &str = "bots"; + +#[async_trait] +impl AbstractBot for MongoDb { + async fn fetch_bot(&self, id: &str) -> Result { + self.find_one_by_id(COL, id).await + } + + async fn fetch_bot_by_token(&self, token: &str) -> Result { + self.find_one( + COL, + doc! { + "token": token + }, + ) + .await + } + + async fn insert_bot(&self, bot: &Bot) -> Result<()> { + self.insert_one(COL, &bot).await.map(|_| ()) + } + + async fn update_bot(&self, id: &str, bot: &PartialBot, remove: Vec) -> Result<()> { + self.update_one_by_id( + COL, + id, + bot, + remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(), + None, + ) + .await + .map(|_| ()) + } + + async fn delete_bot(&self, id: &str) -> Result<()> { + self.delete_one_by_id(COL, id).await.map(|_| ()) + } + + async fn fetch_bots_by_user(&self, user_id: &str) -> Result> { + self.find( + COL, + doc! { + "owner": user_id + }, + ) + .await + } + + async fn get_number_of_bots_by_user(&self, user_id: &str) -> Result { + // ! FIXME: move this to generic? + self.fetch_bots_by_user(user_id).await.map(|x| x.len()) + } +} + +impl IntoDocumentPath for FieldsBot { + fn as_path(&self) -> Option<&'static str> { + match self { + FieldsBot::InteractionsURL => Some("interactions_url"), + FieldsBot::Token => None, + } + } +} diff --git a/crates/quark/src/impl/mongo/users/user.rs b/crates/quark/src/impl/mongo/users/user.rs new file mode 100644 index 00000000..a5459f9d --- /dev/null +++ b/crates/quark/src/impl/mongo/users/user.rs @@ -0,0 +1,319 @@ +use bson::Document; +use futures::StreamExt; +use mongodb::options::{Collation, CollationStrength, FindOneOptions, FindOptions}; + +use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus, User}; +use crate::r#impl::mongo::IntoDocumentPath; +use crate::{AbstractUser, Error, Result}; + +use super::super::MongoDb; + +lazy_static! { + static ref FIND_USERNAME_OPTIONS: FindOneOptions = 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) -> Result { + self.find_one_with_options( + COL, + doc! { + "username": username + }, + 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 is_username_taken(&self, username: &str) -> Result { + // ! FIXME: move this up to generic + match self.fetch_user_by_username(username).await { + Ok(_) => Ok(true), + Err(Error::NotFound) => Ok(false), + Err(error) => Err(error), + } + } + + 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", + }) + } +} diff --git a/crates/quark/src/impl/mongo/users/user_settings.rs b/crates/quark/src/impl/mongo/users/user_settings.rs new file mode 100644 index 00000000..f73bf5d6 --- /dev/null +++ b/crates/quark/src/impl/mongo/users/user_settings.rs @@ -0,0 +1,62 @@ +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/lib.rs b/crates/quark/src/lib.rs new file mode 100644 index 00000000..9a27ea80 --- /dev/null +++ b/crates/quark/src/lib.rs @@ -0,0 +1,72 @@ +#[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 optional_struct; +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate bitfield; +#[macro_use] +pub extern crate bson; + +pub use iso8601_timestamp::Timestamp; +pub use redis_kiss; + +pub mod events; +pub mod r#impl; +pub mod models; +pub mod presence; +pub mod tasks; +pub mod types; + +mod database; +mod permissions; +mod traits; +mod util; + +pub use database::*; +pub use traits::*; + +pub use permissions::defn::*; +pub use permissions::{get_relationship, perms}; + +pub use util::r#ref::Ref; +pub use util::result::{EmptyResponse, Error, Result}; +pub use util::variables; + +#[cfg(feature = "rocket_impl")] +use rocket::State; + +#[cfg(feature = "rocket_impl")] +pub type Db = State; + +/// Configure logging and common Rust variables +pub fn setup_logging() -> sentry::ClientInitGuard { + dotenv::dotenv().ok(); + + if std::env::var("RUST_LOG").is_err() { + std::env::set_var("RUST_LOG", "info"); + } + + if std::env::var("ROCKET_ADDRESS").is_err() { + std::env::set_var("ROCKET_ADDRESS", "0.0.0.0"); + } + + pretty_env_logger::init(); + + sentry::init(( + "https://62fd0e02c5354905b4e286757f4beb16@sentry.insert.moe/4", + sentry::ClientOptions { + release: sentry::release_name!(), + ..Default::default() + }, + )) +} diff --git a/crates/quark/src/models/admin/migrations.rs b/crates/quark/src/models/admin/migrations.rs new file mode 100644 index 00000000..0a3e620f --- /dev/null +++ b/crates/quark/src/models/admin/migrations.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; + +/// Document representing migration information +#[derive(Serialize, Deserialize)] +pub struct MigrationInfo { + /// Unique Id + #[serde(rename = "_id")] + id: i32, + /// Current database revision + revision: i32, +} diff --git a/crates/quark/src/models/admin/simple.rs b/crates/quark/src/models/admin/simple.rs new file mode 100644 index 00000000..fe9bdedd --- /dev/null +++ b/crates/quark/src/models/admin/simple.rs @@ -0,0 +1,8 @@ +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/autumn/attachment.rs b/crates/quark/src/models/autumn/attachment.rs new file mode 100644 index 00000000..120da29a --- /dev/null +++ b/crates/quark/src/models/autumn/attachment.rs @@ -0,0 +1,61 @@ +use serde::{Deserialize, Serialize}; + +/// Metadata associated with file +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +#[serde(tag = "type")] +pub enum Metadata { + /// File is just a generic uncategorised file + 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, +} + +impl Default for Metadata { + fn default() -> Metadata { + Metadata::File + } +} + +/// 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/channels/channel.rs b/crates/quark/src/models/channels/channel.rs new file mode 100644 index 00000000..a8fa3740 --- /dev/null +++ b/crates/quark/src/models/channels/channel.rs @@ -0,0 +1,169 @@ +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, 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 new file mode 100644 index 00000000..0dc62212 --- /dev/null +++ b/crates/quark/src/models/channels/channel_invite.rs @@ -0,0 +1,32 @@ +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 new file mode 100644 index 00000000..6832a014 --- /dev/null +++ b/crates/quark/src/models/channels/channel_unread.rs @@ -0,0 +1,25 @@ +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 new file mode 100644 index 00000000..91ea6e0e --- /dev/null +++ b/crates/quark/src/models/channels/message.rs @@ -0,0 +1,170 @@ +use serde::{Deserialize, Serialize}; +use validator::Validate; + +use iso8601_timestamp::Timestamp; + +#[cfg(feature = "rocket_impl")] +use rocket::FromFormField; + +use crate::{ + models::{attachment::File, Member, User}, + types::january::Embed, +}; + +/// # 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)] +pub struct SendableEmbed { + #[validate(length(min = 1, max = 128))] + pub icon_url: Option, + 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 = 64))] + 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 }, +} + +/// 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 = 128))] + pub avatar: Option, +} + +/// 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 that sent this message + pub author: String, + + /// 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>, + /// 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)] +#[cfg_attr(feature = "rocket_impl", derive(FromFormField))] +pub enum MessageSort { + /// Sort by the most relevant messages + Relevance, + /// Sort by the newest messages first + Latest, + /// Sort by the oldest messages first + Oldest, +} + +impl Default for MessageSort { + fn default() -> MessageSort { + MessageSort::Relevance + } +} + +/// # 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>, +} diff --git a/crates/quark/src/models/mod.rs b/crates/quark/src/models/mod.rs new file mode 100644 index 00000000..8dbd46ca --- /dev/null +++ b/crates/quark/src/models/mod.rs @@ -0,0 +1,47 @@ +mod admin { + pub mod migrations; + pub mod simple; +} + +mod autumn { + pub mod attachment; +} + +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 bot; + pub mod user; + pub mod user_settings; +} + +pub use admin::*; +pub use autumn::*; +pub use channels::*; +pub use servers::*; +pub use users::*; + +pub use attachment::File; +pub use bot::Bot; +pub use channel::Channel; +pub use channel_invite::Invite; +pub use channel_unread::ChannelUnread; +pub use message::Message; +pub use migrations::MigrationInfo; +pub use server::Server; +pub use server_ban::ServerBan; +pub use server_member::Member; +pub use simple::SimpleModel; +pub use user::User; +pub use user_settings::UserSettings; diff --git a/crates/quark/src/models/servers/server.rs b/crates/quark/src/models/servers/server.rs new file mode 100644 index 00000000..2f3ce468 --- /dev/null +++ b/crates/quark/src/models/servers/server.rs @@ -0,0 +1,150 @@ +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 + // ! FIXME: this may be redundant + 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, + + /// Enum 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, Clone)] +pub enum FieldsServer { + Description, + Categories, + SystemMessages, + Icon, + Banner, +} + +/// Optional fields on server object +#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, 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 new file mode 100644 index 00000000..a99adc2a --- /dev/null +++ b/crates/quark/src/models/servers/server_ban.rs @@ -0,0 +1,13 @@ +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 new file mode 100644 index 00000000..7c911bf1 --- /dev/null +++ b/crates/quark/src/models/servers/server_member.rs @@ -0,0 +1,50 @@ +use serde::{Deserialize, Serialize}; + +use crate::models::attachment::File; + +/// 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, Default)] +#[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, + + /// 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 = "Option::is_none")] + pub roles: Option>, +} + +/// Optional fields on server member object +#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Clone)] +pub enum FieldsMember { + Nickname, + Avatar, + Roles, +} + +/// 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 new file mode 100644 index 00000000..fd438d98 --- /dev/null +++ b/crates/quark/src/models/users/bot.rs @@ -0,0 +1,63 @@ +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)] +pub enum FieldsBot { + Token, + InteractionsURL, +} diff --git a/crates/quark/src/models/users/user.rs b/crates/quark/src/models/users/user.rs new file mode 100644 index 00000000..33434ab1 --- /dev/null +++ b/crates/quark/src/models/users/user.rs @@ -0,0 +1,177 @@ +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)] +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)] +pub enum Presence { + Online, + Idle, + Busy, + 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, +} + +/// 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, + #[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, Clone)] +pub enum FieldsUser { + Avatar, + StatusText, + StatusPresence, + ProfileContent, + ProfileBackground, +} + +/// 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 new file mode 100644 index 00000000..21efab9a --- /dev/null +++ b/crates/quark/src/models/users/user_settings.rs @@ -0,0 +1,6 @@ +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 new file mode 100644 index 00000000..f2a7866e --- /dev/null +++ b/crates/quark/src/permissions/defn/mod.rs @@ -0,0 +1,93 @@ +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.0 |= v.allow; + self.0 &= !v.deny; + } +} + +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 as u64) + } +} + +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 new file mode 100644 index 00000000..95cf85fe --- /dev/null +++ b/crates/quark/src/permissions/defn/permission.rs @@ -0,0 +1,156 @@ +use num_enum::TryFromPrimitive; +use serde::{Deserialize, Serialize}; +use std::ops; + +/// 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, + + // % 2 bits 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, + + // % 1 bits reserved + + // * 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 }); + +lazy_static! { + pub static ref DEFAULT_PERMISSION_VIEW_ONLY: u64 = + Permission::ViewChannel + Permission::ReadMessageHistory; + pub static ref DEFAULT_PERMISSION: u64 = *DEFAULT_PERMISSION_VIEW_ONLY + + Permission::SendMessage + + Permission::InviteOthers + + Permission::SendEmbeds + + Permission::UploadFiles + + Permission::Connect + + Permission::Speak; + pub static ref DEFAULT_PERMISSION_SAVED_MESSAGES: u64 = Permission::GrantAllSafe as u64; + pub static ref DEFAULT_PERMISSION_DIRECT_MESSAGE: u64 = + *DEFAULT_PERMISSION + Permission::ManageChannel; + pub static ref DEFAULT_PERMISSION_SERVER: u64 = + *DEFAULT_PERMISSION + Permission::ChangeNickname + Permission::ChangeAvatar; +} + +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; + + // * 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 new file mode 100644 index 00000000..faed0e5f --- /dev/null +++ b/crates/quark/src/permissions/defn/user.rs @@ -0,0 +1,29 @@ +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 new file mode 100644 index 00000000..30506541 --- /dev/null +++ b/crates/quark/src/permissions/impl/mod.rs @@ -0,0 +1,2 @@ +pub mod permission; +pub mod user; diff --git a/crates/quark/src/permissions/impl/permission.rs b/crates/quark/src/permissions/impl/permission.rs new file mode 100644 index 00000000..4cdbfa15 --- /dev/null +++ b/crates/quark/src/permissions/impl/permission.rs @@ -0,0 +1,219 @@ +use std::collections::HashSet; + +use crate::{ + models::Channel, permissions::PermissionCalculator, Override, Permission, PermissionValue, + Permissions, Perms, Result, 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> = if let Some(roles) = member.roles.as_ref() { + roles.iter().collect() + } else { + HashSet::new() + }; + + 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); + } + } + + 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 { .. } => (*DEFAULT_PERMISSION_SAVED_MESSAGES).into(), + Channel::DirectMessage { recipients, .. } => { + // 2. 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); + + // 3. Calculate user permissions. + let perms = data.calc_user(db).await; + + // 4. Check if the user can send messages. + if perms.get_send_message() { + (*DEFAULT_PERMISSION_DIRECT_MESSAGE).into() + } else { + (*DEFAULT_PERMISSION_VIEW_ONLY).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> = if let Some(roles) = member.roles.as_ref() { + roles.iter().collect() + } else { + HashSet::new() + }; + + 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); + } + } + + 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 new file mode 100644 index 00000000..22c1939e --- /dev/null +++ b/crates/quark/src/permissions/impl/user.rs @@ -0,0 +1,100 @@ +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.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; + } + _ => {} + } + + // ! FIXME: add boolean switch for permission for users to globally message a user + // maybe an enum? + // PrivacyLevel { Private, Friends, Mutual, Public, Global } + + // ! FIXME: add boolean switch for permission for users to mutually DM a user + + 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 new file mode 100644 index 00000000..81a15ce6 --- /dev/null +++ b/crates/quark/src/permissions/mod.rs @@ -0,0 +1,171 @@ +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/presence/entry.rs b/crates/quark/src/presence/entry.rs new file mode 100644 index 00000000..8373ae25 --- /dev/null +++ b/crates/quark/src/presence/entry.rs @@ -0,0 +1,64 @@ +use std::env; + +use serde::{Deserialize, Serialize}; + +lazy_static! { + pub static ref REGION_ID: u16 = env::var("REGION_ID") + .unwrap_or_else(|_| "0".to_string()) + .parse() + .unwrap(); + pub static ref REGION_KEY: String = format!("region{}", &*REGION_ID); +} + +/// Compact presence information for a user +#[derive(Serialize, Deserialize, Debug)] +pub struct PresenceEntry { + /// Region this session exists in + /// + /// We can have up to 65535 regions + pub region_id: u16, + + /// Unique session ID + pub session_id: u8, + + /// Known flags about session + pub flags: u8, +} + +impl PresenceEntry { + /// Create a new presence entry from a given session ID and known flags + pub fn from(session_id: u8, flags: u8) -> Self { + Self { + region_id: *REGION_ID, + session_id, + flags, + } + } +} + +pub trait PresenceOp { + /// Find next available session ID + fn find_next_id(&self) -> u8; +} + +impl PresenceOp for Vec { + fn find_next_id(&self) -> u8 { + // O(n^2) scan algorithm + // should be relatively fast at low numbers anyways + for i in 0..255 { + let mut found = false; + for entry in self { + if entry.session_id == i { + found = true; + break; + } + } + + if !found { + return i; + } + } + + 255 + } +} diff --git a/crates/quark/src/presence/mod.rs b/crates/quark/src/presence/mod.rs new file mode 100644 index 00000000..301cd010 --- /dev/null +++ b/crates/quark/src/presence/mod.rs @@ -0,0 +1,162 @@ +use std::collections::HashSet; + +use redis_kiss::{get_connection, AsyncCommands}; + +mod entry; +mod operations; + +use entry::{PresenceEntry, PresenceOp}; +use operations::{ + __add_to_set_sessions, __delete_key_presence_entry, __get_key_presence_entry, + __get_set_sessions, __remove_from_set_sessions, __set_key_presence_entry, +}; + +use crate::presence::operations::__delete_set_sessions; + +use self::entry::REGION_KEY; + +/// Create a new presence session, returns the ID of this session +pub async fn presence_create_session(user_id: &str, flags: u8) -> (bool, u8) { + info!("Creating a presence session for {user_id} with flags {flags}"); + + // Try to find the presence entry for this user. + let mut conn = get_connection().await.unwrap(); + let mut entry: Vec = __get_key_presence_entry(&mut conn, user_id) + .await + .unwrap_or_default(); + + // Return whether this was the first session. + let was_empty = entry.is_empty(); + info!("User ID {} just came online.", &user_id); + + // Generate session ID and push new entry. + let session_id = entry.find_next_id(); + entry.push(PresenceEntry::from(session_id, flags)); + __set_key_presence_entry(&mut conn, user_id, entry).await; + + // Add to region set in case of failure. + __add_to_set_sessions(&mut conn, &*REGION_KEY, user_id, session_id).await; + (was_empty, session_id) +} + +/// Delete existing presence session +pub async fn presence_delete_session(user_id: &str, session_id: u8) -> bool { + presence_delete_session_internal(user_id, session_id, false).await +} + +/// Delete existing presence session (but also choose whether to skip region) +async fn presence_delete_session_internal( + user_id: &str, + session_id: u8, + skip_region: bool, +) -> bool { + info!("Deleting presence session for {user_id} with id {session_id}"); + + // Return whether this was the last session. + let mut is_empty = false; + + // Only continue if we can actually find one. + let mut conn = get_connection().await.unwrap(); + let entry: Option> = __get_key_presence_entry(&mut conn, user_id).await; + if let Some(entry) = entry { + let entries = entry + .into_iter() + .filter(|x| x.session_id != session_id) + .collect::>(); + + // If entry is empty, then just delete it. + if entries.is_empty() { + __delete_key_presence_entry(&mut conn, user_id).await; + is_empty = true; + } else { + __set_key_presence_entry(&mut conn, user_id, entries).await; + } + + // Remove from region set. + if !skip_region { + __remove_from_set_sessions(&mut conn, &*REGION_KEY, user_id, session_id).await; + } + } + + if is_empty { + info!("User ID {} just went offline.", &user_id); + } + + is_empty +} + +/// Check whether a given user ID is online +pub async fn presence_is_online(user_id: &str) -> bool { + if let Ok(mut conn) = get_connection().await { + conn.exists(user_id).await.unwrap_or(false) + } else { + false + } +} + +/// Check whether a set of users is online, returns a set of the online user IDs +pub async fn presence_filter_online(user_ids: &'_ [String]) -> HashSet { + // Ignore empty list immediately, to save time. + let mut set = HashSet::new(); + if user_ids.is_empty() { + return set; + } + + // We need to handle a special case where only one is present + // as for some reason or another, Redis does not like us sending + // a list of just one ID to the server. + if user_ids.len() == 1 { + if presence_is_online(&user_ids[0]).await { + set.insert(user_ids[0].to_string()); + } + + return set; + } + + // Otherwise, go ahead as normal. + if let Ok(mut conn) = get_connection().await { + let data: Vec>> = conn.get(user_ids).await.unwrap_or_default(); + if data.is_empty() { + return set; + } + + // We filter known values to figure out who is online. + for i in 0..user_ids.len() { + if data[i].is_some() { + set.insert(user_ids[i].to_string()); + } + } + } + + set +} + +/// Reset any stale presence data +pub async fn presence_clear_region(region_id: Option<&str>) { + let region_id = region_id.unwrap_or(&*REGION_KEY); + let mut conn = get_connection().await.expect("Redis connection"); + + let sessions = __get_set_sessions(&mut conn, region_id).await; + if !sessions.is_empty() { + info!( + "Cleaning up {} sessions, this may take a while...", + sessions.len() + ); + + // Iterate and delete each session, this will + // also send out any relevant events. + for session in sessions { + let parts = session.split(':').collect::>(); + if let (Some(user_id), Some(session_id)) = (parts.get(0), parts.get(1)) { + if let Ok(session_id) = session_id.parse() { + presence_delete_session_internal(user_id, session_id, true).await; + } + } + } + + // Then clear the set in Redis. + __delete_set_sessions(&mut conn, region_id).await; + + info!("Clean up complete."); + } +} diff --git a/crates/quark/src/presence/operations.rs b/crates/quark/src/presence/operations.rs new file mode 100644 index 00000000..4431b2f4 --- /dev/null +++ b/crates/quark/src/presence/operations.rs @@ -0,0 +1,57 @@ +use redis_kiss::{AsyncCommands, Conn}; + +use super::entry::PresenceEntry; + +/// Set presence entry by given ID +pub async fn __set_key_presence_entry(conn: &mut Conn, id: &str, data: Vec) { + let _: Option<()> = conn.set(id, bincode::serialize(&data).unwrap()).await.ok(); +} + +/// Delete presence entry by given ID +pub async fn __delete_key_presence_entry(conn: &mut Conn, id: &str) { + let _: Option<()> = conn.del(id).await.ok(); +} + +/// Get presence entry by given ID +pub async fn __get_key_presence_entry(conn: &mut Conn, id: &str) -> Option> { + conn.get::<_, Option>>(id) + .await + .unwrap() + .map(|entry| bincode::deserialize(&entry[..]).unwrap()) +} + +/// Add to region session set +pub async fn __add_to_set_sessions( + conn: &mut Conn, + region_id: &str, + user_id: &str, + session_id: u8, +) { + let _: Option<()> = conn + .sadd(region_id, format!("{user_id}:{session_id}")) + .await + .ok(); +} + +/// Remove from region session set +pub async fn __remove_from_set_sessions( + conn: &mut Conn, + region_id: &str, + user_id: &str, + session_id: u8, +) { + let _: Option<()> = conn + .srem(region_id, format!("{user_id}:{session_id}")) + .await + .ok(); +} + +/// Get region session set as list +pub async fn __get_set_sessions(conn: &mut Conn, region_id: &str) -> Vec { + conn.smembers::<_, Vec>(region_id).await.unwrap() +} + +/// Delete region session set +pub async fn __delete_set_sessions(conn: &mut Conn, region_id: &str) { + let _: () = conn.del(region_id).await.unwrap(); +} diff --git a/crates/quark/src/tasks/ack.rs b/crates/quark/src/tasks/ack.rs new file mode 100644 index 00000000..eee72be1 --- /dev/null +++ b/crates/quark/src/tasks/ack.rs @@ -0,0 +1,123 @@ +// Queue Type: Debounced +use crate::Database; + +use deadqueue::limited::Queue; +use mongodb::bson::doc; +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, +} + +lazy_static! { + static ref Q: Queue = 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(mut 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 new file mode 100644 index 00000000..dd8c6c21 --- /dev/null +++ b/crates/quark/src/tasks/last_message_id.rs @@ -0,0 +1,90 @@ +// Queue Type: Debounced +use crate::{models::channel::PartialChannel, Database}; + +use deadqueue::limited::Queue; +use log::info; +use mongodb::bson::doc; +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, +} + +lazy_static! { + static ref Q: Queue = 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(mut 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 new file mode 100644 index 00000000..9d8787ad --- /dev/null +++ b/crates/quark/src/tasks/mod.rs @@ -0,0 +1,58 @@ +//! 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) { + 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(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 new file mode 100644 index 00000000..1eca0a04 --- /dev/null +++ b/crates/quark/src/tasks/process_embeds.rs @@ -0,0 +1,57 @@ +use crate::util::variables::delta::{JANUARY_URL, MAX_EMBED_COUNT}; +use crate::{ + models::{message::AppendMessage, Message}, + types::january::Embed, + Database, +}; + +use deadqueue::limited::Queue; +use log::error; + +/// 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, +} + +lazy_static! { + static ref Q: Queue = 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) { + loop { + let task = Q.pop().await; + if let Ok(embeds) = Embed::generate(task.content, &*JANUARY_URL, *MAX_EMBED_COUNT).await { + 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 new file mode 100644 index 00000000..4c72d062 --- /dev/null +++ b/crates/quark/src/tasks/web_push.rs @@ -0,0 +1,135 @@ +use crate::util::variables::delta::VAPID_PRIVATE_KEY; +use crate::{bson::doc, r#impl::mongo::MongoDb, Database}; + +use deadqueue::limited::Queue; +use futures::StreamExt; +use rauth::entities::Session; +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, +} + +lazy_static! { + static ref Q: Queue = 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`"); + + if let Database::MongoDb(MongoDb(db)) = db { + loop { + let task = Q.pop().await; + + // ! FIXME: this is hard-coded until rauth is merged into quark + if let Ok(mut cursor) = db + .database("revolt") + .collection::("sessions") + .find( + doc! { + "user_id": { + "$in": task.recipients + }, + "subscription": { + "$exists": true + } + }, + None, + ) + .await + { + while let Some(Ok(session)) = cursor.next().await { + 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/migrations.rs b/crates/quark/src/traits/admin/migrations.rs new file mode 100644 index 00000000..bb46db7a --- /dev/null +++ b/crates/quark/src/traits/admin/migrations.rs @@ -0,0 +1,6 @@ +use crate::Result; + +#[async_trait] +pub trait AbstractMigrations: Sync + Send { + async fn migrate_database(&self) -> Result<()>; +} diff --git a/crates/quark/src/traits/autumn/attachment.rs b/crates/quark/src/traits/autumn/attachment.rs new file mode 100644 index 00000000..3a2665ce --- /dev/null +++ b/crates/quark/src/traits/autumn/attachment.rs @@ -0,0 +1,16 @@ +use crate::models::attachment::File; +use crate::Result; + +#[async_trait] +pub trait AbstractAttachment: Sync + Send { + async fn find_and_use_attachment( + &self, + id: &str, + tag: &str, + parent_type: &str, + parent_id: &str, + ) -> Result; + async fn insert_attachment(&self, attachment: &File) -> Result<()>; + async fn mark_attachment_as_reported(&self, id: &str) -> Result<()>; + async fn mark_attachment_as_deleted(&self, id: &str) -> Result<()>; +} diff --git a/crates/quark/src/traits/channels/channel.rs b/crates/quark/src/traits/channels/channel.rs new file mode 100644 index 00000000..0eba4cee --- /dev/null +++ b/crates/quark/src/traits/channels/channel.rs @@ -0,0 +1,61 @@ +use std::collections::HashSet; + +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 + /// ! TODO: we need separate Channel::update which also sends out the relevant events + /// ! also applies to other methods I guess, try to restrict event bound methods to + /// ! the models themselves instead of the abstract database + 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 + /// ! FIXME: may want to refactor to just use normal updates + async fn set_channel_role_permission( + &self, + channel: &str, + role: &str, + permissions: OverrideField, + ) -> Result<()>; + + /// Validate existence of channels + async fn check_channels_exist(&self, channels: &HashSet) -> Result; +} diff --git a/crates/quark/src/traits/channels/channel_invite.rs b/crates/quark/src/traits/channels/channel_invite.rs new file mode 100644 index 00000000..e571233a --- /dev/null +++ b/crates/quark/src/traits/channels/channel_invite.rs @@ -0,0 +1,17 @@ +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 new file mode 100644 index 00000000..24ba8bed --- /dev/null +++ b/crates/quark/src/traits/channels/channel_unread.rs @@ -0,0 +1,22 @@ +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 new file mode 100644 index 00000000..0e4c8110 --- /dev/null +++ b/crates/quark/src/traits/channels/message.rs @@ -0,0 +1,45 @@ +use crate::models::message::{AppendMessage, Message, MessageSort, 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 + async fn fetch_messages( + &self, + channel: &str, + limit: Option, + before: Option, + after: Option, + sort: Option, + nearby: Option, + ) -> Result>; + + /// Search for messages + async fn search_messages( + &self, + channel: &str, + query: &str, + limit: Option, + before: Option, + after: Option, + sort: MessageSort, + ) -> Result>; +} diff --git a/crates/quark/src/traits/mod.rs b/crates/quark/src/traits/mod.rs new file mode 100644 index 00000000..07edde0c --- /dev/null +++ b/crates/quark/src/traits/mod.rs @@ -0,0 +1,64 @@ +mod admin { + pub mod migrations; +} + +mod autumn { + pub mod attachment; +} + +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 bot; + pub mod user; + pub mod user_settings; +} + +pub use admin::migrations::AbstractMigrations; + +pub use autumn::attachment::AbstractAttachment; + +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::bot::AbstractBot; +pub use users::user::AbstractUser; +pub use users::user_settings::AbstractUserSettings; + +// pub trait AbstractEventEmitter {} +// + AbstractEventEmitter + +pub trait AbstractDatabase: + Sync + + Send + + AbstractMigrations + + AbstractAttachment + + AbstractChannel + + AbstractChannelInvite + + AbstractChannelUnread + + AbstractMessage + + AbstractServer + + AbstractServerBan + + AbstractServerMember + + AbstractBot + + AbstractUser + + AbstractUserSettings +{ +} diff --git a/crates/quark/src/traits/servers/server.rs b/crates/quark/src/traits/servers/server.rs new file mode 100644 index 00000000..94a34353 --- /dev/null +++ b/crates/quark/src/traits/servers/server.rs @@ -0,0 +1,42 @@ +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 new file mode 100644 index 00000000..ad25a295 --- /dev/null +++ b/crates/quark/src/traits/servers/server_ban.rs @@ -0,0 +1,18 @@ +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 new file mode 100644 index 00000000..caf57036 --- /dev/null +++ b/crates/quark/src/traits/servers/server_member.rs @@ -0,0 +1,37 @@ +use crate::models::server_member::{FieldsMember, Member, MemberCompositeKey, 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; + + /// 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/bot.rs b/crates/quark/src/traits/users/bot.rs new file mode 100644 index 00000000..e9421580 --- /dev/null +++ b/crates/quark/src/traits/users/bot.rs @@ -0,0 +1,26 @@ +use crate::models::bot::{Bot, FieldsBot, PartialBot}; +use crate::Result; + +#[async_trait] +pub trait AbstractBot: Sync + Send { + /// Fetch a bot by its id + async fn fetch_bot(&self, id: &str) -> Result; + + /// Fetch a bot by its token + async fn fetch_bot_by_token(&self, token: &str) -> Result; + + /// Insert new bot into the database + async fn insert_bot(&self, bot: &Bot) -> Result<()>; + + /// Update bot with new information + async fn update_bot(&self, id: &str, bot: &PartialBot, remove: Vec) -> Result<()>; + + /// Delete a bot from the database + async fn delete_bot(&self, id: &str) -> Result<()>; + + /// Fetch bots owned by a user + async fn fetch_bots_by_user(&self, user_id: &str) -> Result>; + + /// Get the number of bots owned by a user + async fn get_number_of_bots_by_user(&self, user_id: &str) -> Result; +} diff --git a/crates/quark/src/traits/users/user.rs b/crates/quark/src/traits/users/user.rs new file mode 100644 index 00000000..820b1941 --- /dev/null +++ b/crates/quark/src/traits/users/user.rs @@ -0,0 +1,56 @@ +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) -> 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>; + + /// Check whether a username is already in use by another user + async fn is_username_taken(&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 new file mode 100644 index 00000000..c6634cfd --- /dev/null +++ b/crates/quark/src/traits/users/user_settings.rs @@ -0,0 +1,14 @@ +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 new file mode 100644 index 00000000..8c3ce44f --- /dev/null +++ b/crates/quark/src/types/january.rs @@ -0,0 +1,243 @@ +use linkify::{LinkFinder, LinkKind}; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; + +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, + }, +} + +/// 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