]> Untitled Git - lemmy.git/commitdiff
Merge branch 'drone-io-dess' into move_views_to_diesel_drone
authorDessalines <tyhou13@gmx.com>
Mon, 21 Dec 2020 02:48:29 +0000 (21:48 -0500)
committerDessalines <tyhou13@gmx.com>
Mon, 21 Dec 2020 02:48:29 +0000 (21:48 -0500)
158 files changed:
Cargo.lock
Cargo.toml
ansible/templates/nginx.conf
api_tests/.eslintrc.json [new file with mode: 0644]
api_tests/.prettierrc.js [new file with mode: 0644]
api_tests/package.json
api_tests/src/comment.spec.ts
api_tests/src/community.spec.ts
api_tests/src/follow.spec.ts
api_tests/src/post.spec.ts
api_tests/src/private_message.spec.ts
api_tests/src/shared.ts
api_tests/src/user.spec.ts
api_tests/tsconfig.json [new file with mode: 0644]
api_tests/yarn.lock
docker/dev/docker-compose.yml
docker/docker_db_backup.sh
docker/federation/nginx.conf
docs/src/SUMMARY.md
docs/src/about/goals.md
docs/src/administration/administration.md
docs/src/contributing/custom_frontend.md [new file with mode: 0644]
docs/src/federation/federation.md
docs/src/federation/lemmy_protocol.md
docs/src/lemmy_council.md
lemmy_api/Cargo.toml
lemmy_api/src/claims.rs
lemmy_api/src/comment.rs
lemmy_api/src/community.rs
lemmy_api/src/lib.rs
lemmy_api/src/post.rs
lemmy_api/src/site.rs
lemmy_api/src/user.rs
lemmy_apub/Cargo.toml
lemmy_apub/src/activities/receive/comment.rs
lemmy_apub/src/activities/receive/comment_undo.rs
lemmy_apub/src/activities/receive/community.rs
lemmy_apub/src/activities/receive/mod.rs
lemmy_apub/src/activities/receive/post.rs
lemmy_apub/src/activities/receive/post_undo.rs
lemmy_apub/src/activities/receive/private_message.rs
lemmy_apub/src/activities/send/comment.rs
lemmy_apub/src/activities/send/community.rs
lemmy_apub/src/activities/send/post.rs
lemmy_apub/src/activities/send/private_message.rs
lemmy_apub/src/activities/send/user.rs
lemmy_apub/src/activity_queue.rs
lemmy_apub/src/extensions/group_extensions.rs
lemmy_apub/src/extensions/signatures.rs
lemmy_apub/src/fetcher.rs
lemmy_apub/src/http/comment.rs
lemmy_apub/src/http/community.rs
lemmy_apub/src/http/mod.rs
lemmy_apub/src/http/post.rs
lemmy_apub/src/http/user.rs
lemmy_apub/src/inbox/community_inbox.rs
lemmy_apub/src/inbox/mod.rs
lemmy_apub/src/inbox/receive_for_community.rs
lemmy_apub/src/inbox/shared_inbox.rs
lemmy_apub/src/inbox/user_inbox.rs
lemmy_apub/src/lib.rs
lemmy_apub/src/objects/comment.rs
lemmy_apub/src/objects/community.rs
lemmy_apub/src/objects/mod.rs
lemmy_apub/src/objects/post.rs
lemmy_apub/src/objects/private_message.rs
lemmy_apub/src/objects/user.rs
lemmy_db/Cargo.toml
lemmy_db/src/aggregates/comment_aggregates.rs [new file with mode: 0644]
lemmy_db/src/aggregates/community_aggregates.rs [new file with mode: 0644]
lemmy_db/src/aggregates/mod.rs [new file with mode: 0644]
lemmy_db/src/aggregates/post_aggregates.rs [new file with mode: 0644]
lemmy_db/src/aggregates/site_aggregates.rs [new file with mode: 0644]
lemmy_db/src/aggregates/user_aggregates.rs [new file with mode: 0644]
lemmy_db/src/comment_report.rs [deleted file]
lemmy_db/src/comment_view.rs [deleted file]
lemmy_db/src/community_view.rs [deleted file]
lemmy_db/src/lib.rs
lemmy_db/src/moderator_views.rs [deleted file]
lemmy_db/src/post_report.rs [deleted file]
lemmy_db/src/post_view.rs [deleted file]
lemmy_db/src/private_message_view.rs [deleted file]
lemmy_db/src/schema.rs
lemmy_db/src/site_view.rs [deleted file]
lemmy_db/src/source/activity.rs [moved from lemmy_db/src/activity.rs with 98% similarity]
lemmy_db/src/source/category.rs [moved from lemmy_db/src/category.rs with 89% similarity]
lemmy_db/src/source/comment.rs [moved from lemmy_db/src/comment.rs with 91% similarity]
lemmy_db/src/source/comment_report.rs [new file with mode: 0644]
lemmy_db/src/source/community.rs [moved from lemmy_db/src/community.rs with 87% similarity]
lemmy_db/src/source/mod.rs [new file with mode: 0644]
lemmy_db/src/source/moderator.rs [moved from lemmy_db/src/moderator.rs with 96% similarity]
lemmy_db/src/source/password_reset_request.rs [moved from lemmy_db/src/password_reset_request.rs with 98% similarity]
lemmy_db/src/source/post.rs [moved from lemmy_db/src/post.rs with 96% similarity]
lemmy_db/src/source/post_report.rs [new file with mode: 0644]
lemmy_db/src/source/private_message.rs [moved from lemmy_db/src/private_message.rs with 93% similarity]
lemmy_db/src/source/site.rs [moved from lemmy_db/src/site.rs with 82% similarity]
lemmy_db/src/source/user.rs [moved from lemmy_db/src/user.rs with 56% similarity]
lemmy_db/src/source/user_mention.rs [moved from lemmy_db/src/user_mention.rs with 93% similarity]
lemmy_db/src/user_mention_view.rs [deleted file]
lemmy_db/src/user_view.rs [deleted file]
lemmy_db/src/views/comment_report_view.rs [new file with mode: 0644]
lemmy_db/src/views/comment_view.rs [new file with mode: 0644]
lemmy_db/src/views/community/community_follower_view.rs [new file with mode: 0644]
lemmy_db/src/views/community/community_moderator_view.rs [new file with mode: 0644]
lemmy_db/src/views/community/community_user_ban_view.rs [new file with mode: 0644]
lemmy_db/src/views/community/community_view.rs [new file with mode: 0644]
lemmy_db/src/views/community/mod.rs [new file with mode: 0644]
lemmy_db/src/views/mod.rs [new file with mode: 0644]
lemmy_db/src/views/moderator/mod.rs [new file with mode: 0644]
lemmy_db/src/views/moderator/mod_add_community_view.rs [new file with mode: 0644]
lemmy_db/src/views/moderator/mod_add_view.rs [new file with mode: 0644]
lemmy_db/src/views/moderator/mod_ban_from_community_view.rs [new file with mode: 0644]
lemmy_db/src/views/moderator/mod_ban_view.rs [new file with mode: 0644]
lemmy_db/src/views/moderator/mod_lock_post_view.rs [new file with mode: 0644]
lemmy_db/src/views/moderator/mod_remove_comment_view.rs [new file with mode: 0644]
lemmy_db/src/views/moderator/mod_remove_community_view.rs [new file with mode: 0644]
lemmy_db/src/views/moderator/mod_remove_post_view.rs [new file with mode: 0644]
lemmy_db/src/views/moderator/mod_sticky_post_view.rs [new file with mode: 0644]
lemmy_db/src/views/post_report_view.rs [new file with mode: 0644]
lemmy_db/src/views/post_view.rs [new file with mode: 0644]
lemmy_db/src/views/private_message_view.rs [new file with mode: 0644]
lemmy_db/src/views/site_view.rs [new file with mode: 0644]
lemmy_db/src/views/user_mention_view.rs [new file with mode: 0644]
lemmy_db/src/views/user_view.rs [new file with mode: 0644]
lemmy_rate_limit/Cargo.toml
lemmy_structs/Cargo.toml
lemmy_structs/src/comment.rs
lemmy_structs/src/community.rs
lemmy_structs/src/lib.rs
lemmy_structs/src/post.rs
lemmy_structs/src/site.rs
lemmy_structs/src/user.rs
lemmy_structs/src/websocket.rs [deleted file]
lemmy_utils/Cargo.toml
lemmy_websocket/Cargo.toml
lemmy_websocket/src/chat_server.rs
migrations/2020-12-02-152437_create_site_aggregates/down.sql [new file with mode: 0644]
migrations/2020-12-02-152437_create_site_aggregates/up.sql [new file with mode: 0644]
migrations/2020-12-03-035643_create_user_aggregates/down.sql [new file with mode: 0644]
migrations/2020-12-03-035643_create_user_aggregates/up.sql [new file with mode: 0644]
migrations/2020-12-04-183345_create_community_aggregates/down.sql [new file with mode: 0644]
migrations/2020-12-04-183345_create_community_aggregates/up.sql [new file with mode: 0644]
migrations/2020-12-10-152350_create_post_aggregates/down.sql [new file with mode: 0644]
migrations/2020-12-10-152350_create_post_aggregates/up.sql [new file with mode: 0644]
migrations/2020-12-14-020038_create_comment_aggregates/down.sql [new file with mode: 0644]
migrations/2020-12-14-020038_create_comment_aggregates/up.sql [new file with mode: 0644]
migrations/2020-12-17-030456_create_alias_views/down.sql [new file with mode: 0644]
migrations/2020-12-17-030456_create_alias_views/up.sql [new file with mode: 0644]
migrations/2020-12-17-031053_remove_fast_tables_and_views/down.sql [new file with mode: 0644]
migrations/2020-12-17-031053_remove_fast_tables_and_views/up.sql [new file with mode: 0644]
src/code_migrations.rs
src/routes/api.rs
src/routes/feeds.rs
src/routes/images.rs
src/routes/nodeinfo.rs
src/routes/webfinger.rs
test.sh
tests/integration_test.rs

index 7144eac1bcdc5f5ba1c782a8f15f667cc9987b36..815cd20f69d4e55b5ca064fba84937a6302418a4 100644 (file)
@@ -8,7 +8,7 @@ checksum = "5e9fedbe571e267d9b93d071bdc4493f944022c6cce717ebb27d352026fc81c4"
 dependencies = [
  "chrono",
  "mime",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_json",
  "thiserror",
  "url",
@@ -21,7 +21,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bb8e19a0810cc25df3535061a08b7d8f8a734d309ea4411c57a9767e4a2ffa0e"
 dependencies = [
  "activitystreams",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_json",
 ]
 
@@ -33,8 +33,8 @@ checksum = "1be241f88f3b1e7e9a3fbe3b5a8a0f6915b5a1d7ee0d9a248d3376d01068cc60"
 dependencies = [
  "actix-rt",
  "actix_derive",
- "bitflags 1.2.1",
- "bytes",
+ "bitflags",
+ "bytes 0.5.6",
  "crossbeam-channel 0.4.4",
  "derive_more",
  "futures-channel",
@@ -44,7 +44,7 @@ dependencies = [
  "parking_lot",
  "pin-project 0.4.27",
  "smallvec",
- "tokio 0.2.22",
+ "tokio 0.2.24",
  "tokio-util",
  "trust-dns-proto",
  "trust-dns-resolver",
@@ -56,13 +56,13 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570"
 dependencies = [
- "bitflags 1.2.1",
- "bytes",
+ "bitflags",
+ "bytes 0.5.6",
  "futures-core",
  "futures-sink",
  "log",
  "pin-project 0.4.27",
- "tokio 0.2.22",
+ "tokio 0.2.24",
  "tokio-util",
 ]
 
@@ -90,14 +90,14 @@ dependencies = [
 
 [[package]]
 name = "actix-files"
-version = "0.4.0"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fc0a9181e93c91dc7eb401a0debaed5c8294e12019c307c72fd7a1731b672fc"
+checksum = "d031468a7859f71674e5531bd05137e0ea5de05ec9a917314330b88c582e2e0a"
 dependencies = [
  "actix-service",
  "actix-web",
- "bitflags 1.2.1",
- "bytes",
+ "bitflags",
+ "bytes 0.5.6",
  "derive_more",
  "futures-core",
  "futures-util",
@@ -110,9 +110,9 @@ dependencies = [
 
 [[package]]
 name = "actix-http"
-version = "2.0.0"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05dd80ba8f27c4a34357c07e338c8f5c38f8520e6d626ca1727d8fecc41b0cab"
+checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874"
 dependencies = [
  "actix-codec",
  "actix-connect",
@@ -121,10 +121,10 @@ dependencies = [
  "actix-threadpool",
  "actix-tls",
  "actix-utils",
- "base64 0.12.3",
- "bitflags 1.2.1",
+ "base64 0.13.0",
+ "bitflags",
  "brotli2",
- "bytes",
+ "bytes 0.5.6",
  "cookie",
  "copyless",
  "derive_more",
@@ -145,22 +145,22 @@ dependencies = [
  "log",
  "mime",
  "percent-encoding",
- "pin-project 0.4.27",
- "rand 0.7.3",
+ "pin-project 1.0.2",
+ "rand",
  "regex",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_json",
  "serde_urlencoded",
- "sha-1 0.9.1",
+ "sha-1 0.9.2",
  "slab",
- "time 0.2.22",
+ "time 0.2.23",
 ]
 
 [[package]]
 name = "actix-macros"
-version = "0.1.2"
+version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a60f9ba7c4e6df97f3aacb14bb5c0cd7d98a49dcbaed0d7f292912ad9a6a3ed2"
+checksum = "b4ca8ce00b267af8ccebbd647de0d61e0674b6e61185cc7a592ff88772bed655"
 dependencies = [
  "quote",
  "syn",
@@ -176,7 +176,7 @@ dependencies = [
  "http",
  "log",
  "regex",
- "serde 1.0.117",
+ "serde 1.0.118",
 ]
 
 [[package]]
@@ -191,7 +191,7 @@ dependencies = [
  "futures-channel",
  "futures-util",
  "smallvec",
- "tokio 0.2.22",
+ "tokio 0.2.24",
 ]
 
 [[package]]
@@ -278,8 +278,8 @@ dependencies = [
  "actix-codec",
  "actix-rt",
  "actix-service",
- "bitflags 1.2.1",
- "bytes",
+ "bitflags",
+ "bytes 0.5.6",
  "either",
  "futures-channel",
  "futures-sink",
@@ -291,9 +291,9 @@ dependencies = [
 
 [[package]]
 name = "actix-web"
-version = "3.1.0"
+version = "3.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1b12fe25e11cd9ed2ef2e428427eb6178a1b363f3f7f0dab8278572f11b2da1"
+checksum = "e641d4a172e7faa0862241a20ff4f1f5ab0ab7c279f00c2d4587b77483477b86"
 dependencies = [
  "actix-codec",
  "actix-http",
@@ -308,7 +308,7 @@ dependencies = [
  "actix-utils",
  "actix-web-codegen",
  "awc",
- "bytes",
+ "bytes 0.5.6",
  "derive_more",
  "encoding_rs",
  "futures-channel",
@@ -317,15 +317,15 @@ dependencies = [
  "fxhash",
  "log",
  "mime",
- "pin-project 0.4.27",
+ "pin-project 1.0.2",
  "regex",
  "rustls",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_json",
  "serde_urlencoded",
  "socket2",
- "time 0.2.22",
- "tinyvec 1.0.1",
+ "time 0.2.23",
+ "tinyvec",
  "url",
 ]
 
@@ -339,7 +339,7 @@ dependencies = [
  "actix-codec",
  "actix-http",
  "actix-web",
- "bytes",
+ "bytes 0.5.6",
  "futures-channel",
  "futures-core",
  "pin-project 0.4.27",
@@ -347,9 +347,9 @@ dependencies = [
 
 [[package]]
 name = "actix-web-codegen"
-version = "0.3.0"
+version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "750ca8fb60bbdc79491991650ba5d2ae7cd75f3fc00ead51390cfe9efda0d4d8"
+checksum = "ad26f77093333e0e7c6ffe54ebe3582d908a104e448723eec6d43d08b07143fb"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -369,9 +369,9 @@ dependencies = [
 
 [[package]]
 name = "addr2line"
-version = "0.13.0"
+version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072"
+checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423"
 dependencies = [
  "gimli",
 ]
@@ -390,24 +390,18 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
 
 [[package]]
 name = "aho-corasick"
-version = "0.7.14"
+version = "0.7.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d"
+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "anyhow"
-version = "1.0.33"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c"
-
-[[package]]
-name = "arc-swap"
-version = "0.4.7"
+version = "1.0.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034"
+checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4"
 
 [[package]]
 name = "arrayvec"
@@ -426,9 +420,9 @@ dependencies = [
 
 [[package]]
 name = "async-trait"
-version = "0.1.41"
+version = "0.1.42"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b246867b8b3b6ae56035f1eb1ed557c1d8eae97f0d53696138a50fa0e3a3b8c0"
+checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -454,24 +448,25 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
 
 [[package]]
 name = "awc"
-version = "2.0.0"
+version = "2.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "150e00c06683ab44c5f97d033950e5d87a7a042d06d77f5eecb443cbd23d0575"
+checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691"
 dependencies = [
  "actix-codec",
  "actix-http",
  "actix-rt",
  "actix-service",
- "base64 0.12.3",
- "bytes",
+ "base64 0.13.0",
+ "bytes 0.5.6",
+ "cfg-if 1.0.0",
  "derive_more",
  "futures-core",
  "log",
  "mime",
  "percent-encoding",
- "rand 0.7.3",
+ "rand",
  "rustls",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_json",
  "serde_urlencoded",
 ]
@@ -500,11 +495,11 @@ dependencies = [
  "chrono",
  "log",
  "num_cpus",
- "rand 0.7.3",
- "serde 1.0.117",
+ "rand",
+ "serde 1.0.118",
  "serde_json",
  "thiserror",
- "tokio 0.2.22",
+ "tokio 0.2.24",
  "uuid",
 ]
 
@@ -520,41 +515,32 @@ dependencies = [
  "async-trait",
  "chrono",
  "log",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_json",
  "thiserror",
- "tokio 0.2.22",
+ "tokio 0.2.24",
  "uuid",
 ]
 
 [[package]]
 name = "backtrace"
-version = "0.3.53"
+version = "0.3.55"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e"
+checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598"
 dependencies = [
  "addr2line",
  "cfg-if 1.0.0",
  "libc",
- "miniz_oxide",
+ "miniz_oxide 0.4.3",
  "object",
  "rustc-demangle",
 ]
 
 [[package]]
 name = "base-x"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1"
-
-[[package]]
-name = "base64"
-version = "0.5.2"
+version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557"
-dependencies = [
- "byteorder",
-]
+checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
 
 [[package]]
 name = "base64"
@@ -570,27 +556,32 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
 
 [[package]]
 name = "bcrypt"
-version = "0.8.2"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2cab630912253fb9dc92c0e2fabd0a7b51f5a5a4007177cfa31e517015b7204"
+checksum = "a4d0faafe9e089674fc3efdb311ff5253d445c79d85d1d28bd3ace76d45e7164"
 dependencies = [
- "base64 0.12.3",
+ "base64 0.13.0",
  "blowfish",
- "byteorder",
- "getrandom",
+ "getrandom 0.2.0",
 ]
 
 [[package]]
 name = "bitflags"
-version = "0.7.0"
+version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
 
 [[package]]
-name = "bitflags"
-version = "1.2.1"
+name = "bitvec"
+version = "0.19.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+checksum = "a7ba35e9565969edb811639dbebfe34edc0368e472c5018474c8eb2543397f81"
+dependencies = [
+ "funty",
+ "radium",
+ "tap",
+ "wyz",
+]
 
 [[package]]
 name = "block-buffer"
@@ -613,15 +604,6 @@ dependencies = [
  "generic-array 0.14.4",
 ]
 
-[[package]]
-name = "block-cipher"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f337a3e6da609650eb74e02bc9fac7b735049f7623ab12f2e4c719316fcc7e80"
-dependencies = [
- "generic-array 0.14.4",
-]
-
 [[package]]
 name = "block-padding"
 version = "0.1.5"
@@ -633,12 +615,12 @@ dependencies = [
 
 [[package]]
 name = "blowfish"
-version = "0.6.0"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f06850ba969bc59388b2cc0a4f186fc6d9d37208863b15b84ae3866ac90ac06"
+checksum = "32fa6a061124e37baba002e496d203e23ba3d7b73750be82dbfbc92913048a5b"
 dependencies = [
- "block-cipher",
  "byteorder",
+ "cipher",
  "opaque-debug 0.3.0",
 ]
 
@@ -664,11 +646,11 @@ dependencies = [
 
 [[package]]
 name = "buf-min"
-version = "0.1.1"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6ae7069aad07c7cdefe6a22a671f00650728bd2331a4cc62e1e5d0becdf9ca4"
+checksum = "881e704e61d0fb41d7c6c9ae2bd790eb8c13dc974ae102fb98c788b4fdea4349"
 dependencies = [
- "bytes",
+ "bytes 0.6.0",
 ]
 
 [[package]]
@@ -701,33 +683,33 @@ version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
 
+[[package]]
+name = "bytes"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16"
+
 [[package]]
 name = "bytestring"
 version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fc7c05fa5172da78a62d9949d662d2ac89d4cc7355d7b49adee5163f1fb3f363"
 dependencies = [
- "bytes",
+ "bytes 0.5.6",
 ]
 
-[[package]]
-name = "c_vec"
-version = "1.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8a318911dce53b5f1ca6539c44f5342c632269f0fa7ea3e35f32458c27a7c30"
-
 [[package]]
 name = "captcha"
-version = "0.0.7"
+version = "0.0.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d060a3be43adb2fe89d3448e9a193149806139b1ce99281865fcab7aeaf04ed"
+checksum = "29256038744434f6d0e1328d7c9050f14aa5fc8562ff065b9e9481ac293ba5bc"
 dependencies = [
- "base64 0.5.2",
+ "base64 0.13.0",
+ "hound",
  "image",
  "lodepng",
- "rand 0.3.23",
+ "rand",
  "serde_json",
- "time 0.1.44",
 ]
 
 [[package]]
@@ -738,9 +720,9 @@ checksum = "7b02b629252fe8ef6460461409564e2c21d0c8e77e0944f3d189ff06c4e932ad"
 
 [[package]]
 name = "cc"
-version = "1.0.61"
+version = "1.0.66"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
+checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
 
 [[package]]
 name = "cfg-if"
@@ -762,19 +744,19 @@ checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
 dependencies = [
  "libc",
  "num-integer",
- "num-traits 0.2.12",
- "serde 1.0.117",
+ "num-traits 0.2.14",
+ "serde 1.0.118",
  "time 0.1.44",
  "winapi 0.3.9",
 ]
 
 [[package]]
-name = "cloudabi"
-version = "0.1.0"
+name = "cipher"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
+checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
 dependencies = [
- "bitflags 1.2.1",
+ "generic-array 0.14.4",
 ]
 
 [[package]]
@@ -785,9 +767,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
 
 [[package]]
 name = "comrak"
-version = "0.8.2"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d325e4f2ffff52ca77d995bb675494d5364aa332499d5f7c7fbb28c25e671f6"
+checksum = "bcfb8008d04126b176c76cbfdecf9a2ccb4bacc70af87c8da6136d63d7b7292a"
 dependencies = [
  "entities",
  "lazy_static",
@@ -809,24 +791,24 @@ checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3"
 dependencies = [
  "lazy_static",
  "nom 5.1.2",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde-hjson",
 ]
 
 [[package]]
 name = "const_fn"
-version = "0.4.2"
+version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce90df4c658c62f12d78f7508cf92f9173e5184a539c10bfe54a3107b3ffd0f2"
+checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
 
 [[package]]
 name = "cookie"
-version = "0.14.2"
+version = "0.14.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1373a16a4937bc34efec7b391f9c1500c30b8478a701a4f44c9165cc0475a6e0"
+checksum = "784ad0fbab4f3e9cef09f20e0aea6000ae08d2cb98ac4c0abc53df18803d702f"
 dependencies = [
  "percent-encoding",
- "time 0.2.22",
+ "time 0.2.23",
  "version_check 0.9.2",
 ]
 
@@ -838,9 +820,9 @@ checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536"
 
 [[package]]
 name = "core-foundation"
-version = "0.7.0"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
+checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -848,9 +830,9 @@ dependencies = [
 
 [[package]]
 name = "core-foundation-sys"
-version = "0.7.0"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
+checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
 
 [[package]]
 name = "cpuid-bool"
@@ -884,7 +866,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
 dependencies = [
  "cfg-if 1.0.0",
- "crossbeam-utils 0.8.0",
+ "crossbeam-utils 0.8.1",
 ]
 
 [[package]]
@@ -895,18 +877,18 @@ checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
 dependencies = [
  "cfg-if 1.0.0",
  "crossbeam-epoch",
- "crossbeam-utils 0.8.0",
+ "crossbeam-utils 0.8.1",
 ]
 
 [[package]]
 name = "crossbeam-epoch"
-version = "0.9.0"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec0f606a85340376eef0d6d8fec399e6d4a544d648386c6645eb6d0653b27d9f"
+checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
 dependencies = [
  "cfg-if 1.0.0",
  "const_fn",
- "crossbeam-utils 0.8.0",
+ "crossbeam-utils 0.8.1",
  "lazy_static",
  "memoffset",
  "scopeguard",
@@ -925,13 +907,12 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.0"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5"
+checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
 dependencies = [
  "autocfg",
  "cfg-if 1.0.0",
- "const_fn",
  "lazy_static",
 ]
 
@@ -972,9 +953,9 @@ dependencies = [
 
 [[package]]
 name = "deflate"
-version = "0.7.20"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4"
+checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
 dependencies = [
  "adler32",
  "byteorder",
@@ -1022,7 +1003,7 @@ version = "1.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3e2de9deab977a153492a1468d1b1c0662c1cf39e5ea87d0c060ecd59ef18d8c"
 dependencies = [
- "bitflags 1.2.1",
+ "bitflags",
  "byteorder",
  "chrono",
  "diesel_derives",
@@ -1076,12 +1057,6 @@ version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
 
-[[package]]
-name = "dtoa"
-version = "0.4.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
-
 [[package]]
 name = "either"
 version = "1.6.1"
@@ -1090,11 +1065,11 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
 
 [[package]]
 name = "encoding_rs"
-version = "0.8.24"
+version = "0.8.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a51b8cf747471cb9499b6d59e59b0444f4c90eba8968c4e44874e92b5b64ace2"
+checksum = "801bbab217d7f79c0062f4f7205b5d4427c6d1a7bd7aafdd1475f7c59d62b283"
 dependencies = [
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
 ]
 
 [[package]]
@@ -1115,20 +1090,11 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "enum_primitive"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180"
-dependencies = [
- "num-traits 0.1.43",
-]
-
 [[package]]
 name = "env_logger"
-version = "0.8.1"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54532e3223c5af90a6a757c90b5c5521564b07e5e7a958681bcd2afad421cdcd"
+checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e"
 dependencies = [
  "atty",
  "humantime",
@@ -1151,14 +1117,14 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
 
 [[package]]
 name = "flate2"
-version = "1.0.18"
+version = "1.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da80be589a72651dcda34d8b35bcdc9b7254ad06325611074d9cc0fbb19f60ee"
+checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
 dependencies = [
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
  "crc32fast",
  "libc",
- "miniz_oxide",
+ "miniz_oxide 0.4.3",
 ]
 
 [[package]]
@@ -1183,10 +1149,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
 
 [[package]]
-name = "fuchsia-cprng"
-version = "0.1.1"
+name = "form_urlencoded"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
 
 [[package]]
 name = "fuchsia-zircon"
@@ -1194,7 +1164,7 @@ version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
 dependencies = [
- "bitflags 1.2.1",
+ "bitflags",
  "fuchsia-zircon-sys",
 ]
 
@@ -1204,11 +1174,17 @@ version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
 
+[[package]]
+name = "funty"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8"
+
 [[package]]
 name = "futures"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95314d38584ffbfda215621d723e0a3906f032e03ae5551e650058dac83d4797"
+checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1221,9 +1197,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0448174b01148032eed37ac4aed28963aaaa8cfa93569a08e5b479bbc6c2c151"
+checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -1231,15 +1207,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18eaa56102984bed2c88ea39026cff3ce3b4c7f508ca970cedf2450ea10d4e46"
+checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f5f8e0c9258abaea85e78ebdda17ef9666d390e987f006be6080dfe354b708cb"
+checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -1248,15 +1224,15 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e1798854a4727ff944a7b12aa999f58ce7aa81db80d2dfaaf2ba06f065ddd2b"
+checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb"
 
 [[package]]
 name = "futures-macro"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e36fccf3fc58563b4a14d265027c627c3b665d7fed489427e88e7cc929559efe"
+checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556"
 dependencies = [
  "proc-macro-hack",
  "proc-macro2",
@@ -1266,24 +1242,24 @@ dependencies = [
 
 [[package]]
 name = "futures-sink"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e3ca3f17d6e8804ae5d3df7a7d35b2b3a6fe89dac84b31872720fc3060a0b11"
+checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d"
 
 [[package]]
 name = "futures-task"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96d502af37186c4fef99453df03e374683f8a1eec9dcc1e66b3b82dc8278ce3c"
+checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d"
 dependencies = [
  "once_cell",
 ]
 
 [[package]]
 name = "futures-util"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abcb44342f62e6f3e8ac427b8aa815f724fd705dfad060b18ac7866c15bb8e34"
+checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1292,7 +1268,7 @@ dependencies = [
  "futures-sink",
  "futures-task",
  "memchr",
- "pin-project 1.0.1",
+ "pin-project 1.0.2",
  "pin-utils",
  "proc-macro-hack",
  "proc-macro-nested",
@@ -1338,21 +1314,32 @@ dependencies = [
  "wasi 0.9.0+wasi-snapshot-preview1",
 ]
 
+[[package]]
+name = "getrandom"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
 [[package]]
 name = "gif"
-version = "0.9.2"
+version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2e41945ba23db3bf51b24756d73d81acb4f28d85c3dccc32c6fae904438c25f"
+checksum = "02efba560f227847cb41463a7395c514d127d4f74fff12ef0137fff1b84b96c4"
 dependencies = [
  "color_quant",
- "lzw",
+ "weezl",
 ]
 
 [[package]]
 name = "gimli"
-version = "0.22.0"
+version = "0.23.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
+checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
 
 [[package]]
 name = "h2"
@@ -1360,7 +1347,7 @@ version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535"
 dependencies = [
- "bytes",
+ "bytes 0.5.6",
  "fnv",
  "futures-core",
  "futures-sink",
@@ -1368,7 +1355,7 @@ dependencies = [
  "http",
  "indexmap",
  "slab",
- "tokio 0.2.22",
+ "tokio 0.2.24",
  "tokio-util",
  "tracing",
  "tracing-futures",
@@ -1409,13 +1396,19 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
+[[package]]
+name = "hound"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549"
+
 [[package]]
 name = "http"
-version = "0.2.1"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
+checksum = "84129d298a6d57d246960ff8eb831ca4af3f96d29e2e28848dae275408658e26"
 dependencies = [
- "bytes",
+ "bytes 0.5.6",
  "fnv",
  "itoa",
 ]
@@ -1426,7 +1419,7 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
 dependencies = [
- "bytes",
+ "bytes 0.5.6",
  "http",
 ]
 
@@ -1449,7 +1442,7 @@ dependencies = [
  "actix-web",
  "awc",
  "base64 0.12.3",
- "bytes",
+ "bytes 0.5.6",
  "chrono",
  "futures",
  "http-signature-normalization",
@@ -1465,7 +1458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7bc26a68f8963e26453c7fdea9e016e2e31a48ca018a9223f96afe2cca1a4bd1"
 dependencies = [
  "base64 0.12.3",
- "bytes",
+ "bytes 0.5.6",
  "chrono",
  "futures",
  "http",
@@ -1473,7 +1466,7 @@ dependencies = [
  "reqwest",
  "sha2",
  "thiserror",
- "tokio 0.2.22",
+ "tokio 0.2.24",
 ]
 
 [[package]]
@@ -1496,11 +1489,11 @@ checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a"
 
 [[package]]
 name = "hyper"
-version = "0.13.8"
+version = "0.13.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f3afcfae8af5ad0576a31e768415edb627824129e8e5a29b8bfccb2f234e835"
+checksum = "f6ad767baac13b44d4529fcf58ba2cd0995e36e7b435bc5b039de6f47e880dbf"
 dependencies = [
- "bytes",
+ "bytes 0.5.6",
  "futures-channel",
  "futures-core",
  "futures-util",
@@ -1510,9 +1503,9 @@ dependencies = [
  "httparse",
  "httpdate",
  "itoa",
- "pin-project 0.4.27",
+ "pin-project 1.0.2",
  "socket2",
- "tokio 0.2.22",
+ "tokio 0.2.24",
  "tower-service",
  "tracing",
  "want",
@@ -1524,10 +1517,10 @@ version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d979acc56dcb5b8dddba3917601745e877576475aa046df3226eabdecef78eed"
 dependencies = [
- "bytes",
+ "bytes 0.5.6",
  "hyper",
  "native-tls",
- "tokio 0.2.22",
+ "tokio 0.2.24",
  "tokio-tls",
 ]
 
@@ -1538,7 +1531,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2adce67e2c21cd95288ae3d9f2bbb2762cf17c03744628d49679f315ed1e2e58"
 dependencies = [
  "base64 0.13.0",
- "bytes",
+ "bytes 0.5.6",
  "http",
  "httparse",
  "httpdate",
@@ -1568,42 +1561,38 @@ dependencies = [
 
 [[package]]
 name = "image"
-version = "0.13.0"
+version = "0.23.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c3f4f5ea213ed9899eca760a8a14091d4b82d33e27cf8ced336ff730e9f6da8"
+checksum = "7ce04077ead78e39ae8610ad26216aed811996b043d47beed5090db674f9e9b5"
 dependencies = [
+ "bytemuck",
  "byteorder",
- "enum_primitive",
+ "color_quant",
  "gif",
  "jpeg-decoder",
  "num-iter",
  "num-rational",
- "num-traits 0.1.43",
+ "num-traits 0.2.14",
  "png",
  "scoped_threadpool",
+ "tiff",
 ]
 
 [[package]]
 name = "indexmap"
-version = "1.6.0"
+version = "1.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
+checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b"
 dependencies = [
  "autocfg",
  "hashbrown",
 ]
 
-[[package]]
-name = "inflate"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1238524675af3938a7c74980899535854b88ba07907bb1c944abe5b8fc437e5"
-
 [[package]]
 name = "instant"
-version = "0.1.8"
+version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb1fc4429a33e1f80d41dc9fea4d108a88bec1de8053878898ae448a0b52f613"
+checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
 dependencies = [
  "cfg-if 1.0.0",
 ]
@@ -1662,9 +1651,9 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.45"
+version = "0.3.46"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8"
+checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -1678,7 +1667,7 @@ dependencies = [
  "base64 0.12.3",
  "pem",
  "ring",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_json",
  "simple_asn1",
 ]
@@ -1735,15 +1724,15 @@ dependencies = [
  "lemmy_websocket",
  "log",
  "openssl",
- "rand 0.7.3",
+ "rand",
  "reqwest",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_json",
  "sha2",
  "strum",
  "strum_macros",
  "thiserror",
- "tokio 0.3.1",
+ "tokio 0.3.6",
  "url",
  "uuid",
 ]
@@ -1779,15 +1768,15 @@ dependencies = [
  "log",
  "openssl",
  "percent-encoding",
- "rand 0.7.3",
+ "rand",
  "reqwest",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_json",
  "sha2",
  "strum",
  "strum_macros",
  "thiserror",
- "tokio 0.3.1",
+ "tokio 0.3.6",
  "url",
  "uuid",
 ]
@@ -1804,7 +1793,7 @@ dependencies = [
  "lemmy_utils",
  "log",
  "regex",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_json",
  "sha2",
  "strum",
@@ -1822,7 +1811,7 @@ dependencies = [
  "log",
  "strum",
  "strum_macros",
- "tokio 0.3.1",
+ "tokio 0.3.6",
 ]
 
 [[package]]
@@ -1855,11 +1844,11 @@ dependencies = [
  "openssl",
  "reqwest",
  "rss",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_json",
  "sha2",
  "strum",
- "tokio 0.3.1",
+ "tokio 0.3.6",
  "url",
 ]
 
@@ -1873,7 +1862,7 @@ dependencies = [
  "lemmy_db",
  "lemmy_utils",
  "log",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_json",
 ]
 
@@ -1893,10 +1882,10 @@ dependencies = [
  "log",
  "openssl",
  "percent-encoding",
- "rand 0.7.3",
+ "rand",
  "regex",
  "reqwest",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_json",
  "thiserror",
  "url",
@@ -1916,20 +1905,20 @@ dependencies = [
  "lemmy_structs",
  "lemmy_utils",
  "log",
- "rand 0.7.3",
+ "rand",
  "reqwest",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_json",
  "strum",
  "strum_macros",
- "tokio 0.3.1",
+ "tokio 0.3.6",
 ]
 
 [[package]]
 name = "lettre"
-version = "0.10.0-alpha.3"
+version = "0.10.0-alpha.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e422b6c03563bc47db09bb61a8ece4f1462de131455beb96c091e2998fa316a2"
+checksum = "dc8c2fc7873920aca23647e5e86d44ff3f40bbc5a5efaab445c9eb0e001c9f71"
 dependencies = [
  "base64 0.13.0",
  "hostname",
@@ -1937,13 +1926,13 @@ dependencies = [
  "idna",
  "mime",
  "native-tls",
- "nom 5.1.2",
+ "nom 6.0.1",
  "once_cell",
  "quoted_printable",
  "r2d2",
- "rand 0.7.3",
+ "rand",
  "regex",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_json",
  "uuid",
 ]
@@ -1955,7 +1944,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616"
 dependencies = [
  "arrayvec",
- "bitflags 1.2.1",
+ "bitflags",
  "cfg-if 0.1.10",
  "ryu",
  "static_assertions",
@@ -1963,9 +1952,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.80"
+version = "0.2.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
+checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
 
 [[package]]
 name = "linked-hash-map"
@@ -1985,21 +1974,20 @@ checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
 
 [[package]]
 name = "lock_api"
-version = "0.4.1"
+version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c"
+checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
 dependencies = [
  "scopeguard",
 ]
 
 [[package]]
 name = "lodepng"
-version = "1.2.2"
+version = "3.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ac1dfdf85b7d5dea61a620e12c051a72078189366a0b3c0ab331e30847def2f"
+checksum = "b6eb909184223b89c76d66b80199b7ad4163aebb2519244e6ebac8ba74e67eab"
 dependencies = [
- "c_vec",
- "cc",
+ "flate2",
  "libc",
  "rgb",
 ]
@@ -2022,12 +2010,6 @@ dependencies = [
  "linked-hash-map 0.5.3",
 ]
 
-[[package]]
-name = "lzw"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
-
 [[package]]
 name = "maplit"
 version = "1.0.2"
@@ -2054,15 +2036,15 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
 
 [[package]]
 name = "memchr"
-version = "2.3.3"
+version = "2.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
 
 [[package]]
 name = "memoffset"
-version = "0.5.6"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
+checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
 dependencies = [
  "autocfg",
 ]
@@ -2104,6 +2086,15 @@ dependencies = [
  "unicase",
 ]
 
+[[package]]
+name = "miniz_oxide"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
+dependencies = [
+ "adler32",
+]
+
 [[package]]
 name = "miniz_oxide"
 version = "0.4.3"
@@ -2116,9 +2107,9 @@ dependencies = [
 
 [[package]]
 name = "mio"
-version = "0.6.22"
+version = "0.6.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
+checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
 dependencies = [
  "cfg-if 0.1.10",
  "fuchsia-zircon",
@@ -2146,9 +2137,9 @@ dependencies = [
 
 [[package]]
 name = "miow"
-version = "0.2.1"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
+checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
 dependencies = [
  "kernel32-sys",
  "net2",
@@ -2158,9 +2149,9 @@ dependencies = [
 
 [[package]]
 name = "native-tls"
-version = "0.2.4"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d"
+checksum = "6fcc7939b5edc4e4f86b1b4a04bb1498afaaf871b1a6691838ed06fcb48d3a3f"
 dependencies = [
  "lazy_static",
  "libc",
@@ -2176,9 +2167,9 @@ dependencies = [
 
 [[package]]
 name = "net2"
-version = "0.2.35"
+version = "0.2.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853"
+checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
 dependencies = [
  "cfg-if 0.1.10",
  "libc",
@@ -2206,6 +2197,17 @@ dependencies = [
  "version_check 0.9.2",
 ]
 
+[[package]]
+name = "nom"
+version = "6.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88034cfd6b4a0d54dd14f4a507eceee36c0b70e5a02236c4e4df571102be17f0"
+dependencies = [
+ "bitvec",
+ "memchr",
+ "version_check 0.9.2",
+]
+
 [[package]]
 name = "num-bigint"
 version = "0.2.6"
@@ -2214,38 +2216,39 @@ checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
 dependencies = [
  "autocfg",
  "num-integer",
- "num-traits 0.2.12",
+ "num-traits 0.2.14",
 ]
 
 [[package]]
 name = "num-integer"
-version = "0.1.43"
+version = "0.1.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
 dependencies = [
  "autocfg",
- "num-traits 0.2.12",
+ "num-traits 0.2.14",
 ]
 
 [[package]]
 name = "num-iter"
-version = "0.1.41"
+version = "0.1.42"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f"
+checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
 dependencies = [
  "autocfg",
  "num-integer",
- "num-traits 0.2.12",
+ "num-traits 0.2.14",
 ]
 
 [[package]]
 name = "num-rational"
-version = "0.1.42"
+version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e"
+checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
 dependencies = [
+ "autocfg",
  "num-integer",
- "num-traits 0.2.12",
+ "num-traits 0.2.14",
 ]
 
 [[package]]
@@ -2254,14 +2257,14 @@ version = "0.1.43"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
 dependencies = [
- "num-traits 0.2.12",
+ "num-traits 0.2.14",
 ]
 
 [[package]]
 name = "num-traits"
-version = "0.2.12"
+version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
 dependencies = [
  "autocfg",
 ]
@@ -2278,15 +2281,15 @@ dependencies = [
 
 [[package]]
 name = "object"
-version = "0.21.1"
+version = "0.22.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693"
+checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
 
 [[package]]
 name = "once_cell"
-version = "1.4.1"
+version = "1.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
+checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
 
 [[package]]
 name = "opaque-debug"
@@ -2302,12 +2305,12 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
 
 [[package]]
 name = "openssl"
-version = "0.10.30"
+version = "0.10.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4"
+checksum = "8d008f51b1acffa0d3450a68606e6a51c123012edaacb0f4e1426bd978869187"
 dependencies = [
- "bitflags 1.2.1",
- "cfg-if 0.1.10",
+ "bitflags",
+ "cfg-if 1.0.0",
  "foreign-types",
  "lazy_static",
  "libc",
@@ -2322,9 +2325,9 @@ checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.58"
+version = "0.9.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de"
+checksum = "de52d8eabd217311538a39bba130d7dea1f1e118010fee7a033d966845e7d5fe"
 dependencies = [
  "autocfg",
  "cc",
@@ -2335,9 +2338,9 @@ dependencies = [
 
 [[package]]
 name = "parking_lot"
-version = "0.11.0"
+version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733"
+checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
 dependencies = [
  "instant",
  "lock_api",
@@ -2346,12 +2349,11 @@ dependencies = [
 
 [[package]]
 name = "parking_lot_core"
-version = "0.8.0"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
+checksum = "d7c6d9b8427445284a09c55be860a15855ab580a417ccad9da88f5a06787ced0"
 dependencies = [
- "cfg-if 0.1.10",
- "cloudabi",
+ "cfg-if 1.0.0",
  "instant",
  "libc",
  "redox_syscall",
@@ -2361,11 +2363,11 @@ dependencies = [
 
 [[package]]
 name = "pem"
-version = "0.8.1"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59698ea79df9bf77104aefd39cc3ec990cb9693fb59c3b0a70ddf2646fdffb4b"
+checksum = "f4c220d01f863d13d96ca82359d1e81e64a7c6bf0637bcde7b2349630addf0c6"
 dependencies = [
- "base64 0.12.3",
+ "base64 0.13.0",
  "once_cell",
  "regex",
 ]
@@ -2430,11 +2432,11 @@ dependencies = [
 
 [[package]]
 name = "pin-project"
-version = "1.0.1"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee41d838744f60d959d7074e3afb6b35c7456d0f61cad38a24e35e6553f73841"
+checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7"
 dependencies = [
- "pin-project-internal 1.0.1",
+ "pin-project-internal 1.0.2",
 ]
 
 [[package]]
@@ -2450,9 +2452,9 @@ dependencies = [
 
 [[package]]
 name = "pin-project-internal"
-version = "1.0.1"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81a4ffa594b66bff340084d4081df649a7dc049ac8d7fc458d8e628bfbbb2f86"
+checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2465,6 +2467,12 @@ version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"
 
+[[package]]
+name = "pin-project-lite"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c"
+
 [[package]]
 name = "pin-utils"
 version = "0.1.0"
@@ -2479,21 +2487,21 @@ checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
 
 [[package]]
 name = "png"
-version = "0.7.0"
+version = "0.16.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48f397b84083c2753ba53c7b56ad023edb94512b2885ffe227c66ff7edb61868"
+checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
 dependencies = [
- "bitflags 0.7.0",
+ "bitflags",
+ "crc32fast",
  "deflate",
- "inflate",
- "num-iter",
+ "miniz_oxide 0.3.7",
 ]
 
 [[package]]
 name = "ppv-lite86"
-version = "0.2.9"
+version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
 
 [[package]]
 name = "pq-sys"
@@ -2506,9 +2514,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro-hack"
-version = "0.5.18"
+version = "0.5.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
 
 [[package]]
 name = "proc-macro-nested"
@@ -2568,27 +2576,10 @@ dependencies = [
 ]
 
 [[package]]
-name = "rand"
-version = "0.3.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c"
-dependencies = [
- "libc",
- "rand 0.4.6",
-]
-
-[[package]]
-name = "rand"
-version = "0.4.6"
+name = "radium"
+version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
-dependencies = [
- "fuchsia-cprng",
- "libc",
- "rand_core 0.3.1",
- "rdrand",
- "winapi 0.3.9",
-]
+checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
 
 [[package]]
 name = "rand"
@@ -2596,10 +2587,10 @@ version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
 dependencies = [
- "getrandom",
+ "getrandom 0.1.15",
  "libc",
  "rand_chacha",
- "rand_core 0.5.1",
+ "rand_core",
  "rand_hc",
 ]
 
@@ -2610,31 +2601,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
 dependencies = [
  "ppv-lite86",
- "rand_core 0.5.1",
+ "rand_core",
 ]
 
-[[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",
+ "getrandom 0.1.15",
 ]
 
 [[package]]
@@ -2643,7 +2619,7 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
 dependencies = [
- "rand_core 0.5.1",
+ "rand_core",
 ]
 
 [[package]]
@@ -2666,20 +2642,11 @@ checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
 dependencies = [
  "crossbeam-channel 0.5.0",
  "crossbeam-deque",
- "crossbeam-utils 0.8.0",
+ "crossbeam-utils 0.8.1",
  "lazy_static",
  "num_cpus",
 ]
 
-[[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 = "redox_syscall"
 version = "0.1.57"
@@ -2688,9 +2655,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
 
 [[package]]
 name = "regex"
-version = "1.4.1"
+version = "1.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b"
+checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -2700,9 +2667,9 @@ dependencies = [
 
 [[package]]
 name = "regex-syntax"
-version = "0.6.20"
+version = "0.6.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c"
+checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
 
 [[package]]
 name = "remove_dir_all"
@@ -2715,12 +2682,12 @@ dependencies = [
 
 [[package]]
 name = "reqwest"
-version = "0.10.8"
+version = "0.10.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9eaa17ac5d7b838b7503d118fa16ad88f440498bf9ffe5424e621f93190d61e"
+checksum = "0718f81a8e14c4dbb3b34cf23dc6aaf9ab8a0dfec160c534b3dbca1aaa21f47c"
 dependencies = [
- "base64 0.12.3",
- "bytes",
+ "base64 0.13.0",
+ "bytes 0.5.6",
  "encoding_rs",
  "futures-core",
  "futures-util",
@@ -2736,11 +2703,11 @@ dependencies = [
  "mime_guess",
  "native-tls",
  "percent-encoding",
- "pin-project-lite",
- "serde 1.0.117",
+ "pin-project-lite 0.2.0",
+ "serde 1.0.118",
  "serde_json",
  "serde_urlencoded",
- "tokio 0.2.22",
+ "tokio 0.2.24",
  "tokio-tls",
  "url",
  "wasm-bindgen",
@@ -2751,9 +2718,9 @@ dependencies = [
 
 [[package]]
 name = "resolv-conf"
-version = "0.6.3"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11834e137f3b14e309437a8276714eed3a80d1ef894869e510f2c0c0b98b9f4a"
+checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
 dependencies = [
  "hostname",
  "quick-error",
@@ -2770,9 +2737,9 @@ dependencies = [
 
 [[package]]
 name = "ring"
-version = "0.16.15"
+version = "0.16.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "952cd6b98c85bbc30efa1ba5783b8abf12fec8b3287ffa52605b9432313e34e4"
+checksum = "024a1e66fea74c66c66624ee5622a7ff0e4b73a13b4f5c326ddb50c708944226"
 dependencies = [
  "cc",
  "libc",
@@ -2870,11 +2837,11 @@ dependencies = [
 
 [[package]]
 name = "security-framework"
-version = "0.4.4"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535"
+checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69"
 dependencies = [
- "bitflags 1.2.1",
+ "bitflags",
  "core-foundation",
  "core-foundation-sys",
  "libc",
@@ -2883,9 +2850,9 @@ dependencies = [
 
 [[package]]
 name = "security-framework-sys"
-version = "0.4.3"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405"
+checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -2914,9 +2881,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8"
 
 [[package]]
 name = "serde"
-version = "1.0.117"
+version = "1.0.118"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
+checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
 dependencies = [
  "serde_derive",
 ]
@@ -2936,9 +2903,9 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.117"
+version = "1.0.118"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
+checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2947,14 +2914,14 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.59"
+version = "1.0.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
+checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
 dependencies = [
  "indexmap",
  "itoa",
  "ryu",
- "serde 1.0.117",
+ "serde 1.0.118",
 ]
 
 [[package]]
@@ -2968,14 +2935,14 @@ dependencies = [
 
 [[package]]
 name = "serde_urlencoded"
-version = "0.6.1"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97"
+checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
 dependencies = [
- "dtoa",
+ "form_urlencoded",
  "itoa",
- "serde 1.0.117",
- "url",
+ "ryu",
+ "serde 1.0.118",
 ]
 
 [[package]]
@@ -2992,12 +2959,12 @@ dependencies = [
 
 [[package]]
 name = "sha-1"
-version = "0.9.1"
+version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "170a36ea86c864a3f16dd2687712dd6646f7019f301e57537c7f4dc9f5916770"
+checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c"
 dependencies = [
  "block-buffer 0.9.0",
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
  "cpuid-bool",
  "digest 0.9.0",
  "opaque-debug 0.3.0",
@@ -3011,12 +2978,12 @@ checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
 
 [[package]]
 name = "sha2"
-version = "0.9.1"
+version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1"
+checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8"
 dependencies = [
  "block-buffer 0.9.0",
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
  "cpuid-bool",
  "digest 0.9.0",
  "opaque-debug 0.3.0",
@@ -3030,11 +2997,10 @@ checksum = "b6fa3938c99da4914afedd13bf3d79bcb6c277d1b2c398d23257a304d9e1b074"
 
 [[package]]
 name = "signal-hook-registry"
-version = "1.2.1"
+version = "1.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035"
+checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab"
 dependencies = [
- "arc-swap",
  "libc",
 ]
 
@@ -3046,7 +3012,7 @@ checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b"
 dependencies = [
  "chrono",
  "num-bigint",
- "num-traits 0.2.12",
+ "num-traits 0.2.14",
 ]
 
 [[package]]
@@ -3057,19 +3023,18 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
 
 [[package]]
 name = "smallvec"
-version = "1.4.2"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
+checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
 
 [[package]]
 name = "socket2"
-version = "0.3.15"
+version = "0.3.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44"
+checksum = "97e0e9fd577458a4f61fb91fcb559ea2afecc54c934119421f9f5d3d5b1a1057"
 dependencies = [
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
  "libc",
- "redox_syscall",
  "winapi 0.3.9",
 ]
 
@@ -3081,9 +3046,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
 
 [[package]]
 name = "standback"
-version = "0.2.11"
+version = "0.2.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4e0831040d2cf2bdfd51b844be71885783d489898a192f254ae25d57cce725c"
+checksum = "cf906c8b8fc3f6ecd1046e01da1d8ddec83e48c8b08b84dcc02b585a6bedf5a8"
 dependencies = [
  "version_check 0.9.2",
 ]
@@ -3116,7 +3081,7 @@ checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
 dependencies = [
  "proc-macro2",
  "quote",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_derive",
  "syn",
 ]
@@ -3130,7 +3095,7 @@ dependencies = [
  "base-x",
  "proc-macro2",
  "quote",
- "serde 1.0.117",
+ "serde 1.0.118",
  "serde_derive",
  "serde_json",
  "sha1",
@@ -3151,15 +3116,15 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
 
 [[package]]
 name = "strum"
-version = "0.19.5"
+version = "0.20.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b89a286a7e3b5720b9a477b23253bc50debac207c8d21505f8e70b36792f11b5"
+checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c"
 
 [[package]]
 name = "strum_macros"
-version = "0.19.4"
+version = "0.20.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e61bb0be289045cb80bfce000512e32d09f8337e54c186725da381377ad1f8d5"
+checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149"
 dependencies = [
  "heck",
  "proc-macro2",
@@ -3169,15 +3134,21 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.48"
+version = "1.0.54"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
+checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
 dependencies = [
  "proc-macro2",
  "quote",
  "unicode-xid",
 ]
 
+[[package]]
+name = "tap"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36474e732d1affd3a6ed582781b3683df3d0563714c59c39591e8ff707cf078e"
+
 [[package]]
 name = "tempfile"
 version = "3.1.0"
@@ -3186,7 +3157,7 @@ checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
 dependencies = [
  "cfg-if 0.1.10",
  "libc",
- "rand 0.7.3",
+ "rand",
  "redox_syscall",
  "remove_dir_all",
  "winapi 0.3.9",
@@ -3194,27 +3165,27 @@ dependencies = [
 
 [[package]]
 name = "termcolor"
-version = "1.1.0"
+version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
 dependencies = [
  "winapi-util",
 ]
 
 [[package]]
 name = "thiserror"
-version = "1.0.21"
+version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "318234ffa22e0920fe9a40d7b8369b5f649d490980cf7aadcf1eb91594869b42"
+checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.21"
+version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cae2447b6282786c3493999f40a9be2a6ad20cb8bd268b0a0dbf5a065535c0ab"
+checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3239,6 +3210,17 @@ dependencies = [
  "num_cpus",
 ]
 
+[[package]]
+name = "tiff"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
+dependencies = [
+ "jpeg-decoder",
+ "miniz_oxide 0.4.3",
+ "weezl",
+]
+
 [[package]]
 name = "time"
 version = "0.1.44"
@@ -3252,9 +3234,9 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.2.22"
+version = "0.2.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55b7151c9065e80917fbf285d9a5d1432f60db41d170ccafc749a136b41a93af"
+checksum = "bcdaeea317915d59b2b4cd3b5efcd156c309108664277793f5351700c02ce98b"
 dependencies = [
  "const_fn",
  "libc",
@@ -3290,15 +3272,9 @@ dependencies = [
 
 [[package]]
 name = "tinyvec"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117"
-
-[[package]]
-name = "tinyvec"
-version = "1.0.1"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b78a366903f506d2ad52ca8dc552102ffdd3e937ba8a227f024dc1d1eae28575"
+checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f"
 dependencies = [
  "tinyvec_macros",
 ]
@@ -3311,11 +3287,11 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 
 [[package]]
 name = "tokio"
-version = "0.2.22"
+version = "0.2.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd"
+checksum = "099837d3464c16a808060bb3f02263b412f6fafcb5d01c533d309985fbeebe48"
 dependencies = [
- "bytes",
+ "bytes 0.5.6",
  "fnv",
  "futures-core",
  "iovec",
@@ -3324,7 +3300,7 @@ dependencies = [
  "memchr",
  "mio",
  "mio-uds",
- "pin-project-lite",
+ "pin-project-lite 0.1.11",
  "signal-hook-registry",
  "slab",
  "winapi 0.3.9",
@@ -3332,12 +3308,12 @@ dependencies = [
 
 [[package]]
 name = "tokio"
-version = "0.3.1"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1911a203c5c240fd237e23a42e48846475f3d3b1e1dad3f17e6cc17a775b707c"
+checksum = "720ba21c25078711bf456d607987d95bce90f7c3bea5abe1db587862e7a1e87c"
 dependencies = [
- "fnv",
- "pin-project-lite",
+ "autocfg",
+ "pin-project-lite 0.2.0",
 ]
 
 [[package]]
@@ -3348,7 +3324,7 @@ checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a"
 dependencies = [
  "futures-core",
  "rustls",
- "tokio 0.2.22",
+ "tokio 0.2.24",
  "webpki",
 ]
 
@@ -3359,7 +3335,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343"
 dependencies = [
  "native-tls",
- "tokio 0.2.22",
+ "tokio 0.2.24",
 ]
 
 [[package]]
@@ -3368,13 +3344,13 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
 dependencies = [
- "bytes",
+ "bytes 0.5.6",
  "futures-core",
  "futures-io",
  "futures-sink",
  "log",
- "pin-project-lite",
- "tokio 0.2.22",
+ "pin-project-lite 0.1.11",
+ "tokio 0.2.24",
 ]
 
 [[package]]
@@ -3385,13 +3361,13 @@ checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860"
 
 [[package]]
 name = "tracing"
-version = "0.1.21"
+version = "0.1.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27"
+checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3"
 dependencies = [
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
  "log",
- "pin-project-lite",
+ "pin-project-lite 0.2.0",
  "tracing-core",
 ]
 
@@ -3416,9 +3392,9 @@ dependencies = [
 
 [[package]]
 name = "trust-dns-proto"
-version = "0.19.5"
+version = "0.19.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdd7061ba6f4d4d9721afedffbfd403f20f39a4301fee1b70d6fcd09cca69f28"
+checksum = "53861fcb288a166aae4c508ae558ed18b53838db728d4d310aad08270a7d4c2b"
 dependencies = [
  "async-trait",
  "backtrace",
@@ -3427,18 +3403,18 @@ dependencies = [
  "idna",
  "lazy_static",
  "log",
- "rand 0.7.3",
+ "rand",
  "smallvec",
  "thiserror",
- "tokio 0.2.22",
+ "tokio 0.2.24",
  "url",
 ]
 
 [[package]]
 name = "trust-dns-resolver"
-version = "0.19.5"
+version = "0.19.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f23cdfdc3d8300b3c50c9e84302d3bd6d860fb9529af84ace6cf9665f181b77"
+checksum = "6759e8efc40465547b0dfce9500d733c65f969a4cbbfbe3ccf68daaa46ef179e"
 dependencies = [
  "backtrace",
  "cfg-if 0.1.10",
@@ -3450,7 +3426,7 @@ dependencies = [
  "resolv-conf",
  "smallvec",
  "thiserror",
- "tokio 0.2.22",
+ "tokio 0.2.24",
  "trust-dns-proto",
 ]
 
@@ -3514,18 +3490,18 @@ dependencies = [
 
 [[package]]
 name = "unicode-normalization"
-version = "0.1.13"
+version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977"
+checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606"
 dependencies = [
- "tinyvec 0.3.4",
+ "tinyvec",
 ]
 
 [[package]]
 name = "unicode-segmentation"
-version = "1.6.0"
+version = "1.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
+checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
 
 [[package]]
 name = "unicode-xid"
@@ -3547,14 +3523,15 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
 
 [[package]]
 name = "url"
-version = "2.1.1"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
+checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
 dependencies = [
+ "form_urlencoded",
  "idna",
  "matches",
  "percent-encoding",
- "serde 1.0.117",
+ "serde 1.0.118",
 ]
 
 [[package]]
@@ -3563,15 +3540,15 @@ version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
 dependencies = [
- "rand 0.7.3",
- "serde 1.0.117",
+ "rand",
+ "serde 1.0.118",
 ]
 
 [[package]]
 name = "v_escape"
-version = "0.13.2"
+version = "0.14.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "039a44473286eb84e4e74f90165feff67c802dbeced7ee4c5b00d719b0d0475e"
+checksum = "ccca9e73c678b882900cbaec16dae4d3662ace5a17774ac45af04e0f3988fafa"
 dependencies = [
  "buf-min",
  "v_escape_derive",
@@ -3591,9 +3568,9 @@ dependencies = [
 
 [[package]]
 name = "v_htmlescape"
-version = "0.10.4"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11d7c2a33ed7cf0dc1b42bcf39e01b6512f9df08f09e1cd8a49d9dc49a6a9482"
+checksum = "db00c903248abee8499af60bf20d242e7882335bbbffd2614915184cbb207402"
 dependencies = [
  "cfg-if 1.0.0",
  "v_escape",
@@ -3601,9 +3578,9 @@ dependencies = [
 
 [[package]]
 name = "vcpkg"
-version = "0.2.10"
+version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
+checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
 
 [[package]]
 name = "version_check"
@@ -3641,21 +3618,21 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.68"
+version = "0.2.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42"
+checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e"
 dependencies = [
- "cfg-if 0.1.10",
- "serde 1.0.117",
+ "cfg-if 1.0.0",
+ "serde 1.0.118",
  "serde_json",
  "wasm-bindgen-macro",
 ]
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.68"
+version = "0.2.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68"
+checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62"
 dependencies = [
  "bumpalo",
  "lazy_static",
@@ -3668,11 +3645,11 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.18"
+version = "0.4.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da"
+checksum = "1fe9756085a84584ee9457a002b7cdfe0bfff169f45d2591d8be1345a6780e35"
 dependencies = [
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
  "js-sys",
  "wasm-bindgen",
  "web-sys",
@@ -3680,9 +3657,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.68"
+version = "0.2.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038"
+checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -3690,9 +3667,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.68"
+version = "0.2.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe"
+checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3703,15 +3680,15 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.68"
+version = "0.2.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"
+checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
 
 [[package]]
 name = "web-sys"
-version = "0.3.45"
+version = "0.3.46"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d"
+checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -3719,9 +3696,9 @@ dependencies = [
 
 [[package]]
 name = "webpki"
-version = "0.21.3"
+version = "0.21.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae"
+checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
 dependencies = [
  "ring",
  "untrusted",
@@ -3736,6 +3713,12 @@ dependencies = [
  "webpki",
 ]
 
+[[package]]
+name = "weezl"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2bb9fc8309084dd7cd651336673844c1d47f8ef6d2091ec160b27f5c4aa277"
+
 [[package]]
 name = "widestring"
 version = "0.4.3"
@@ -3813,6 +3796,12 @@ dependencies = [
  "winapi-build",
 ]
 
+[[package]]
+name = "wyz"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
+
 [[package]]
 name = "xdg"
 version = "2.2.0"
index f0b98709d3a041b686169148aa97160b3aa5bcdb..40b28677591cdb631bbd1a5d0c390b2200664280 100644 (file)
@@ -25,32 +25,32 @@ lemmy_db = { path = "./lemmy_db" }
 lemmy_structs = { path = "./lemmy_structs" }
 lemmy_rate_limit = { path = "./lemmy_rate_limit" }
 lemmy_websocket = { path = "./lemmy_websocket" }
-diesel = "1.4"
-diesel_migrations = "1.4"
-chrono = { version = "0.4", features = ["serde"] }
-serde = { version = "1.0", features = ["derive"] }
-actix = "0.10"
-actix-web = { version = "3.1", default-features = false, features = ["rustls"] }
-actix-files = { version = "0.4", default-features = false }
-actix-web-actors = { version = "3.0", default-features = false }
-awc = { version = "2.0", default-features = false }
-log = "0.4"
-env_logger = "0.8"
-strum = "0.19"
-lazy_static = "1.3"
-rss = "1.9"
-url = { version = "2.1", features = ["serde"] }
-openssl = "0.10"
-http-signature-normalization-actix = { version = "0.4", default-features = false, features = ["sha-2"] }
-tokio = "0.3"
-sha2 = "0.9"
-anyhow = "1.0"
-reqwest = { version = "0.10", features = ["json"] }
-activitystreams = "0.7.0-alpha.4"
-actix-rt = { version = "1.1", default-features = false }
-serde_json = { version = "1.0", features = ["preserve_order"]}
+diesel = "1.4.5"
+diesel_migrations = "1.4.0"
+chrono = { version = "0.4.19", features = ["serde"] }
+serde = { version = "1.0.118", features = ["derive"] }
+actix = "0.10.0"
+actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] }
+actix-files = { version = "0.4.1", default-features = false }
+actix-web-actors = { version = "3.0.0", default-features = false }
+awc = { version = "2.0.3", default-features = false }
+log = "0.4.11"
+env_logger = "0.8.2"
+strum = "0.20.0"
+lazy_static = "1.4.0"
+rss = "1.9.0"
+url = { version = "2.2.0", features = ["serde"] }
+openssl = "0.10.31"
+http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] }
+tokio = "0.3.6"
+sha2 = "0.9.2"
+anyhow = "1.0.35"
+reqwest = { version = "0.10.10", features = ["json"] }
+activitystreams = "0.7.0-alpha.8"
+actix-rt = { version = "1.1.1", default-features = false }
+serde_json = { version = "1.0.60", features = ["preserve_order"] }
 
 [dev-dependencies.cargo-husky]
-version = "1"
+version = "1.5.0"
 default-features = false # Disable features which are enabled by default
 features = ["precommit-hook", "run-cargo-fmt", "run-cargo-clippy"]
index 0874be8f9c853fecd5b9223d6a4c11439a0f0c50..84b9c665648830977465113cfb4e8c2e7c30ceea 100644 (file)
@@ -61,12 +61,20 @@ server {
       if ($http_accept = "application/activity+json") {
         set $proxpass "http://0.0.0.0:{{ lemmy_port }}";
       }
+      if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
+        set $proxpass "http://0.0.0.0:{{ lemmy_port }}";
+      }
       if ($request_method = POST) {
         set $proxpass "http://0.0.0.0:{{ lemmy_port }}";
       }
       proxy_pass $proxpass;
 
       rewrite ^(.+)/+$ $1 permanent;
+
+      # Send actual client IP upstream
+      proxy_set_header X-Real-IP $remote_addr;
+      proxy_set_header Host $host;
+      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     }
 
     # backend
diff --git a/api_tests/.eslintrc.json b/api_tests/.eslintrc.json
new file mode 100644 (file)
index 0000000..aec9f66
--- /dev/null
@@ -0,0 +1,51 @@
+{
+  "root": true,
+  "env": {
+    "browser": true
+  },
+  "plugins": [
+    "jane"
+  ],
+  "extends": [
+    "plugin:jane/recommended",
+    "plugin:jane/typescript"
+  ],
+  "parser": "@typescript-eslint/parser",
+  "parserOptions": {
+    "project": "./tsconfig.json",
+    "warnOnUnsupportedTypeScriptVersion": false
+  },
+  "rules": {
+    "@typescript-eslint/camelcase": 0,
+    "@typescript-eslint/member-delimiter-style": 0,
+    "@typescript-eslint/no-empty-interface": 0,
+    "@typescript-eslint/no-explicit-any": 0,
+    "@typescript-eslint/no-this-alias": 0,
+    "@typescript-eslint/no-unused-vars": 0,
+    "@typescript-eslint/no-use-before-define": 0,
+    "@typescript-eslint/no-useless-constructor": 0,
+    "arrow-body-style": 0,
+    "curly": 0,
+    "eol-last": 0,
+    "eqeqeq": 0,
+    "func-style": 0,
+    "import/no-duplicates": 0,
+    "max-statements": 0,
+    "max-params": 0,
+    "new-cap": 0,
+    "no-console": 0,
+    "no-duplicate-imports": 0,
+    "no-extra-parens": 0,
+    "no-return-assign": 0,
+    "no-throw-literal": 0,
+    "no-trailing-spaces": 0,
+    "no-unused-expressions": 0,
+    "no-useless-constructor": 0,
+    "no-useless-escape": 0,
+    "no-var": 0,
+    "prefer-const": 0,
+    "prefer-rest-params": 0,
+    "quote-props": 0,
+    "unicorn/filename-case": 0
+  }
+}
diff --git a/api_tests/.prettierrc.js b/api_tests/.prettierrc.js
new file mode 100644 (file)
index 0000000..5983e1a
--- /dev/null
@@ -0,0 +1,4 @@
+module.exports = Object.assign(require('eslint-plugin-jane/prettier-ts'), {
+  arrowParens: 'avoid',
+  semi: true,
+});
index 5c7cd3eb76b4847d68bfeeb70837853694b2225b..13c194963a1db0e43f63aaaa1410a5ead3baf6ae 100644 (file)
@@ -7,14 +7,19 @@
   "author": "Dessalines",
   "license": "AGPL-3.0",
   "scripts": {
+    "lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src",
+    "fix": "prettier --write src && eslint --fix src",
     "api-test": "jest src/ -i --verbose"
   },
   "devDependencies": {
-    "@types/jest": "^26.0.14",
-    "jest": "^26.4.2",
-    "lemmy-js-client": "^1.0.14",
+    "@types/jest": "^26.0.19",
+    "jest": "^26.6.3",
+    "lemmy-js-client": "1.0.17-beta3",
     "node-fetch": "^2.6.1",
-    "ts-jest": "^26.4.1",
-    "typescript": "^4.0.3"
+    "ts-jest": "^26.4.4",
+    "prettier": "^2.1.2",
+    "eslint": "^7.10.0",
+    "eslint-plugin-jane": "^9.0.3",
+    "typescript": "^4.1.3"
   }
 }
index 57756a39e3779749eb18c0ad919314453080d116..b0ca55d0d648fa8d0d8795248ff9fda6a31a816f 100644 (file)
@@ -11,7 +11,7 @@ import {
   followBeta,
   searchForBetaCommunity,
   createComment,
-  updateComment,
+  editComment,
   deleteComment,
   removeComment,
   getMentions,
@@ -21,9 +21,7 @@ import {
   registerUser,
   API,
 } from './shared';
-import {
-  Comment,
-} from 'lemmy-js-client';
+import { CommentView } from 'lemmy-js-client';
 
 import { PostResponse } from 'lemmy-js-client';
 
@@ -36,7 +34,7 @@ beforeAll(async () => {
   let search = await searchForBetaCommunity(alpha);
   postRes = await createPost(
     alpha,
-    search.communities.filter(c => c.local == false)[0].id
+    search.communities.find(c => c.community.local == false).community.id
   );
 });
 
@@ -46,33 +44,34 @@ afterAll(async () => {
 });
 
 function assertCommentFederation(
-  commentOne: Comment,
-  commentTwo: Comment) {
-  expect(commentOne.ap_id).toBe(commentOne.ap_id);
-  expect(commentOne.content).toBe(commentTwo.content);
-  expect(commentOne.creator_name).toBe(commentTwo.creator_name);
-  expect(commentOne.community_actor_id).toBe(commentTwo.community_actor_id);
-  expect(commentOne.published).toBe(commentTwo.published);
-  expect(commentOne.updated).toBe(commentOne.updated);
-  expect(commentOne.deleted).toBe(commentOne.deleted);
-  expect(commentOne.removed).toBe(commentOne.removed);
+  commentOne: CommentView,
+  commentTwo: CommentView
+) {
+  expect(commentOne.comment.ap_id).toBe(commentOne.comment.ap_id);
+  expect(commentOne.comment.content).toBe(commentTwo.comment.content);
+  expect(commentOne.creator.name).toBe(commentTwo.creator.name);
+  expect(commentOne.community.actor_id).toBe(commentTwo.community.actor_id);
+  expect(commentOne.comment.published).toBe(commentTwo.comment.published);
+  expect(commentOne.comment.updated).toBe(commentOne.comment.updated);
+  expect(commentOne.comment.deleted).toBe(commentOne.comment.deleted);
+  expect(commentOne.comment.removed).toBe(commentOne.comment.removed);
 }
 
 test('Create a comment', async () => {
-  let commentRes = await createComment(alpha, postRes.post.id);
-  expect(commentRes.comment.content).toBeDefined();
-  expect(commentRes.comment.community_local).toBe(false);
-  expect(commentRes.comment.creator_local).toBe(true);
-  expect(commentRes.comment.score).toBe(1);
+  let commentRes = await createComment(alpha, postRes.post_view.post.id);
+  expect(commentRes.comment_view.comment.content).toBeDefined();
+  expect(commentRes.comment_view.community.local).toBe(false);
+  expect(commentRes.comment_view.creator.local).toBe(true);
+  expect(commentRes.comment_view.counts.score).toBe(1);
 
   // Make sure that comment is liked on beta
-  let searchBeta = await searchComment(beta, commentRes.comment);
+  let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
   let betaComment = searchBeta.comments[0];
   expect(betaComment).toBeDefined();
-  expect(betaComment.community_local).toBe(true);
-  expect(betaComment.creator_local).toBe(false);
-  expect(betaComment.score).toBe(1);
-  assertCommentFederation(betaComment, commentRes.comment);
+  expect(betaComment.community.local).toBe(true);
+  expect(betaComment.creator.local).toBe(false);
+  expect(betaComment.counts.score).toBe(1);
+  assertCommentFederation(betaComment, commentRes.comment_view);
 });
 
 test('Create a comment in a non-existent post', async () => {
@@ -81,74 +80,90 @@ test('Create a comment in a non-existent post', async () => {
 });
 
 test('Update a comment', async () => {
-  let commentRes = await createComment(alpha, postRes.post.id);
+  let commentRes = await createComment(alpha, postRes.post_view.post.id);
   // Federate the comment first
-  let searchBeta = await searchComment(beta, commentRes.comment);
-  assertCommentFederation(searchBeta.comments[0], commentRes.comment);
+  let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
+  assertCommentFederation(searchBeta.comments[0], commentRes.comment_view);
 
-  let updateCommentRes = await updateComment(alpha, commentRes.comment.id);
-  expect(updateCommentRes.comment.content).toBe(
+  let updateCommentRes = await editComment(
+    alpha,
+    commentRes.comment_view.comment.id
+  );
+  expect(updateCommentRes.comment_view.comment.content).toBe(
     'A jest test federated comment update'
   );
-  expect(updateCommentRes.comment.community_local).toBe(false);
-  expect(updateCommentRes.comment.creator_local).toBe(true);
+  expect(updateCommentRes.comment_view.community.local).toBe(false);
+  expect(updateCommentRes.comment_view.creator.local).toBe(true);
 
   // Make sure that post is updated on beta
-  let searchBetaUpdated = await searchComment(beta, commentRes.comment);
-  assertCommentFederation(searchBetaUpdated.comments[0], updateCommentRes.comment);
+  let searchBetaUpdated = await searchComment(
+    beta,
+    commentRes.comment_view.comment
+  );
+  assertCommentFederation(
+    searchBetaUpdated.comments[0],
+    updateCommentRes.comment_view
+  );
 });
 
 test('Delete a comment', async () => {
-  let commentRes = await createComment(alpha, postRes.post.id);
+  let commentRes = await createComment(alpha, postRes.post_view.post.id);
 
   let deleteCommentRes = await deleteComment(
     alpha,
     true,
-    commentRes.comment.id
+    commentRes.comment_view.comment.id
   );
-  expect(deleteCommentRes.comment.deleted).toBe(true);
+  expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
 
   // Make sure that comment is undefined on beta
-  let searchBeta = await searchComment(beta, commentRes.comment);
+  let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
   let betaComment = searchBeta.comments[0];
   expect(betaComment).toBeUndefined();
 
   let undeleteCommentRes = await deleteComment(
     alpha,
     false,
-    commentRes.comment.id
+    commentRes.comment_view.comment.id
   );
-  expect(undeleteCommentRes.comment.deleted).toBe(false);
+  expect(undeleteCommentRes.comment_view.comment.deleted).toBe(false);
 
   // Make sure that comment is undeleted on beta
-  let searchBeta2 = await searchComment(beta, commentRes.comment);
+  let searchBeta2 = await searchComment(beta, commentRes.comment_view.comment);
   let betaComment2 = searchBeta2.comments[0];
-  expect(betaComment2.deleted).toBe(false);
-  assertCommentFederation(searchBeta2.comments[0], undeleteCommentRes.comment);
+  expect(betaComment2.comment.deleted).toBe(false);
+  assertCommentFederation(
+    searchBeta2.comments[0],
+    undeleteCommentRes.comment_view
+  );
 });
 
 test('Remove a comment from admin and community on the same instance', async () => {
-  let commentRes = await createComment(alpha, postRes.post.id);
+  let commentRes = await createComment(alpha, postRes.post_view.post.id);
 
   // Get the id for beta
-  let betaCommentId = (await searchComment(beta, commentRes.comment))
-    .comments[0].id;
+  let betaCommentId = (
+    await searchComment(beta, commentRes.comment_view.comment)
+  ).comments[0].comment.id;
 
   // The beta admin removes it (the community lives on beta)
   let removeCommentRes = await removeComment(beta, true, betaCommentId);
-  expect(removeCommentRes.comment.removed).toBe(true);
+  expect(removeCommentRes.comment_view.comment.removed).toBe(true);
 
   // Make sure that comment is removed on alpha (it gets pushed since an admin from beta removed it)
-  let refetchedPost = await getPost(alpha, postRes.post.id);
-  expect(refetchedPost.comments[0].removed).toBe(true);
+  let refetchedPost = await getPost(alpha, postRes.post_view.post.id);
+  expect(refetchedPost.comments[0].comment.removed).toBe(true);
 
   let unremoveCommentRes = await removeComment(beta, false, betaCommentId);
-  expect(unremoveCommentRes.comment.removed).toBe(false);
+  expect(unremoveCommentRes.comment_view.comment.removed).toBe(false);
 
   // Make sure that comment is unremoved on beta
-  let refetchedPost2 = await getPost(alpha, postRes.post.id);
-  expect(refetchedPost2.comments[0].removed).toBe(false);
-  assertCommentFederation(refetchedPost2.comments[0], unremoveCommentRes.comment);
+  let refetchedPost2 = await getPost(alpha, postRes.post_view.post.id);
+  expect(refetchedPost2.comments[0].comment.removed).toBe(false);
+  assertCommentFederation(
+    refetchedPost2.comments[0],
+    unremoveCommentRes.comment_view
+  );
 });
 
 test('Remove a comment from admin and community on different instance', async () => {
@@ -160,144 +175,155 @@ test('Remove a comment from admin and community on different instance', async ()
 
   // New alpha user creates a community, post, and comment.
   let newCommunity = await createCommunity(newAlphaApi);
-  let newPost = await createPost(newAlphaApi, newCommunity.community.id);
-  let commentRes = await createComment(newAlphaApi, newPost.post.id);
-  expect(commentRes.comment.content).toBeDefined();
+  let newPost = await createPost(
+    newAlphaApi,
+    newCommunity.community_view.community.id
+  );
+  let commentRes = await createComment(newAlphaApi, newPost.post_view.post.id);
+  expect(commentRes.comment_view.comment.content).toBeDefined();
 
   // Beta searches that to cache it, then removes it
-  let searchBeta = await searchComment(beta, commentRes.comment);
+  let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
   let betaComment = searchBeta.comments[0];
-  let removeCommentRes = await removeComment(beta, true, betaComment.id);
-  expect(removeCommentRes.comment.removed).toBe(true);
+  let removeCommentRes = await removeComment(
+    beta,
+    true,
+    betaComment.comment.id
+  );
+  expect(removeCommentRes.comment_view.comment.removed).toBe(true);
 
   // Make sure its not removed on alpha
-  let refetchedPost = await getPost(newAlphaApi, newPost.post.id);
-  expect(refetchedPost.comments[0].removed).toBe(false);
-  assertCommentFederation(refetchedPost.comments[0], commentRes.comment);
+  let refetchedPost = await getPost(newAlphaApi, newPost.post_view.post.id);
+  expect(refetchedPost.comments[0].comment.removed).toBe(false);
+  assertCommentFederation(refetchedPost.comments[0], commentRes.comment_view);
 });
 
 test('Unlike a comment', async () => {
-  let commentRes = await createComment(alpha, postRes.post.id);
-  let unlike = await likeComment(alpha, 0, commentRes.comment);
-  expect(unlike.comment.score).toBe(0);
+  let commentRes = await createComment(alpha, postRes.post_view.post.id);
+  let unlike = await likeComment(alpha, 0, commentRes.comment_view.comment);
+  expect(unlike.comment_view.counts.score).toBe(0);
 
   // Make sure that post is unliked on beta
-  let searchBeta = await searchComment(beta, commentRes.comment);
+  let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
   let betaComment = searchBeta.comments[0];
   expect(betaComment).toBeDefined();
-  expect(betaComment.community_local).toBe(true);
-  expect(betaComment.creator_local).toBe(false);
-  expect(betaComment.score).toBe(0);
+  expect(betaComment.community.local).toBe(true);
+  expect(betaComment.creator.local).toBe(false);
+  expect(betaComment.counts.score).toBe(0);
 });
 
 test('Federated comment like', async () => {
-  let commentRes = await createComment(alpha, postRes.post.id);
+  let commentRes = await createComment(alpha, postRes.post_view.post.id);
 
   // Find the comment on beta
-  let searchBeta = await searchComment(beta, commentRes.comment);
+  let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
   let betaComment = searchBeta.comments[0];
 
-  let like = await likeComment(beta, 1, betaComment);
-  expect(like.comment.score).toBe(2);
+  let like = await likeComment(beta, 1, betaComment.comment);
+  expect(like.comment_view.counts.score).toBe(2);
 
   // Get the post from alpha, check the likes
-  let post = await getPost(alpha, postRes.post.id);
-  expect(post.comments[0].score).toBe(2);
+  let post = await getPost(alpha, postRes.post_view.post.id);
+  expect(post.comments[0].counts.score).toBe(2);
 });
 
 test('Reply to a comment', async () => {
   // Create a comment on alpha, find it on beta
-  let commentRes = await createComment(alpha, postRes.post.id);
-  let searchBeta = await searchComment(beta, commentRes.comment);
+  let commentRes = await createComment(alpha, postRes.post_view.post.id);
+  let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
   let betaComment = searchBeta.comments[0];
 
   // find that comment id on beta
 
   // Reply from beta
-  let replyRes = await createComment(beta, betaComment.post_id, betaComment.id);
-  expect(replyRes.comment.content).toBeDefined();
-  expect(replyRes.comment.community_local).toBe(true);
-  expect(replyRes.comment.creator_local).toBe(true);
-  expect(replyRes.comment.parent_id).toBe(betaComment.id);
-  expect(replyRes.comment.score).toBe(1);
+  let replyRes = await createComment(
+    beta,
+    betaComment.post.id,
+    betaComment.comment.id
+  );
+  expect(replyRes.comment_view.comment.content).toBeDefined();
+  expect(replyRes.comment_view.community.local).toBe(true);
+  expect(replyRes.comment_view.creator.local).toBe(true);
+  expect(replyRes.comment_view.comment.parent_id).toBe(betaComment.comment.id);
+  expect(replyRes.comment_view.counts.score).toBe(1);
 
   // Make sure that comment is seen on alpha
   // TODO not sure why, but a searchComment back to alpha, for the ap_id of betas
   // comment, isn't working.
   // let searchAlpha = await searchComment(alpha, replyRes.comment);
-  let post = await getPost(alpha, postRes.post.id);
+  let post = await getPost(alpha, postRes.post_view.post.id);
   let alphaComment = post.comments[0];
-  expect(alphaComment.content).toBeDefined();
-  expect(alphaComment.parent_id).toBe(post.comments[1].id);
-  expect(alphaComment.community_local).toBe(false);
-  expect(alphaComment.creator_local).toBe(false);
-  expect(alphaComment.score).toBe(1);
-  assertCommentFederation(alphaComment, replyRes.comment);
+  expect(alphaComment.comment.content).toBeDefined();
+  expect(alphaComment.comment.parent_id).toBe(post.comments[1].comment.id);
+  expect(alphaComment.community.local).toBe(false);
+  expect(alphaComment.creator.local).toBe(false);
+  expect(alphaComment.counts.score).toBe(1);
+  assertCommentFederation(alphaComment, replyRes.comment_view);
 });
 
 test('Mention beta', async () => {
   // Create a mention on alpha
   let mentionContent = 'A test mention of @lemmy_beta@lemmy-beta:8551';
-  let commentRes = await createComment(alpha, postRes.post.id);
+  let commentRes = await createComment(alpha, postRes.post_view.post.id);
   let mentionRes = await createComment(
     alpha,
-    postRes.post.id,
-    commentRes.comment.id,
+    postRes.post_view.post.id,
+    commentRes.comment_view.comment.id,
     mentionContent
   );
-  expect(mentionRes.comment.content).toBeDefined();
-  expect(mentionRes.comment.community_local).toBe(false);
-  expect(mentionRes.comment.creator_local).toBe(true);
-  expect(mentionRes.comment.score).toBe(1);
+  expect(mentionRes.comment_view.comment.content).toBeDefined();
+  expect(mentionRes.comment_view.community.local).toBe(false);
+  expect(mentionRes.comment_view.creator.local).toBe(true);
+  expect(mentionRes.comment_view.counts.score).toBe(1);
 
   let mentionsRes = await getMentions(beta);
-  expect(mentionsRes.mentions[0].content).toBeDefined();
-  expect(mentionsRes.mentions[0].community_local).toBe(true);
-  expect(mentionsRes.mentions[0].creator_local).toBe(false);
-  expect(mentionsRes.mentions[0].score).toBe(1);
+  expect(mentionsRes.mentions[0].comment.content).toBeDefined();
+  expect(mentionsRes.mentions[0].community.local).toBe(true);
+  expect(mentionsRes.mentions[0].creator.local).toBe(false);
+  expect(mentionsRes.mentions[0].counts.score).toBe(1);
 });
 
 test('Comment Search', async () => {
-  let commentRes = await createComment(alpha, postRes.post.id);
-  let searchBeta = await searchComment(beta, commentRes.comment);
-  assertCommentFederation(searchBeta.comments[0], commentRes.comment);
+  let commentRes = await createComment(alpha, postRes.post_view.post.id);
+  let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
+  assertCommentFederation(searchBeta.comments[0], commentRes.comment_view);
 });
 
 test('A and G subscribe to B (center) A posts, G mentions B, it gets announced to A', async () => {
   // Create a local post
   let alphaPost = await createPost(alpha, 2);
-  expect(alphaPost.post.community_local).toBe(true);
+  expect(alphaPost.post_view.community.local).toBe(true);
 
   // Make sure gamma sees it
-  let search = await searchPost(gamma, alphaPost.post);
+  let search = await searchPost(gamma, alphaPost.post_view.post);
   let gammaPost = search.posts[0];
 
   let commentContent =
     'A jest test federated comment announce, lets mention @lemmy_beta@lemmy-beta:8551';
   let commentRes = await createComment(
     gamma,
-    gammaPost.id,
+    gammaPost.post.id,
     undefined,
     commentContent
   );
-  expect(commentRes.comment.content).toBe(commentContent);
-  expect(commentRes.comment.community_local).toBe(false);
-  expect(commentRes.comment.creator_local).toBe(true);
-  expect(commentRes.comment.score).toBe(1);
+  expect(commentRes.comment_view.comment.content).toBe(commentContent);
+  expect(commentRes.comment_view.community.local).toBe(false);
+  expect(commentRes.comment_view.creator.local).toBe(true);
+  expect(commentRes.comment_view.counts.score).toBe(1);
 
   // Make sure alpha sees it
-  let alphaPost2 = await getPost(alpha, alphaPost.post.id);
-  expect(alphaPost2.comments[0].content).toBe(commentContent);
-  expect(alphaPost2.comments[0].community_local).toBe(true);
-  expect(alphaPost2.comments[0].creator_local).toBe(false);
-  expect(alphaPost2.comments[0].score).toBe(1);
-  assertCommentFederation(alphaPost2.comments[0], commentRes.comment);
+  let alphaPost2 = await getPost(alpha, alphaPost.post_view.post.id);
+  expect(alphaPost2.comments[0].comment.content).toBe(commentContent);
+  expect(alphaPost2.comments[0].community.local).toBe(true);
+  expect(alphaPost2.comments[0].creator.local).toBe(false);
+  expect(alphaPost2.comments[0].counts.score).toBe(1);
+  assertCommentFederation(alphaPost2.comments[0], commentRes.comment_view);
 
   // Make sure beta has mentions
   let mentionsRes = await getMentions(beta);
-  expect(mentionsRes.mentions[0].content).toBe(commentContent);
-  expect(mentionsRes.mentions[0].community_local).toBe(false);
-  expect(mentionsRes.mentions[0].creator_local).toBe(false);
+  expect(mentionsRes.mentions[0].comment.content).toBe(commentContent);
+  expect(mentionsRes.mentions[0].community.local).toBe(false);
+  expect(mentionsRes.mentions[0].creator.local).toBe(false);
   // TODO this is failing because fetchInReplyTos aren't getting score
   // expect(mentionsRes.mentions[0].score).toBe(1);
 });
@@ -306,56 +332,60 @@ test('Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde
   // Unfollow all remote communities
   let followed = await unfollowRemotes(alpha);
   expect(
-    followed.communities.filter(c => c.community_local == false).length
+    followed.communities.filter(c => c.community.local == false).length
   ).toBe(0);
 
   // B creates a post, and two comments, should be invisible to A
   let postRes = await createPost(beta, 2);
-  expect(postRes.post.name).toBeDefined();
+  expect(postRes.post_view.post.name).toBeDefined();
 
   let parentCommentContent = 'An invisible top level comment from beta';
   let parentCommentRes = await createComment(
     beta,
-    postRes.post.id,
+    postRes.post_view.post.id,
     undefined,
     parentCommentContent
   );
-  expect(parentCommentRes.comment.content).toBe(parentCommentContent);
+  expect(parentCommentRes.comment_view.comment.content).toBe(
+    parentCommentContent
+  );
 
   // B creates a comment, then a child one of that.
   let childCommentContent = 'An invisible child comment from beta';
   let childCommentRes = await createComment(
     beta,
-    postRes.post.id,
-    parentCommentRes.comment.id,
+    postRes.post_view.post.id,
+    parentCommentRes.comment_view.comment.id,
+    childCommentContent
+  );
+  expect(childCommentRes.comment_view.comment.content).toBe(
     childCommentContent
   );
-  expect(childCommentRes.comment.content).toBe(childCommentContent);
 
   // Follow beta again
   let follow = await followBeta(alpha);
-  expect(follow.community.local).toBe(false);
-  expect(follow.community.name).toBe('main');
+  expect(follow.community_view.community.local).toBe(false);
+  expect(follow.community_view.community.name).toBe('main');
 
   // An update to the child comment on beta, should push the post, parent, and child to alpha now
   let updatedCommentContent = 'An update child comment from beta';
-  let updateRes = await updateComment(
+  let updateRes = await editComment(
     beta,
-    childCommentRes.comment.id,
+    childCommentRes.comment_view.comment.id,
     updatedCommentContent
   );
-  expect(updateRes.comment.content).toBe(updatedCommentContent);
+  expect(updateRes.comment_view.comment.content).toBe(updatedCommentContent);
 
   // Get the post from alpha
-  let search = await searchPost(alpha, postRes.post);
+  let search = await searchPost(alpha, postRes.post_view.post);
   let alphaPostB = search.posts[0];
 
-  let alphaPost = await getPost(alpha, alphaPostB.id);
-  expect(alphaPost.post.name).toBeDefined();
-  assertCommentFederation(alphaPost.comments[1], parentCommentRes.comment);
-  assertCommentFederation(alphaPost.comments[0], updateRes.comment);
-  expect(alphaPost.post.community_local).toBe(false);
-  expect(alphaPost.post.creator_local).toBe(false);
+  let alphaPost = await getPost(alpha, alphaPostB.post.id);
+  expect(alphaPost.post_view.post.name).toBeDefined();
+  assertCommentFederation(alphaPost.comments[1], parentCommentRes.comment_view);
+  assertCommentFederation(alphaPost.comments[0], updateRes.comment_view);
+  expect(alphaPost.post_view.community.local).toBe(false);
+  expect(alphaPost.post_view.creator.local).toBe(false);
 
   await unfollowRemotes(alpha);
 });
index 906900644203215f11dd734f639a4d51f25e1cae..25d8109c3694e13a66b0a1e16fe24ae28914b1bf 100644 (file)
@@ -10,131 +10,160 @@ import {
   getCommunity,
   followCommunity,
 } from './shared';
-import {
-  Community,
-} from 'lemmy-js-client';
+import { CommunityView } from 'lemmy-js-client';
 
 beforeAll(async () => {
   await setupLogins();
 });
 
 function assertCommunityFederation(
-  communityOne: Community,
-  communityTwo: Community) {
-  expect(communityOne.actor_id).toBe(communityTwo.actor_id);
-  expect(communityOne.name).toBe(communityTwo.name);
-  expect(communityOne.title).toBe(communityTwo.title);
-  expect(communityOne.description).toBe(communityTwo.description);
-  expect(communityOne.icon).toBe(communityTwo.icon);
-  expect(communityOne.banner).toBe(communityTwo.banner);
-  expect(communityOne.published).toBe(communityTwo.published);
-  expect(communityOne.creator_actor_id).toBe(communityTwo.creator_actor_id);
-  expect(communityOne.nsfw).toBe(communityTwo.nsfw);
-  expect(communityOne.category_id).toBe(communityTwo.category_id);
-  expect(communityOne.removed).toBe(communityTwo.removed);
-  expect(communityOne.deleted).toBe(communityTwo.deleted);
+  communityOne: CommunityView,
+  communityTwo: CommunityView
+) {
+  expect(communityOne.community.actor_id).toBe(communityTwo.community.actor_id);
+  expect(communityOne.community.name).toBe(communityTwo.community.name);
+  expect(communityOne.community.title).toBe(communityTwo.community.title);
+  expect(communityOne.community.description).toBe(
+    communityTwo.community.description
+  );
+  expect(communityOne.community.icon).toBe(communityTwo.community.icon);
+  expect(communityOne.community.banner).toBe(communityTwo.community.banner);
+  expect(communityOne.community.published).toBe(
+    communityTwo.community.published
+  );
+  expect(communityOne.creator.actor_id).toBe(communityTwo.creator.actor_id);
+  expect(communityOne.community.nsfw).toBe(communityTwo.community.nsfw);
+  expect(communityOne.community.category_id).toBe(
+    communityTwo.community.category_id
+  );
+  expect(communityOne.community.removed).toBe(communityTwo.community.removed);
+  expect(communityOne.community.deleted).toBe(communityTwo.community.deleted);
 }
 
 test('Create community', async () => {
   let communityRes = await createCommunity(alpha);
-  expect(communityRes.community.name).toBeDefined();
+  expect(communityRes.community_view.community.name).toBeDefined();
 
   // A dupe check
-  let prevName = communityRes.community.name;
-  let communityRes2 = await createCommunity(alpha, prevName);
+  let prevName = communityRes.community_view.community.name;
+  let communityRes2: any = await createCommunity(alpha, prevName);
   expect(communityRes2['error']).toBe('community_already_exists');
 
   // Cache the community on beta, make sure it has the other fields
   let searchShort = `!${prevName}@lemmy-alpha:8541`;
   let search = await searchForCommunity(beta, searchShort);
   let communityOnBeta = search.communities[0];
-  assertCommunityFederation(communityOnBeta, communityRes.community);
+  assertCommunityFederation(communityOnBeta, communityRes.community_view);
 });
 
 test('Delete community', async () => {
   let communityRes = await createCommunity(beta);
 
   // Cache the community on Alpha
-  let searchShort = `!${communityRes.community.name}@lemmy-beta:8551`;
+  let searchShort = `!${communityRes.community_view.community.name}@lemmy-beta:8551`;
   let search = await searchForCommunity(alpha, searchShort);
   let communityOnAlpha = search.communities[0];
-  assertCommunityFederation(communityOnAlpha, communityRes.community);
+  assertCommunityFederation(communityOnAlpha, communityRes.community_view);
 
   // Follow the community from alpha
-  let follow = await followCommunity(alpha, true, communityOnAlpha.id);
+  let follow = await followCommunity(
+    alpha,
+    true,
+    communityOnAlpha.community.id
+  );
 
   // Make sure the follow response went through
-  expect(follow.community.local).toBe(false);
+  expect(follow.community_view.community.local).toBe(false);
 
   let deleteCommunityRes = await deleteCommunity(
     beta,
     true,
-    communityRes.community.id
+    communityRes.community_view.community.id
   );
-  expect(deleteCommunityRes.community.deleted).toBe(true);
+  expect(deleteCommunityRes.community_view.community.deleted).toBe(true);
 
   // Make sure it got deleted on A
-  let communityOnAlphaDeleted = await getCommunity(alpha, communityOnAlpha.id);
-  expect(communityOnAlphaDeleted.community.deleted).toBe(true);
+  let communityOnAlphaDeleted = await getCommunity(
+    alpha,
+    communityOnAlpha.community.id
+  );
+  expect(communityOnAlphaDeleted.community_view.community.deleted).toBe(true);
 
   // Undelete
   let undeleteCommunityRes = await deleteCommunity(
     beta,
     false,
-    communityRes.community.id
+    communityRes.community_view.community.id
   );
-  expect(undeleteCommunityRes.community.deleted).toBe(false);
+  expect(undeleteCommunityRes.community_view.community.deleted).toBe(false);
 
   // Make sure it got undeleted on A
-  let communityOnAlphaUnDeleted = await getCommunity(alpha, communityOnAlpha.id);
-  expect(communityOnAlphaUnDeleted.community.deleted).toBe(false);
+  let communityOnAlphaUnDeleted = await getCommunity(
+    alpha,
+    communityOnAlpha.community.id
+  );
+  expect(communityOnAlphaUnDeleted.community_view.community.deleted).toBe(
+    false
+  );
 });
 
 test('Remove community', async () => {
   let communityRes = await createCommunity(beta);
 
   // Cache the community on Alpha
-  let searchShort = `!${communityRes.community.name}@lemmy-beta:8551`;
+  let searchShort = `!${communityRes.community_view.community.name}@lemmy-beta:8551`;
   let search = await searchForCommunity(alpha, searchShort);
   let communityOnAlpha = search.communities[0];
-  assertCommunityFederation(communityOnAlpha, communityRes.community);
+  assertCommunityFederation(communityOnAlpha, communityRes.community_view);
 
   // Follow the community from alpha
-  let follow = await followCommunity(alpha, true, communityOnAlpha.id);
+  let follow = await followCommunity(
+    alpha,
+    true,
+    communityOnAlpha.community.id
+  );
 
   // Make sure the follow response went through
-  expect(follow.community.local).toBe(false);
+  expect(follow.community_view.community.local).toBe(false);
 
   let removeCommunityRes = await removeCommunity(
     beta,
     true,
-    communityRes.community.id
+    communityRes.community_view.community.id
   );
-  expect(removeCommunityRes.community.removed).toBe(true);
+  expect(removeCommunityRes.community_view.community.removed).toBe(true);
 
   // Make sure it got Removed on A
-  let communityOnAlphaRemoved = await getCommunity(alpha, communityOnAlpha.id);
-  expect(communityOnAlphaRemoved.community.removed).toBe(true);
+  let communityOnAlphaRemoved = await getCommunity(
+    alpha,
+    communityOnAlpha.community.id
+  );
+  expect(communityOnAlphaRemoved.community_view.community.removed).toBe(true);
 
   // unremove
   let unremoveCommunityRes = await removeCommunity(
     beta,
     false,
-    communityRes.community.id
+    communityRes.community_view.community.id
   );
-  expect(unremoveCommunityRes.community.removed).toBe(false);
+  expect(unremoveCommunityRes.community_view.community.removed).toBe(false);
 
   // Make sure it got undeleted on A
-  let communityOnAlphaUnRemoved = await getCommunity(alpha, communityOnAlpha.id);
-  expect(communityOnAlphaUnRemoved.community.removed).toBe(false);
+  let communityOnAlphaUnRemoved = await getCommunity(
+    alpha,
+    communityOnAlpha.community.id
+  );
+  expect(communityOnAlphaUnRemoved.community_view.community.removed).toBe(
+    false
+  );
 });
 
 test('Search for beta community', async () => {
   let communityRes = await createCommunity(beta);
-  expect(communityRes.community.name).toBeDefined();
+  expect(communityRes.community_view.community.name).toBeDefined();
 
-  let searchShort = `!${communityRes.community.name}@lemmy-beta:8551`;
+  let searchShort = `!${communityRes.community_view.community.name}@lemmy-beta:8551`;
   let search = await searchForCommunity(alpha, searchShort);
   let communityOnAlpha = search.communities[0];
-  assertCommunityFederation(communityOnAlpha, communityRes.community);
+  assertCommunityFederation(communityOnAlpha, communityRes.community_view);
 });
index 651c526a22f6ce94358958a5226c97a57f5c199a..0749439c08e99b89cd755a5570957c6de66d738f 100644 (file)
@@ -18,22 +18,26 @@ afterAll(async () => {
 
 test('Follow federated community', async () => {
   let search = await searchForBetaCommunity(alpha); // TODO sometimes this is returning null?
-  let follow = await followCommunity(alpha, true, search.communities[0].id);
+  let follow = await followCommunity(
+    alpha,
+    true,
+    search.communities[0].community.id
+  );
 
   // Make sure the follow response went through
-  expect(follow.community.local).toBe(false);
-  expect(follow.community.name).toBe('main');
+  expect(follow.community_view.community.local).toBe(false);
+  expect(follow.community_view.community.name).toBe('main');
 
   // Check it from local
   let followCheck = await checkFollowedCommunities(alpha);
-  let remoteCommunityId = followCheck.communities.filter(
-    c => c.community_local == false
-  )[0].community_id;
+  let remoteCommunityId = followCheck.communities.find(
+    c => c.community.local == false
+  ).community.id;
   expect(remoteCommunityId).toBeDefined();
 
   // Test an unfollow
   let unfollow = await followCommunity(alpha, false, remoteCommunityId);
-  expect(unfollow.community.local).toBe(false);
+  expect(unfollow.community_view.community.local).toBe(false);
 
   // Make sure you are unsubbed locally
   let unfollowCheck = await checkFollowedCommunities(alpha);
index 44edcb24cd76eb5351c6fd24631649d820427551..01befa60cff84a15408986655112183c2c103791 100644 (file)
@@ -7,7 +7,7 @@ import {
   epsilon,
   setupLogins,
   createPost,
-  updatePost,
+  editPost,
   stickyPost,
   lockPost,
   searchPost,
@@ -24,12 +24,9 @@ import {
   searchPostLocal,
   banUserFromCommunity,
 } from './shared';
-import {
-  Post,
-  Community,
-} from 'lemmy-js-client';
+import { PostView, CommunityView } from 'lemmy-js-client';
 
-let betaCommunity: Community;
+let betaCommunity: CommunityView;
 
 beforeAll(async () => {
   await setupLogins();
@@ -49,47 +46,45 @@ async function unfollows() {
   await unfollowRemotes(epsilon);
 }
 
-function assertPostFederation(
-  postOne: Post,
-  postTwo: Post) {
-  expect(postOne.ap_id).toBe(postTwo.ap_id);
-  expect(postOne.name).toBe(postTwo.name);
-  expect(postOne.body).toBe(postTwo.body);
-  expect(postOne.url).toBe(postTwo.url);
-  expect(postOne.nsfw).toBe(postTwo.nsfw);
-  expect(postOne.embed_title).toBe(postTwo.embed_title);
-  expect(postOne.embed_description).toBe(postTwo.embed_description);
-  expect(postOne.embed_html).toBe(postTwo.embed_html);
-  expect(postOne.published).toBe(postTwo.published);
-  expect(postOne.community_actor_id).toBe(postTwo.community_actor_id);
-  expect(postOne.locked).toBe(postTwo.locked);
-  expect(postOne.removed).toBe(postTwo.removed);
-  expect(postOne.deleted).toBe(postTwo.deleted);
+function assertPostFederation(postOne: PostView, postTwo: PostView) {
+  expect(postOne.post.ap_id).toBe(postTwo.post.ap_id);
+  expect(postOne.post.name).toBe(postTwo.post.name);
+  expect(postOne.post.body).toBe(postTwo.post.body);
+  expect(postOne.post.url).toBe(postTwo.post.url);
+  expect(postOne.post.nsfw).toBe(postTwo.post.nsfw);
+  expect(postOne.post.embed_title).toBe(postTwo.post.embed_title);
+  expect(postOne.post.embed_description).toBe(postTwo.post.embed_description);
+  expect(postOne.post.embed_html).toBe(postTwo.post.embed_html);
+  expect(postOne.post.published).toBe(postTwo.post.published);
+  expect(postOne.community.actor_id).toBe(postTwo.community.actor_id);
+  expect(postOne.post.locked).toBe(postTwo.post.locked);
+  expect(postOne.post.removed).toBe(postTwo.post.removed);
+  expect(postOne.post.deleted).toBe(postTwo.post.deleted);
 }
 
 test('Create a post', async () => {
-  let postRes = await createPost(alpha, betaCommunity.id);
-  expect(postRes.post).toBeDefined();
-  expect(postRes.post.community_local).toBe(false);
-  expect(postRes.post.creator_local).toBe(true);
-  expect(postRes.post.score).toBe(1);
+  let postRes = await createPost(alpha, betaCommunity.community.id);
+  expect(postRes.post_view.post).toBeDefined();
+  expect(postRes.post_view.community.local).toBe(false);
+  expect(postRes.post_view.creator.local).toBe(true);
+  expect(postRes.post_view.counts.score).toBe(1);
 
   // Make sure that post is liked on beta
-  let searchBeta = await searchPost(beta, postRes.post);
+  let searchBeta = await searchPost(beta, postRes.post_view.post);
   let betaPost = searchBeta.posts[0];
 
   expect(betaPost).toBeDefined();
-  expect(betaPost.community_local).toBe(true);
-  expect(betaPost.creator_local).toBe(false);
-  expect(betaPost.score).toBe(1);
-  assertPostFederation(betaPost, postRes.post);
+  expect(betaPost.community.local).toBe(true);
+  expect(betaPost.creator.local).toBe(false);
+  expect(betaPost.counts.score).toBe(1);
+  assertPostFederation(betaPost, postRes.post_view);
 
   // Delta only follows beta, so it should not see an alpha ap_id
-  let searchDelta = await searchPost(delta, postRes.post);
+  let searchDelta = await searchPost(delta, postRes.post_view.post);
   expect(searchDelta.posts[0]).toBeUndefined();
 
   // Epsilon has alpha blocked, it should not see the alpha post
-  let searchEpsilon = await searchPost(epsilon, postRes.post);
+  let searchEpsilon = await searchPost(epsilon, postRes.post_view.post);
   expect(searchEpsilon.posts[0]).toBeUndefined();
 });
 
@@ -99,234 +94,234 @@ test('Create a post in a non-existent community', async () => {
 });
 
 test('Unlike a post', async () => {
-  let postRes = await createPost(alpha, betaCommunity.id);
-  let unlike = await likePost(alpha, 0, postRes.post);
-  expect(unlike.post.score).toBe(0);
+  let postRes = await createPost(alpha, betaCommunity.community.id);
+  let unlike = await likePost(alpha, 0, postRes.post_view.post);
+  expect(unlike.post_view.counts.score).toBe(0);
 
   // Try to unlike it again, make sure it stays at 0
-  let unlike2 = await likePost(alpha, 0, postRes.post);
-  expect(unlike2.post.score).toBe(0);
+  let unlike2 = await likePost(alpha, 0, postRes.post_view.post);
+  expect(unlike2.post_view.counts.score).toBe(0);
 
   // Make sure that post is unliked on beta
-  let searchBeta = await searchPost(beta, postRes.post);
+  let searchBeta = await searchPost(beta, postRes.post_view.post);
   let betaPost = searchBeta.posts[0];
   expect(betaPost).toBeDefined();
-  expect(betaPost.community_local).toBe(true);
-  expect(betaPost.creator_local).toBe(false);
-  expect(betaPost.score).toBe(0);
-  assertPostFederation(betaPost, postRes.post);
+  expect(betaPost.community.local).toBe(true);
+  expect(betaPost.creator.local).toBe(false);
+  expect(betaPost.counts.score).toBe(0);
+  assertPostFederation(betaPost, postRes.post_view);
 });
 
 test('Update a post', async () => {
-  let postRes = await createPost(alpha, betaCommunity.id);
+  let postRes = await createPost(alpha, betaCommunity.community.id);
 
   let updatedName = 'A jest test federated post, updated';
-  let updatedPost = await updatePost(alpha, postRes.post);
-  expect(updatedPost.post.name).toBe(updatedName);
-  expect(updatedPost.post.community_local).toBe(false);
-  expect(updatedPost.post.creator_local).toBe(true);
+  let updatedPost = await editPost(alpha, postRes.post_view.post);
+  expect(updatedPost.post_view.post.name).toBe(updatedName);
+  expect(updatedPost.post_view.community.local).toBe(false);
+  expect(updatedPost.post_view.creator.local).toBe(true);
 
   // Make sure that post is updated on beta
-  let searchBeta = await searchPost(beta, postRes.post);
+  let searchBeta = await searchPost(beta, postRes.post_view.post);
   let betaPost = searchBeta.posts[0];
-  expect(betaPost.community_local).toBe(true);
-  expect(betaPost.creator_local).toBe(false);
-  expect(betaPost.name).toBe(updatedName);
-  assertPostFederation(betaPost, updatedPost.post);
+  expect(betaPost.community.local).toBe(true);
+  expect(betaPost.creator.local).toBe(false);
+  expect(betaPost.post.name).toBe(updatedName);
+  assertPostFederation(betaPost, updatedPost.post_view);
 
   // Make sure lemmy beta cannot update the post
-  let updatedPostBeta = await updatePost(beta, betaPost);
+  let updatedPostBeta = await editPost(beta, betaPost.post);
   expect(updatedPostBeta).toStrictEqual({ error: 'no_post_edit_allowed' });
 });
 
 test('Sticky a post', async () => {
-  let postRes = await createPost(alpha, betaCommunity.id);
+  let postRes = await createPost(alpha, betaCommunity.community.id);
+
+  let stickiedPostRes = await stickyPost(alpha, true, postRes.post_view.post);
+  expect(stickiedPostRes.post_view.post.stickied).toBe(true);
 
-  let stickiedPostRes = await stickyPost(alpha, true, postRes.post);
-  expect(stickiedPostRes.post.stickied).toBe(true);
   // Make sure that post is stickied on beta
-  let searchBeta = await searchPost(beta, postRes.post);
+  let searchBeta = await searchPost(beta, postRes.post_view.post);
   let betaPost = searchBeta.posts[0];
-  expect(betaPost.community_local).toBe(true);
-  expect(betaPost.creator_local).toBe(false);
-  expect(betaPost.stickied).toBe(true);
+  expect(betaPost.community.local).toBe(true);
+  expect(betaPost.creator.local).toBe(false);
+  expect(betaPost.post.stickied).toBe(true);
 
   // Unsticky a post
-  let unstickiedPost = await stickyPost(alpha, false, postRes.post);
-  expect(unstickiedPost.post.stickied).toBe(false);
+  let unstickiedPost = await stickyPost(alpha, false, postRes.post_view.post);
+  expect(unstickiedPost.post_view.post.stickied).toBe(false);
 
   // Make sure that post is unstickied on beta
-  let searchBeta2 = await searchPost(beta, postRes.post);
+  let searchBeta2 = await searchPost(beta, postRes.post_view.post);
   let betaPost2 = searchBeta2.posts[0];
-  expect(betaPost2.community_local).toBe(true);
-  expect(betaPost2.creator_local).toBe(false);
-  expect(betaPost2.stickied).toBe(false);
+  expect(betaPost2.community.local).toBe(true);
+  expect(betaPost2.creator.local).toBe(false);
+  expect(betaPost2.post.stickied).toBe(false);
 
   // Make sure that gamma cannot sticky the post on beta
-  let searchGamma = await searchPost(gamma, postRes.post);
+  let searchGamma = await searchPost(gamma, postRes.post_view.post);
   let gammaPost = searchGamma.posts[0];
-  let gammaTrySticky = await stickyPost(gamma, true, gammaPost);
-  let searchBeta3 = await searchPost(beta, postRes.post);
+  let gammaTrySticky = await stickyPost(gamma, true, gammaPost.post);
+  let searchBeta3 = await searchPost(beta, postRes.post_view.post);
   let betaPost3 = searchBeta3.posts[0];
-  expect(gammaTrySticky.post.stickied).toBe(true);
-  expect(betaPost3.stickied).toBe(false);
+  expect(gammaTrySticky.post_view.post.stickied).toBe(true);
+  expect(betaPost3.post.stickied).toBe(false);
 });
 
 test('Lock a post', async () => {
-  let postRes = await createPost(alpha, betaCommunity.id);
+  let postRes = await createPost(alpha, betaCommunity.community.id);
 
   // Lock the post
-  let lockedPostRes = await lockPost(alpha, true, postRes.post);
-  expect(lockedPostRes.post.locked).toBe(true);
+  let lockedPostRes = await lockPost(alpha, true, postRes.post_view.post);
+  expect(lockedPostRes.post_view.post.locked).toBe(true);
 
   // Make sure that post is locked on beta
-  let searchBeta = await searchPostLocal(beta, postRes.post);
+  let searchBeta = await searchPostLocal(beta, postRes.post_view.post);
   let betaPost1 = searchBeta.posts[0];
-  expect(betaPost1.locked).toBe(true);
+  expect(betaPost1.post.locked).toBe(true);
 
   // Try to make a new comment there, on alpha
-  let comment = await createComment(alpha, postRes.post.id);
+  let comment: any = await createComment(alpha, postRes.post_view.post.id);
   expect(comment['error']).toBe('locked');
 
   // Unlock a post
-  let unlockedPost = await lockPost(alpha, false, postRes.post);
-  expect(unlockedPost.post.locked).toBe(false);
+  let unlockedPost = await lockPost(alpha, false, postRes.post_view.post);
+  expect(unlockedPost.post_view.post.locked).toBe(false);
 
   // Make sure that post is unlocked on beta
-  let searchBeta2 = await searchPost(beta, postRes.post);
+  let searchBeta2 = await searchPost(beta, postRes.post_view.post);
   let betaPost2 = searchBeta2.posts[0];
-  expect(betaPost2.community_local).toBe(true);
-  expect(betaPost2.creator_local).toBe(false);
-  expect(betaPost2.locked).toBe(false);
+  expect(betaPost2.community.local).toBe(true);
+  expect(betaPost2.creator.local).toBe(false);
+  expect(betaPost2.post.locked).toBe(false);
 
   // Try to create a new comment, on beta
-  let commentBeta = await createComment(beta, betaPost2.id);
+  let commentBeta = await createComment(beta, betaPost2.post.id);
   expect(commentBeta).toBeDefined();
 });
 
 test('Delete a post', async () => {
-  let postRes = await createPost(alpha, betaCommunity.id);
-  expect(postRes.post).toBeDefined();
+  let postRes = await createPost(alpha, betaCommunity.community.id);
+  expect(postRes.post_view.post).toBeDefined();
 
-  let deletedPost = await deletePost(alpha, true, postRes.post);
-  expect(deletedPost.post.deleted).toBe(true);
+  let deletedPost = await deletePost(alpha, true, postRes.post_view.post);
+  expect(deletedPost.post_view.post.deleted).toBe(true);
 
   // Make sure lemmy beta sees post is deleted
-  let searchBeta = await searchPost(beta, postRes.post);
+  let searchBeta = await searchPost(beta, postRes.post_view.post);
   let betaPost = searchBeta.posts[0];
   // This will be undefined because of the tombstone
   expect(betaPost).toBeUndefined();
 
   // Undelete
-  let undeletedPost = await deletePost(alpha, false, postRes.post);
-  expect(undeletedPost.post.deleted).toBe(false);
+  let undeletedPost = await deletePost(alpha, false, postRes.post_view.post);
+  expect(undeletedPost.post_view.post.deleted).toBe(false);
 
   // Make sure lemmy beta sees post is undeleted
-  let searchBeta2 = await searchPost(beta, postRes.post);
+  let searchBeta2 = await searchPost(beta, postRes.post_view.post);
   let betaPost2 = searchBeta2.posts[0];
-  expect(betaPost2.deleted).toBe(false);
-  assertPostFederation(betaPost2, undeletedPost.post);
+  expect(betaPost2.post.deleted).toBe(false);
+  assertPostFederation(betaPost2, undeletedPost.post_view);
 
   // Make sure lemmy beta cannot delete the post
-  let deletedPostBeta = await deletePost(beta, true, betaPost2);
+  let deletedPostBeta = await deletePost(beta, true, betaPost2.post);
   expect(deletedPostBeta).toStrictEqual({ error: 'no_post_edit_allowed' });
 });
 
 test('Remove a post from admin and community on different instance', async () => {
-  let postRes = await createPost(alpha, betaCommunity.id);
+  let postRes = await createPost(alpha, betaCommunity.community.id);
 
-  let removedPost = await removePost(alpha, true, postRes.post);
-  expect(removedPost.post.removed).toBe(true);
+  let removedPost = await removePost(alpha, true, postRes.post_view.post);
+  expect(removedPost.post_view.post.removed).toBe(true);
 
   // Make sure lemmy beta sees post is NOT removed
-  let searchBeta = await searchPost(beta, postRes.post);
+  let searchBeta = await searchPost(beta, postRes.post_view.post);
   let betaPost = searchBeta.posts[0];
-  expect(betaPost.removed).toBe(false);
+  expect(betaPost.post.removed).toBe(false);
 
   // Undelete
-  let undeletedPost = await removePost(alpha, false, postRes.post);
-  expect(undeletedPost.post.removed).toBe(false);
+  let undeletedPost = await removePost(alpha, false, postRes.post_view.post);
+  expect(undeletedPost.post_view.post.removed).toBe(false);
 
   // Make sure lemmy beta sees post is undeleted
-  let searchBeta2 = await searchPost(beta, postRes.post);
+  let searchBeta2 = await searchPost(beta, postRes.post_view.post);
   let betaPost2 = searchBeta2.posts[0];
-  expect(betaPost2.removed).toBe(false);
-  assertPostFederation(betaPost2, undeletedPost.post);
+  expect(betaPost2.post.removed).toBe(false);
+  assertPostFederation(betaPost2, undeletedPost.post_view);
 });
 
 test('Remove a post from admin and community on same instance', async () => {
   await followBeta(alpha);
-  let postRes = await createPost(alpha, betaCommunity.id);
-  expect(postRes.post).toBeDefined();
+  let postRes = await createPost(alpha, betaCommunity.community.id);
+  expect(postRes.post_view.post).toBeDefined();
 
   // Get the id for beta
-  let searchBeta = await searchPostLocal(beta, postRes.post);
+  let searchBeta = await searchPostLocal(beta, postRes.post_view.post);
   let betaPost = searchBeta.posts[0];
   expect(betaPost).toBeDefined();
 
   // The beta admin removes it (the community lives on beta)
-  let removePostRes = await removePost(beta, true, betaPost);
-  expect(removePostRes.post.removed).toBe(true);
+  let removePostRes = await removePost(beta, true, betaPost.post);
+  expect(removePostRes.post_view.post.removed).toBe(true);
 
   // Make sure lemmy alpha sees post is removed
-  let alphaPost = await getPost(alpha, postRes.post.id);
-  // expect(alphaPost.post.removed).toBe(true); // TODO this shouldn't be commented
-  // assertPostFederation(alphaPost.post, removePostRes.post);
+  let alphaPost = await getPost(alpha, postRes.post_view.post.id);
+  // expect(alphaPost.post_view.post.removed).toBe(true); // TODO this shouldn't be commented
+  // assertPostFederation(alphaPost.post_view, removePostRes.post_view);
 
   // Undelete
-  let undeletedPost = await removePost(beta, false, betaPost);
-  expect(undeletedPost.post.removed).toBe(false);
+  let undeletedPost = await removePost(beta, false, betaPost.post);
+  expect(undeletedPost.post_view.post.removed).toBe(false);
 
   // Make sure lemmy alpha sees post is undeleted
-  let alphaPost2 = await getPost(alpha, postRes.post.id);
-  expect(alphaPost2.post.removed).toBe(false);
-  assertPostFederation(alphaPost2.post, undeletedPost.post);
+  let alphaPost2 = await getPost(alpha, postRes.post_view.post.id);
+  expect(alphaPost2.post_view.post.removed).toBe(false);
+  assertPostFederation(alphaPost2.post_view, undeletedPost.post_view);
   await unfollowRemotes(alpha);
 });
 
 test('Search for a post', async () => {
   await unfollowRemotes(alpha);
-  let postRes = await createPost(alpha, betaCommunity.id);
-  expect(postRes.post).toBeDefined();
+  let postRes = await createPost(alpha, betaCommunity.community.id);
+  expect(postRes.post_view.post).toBeDefined();
 
-  let searchBeta = await searchPost(beta, postRes.post);
+  let searchBeta = await searchPost(beta, postRes.post_view.post);
 
-  expect(searchBeta.posts[0].name).toBeDefined();
+  expect(searchBeta.posts[0].post.name).toBeDefined();
 });
 
 test('A and G subscribe to B (center) A posts, it gets announced to G', async () => {
-  let postRes = await createPost(alpha, betaCommunity.id);
-  expect(postRes.post).toBeDefined();
+  let postRes = await createPost(alpha, betaCommunity.community.id);
+  expect(postRes.post_view.post).toBeDefined();
 
-  let search2 = await searchPost(gamma, postRes.post);
-  expect(search2.posts[0].name).toBeDefined();
+  let search2 = await searchPost(gamma, postRes.post_view.post);
+  expect(search2.posts[0].post.name).toBeDefined();
 });
 
 test('Enforce site ban for federated user', async () => {
-
   let alphaShortname = `@lemmy_alpha@lemmy-alpha:8541`;
   let userSearch = await searchForUser(beta, alphaShortname);
   let alphaUser = userSearch.users[0];
   expect(alphaUser).toBeDefined();
 
   // ban alpha from beta site
-  let banAlpha = await banUserFromSite(beta, alphaUser.id, true);
+  let banAlpha = await banUserFromSite(beta, alphaUser.user.id, true);
   expect(banAlpha.banned).toBe(true);
 
   // Alpha makes post on beta
-  let postRes = await createPost(alpha, betaCommunity.id);
-  expect(postRes.post).toBeDefined();
-  expect(postRes.post.community_local).toBe(false);
-  expect(postRes.post.creator_local).toBe(true);
-  expect(postRes.post.score).toBe(1);
+  let postRes = await createPost(alpha, betaCommunity.community.id);
+  expect(postRes.post_view.post).toBeDefined();
+  expect(postRes.post_view.community.local).toBe(false);
+  expect(postRes.post_view.creator.local).toBe(true);
+  expect(postRes.post_view.counts.score).toBe(1);
 
   // Make sure that post doesn't make it to beta
-  let searchBeta = await searchPostLocal(beta, postRes.post);
+  let searchBeta = await searchPostLocal(beta, postRes.post_view.post);
   let betaPost = searchBeta.posts[0];
   expect(betaPost).toBeUndefined();
 
   // Unban alpha
-  let unBanAlpha = await banUserFromSite(beta, alphaUser.id, false);
+  let unBanAlpha = await banUserFromSite(beta, alphaUser.user.id, false);
   expect(unBanAlpha.banned).toBe(false);
 });
 
@@ -337,23 +332,28 @@ test('Enforce community ban for federated user', async () => {
   expect(alphaUser).toBeDefined();
 
   // ban alpha from beta site
-  await banUserFromCommunity(beta, alphaUser.id, 2, false);
-  let banAlpha = await banUserFromCommunity(beta, alphaUser.id, 2, true);
+  await banUserFromCommunity(beta, alphaUser.user.id, 2, false);
+  let banAlpha = await banUserFromCommunity(beta, alphaUser.user.id, 2, true);
   expect(banAlpha.banned).toBe(true);
 
   // Alpha makes post on beta
-  let postRes = await createPost(alpha, betaCommunity.id);
-  expect(postRes.post).toBeDefined();
-  expect(postRes.post.community_local).toBe(false);
-  expect(postRes.post.creator_local).toBe(true);
-  expect(postRes.post.score).toBe(1);
+  let postRes = await createPost(alpha, betaCommunity.community.id);
+  expect(postRes.post_view.post).toBeDefined();
+  expect(postRes.post_view.community.local).toBe(false);
+  expect(postRes.post_view.creator.local).toBe(true);
+  expect(postRes.post_view.counts.score).toBe(1);
 
   // Make sure that post doesn't make it to beta community
-  let searchBeta = await searchPostLocal(beta, postRes.post);
+  let searchBeta = await searchPostLocal(beta, postRes.post_view.post);
   let betaPost = searchBeta.posts[0];
   expect(betaPost).toBeUndefined();
 
   // Unban alpha
-  let unBanAlpha = await banUserFromCommunity(beta, alphaUser.id, 2, false);
+  let unBanAlpha = await banUserFromCommunity(
+    beta,
+    alphaUser.user.id,
+    2,
+    false
+  );
   expect(unBanAlpha.banned).toBe(false);
 });
index 4dc0e705951352632ee4e3435cf96c559752570d..5539b19c7d47918a2f82b221c042e81a77be840a 100644 (file)
@@ -5,7 +5,7 @@ import {
   setupLogins,
   followBeta,
   createPrivateMessage,
-  updatePrivateMessage,
+  editPrivateMessage,
   listPrivateMessages,
   deletePrivateMessage,
   unfollowRemotes,
@@ -16,7 +16,7 @@ let recipient_id: number;
 beforeAll(async () => {
   await setupLogins();
   let follow = await followBeta(alpha);
-  recipient_id = follow.community.creator_id;
+  recipient_id = follow.community_view.creator.id;
 });
 
 afterAll(async () => {
@@ -25,49 +25,66 @@ afterAll(async () => {
 
 test('Create a private message', async () => {
   let pmRes = await createPrivateMessage(alpha, recipient_id);
-  expect(pmRes.message.content).toBeDefined();
-  expect(pmRes.message.local).toBe(true);
-  expect(pmRes.message.creator_local).toBe(true);
-  expect(pmRes.message.recipient_local).toBe(false);
+  expect(pmRes.private_message_view.private_message.content).toBeDefined();
+  expect(pmRes.private_message_view.private_message.local).toBe(true);
+  expect(pmRes.private_message_view.creator.local).toBe(true);
+  expect(pmRes.private_message_view.recipient.local).toBe(false);
 
   let betaPms = await listPrivateMessages(beta);
-  expect(betaPms.messages[0].content).toBeDefined();
-  expect(betaPms.messages[0].local).toBe(false);
-  expect(betaPms.messages[0].creator_local).toBe(false);
-  expect(betaPms.messages[0].recipient_local).toBe(true);
+  expect(betaPms.private_messages[0].private_message.content).toBeDefined();
+  expect(betaPms.private_messages[0].private_message.local).toBe(false);
+  expect(betaPms.private_messages[0].creator.local).toBe(false);
+  expect(betaPms.private_messages[0].recipient.local).toBe(true);
 });
 
 test('Update a private message', async () => {
   let updatedContent = 'A jest test federated private message edited';
 
   let pmRes = await createPrivateMessage(alpha, recipient_id);
-  let pmUpdated = await updatePrivateMessage(alpha, pmRes.message.id);
-  expect(pmUpdated.message.content).toBe(updatedContent);
+  let pmUpdated = await editPrivateMessage(
+    alpha,
+    pmRes.private_message_view.private_message.id
+  );
+  expect(pmUpdated.private_message_view.private_message.content).toBe(
+    updatedContent
+  );
 
   let betaPms = await listPrivateMessages(beta);
-  expect(betaPms.messages[0].content).toBe(updatedContent);
+  expect(betaPms.private_messages[0].private_message.content).toBe(
+    updatedContent
+  );
 });
 
 test('Delete a private message', async () => {
   let pmRes = await createPrivateMessage(alpha, recipient_id);
   let betaPms1 = await listPrivateMessages(beta);
-  let deletedPmRes = await deletePrivateMessage(alpha, true, pmRes.message.id);
-  expect(deletedPmRes.message.deleted).toBe(true);
+  let deletedPmRes = await deletePrivateMessage(
+    alpha,
+    true,
+    pmRes.private_message_view.private_message.id
+  );
+  expect(deletedPmRes.private_message_view.private_message.deleted).toBe(true);
 
   // The GetPrivateMessages filters out deleted,
   // even though they are in the actual database.
   // no reason to show them
   let betaPms2 = await listPrivateMessages(beta);
-  expect(betaPms2.messages.length).toBe(betaPms1.messages.length - 1);
+  expect(betaPms2.private_messages.length).toBe(
+    betaPms1.private_messages.length - 1
+  );
 
   // Undelete
   let undeletedPmRes = await deletePrivateMessage(
     alpha,
     false,
-    pmRes.message.id
+    pmRes.private_message_view.private_message.id
+  );
+  expect(undeletedPmRes.private_message_view.private_message.deleted).toBe(
+    false
   );
-  expect(undeletedPmRes.message.deleted).toBe(false);
 
   let betaPms3 = await listPrivateMessages(beta);
-  expect(betaPms3.messages.length).toBe(betaPms1.messages.length);
+  expect(betaPms3.private_messages.length).toBe(
+    betaPms1.private_messages.length
+  );
 });
index cf1fb7c6d75a702bd48be24ff907d6be48afb2d1..8e6d5334847c17382f129630b52850c3891c7271 100644 (file)
@@ -1,52 +1,54 @@
 import {
-  LoginForm,
+  Login,
   LoginResponse,
-  Post,
-  PostForm,
-  Comment,
-  DeletePostForm,
-  RemovePostForm,
-  StickyPostForm,
-  LockPostForm,
+  CreatePost,
+  EditPost,
+  CreateComment,
+  DeletePost,
+  RemovePost,
+  StickyPost,
+  LockPost,
   PostResponse,
   SearchResponse,
-  FollowCommunityForm,
+  FollowCommunity,
   CommunityResponse,
   GetFollowedCommunitiesResponse,
   GetPostResponse,
-  RegisterForm,
-  CommentForm,
-  DeleteCommentForm,
-  RemoveCommentForm,
-  SearchForm,
+  Register,
+  Comment,
+  EditComment,
+  DeleteComment,
+  RemoveComment,
+  Search,
   CommentResponse,
-  GetCommunityForm,
-  CommunityForm,
-  DeleteCommunityForm,
-  RemoveCommunityForm,
-  GetUserMentionsForm,
-  CommentLikeForm,
-  CreatePostLikeForm,
-  PrivateMessageForm,
-  EditPrivateMessageForm,
-  DeletePrivateMessageForm,
-  GetFollowedCommunitiesForm,
-  GetPrivateMessagesForm,
-  GetSiteForm,
-  GetPostForm,
+  GetCommunity,
+  CreateCommunity,
+  DeleteCommunity,
+  RemoveCommunity,
+  GetUserMentions,
+  CreateCommentLike,
+  CreatePostLike,
+  EditPrivateMessage,
+  DeletePrivateMessage,
+  GetFollowedCommunities,
+  GetPrivateMessages,
+  GetSite,
+  GetPost,
   PrivateMessageResponse,
   PrivateMessagesResponse,
   GetUserMentionsResponse,
-  UserSettingsForm,
+  SaveUserSettings,
   SortType,
   ListingType,
   GetSiteResponse,
   SearchType,
   LemmyHttp,
   BanUserResponse,
-  BanUserForm,
-  BanFromCommunityForm,
+  BanUser,
+  BanFromCommunity,
   BanFromCommunityResponse,
+  Post,
+  CreatePrivateMessage,
 } from 'lemmy-js-client';
 
 export interface API {
@@ -55,27 +57,27 @@ export interface API {
 }
 
 export let alpha: API = {
-  client: new LemmyHttp('http://localhost:8541/api/v1'),
+  client: new LemmyHttp('http://localhost:8541/api/v2'),
 };
 
 export let beta: API = {
-  client: new LemmyHttp('http://localhost:8551/api/v1'),
+  client: new LemmyHttp('http://localhost:8551/api/v2'),
 };
 
 export let gamma: API = {
-  client: new LemmyHttp('http://localhost:8561/api/v1'),
+  client: new LemmyHttp('http://localhost:8561/api/v2'),
 };
 
 export let delta: API = {
-  client: new LemmyHttp('http://localhost:8571/api/v1'),
+  client: new LemmyHttp('http://localhost:8571/api/v2'),
 };
 
 export let epsilon: API = {
-  client: new LemmyHttp('http://localhost:8581/api/v1'),
+  client: new LemmyHttp('http://localhost:8581/api/v2'),
 };
 
 export async function setupLogins() {
-  let formAlpha: LoginForm = {
+  let formAlpha: Login = {
     username_or_email: 'lemmy_alpha',
     password: 'lemmy',
   };
@@ -127,7 +129,7 @@ export async function createPost(
   let name = randomString(5);
   let body = randomString(10);
   let url = 'https://google.com/';
-  let form: PostForm = {
+  let form: CreatePost = {
     name,
     url,
     body,
@@ -138,9 +140,9 @@ export async function createPost(
   return api.client.createPost(form);
 }
 
-export async function updatePost(api: API, post: Post): Promise<PostResponse> {
+export async function editPost(api: API, post: Post): Promise<PostResponse> {
   let name = 'A jest test federated post, updated';
-  let form: PostForm = {
+  let form: EditPost = {
     name,
     edit_id: post.id,
     auth: api.auth,
@@ -154,7 +156,7 @@ export async function deletePost(
   deleted: boolean,
   post: Post
 ): Promise<PostResponse> {
-  let form: DeletePostForm = {
+  let form: DeletePost = {
     edit_id: post.id,
     deleted: deleted,
     auth: api.auth,
@@ -167,7 +169,7 @@ export async function removePost(
   removed: boolean,
   post: Post
 ): Promise<PostResponse> {
-  let form: RemovePostForm = {
+  let form: RemovePost = {
     edit_id: post.id,
     removed,
     auth: api.auth,
@@ -180,7 +182,7 @@ export async function stickyPost(
   stickied: boolean,
   post: Post
 ): Promise<PostResponse> {
-  let form: StickyPostForm = {
+  let form: StickyPost = {
     edit_id: post.id,
     stickied,
     auth: api.auth,
@@ -193,7 +195,7 @@ export async function lockPost(
   locked: boolean,
   post: Post
 ): Promise<PostResponse> {
-  let form: LockPostForm = {
+  let form: LockPost = {
     edit_id: post.id,
     locked,
     auth: api.auth,
@@ -205,7 +207,7 @@ export async function searchPost(
   api: API,
   post: Post
 ): Promise<SearchResponse> {
-  let form: SearchForm = {
+  let form: Search = {
     q: post.ap_id,
     type_: SearchType.Posts,
     sort: SortType.TopAll,
@@ -217,7 +219,7 @@ export async function searchPostLocal(
   api: API,
   post: Post
 ): Promise<SearchResponse> {
-  let form: SearchForm = {
+  let form: Search = {
     q: post.name,
     type_: SearchType.Posts,
     sort: SortType.TopAll,
@@ -229,7 +231,7 @@ export async function getPost(
   api: API,
   post_id: number
 ): Promise<GetPostResponse> {
-  let form: GetPostForm = {
+  let form: GetPost = {
     id: post_id,
   };
   return api.client.getPost(form);
@@ -239,7 +241,7 @@ export async function searchComment(
   api: API,
   comment: Comment
 ): Promise<SearchResponse> {
-  let form: SearchForm = {
+  let form: Search = {
     q: comment.ap_id,
     type_: SearchType.Comments,
     sort: SortType.TopAll,
@@ -252,7 +254,7 @@ export async function searchForBetaCommunity(
 ): Promise<SearchResponse> {
   // Make sure lemmy-beta/c/main is cached on lemmy_alpha
   // Use short-hand search url
-  let form: SearchForm = {
+  let form: Search = {
     q: '!main@lemmy-beta:8551',
     type_: SearchType.Communities,
     sort: SortType.TopAll,
@@ -262,10 +264,10 @@ export async function searchForBetaCommunity(
 
 export async function searchForCommunity(
   api: API,
-  q: string,
+  q: string
 ): Promise<SearchResponse> {
   // Use short-hand search url
-  let form: SearchForm = {
+  let form: Search = {
     q,
     type_: SearchType.Communities,
     sort: SortType.TopAll,
@@ -279,7 +281,7 @@ export async function searchForUser(
 ): Promise<SearchResponse> {
   // Make sure lemmy-beta/c/main is cached on lemmy_alpha
   // Use short-hand search url
-  let form: SearchForm = {
+  let form: Search = {
     q: apShortname,
     type_: SearchType.Users,
     sort: SortType.TopAll,
@@ -290,13 +292,14 @@ export async function searchForUser(
 export async function banUserFromSite(
   api: API,
   user_id: number,
-  ban: boolean,
+  ban: boolean
 ): Promise<BanUserResponse> {
   // Make sure lemmy-beta/c/main is cached on lemmy_alpha
   // Use short-hand search url
-  let form: BanUserForm = {
+  let form: BanUser = {
     user_id,
     ban,
+    remove_data: false,
     auth: api.auth,
   };
   return api.client.banUser(form);
@@ -306,13 +309,14 @@ export async function banUserFromCommunity(
   api: API,
   user_id: number,
   community_id: number,
-  ban: boolean,
+  ban: boolean
 ): Promise<BanFromCommunityResponse> {
   // Make sure lemmy-beta/c/main is cached on lemmy_alpha
   // Use short-hand search url
-  let form: BanFromCommunityForm = {
+  let form: BanFromCommunity = {
     user_id,
     community_id,
+    remove_data: false,
     ban,
     auth: api.auth,
   };
@@ -324,7 +328,7 @@ export async function followCommunity(
   follow: boolean,
   community_id: number
 ): Promise<CommunityResponse> {
-  let form: FollowCommunityForm = {
+  let form: FollowCommunity = {
     community_id,
     follow,
     auth: api.auth,
@@ -335,7 +339,7 @@ export async function followCommunity(
 export async function checkFollowedCommunities(
   api: API
 ): Promise<GetFollowedCommunitiesResponse> {
-  let form: GetFollowedCommunitiesForm = {
+  let form: GetFollowedCommunities = {
     auth: api.auth,
   };
   return api.client.getFollowedCommunities(form);
@@ -346,7 +350,7 @@ export async function likePost(
   score: number,
   post: Post
 ): Promise<PostResponse> {
-  let form: CreatePostLikeForm = {
+  let form: CreatePostLike = {
     post_id: post.id,
     score: score,
     auth: api.auth,
@@ -361,7 +365,7 @@ export async function createComment(
   parent_id?: number,
   content = 'a jest test comment'
 ): Promise<CommentResponse> {
-  let form: CommentForm = {
+  let form: CreateComment = {
     content,
     post_id,
     parent_id,
@@ -370,12 +374,12 @@ export async function createComment(
   return api.client.createComment(form);
 }
 
-export async function updateComment(
+export async function editComment(
   api: API,
   edit_id: number,
   content = 'A jest test federated comment update'
 ): Promise<CommentResponse> {
-  let form: CommentForm = {
+  let form: EditComment = {
     content,
     edit_id,
     auth: api.auth,
@@ -388,7 +392,7 @@ export async function deleteComment(
   deleted: boolean,
   edit_id: number
 ): Promise<CommentResponse> {
-  let form: DeleteCommentForm = {
+  let form: DeleteComment = {
     edit_id,
     deleted,
     auth: api.auth,
@@ -401,7 +405,7 @@ export async function removeComment(
   removed: boolean,
   edit_id: number
 ): Promise<CommentResponse> {
-  let form: RemoveCommentForm = {
+  let form: RemoveComment = {
     edit_id,
     removed,
     auth: api.auth,
@@ -410,7 +414,7 @@ export async function removeComment(
 }
 
 export async function getMentions(api: API): Promise<GetUserMentionsResponse> {
-  let form: GetUserMentionsForm = {
+  let form: GetUserMentions = {
     sort: SortType.New,
     unread_only: false,
     auth: api.auth,
@@ -423,7 +427,7 @@ export async function likeComment(
   score: number,
   comment: Comment
 ): Promise<CommentResponse> {
-  let form: CommentLikeForm = {
+  let form: CreateCommentLike = {
     comment_id: comment.id,
     score,
     auth: api.auth,
@@ -438,7 +442,7 @@ export async function createCommunity(
   let description = 'a sample description';
   let icon = 'https://image.flaticon.com/icons/png/512/35/35896.png';
   let banner = 'https://image.flaticon.com/icons/png/512/35/35896.png';
-  let form: CommunityForm = {
+  let form: CreateCommunity = {
     name: name_,
     title: name_,
     description,
@@ -453,9 +457,9 @@ export async function createCommunity(
 
 export async function getCommunity(
   api: API,
-  id: number,
+  id: number
 ): Promise<CommunityResponse> {
-  let form: GetCommunityForm = {
+  let form: GetCommunity = {
     id,
   };
   return api.client.getCommunity(form);
@@ -466,7 +470,7 @@ export async function deleteCommunity(
   deleted: boolean,
   edit_id: number
 ): Promise<CommunityResponse> {
-  let form: DeleteCommunityForm = {
+  let form: DeleteCommunity = {
     edit_id,
     deleted,
     auth: api.auth,
@@ -479,7 +483,7 @@ export async function removeCommunity(
   removed: boolean,
   edit_id: number
 ): Promise<CommunityResponse> {
-  let form: RemoveCommunityForm = {
+  let form: RemoveCommunity = {
     edit_id,
     removed,
     auth: api.auth,
@@ -492,7 +496,7 @@ export async function createPrivateMessage(
   recipient_id: number
 ): Promise<PrivateMessageResponse> {
   let content = 'A jest test federated private message';
-  let form: PrivateMessageForm = {
+  let form: CreatePrivateMessage = {
     content,
     recipient_id,
     auth: api.auth,
@@ -500,12 +504,12 @@ export async function createPrivateMessage(
   return api.client.createPrivateMessage(form);
 }
 
-export async function updatePrivateMessage(
+export async function editPrivateMessage(
   api: API,
   edit_id: number
 ): Promise<PrivateMessageResponse> {
   let updatedContent = 'A jest test federated private message edited';
-  let form: EditPrivateMessageForm = {
+  let form: EditPrivateMessage = {
     content: updatedContent,
     edit_id,
     auth: api.auth,
@@ -518,7 +522,7 @@ export async function deletePrivateMessage(
   deleted: boolean,
   edit_id: number
 ): Promise<PrivateMessageResponse> {
-  let form: DeletePrivateMessageForm = {
+  let form: DeletePrivateMessage = {
     deleted,
     edit_id,
     auth: api.auth,
@@ -530,7 +534,7 @@ export async function registerUser(
   api: API,
   username: string = randomString(5)
 ): Promise<LoginResponse> {
-  let form: RegisterForm = {
+  let form: Register = {
     username,
     password: 'test',
     password_verify: 'test',
@@ -544,11 +548,11 @@ export async function saveUserSettingsBio(
   api: API,
   auth: string
 ): Promise<LoginResponse> {
-  let form: UserSettingsForm = {
+  let form: SaveUserSettings = {
     show_nsfw: true,
     theme: 'darkly',
-    default_sort_type: Object.keys(SortType).indexOf(SortType.Active),
-    default_listing_type: Object.keys(ListingType).indexOf(ListingType.All),
+    default_sort_type: SortType.Active,
+    default_listing_type: ListingType.All,
     lang: 'en',
     show_avatars: true,
     send_notifications_to_email: false,
@@ -560,7 +564,7 @@ export async function saveUserSettingsBio(
 
 export async function saveUserSettings(
   api: API,
-  form: UserSettingsForm
+  form: SaveUserSettings
 ): Promise<LoginResponse> {
   return api.client.saveUserSettings(form);
 }
@@ -569,7 +573,7 @@ export async function getSite(
   api: API,
   auth: string
 ): Promise<GetSiteResponse> {
-  let form: GetSiteForm = {
+  let form: GetSite = {
     auth,
   };
   return api.client.getSite(form);
@@ -578,7 +582,7 @@ export async function getSite(
 export async function listPrivateMessages(
   api: API
 ): Promise<PrivateMessagesResponse> {
-  let form: GetPrivateMessagesForm = {
+  let form: GetPrivateMessages = {
     auth: api.auth,
     unread_only: false,
     limit: 999,
@@ -592,10 +596,10 @@ export async function unfollowRemotes(
   // Unfollow all remote communities
   let followed = await checkFollowedCommunities(api);
   let remoteFollowed = followed.communities.filter(
-    c => c.community_local == false
+    c => c.community.local == false
   );
   for (let cu of remoteFollowed) {
-    await followCommunity(api, false, cu.community_id);
+    await followCommunity(api, false, cu.community.id);
   }
   let followed2 = await checkFollowedCommunities(api);
   return followed2;
@@ -604,17 +608,15 @@ export async function unfollowRemotes(
 export async function followBeta(api: API): Promise<CommunityResponse> {
   // Cache it
   let search = await searchForBetaCommunity(api);
-  let com = search.communities.filter(c => c.local == false);
-  if (com[0]) {
-    let follow = await followCommunity(api, true, com[0].id);
+  let com = search.communities.find(c => c.community.local == false);
+  if (com) {
+    let follow = await followCommunity(api, true, com.community.id);
     return follow;
   }
 }
 
 export function delay(millis: number = 500) {
-  return new Promise((resolve, _reject) => {
-    setTimeout(_ => resolve(), millis);
-  });
+  return new Promise(resolve => setTimeout(resolve, millis));
 }
 
 export function longDelay() {
index 3a327c2a1d3a6c33ac7e1d4d7a04b744a5fb3020..7886f8eb42003275cd1fce6e679508824c48d930 100644 (file)
@@ -8,23 +8,23 @@ import {
   getSite,
 } from './shared';
 import {
-  UserView,
-  UserSettingsForm,
+  UserViewSafe,
+  SaveUserSettings,
+  SortType,
+  ListingType,
 } from 'lemmy-js-client';
 
 let auth: string;
 let apShortname: string;
 
-function assertUserFederation(
-  userOne: UserView,
-  userTwo: UserView) {
-  expect(userOne.name).toBe(userTwo.name);
-  expect(userOne.preferred_username).toBe(userTwo.preferred_username);
-  expect(userOne.bio).toBe(userTwo.bio);
-  expect(userOne.actor_id).toBe(userTwo.actor_id);
-  expect(userOne.avatar).toBe(userTwo.avatar);
-  expect(userOne.banner).toBe(userTwo.banner);
-  expect(userOne.published).toBe(userTwo.published);
+function assertUserFederation(userOne: UserViewSafe, userTwo: UserViewSafe) {
+  expect(userOne.user.name).toBe(userTwo.user.name);
+  expect(userOne.user.preferred_username).toBe(userTwo.user.preferred_username);
+  expect(userOne.user.bio).toBe(userTwo.user.bio);
+  expect(userOne.user.actor_id).toBe(userTwo.user.actor_id);
+  expect(userOne.user.avatar).toBe(userTwo.user.avatar);
+  expect(userOne.user.banner).toBe(userTwo.user.banner);
+  expect(userOne.user.published).toBe(userTwo.user.published);
 }
 
 test('Create user', async () => {
@@ -41,20 +41,20 @@ test('Set some user settings, check that they are federated', async () => {
   let avatar = 'https://image.flaticon.com/icons/png/512/35/35896.png';
   let banner = 'https://image.flaticon.com/icons/png/512/36/35896.png';
   let bio = 'a changed bio';
-  let form: UserSettingsForm = {
+  let form: SaveUserSettings = {
     show_nsfw: false,
-    theme: "",
-    default_sort_type: 0,
-    default_listing_type: 0,
-    lang: "",
+    theme: '',
+    default_sort_type: SortType.Hot,
+    default_listing_type: ListingType.All,
+    lang: '',
     avatar,
     banner,
-    preferred_username: "user321",
+    preferred_username: 'user321',
     show_avatars: false,
     send_notifications_to_email: false,
     bio,
     auth,
-  }
+  };
   await saveUserSettings(alpha, form);
 
   let searchAlpha = await searchForUser(alpha, apShortname);
diff --git a/api_tests/tsconfig.json b/api_tests/tsconfig.json
new file mode 100644 (file)
index 0000000..b3be051
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "compilerOptions": {
+    "declaration": true,
+    "declarationDir": "./dist",
+    "module": "CommonJS",
+    "noImplicitAny": true,
+    "lib": ["es2017", "es7", "es6", "dom"],
+    "outDir": "./dist",
+    "target": "ES5",
+    "moduleResolution": "Node"
+  },
+  "include": [
+    "src/**/*"
+  ],
+  "exclude": ["node_modules", "dist"]
+}
index e1ee01ac337737d464d54141a62998c222153ca2..3a59e67379482b4ba82bdb6bb5d8d426f2cd67da 100644 (file)
 
 
 "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4":
-  version "7.10.4"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
-  integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==
+  version "7.12.11"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
+  integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==
   dependencies:
     "@babel/highlight" "^7.10.4"
 
 "@babel/core@^7.1.0", "@babel/core@^7.7.5":
-  version "7.11.6"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.6.tgz#3a9455dc7387ff1bac45770650bc13ba04a15651"
-  integrity sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==
+  version "7.12.10"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.10.tgz#b79a2e1b9f70ed3d84bbfb6d8c4ef825f606bccd"
+  integrity sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==
   dependencies:
     "@babel/code-frame" "^7.10.4"
-    "@babel/generator" "^7.11.6"
-    "@babel/helper-module-transforms" "^7.11.0"
-    "@babel/helpers" "^7.10.4"
-    "@babel/parser" "^7.11.5"
-    "@babel/template" "^7.10.4"
-    "@babel/traverse" "^7.11.5"
-    "@babel/types" "^7.11.5"
+    "@babel/generator" "^7.12.10"
+    "@babel/helper-module-transforms" "^7.12.1"
+    "@babel/helpers" "^7.12.5"
+    "@babel/parser" "^7.12.10"
+    "@babel/template" "^7.12.7"
+    "@babel/traverse" "^7.12.10"
+    "@babel/types" "^7.12.10"
     convert-source-map "^1.7.0"
     debug "^4.1.0"
     gensync "^1.0.0-beta.1"
     json5 "^2.1.2"
     lodash "^4.17.19"
-    resolve "^1.3.2"
     semver "^5.4.1"
     source-map "^0.5.0"
 
-"@babel/generator@^7.11.5", "@babel/generator@^7.11.6":
-  version "7.11.6"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.6.tgz#b868900f81b163b4d464ea24545c61cbac4dc620"
-  integrity sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==
+"@babel/generator@^7.12.10":
+  version "7.12.11"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.11.tgz#98a7df7b8c358c9a37ab07a24056853016aba3af"
+  integrity sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==
   dependencies:
-    "@babel/types" "^7.11.5"
+    "@babel/types" "^7.12.11"
     jsesc "^2.5.1"
     source-map "^0.5.0"
 
 "@babel/helper-function-name@^7.10.4":
-  version "7.10.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a"
-  integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==
+  version "7.12.11"
+  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz#1fd7738aee5dcf53c3ecff24f1da9c511ec47b42"
+  integrity sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==
   dependencies:
-    "@babel/helper-get-function-arity" "^7.10.4"
-    "@babel/template" "^7.10.4"
-    "@babel/types" "^7.10.4"
+    "@babel/helper-get-function-arity" "^7.12.10"
+    "@babel/template" "^7.12.7"
+    "@babel/types" "^7.12.11"
 
-"@babel/helper-get-function-arity@^7.10.4":
-  version "7.10.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2"
-  integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==
+"@babel/helper-get-function-arity@^7.12.10":
+  version "7.12.10"
+  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz#b158817a3165b5faa2047825dfa61970ddcc16cf"
+  integrity sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==
   dependencies:
-    "@babel/types" "^7.10.4"
+    "@babel/types" "^7.12.10"
 
-"@babel/helper-member-expression-to-functions@^7.10.4":
-  version "7.11.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz#ae69c83d84ee82f4b42f96e2a09410935a8f26df"
-  integrity sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==
+"@babel/helper-member-expression-to-functions@^7.12.7":
+  version "7.12.7"
+  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz#aa77bd0396ec8114e5e30787efa78599d874a855"
+  integrity sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==
   dependencies:
-    "@babel/types" "^7.11.0"
+    "@babel/types" "^7.12.7"
 
-"@babel/helper-module-imports@^7.10.4":
-  version "7.10.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620"
-  integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==
+"@babel/helper-module-imports@^7.12.1":
+  version "7.12.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz#1bfc0229f794988f76ed0a4d4e90860850b54dfb"
+  integrity sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==
   dependencies:
-    "@babel/types" "^7.10.4"
+    "@babel/types" "^7.12.5"
 
-"@babel/helper-module-transforms@^7.11.0":
-  version "7.11.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz#b16f250229e47211abdd84b34b64737c2ab2d359"
-  integrity sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==
+"@babel/helper-module-transforms@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz#7954fec71f5b32c48e4b303b437c34453fd7247c"
+  integrity sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==
   dependencies:
-    "@babel/helper-module-imports" "^7.10.4"
-    "@babel/helper-replace-supers" "^7.10.4"
-    "@babel/helper-simple-access" "^7.10.4"
+    "@babel/helper-module-imports" "^7.12.1"
+    "@babel/helper-replace-supers" "^7.12.1"
+    "@babel/helper-simple-access" "^7.12.1"
     "@babel/helper-split-export-declaration" "^7.11.0"
+    "@babel/helper-validator-identifier" "^7.10.4"
     "@babel/template" "^7.10.4"
-    "@babel/types" "^7.11.0"
+    "@babel/traverse" "^7.12.1"
+    "@babel/types" "^7.12.1"
     lodash "^4.17.19"
 
-"@babel/helper-optimise-call-expression@^7.10.4":
-  version "7.10.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673"
-  integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==
+"@babel/helper-optimise-call-expression@^7.12.10":
+  version "7.12.10"
+  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz#94ca4e306ee11a7dd6e9f42823e2ac6b49881e2d"
+  integrity sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==
   dependencies:
-    "@babel/types" "^7.10.4"
+    "@babel/types" "^7.12.10"
 
 "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0":
   version "7.10.4"
   resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375"
   integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==
 
-"@babel/helper-replace-supers@^7.10.4":
-  version "7.10.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf"
-  integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==
+"@babel/helper-replace-supers@^7.12.1":
+  version "7.12.11"
+  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz#ea511658fc66c7908f923106dd88e08d1997d60d"
+  integrity sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==
   dependencies:
-    "@babel/helper-member-expression-to-functions" "^7.10.4"
-    "@babel/helper-optimise-call-expression" "^7.10.4"
-    "@babel/traverse" "^7.10.4"
-    "@babel/types" "^7.10.4"
+    "@babel/helper-member-expression-to-functions" "^7.12.7"
+    "@babel/helper-optimise-call-expression" "^7.12.10"
+    "@babel/traverse" "^7.12.10"
+    "@babel/types" "^7.12.11"
 
-"@babel/helper-simple-access@^7.10.4":
-  version "7.10.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461"
-  integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==
+"@babel/helper-simple-access@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz#32427e5aa61547d38eb1e6eaf5fd1426fdad9136"
+  integrity sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==
   dependencies:
-    "@babel/template" "^7.10.4"
-    "@babel/types" "^7.10.4"
+    "@babel/types" "^7.12.1"
 
 "@babel/helper-split-export-declaration@^7.11.0":
-  version "7.11.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f"
-  integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==
+  version "7.12.11"
+  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz#1b4cc424458643c47d37022223da33d76ea4603a"
+  integrity sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==
   dependencies:
-    "@babel/types" "^7.11.0"
+    "@babel/types" "^7.12.11"
 
-"@babel/helper-validator-identifier@^7.10.4":
-  version "7.10.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2"
-  integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==
+"@babel/helper-validator-identifier@^7.10.4", "@babel/helper-validator-identifier@^7.12.11":
+  version "7.12.11"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed"
+  integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==
 
-"@babel/helpers@^7.10.4":
-  version "7.10.4"
-  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044"
-  integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==
+"@babel/helpers@^7.12.5":
+  version "7.12.5"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.12.5.tgz#1a1ba4a768d9b58310eda516c449913fe647116e"
+  integrity sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==
   dependencies:
     "@babel/template" "^7.10.4"
-    "@babel/traverse" "^7.10.4"
-    "@babel/types" "^7.10.4"
+    "@babel/traverse" "^7.12.5"
+    "@babel/types" "^7.12.5"
 
 "@babel/highlight@^7.10.4":
   version "7.10.4"
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.5":
-  version "7.11.5"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037"
-  integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==
+"@babel/parser@^7.1.0", "@babel/parser@^7.12.10", "@babel/parser@^7.12.7", "@babel/parser@^7.7.0":
+  version "7.12.11"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.11.tgz#9ce3595bcd74bc5c466905e86c535b8b25011e79"
+  integrity sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==
 
 "@babel/plugin-syntax-async-generators@^7.8.4":
   version "7.8.4"
     "@babel/helper-plugin-utils" "^7.8.0"
 
 "@babel/plugin-syntax-class-properties@^7.8.3":
-  version "7.10.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz#6644e6a0baa55a61f9e3231f6c9eeb6ee46c124c"
-  integrity sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz#bcb297c5366e79bebadef509549cd93b04f19978"
+  integrity sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==
   dependencies:
     "@babel/helper-plugin-utils" "^7.10.4"
 
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/template@^7.10.4", "@babel/template@^7.3.3":
-  version "7.10.4"
-  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278"
-  integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==
+"@babel/plugin-syntax-top-level-await@^7.8.3":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz#dd6c0b357ac1bb142d98537450a319625d13d2a0"
+  integrity sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/runtime-corejs3@^7.10.2":
+  version "7.12.5"
+  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz#ffee91da0eb4c6dae080774e94ba606368e414f4"
+  integrity sha512-roGr54CsTmNPPzZoCP1AmDXuBoNao7tnSA83TXTwt+UK5QVyh1DIJnrgYRPWKCF2flqZQXwa7Yr8v7VmLzF0YQ==
+  dependencies:
+    core-js-pure "^3.0.0"
+    regenerator-runtime "^0.13.4"
+
+"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2":
+  version "7.12.5"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
+  integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
+  dependencies:
+    regenerator-runtime "^0.13.4"
+
+"@babel/template@^7.10.4", "@babel/template@^7.12.7", "@babel/template@^7.3.3":
+  version "7.12.7"
+  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc"
+  integrity sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==
   dependencies:
     "@babel/code-frame" "^7.10.4"
-    "@babel/parser" "^7.10.4"
-    "@babel/types" "^7.10.4"
+    "@babel/parser" "^7.12.7"
+    "@babel/types" "^7.12.7"
 
-"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.11.5":
-  version "7.11.5"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3"
-  integrity sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==
+"@babel/traverse@^7.1.0", "@babel/traverse@^7.12.1", "@babel/traverse@^7.12.10", "@babel/traverse@^7.12.5", "@babel/traverse@^7.7.0":
+  version "7.12.10"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.10.tgz#2d1f4041e8bf42ea099e5b2dc48d6a594c00017a"
+  integrity sha512-6aEtf0IeRgbYWzta29lePeYSk+YAFIC3kyqESeft8o5CkFlYIMX+EQDDWEiAQ9LHOA3d0oHdgrSsID/CKqXJlg==
   dependencies:
     "@babel/code-frame" "^7.10.4"
-    "@babel/generator" "^7.11.5"
+    "@babel/generator" "^7.12.10"
     "@babel/helper-function-name" "^7.10.4"
     "@babel/helper-split-export-declaration" "^7.11.0"
-    "@babel/parser" "^7.11.5"
-    "@babel/types" "^7.11.5"
+    "@babel/parser" "^7.12.10"
+    "@babel/types" "^7.12.10"
     debug "^4.1.0"
     globals "^11.1.0"
     lodash "^4.17.19"
 
-"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3":
-  version "7.11.5"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d"
-  integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==
+"@babel/types@^7.0.0", "@babel/types@^7.12.1", "@babel/types@^7.12.10", "@babel/types@^7.12.11", "@babel/types@^7.12.5", "@babel/types@^7.12.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.7.0":
+  version "7.12.11"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.11.tgz#a86e4d71e30a9b6ee102590446c98662589283ce"
+  integrity sha512-ukA9SQtKThINm++CX1CwmliMrE54J6nIYB5XTwL5f/CLFW9owfls+YSU8tVW15RQ2w+a3fSbPjC6HdQNtWZkiA==
   dependencies:
-    "@babel/helper-validator-identifier" "^7.10.4"
+    "@babel/helper-validator-identifier" "^7.12.11"
     lodash "^4.17.19"
     to-fast-properties "^2.0.0"
 
     exec-sh "^0.3.2"
     minimist "^1.2.0"
 
+"@eslint/eslintrc@^0.2.2":
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.2.tgz#d01fc791e2fc33e88a29d6f3dc7e93d0cd784b76"
+  integrity sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==
+  dependencies:
+    ajv "^6.12.4"
+    debug "^4.1.1"
+    espree "^7.3.0"
+    globals "^12.1.0"
+    ignore "^4.0.6"
+    import-fresh "^3.2.1"
+    js-yaml "^3.13.1"
+    lodash "^4.17.19"
+    minimatch "^3.0.4"
+    strip-json-comments "^3.1.1"
+
 "@istanbuljs/load-nyc-config@^1.0.0":
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
   resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
   integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
 
-"@jest/console@^26.3.0":
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.3.0.tgz#ed04063efb280c88ba87388b6f16427c0a85c856"
-  integrity sha512-/5Pn6sJev0nPUcAdpJHMVIsA8sKizL2ZkcKPE5+dJrCccks7tcM7c9wbgHudBJbxXLoTbqsHkG1Dofoem4F09w==
+"@jest/console@^26.6.2":
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2"
+  integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.6.2"
     "@types/node" "*"
     chalk "^4.0.0"
-    jest-message-util "^26.3.0"
-    jest-util "^26.3.0"
+    jest-message-util "^26.6.2"
+    jest-util "^26.6.2"
     slash "^3.0.0"
 
-"@jest/core@^26.4.2":
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.4.2.tgz#85d0894f31ac29b5bab07aa86806d03dd3d33edc"
-  integrity sha512-sDva7YkeNprxJfepOctzS8cAk9TOekldh+5FhVuXS40+94SHbiicRO1VV2tSoRtgIo+POs/Cdyf8p76vPTd6dg==
+"@jest/core@^26.6.3":
+  version "26.6.3"
+  resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad"
+  integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==
   dependencies:
-    "@jest/console" "^26.3.0"
-    "@jest/reporters" "^26.4.1"
-    "@jest/test-result" "^26.3.0"
-    "@jest/transform" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/console" "^26.6.2"
+    "@jest/reporters" "^26.6.2"
+    "@jest/test-result" "^26.6.2"
+    "@jest/transform" "^26.6.2"
+    "@jest/types" "^26.6.2"
     "@types/node" "*"
     ansi-escapes "^4.2.1"
     chalk "^4.0.0"
     exit "^0.1.2"
     graceful-fs "^4.2.4"
-    jest-changed-files "^26.3.0"
-    jest-config "^26.4.2"
-    jest-haste-map "^26.3.0"
-    jest-message-util "^26.3.0"
+    jest-changed-files "^26.6.2"
+    jest-config "^26.6.3"
+    jest-haste-map "^26.6.2"
+    jest-message-util "^26.6.2"
     jest-regex-util "^26.0.0"
-    jest-resolve "^26.4.0"
-    jest-resolve-dependencies "^26.4.2"
-    jest-runner "^26.4.2"
-    jest-runtime "^26.4.2"
-    jest-snapshot "^26.4.2"
-    jest-util "^26.3.0"
-    jest-validate "^26.4.2"
-    jest-watcher "^26.3.0"
+    jest-resolve "^26.6.2"
+    jest-resolve-dependencies "^26.6.3"
+    jest-runner "^26.6.3"
+    jest-runtime "^26.6.3"
+    jest-snapshot "^26.6.2"
+    jest-util "^26.6.2"
+    jest-validate "^26.6.2"
+    jest-watcher "^26.6.2"
     micromatch "^4.0.2"
     p-each-series "^2.1.0"
     rimraf "^3.0.0"
     slash "^3.0.0"
     strip-ansi "^6.0.0"
 
-"@jest/environment@^26.3.0":
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.3.0.tgz#e6953ab711ae3e44754a025f838bde1a7fd236a0"
-  integrity sha512-EW+MFEo0DGHahf83RAaiqQx688qpXgl99wdb8Fy67ybyzHwR1a58LHcO376xQJHfmoXTu89M09dH3J509cx2AA==
+"@jest/environment@^26.6.2":
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c"
+  integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==
   dependencies:
-    "@jest/fake-timers" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/fake-timers" "^26.6.2"
+    "@jest/types" "^26.6.2"
     "@types/node" "*"
-    jest-mock "^26.3.0"
+    jest-mock "^26.6.2"
 
-"@jest/fake-timers@^26.3.0":
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.3.0.tgz#f515d4667a6770f60ae06ae050f4e001126c666a"
-  integrity sha512-ZL9ytUiRwVP8ujfRepffokBvD2KbxbqMhrXSBhSdAhISCw3gOkuntisiSFv+A6HN0n0fF4cxzICEKZENLmW+1A==
+"@jest/fake-timers@^26.6.2":
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad"
+  integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.6.2"
     "@sinonjs/fake-timers" "^6.0.1"
     "@types/node" "*"
-    jest-message-util "^26.3.0"
-    jest-mock "^26.3.0"
-    jest-util "^26.3.0"
+    jest-message-util "^26.6.2"
+    jest-mock "^26.6.2"
+    jest-util "^26.6.2"
 
-"@jest/globals@^26.4.2":
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.4.2.tgz#73c2a862ac691d998889a241beb3dc9cada40d4a"
-  integrity sha512-Ot5ouAlehhHLRhc+sDz2/9bmNv9p5ZWZ9LE1pXGGTCXBasmi5jnYjlgYcYt03FBwLmZXCZ7GrL29c33/XRQiow==
+"@jest/globals@^26.6.2":
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a"
+  integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==
   dependencies:
-    "@jest/environment" "^26.3.0"
-    "@jest/types" "^26.3.0"
-    expect "^26.4.2"
+    "@jest/environment" "^26.6.2"
+    "@jest/types" "^26.6.2"
+    expect "^26.6.2"
 
-"@jest/reporters@^26.4.1":
-  version "26.4.1"
-  resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.4.1.tgz#3b4d6faf28650f3965f8b97bc3d114077fb71795"
-  integrity sha512-aROTkCLU8++yiRGVxLsuDmZsQEKO6LprlrxtAuzvtpbIFl3eIjgIf3EUxDKgomkS25R9ZzwGEdB5weCcBZlrpQ==
+"@jest/reporters@^26.6.2":
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6"
+  integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==
   dependencies:
     "@bcoe/v8-coverage" "^0.2.3"
-    "@jest/console" "^26.3.0"
-    "@jest/test-result" "^26.3.0"
-    "@jest/transform" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/console" "^26.6.2"
+    "@jest/test-result" "^26.6.2"
+    "@jest/transform" "^26.6.2"
+    "@jest/types" "^26.6.2"
     chalk "^4.0.0"
     collect-v8-coverage "^1.0.0"
     exit "^0.1.2"
     istanbul-lib-report "^3.0.0"
     istanbul-lib-source-maps "^4.0.0"
     istanbul-reports "^3.0.2"
-    jest-haste-map "^26.3.0"
-    jest-resolve "^26.4.0"
-    jest-util "^26.3.0"
-    jest-worker "^26.3.0"
+    jest-haste-map "^26.6.2"
+    jest-resolve "^26.6.2"
+    jest-util "^26.6.2"
+    jest-worker "^26.6.2"
     slash "^3.0.0"
     source-map "^0.6.0"
     string-length "^4.0.1"
     terminal-link "^2.0.0"
-    v8-to-istanbul "^5.0.1"
+    v8-to-istanbul "^7.0.0"
   optionalDependencies:
     node-notifier "^8.0.0"
 
-"@jest/source-map@^26.3.0":
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.3.0.tgz#0e646e519883c14c551f7b5ae4ff5f1bfe4fc3d9"
-  integrity sha512-hWX5IHmMDWe1kyrKl7IhFwqOuAreIwHhbe44+XH2ZRHjrKIh0LO5eLQ/vxHFeAfRwJapmxuqlGAEYLadDq6ZGQ==
+"@jest/source-map@^26.6.2":
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535"
+  integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==
   dependencies:
     callsites "^3.0.0"
     graceful-fs "^4.2.4"
     source-map "^0.6.0"
 
-"@jest/test-result@^26.3.0":
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.3.0.tgz#46cde01fa10c0aaeb7431bf71e4a20d885bc7fdb"
-  integrity sha512-a8rbLqzW/q7HWheFVMtghXV79Xk+GWwOK1FrtimpI5n1la2SY0qHri3/b0/1F0Ve0/yJmV8pEhxDfVwiUBGtgg==
+"@jest/test-result@^26.6.2":
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18"
+  integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==
   dependencies:
-    "@jest/console" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/console" "^26.6.2"
+    "@jest/types" "^26.6.2"
     "@types/istanbul-lib-coverage" "^2.0.0"
     collect-v8-coverage "^1.0.0"
 
-"@jest/test-sequencer@^26.4.2":
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.4.2.tgz#58a3760a61eec758a2ce6080201424580d97cbba"
-  integrity sha512-83DRD8N3M0tOhz9h0bn6Kl6dSp+US6DazuVF8J9m21WAp5x7CqSMaNycMP0aemC/SH/pDQQddbsfHRTBXVUgog==
+"@jest/test-sequencer@^26.6.3":
+  version "26.6.3"
+  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17"
+  integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==
   dependencies:
-    "@jest/test-result" "^26.3.0"
+    "@jest/test-result" "^26.6.2"
     graceful-fs "^4.2.4"
-    jest-haste-map "^26.3.0"
-    jest-runner "^26.4.2"
-    jest-runtime "^26.4.2"
+    jest-haste-map "^26.6.2"
+    jest-runner "^26.6.3"
+    jest-runtime "^26.6.3"
 
-"@jest/transform@^26.3.0":
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.3.0.tgz#c393e0e01459da8a8bfc6d2a7c2ece1a13e8ba55"
-  integrity sha512-Isj6NB68QorGoFWvcOjlUhpkT56PqNIsXKR7XfvoDlCANn/IANlh8DrKAA2l2JKC3yWSMH5wS0GwuQM20w3b2A==
+"@jest/transform@^26.6.2":
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b"
+  integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==
   dependencies:
     "@babel/core" "^7.1.0"
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.6.2"
     babel-plugin-istanbul "^6.0.0"
     chalk "^4.0.0"
     convert-source-map "^1.4.0"
     fast-json-stable-stringify "^2.0.0"
     graceful-fs "^4.2.4"
-    jest-haste-map "^26.3.0"
+    jest-haste-map "^26.6.2"
     jest-regex-util "^26.0.0"
-    jest-util "^26.3.0"
+    jest-util "^26.6.2"
     micromatch "^4.0.2"
     pirates "^4.0.1"
     slash "^3.0.0"
     source-map "^0.6.1"
     write-file-atomic "^3.0.0"
 
-"@jest/types@^25.5.0":
-  version "25.5.0"
-  resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
-  integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==
-  dependencies:
-    "@types/istanbul-lib-coverage" "^2.0.0"
-    "@types/istanbul-reports" "^1.1.1"
-    "@types/yargs" "^15.0.0"
-    chalk "^3.0.0"
-
-"@jest/types@^26.3.0":
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.3.0.tgz#97627bf4bdb72c55346eef98e3b3f7ddc4941f71"
-  integrity sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==
+"@jest/types@^26.6.2":
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
+  integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==
   dependencies:
     "@types/istanbul-lib-coverage" "^2.0.0"
     "@types/istanbul-reports" "^3.0.0"
     "@types/yargs" "^15.0.0"
     chalk "^4.0.0"
 
+"@nodelib/fs.scandir@2.1.3":
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
+  integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==
+  dependencies:
+    "@nodelib/fs.stat" "2.0.3"
+    run-parallel "^1.1.9"
+
+"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2":
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3"
+  integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==
+
+"@nodelib/fs.walk@^1.2.3":
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976"
+  integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==
+  dependencies:
+    "@nodelib/fs.scandir" "2.1.3"
+    fastq "^1.6.0"
+
 "@sinonjs/commons@^1.7.0":
   version "1.8.1"
   resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
     "@sinonjs/commons" "^1.7.0"
 
 "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
-  version "7.1.9"
-  resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d"
-  integrity sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==
+  version "7.1.12"
+  resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.12.tgz#4d8e9e51eb265552a7e4f1ff2219ab6133bdfb2d"
+  integrity sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ==
   dependencies:
     "@babel/parser" "^7.1.0"
     "@babel/types" "^7.0.0"
     "@types/babel__traverse" "*"
 
 "@types/babel__generator@*":
-  version "7.6.1"
-  resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04"
-  integrity sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==
+  version "7.6.2"
+  resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.2.tgz#f3d71178e187858f7c45e30380f8f1b7415a12d8"
+  integrity sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==
   dependencies:
     "@babel/types" "^7.0.0"
 
 "@types/babel__template@*":
-  version "7.0.2"
-  resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307"
-  integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==
+  version "7.4.0"
+  resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.0.tgz#0c888dd70b3ee9eebb6e4f200e809da0076262be"
+  integrity sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==
   dependencies:
     "@babel/parser" "^7.1.0"
     "@babel/types" "^7.0.0"
 
-"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6":
-  version "7.0.14"
-  resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.14.tgz#e99da8c075d4fb098c774ba65dabf7dc9954bd13"
-  integrity sha512-8w9szzKs14ZtBVuP6Wn7nMLRJ0D6dfB0VEBEyRgxrZ/Ln49aNMykrghM2FaNn4FJRzNppCSa0Rv9pBRM5Xc3wg==
+"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6":
+  version "7.11.0"
+  resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.11.0.tgz#b9a1efa635201ba9bc850323a8793ee2d36c04a0"
+  integrity sha512-kSjgDMZONiIfSH1Nxcr5JIRMwUetDki63FSQfpTCz8ogF3Ulqm8+mr5f78dUYs6vMiB6gBusQqfQmBvHZj/lwg==
   dependencies:
     "@babel/types" "^7.3.0"
 
-"@types/color-name@^1.1.1":
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
-  integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
-
 "@types/graceful-fs@^4.1.2":
-  version "4.1.3"
-  resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.3.tgz#039af35fe26bec35003e8d86d2ee9c586354348f"
-  integrity sha512-AiHRaEB50LQg0pZmm659vNBb9f4SJ0qrAnteuzhSeAUcJKxoYgEnprg/83kppCnc2zvtCKbdZry1a5pVY3lOTQ==
+  version "4.1.4"
+  resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.4.tgz#4ff9f641a7c6d1a3508ff88bc3141b152772e753"
+  integrity sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==
   dependencies:
     "@types/node" "*"
 
   dependencies:
     "@types/istanbul-lib-coverage" "*"
 
-"@types/istanbul-reports@^1.1.1":
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz#e875cc689e47bce549ec81f3df5e6f6f11cfaeb2"
-  integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==
-  dependencies:
-    "@types/istanbul-lib-coverage" "*"
-    "@types/istanbul-lib-report" "*"
-
 "@types/istanbul-reports@^3.0.0":
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821"
   dependencies:
     "@types/istanbul-lib-report" "*"
 
-"@types/jest@26.x":
-  version "26.0.13"
-  resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.13.tgz#5a7b9d5312f5dd521a38329c38ee9d3802a0b85e"
-  integrity sha512-sCzjKow4z9LILc6DhBvn5AkIfmQzDZkgtVVKmGwVrs5tuid38ws281D4l+7x1kP487+FlKDh5kfMZ8WSPAdmdA==
+"@types/jest@26.x", "@types/jest@^26.0.19":
+  version "26.0.19"
+  resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.19.tgz#e6fa1e3def5842ec85045bd5210e9bb8289de790"
+  integrity sha512-jqHoirTG61fee6v6rwbnEuKhpSKih0tuhqeFbCmMmErhtu3BYlOZaXWjffgOstMM4S/3iQD31lI5bGLTrs97yQ==
   dependencies:
-    jest-diff "^25.2.1"
-    pretty-format "^25.2.1"
+    jest-diff "^26.0.0"
+    pretty-format "^26.0.0"
 
-"@types/jest@^26.0.14":
-  version "26.0.14"
-  resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.14.tgz#078695f8f65cb55c5a98450d65083b2b73e5a3f3"
-  integrity sha512-Hz5q8Vu0D288x3iWXePSn53W7hAjP0H7EQ6QvDO9c7t46mR0lNOLlfuwQ+JkVxuhygHzlzPX+0jKdA3ZgSh+Vg==
-  dependencies:
-    jest-diff "^25.2.1"
-    pretty-format "^25.2.1"
+"@types/json-schema@^7.0.3":
+  version "7.0.6"
+  resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
+  integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
+
+"@types/json5@^0.0.29":
+  version "0.0.29"
+  resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
+  integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
 
 "@types/node@*":
-  version "14.10.1"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.10.1.tgz#cc323bad8e8a533d4822f45ce4e5326f36e42177"
-  integrity sha512-aYNbO+FZ/3KGeQCEkNhHFRIzBOUgc7QvcVNKXbfnhDkSfwUv91JsQQa10rDgKSTSLkXZ1UIyPe4FJJNVgw1xWQ==
+  version "14.14.14"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae"
+  integrity sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==
 
 "@types/normalize-package-data@^2.4.0":
   version "2.4.0"
   integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
 
 "@types/prettier@^2.0.0":
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.0.tgz#5f96562c1075ee715a5b138f0b7f591c1f40f6b8"
-  integrity sha512-hiYA88aHiEIgDmeKlsyVsuQdcFn3Z2VuFd/Xm/HCnGnPD8UFU5BM128uzzRVVGEzKDKYUrRsRH9S2o+NUy/3IA==
+  version "2.1.5"
+  resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.5.tgz#b6ab3bba29e16b821d84e09ecfaded462b816b00"
+  integrity sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ==
 
-"@types/stack-utils@^1.0.1":
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
-  integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
+"@types/stack-utils@^2.0.0":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"
+  integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==
 
 "@types/yargs-parser@*":
   version "15.0.0"
   integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
 
 "@types/yargs@^15.0.0":
-  version "15.0.5"
-  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79"
-  integrity sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==
+  version "15.0.12"
+  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.12.tgz#6234ce3e3e3fa32c5db301a170f96a599c960d74"
+  integrity sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==
   dependencies:
     "@types/yargs-parser" "*"
 
+"@typescript-eslint/eslint-plugin@4.9.1":
+  version "4.9.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.9.1.tgz#66758cbe129b965fe9c63b04b405d0cf5280868b"
+  integrity sha512-QRLDSvIPeI1pz5tVuurD+cStNR4sle4avtHhxA+2uyixWGFjKzJ+EaFVRW6dA/jOgjV5DTAjOxboQkRDE8cRlQ==
+  dependencies:
+    "@typescript-eslint/experimental-utils" "4.9.1"
+    "@typescript-eslint/scope-manager" "4.9.1"
+    debug "^4.1.1"
+    functional-red-black-tree "^1.0.1"
+    regexpp "^3.0.0"
+    semver "^7.3.2"
+    tsutils "^3.17.1"
+
+"@typescript-eslint/experimental-utils@4.9.1":
+  version "4.9.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.9.1.tgz#86633e8395191d65786a808dc3df030a55267ae2"
+  integrity sha512-c3k/xJqk0exLFs+cWSJxIjqLYwdHCuLWhnpnikmPQD2+NGAx9KjLYlBDcSI81EArh9FDYSL6dslAUSwILeWOxg==
+  dependencies:
+    "@types/json-schema" "^7.0.3"
+    "@typescript-eslint/scope-manager" "4.9.1"
+    "@typescript-eslint/types" "4.9.1"
+    "@typescript-eslint/typescript-estree" "4.9.1"
+    eslint-scope "^5.0.0"
+    eslint-utils "^2.0.0"
+
+"@typescript-eslint/experimental-utils@^4.0.1":
+  version "4.10.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.10.0.tgz#dbf5d0f89802d5feaf7d11e5b32df29bbc2f3a0e"
+  integrity sha512-opX+7ai1sdWBOIoBgpVJrH5e89ra1KoLrJTz0UtWAa4IekkKmqDosk5r6xqRaNJfCXEfteW4HXQAwMdx+jjEmw==
+  dependencies:
+    "@types/json-schema" "^7.0.3"
+    "@typescript-eslint/scope-manager" "4.10.0"
+    "@typescript-eslint/types" "4.10.0"
+    "@typescript-eslint/typescript-estree" "4.10.0"
+    eslint-scope "^5.0.0"
+    eslint-utils "^2.0.0"
+
+"@typescript-eslint/parser@4.9.1":
+  version "4.9.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.9.1.tgz#2d74c4db5dd5117379a9659081a4d1ec02629055"
+  integrity sha512-Gv2VpqiomvQ2v4UL+dXlQcZ8zCX4eTkoIW+1aGVWT6yTO+6jbxsw7yQl2z2pPl/4B9qa5JXeIbhJpONKjXIy3g==
+  dependencies:
+    "@typescript-eslint/scope-manager" "4.9.1"
+    "@typescript-eslint/types" "4.9.1"
+    "@typescript-eslint/typescript-estree" "4.9.1"
+    debug "^4.1.1"
+
+"@typescript-eslint/scope-manager@4.10.0":
+  version "4.10.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.10.0.tgz#dbd7e1fc63d7363e3aaff742a6f2b8afdbac9d27"
+  integrity sha512-WAPVw35P+fcnOa8DEic0tQUhoJJsgt+g6DEcz257G7vHFMwmag58EfowdVbiNcdfcV27EFR0tUBVXkDoIvfisQ==
+  dependencies:
+    "@typescript-eslint/types" "4.10.0"
+    "@typescript-eslint/visitor-keys" "4.10.0"
+
+"@typescript-eslint/scope-manager@4.9.1":
+  version "4.9.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.9.1.tgz#cc2fde310b3f3deafe8436a924e784eaab265103"
+  integrity sha512-sa4L9yUfD/1sg9Kl8OxPxvpUcqxKXRjBeZxBuZSSV1v13hjfEJkn84n0An2hN8oLQ1PmEl2uA6FkI07idXeFgQ==
+  dependencies:
+    "@typescript-eslint/types" "4.9.1"
+    "@typescript-eslint/visitor-keys" "4.9.1"
+
+"@typescript-eslint/types@4.10.0":
+  version "4.10.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.10.0.tgz#12f983750ebad867f0c806e705c1953cd6415789"
+  integrity sha512-+dt5w1+Lqyd7wIPMa4XhJxUuE8+YF+vxQ6zxHyhLGHJjHiunPf0wSV8LtQwkpmAsRi1lEOoOIR30FG5S2HS33g==
+
+"@typescript-eslint/types@4.9.1":
+  version "4.9.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.9.1.tgz#a1a7dd80e4e5ac2c593bc458d75dd1edaf77faa2"
+  integrity sha512-fjkT+tXR13ks6Le7JiEdagnwEFc49IkOyys7ueWQ4O8k4quKPwPJudrwlVOJCUQhXo45PrfIvIarcrEjFTNwUA==
+
+"@typescript-eslint/typescript-estree@4.10.0":
+  version "4.10.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.10.0.tgz#1e62e45fd57866afd42daf5e9fb6bd4e8dbcfa75"
+  integrity sha512-mGK0YRp9TOk6ZqZ98F++bW6X5kMTzCRROJkGXH62d2azhghmq+1LNLylkGe6uGUOQzD452NOAEth5VAF6PDo5g==
+  dependencies:
+    "@typescript-eslint/types" "4.10.0"
+    "@typescript-eslint/visitor-keys" "4.10.0"
+    debug "^4.1.1"
+    globby "^11.0.1"
+    is-glob "^4.0.1"
+    lodash "^4.17.15"
+    semver "^7.3.2"
+    tsutils "^3.17.1"
+
+"@typescript-eslint/typescript-estree@4.9.1":
+  version "4.9.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.9.1.tgz#6e5b86ff5a5f66809e1f347469fadeec69ac50bf"
+  integrity sha512-bzP8vqwX6Vgmvs81bPtCkLtM/Skh36NE6unu6tsDeU/ZFoYthlTXbBmpIrvosgiDKlWTfb2ZpPELHH89aQjeQw==
+  dependencies:
+    "@typescript-eslint/types" "4.9.1"
+    "@typescript-eslint/visitor-keys" "4.9.1"
+    debug "^4.1.1"
+    globby "^11.0.1"
+    is-glob "^4.0.1"
+    lodash "^4.17.15"
+    semver "^7.3.2"
+    tsutils "^3.17.1"
+
+"@typescript-eslint/visitor-keys@4.10.0":
+  version "4.10.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.10.0.tgz#9478822329a9bc8ebcc80623d7f79a01da5ee451"
+  integrity sha512-hPyz5qmDMuZWFtHZkjcCpkAKHX8vdu1G3YsCLEd25ryZgnJfj6FQuJ5/O7R+dB1ueszilJmAFMtlU4CA6se3Jg==
+  dependencies:
+    "@typescript-eslint/types" "4.10.0"
+    eslint-visitor-keys "^2.0.0"
+
+"@typescript-eslint/visitor-keys@4.9.1":
+  version "4.9.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.9.1.tgz#d76374a58c4ead9e92b454d186fea63487b25ae1"
+  integrity sha512-9gspzc6UqLQHd7lXQS7oWs+hrYggspv/rk6zzEMhCbYwPE/sF7oxo7GAjkS35Tdlt7wguIG+ViWCPtVZHz/ybQ==
+  dependencies:
+    "@typescript-eslint/types" "4.9.1"
+    eslint-visitor-keys "^2.0.0"
+
 abab@^2.0.3:
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
@@ -615,26 +770,36 @@ acorn-globals@^6.0.0:
     acorn "^7.1.1"
     acorn-walk "^7.1.1"
 
+acorn-jsx@^5.3.1:
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
+  integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
+
 acorn-walk@^7.1.1:
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
   integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
 
-acorn@^7.1.1:
-  version "7.4.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c"
-  integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==
+acorn@^7.1.1, acorn@^7.4.0:
+  version "7.4.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
+  integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
 
-ajv@^6.12.3:
-  version "6.12.4"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.4.tgz#0614facc4522127fa713445c6bfd3ebd376e2234"
-  integrity sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==
+ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4:
+  version "6.12.6"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+  integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
   dependencies:
     fast-deep-equal "^3.1.1"
     fast-json-stable-stringify "^2.0.0"
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
+ansi-colors@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
+  integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
+
 ansi-escapes@^4.2.1:
   version "4.3.1"
   resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
@@ -655,11 +820,10 @@ ansi-styles@^3.2.1:
     color-convert "^1.9.0"
 
 ansi-styles@^4.0.0, ansi-styles@^4.1.0:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
-  integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+  integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
   dependencies:
-    "@types/color-name" "^1.1.1"
     color-convert "^2.0.1"
 
 anymatch@^2.0.0:
@@ -685,6 +849,14 @@ argparse@^1.0.7:
   dependencies:
     sprintf-js "~1.0.2"
 
+aria-query@^4.2.2:
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
+  integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==
+  dependencies:
+    "@babel/runtime" "^7.10.2"
+    "@babel/runtime-corejs3" "^7.10.2"
+
 arr-diff@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
@@ -700,11 +872,46 @@ arr-union@^3.1.0:
   resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
   integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
 
+array-includes@^3.1.1, array-includes@^3.1.2:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.2.tgz#a8db03e0b88c8c6aeddc49cb132f9bcab4ebf9c8"
+  integrity sha512-w2GspexNQpx+PutG3QpT437/BenZBj0M/MZGn5mzv/MofYqo0xmRHzn4lFsoDlWJ+THYsGJmFlW68WlDFx7VRw==
+  dependencies:
+    call-bind "^1.0.0"
+    define-properties "^1.1.3"
+    es-abstract "^1.18.0-next.1"
+    get-intrinsic "^1.0.1"
+    is-string "^1.0.5"
+
+array-union@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
+  integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
+
 array-unique@^0.3.2:
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
   integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
 
+array.prototype.flat@^1.2.3:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123"
+  integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==
+  dependencies:
+    call-bind "^1.0.0"
+    define-properties "^1.1.3"
+    es-abstract "^1.18.0-next.1"
+
+array.prototype.flatmap@^1.2.3:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9"
+  integrity sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q==
+  dependencies:
+    call-bind "^1.0.0"
+    define-properties "^1.1.3"
+    es-abstract "^1.18.0-next.1"
+    function-bind "^1.1.1"
+
 asn1@~0.2.3:
   version "0.2.4"
   resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
@@ -722,6 +929,16 @@ assign-symbols@^1.0.0:
   resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
   integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
 
+ast-types-flow@^0.0.7:
+  version "0.0.7"
+  resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
+  integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0=
+
+astral-regex@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
+  integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
+
 asynckit@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -738,20 +955,42 @@ aws-sign2@~0.7.0:
   integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
 
 aws4@^1.8.0:
-  version "1.10.1"
-  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428"
-  integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==
+  version "1.11.0"
+  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
+  integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
 
-babel-jest@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.3.0.tgz#10d0ca4b529ca3e7d1417855ef7d7bd6fc0c3463"
-  integrity sha512-sxPnQGEyHAOPF8NcUsD0g7hDCnvLL2XyblRBcgrzTWBB/mAIpWow3n1bEL+VghnnZfreLhFSBsFluRoK2tRK4g==
+axe-core@^4.0.2:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.1.tgz#70a7855888e287f7add66002211a423937063eaf"
+  integrity sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ==
+
+axobject-query@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
+  integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==
+
+babel-eslint@10.1.0, babel-eslint@^10.1.0:
+  version "10.1.0"
+  resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232"
+  integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==
   dependencies:
-    "@jest/transform" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@babel/code-frame" "^7.0.0"
+    "@babel/parser" "^7.7.0"
+    "@babel/traverse" "^7.7.0"
+    "@babel/types" "^7.7.0"
+    eslint-visitor-keys "^1.0.0"
+    resolve "^1.12.0"
+
+babel-jest@^26.6.3:
+  version "26.6.3"
+  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056"
+  integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==
+  dependencies:
+    "@jest/transform" "^26.6.2"
+    "@jest/types" "^26.6.2"
     "@types/babel__core" "^7.1.7"
     babel-plugin-istanbul "^6.0.0"
-    babel-preset-jest "^26.3.0"
+    babel-preset-jest "^26.6.2"
     chalk "^4.0.0"
     graceful-fs "^4.2.4"
     slash "^3.0.0"
@@ -767,20 +1006,20 @@ babel-plugin-istanbul@^6.0.0:
     istanbul-lib-instrument "^4.0.0"
     test-exclude "^6.0.0"
 
-babel-plugin-jest-hoist@^26.2.0:
-  version "26.2.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.2.0.tgz#bdd0011df0d3d513e5e95f76bd53b51147aca2dd"
-  integrity sha512-B/hVMRv8Nh1sQ1a3EY8I0n4Y1Wty3NrR5ebOyVT302op+DOAau+xNEImGMsUWOC3++ZlMooCytKz+NgN8aKGbA==
+babel-plugin-jest-hoist@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d"
+  integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==
   dependencies:
     "@babel/template" "^7.3.3"
     "@babel/types" "^7.3.3"
     "@types/babel__core" "^7.0.0"
     "@types/babel__traverse" "^7.0.6"
 
-babel-preset-current-node-syntax@^0.1.3:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.3.tgz#b4b547acddbf963cba555ba9f9cbbb70bfd044da"
-  integrity sha512-uyexu1sVwcdFnyq9o8UQYsXwXflIh8LvrF5+cKrYam93ned1CStffB3+BEcsxGSgagoA3GEyjDqO4a/58hyPYQ==
+babel-preset-current-node-syntax@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b"
+  integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==
   dependencies:
     "@babel/plugin-syntax-async-generators" "^7.8.4"
     "@babel/plugin-syntax-bigint" "^7.8.3"
@@ -793,14 +1032,15 @@ babel-preset-current-node-syntax@^0.1.3:
     "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
     "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
     "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+    "@babel/plugin-syntax-top-level-await" "^7.8.3"
 
-babel-preset-jest@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.3.0.tgz#ed6344506225c065fd8a0b53e191986f74890776"
-  integrity sha512-5WPdf7nyYi2/eRxCbVrE1kKCWxgWY4RsPEbdJWFm7QsesFGqjdkyLeu1zRkwM1cxK6EPIlNd6d2AxLk7J+t4pw==
+babel-preset-jest@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee"
+  integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==
   dependencies:
-    babel-plugin-jest-hoist "^26.2.0"
-    babel-preset-current-node-syntax "^0.1.3"
+    babel-plugin-jest-hoist "^26.6.2"
+    babel-preset-current-node-syntax "^1.0.0"
 
 balanced-match@^1.0.0:
   version "1.0.0"
@@ -897,6 +1137,14 @@ cache-base@^1.0.1:
     union-value "^1.0.0"
     unset-value "^1.0.0"
 
+call-bind@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce"
+  integrity sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==
+  dependencies:
+    function-bind "^1.1.1"
+    get-intrinsic "^1.0.0"
+
 callsites@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@@ -908,9 +1156,9 @@ camelcase@^5.0.0, camelcase@^5.3.1:
   integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
 
 camelcase@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e"
-  integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
+  integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
 
 capture-exit@^2.0.0:
   version "2.0.0"
@@ -933,14 +1181,6 @@ chalk@^2.0.0:
     escape-string-regexp "^1.0.5"
     supports-color "^5.3.0"
 
-chalk@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
-  integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
-  dependencies:
-    ansi-styles "^4.1.0"
-    supports-color "^7.1.0"
-
 chalk@^4.0.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
@@ -959,6 +1199,11 @@ ci-info@^2.0.0:
   resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
   integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
 
+cjs-module-lexer@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f"
+  integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==
+
 class-utils@^0.3.5:
   version "0.3.6"
   resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
@@ -969,6 +1214,13 @@ class-utils@^0.3.5:
     isobject "^3.0.0"
     static-extend "^0.1.1"
 
+clean-regexp@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7"
+  integrity sha1-jffHquUf02h06PjQW5GAvBGj/tc=
+  dependencies:
+    escape-string-regexp "^1.0.5"
+
 cliui@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
@@ -1037,6 +1289,11 @@ concat-map@0.0.1:
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
   integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
 
+contains-path@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
+  integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=
+
 convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
@@ -1049,6 +1306,11 @@ copy-descriptor@^0.1.0:
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
   integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
 
+core-js-pure@^3.0.0:
+  version "3.8.1"
+  resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.8.1.tgz#23f84048f366fdfcf52d3fd1c68fec349177d119"
+  integrity sha512-Se+LaxqXlVXGvmexKGPvnUIYC1jwXu1H6Pkyb3uBM5d8/NELMYCHs/4/roD7721NxrTLyv7e5nXd5/QLBO+10g==
+
 core-util-is@1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -1065,7 +1327,7 @@ cross-spawn@^6.0.0:
     shebang-command "^1.2.0"
     which "^1.2.9"
 
-cross-spawn@^7.0.0:
+cross-spawn@^7.0.0, cross-spawn@^7.0.2:
   version "7.0.3"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
   integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@@ -1091,6 +1353,11 @@ cssstyle@^2.2.0:
   dependencies:
     cssom "~0.3.6"
 
+damerau-levenshtein@^1.0.6:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791"
+  integrity sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==
+
 dashdash@^1.12.0:
   version "1.14.1"
   resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@@ -1107,19 +1374,19 @@ data-urls@^2.0.0:
     whatwg-mimetype "^2.3.0"
     whatwg-url "^8.0.0"
 
-debug@^2.2.0, debug@^2.3.3:
+debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
   dependencies:
     ms "2.0.0"
 
-debug@^4.1.0, debug@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
-  integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
+debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
+  integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
   dependencies:
-    ms "^2.1.1"
+    ms "2.1.2"
 
 decamelize@^1.2.0:
   version "1.2.0"
@@ -1127,16 +1394,16 @@ decamelize@^1.2.0:
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
 
 decimal.js@^10.2.0:
-  version "10.2.0"
-  resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231"
-  integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==
+  version "10.2.1"
+  resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3"
+  integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==
 
 decode-uri-component@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
   integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
 
-deep-is@~0.1.3:
+deep-is@^0.1.3, deep-is@~0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
   integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
@@ -1146,6 +1413,13 @@ deepmerge@^4.2.2:
   resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
   integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
 
+define-properties@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
+  integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
+  dependencies:
+    object-keys "^1.0.12"
+
 define-property@^0.2.5:
   version "0.2.5"
   resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
@@ -1178,15 +1452,39 @@ detect-newline@^3.0.0:
   resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
   integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
 
-diff-sequences@^25.2.6:
-  version "25.2.6"
-  resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
-  integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==
+diff-sequences@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
+  integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==
 
-diff-sequences@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.3.0.tgz#62a59b1b29ab7fd27cef2a33ae52abe73042d0a2"
-  integrity sha512-5j5vdRcw3CNctePNYN0Wy2e/JbWT6cAYnXv5OuqPhDpyCGc0uLu2TK0zOCJWNB9kOIfYMSpIulRaDgIi4HJ6Ig==
+dir-glob@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
+  integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
+  dependencies:
+    path-type "^4.0.0"
+
+doctrine@1.5.0:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
+  integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=
+  dependencies:
+    esutils "^2.0.2"
+    isarray "^1.0.0"
+
+doctrine@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
+  integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==
+  dependencies:
+    esutils "^2.0.2"
+
+doctrine@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
+  integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
+  dependencies:
+    esutils "^2.0.2"
 
 domexception@^2.0.1:
   version "2.0.1"
@@ -1204,15 +1502,20 @@ ecc-jsbn@~0.1.1:
     safer-buffer "^2.1.0"
 
 emittery@^0.7.1:
-  version "0.7.1"
-  resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.1.tgz#c02375a927a40948c0345cc903072597f5270451"
-  integrity sha512-d34LN4L6h18Bzz9xpoku2nPwKxCPlPMr3EEKTkoEBi+1/+b0lcRkRJ1UVyyZaKNeqGR3swcGl6s390DNO4YVgQ==
+  version "0.7.2"
+  resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82"
+  integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==
 
 emoji-regex@^8.0.0:
   version "8.0.0"
   resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
   integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
 
+emoji-regex@^9.0.0:
+  version "9.2.0"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.0.tgz#a26da8e832b16a9753309f25e35e3c0efb9a066a"
+  integrity sha512-DNc3KFPK18bPdElMJnf/Pkv5TXhxFU3YFDEuGLDRtPmV4rkmCjBkCSEp22u6rBHdSN9Vlp/GK7k98prmE1Jgug==
+
 end-of-stream@^1.1.0:
   version "1.4.4"
   resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
@@ -1220,13 +1523,64 @@ end-of-stream@^1.1.0:
   dependencies:
     once "^1.4.0"
 
-error-ex@^1.3.1:
+enquirer@^2.3.5:
+  version "2.3.6"
+  resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
+  integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
+  dependencies:
+    ansi-colors "^4.1.1"
+
+error-ex@^1.2.0, error-ex@^1.3.1:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
   integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
   dependencies:
     is-arrayish "^0.2.1"
 
+es-abstract@^1.17.0-next.1:
+  version "1.17.7"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c"
+  integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==
+  dependencies:
+    es-to-primitive "^1.2.1"
+    function-bind "^1.1.1"
+    has "^1.0.3"
+    has-symbols "^1.0.1"
+    is-callable "^1.2.2"
+    is-regex "^1.1.1"
+    object-inspect "^1.8.0"
+    object-keys "^1.1.1"
+    object.assign "^4.1.1"
+    string.prototype.trimend "^1.0.1"
+    string.prototype.trimstart "^1.0.1"
+
+es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1:
+  version "1.18.0-next.1"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68"
+  integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==
+  dependencies:
+    es-to-primitive "^1.2.1"
+    function-bind "^1.1.1"
+    has "^1.0.3"
+    has-symbols "^1.0.1"
+    is-callable "^1.2.2"
+    is-negative-zero "^2.0.0"
+    is-regex "^1.1.1"
+    object-inspect "^1.8.0"
+    object-keys "^1.1.1"
+    object.assign "^4.1.1"
+    string.prototype.trimend "^1.0.1"
+    string.prototype.trimstart "^1.0.1"
+
+es-to-primitive@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
+  integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
+  dependencies:
+    is-callable "^1.1.4"
+    is-date-object "^1.0.1"
+    is-symbol "^1.0.2"
+
 escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@@ -1249,16 +1603,299 @@ escodegen@^1.14.1:
   optionalDependencies:
     source-map "~0.6.1"
 
+eslint-ast-utils@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-ast-utils/-/eslint-ast-utils-1.1.0.tgz#3d58ba557801cfb1c941d68131ee9f8c34bd1586"
+  integrity sha512-otzzTim2/1+lVrlH19EfQQJEhVJSu0zOb9ygb3iapN6UlyaDtyRq4b5U1FuW0v1lRa9Fp/GJyHkSwm6NqABgCA==
+  dependencies:
+    lodash.get "^4.4.2"
+    lodash.zip "^4.2.0"
+
+eslint-config-prettier@7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-7.0.0.tgz#c1ae4106f74e6c0357f44adb076771d032ac0e97"
+  integrity sha512-8Y8lGLVPPZdaNA7JXqnvETVC7IiVRgAP6afQu9gOQRn90YY3otMNh+x7Vr2vMePQntF+5erdSUBqSzCmU/AxaQ==
+
+eslint-import-resolver-node@^0.3.4:
+  version "0.3.4"
+  resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717"
+  integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==
+  dependencies:
+    debug "^2.6.9"
+    resolve "^1.13.1"
+
+eslint-module-utils@^2.6.0:
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6"
+  integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==
+  dependencies:
+    debug "^2.6.9"
+    pkg-dir "^2.0.0"
+
+eslint-plugin-babel@5.3.1:
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz#75a2413ffbf17e7be57458301c60291f2cfbf560"
+  integrity sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g==
+  dependencies:
+    eslint-rule-composer "^0.3.0"
+
+eslint-plugin-es@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893"
+  integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==
+  dependencies:
+    eslint-utils "^2.0.0"
+    regexpp "^3.0.0"
+
+eslint-plugin-import@2.22.1:
+  version "2.22.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702"
+  integrity sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==
+  dependencies:
+    array-includes "^3.1.1"
+    array.prototype.flat "^1.2.3"
+    contains-path "^0.1.0"
+    debug "^2.6.9"
+    doctrine "1.5.0"
+    eslint-import-resolver-node "^0.3.4"
+    eslint-module-utils "^2.6.0"
+    has "^1.0.3"
+    minimatch "^3.0.4"
+    object.values "^1.1.1"
+    read-pkg-up "^2.0.0"
+    resolve "^1.17.0"
+    tsconfig-paths "^3.9.0"
+
+eslint-plugin-jane@^9.0.3:
+  version "9.0.5"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jane/-/eslint-plugin-jane-9.0.5.tgz#13e9662a177172db25b84cdf0d31f2a14ac19c39"
+  integrity sha512-wh1fDVzWKlIuP6qQGm3QvRkYChbCNPbaIBie56fz+XbDQcxvULK2hLwnQ3huMwexON1jyTYqVy2hFJ98olMbpw==
+  dependencies:
+    "@typescript-eslint/eslint-plugin" "4.9.1"
+    "@typescript-eslint/parser" "4.9.1"
+    babel-eslint "10.1.0"
+    eslint-config-prettier "7.0.0"
+    eslint-plugin-babel "5.3.1"
+    eslint-plugin-import "2.22.1"
+    eslint-plugin-jest "24.1.3"
+    eslint-plugin-jsx-a11y "6.4.1"
+    eslint-plugin-node "11.1.0"
+    eslint-plugin-prettier "3.2.0"
+    eslint-plugin-promise "4.2.1"
+    eslint-plugin-react "7.21.5"
+    eslint-plugin-react-hooks "4.2.0"
+    eslint-plugin-unicorn "23.0.0"
+
+eslint-plugin-jest@24.1.3:
+  version "24.1.3"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.1.3.tgz#fa3db864f06c5623ff43485ca6c0e8fc5fe8ba0c"
+  integrity sha512-dNGGjzuEzCE3d5EPZQ/QGtmlMotqnYWD/QpCZ1UuZlrMAdhG5rldh0N0haCvhGnUkSeuORS5VNROwF9Hrgn3Lg==
+  dependencies:
+    "@typescript-eslint/experimental-utils" "^4.0.1"
+
+eslint-plugin-jsx-a11y@6.4.1:
+  version "6.4.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz#a2d84caa49756942f42f1ffab9002436391718fd"
+  integrity sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg==
+  dependencies:
+    "@babel/runtime" "^7.11.2"
+    aria-query "^4.2.2"
+    array-includes "^3.1.1"
+    ast-types-flow "^0.0.7"
+    axe-core "^4.0.2"
+    axobject-query "^2.2.0"
+    damerau-levenshtein "^1.0.6"
+    emoji-regex "^9.0.0"
+    has "^1.0.3"
+    jsx-ast-utils "^3.1.0"
+    language-tags "^1.0.5"
+
+eslint-plugin-node@11.1.0:
+  version "11.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d"
+  integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==
+  dependencies:
+    eslint-plugin-es "^3.0.0"
+    eslint-utils "^2.0.0"
+    ignore "^5.1.1"
+    minimatch "^3.0.4"
+    resolve "^1.10.1"
+    semver "^6.1.0"
+
+eslint-plugin-prettier@3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.2.0.tgz#af391b2226fa0e15c96f36c733f6e9035dbd952c"
+  integrity sha512-kOUSJnFjAUFKwVxuzy6sA5yyMx6+o9ino4gCdShzBNx4eyFRudWRYKCFolKjoM40PEiuU6Cn7wBLfq3WsGg7qg==
+  dependencies:
+    prettier-linter-helpers "^1.0.0"
+
+eslint-plugin-promise@4.2.1:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
+  integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
+
+eslint-plugin-react-hooks@4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556"
+  integrity sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==
+
+eslint-plugin-react@7.21.5:
+  version "7.21.5"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz#50b21a412b9574bfe05b21db176e8b7b3b15bff3"
+  integrity sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g==
+  dependencies:
+    array-includes "^3.1.1"
+    array.prototype.flatmap "^1.2.3"
+    doctrine "^2.1.0"
+    has "^1.0.3"
+    jsx-ast-utils "^2.4.1 || ^3.0.0"
+    object.entries "^1.1.2"
+    object.fromentries "^2.0.2"
+    object.values "^1.1.1"
+    prop-types "^15.7.2"
+    resolve "^1.18.1"
+    string.prototype.matchall "^4.0.2"
+
+eslint-plugin-unicorn@23.0.0:
+  version "23.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-23.0.0.tgz#b2820212874735f9d91ecc8678b263ecfa6cf5f6"
+  integrity sha512-Vabo3cjl6cjyhcf+76CdQEY6suOFzK0Xh3xo0uL9VDYrDJP5+B6PjV0tHTYm82WZmFWniugFJM3ywHSNYTi/ZQ==
+  dependencies:
+    ci-info "^2.0.0"
+    clean-regexp "^1.0.0"
+    eslint-ast-utils "^1.1.0"
+    eslint-template-visitor "^2.2.1"
+    eslint-utils "^2.1.0"
+    import-modules "^2.0.0"
+    lodash "^4.17.20"
+    pluralize "^8.0.0"
+    read-pkg-up "^7.0.1"
+    regexp-tree "^0.1.21"
+    reserved-words "^0.1.2"
+    safe-regex "^2.1.1"
+    semver "^7.3.2"
+
+eslint-rule-composer@^0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9"
+  integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==
+
+eslint-scope@^5.0.0, eslint-scope@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
+  integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
+  dependencies:
+    esrecurse "^4.3.0"
+    estraverse "^4.1.1"
+
+eslint-template-visitor@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/eslint-template-visitor/-/eslint-template-visitor-2.2.1.tgz#2dccb1ab28fa7429e56ba6dd0144def2d89bc2d6"
+  integrity sha512-q3SxoBXz0XjPGkUpwGVAwIwIPIxzCAJX1uwfVc8tW3v7u/zS7WXNH3I2Mu2MDz2NgSITAyKLRaQFPHu/iyKxDQ==
+  dependencies:
+    babel-eslint "^10.1.0"
+    eslint-visitor-keys "^1.3.0"
+    esquery "^1.3.1"
+    multimap "^1.1.0"
+
+eslint-utils@^2.0.0, eslint-utils@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
+  integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==
+  dependencies:
+    eslint-visitor-keys "^1.1.0"
+
+eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
+  integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
+
+eslint-visitor-keys@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
+  integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
+
+eslint@^7.10.0:
+  version "7.16.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.16.0.tgz#a761605bf9a7b32d24bb7cde59aeb0fd76f06092"
+  integrity sha512-iVWPS785RuDA4dWuhhgXTNrGxHHK3a8HLSMBgbbU59ruJDubUraXN8N5rn7kb8tG6sjg74eE0RA3YWT51eusEw==
+  dependencies:
+    "@babel/code-frame" "^7.0.0"
+    "@eslint/eslintrc" "^0.2.2"
+    ajv "^6.10.0"
+    chalk "^4.0.0"
+    cross-spawn "^7.0.2"
+    debug "^4.0.1"
+    doctrine "^3.0.0"
+    enquirer "^2.3.5"
+    eslint-scope "^5.1.1"
+    eslint-utils "^2.1.0"
+    eslint-visitor-keys "^2.0.0"
+    espree "^7.3.1"
+    esquery "^1.2.0"
+    esutils "^2.0.2"
+    file-entry-cache "^6.0.0"
+    functional-red-black-tree "^1.0.1"
+    glob-parent "^5.0.0"
+    globals "^12.1.0"
+    ignore "^4.0.6"
+    import-fresh "^3.0.0"
+    imurmurhash "^0.1.4"
+    is-glob "^4.0.0"
+    js-yaml "^3.13.1"
+    json-stable-stringify-without-jsonify "^1.0.1"
+    levn "^0.4.1"
+    lodash "^4.17.19"
+    minimatch "^3.0.4"
+    natural-compare "^1.4.0"
+    optionator "^0.9.1"
+    progress "^2.0.0"
+    regexpp "^3.1.0"
+    semver "^7.2.1"
+    strip-ansi "^6.0.0"
+    strip-json-comments "^3.1.0"
+    table "^6.0.4"
+    text-table "^0.2.0"
+    v8-compile-cache "^2.0.3"
+
+espree@^7.3.0, espree@^7.3.1:
+  version "7.3.1"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6"
+  integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==
+  dependencies:
+    acorn "^7.4.0"
+    acorn-jsx "^5.3.1"
+    eslint-visitor-keys "^1.3.0"
+
 esprima@^4.0.0, esprima@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
   integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
 
-estraverse@^4.2.0:
+esquery@^1.2.0, esquery@^1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57"
+  integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==
+  dependencies:
+    estraverse "^5.1.0"
+
+esrecurse@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+  integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+  dependencies:
+    estraverse "^5.2.0"
+
+estraverse@^4.1.1, estraverse@^4.2.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
   integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
 
+estraverse@^5.1.0, estraverse@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
+  integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
+
 esutils@^2.0.2:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
@@ -1283,9 +1920,9 @@ execa@^1.0.0:
     strip-eof "^1.0.0"
 
 execa@^4.0.0:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2"
-  integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
+  integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
   dependencies:
     cross-spawn "^7.0.0"
     get-stream "^5.0.0"
@@ -1315,16 +1952,16 @@ expand-brackets@^2.1.4:
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
 
-expect@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/expect/-/expect-26.4.2.tgz#36db120928a5a2d7d9736643032de32f24e1b2a1"
-  integrity sha512-IlJ3X52Z0lDHm7gjEp+m76uX46ldH5VpqmU0006vqDju/285twh7zaWMRhs67VpQhBwjjMchk+p5aA0VkERCAA==
+expect@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417"
+  integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.6.2"
     ansi-styles "^4.0.0"
     jest-get-type "^26.3.0"
-    jest-matcher-utils "^26.4.2"
-    jest-message-util "^26.3.0"
+    jest-matcher-utils "^26.6.2"
+    jest-message-util "^26.6.2"
     jest-regex-util "^26.0.0"
 
 extend-shallow@^2.0.1:
@@ -1376,16 +2013,40 @@ fast-deep-equal@^3.1.1:
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
   integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
 
+fast-diff@^1.1.2:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
+  integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
+
+fast-glob@^3.1.1:
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3"
+  integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==
+  dependencies:
+    "@nodelib/fs.stat" "^2.0.2"
+    "@nodelib/fs.walk" "^1.2.3"
+    glob-parent "^5.1.0"
+    merge2 "^1.3.0"
+    micromatch "^4.0.2"
+    picomatch "^2.2.1"
+
 fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
   integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
 
-fast-levenshtein@~2.0.6:
+fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
   integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
 
+fastq@^1.6.0:
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.10.0.tgz#74dbefccade964932cdf500473ef302719c652bb"
+  integrity sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==
+  dependencies:
+    reusify "^1.0.4"
+
 fb-watchman@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85"
@@ -1393,6 +2054,13 @@ fb-watchman@^2.0.0:
   dependencies:
     bser "2.1.1"
 
+file-entry-cache@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.0.tgz#7921a89c391c6d93efec2169ac6bf300c527ea0a"
+  integrity sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==
+  dependencies:
+    flat-cache "^3.0.4"
+
 fill-range@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
@@ -1410,6 +2078,13 @@ fill-range@^7.0.1:
   dependencies:
     to-regex-range "^5.0.1"
 
+find-up@^2.0.0, find-up@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
+  integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
+  dependencies:
+    locate-path "^2.0.0"
+
 find-up@^4.0.0, find-up@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
@@ -1418,6 +2093,19 @@ find-up@^4.0.0, find-up@^4.1.0:
     locate-path "^5.0.0"
     path-exists "^4.0.0"
 
+flat-cache@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
+  integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==
+  dependencies:
+    flatted "^3.1.0"
+    rimraf "^3.0.2"
+
+flatted@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.0.tgz#a5d06b4a8b01e3a63771daa5cb7a1903e2e57067"
+  integrity sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==
+
 for-in@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -1450,20 +2138,39 @@ fs.realpath@^1.0.0:
   integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
 
 fsevents@^2.1.2:
-  version "2.1.3"
-  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
-  integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.2.1.tgz#1fb02ded2036a8ac288d507a65962bd87b97628d"
+  integrity sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==
+
+function-bind@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+  integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+functional-red-black-tree@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
+  integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
 
 gensync@^1.0.0-beta.1:
-  version "1.0.0-beta.1"
-  resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269"
-  integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==
+  version "1.0.0-beta.2"
+  resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
+  integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
 
 get-caller-file@^2.0.1:
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
   integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
 
+get-intrinsic@^1.0.0, get-intrinsic@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.2.tgz#6820da226e50b24894e08859469dc68361545d49"
+  integrity sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==
+  dependencies:
+    function-bind "^1.1.1"
+    has "^1.0.3"
+    has-symbols "^1.0.1"
+
 get-package-type@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
@@ -1495,6 +2202,13 @@ getpass@^0.1.1:
   dependencies:
     assert-plus "^1.0.0"
 
+glob-parent@^5.0.0, glob-parent@^5.1.0:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
+  integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
+  dependencies:
+    is-glob "^4.0.1"
+
 glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
   version "7.1.6"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
@@ -1512,7 +2226,26 @@ globals@^11.1.0:
   resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
   integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
 
-graceful-fs@^4.2.4:
+globals@^12.1.0:
+  version "12.4.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8"
+  integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==
+  dependencies:
+    type-fest "^0.8.1"
+
+globby@^11.0.1:
+  version "11.0.1"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357"
+  integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==
+  dependencies:
+    array-union "^2.1.0"
+    dir-glob "^3.0.1"
+    fast-glob "^3.1.1"
+    ignore "^5.1.4"
+    merge2 "^1.3.0"
+    slash "^3.0.0"
+
+graceful-fs@^4.1.2, graceful-fs@^4.2.4:
   version "4.2.4"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
   integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
@@ -1545,6 +2278,11 @@ has-flag@^4.0.0:
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
   integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
 
+has-symbols@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
+  integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
+
 has-value@^0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
@@ -1576,6 +2314,13 @@ has-values@^1.0.0:
     is-number "^3.0.0"
     kind-of "^4.0.0"
 
+has@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+  integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+  dependencies:
+    function-bind "^1.1.1"
+
 hosted-git-info@^2.1.4:
   version "2.8.8"
   resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
@@ -1614,6 +2359,24 @@ iconv-lite@0.4.24:
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
+ignore@^4.0.6:
+  version "4.0.6"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
+  integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
+
+ignore@^5.1.1, ignore@^5.1.4:
+  version "5.1.8"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
+  integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
+
+import-fresh@^3.0.0, import-fresh@^3.2.1:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
+  integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
+  dependencies:
+    parent-module "^1.0.0"
+    resolve-from "^4.0.0"
+
 import-local@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6"
@@ -1622,6 +2385,11 @@ import-local@^3.0.2:
     pkg-dir "^4.2.0"
     resolve-cwd "^3.0.0"
 
+import-modules@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/import-modules/-/import-modules-2.1.0.tgz#abe7df297cb6c1f19b57246eb8b8bd9664b6d8c2"
+  integrity sha512-8HEWcnkbGpovH9yInoisxaSoIg9Brbul+Ju3Kqe2UsYDUBJD/iQjSgEj0zPcTDPKfPp2fs5xlv1i+JSye/m1/A==
+
 imurmurhash@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@@ -1640,6 +2408,15 @@ inherits@2:
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
 
+internal-slot@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.2.tgz#9c2e9fb3cd8e5e4256c6f45fe310067fcfa378a3"
+  integrity sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==
+  dependencies:
+    es-abstract "^1.17.0-next.1"
+    has "^1.0.3"
+    side-channel "^1.0.2"
+
 ip-regex@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
@@ -1669,6 +2446,11 @@ is-buffer@^1.1.5:
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
   integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
 
+is-callable@^1.1.4, is-callable@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9"
+  integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==
+
 is-ci@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
@@ -1676,6 +2458,13 @@ is-ci@^2.0.0:
   dependencies:
     ci-info "^2.0.0"
 
+is-core-module@^2.1.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a"
+  integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==
+  dependencies:
+    has "^1.0.3"
+
 is-data-descriptor@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@@ -1690,6 +2479,11 @@ is-data-descriptor@^1.0.0:
   dependencies:
     kind-of "^6.0.0"
 
+is-date-object@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
+  integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
+
 is-descriptor@^0.1.0:
   version "0.1.6"
   resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
@@ -1725,6 +2519,11 @@ is-extendable@^1.0.1:
   dependencies:
     is-plain-object "^2.0.4"
 
+is-extglob@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+  integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
+
 is-fullwidth-code-point@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
@@ -1735,6 +2534,18 @@ is-generator-fn@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
   integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
 
+is-glob@^4.0.0, is-glob@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
+  integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
+  dependencies:
+    is-extglob "^2.1.1"
+
+is-negative-zero@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
+  integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
+
 is-number@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
@@ -1759,6 +2570,13 @@ is-potential-custom-element-name@^1.0.0:
   resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397"
   integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c=
 
+is-regex@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9"
+  integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==
+  dependencies:
+    has-symbols "^1.0.1"
+
 is-stream@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
@@ -1769,6 +2587,18 @@ is-stream@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3"
   integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==
 
+is-string@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6"
+  integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
+
+is-symbol@^1.0.2:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
+  integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==
+  dependencies:
+    has-symbols "^1.0.1"
+
 is-typedarray@^1.0.0, is-typedarray@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -1786,7 +2616,7 @@ is-wsl@^2.2.0:
   dependencies:
     is-docker "^2.0.0"
 
-isarray@1.0.0:
+isarray@1.0.0, isarray@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
   integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
@@ -1854,77 +2684,67 @@ istanbul-reports@^3.0.2:
     html-escaper "^2.0.0"
     istanbul-lib-report "^3.0.0"
 
-jest-changed-files@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.3.0.tgz#68fb2a7eb125f50839dab1f5a17db3607fe195b1"
-  integrity sha512-1C4R4nijgPltX6fugKxM4oQ18zimS7LqQ+zTTY8lMCMFPrxqBFb7KJH0Z2fRQJvw2Slbaipsqq7s1mgX5Iot+g==
+jest-changed-files@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0"
+  integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.6.2"
     execa "^4.0.0"
     throat "^5.0.0"
 
-jest-cli@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.4.2.tgz#24afc6e4dfc25cde4c7ec4226fb7db5f157c21da"
-  integrity sha512-zb+lGd/SfrPvoRSC/0LWdaWCnscXc1mGYW//NP4/tmBvRPT3VntZ2jtKUONsRi59zc5JqmsSajA9ewJKFYp8Cw==
+jest-cli@^26.6.3:
+  version "26.6.3"
+  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a"
+  integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==
   dependencies:
-    "@jest/core" "^26.4.2"
-    "@jest/test-result" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/core" "^26.6.3"
+    "@jest/test-result" "^26.6.2"
+    "@jest/types" "^26.6.2"
     chalk "^4.0.0"
     exit "^0.1.2"
     graceful-fs "^4.2.4"
     import-local "^3.0.2"
     is-ci "^2.0.0"
-    jest-config "^26.4.2"
-    jest-util "^26.3.0"
-    jest-validate "^26.4.2"
+    jest-config "^26.6.3"
+    jest-util "^26.6.2"
+    jest-validate "^26.6.2"
     prompts "^2.0.1"
-    yargs "^15.3.1"
+    yargs "^15.4.1"
 
-jest-config@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.4.2.tgz#da0cbb7dc2c131ffe831f0f7f2a36256e6086558"
-  integrity sha512-QBf7YGLuToiM8PmTnJEdRxyYy3mHWLh24LJZKVdXZ2PNdizSe1B/E8bVm+HYcjbEzGuVXDv/di+EzdO/6Gq80A==
+jest-config@^26.6.3:
+  version "26.6.3"
+  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349"
+  integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==
   dependencies:
     "@babel/core" "^7.1.0"
-    "@jest/test-sequencer" "^26.4.2"
-    "@jest/types" "^26.3.0"
-    babel-jest "^26.3.0"
+    "@jest/test-sequencer" "^26.6.3"
+    "@jest/types" "^26.6.2"
+    babel-jest "^26.6.3"
     chalk "^4.0.0"
     deepmerge "^4.2.2"
     glob "^7.1.1"
     graceful-fs "^4.2.4"
-    jest-environment-jsdom "^26.3.0"
-    jest-environment-node "^26.3.0"
+    jest-environment-jsdom "^26.6.2"
+    jest-environment-node "^26.6.2"
     jest-get-type "^26.3.0"
-    jest-jasmine2 "^26.4.2"
+    jest-jasmine2 "^26.6.3"
     jest-regex-util "^26.0.0"
-    jest-resolve "^26.4.0"
-    jest-util "^26.3.0"
-    jest-validate "^26.4.2"
+    jest-resolve "^26.6.2"
+    jest-util "^26.6.2"
+    jest-validate "^26.6.2"
     micromatch "^4.0.2"
-    pretty-format "^26.4.2"
+    pretty-format "^26.6.2"
 
-jest-diff@^25.2.1:
-  version "25.5.0"
-  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9"
-  integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==
-  dependencies:
-    chalk "^3.0.0"
-    diff-sequences "^25.2.6"
-    jest-get-type "^25.2.6"
-    pretty-format "^25.5.0"
-
-jest-diff@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.4.2.tgz#a1b7b303bcc534aabdb3bd4a7caf594ac059f5aa"
-  integrity sha512-6T1XQY8U28WH0Z5rGpQ+VqZSZz8EN8rZcBtfvXaOkbwxIEeRre6qnuZQlbY1AJ4MKDxQF8EkrCvK+hL/VkyYLQ==
+jest-diff@^26.0.0, jest-diff@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394"
+  integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==
   dependencies:
     chalk "^4.0.0"
-    diff-sequences "^26.3.0"
+    diff-sequences "^26.6.2"
     jest-get-type "^26.3.0"
-    pretty-format "^26.4.2"
+    pretty-format "^26.6.2"
 
 jest-docblock@^26.0.0:
   version "26.0.0"
@@ -1933,135 +2753,131 @@ jest-docblock@^26.0.0:
   dependencies:
     detect-newline "^3.0.0"
 
-jest-each@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.4.2.tgz#bb14f7f4304f2bb2e2b81f783f989449b8b6ffae"
-  integrity sha512-p15rt8r8cUcRY0Mvo1fpkOGYm7iI8S6ySxgIdfh3oOIv+gHwrHTy5VWCGOecWUhDsit4Nz8avJWdT07WLpbwDA==
+jest-each@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb"
+  integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.6.2"
     chalk "^4.0.0"
     jest-get-type "^26.3.0"
-    jest-util "^26.3.0"
-    pretty-format "^26.4.2"
+    jest-util "^26.6.2"
+    pretty-format "^26.6.2"
 
-jest-environment-jsdom@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.3.0.tgz#3b749ba0f3a78e92ba2c9ce519e16e5dd515220c"
-  integrity sha512-zra8He2btIMJkAzvLaiZ9QwEPGEetbxqmjEBQwhH3CA+Hhhu0jSiEJxnJMbX28TGUvPLxBt/zyaTLrOPF4yMJA==
+jest-environment-jsdom@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e"
+  integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==
   dependencies:
-    "@jest/environment" "^26.3.0"
-    "@jest/fake-timers" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/environment" "^26.6.2"
+    "@jest/fake-timers" "^26.6.2"
+    "@jest/types" "^26.6.2"
     "@types/node" "*"
-    jest-mock "^26.3.0"
-    jest-util "^26.3.0"
-    jsdom "^16.2.2"
-
-jest-environment-node@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.3.0.tgz#56c6cfb506d1597f94ee8d717072bda7228df849"
-  integrity sha512-c9BvYoo+FGcMj5FunbBgtBnbR5qk3uky8PKyRVpSfe2/8+LrNQMiXX53z6q2kY+j15SkjQCOSL/6LHnCPLVHNw==
-  dependencies:
-    "@jest/environment" "^26.3.0"
-    "@jest/fake-timers" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    jest-mock "^26.6.2"
+    jest-util "^26.6.2"
+    jsdom "^16.4.0"
+
+jest-environment-node@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c"
+  integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==
+  dependencies:
+    "@jest/environment" "^26.6.2"
+    "@jest/fake-timers" "^26.6.2"
+    "@jest/types" "^26.6.2"
     "@types/node" "*"
-    jest-mock "^26.3.0"
-    jest-util "^26.3.0"
-
-jest-get-type@^25.2.6:
-  version "25.2.6"
-  resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877"
-  integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==
+    jest-mock "^26.6.2"
+    jest-util "^26.6.2"
 
 jest-get-type@^26.3.0:
   version "26.3.0"
   resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0"
   integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==
 
-jest-haste-map@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.3.0.tgz#c51a3b40100d53ab777bfdad382d2e7a00e5c726"
-  integrity sha512-DHWBpTJgJhLLGwE5Z1ZaqLTYqeODQIZpby0zMBsCU9iRFHYyhklYqP4EiG73j5dkbaAdSZhgB938mL51Q5LeZA==
+jest-haste-map@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa"
+  integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.6.2"
     "@types/graceful-fs" "^4.1.2"
     "@types/node" "*"
     anymatch "^3.0.3"
     fb-watchman "^2.0.0"
     graceful-fs "^4.2.4"
     jest-regex-util "^26.0.0"
-    jest-serializer "^26.3.0"
-    jest-util "^26.3.0"
-    jest-worker "^26.3.0"
+    jest-serializer "^26.6.2"
+    jest-util "^26.6.2"
+    jest-worker "^26.6.2"
     micromatch "^4.0.2"
     sane "^4.0.3"
     walker "^1.0.7"
   optionalDependencies:
     fsevents "^2.1.2"
 
-jest-jasmine2@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.4.2.tgz#18a9d5bec30904267ac5e9797570932aec1e2257"
-  integrity sha512-z7H4EpCldHN1J8fNgsja58QftxBSL+JcwZmaXIvV9WKIM+x49F4GLHu/+BQh2kzRKHAgaN/E82od+8rTOBPyPA==
+jest-jasmine2@^26.6.3:
+  version "26.6.3"
+  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd"
+  integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==
   dependencies:
     "@babel/traverse" "^7.1.0"
-    "@jest/environment" "^26.3.0"
-    "@jest/source-map" "^26.3.0"
-    "@jest/test-result" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/environment" "^26.6.2"
+    "@jest/source-map" "^26.6.2"
+    "@jest/test-result" "^26.6.2"
+    "@jest/types" "^26.6.2"
     "@types/node" "*"
     chalk "^4.0.0"
     co "^4.6.0"
-    expect "^26.4.2"
+    expect "^26.6.2"
     is-generator-fn "^2.0.0"
-    jest-each "^26.4.2"
-    jest-matcher-utils "^26.4.2"
-    jest-message-util "^26.3.0"
-    jest-runtime "^26.4.2"
-    jest-snapshot "^26.4.2"
-    jest-util "^26.3.0"
-    pretty-format "^26.4.2"
+    jest-each "^26.6.2"
+    jest-matcher-utils "^26.6.2"
+    jest-message-util "^26.6.2"
+    jest-runtime "^26.6.3"
+    jest-snapshot "^26.6.2"
+    jest-util "^26.6.2"
+    pretty-format "^26.6.2"
     throat "^5.0.0"
 
-jest-leak-detector@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.4.2.tgz#c73e2fa8757bf905f6f66fb9e0070b70fa0f573f"
-  integrity sha512-akzGcxwxtE+9ZJZRW+M2o+nTNnmQZxrHJxX/HjgDaU5+PLmY1qnQPnMjgADPGCRPhB+Yawe1iij0REe+k/aHoA==
+jest-leak-detector@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af"
+  integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==
   dependencies:
     jest-get-type "^26.3.0"
-    pretty-format "^26.4.2"
+    pretty-format "^26.6.2"
 
-jest-matcher-utils@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.4.2.tgz#fa81f3693f7cb67e5fc1537317525ef3b85f4b06"
-  integrity sha512-KcbNqWfWUG24R7tu9WcAOKKdiXiXCbMvQYT6iodZ9k1f7065k0keUOW6XpJMMvah+hTfqkhJhRXmA3r3zMAg0Q==
+jest-matcher-utils@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a"
+  integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==
   dependencies:
     chalk "^4.0.0"
-    jest-diff "^26.4.2"
+    jest-diff "^26.6.2"
     jest-get-type "^26.3.0"
-    pretty-format "^26.4.2"
+    pretty-format "^26.6.2"
 
-jest-message-util@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.3.0.tgz#3bdb538af27bb417f2d4d16557606fd082d5841a"
-  integrity sha512-xIavRYqr4/otGOiLxLZGj3ieMmjcNE73Ui+LdSW/Y790j5acqCsAdDiLIbzHCZMpN07JOENRWX5DcU+OQ+TjTA==
+jest-message-util@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07"
+  integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==
   dependencies:
     "@babel/code-frame" "^7.0.0"
-    "@jest/types" "^26.3.0"
-    "@types/stack-utils" "^1.0.1"
+    "@jest/types" "^26.6.2"
+    "@types/stack-utils" "^2.0.0"
     chalk "^4.0.0"
     graceful-fs "^4.2.4"
     micromatch "^4.0.2"
+    pretty-format "^26.6.2"
     slash "^3.0.0"
     stack-utils "^2.0.2"
 
-jest-mock@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.3.0.tgz#ee62207c3c5ebe5f35b760e1267fee19a1cfdeba"
-  integrity sha512-PeaRrg8Dc6mnS35gOo/CbZovoDPKAeB1FICZiuagAgGvbWdNNyjQjkOaGUa/3N3JtpQ/Mh9P4A2D4Fv51NnP8Q==
+jest-mock@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302"
+  integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.6.2"
     "@types/node" "*"
 
 jest-pnp-resolver@^1.2.2:
@@ -2074,180 +2890,182 @@ jest-regex-util@^26.0.0:
   resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28"
   integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==
 
-jest-resolve-dependencies@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.4.2.tgz#739bdb027c14befb2fe5aabbd03f7bab355f1dc5"
-  integrity sha512-ADHaOwqEcVc71uTfySzSowA/RdxUpCxhxa2FNLiin9vWLB1uLPad3we+JSSROq5+SrL9iYPdZZF8bdKM7XABTQ==
+jest-resolve-dependencies@^26.6.3:
+  version "26.6.3"
+  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6"
+  integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.6.2"
     jest-regex-util "^26.0.0"
-    jest-snapshot "^26.4.2"
+    jest-snapshot "^26.6.2"
 
-jest-resolve@^26.4.0:
-  version "26.4.0"
-  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.4.0.tgz#6dc0af7fb93e65b73fec0368ca2b76f3eb59a6d7"
-  integrity sha512-bn/JoZTEXRSlEx3+SfgZcJAVuTMOksYq9xe9O6s4Ekg84aKBObEaVXKOEilULRqviSLAYJldnoWV9c07kwtiCg==
+jest-resolve@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507"
+  integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.6.2"
     chalk "^4.0.0"
     graceful-fs "^4.2.4"
     jest-pnp-resolver "^1.2.2"
-    jest-util "^26.3.0"
+    jest-util "^26.6.2"
     read-pkg-up "^7.0.1"
-    resolve "^1.17.0"
+    resolve "^1.18.1"
     slash "^3.0.0"
 
-jest-runner@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.4.2.tgz#c3ec5482c8edd31973bd3935df5a449a45b5b853"
-  integrity sha512-FgjDHeVknDjw1gRAYaoUoShe1K3XUuFMkIaXbdhEys+1O4bEJS8Avmn4lBwoMfL8O5oFTdWYKcf3tEJyyYyk8g==
+jest-runner@^26.6.3:
+  version "26.6.3"
+  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159"
+  integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==
   dependencies:
-    "@jest/console" "^26.3.0"
-    "@jest/environment" "^26.3.0"
-    "@jest/test-result" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/console" "^26.6.2"
+    "@jest/environment" "^26.6.2"
+    "@jest/test-result" "^26.6.2"
+    "@jest/types" "^26.6.2"
     "@types/node" "*"
     chalk "^4.0.0"
     emittery "^0.7.1"
     exit "^0.1.2"
     graceful-fs "^4.2.4"
-    jest-config "^26.4.2"
+    jest-config "^26.6.3"
     jest-docblock "^26.0.0"
-    jest-haste-map "^26.3.0"
-    jest-leak-detector "^26.4.2"
-    jest-message-util "^26.3.0"
-    jest-resolve "^26.4.0"
-    jest-runtime "^26.4.2"
-    jest-util "^26.3.0"
-    jest-worker "^26.3.0"
+    jest-haste-map "^26.6.2"
+    jest-leak-detector "^26.6.2"
+    jest-message-util "^26.6.2"
+    jest-resolve "^26.6.2"
+    jest-runtime "^26.6.3"
+    jest-util "^26.6.2"
+    jest-worker "^26.6.2"
     source-map-support "^0.5.6"
     throat "^5.0.0"
 
-jest-runtime@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.4.2.tgz#94ce17890353c92e4206580c73a8f0c024c33c42"
-  integrity sha512-4Pe7Uk5a80FnbHwSOk7ojNCJvz3Ks2CNQWT5Z7MJo4tX0jb3V/LThKvD9tKPNVNyeMH98J/nzGlcwc00R2dSHQ==
-  dependencies:
-    "@jest/console" "^26.3.0"
-    "@jest/environment" "^26.3.0"
-    "@jest/fake-timers" "^26.3.0"
-    "@jest/globals" "^26.4.2"
-    "@jest/source-map" "^26.3.0"
-    "@jest/test-result" "^26.3.0"
-    "@jest/transform" "^26.3.0"
-    "@jest/types" "^26.3.0"
+jest-runtime@^26.6.3:
+  version "26.6.3"
+  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b"
+  integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==
+  dependencies:
+    "@jest/console" "^26.6.2"
+    "@jest/environment" "^26.6.2"
+    "@jest/fake-timers" "^26.6.2"
+    "@jest/globals" "^26.6.2"
+    "@jest/source-map" "^26.6.2"
+    "@jest/test-result" "^26.6.2"
+    "@jest/transform" "^26.6.2"
+    "@jest/types" "^26.6.2"
     "@types/yargs" "^15.0.0"
     chalk "^4.0.0"
+    cjs-module-lexer "^0.6.0"
     collect-v8-coverage "^1.0.0"
     exit "^0.1.2"
     glob "^7.1.3"
     graceful-fs "^4.2.4"
-    jest-config "^26.4.2"
-    jest-haste-map "^26.3.0"
-    jest-message-util "^26.3.0"
-    jest-mock "^26.3.0"
+    jest-config "^26.6.3"
+    jest-haste-map "^26.6.2"
+    jest-message-util "^26.6.2"
+    jest-mock "^26.6.2"
     jest-regex-util "^26.0.0"
-    jest-resolve "^26.4.0"
-    jest-snapshot "^26.4.2"
-    jest-util "^26.3.0"
-    jest-validate "^26.4.2"
+    jest-resolve "^26.6.2"
+    jest-snapshot "^26.6.2"
+    jest-util "^26.6.2"
+    jest-validate "^26.6.2"
     slash "^3.0.0"
     strip-bom "^4.0.0"
-    yargs "^15.3.1"
+    yargs "^15.4.1"
 
-jest-serializer@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.3.0.tgz#1c9d5e1b74d6e5f7e7f9627080fa205d976c33ef"
-  integrity sha512-IDRBQBLPlKa4flg77fqg0n/pH87tcRKwe8zxOVTWISxGpPHYkRZ1dXKyh04JOja7gppc60+soKVZ791mruVdow==
+jest-serializer@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1"
+  integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==
   dependencies:
     "@types/node" "*"
     graceful-fs "^4.2.4"
 
-jest-snapshot@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.4.2.tgz#87d3ac2f2bd87ea8003602fbebd8fcb9e94104f6"
-  integrity sha512-N6Uub8FccKlf5SBFnL2Ri/xofbaA68Cc3MGjP/NuwgnsvWh+9hLIR/DhrxbSiKXMY9vUW5dI6EW1eHaDHqe9sg==
+jest-snapshot@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84"
+  integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==
   dependencies:
     "@babel/types" "^7.0.0"
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.6.2"
+    "@types/babel__traverse" "^7.0.4"
     "@types/prettier" "^2.0.0"
     chalk "^4.0.0"
-    expect "^26.4.2"
+    expect "^26.6.2"
     graceful-fs "^4.2.4"
-    jest-diff "^26.4.2"
+    jest-diff "^26.6.2"
     jest-get-type "^26.3.0"
-    jest-haste-map "^26.3.0"
-    jest-matcher-utils "^26.4.2"
-    jest-message-util "^26.3.0"
-    jest-resolve "^26.4.0"
+    jest-haste-map "^26.6.2"
+    jest-matcher-utils "^26.6.2"
+    jest-message-util "^26.6.2"
+    jest-resolve "^26.6.2"
     natural-compare "^1.4.0"
-    pretty-format "^26.4.2"
+    pretty-format "^26.6.2"
     semver "^7.3.2"
 
-jest-util@^26.1.0, jest-util@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.3.0.tgz#a8974b191df30e2bf523ebbfdbaeb8efca535b3e"
-  integrity sha512-4zpn6bwV0+AMFN0IYhH/wnzIQzRaYVrz1A8sYnRnj4UXDXbOVtWmlaZkO9mipFqZ13okIfN87aDoJWB7VH6hcw==
+jest-util@^26.1.0, jest-util@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1"
+  integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.6.2"
     "@types/node" "*"
     chalk "^4.0.0"
     graceful-fs "^4.2.4"
     is-ci "^2.0.0"
     micromatch "^4.0.2"
 
-jest-validate@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.4.2.tgz#e871b0dfe97747133014dcf6445ee8018398f39c"
-  integrity sha512-blft+xDX7XXghfhY0mrsBCYhX365n8K5wNDC4XAcNKqqjEzsRUSXP44m6PL0QJEW2crxQFLLztVnJ4j7oPlQrQ==
+jest-validate@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec"
+  integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.6.2"
     camelcase "^6.0.0"
     chalk "^4.0.0"
     jest-get-type "^26.3.0"
     leven "^3.1.0"
-    pretty-format "^26.4.2"
+    pretty-format "^26.6.2"
 
-jest-watcher@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.3.0.tgz#f8ef3068ddb8af160ef868400318dc4a898eed08"
-  integrity sha512-XnLdKmyCGJ3VoF6G/p5ohbJ04q/vv5aH9ENI+i6BL0uu9WWB6Z7Z2lhQQk0d2AVZcRGp1yW+/TsoToMhBFPRdQ==
+jest-watcher@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975"
+  integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==
   dependencies:
-    "@jest/test-result" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/test-result" "^26.6.2"
+    "@jest/types" "^26.6.2"
     "@types/node" "*"
     ansi-escapes "^4.2.1"
     chalk "^4.0.0"
-    jest-util "^26.3.0"
+    jest-util "^26.6.2"
     string-length "^4.0.1"
 
-jest-worker@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.3.0.tgz#7c8a97e4f4364b4f05ed8bca8ca0c24de091871f"
-  integrity sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==
+jest-worker@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
+  integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==
   dependencies:
     "@types/node" "*"
     merge-stream "^2.0.0"
     supports-color "^7.0.0"
 
-jest@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest/-/jest-26.4.2.tgz#7e8bfb348ec33f5459adeaffc1a25d5752d9d312"
-  integrity sha512-LLCjPrUh98Ik8CzW8LLVnSCfLaiY+wbK53U7VxnFSX7Q+kWC4noVeDvGWIFw0Amfq1lq2VfGm7YHWSLBV62MJw==
+jest@^26.6.3:
+  version "26.6.3"
+  resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef"
+  integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==
   dependencies:
-    "@jest/core" "^26.4.2"
+    "@jest/core" "^26.6.3"
     import-local "^3.0.2"
-    jest-cli "^26.4.2"
+    jest-cli "^26.6.3"
 
-js-tokens@^4.0.0:
+"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
   integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
 
 js-yaml@^3.13.1:
-  version "3.14.0"
-  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
-  integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
+  version "3.14.1"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
+  integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
   dependencies:
     argparse "^1.0.7"
     esprima "^4.0.0"
@@ -2257,7 +3075,7 @@ jsbn@~0.1.0:
   resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
   integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
 
-jsdom@^16.2.2:
+jsdom@^16.4.0:
   version "16.4.0"
   resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb"
   integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==
@@ -2309,6 +3127,11 @@ json-schema@0.2.3:
   resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
   integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
 
+json-stable-stringify-without-jsonify@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
+  integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
+
 json-stringify-safe@~5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
@@ -2321,6 +3144,13 @@ json5@2.x, json5@^2.1.2:
   dependencies:
     minimist "^1.2.5"
 
+json5@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
+  integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
+  dependencies:
+    minimist "^1.2.0"
+
 jsprim@^1.2.2:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -2331,6 +3161,14 @@ jsprim@^1.2.2:
     json-schema "0.2.3"
     verror "1.10.0"
 
+"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82"
+  integrity sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==
+  dependencies:
+    array-includes "^3.1.2"
+    object.assign "^4.1.2"
+
 kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
@@ -2360,16 +3198,36 @@ kleur@^3.0.3:
   resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
   integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
 
-lemmy-js-client@^1.0.14:
-  version "1.0.14"
-  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-1.0.14.tgz#81a847dd0c7d97c83913f198717498c223dc371e"
-  integrity sha512-hiGxAnAD5RFmE8qHMBtYNNYD/UrfCZ5JzmVEH/i5Vg/v5i/ZVmebx20uWtRMmdSSy6s4GbW0w4niszLW6SaJ3Q==
+language-subtag-registry@~0.3.2:
+  version "0.3.21"
+  resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a"
+  integrity sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg==
+
+language-tags@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a"
+  integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=
+  dependencies:
+    language-subtag-registry "~0.3.2"
+
+lemmy-js-client@1.0.17-beta3:
+  version "1.0.17-beta3"
+  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-1.0.17-beta3.tgz#ad53f3fd57e7656732f0ccde4f6b22242d77fd9b"
+  integrity sha512-0GIStZtmkCZmKk12faEusYyEIdDhhnSZ52HIu1b8rPhl68OrxVYBRbl2DQC6Ue7LFR2yMsa1q6Mp1UrTCTssRA==
 
 leven@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
   integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
 
+levn@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
+  integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+  dependencies:
+    prelude-ls "^1.2.1"
+    type-check "~0.4.0"
+
 levn@~0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
@@ -2383,6 +3241,24 @@ lines-and-columns@^1.1.6:
   resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
   integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
 
+load-json-file@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
+  integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=
+  dependencies:
+    graceful-fs "^4.1.2"
+    parse-json "^2.2.0"
+    pify "^2.0.0"
+    strip-bom "^3.0.0"
+
+locate-path@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
+  integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=
+  dependencies:
+    p-locate "^2.0.0"
+    path-exists "^3.0.0"
+
 locate-path@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
@@ -2390,6 +3266,11 @@ locate-path@^5.0.0:
   dependencies:
     p-locate "^4.1.0"
 
+lodash.get@^4.4.2:
+  version "4.4.2"
+  resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
+  integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
+
 lodash.memoize@4.x:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@@ -2400,11 +3281,30 @@ lodash.sortby@^4.7.0:
   resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
   integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
 
-lodash@^4.17.19:
+lodash.zip@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/lodash.zip/-/lodash.zip-4.2.0.tgz#ec6662e4896408ed4ab6c542a3990b72cc080020"
+  integrity sha1-7GZi5IlkCO1KtsVCo5kLcswIACA=
+
+lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20:
   version "4.17.20"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
   integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
 
+loose-envify@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+  integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
+  dependencies:
+    js-tokens "^3.0.0 || ^4.0.0"
+
+lru-cache@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+  integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+  dependencies:
+    yallist "^4.0.0"
+
 make-dir@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
@@ -2441,6 +3341,11 @@ merge-stream@^2.0.0:
   resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
   integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
 
+merge2@^1.3.0:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
+  integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+
 micromatch@^3.1.4:
   version "3.1.10"
   resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
@@ -2515,11 +3420,16 @@ ms@2.0.0:
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
   integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
 
-ms@^2.1.1:
+ms@2.1.2:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
   integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
 
+multimap@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/multimap/-/multimap-1.1.0.tgz#5263febc085a1791c33b59bb3afc6a76a2a10ca8"
+  integrity sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw==
+
 nanomatch@^1.2.9:
   version "1.2.13"
   resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@@ -2563,9 +3473,9 @@ node-modules-regexp@^1.0.0:
   integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
 
 node-notifier@^8.0.0:
-  version "8.0.0"
-  resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.0.tgz#a7eee2d51da6d0f7ff5094bc7108c911240c1620"
-  integrity sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA==
+  version "8.0.1"
+  resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.1.tgz#f86e89bbc925f2b068784b31f382afdc6ca56be1"
+  integrity sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==
   dependencies:
     growly "^1.3.0"
     is-wsl "^2.2.0"
@@ -2574,7 +3484,7 @@ node-notifier@^8.0.0:
     uuid "^8.3.0"
     which "^2.0.2"
 
-normalize-package-data@^2.5.0:
+normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
   integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
@@ -2620,6 +3530,11 @@ oauth-sign@~0.9.0:
   resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
   integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
 
+object-assign@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+  integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
+
 object-copy@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
@@ -2629,6 +3544,16 @@ object-copy@^0.1.0:
     define-property "^0.2.5"
     kind-of "^3.0.3"
 
+object-inspect@^1.8.0:
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a"
+  integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==
+
+object-keys@^1.0.12, object-keys@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+  integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+
 object-visit@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
@@ -2636,6 +3561,36 @@ object-visit@^1.0.0:
   dependencies:
     isobject "^3.0.0"
 
+object.assign@^4.1.1, object.assign@^4.1.2:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
+  integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
+  dependencies:
+    call-bind "^1.0.0"
+    define-properties "^1.1.3"
+    has-symbols "^1.0.1"
+    object-keys "^1.1.1"
+
+object.entries@^1.1.2:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.3.tgz#c601c7f168b62374541a07ddbd3e2d5e4f7711a6"
+  integrity sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg==
+  dependencies:
+    call-bind "^1.0.0"
+    define-properties "^1.1.3"
+    es-abstract "^1.18.0-next.1"
+    has "^1.0.3"
+
+object.fromentries@^2.0.2:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.3.tgz#13cefcffa702dc67750314a3305e8cb3fad1d072"
+  integrity sha512-IDUSMXs6LOSJBWE++L0lzIbSqHl9KDCfff2x/JSEIDtEUavUnyMYC2ZGay/04Zq4UT8lvd4xNhU4/YHKibAOlw==
+  dependencies:
+    call-bind "^1.0.0"
+    define-properties "^1.1.3"
+    es-abstract "^1.18.0-next.1"
+    has "^1.0.3"
+
 object.pick@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
@@ -2643,6 +3598,16 @@ object.pick@^1.3.0:
   dependencies:
     isobject "^3.0.1"
 
+object.values@^1.1.1:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.2.tgz#7a2015e06fcb0f546bd652486ce8583a4731c731"
+  integrity sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag==
+  dependencies:
+    call-bind "^1.0.0"
+    define-properties "^1.1.3"
+    es-abstract "^1.18.0-next.1"
+    has "^1.0.3"
+
 once@^1.3.0, once@^1.3.1, once@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@@ -2669,16 +3634,35 @@ optionator@^0.8.1:
     type-check "~0.3.2"
     word-wrap "~1.2.3"
 
+optionator@^0.9.1:
+  version "0.9.1"
+  resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
+  integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==
+  dependencies:
+    deep-is "^0.1.3"
+    fast-levenshtein "^2.0.6"
+    levn "^0.4.1"
+    prelude-ls "^1.2.1"
+    type-check "^0.4.0"
+    word-wrap "^1.2.3"
+
 p-each-series@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48"
-  integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a"
+  integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==
 
 p-finally@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
   integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
 
+p-limit@^1.1.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
+  integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
+  dependencies:
+    p-try "^1.0.0"
+
 p-limit@^2.2.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
@@ -2686,6 +3670,13 @@ p-limit@^2.2.0:
   dependencies:
     p-try "^2.0.0"
 
+p-locate@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
+  integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=
+  dependencies:
+    p-limit "^1.1.0"
+
 p-locate@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
@@ -2693,11 +3684,30 @@ p-locate@^4.1.0:
   dependencies:
     p-limit "^2.2.0"
 
+p-try@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
+  integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
+
 p-try@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
   integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
 
+parent-module@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
+  integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+  dependencies:
+    callsites "^3.0.0"
+
+parse-json@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
+  integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=
+  dependencies:
+    error-ex "^1.2.0"
+
 parse-json@^5.0.0:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.1.0.tgz#f96088cdf24a8faa9aea9a009f2d9d942c999646"
@@ -2718,6 +3728,11 @@ pascalcase@^0.1.1:
   resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
   integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
 
+path-exists@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
+  integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
+
 path-exists@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
@@ -2743,16 +3758,33 @@ path-parse@^1.0.6:
   resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
   integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
 
+path-type@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
+  integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=
+  dependencies:
+    pify "^2.0.0"
+
+path-type@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
+  integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
+
 performance-now@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
   integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
 
-picomatch@^2.0.4, picomatch@^2.0.5:
+picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
   integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
 
+pify@^2.0.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
+  integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
+
 pirates@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87"
@@ -2760,6 +3792,13 @@ pirates@^4.0.1:
   dependencies:
     node-modules-regexp "^1.0.0"
 
+pkg-dir@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
+  integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=
+  dependencies:
+    find-up "^2.1.0"
+
 pkg-dir@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
@@ -2767,43 +3806,69 @@ pkg-dir@^4.2.0:
   dependencies:
     find-up "^4.0.0"
 
+pluralize@^8.0.0:
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
+  integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
+
 posix-character-classes@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
   integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
 
+prelude-ls@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
+  integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+
 prelude-ls@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
   integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
 
-pretty-format@^25.2.1, pretty-format@^25.5.0:
-  version "25.5.0"
-  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
-  integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==
+prettier-linter-helpers@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
+  integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
   dependencies:
-    "@jest/types" "^25.5.0"
-    ansi-regex "^5.0.0"
-    ansi-styles "^4.0.0"
-    react-is "^16.12.0"
+    fast-diff "^1.1.2"
+
+prettier@^2.1.2:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
+  integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==
 
-pretty-format@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.4.2.tgz#d081d032b398e801e2012af2df1214ef75a81237"
-  integrity sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA==
+pretty-format@^26.0.0, pretty-format@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
+  integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.6.2"
     ansi-regex "^5.0.0"
     ansi-styles "^4.0.0"
-    react-is "^16.12.0"
+    react-is "^17.0.1"
+
+progress@^2.0.0:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
+  integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
 
 prompts@^2.0.1:
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068"
-  integrity sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7"
+  integrity sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==
   dependencies:
     kleur "^3.0.3"
-    sisteransi "^1.0.4"
+    sisteransi "^1.0.5"
+
+prop-types@^15.7.2:
+  version "15.7.2"
+  resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
+  integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
+  dependencies:
+    loose-envify "^1.4.0"
+    object-assign "^4.1.1"
+    react-is "^16.8.1"
 
 psl@^1.1.28:
   version "1.8.0"
@@ -2828,11 +3893,24 @@ qs@~6.5.2:
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
   integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
 
-react-is@^16.12.0:
+react-is@^16.8.1:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 
+react-is@^17.0.1:
+  version "17.0.1"
+  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
+  integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
+
+read-pkg-up@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
+  integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=
+  dependencies:
+    find-up "^2.0.0"
+    read-pkg "^2.0.0"
+
 read-pkg-up@^7.0.1:
   version "7.0.1"
   resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507"
@@ -2842,6 +3920,15 @@ read-pkg-up@^7.0.1:
     read-pkg "^5.2.0"
     type-fest "^0.8.1"
 
+read-pkg@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
+  integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=
+  dependencies:
+    load-json-file "^2.0.0"
+    normalize-package-data "^2.3.2"
+    path-type "^2.0.0"
+
 read-pkg@^5.2.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc"
@@ -2852,6 +3939,11 @@ read-pkg@^5.2.0:
     parse-json "^5.0.0"
     type-fest "^0.6.0"
 
+regenerator-runtime@^0.13.4:
+  version "0.13.7"
+  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
+  integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
+
 regex-not@^1.0.0, regex-not@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
@@ -2860,6 +3952,24 @@ regex-not@^1.0.0, regex-not@^1.0.2:
     extend-shallow "^3.0.2"
     safe-regex "^1.1.0"
 
+regexp-tree@^0.1.21, regexp-tree@~0.1.1:
+  version "0.1.21"
+  resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.21.tgz#55e2246b7f7d36f1b461490942fa780299c400d7"
+  integrity sha512-kUUXjX4AnqnR8KRTCrayAo9PzYMRKmVoGgaz2tBuz0MF3g1ZbGebmtW0yFHfFK9CmBjQKeYIgoL22pFLBJY7sw==
+
+regexp.prototype.flags@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75"
+  integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==
+  dependencies:
+    define-properties "^1.1.3"
+    es-abstract "^1.17.0-next.1"
+
+regexpp@^3.0.0, regexpp@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2"
+  integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
+
 remove-trailing-separator@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
@@ -2927,6 +4037,11 @@ require-main-filename@^2.0.0:
   resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
   integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
 
+reserved-words@^0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1"
+  integrity sha1-AKCUD5jNUBrqqsMWQR2a3FKzGrE=
+
 resolve-cwd@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
@@ -2934,6 +4049,11 @@ resolve-cwd@^3.0.0:
   dependencies:
     resolve-from "^5.0.0"
 
+resolve-from@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
+  integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
 resolve-from@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
@@ -2944,11 +4064,12 @@ resolve-url@^0.2.1:
   resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
   integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
 
-resolve@^1.10.0, resolve@^1.17.0, resolve@^1.3.2:
-  version "1.17.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
-  integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
+resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.18.1:
+  version "1.19.0"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c"
+  integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==
   dependencies:
+    is-core-module "^2.1.0"
     path-parse "^1.0.6"
 
 ret@~0.1.10:
@@ -2956,7 +4077,12 @@ ret@~0.1.10:
   resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
   integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
 
-rimraf@^3.0.0:
+reusify@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
+  integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+
+rimraf@^3.0.0, rimraf@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
   integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
@@ -2968,6 +4094,11 @@ rsvp@^4.8.4:
   resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
   integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
 
+run-parallel@^1.1.9:
+  version "1.1.10"
+  resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef"
+  integrity sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==
+
 safe-buffer@^5.0.1, safe-buffer@^5.1.2:
   version "5.2.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
@@ -2985,6 +4116,13 @@ safe-regex@^1.1.0:
   dependencies:
     ret "~0.1.10"
 
+safe-regex@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-2.1.1.tgz#f7128f00d056e2fe5c11e81a1324dd974aadced2"
+  integrity sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==
+  dependencies:
+    regexp-tree "~0.1.1"
+
 "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
@@ -3017,12 +4155,14 @@ saxes@^5.0.0:
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
   integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
 
-semver@7.x, semver@^7.3.2:
-  version "7.3.2"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
-  integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
+semver@7.x, semver@^7.2.1, semver@^7.3.2:
+  version "7.3.4"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
+  integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
+  dependencies:
+    lru-cache "^6.0.0"
 
-semver@^6.0.0, semver@^6.3.0:
+semver@^6.0.0, semver@^6.1.0, semver@^6.3.0:
   version "6.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
@@ -3071,12 +4211,20 @@ shellwords@^0.1.1:
   resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
   integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
 
+side-channel@^1.0.2, side-channel@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3"
+  integrity sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==
+  dependencies:
+    es-abstract "^1.18.0-next.0"
+    object-inspect "^1.8.0"
+
 signal-exit@^3.0.0, signal-exit@^3.0.2:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
   integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
 
-sisteransi@^1.0.4:
+sisteransi@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
   integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
@@ -3086,6 +4234,15 @@ slash@^3.0.0:
   resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
   integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
 
+slice-ansi@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
+  integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
+  dependencies:
+    ansi-styles "^4.0.0"
+    astral-regex "^2.0.0"
+    is-fullwidth-code-point "^3.0.0"
+
 snapdragon-node@^2.0.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
@@ -3177,9 +4334,9 @@ spdx-expression-parse@^3.0.0:
     spdx-license-ids "^3.0.0"
 
 spdx-license-ids@^3.0.0:
-  version "3.0.5"
-  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654"
-  integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==
+  version "3.0.7"
+  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65"
+  integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==
 
 split-string@^3.0.1, split-string@^3.0.2:
   version "3.1.0"
@@ -3209,9 +4366,9 @@ sshpk@^1.7.0:
     tweetnacl "~0.14.0"
 
 stack-utils@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.2.tgz#5cf48b4557becb4638d0bc4f21d23f5d19586593"
-  integrity sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg==
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277"
+  integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==
   dependencies:
     escape-string-regexp "^2.0.0"
 
@@ -3245,6 +4402,35 @@ string-width@^4.1.0, string-width@^4.2.0:
     is-fullwidth-code-point "^3.0.0"
     strip-ansi "^6.0.0"
 
+string.prototype.matchall@^4.0.2:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.3.tgz#24243399bc31b0a49d19e2b74171a15653ec996a"
+  integrity sha512-OBxYDA2ifZQ2e13cP82dWFMaCV9CGF8GzmN4fljBVw5O5wep0lu4gacm1OL6MjROoUnB8VbkWRThqkV2YFLNxw==
+  dependencies:
+    call-bind "^1.0.0"
+    define-properties "^1.1.3"
+    es-abstract "^1.18.0-next.1"
+    has-symbols "^1.0.1"
+    internal-slot "^1.0.2"
+    regexp.prototype.flags "^1.3.0"
+    side-channel "^1.0.3"
+
+string.prototype.trimend@^1.0.1:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz#a22bd53cca5c7cf44d7c9d5c732118873d6cd18b"
+  integrity sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==
+  dependencies:
+    call-bind "^1.0.0"
+    define-properties "^1.1.3"
+
+string.prototype.trimstart@^1.0.1:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz#9b4cb590e123bb36564401d59824298de50fd5aa"
+  integrity sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==
+  dependencies:
+    call-bind "^1.0.0"
+    define-properties "^1.1.3"
+
 strip-ansi@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
@@ -3252,6 +4438,11 @@ strip-ansi@^6.0.0:
   dependencies:
     ansi-regex "^5.0.0"
 
+strip-bom@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
+  integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
+
 strip-bom@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
@@ -3267,6 +4458,11 @@ strip-final-newline@^2.0.0:
   resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
   integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
 
+strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+  integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
 supports-color@^5.3.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -3294,6 +4490,16 @@ symbol-tree@^3.2.4:
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
   integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
 
+table@^6.0.4:
+  version "6.0.4"
+  resolved "https://registry.yarnpkg.com/table/-/table-6.0.4.tgz#c523dd182177e926c723eb20e1b341238188aa0d"
+  integrity sha512-sBT4xRLdALd+NFBvwOz8bw4b15htyythha+q+DVZqy2RS08PPC8O2sZFgJYEY7bJvbCFKccs+WIZ/cd+xxTWCw==
+  dependencies:
+    ajv "^6.12.4"
+    lodash "^4.17.20"
+    slice-ansi "^4.0.0"
+    string-width "^4.2.0"
+
 terminal-link@^2.0.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
@@ -3311,6 +4517,11 @@ test-exclude@^6.0.0:
     glob "^7.1.4"
     minimatch "^3.0.4"
 
+text-table@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
+  integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
+
 throat@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
@@ -3382,10 +4593,10 @@ tr46@^2.0.2:
   dependencies:
     punycode "^2.1.1"
 
-ts-jest@^26.4.1:
-  version "26.4.1"
-  resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.4.1.tgz#08ec0d3fc2c3a39e4a46eae5610b69fafa6babd0"
-  integrity sha512-F4aFq01aS6mnAAa0DljNmKr/Kk9y4HVZ1m6/rtJ0ED56cuxINGq3Q9eVAh+z5vcYKe5qnTMvv90vE8vUMFxomg==
+ts-jest@^26.4.4:
+  version "26.4.4"
+  resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.4.4.tgz#61f13fb21ab400853c532270e52cc0ed7e502c49"
+  integrity sha512-3lFWKbLxJm34QxyVNNCgXX1u4o/RV0myvA2y2Bxm46iGIjKlaY0own9gIckbjZJPn+WaJEnfPPJ20HHGpoq4yg==
   dependencies:
     "@types/jest" "26.x"
     bs-logger "0.x"
@@ -3399,6 +4610,28 @@ ts-jest@^26.4.1:
     semver "7.x"
     yargs-parser "20.x"
 
+tsconfig-paths@^3.9.0:
+  version "3.9.0"
+  resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b"
+  integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==
+  dependencies:
+    "@types/json5" "^0.0.29"
+    json5 "^1.0.1"
+    minimist "^1.2.0"
+    strip-bom "^3.0.0"
+
+tslib@^1.8.1:
+  version "1.14.1"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
+  integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+
+tsutils@^3.17.1:
+  version "3.17.1"
+  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
+  integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==
+  dependencies:
+    tslib "^1.8.1"
+
 tunnel-agent@^0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
@@ -3411,6 +4644,13 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
   resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
   integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
 
+type-check@^0.4.0, type-check@~0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
+  integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+  dependencies:
+    prelude-ls "^1.2.1"
+
 type-check@~0.3.2:
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
@@ -3445,10 +4685,10 @@ typedarray-to-buffer@^3.1.5:
   dependencies:
     is-typedarray "^1.0.0"
 
-typescript@^4.0.3:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.3.tgz#153bbd468ef07725c1df9c77e8b453f8d36abba5"
-  integrity sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==
+typescript@^4.1.3:
+  version "4.1.3"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"
+  integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
 
 union-value@^1.0.0:
   version "1.0.1"
@@ -3491,14 +4731,19 @@ uuid@^3.3.2:
   integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
 
 uuid@^8.3.0:
-  version "8.3.0"
-  resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea"
-  integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==
+  version "8.3.2"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
+  integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
 
-v8-to-istanbul@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-5.0.1.tgz#0608f5b49a481458625edb058488607f25498ba5"
-  integrity sha512-mbDNjuDajqYe3TXFk5qxcQy8L1msXNE37WTlLoqqpBfRsimbNcrlhQlDPntmECEcUvdC+AQ8CyMMf6EUx1r74Q==
+v8-compile-cache@^2.0.3:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132"
+  integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==
+
+v8-to-istanbul@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz#b4fe00e35649ef7785a9b7fcebcea05f37c332fc"
+  integrity sha512-fLL2rFuQpMtm9r8hrAV2apXX/WqHJ6+IC4/eQVdMDGBUgH/YMV4Gv3duk3kjmyg6uiQWBAA9nJwue4iJUOkHeA==
   dependencies:
     "@types/istanbul-lib-coverage" "^2.0.1"
     convert-source-map "^1.6.0"
@@ -3565,9 +4810,9 @@ whatwg-mimetype@^2.3.0:
   integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
 
 whatwg-url@^8.0.0:
-  version "8.2.2"
-  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.2.2.tgz#85e7f9795108b53d554cec640b2e8aee2a0d4bfd"
-  integrity sha512-PcVnO6NiewhkmzV0qn7A+UZ9Xx4maNTI+O+TShmfE4pqjoCMwUMjkvoNhNHPTvgR7QH9Xt3R13iHuWy2sToFxQ==
+  version "8.4.0"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.4.0.tgz#50fb9615b05469591d2b2bd6dfaed2942ed72837"
+  integrity sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==
   dependencies:
     lodash.sortby "^4.7.0"
     tr46 "^2.0.2"
@@ -3592,7 +4837,7 @@ which@^2.0.1, which@^2.0.2:
   dependencies:
     isexe "^2.0.0"
 
-word-wrap@~1.2.3:
+word-wrap@^1.2.3, word-wrap@~1.2.3:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
   integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
@@ -3622,9 +4867,9 @@ write-file-atomic@^3.0.0:
     typedarray-to-buffer "^3.1.5"
 
 ws@^7.2.3:
-  version "7.3.1"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8"
-  integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==
+  version "7.4.1"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.1.tgz#a333be02696bd0e54cea0434e21dcc8a9ac294bb"
+  integrity sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==
 
 xml-name-validator@^3.0.0:
   version "3.0.0"
@@ -3637,14 +4882,19 @@ xmlchars@^2.2.0:
   integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
 
 y18n@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
+  integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==
+
+yallist@^4.0.0:
   version "4.0.0"
-  resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
-  integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
+  resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+  integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
 
 yargs-parser@20.x:
-  version "20.2.0"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.0.tgz#944791ca2be2e08ddadd3d87e9de4c6484338605"
-  integrity sha512-2agPoRFPoIcFzOIp6656gcvsg2ohtscpw2OINr/q46+Sq41xz2OYLqx5HRHabmFU1OARIPAYH5uteICE7mn/5A==
+  version "20.2.4"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
+  integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==
 
 yargs-parser@^18.1.2:
   version "18.1.3"
@@ -3654,7 +4904,7 @@ yargs-parser@^18.1.2:
     camelcase "^5.0.0"
     decamelize "^1.2.0"
 
-yargs@^15.3.1:
+yargs@^15.4.1:
   version "15.4.1"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
   integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
index a09fb8eb1bb51e0d71a4686d17473be91299edbc..5342f8b10009a948e166d8a22f30e03135d42588 100644 (file)
@@ -15,6 +15,7 @@ services:
       - pictrs
       - postgres
       - iframely
+
   lemmy-ui:
     image: dessalines/lemmy-ui:v0.8.10
     ports:
index d42826e06d6123dd8d53af3b2235d69e94387a03..e9473a29027108716c28c503ac86ef581f05f2b3 100755 (executable)
@@ -1 +1,4 @@
-docker exec -it dev_lemmy_db_1 pg_dumpall -c -U rrr > dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql
+#!/bin/bash
+pushd dev
+docker-compose exec postgres pg_dumpall -c -U lemmy > dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql
+popd
index 003f88dc027b1712e28d027c461a838e04cb85a9..357b87c90ec952ef4b9e8a08b851732052486818 100644 (file)
@@ -28,6 +28,9 @@ http {
             if ($http_accept = "application/activity+json") {
               set $proxpass http://lemmy-alpha;
             }
+            if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
+              set $proxpass http://lemmy-alpha;
+            }
             proxy_pass $proxpass;
 
             proxy_set_header X-Real-IP $remote_addr;
@@ -70,6 +73,9 @@ http {
             if ($http_accept = "application/activity+json") {
               set $proxpass http://lemmy-beta;
             }
+            if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
+              set $proxpass http://lemmy-beta;
+            }
             proxy_pass $proxpass;
 
             proxy_set_header X-Real-IP $remote_addr;
@@ -112,6 +118,9 @@ http {
             if ($http_accept = "application/activity+json") {
               set $proxpass http://lemmy-gamma;
             }
+            if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
+              set $proxpass http://lemmy-gamma;
+            }
             proxy_pass $proxpass;
 
             proxy_set_header X-Real-IP $remote_addr;
@@ -154,6 +163,9 @@ http {
             if ($http_accept = "application/activity+json") {
               set $proxpass http://lemmy-delta;
             }
+            if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
+              set $proxpass http://lemmy-delta;
+            }
             proxy_pass $proxpass;
 
             proxy_set_header X-Real-IP $remote_addr;
@@ -196,6 +208,9 @@ http {
             if ($http_accept = "application/activity+json") {
               set $proxpass http://lemmy-epsilon;
             }
+            if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
+              set $proxpass http://lemmy-epsilon;
+            }
             proxy_pass $proxpass;
 
             proxy_set_header X-Real-IP $remote_addr;
index 93f6c42d0692f9cd8a5abd6123983989b8ba78e8..c8b76f7c3c1a9b07da782491d3046ed9160beb52 100644 (file)
@@ -19,7 +19,8 @@
   - [Docker Development](contributing/docker_development.md)
   - [Local Development](contributing/local_development.md)
   - [Theming Guide](contributing/theming.md)
-  - [Tests](contributing/tests.md)
   - [Websocket/HTTP API](contributing/websocket_http_api.md)
+  - [Creating a Custom Frontend](contributing/custom_frontend.md)
+  - [Tests](contributing/tests.md)
   - [Federation Development](contributing/federation_development.md)
 - [Lemmy Council](lemmy_council.md)
index e7d8e8dc6b2a72f51a8c737481c9dfe9a3305e91..0ce019fcfd69938032207012a06eba766cca0b74 100644 (file)
@@ -36,3 +36,4 @@
 - [Rust docker build](https://shaneutt.com/blog/rust-fast-small-docker-image-builds/)
 - [Zurb mentions](https://github.com/zurb/tribute)
 - [TippyJS](https://github.com/atomiks/tippyjs)
+- [SQL function indexes](https://sorentwo.com/2013/12/30/let-postgres-do-the-work.html)
index 3211859bf17580fedae91e926e025988309ecb49..db4e7db1bbc4b4ae89a450622e0ecd12075a00e9 100644 (file)
@@ -4,8 +4,8 @@ Information for Lemmy instance admins, and those who want to run a server.
 
 ## Install
 
-Lemmy has two primary install methods, [docker](install_docker.md), and [ansible](install_ansible.md). Ansible simplifies deploying to a remote server, while docker is best for local testing.
+Lemmy has two primary installation methods, [manually with Docker](install_docker.md), and [automated with Ansible](install_ansible.md). We recommend using Ansible, because it simplifies the installation and also makes updating easier.
 
-### Manual install
+### Manual install (without Docker)
 
 Manual installs are *possible*, but not preferred, since Lemmy is dependent on other local services: The [lemmy-ui](https://github.com/LemmyNet/lemmy-ui), [a Postgresql Database](https://www.postgresql.org/), [pict-rs](https://git.asonix.dog/asonix/pict-rs/) for images, and [iframely](https://iframely.com/) for embeds. To see how these are wired together, look at the docker-compose.yml files. Due to the complexity of different systems, we will not support manual installs.
diff --git a/docs/src/contributing/custom_frontend.md b/docs/src/contributing/custom_frontend.md
new file mode 100644 (file)
index 0000000..0856857
--- /dev/null
@@ -0,0 +1,66 @@
+# Creating a Custom Frontend
+
+The backend and frontend are completely decoupled, and run in independent Docker containers. They only communicate over the [Lemmy API](websocket_http_api.md), which makes it quite easy to write alternative frontends.
+
+This creates a lot of potential for custom frontends, which could change much of the design and user experience of Lemmy. For example, it would be possible to create a frontend in the style of a traditional forum like [phpBB](https://www.phpbb.com/), or a question-and-answer site like [stackoverflow](https://stackoverflow.com/). All without having to think about database queries, authentification or ActivityPub, which you essentially get for free.
+
+## Development
+
+You can use any language to create a custom frontend. The easiest option would be forking our [official frontend](https://github.com/LemmyNet/lemmy-ui), [lemmy-lite](https://github.com/IronOxidizer/lemmy-lite), or the [lemmy-frontend-example](https://github.com/LemmyNet/lemmy-front-end-example). In any case, the principle is the same: bind to `LEMMY_EXTERNAL_HOST` (default: `localhost:8536`) and handle requests using the Lemmy API at `LEMMY_INTERNAL_HOST` (default: `lemmy:8536`). Also use `LEMMY_HTTPS` to generate links with the correct protocol.
+
+The next step is building a Docker image from your frontend. If you forked an existing project, it should already include a `Dockerfile` and instructions to build it. Otherwise, try searching for your language on [dockerhub](https://hub.docker.com/), official images usually have build instructions in their readme. Build a Docker image with a tag, then look for the following section in `docker/dev/docker-compose.yml`:
+
+```
+  lemmy-ui:
+    image: dessalines/lemmy-ui:v0.8.10
+    ports:
+      - "1235:1234"
+    restart: always
+    environment:
+      - LEMMY_INTERNAL_HOST=lemmy:8536
+      - LEMMY_EXTERNAL_HOST=localhost:8536
+      - LEMMY_HTTPS=false
+    depends_on: 
+      - lemmy
+```
+
+All you need to do is replace the value for `image` with the tag of your own Docker image (and possibly the environment variables if you need different ones). Then run `./docker_update.sh`, and after compilation, your frontend will be available on `http://localhost:1235`. You can also make the same change to `docker/federation/docker-compose.yml` and run `./start-local-instances.bash` to test federation with your frontend.
+
+## Deploy with Docker
+
+After building the Docker image, you need to push it to a Docker registry (such as [dockerhub](https://hub.docker.com/)). Then update the `docker-compose.yml` on your server, replacing the `image` for `lemmy-ui`, just as described above. Run `docker-compose.yml`, and after a short wait, your instance will use the new frontend.
+
+Note, if your instance is deployed with Ansible, it will override `docker-compose.yml` with every run, reverting back to the default frontend. In that case you should copy the `ansible/` folder from this project to your own repository, and adjust `docker-compose.yml` directly in the repo.
+
+It is also possible to use multiple frontends for the same Lemmy instance, either using subdomains or subfolders. To do that, don't edit the `lemmy-ui` section in `docker-compose.yml`, but duplicate it, adjusting the name, image and port so they are distinct for each. Then edit your nginx config to pass requests to the appropriate frontend, depending on the subdomain or path.
+
+## Translations
+
+You can add the [lemmy-translations](https://github.com/LemmyNet/lemmy-translations) repository to your project as a [git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules). That way you can take advantage of same translations used in the official frontend, and you will also receive new translations contributed via weblate.
+
+## Rate limiting
+
+Lemmy does rate limiting for many actions based on the client IP. But if you make any API calls on the server side (eg in the case of server-side rendering, or javascript pre-rendering), Lemmy will take the IP of the Docker container. Meaning that all requests come from the same IP, and get rate limited much earlier. To avoid this problem, you need to pass the headers `X-REAL-IP` and `X-FORWARDED-FOR` on to Lemmy (the headers are set by our nginx config).
+
+Here is an example snipped for NodeJS:
+
+```javascript
+function setForwardedHeaders(
+  headers: IncomingHttpHeaders
+): { [key: string]: string } {
+  let out = {
+    host: headers.host,
+  };
+  if (headers['x-real-ip']) {
+    out['x-real-ip'] = headers['x-real-ip'];
+  }
+  if (headers['x-forwarded-for']) {
+    out['x-forwarded-for'] = headers['x-forwarded-for'];
+  }
+
+  return out;
+}
+
+let headers = setForwardedHeaders(req.headers);
+let client = new LemmyHttp(httpUri, headers);
+```
\ No newline at end of file
index 28e304d5a43bed3710a1da7f504a1d9813438d11..070e7f13887db5c7dbf19c751c89c8151a8eebdf 100644 (file)
@@ -1,6 +1,6 @@
 # Federation
 
-Lemmy uses the ActivityPub protocol (a W3C standard) to enable federation between different servers (often called instances). This is very similar to the way email works. For example, if you use gmail.com, then you can't just send mails to other gmail.com users, but also to yahoo.com, yandex.ru and so on. Email uses the SMTP protocol to achieve this, so you can think of ActivityPub as "SMTP for social media". The amount of different actions possible on social media (post, comment, like, share, etc) means that ActivityPub is much more complicated than SMTP.
+Lemmy uses the ActivityPub protocol (a W3C standard) to enable federation between different servers (often called instances). This is very similar to the way email works. For example, if you use gmail.com, then you can not only send mails to other gmail.com users, but also to yahoo.com, yandex.ru and so on. Email uses the SMTP protocol to achieve this, so you can think of ActivityPub as "SMTP for social media". The amount of different actions possible on social media (post, comment, like, share, etc) means that ActivityPub is much more complicated than SMTP.
 
 As with email, ActivityPub federation happens only between servers. So if you are registered on `enterprise.lemmy.ml`, you only connect to the API of `enterprise.lemmy.ml`, while the server takes care of sending and receiving data from other instances (eg `voyager.lemmy.ml`). The great advantage of this approach is that the average user doesn't have to do anything to use federation. In fact if you are using Lemmy, you are likely already using it. One way to confirm is by going to a community or user profile. If you are on `enterprise.lemmy.ml` and you see a user like `@nutomic@voyager.lemmy.ml`, or a community like `!main@ds9.lemmy.ml`, then those are federated, meaning they use a different instance from yours.
 
@@ -11,4 +11,6 @@ One way you can take advantage of federation is by opening a different instance,
 - `https://lemmy.ml/c/programming` (Community)
 - `https://lemmy.ml/u/nutomic` (User)
 - `https://lemmy.ml/post/123` (Post)
-- `https://lemmy.ml/comment/321` (Comment)
\ No newline at end of file
+- `https://lemmy.ml/comment/321` (Comment)
+
+You can see the list of linked instances by following the "Instances" link at the bottom of any Lemmy page.
\ No newline at end of file
index 75dfe263469aba85cec9e8fe45bd7fc1e39da8f9..feb2324b955c3549f705cd9ce91f680e07993b3f 100644 (file)
@@ -440,7 +440,7 @@ Sent to: Community
     "cc": [
         "https://ds9.lemmy.ml/c/main/"
     ],
-    "object": ...
+    "object": "https://enterprise.lemmy.ml/p/123"
 }
 ```
 
@@ -467,7 +467,7 @@ Sent to: Community
     "cc": [
       "https://ds9.lemmy.ml/c/main/"
     ],
-    "object": ...
+    "object": "https://enterprise.lemmy.ml/p/123"
 }
 ```
 
index 7bcb30892d6ccbd5babfe0636cdc4144694ddc46..a495d3b7afa4d76a3e9f6ba9e856f001fb01b9ac 100644 (file)
@@ -77,3 +77,4 @@ General Contact [@LemmyDev Mastodon](https://mastodon.social/@LemmyDev)
 - [fruechtchen](https://lemmy.ml/u/fruechtchen)
 - [kixiQu](https://lemmy.ml/u/kixiQu)
 - [Karanja](https://baraza.africa/u/mwalimu)
+- [zedi](https://lemmy.161.social/u/zedi)
index 77c34e13fbcb93c5c0a4fe28742fe048c2947fee..94fd2d500892b73360ed9c34cbe86bc945c4adaa 100644 (file)
@@ -15,34 +15,34 @@ lemmy_db = { path = "../lemmy_db" }
 lemmy_structs = { path = "../lemmy_structs" }
 lemmy_rate_limit = { path = "../lemmy_rate_limit" }
 lemmy_websocket = { path = "../lemmy_websocket" }
-diesel = "1.4"
-bcrypt = "0.8"
-chrono = { version = "0.4", features = ["serde"] }
-serde_json = { version = "1.0", features = ["preserve_order"]}
-serde = { version = "1.0", features = ["derive"] }
-actix = "0.10"
-actix-web = { version = "3.0", default-features = false }
-actix-rt = { version = "1.1", default-features = false }
-awc = { version = "2.0", default-features = false }
-log = "0.4"
-rand = "0.7"
-strum = "0.19"
-strum_macros = "0.19"
-jsonwebtoken = "7.0"
-lazy_static = "1.3"
-url = { version = "2.1", features = ["serde"] }
-openssl = "0.10"
-http = "0.2"
-http-signature-normalization-actix = { version = "0.4", default-features = false, features = ["sha-2"] }
-base64 = "0.13"
-tokio = "0.3"
-futures = "0.3"
-itertools = "0.9"
-uuid = { version = "0.8", features = ["serde", "v4"] }
-sha2 = "0.9"
-async-trait = "0.1"
-captcha = "0.0"
-anyhow = "1.0"
-thiserror = "1.0"
-background-jobs = " 0.8"
-reqwest = { version = "0.10", features = ["json"] }
+diesel = "1.4.5"
+bcrypt = "0.9.0"
+chrono = { version = "0.4.19", features = ["serde"] }
+serde_json = { version = "1.0.60", features = ["preserve_order"] }
+serde = { version = "1.0.118", features = ["derive"] }
+actix = "0.10.0"
+actix-web = { version = "3.3.2", default-features = false }
+actix-rt = { version = "1.1.1", default-features = false }
+awc = { version = "2.0.3", default-features = false }
+log = "0.4.11"
+rand = "0.7.3"
+strum = "0.20.0"
+strum_macros = "0.20.1"
+jsonwebtoken = "7.2.0"
+lazy_static = "1.4.0"
+url = { version = "2.2.0", features = ["serde"] }
+openssl = "0.10.31"
+http = "0.2.2"
+http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] }
+base64 = "0.13.0"
+tokio = "0.3.6"
+futures = "0.3.8"
+itertools = "0.9.0"
+uuid = { version = "0.8.1", features = ["serde", "v4"] }
+sha2 = "0.9.2"
+async-trait = "0.1.42"
+captcha = "0.0.8"
+anyhow = "1.0.35"
+thiserror = "1.0.22"
+background-jobs = "0.8.0"
+reqwest = { version = "0.10.10", features = ["json"] }
index f475f1dfe763d4a8d8aa4bf5cdbeae78308c7118..4a0ab12b81c93509b983af031df54c3354e74ed6 100644 (file)
@@ -1,5 +1,5 @@
 use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation};
-use lemmy_db::user::User_;
+use lemmy_db::source::user::User_;
 use lemmy_utils::settings::Settings;
 use serde::{Deserialize, Serialize};
 
index e74fa808e6ef30ce76e074156d0a3b0463cf213d..e5f079ad2ae395aa556d41315ac5eedc2117e2dc 100644 (file)
@@ -1,5 +1,6 @@
 use crate::{
   check_community_ban,
+  check_downvotes_enabled,
   collect_moderated_communities,
   get_post,
   get_user_from_jwt,
@@ -10,13 +11,15 @@ use crate::{
 use actix_web::web::Data;
 use lemmy_apub::{ApubLikeableType, ApubObjectType};
 use lemmy_db::{
-  comment::*,
-  comment_report::*,
-  comment_view::*,
-  moderator::*,
-  post::*,
-  site_view::*,
-  user::*,
+  source::{
+    comment::{Comment, CommentForm, CommentLike, CommentLikeForm, CommentSaved, CommentSavedForm},
+    comment_report::{CommentReport, CommentReportForm},
+    moderator::{ModRemoveComment, ModRemoveCommentForm},
+  },
+  views::{
+    comment_report_view::{CommentReportQueryBuilder, CommentReportView},
+    comment_view::{CommentQueryBuilder, CommentView},
+  },
   Crud,
   Likeable,
   ListingType,
@@ -105,6 +108,7 @@ impl Perform for CreateComment {
     updated_comment.send_create(&user, context).await?;
 
     // Scan the comment for user mentions, add those rows
+    let post_id = post.id;
     let mentions = scrape_text_for_mentions(&comment_form.content);
     let recipient_ids = send_local_notifs(
       mentions,
@@ -119,7 +123,7 @@ impl Perform for CreateComment {
     // You like your own comment by default
     let like_form = CommentLikeForm {
       comment_id: inserted_comment.id,
-      post_id: data.post_id,
+      post_id,
       user_id: user.id,
       score: 1,
     };
@@ -138,7 +142,7 @@ impl Perform for CreateComment {
     .await??;
 
     let mut res = CommentResponse {
-      comment: comment_view,
+      comment_view,
       recipient_ids,
       form_id: data.form_id.to_owned(),
     };
@@ -151,6 +155,7 @@ impl Perform for CreateComment {
 
     // strip out the recipient_ids, so that
     // users don't get double notifs
+    // TODO Do this in a different way
     res.recipient_ids = Vec::new();
 
     Ok(res)
@@ -175,10 +180,10 @@ impl Perform for EditComment {
     })
     .await??;
 
-    check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
+    check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
 
     // Verify that only the creator can edit
-    if user.id != orig_comment.creator_id {
+    if user.id != orig_comment.creator.id {
       return Err(APIError::err("no_comment_edit_allowed").into());
     }
 
@@ -198,16 +203,13 @@ impl Perform for EditComment {
     updated_comment.send_update(&user, context).await?;
 
     // Do the mentions / recipients
-    let post_id = orig_comment.post_id;
-    let post = get_post(post_id, context.pool()).await?;
-
     let updated_comment_content = updated_comment.content.to_owned();
     let mentions = scrape_text_for_mentions(&updated_comment_content);
     let recipient_ids = send_local_notifs(
       mentions,
       updated_comment,
       &user,
-      post,
+      orig_comment.post,
       context.pool(),
       false,
     )
@@ -221,7 +223,7 @@ impl Perform for EditComment {
     .await??;
 
     let mut res = CommentResponse {
-      comment: comment_view,
+      comment_view,
       recipient_ids,
       form_id: data.form_id.to_owned(),
     };
@@ -234,6 +236,7 @@ impl Perform for EditComment {
 
     // strip out the recipient_ids, so that
     // users don't get double notifs
+    // TODO again
     res.recipient_ids = Vec::new();
 
     Ok(res)
@@ -258,10 +261,10 @@ impl Perform for DeleteComment {
     })
     .await??;
 
-    check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
+    check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
 
     // Verify that only the creator can delete
-    if user.id != orig_comment.creator_id {
+    if user.id != orig_comment.creator.id {
       return Err(APIError::err("no_comment_edit_allowed").into());
     }
 
@@ -292,23 +295,22 @@ impl Perform for DeleteComment {
     .await??;
 
     // Build the recipients
-    let post_id = comment_view.post_id;
-    let post = get_post(post_id, context.pool()).await?;
+    let comment_view_2 = comment_view.clone();
     let mentions = vec![];
     let recipient_ids = send_local_notifs(
       mentions,
       updated_comment,
       &user,
-      post,
+      comment_view_2.post,
       context.pool(),
       false,
     )
     .await?;
 
     let mut res = CommentResponse {
-      comment: comment_view,
+      comment_view,
       recipient_ids,
-      form_id: None,
+      form_id: None, // TODO a comment delete might clear forms?
     };
 
     context.chat_server().do_send(SendComment {
@@ -319,6 +321,7 @@ impl Perform for DeleteComment {
 
     // strip out the recipient_ids, so that
     // users don't get double notifs
+    // TODO again
     res.recipient_ids = Vec::new();
 
     Ok(res)
@@ -343,10 +346,10 @@ impl Perform for RemoveComment {
     })
     .await??;
 
-    check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
+    check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
 
     // Verify that only a mod or admin can remove
-    is_mod_or_admin(context.pool(), user.id, orig_comment.community_id).await?;
+    is_mod_or_admin(context.pool(), user.id, orig_comment.community.id).await?;
 
     // Do the remove
     let removed = data.removed;
@@ -387,23 +390,23 @@ impl Perform for RemoveComment {
     .await??;
 
     // Build the recipients
-    let post_id = comment_view.post_id;
-    let post = get_post(post_id, context.pool()).await?;
+    let comment_view_2 = comment_view.clone();
+
     let mentions = vec![];
     let recipient_ids = send_local_notifs(
       mentions,
       updated_comment,
       &user,
-      post,
+      comment_view_2.post,
       context.pool(),
       false,
     )
     .await?;
 
     let mut res = CommentResponse {
-      comment: comment_view,
+      comment_view,
       recipient_ids,
-      form_id: None,
+      form_id: None, // TODO maybe this might clear other forms
     };
 
     context.chat_server().do_send(SendComment {
@@ -414,6 +417,7 @@ impl Perform for RemoveComment {
 
     // strip out the recipient_ids, so that
     // users don't get double notifs
+    // TODO again
     res.recipient_ids = Vec::new();
 
     Ok(res)
@@ -432,41 +436,23 @@ impl Perform for MarkCommentAsRead {
     let data: &MarkCommentAsRead = &self;
     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
 
-    let edit_id = data.edit_id;
+    let comment_id = data.comment_id;
     let orig_comment = blocking(context.pool(), move |conn| {
-      CommentView::read(&conn, edit_id, None)
+      CommentView::read(&conn, comment_id, None)
     })
     .await??;
 
-    check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
+    check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
 
     // Verify that only the recipient can mark as read
-    // Needs to fetch the parent comment / post to get the recipient
-    let parent_id = orig_comment.parent_id;
-    match parent_id {
-      Some(pid) => {
-        let parent_comment = blocking(context.pool(), move |conn| {
-          CommentView::read(&conn, pid, None)
-        })
-        .await??;
-        if user.id != parent_comment.creator_id {
-          return Err(APIError::err("no_comment_edit_allowed").into());
-        }
-      }
-      None => {
-        let parent_post_id = orig_comment.post_id;
-        let parent_post =
-          blocking(context.pool(), move |conn| Post::read(conn, parent_post_id)).await??;
-        if user.id != parent_post.creator_id {
-          return Err(APIError::err("no_comment_edit_allowed").into());
-        }
-      }
+    if user.id != orig_comment.get_recipient_id() {
+      return Err(APIError::err("no_comment_edit_allowed").into());
     }
 
     // Do the mark as read
     let read = data.read;
     match blocking(context.pool(), move |conn| {
-      Comment::update_read(conn, edit_id, read)
+      Comment::update_read(conn, comment_id, read)
     })
     .await?
     {
@@ -475,7 +461,7 @@ impl Perform for MarkCommentAsRead {
     };
 
     // Refetch it
-    let edit_id = data.edit_id;
+    let edit_id = data.comment_id;
     let user_id = user.id;
     let comment_view = blocking(context.pool(), move |conn| {
       CommentView::read(conn, edit_id, Some(user_id))
@@ -483,7 +469,7 @@ impl Perform for MarkCommentAsRead {
     .await??;
 
     let res = CommentResponse {
-      comment: comment_view,
+      comment_view,
       recipient_ids: Vec::new(),
       form_id: None,
     };
@@ -529,7 +515,7 @@ impl Perform for SaveComment {
     .await??;
 
     Ok(CommentResponse {
-      comment: comment_view,
+      comment_view,
       recipient_ids: Vec::new(),
       form_id: None,
     })
@@ -551,12 +537,7 @@ impl Perform for CreateCommentLike {
     let mut recipient_ids = Vec::new();
 
     // Don't do a downvote if site has downvotes disabled
-    if data.score == -1 {
-      let site = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
-      if !site.enable_downvotes {
-        return Err(APIError::err("downvotes_disabled").into());
-      }
-    }
+    check_downvotes_enabled(data.score, context.pool()).await?;
 
     let comment_id = data.comment_id;
     let orig_comment = blocking(context.pool(), move |conn| {
@@ -564,34 +545,14 @@ impl Perform for CreateCommentLike {
     })
     .await??;
 
-    let post_id = orig_comment.post_id;
-    let post = get_post(post_id, context.pool()).await?;
-    check_community_ban(user.id, post.community_id, context.pool()).await?;
+    check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
 
-    let comment_id = data.comment_id;
-    let comment = blocking(context.pool(), move |conn| Comment::read(conn, comment_id)).await??;
-
-    // Add to recipient ids
-    match comment.parent_id {
-      Some(parent_id) => {
-        let parent_comment =
-          blocking(context.pool(), move |conn| Comment::read(conn, parent_id)).await??;
-        if parent_comment.creator_id != user.id {
-          let parent_user = blocking(context.pool(), move |conn| {
-            User_::read(conn, parent_comment.creator_id)
-          })
-          .await??;
-          recipient_ids.push(parent_user.id);
-        }
-      }
-      None => {
-        recipient_ids.push(post.creator_id);
-      }
-    }
+    // Add parent user to recipients
+    recipient_ids.push(orig_comment.get_recipient_id());
 
     let like_form = CommentLikeForm {
       comment_id: data.comment_id,
-      post_id,
+      post_id: orig_comment.post.id,
       user_id: user.id,
       score: data.score,
     };
@@ -604,6 +565,7 @@ impl Perform for CreateCommentLike {
     .await??;
 
     // Only add the like if the score isnt 0
+    let comment = orig_comment.comment;
     let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
     if do_add {
       let like_form2 = like_form.clone();
@@ -630,7 +592,7 @@ impl Perform for CreateCommentLike {
     .await??;
 
     let mut res = CommentResponse {
-      comment: liked_comment,
+      comment_view: liked_comment,
       recipient_ids,
       form_id: None,
     };
@@ -644,6 +606,7 @@ impl Perform for CreateCommentLike {
     // strip out the recipient_ids, so that
     // users don't get double notifs
     res.recipient_ids = Vec::new();
+    // TODO why
 
     Ok(res)
   }
@@ -673,8 +636,8 @@ impl Perform for GetComments {
       CommentQueryBuilder::create(conn)
         .listing_type(type_)
         .sort(&sort)
-        .for_community_id(community_id)
-        .for_community_name(community_name)
+        .community_id(community_id)
+        .community_name(community_name)
         .my_user_id(user_id)
         .page(page)
         .limit(limit)
@@ -714,17 +677,17 @@ impl Perform for CreateCommentReport {
 
     let user_id = user.id;
     let comment_id = data.comment_id;
-    let comment = blocking(context.pool(), move |conn| {
+    let comment_view = blocking(context.pool(), move |conn| {
       CommentView::read(&conn, comment_id, None)
     })
     .await??;
 
-    check_community_ban(user_id, comment.community_id, context.pool()).await?;
+    check_community_ban(user_id, comment_view.community.id, context.pool()).await?;
 
     let report_form = CommentReportForm {
       creator_id: user_id,
       comment_id,
-      original_comment_text: comment.content,
+      original_comment_text: comment_view.comment.content,
       reason: data.reason.to_owned(),
     };
 
@@ -749,7 +712,7 @@ impl Perform for CreateCommentReport {
     context.chat_server().do_send(SendModRoomMessage {
       op: UserOperation::CreateCommentReport,
       response: report,
-      community_id: comment.community_id,
+      community_id: comment_view.community.id,
       websocket_id,
     });
 
@@ -777,7 +740,7 @@ impl Perform for ResolveCommentReport {
     .await??;
 
     let user_id = user.id;
-    is_mod_or_admin(context.pool(), user_id, report.community_id).await?;
+    is_mod_or_admin(context.pool(), user_id, report.community.id).await?;
 
     let resolved = data.resolved;
     let resolve_fun = move |conn: &'_ _| {
@@ -801,7 +764,7 @@ impl Perform for ResolveCommentReport {
     context.chat_server().do_send(SendModRoomMessage {
       op: UserOperation::ResolveCommentReport,
       response: res.clone(),
-      community_id: report.community_id,
+      community_id: report.community.id,
       websocket_id,
     });
 
index 76242020256e286d211ed180b70ee95fcf2ae4fb..c5ac408295f47a251e34cb17755f6292922868cc 100644 (file)
@@ -10,16 +10,19 @@ use actix_web::web::Data;
 use anyhow::Context;
 use lemmy_apub::ActorType;
 use lemmy_db::{
-  comment::Comment,
-  comment_view::CommentQueryBuilder,
-  community::*,
-  community_view::*,
   diesel_option_overwrite,
-  moderator::*,
   naive_now,
-  post::Post,
-  site::*,
-  user_view::*,
+  source::{comment::Comment, community::*, moderator::*, post::Post, site::*},
+  views::{
+    comment_view::CommentQueryBuilder,
+    community::{
+      community_follower_view::CommunityFollowerView,
+      community_moderator_view::CommunityModeratorView,
+      community_view::{CommunityQueryBuilder, CommunityView},
+    },
+    user_view::UserViewSafe,
+  },
+  ApubObject,
   Bannable,
   Crud,
   Followable,
@@ -55,20 +58,22 @@ impl Perform for GetCommunity {
     let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
     let user_id = user.map(|u| u.id);
 
-    let name = data.name.to_owned().unwrap_or_else(|| "main".to_string());
-    let community = match data.id {
-      Some(id) => blocking(context.pool(), move |conn| Community::read(conn, id)).await??,
-      None => match blocking(context.pool(), move |conn| {
-        Community::read_from_name(conn, &name)
-      })
-      .await?
-      {
-        Ok(community) => community,
-        Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
-      },
+    let community_id = match data.id {
+      Some(id) => id,
+      None => {
+        let name = data.name.to_owned().unwrap_or_else(|| "main".to_string());
+        match blocking(context.pool(), move |conn| {
+          Community::read_from_name(conn, &name)
+        })
+        .await?
+        {
+          Ok(community) => community,
+          Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
+        }
+        .id
+      }
     };
 
-    let community_id = community.id;
     let community_view = match blocking(context.pool(), move |conn| {
       CommunityView::read(conn, community_id, user_id)
     })
@@ -78,7 +83,6 @@ impl Perform for GetCommunity {
       Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
     };
 
-    let community_id = community.id;
     let moderators: Vec<CommunityModeratorView> = match blocking(context.pool(), move |conn| {
       CommunityModeratorView::for_community(conn, community_id)
     })
@@ -95,7 +99,7 @@ impl Perform for GetCommunity {
       .unwrap_or(1);
 
     let res = GetCommunityResponse {
-      community: community_view,
+      community_view,
       moderators,
       online,
     };
@@ -129,7 +133,7 @@ impl Perform for CreateCommunity {
     let actor_id = make_apub_endpoint(EndpointType::Community, &data.name).to_string();
     let actor_id_cloned = actor_id.to_owned();
     let community_dupe = blocking(context.pool(), move |conn| {
-      Community::read_from_actor_id(conn, &actor_id_cloned)
+      Community::read_from_apub_id(conn, &actor_id_cloned)
     })
     .await?;
     if community_dupe.is_ok() {
@@ -175,6 +179,7 @@ impl Perform for CreateCommunity {
       Err(_e) => return Err(APIError::err("community_already_exists").into()),
     };
 
+    // The community creator becomes a moderator
     let community_moderator_form = CommunityModeratorForm {
       community_id: inserted_community.id,
       user_id: user.id,
@@ -185,6 +190,7 @@ impl Perform for CreateCommunity {
       return Err(APIError::err("community_moderator_already_exists").into());
     }
 
+    // Follow your own community
     let community_follower_form = CommunityFollowerForm {
       community_id: inserted_community.id,
       user_id: user.id,
@@ -202,9 +208,7 @@ impl Perform for CreateCommunity {
     })
     .await??;
 
-    Ok(CommunityResponse {
-      community: community_view,
-    })
+    Ok(CommunityResponse { community_view })
   }
 }
 
@@ -227,7 +231,7 @@ impl Perform for EditCommunity {
     let edit_id = data.edit_id;
     let mods: Vec<i32> = blocking(context.pool(), move |conn| {
       CommunityModeratorView::for_community(conn, edit_id)
-        .map(|v| v.into_iter().map(|m| m.user_id).collect())
+        .map(|v| v.into_iter().map(|m| m.moderator.id).collect())
     })
     .await??;
     if !mods.contains(&user.id) {
@@ -284,9 +288,7 @@ impl Perform for EditCommunity {
     })
     .await??;
 
-    let res = CommunityResponse {
-      community: community_view,
-    };
+    let res = CommunityResponse { community_view };
 
     send_community_websocket(&res, context, websocket_id, UserOperation::EditCommunity);
 
@@ -340,9 +342,7 @@ impl Perform for DeleteCommunity {
     })
     .await??;
 
-    let res = CommunityResponse {
-      community: community_view,
-    };
+    let res = CommunityResponse { community_view };
 
     send_community_websocket(&res, context, websocket_id, UserOperation::DeleteCommunity);
 
@@ -408,9 +408,7 @@ impl Perform for RemoveCommunity {
     })
     .await??;
 
-    let res = CommunityResponse {
-      community: community_view,
-    };
+    let res = CommunityResponse { community_view };
 
     send_community_websocket(&res, context, websocket_id, UserOperation::RemoveCommunity);
 
@@ -447,8 +445,8 @@ impl Perform for ListCommunities {
     let communities = blocking(context.pool(), move |conn| {
       CommunityQueryBuilder::create(conn)
         .sort(&sort)
-        .for_user(user_id)
         .show_nsfw(show_nsfw)
+        .my_user_id(user_id)
         .page(page)
         .limit(limit)
         .list()
@@ -519,12 +517,10 @@ impl Perform for FollowCommunity {
     // For now, just assume that remote follows are accepted.
     // Otherwise, the subscribed will be null
     if !community.local {
-      community_view.subscribed = Some(data.follow);
+      community_view.subscribed = data.follow;
     }
 
-    Ok(CommunityResponse {
-      community: community_view,
-    })
+    Ok(CommunityResponse { community_view })
   }
 }
 
@@ -591,28 +587,28 @@ impl Perform for BanFromCommunity {
     }
 
     // Remove/Restore their data if that's desired
-    if let Some(remove_data) = data.remove_data {
+    if data.remove_data {
       // Posts
       blocking(context.pool(), move |conn: &'_ _| {
-        Post::update_removed_for_creator(conn, banned_user_id, Some(community_id), remove_data)
+        Post::update_removed_for_creator(conn, banned_user_id, Some(community_id), true)
       })
       .await??;
 
       // Comments
-      // Diesel doesn't allow updates with joins, so this has to be a loop
+      // TODO Diesel doesn't allow updates with joins, so this has to be a loop
       let comments = blocking(context.pool(), move |conn| {
         CommentQueryBuilder::create(conn)
-          .for_creator_id(banned_user_id)
-          .for_community_id(community_id)
+          .creator_id(banned_user_id)
+          .community_id(community_id)
           .limit(std::i64::MAX)
           .list()
       })
       .await??;
 
-      for comment in &comments {
-        let comment_id = comment.id;
+      for comment_view in &comments {
+        let comment_id = comment_view.comment.id;
         blocking(context.pool(), move |conn: &'_ _| {
-          Comment::update_removed(conn, comment_id, remove_data)
+          Comment::update_removed(conn, comment_id, true)
         })
         .await??;
       }
@@ -640,12 +636,12 @@ impl Perform for BanFromCommunity {
 
     let user_id = data.user_id;
     let user_view = blocking(context.pool(), move |conn| {
-      UserView::get_user_secure(conn, user_id)
+      UserViewSafe::read(conn, user_id)
     })
     .await??;
 
     let res = BanFromCommunityResponse {
-      user: user_view,
+      user_view,
       banned: data.ban,
     };
 
@@ -748,17 +744,20 @@ impl Perform for TransferCommunity {
     })
     .await??;
 
-    let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
+    let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
 
+    // Making sure the creator, if an admin, is at the top
     let creator_index = admins
       .iter()
-      .position(|r| r.id == site_creator_id)
+      .position(|r| r.user.id == site_creator_id)
       .context(location_info!())?;
     let creator_user = admins.remove(creator_index);
     admins.insert(0, creator_user);
 
     // Make sure user is the creator, or an admin
-    if user.id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user.id) {
+    if user.id != read_community.creator_id
+      && !admins.iter().map(|a| a.user.id).any(|x| x == user.id)
+    {
       return Err(APIError::err("not_an_admin").into());
     }
 
@@ -777,7 +776,7 @@ impl Perform for TransferCommunity {
     .await??;
     let creator_index = community_mods
       .iter()
-      .position(|r| r.user_id == data.user_id)
+      .position(|r| r.moderator.id == data.user_id)
       .context(location_info!())?;
     let creator_user = community_mods.remove(creator_index);
     community_mods.insert(0, creator_user);
@@ -791,8 +790,8 @@ impl Perform for TransferCommunity {
     // TODO: this should probably be a bulk operation
     for cmod in &community_mods {
       let community_moderator_form = CommunityModeratorForm {
-        community_id: cmod.community_id,
-        user_id: cmod.user_id,
+        community_id: cmod.community.id,
+        user_id: cmod.moderator.id,
       };
 
       let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
@@ -836,7 +835,7 @@ impl Perform for TransferCommunity {
 
     // Return the jwt
     Ok(GetCommunityResponse {
-      community: community_view,
+      community_view,
       moderators,
       online: 0,
     })
@@ -849,15 +848,16 @@ fn send_community_websocket(
   websocket_id: Option<ConnectionId>,
   op: UserOperation,
 ) {
+  // TODO is there any way around this?
   // Strip out the user id and subscribed when sending to others
-  let mut res_sent = res.clone();
-  res_sent.community.user_id = None;
-  res_sent.community.subscribed = None;
+  // let mut res_sent = res.clone();
+  // res_sent.community_view.user_id = None;
+  // res_sent.community.subscribed = None;
 
   context.chat_server().do_send(SendCommunityRoomMessage {
     op,
-    response: res_sent,
-    community_id: res.community.id,
+    response: res.to_owned(),
+    community_id: res.community_view.community.id,
     websocket_id,
   });
 }
index 06b629c772cd78e592c91ba802e67f603d93f46e..eadb0d1ac9278e8e58318b09662173d61510daba 100644 (file)
@@ -1,10 +1,13 @@
 use crate::claims::Claims;
 use actix_web::{web, web::Data};
 use lemmy_db::{
-  community::{Community, CommunityModerator},
-  community_view::CommunityUserBanView,
-  post::Post,
-  user::User_,
+  source::{
+    community::{Community, CommunityModerator},
+    post::Post,
+    site::Site,
+    user::User_,
+  },
+  views::community::community_user_ban_view::CommunityUserBanView,
   Crud,
   DbPool,
 };
@@ -100,6 +103,16 @@ pub(crate) async fn check_community_ban(
   }
 }
 
+pub(crate) async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
+  if score == -1 {
+    let site = blocking(pool, move |conn| Site::read_simple(conn)).await??;
+    if !site.enable_downvotes {
+      return Err(APIError::err("downvotes_disabled").into());
+    }
+  }
+  Ok(())
+}
+
 /// Returns a list of communities that the user moderates
 /// or if a community_id is supplied validates the user is a moderator
 /// of that community and returns the community id in a vec
index 298076f75bbdcf1dd4dc9d9981b9e7d36d98ef31..f021b0d7ce2bc4545e423f11b392fcaf6fc34ff2 100644 (file)
@@ -1,5 +1,6 @@
 use crate::{
   check_community_ban,
+  check_downvotes_enabled,
   check_optional_url,
   collect_moderated_communities,
   get_user_from_jwt,
@@ -10,14 +11,18 @@ use crate::{
 use actix_web::web::Data;
 use lemmy_apub::{ApubLikeableType, ApubObjectType};
 use lemmy_db::{
-  comment_view::*,
-  community_view::*,
-  moderator::*,
   naive_now,
-  post::*,
-  post_report::*,
-  post_view::*,
-  site_view::*,
+  source::{
+    moderator::*,
+    post::*,
+    post_report::{PostReport, PostReportForm},
+  },
+  views::{
+    comment_view::CommentQueryBuilder,
+    community::community_moderator_view::CommunityModeratorView,
+    post_report_view::{PostReportQueryBuilder, PostReportView},
+    post_view::{PostQueryBuilder, PostView},
+  },
   Crud,
   Likeable,
   ListingType,
@@ -142,7 +147,7 @@ impl Perform for CreatePost {
       Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
     };
 
-    let res = PostResponse { post: post_view };
+    let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
       op: UserOperation::CreatePost,
@@ -180,20 +185,14 @@ impl Perform for GetPost {
     let id = data.id;
     let comments = blocking(context.pool(), move |conn| {
       CommentQueryBuilder::create(conn)
-        .for_post_id(id)
         .my_user_id(user_id)
+        .post_id(id)
         .limit(9999)
         .list()
     })
     .await??;
 
-    let community_id = post_view.community_id;
-    let community = blocking(context.pool(), move |conn| {
-      CommunityView::read(conn, community_id, user_id)
-    })
-    .await??;
-
-    let community_id = post_view.community_id;
+    let community_id = post_view.community.id;
     let moderators = blocking(context.pool(), move |conn| {
       CommunityModeratorView::for_community(conn, community_id)
     })
@@ -207,9 +206,8 @@ impl Perform for GetPost {
 
     // Return the jwt
     Ok(GetPostResponse {
-      post: post_view,
+      post_view,
       comments,
-      community,
       moderators,
       online,
     })
@@ -250,8 +248,8 @@ impl Perform for GetPosts {
         .listing_type(&type_)
         .sort(&sort)
         .show_nsfw(show_nsfw)
-        .for_community_id(community_id)
-        .for_community_name(community_name)
+        .community_id(community_id)
+        .community_name(community_name)
         .my_user_id(user_id)
         .page(page)
         .limit(limit)
@@ -280,12 +278,7 @@ impl Perform for CreatePostLike {
     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
 
     // Don't do a downvote if site has downvotes disabled
-    if data.score == -1 {
-      let site = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
-      if !site.enable_downvotes {
-        return Err(APIError::err("downvotes_disabled").into());
-      }
-    }
+    check_downvotes_enabled(data.score, context.pool()).await?;
 
     // Check for a community ban
     let post_id = data.post_id;
@@ -335,7 +328,7 @@ impl Perform for CreatePostLike {
       Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
     };
 
-    let res = PostResponse { post: post_view };
+    let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
       op: UserOperation::CreatePostLike,
@@ -428,7 +421,7 @@ impl Perform for EditPost {
     })
     .await??;
 
-    let res = PostResponse { post: post_view };
+    let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
       op: UserOperation::EditPost,
@@ -484,7 +477,7 @@ impl Perform for DeletePost {
     })
     .await??;
 
-    let res = PostResponse { post: post_view };
+    let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
       op: UserOperation::DeletePost,
@@ -551,7 +544,7 @@ impl Perform for RemovePost {
     })
     .await??;
 
-    let res = PostResponse { post: post_view };
+    let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
       op: UserOperation::RemovePost,
@@ -609,7 +602,7 @@ impl Perform for LockPost {
     })
     .await??;
 
-    let res = PostResponse { post: post_view };
+    let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
       op: UserOperation::LockPost,
@@ -671,7 +664,7 @@ impl Perform for StickyPost {
     })
     .await??;
 
-    let res = PostResponse { post: post_view };
+    let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
       op: UserOperation::StickyPost,
@@ -719,7 +712,7 @@ impl Perform for SavePost {
     })
     .await??;
 
-    Ok(PostResponse { post: post_view })
+    Ok(PostResponse { post_view })
   }
 }
 
@@ -769,19 +762,19 @@ impl Perform for CreatePostReport {
 
     let user_id = user.id;
     let post_id = data.post_id;
-    let post = blocking(context.pool(), move |conn| {
+    let post_view = blocking(context.pool(), move |conn| {
       PostView::read(&conn, post_id, None)
     })
     .await??;
 
-    check_community_ban(user_id, post.community_id, context.pool()).await?;
+    check_community_ban(user_id, post_view.community.id, context.pool()).await?;
 
     let report_form = PostReportForm {
       creator_id: user_id,
       post_id,
-      original_post_name: post.name,
-      original_post_url: post.url,
-      original_post_body: post.body,
+      original_post_name: post_view.post.name,
+      original_post_url: post_view.post.url,
+      original_post_body: post_view.post.body,
       reason: data.reason.to_owned(),
     };
 
@@ -806,7 +799,7 @@ impl Perform for CreatePostReport {
     context.chat_server().do_send(SendModRoomMessage {
       op: UserOperation::CreatePostReport,
       response: report,
-      community_id: post.community_id,
+      community_id: post_view.community.id,
       websocket_id,
     });
 
@@ -834,7 +827,7 @@ impl Perform for ResolvePostReport {
     .await??;
 
     let user_id = user.id;
-    is_mod_or_admin(context.pool(), user_id, report.community_id).await?;
+    is_mod_or_admin(context.pool(), user_id, report.community.id).await?;
 
     let resolved = data.resolved;
     let resolve_fun = move |conn: &'_ _| {
@@ -857,7 +850,7 @@ impl Perform for ResolvePostReport {
     context.chat_server().do_send(SendModRoomMessage {
       op: UserOperation::ResolvePostReport,
       response: res.clone(),
-      community_id: report.community_id,
+      community_id: report.community.id,
       websocket_id,
     });
 
index e4b1dd21330a8508eda289b448c7243555e24422..16c6ecec9bd7063a440ac791be5148f695858669 100644 (file)
@@ -10,17 +10,27 @@ use actix_web::web::Data;
 use anyhow::Context;
 use lemmy_apub::fetcher::search_by_apub_id;
 use lemmy_db::{
-  category::*,
-  comment_view::*,
-  community_view::*,
   diesel_option_overwrite,
-  moderator::*,
-  moderator_views::*,
   naive_now,
-  post_view::*,
-  site::*,
-  site_view::*,
-  user_view::*,
+  source::{category::*, moderator::*, site::*},
+  views::{
+    comment_view::CommentQueryBuilder,
+    community::community_view::CommunityQueryBuilder,
+    moderator::{
+      mod_add_community_view::ModAddCommunityView,
+      mod_add_view::ModAddView,
+      mod_ban_from_community_view::ModBanFromCommunityView,
+      mod_ban_view::ModBanView,
+      mod_lock_post_view::ModLockPostView,
+      mod_remove_comment_view::ModRemoveCommentView,
+      mod_remove_community_view::ModRemoveCommunityView,
+      mod_remove_post_view::ModRemovePostView,
+      mod_sticky_post_view::ModStickyPostView,
+    },
+    post_view::PostQueryBuilder,
+    site_view::SiteView,
+    user_view::{UserQueryBuilder, UserViewSafe},
+  },
   Crud,
   SearchType,
   SortType,
@@ -145,7 +155,7 @@ impl Perform for CreateSite {
   ) -> Result<SiteResponse, LemmyError> {
     let data: &CreateSite = &self;
 
-    let read_site = move |conn: &'_ _| Site::read(conn, 1);
+    let read_site = move |conn: &'_ _| Site::read_simple(conn);
     if blocking(context.pool(), read_site).await?.is_ok() {
       return Err(APIError::err("site_already_exists").into());
     };
@@ -177,7 +187,7 @@ impl Perform for CreateSite {
 
     let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
 
-    Ok(SiteResponse { site: site_view })
+    Ok(SiteResponse { site_view })
   }
 }
 
@@ -198,7 +208,7 @@ impl Perform for EditSite {
     // Make sure user is an admin
     is_admin(context.pool(), user.id).await?;
 
-    let found_site = blocking(context.pool(), move |conn| Site::read(conn, 1)).await??;
+    let found_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??;
 
     let icon = diesel_option_overwrite(&data.icon);
     let banner = diesel_option_overwrite(&data.banner);
@@ -222,7 +232,7 @@ impl Perform for EditSite {
 
     let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
 
-    let res = SiteResponse { site: site_view };
+    let res = SiteResponse { site_view };
 
     context.chat_server().do_send(SendAllMessage {
       op: UserOperation::EditSite,
@@ -245,55 +255,57 @@ impl Perform for GetSite {
   ) -> Result<GetSiteResponse, LemmyError> {
     let data: &GetSite = &self;
 
-    // TODO refactor this a little
-    let res = blocking(context.pool(), move |conn| Site::read(conn, 1)).await?;
-    let site_view = if res.is_ok() {
-      Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??)
-    } else if let Some(setup) = Settings::get().setup.as_ref() {
-      let register = Register {
-        username: setup.admin_username.to_owned(),
-        email: setup.admin_email.to_owned(),
-        password: setup.admin_password.to_owned(),
-        password_verify: setup.admin_password.to_owned(),
-        admin: true,
-        show_nsfw: true,
-        captcha_uuid: None,
-        captcha_answer: None,
-      };
-      let login_response = register.perform(context, websocket_id).await?;
-      info!("Admin {} created", setup.admin_username);
-
-      let create_site = CreateSite {
-        name: setup.site_name.to_owned(),
-        description: None,
-        icon: None,
-        banner: None,
-        enable_downvotes: true,
-        open_registration: true,
-        enable_nsfw: true,
-        auth: login_response.jwt,
-      };
-      create_site.perform(context, websocket_id).await?;
-      info!("Site {} created", setup.site_name);
-      Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??)
-    } else {
-      None
+    let site_view = match blocking(context.pool(), move |conn| SiteView::read(conn)).await? {
+      Ok(site_view) => Some(site_view),
+      // If the site isn't created yet, check the setup
+      Err(_) => {
+        if let Some(setup) = Settings::get().setup.as_ref() {
+          let register = Register {
+            username: setup.admin_username.to_owned(),
+            email: setup.admin_email.to_owned(),
+            password: setup.admin_password.to_owned(),
+            password_verify: setup.admin_password.to_owned(),
+            admin: true,
+            show_nsfw: true,
+            captcha_uuid: None,
+            captcha_answer: None,
+          };
+          let login_response = register.perform(context, websocket_id).await?;
+          info!("Admin {} created", setup.admin_username);
+
+          let create_site = CreateSite {
+            name: setup.site_name.to_owned(),
+            description: None,
+            icon: None,
+            banner: None,
+            enable_downvotes: true,
+            open_registration: true,
+            enable_nsfw: true,
+            auth: login_response.jwt,
+          };
+          create_site.perform(context, websocket_id).await?;
+          info!("Site {} created", setup.site_name);
+          Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??)
+        } else {
+          None
+        }
+      }
     };
 
-    let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
+    let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
 
     // Make sure the site creator is the top admin
     if let Some(site_view) = site_view.to_owned() {
-      let site_creator_id = site_view.creator_id;
+      let site_creator_id = site_view.creator.id;
       // TODO investigate why this is sometimes coming back null
       // Maybe user_.admin isn't being set to true?
-      if let Some(creator_index) = admins.iter().position(|r| r.id == site_creator_id) {
+      if let Some(creator_index) = admins.iter().position(|r| r.user.id == site_creator_id) {
         let creator_user = admins.remove(creator_index);
         admins.insert(0, creator_user);
       }
     }
 
-    let banned = blocking(context.pool(), move |conn| UserView::banned(conn)).await??;
+    let banned = blocking(context.pool(), move |conn| UserViewSafe::banned(conn)).await??;
 
     let online = context
       .chat_server()
@@ -311,7 +323,7 @@ impl Perform for GetSite {
       });
 
     Ok(GetSiteResponse {
-      site: site_view,
+      site_view,
       admins,
       banned,
       online,
@@ -362,10 +374,10 @@ impl Perform for Search {
           PostQueryBuilder::create(conn)
             .sort(&sort)
             .show_nsfw(true)
-            .for_community_id(community_id)
-            .for_community_name(community_name)
-            .search_term(q)
+            .community_id(community_id)
+            .community_name(community_name)
             .my_user_id(user_id)
+            .search_term(q)
             .page(page)
             .limit(limit)
             .list()
@@ -411,10 +423,10 @@ impl Perform for Search {
           PostQueryBuilder::create(conn)
             .sort(&sort)
             .show_nsfw(true)
-            .for_community_id(community_id)
-            .for_community_name(community_name)
-            .search_term(q)
+            .community_id(community_id)
+            .community_name(community_name)
             .my_user_id(user_id)
+            .search_term(q)
             .page(page)
             .limit(limit)
             .list()
@@ -466,8 +478,8 @@ impl Perform for Search {
           PostQueryBuilder::create(conn)
             .sort(&sort)
             .show_nsfw(true)
-            .for_community_id(community_id)
-            .for_community_name(community_name)
+            .community_id(community_id)
+            .community_name(community_name)
             .url_search(q)
             .page(page)
             .limit(limit)
@@ -507,7 +519,7 @@ impl Perform for TransferSite {
     user.private_key = None;
     user.public_key = None;
 
-    let read_site = blocking(context.pool(), move |conn| Site::read(conn, 1)).await??;
+    let read_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??;
 
     // Make sure user is the creator
     if read_site.creator_id != user.id {
@@ -531,18 +543,18 @@ impl Perform for TransferSite {
 
     let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
 
-    let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
+    let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
     let creator_index = admins
       .iter()
-      .position(|r| r.id == site_view.creator_id)
+      .position(|r| r.user.id == site_view.creator.id)
       .context(location_info!())?;
     let creator_user = admins.remove(creator_index);
     admins.insert(0, creator_user);
 
-    let banned = blocking(context.pool(), move |conn| UserView::banned(conn)).await??;
+    let banned = blocking(context.pool(), move |conn| UserViewSafe::banned(conn)).await??;
 
     Ok(GetSiteResponse {
-      site: Some(site_view),
+      site_view: Some(site_view),
       admins,
       banned,
       online: 0,
@@ -587,12 +599,8 @@ impl Perform for SaveSiteConfig {
     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
 
     // Only let admins read this
-    let admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
-    let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
-
-    if !admin_ids.contains(&user.id) {
-      return Err(APIError::err("not_an_admin").into());
-    }
+    let user_id = user.id;
+    is_admin(context.pool(), user_id).await?;
 
     // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
     let config_hjson = match Settings::save_config_file(&data.config_hjson) {
index 0d96c2a2f1d9c178a44978b558eca8ce9b807495..cca8dffb8909225d06ecb23c12ce5f9fef75ea60 100644 (file)
@@ -15,26 +15,32 @@ use captcha::{gen, Difficulty};
 use chrono::Duration;
 use lemmy_apub::ApubObjectType;
 use lemmy_db::{
-  comment::*,
-  comment_report::CommentReportView,
-  comment_view::*,
-  community::*,
-  community_view::*,
   diesel_option_overwrite,
-  moderator::*,
   naive_now,
-  password_reset_request::*,
-  post::*,
-  post_report::PostReportView,
-  post_view::*,
-  private_message::*,
-  private_message_view::*,
-  site::*,
-  site_view::*,
-  user::*,
-  user_mention::*,
-  user_mention_view::*,
-  user_view::*,
+  source::{
+    comment::*,
+    community::*,
+    moderator::*,
+    password_reset_request::*,
+    post::*,
+    private_message::*,
+    site::*,
+    user::*,
+    user_mention::*,
+  },
+  views::{
+    comment_report_view::CommentReportView,
+    comment_view::CommentQueryBuilder,
+    community::{
+      community_follower_view::CommunityFollowerView,
+      community_moderator_view::CommunityModeratorView,
+    },
+    post_report_view::PostReportView,
+    post_view::PostQueryBuilder,
+    private_message_view::{PrivateMessageQueryBuilder, PrivateMessageView},
+    user_mention_view::{UserMentionQueryBuilder, UserMentionView},
+    user_view::{UserViewDangerous, UserViewSafe},
+  },
   Crud,
   Followable,
   Joinable,
@@ -113,8 +119,7 @@ impl Perform for Register {
     let data: &Register = &self;
 
     // Make sure site has open registration
-    if let Ok(site) = blocking(context.pool(), move |conn| SiteView::read(conn)).await? {
-      let site: SiteView = site;
+    if let Ok(site) = blocking(context.pool(), move |conn| Site::read_simple(conn)).await? {
       if !site.open_registration {
         return Err(APIError::err("registration_closed").into());
       }
@@ -154,7 +159,7 @@ impl Perform for Register {
 
     // Make sure there are no admins
     let any_admins = blocking(context.pool(), move |conn| {
-      UserView::admins(conn).map(|a| a.is_empty())
+      UserViewSafe::admins(conn).map(|a| a.is_empty())
     })
     .await??;
     if data.admin && !any_admins {
@@ -341,9 +346,6 @@ impl Perform for SaveUserSettings {
     let data: &SaveUserSettings = &self;
     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
 
-    let user_id = user.id;
-    let read_user = blocking(context.pool(), move |conn| User_::read(conn, user_id)).await??;
-
     let avatar = diesel_option_overwrite(&data.avatar);
     let banner = diesel_option_overwrite(&data.banner);
     let email = diesel_option_overwrite(&data.email);
@@ -367,6 +369,7 @@ impl Perform for SaveUserSettings {
       }
     }
 
+    let user_id = user.id;
     let password_encrypted = match &data.new_password {
       Some(new_password) => {
         match &data.new_password_verify {
@@ -379,8 +382,7 @@ impl Perform for SaveUserSettings {
             // Check the old password
             match &data.old_password {
               Some(old_password) => {
-                let valid: bool =
-                  verify(old_password, &read_user.password_encrypted).unwrap_or(false);
+                let valid: bool = verify(old_password, &user.password_encrypted).unwrap_or(false);
                 if !valid {
                   return Err(APIError::err("password_incorrect").into());
                 }
@@ -397,33 +399,36 @@ impl Perform for SaveUserSettings {
           None => return Err(APIError::err("passwords_dont_match").into()),
         }
       }
-      None => read_user.password_encrypted,
+      None => user.password_encrypted,
     };
 
+    let default_listing_type = ListingType::from_str(&data.default_listing_type)? as i16;
+    let default_sort_type = SortType::from_str(&data.default_sort_type)? as i16;
+
     let user_form = UserForm {
-      name: read_user.name,
+      name: user.name,
       email,
       matrix_user_id,
       avatar,
       banner,
       password_encrypted,
       preferred_username,
-      published: Some(read_user.published),
+      published: Some(user.published),
       updated: Some(naive_now()),
-      admin: read_user.admin,
-      banned: Some(read_user.banned),
+      admin: user.admin,
+      banned: Some(user.banned),
       show_nsfw: data.show_nsfw,
       theme: data.theme.to_owned(),
-      default_sort_type: data.default_sort_type,
-      default_listing_type: data.default_listing_type,
+      default_sort_type,
+      default_listing_type,
       lang: data.lang.to_owned(),
       show_avatars: data.show_avatars,
       send_notifications_to_email: data.send_notifications_to_email,
-      actor_id: Some(read_user.actor_id),
+      actor_id: Some(user.actor_id),
       bio,
-      local: read_user.local,
-      private_key: read_user.private_key,
-      public_key: read_user.public_key,
+      local: user.local,
+      private_key: user.private_key,
+      public_key: user.public_key,
       last_refreshed_at: None,
     };
 
@@ -491,24 +496,41 @@ impl Perform for GetUserDetails {
     };
 
     let user_id = user.map(|u| u.id);
-    let user_fun = move |conn: &'_ _| {
-      match user_id {
-        // if there's a logged in user and it's the same id as the user whose details are being
-        // requested we need to use get_user_dangerous so it returns their email or other sensitive
-        // data hidden when viewing users other than yourself
-        Some(auth_user_id) => {
-          if user_details_id == auth_user_id {
-            UserView::get_user_dangerous(conn, auth_user_id)
-          } else {
-            UserView::get_user_secure(conn, user_details_id)
-          }
-        }
-        None => UserView::get_user_secure(conn, user_details_id),
+
+    let (user_view, user_dangerous) = if let Some(auth_user_id) = user_id {
+      if user_details_id == auth_user_id {
+        (
+          None,
+          Some(
+            blocking(context.pool(), move |conn| {
+              UserViewDangerous::read(conn, auth_user_id)
+            })
+            .await??,
+          ),
+        )
+      } else {
+        (
+          Some(
+            blocking(context.pool(), move |conn| {
+              UserViewSafe::read(conn, user_details_id)
+            })
+            .await??,
+          ),
+          None,
+        )
       }
+    } else {
+      (
+        Some(
+          blocking(context.pool(), move |conn| {
+            UserViewSafe::read(conn, user_details_id)
+          })
+          .await??,
+        ),
+        None,
+      )
     };
 
-    let user_view = blocking(context.pool(), user_fun).await??;
-
     let page = data.page;
     let limit = data.limit;
     let saved_only = data.saved_only;
@@ -519,23 +541,23 @@ impl Perform for GetUserDetails {
         .sort(&sort)
         .show_nsfw(show_nsfw)
         .saved_only(saved_only)
-        .for_community_id(community_id)
+        .community_id(community_id)
         .my_user_id(user_id)
         .page(page)
         .limit(limit);
 
       let mut comments_query = CommentQueryBuilder::create(conn)
+        .my_user_id(user_id)
         .sort(&sort)
         .saved_only(saved_only)
-        .my_user_id(user_id)
         .page(page)
         .limit(limit);
 
       // If its saved only, you don't care what creator it was
       // Or, if its not saved, then you only want it for that specific creator
       if !saved_only {
-        posts_query = posts_query.for_creator_id(user_details_id);
-        comments_query = comments_query.for_creator_id(user_details_id);
+        posts_query = posts_query.creator_id(user_details_id);
+        comments_query = comments_query.creator_id(user_details_id);
       }
 
       let posts = posts_query.list()?;
@@ -556,7 +578,8 @@ impl Perform for GetUserDetails {
 
     // Return the jwt
     Ok(GetUserDetailsResponse {
-      user: user_view,
+      user_view,
+      user_view_dangerous: user_dangerous,
       follows,
       moderates,
       comments,
@@ -601,10 +624,10 @@ impl Perform for AddAdmin {
     })
     .await??;
 
-    let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
+    let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
     let creator_index = admins
       .iter()
-      .position(|r| r.id == site_creator_id)
+      .position(|r| r.user.id == site_creator_id)
       .context(location_info!())?;
     let creator_user = admins.remove(creator_index);
     admins.insert(0, creator_user);
@@ -644,22 +667,22 @@ impl Perform for BanUser {
     }
 
     // Remove their data if that's desired
-    if let Some(remove_data) = data.remove_data {
+    if data.remove_data {
       // Posts
       blocking(context.pool(), move |conn: &'_ _| {
-        Post::update_removed_for_creator(conn, banned_user_id, None, remove_data)
+        Post::update_removed_for_creator(conn, banned_user_id, None, true)
       })
       .await??;
 
       // Communities
       blocking(context.pool(), move |conn: &'_ _| {
-        Community::update_removed_for_creator(conn, banned_user_id, remove_data)
+        Community::update_removed_for_creator(conn, banned_user_id, true)
       })
       .await??;
 
       // Comments
       blocking(context.pool(), move |conn: &'_ _| {
-        Comment::update_removed_for_creator(conn, banned_user_id, remove_data)
+        Comment::update_removed_for_creator(conn, banned_user_id, true)
       })
       .await??;
     }
@@ -682,12 +705,12 @@ impl Perform for BanUser {
 
     let user_id = data.user_id;
     let user_view = blocking(context.pool(), move |conn| {
-      UserView::get_user_secure(conn, user_id)
+      UserViewSafe::read(conn, user_id)
     })
     .await??;
 
     let res = BanUserResponse {
-      user: user_view,
+      user_view,
       banned: data.ban,
     };
 
@@ -720,9 +743,11 @@ impl Perform for GetReplies {
     let unread_only = data.unread_only;
     let user_id = user.id;
     let replies = blocking(context.pool(), move |conn| {
-      ReplyQueryBuilder::create(conn, user_id)
+      CommentQueryBuilder::create(conn)
         .sort(&sort)
         .unread_only(unread_only)
+        .recipient_id(user_id)
+        .my_user_id(user_id)
         .page(page)
         .limit(limit)
         .list()
@@ -752,7 +777,9 @@ impl Perform for GetUserMentions {
     let unread_only = data.unread_only;
     let user_id = user.id;
     let mentions = blocking(context.pool(), move |conn| {
-      UserMentionQueryBuilder::create(conn, user_id)
+      UserMentionQueryBuilder::create(conn)
+        .recipient_id(user_id)
+        .my_user_id(user_id)
         .sort(&sort)
         .unread_only(unread_only)
         .page(page)
@@ -797,13 +824,11 @@ impl Perform for MarkUserMentionAsRead {
     let user_mention_id = read_user_mention.id;
     let user_id = user.id;
     let user_mention_view = blocking(context.pool(), move |conn| {
-      UserMentionView::read(conn, user_mention_id, user_id)
+      UserMentionView::read(conn, user_mention_id, Some(user_id))
     })
     .await??;
 
-    Ok(UserMentionResponse {
-      mention: user_mention_view,
-    })
+    Ok(UserMentionResponse { user_mention_view })
   }
 }
 
@@ -821,7 +846,9 @@ impl Perform for MarkAllAsRead {
 
     let user_id = user.id;
     let replies = blocking(context.pool(), move |conn| {
-      ReplyQueryBuilder::create(conn, user_id)
+      CommentQueryBuilder::create(conn)
+        .my_user_id(user_id)
+        .recipient_id(user_id)
         .unread_only(true)
         .page(1)
         .limit(999)
@@ -832,8 +859,8 @@ impl Perform for MarkAllAsRead {
     // TODO: this should probably be a bulk operation
     // Not easy to do as a bulk operation,
     // because recipient_id isn't in the comment table
-    for reply in &replies {
-      let reply_id = reply.id;
+    for comment_view in &replies {
+      let reply_id = comment_view.comment.id;
       let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
       if blocking(context.pool(), mark_as_read).await?.is_err() {
         return Err(APIError::err("couldnt_update_comment").into());
@@ -1062,7 +1089,9 @@ impl Perform for CreatePrivateMessage {
     })
     .await??;
 
-    let res = PrivateMessageResponse { message };
+    let res = PrivateMessageResponse {
+      private_message_view: message,
+    };
 
     context.chat_server().do_send(SendUserRoomMessage {
       op: UserOperation::CreatePrivateMessage,
@@ -1117,9 +1146,11 @@ impl Perform for EditPrivateMessage {
       PrivateMessageView::read(conn, edit_id)
     })
     .await??;
-    let recipient_id = message.recipient_id;
+    let recipient_id = message.recipient.id;
 
-    let res = PrivateMessageResponse { message };
+    let res = PrivateMessageResponse {
+      private_message_view: message,
+    };
 
     context.chat_server().do_send(SendUserRoomMessage {
       op: UserOperation::EditPrivateMessage,
@@ -1180,9 +1211,11 @@ impl Perform for DeletePrivateMessage {
       PrivateMessageView::read(conn, edit_id)
     })
     .await??;
-    let recipient_id = message.recipient_id;
+    let recipient_id = message.recipient.id;
 
-    let res = PrivateMessageResponse { message };
+    let res = PrivateMessageResponse {
+      private_message_view: message,
+    };
 
     context.chat_server().do_send(SendUserRoomMessage {
       op: UserOperation::DeletePrivateMessage,
@@ -1236,9 +1269,11 @@ impl Perform for MarkPrivateMessageAsRead {
       PrivateMessageView::read(conn, edit_id)
     })
     .await??;
-    let recipient_id = message.recipient_id;
+    let recipient_id = message.recipient.id;
 
-    let res = PrivateMessageResponse { message };
+    let res = PrivateMessageResponse {
+      private_message_view: message,
+    };
 
     context.chat_server().do_send(SendUserRoomMessage {
       op: UserOperation::MarkPrivateMessageAsRead,
@@ -1276,7 +1311,9 @@ impl Perform for GetPrivateMessages {
     })
     .await??;
 
-    Ok(PrivateMessagesResponse { messages })
+    Ok(PrivateMessagesResponse {
+      private_messages: messages,
+    })
   }
 }
 
index 7e8f792a5e5a67d75bea39a45bea88d41f59b4f5..2dd9a64a47bf45e5e16ac6546e2623d56168f120 100644 (file)
@@ -13,37 +13,37 @@ lemmy_utils = { path = "../lemmy_utils" }
 lemmy_db = { path = "../lemmy_db" }
 lemmy_structs = { path = "../lemmy_structs" }
 lemmy_websocket = { path = "../lemmy_websocket" }
-diesel = "1.4"
-activitystreams = "0.7.0-alpha.7"
+diesel = "1.4.5"
+activitystreams = "0.7.0-alpha.8"
 activitystreams-ext = "0.1.0-alpha.2"
-bcrypt = "0.8"
-chrono = { version = "0.4", features = ["serde"] }
-serde_json = { version = "1.0", features = ["preserve_order"]}
-serde = { version = "1.0", features = ["derive"] }
-actix = "0.10"
-actix-web = { version = "3.0", default-features = false }
-actix-rt = { version = "1.1", default-features = false }
-awc = { version = "2.0", default-features = false }
-log = "0.4"
-rand = "0.7"
-strum = "0.19"
-strum_macros = "0.19"
-lazy_static = "1.3"
-url = { version = "2.1", features = ["serde"] }
-percent-encoding = "2.1"
-openssl = "0.10"
-http = "0.2"
-http-signature-normalization-actix = { version = "0.4", default-features = false, features = ["sha-2"] }
+bcrypt = "0.9.0"
+chrono = { version = "0.4.19", features = ["serde"] }
+serde_json = { version = "1.0.60", features = ["preserve_order"] }
+serde = { version = "1.0.118", features = ["derive"] }
+actix = "0.10.0"
+actix-web = { version = "3.3.2", default-features = false }
+actix-rt = { version = "1.1.1", default-features = false }
+awc = { version = "2.0.3", default-features = false }
+log = "0.4.11"
+rand = "0.7.3"
+strum = "0.20.0"
+strum_macros = "0.20.1"
+lazy_static = "1.4.0"
+url = { version = "2.2.0", features = ["serde"] }
+percent-encoding = "2.1.0"
+openssl = "0.10.31"
+http = "0.2.2"
+http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] }
 http-signature-normalization-reqwest = { version = "0.1.3", default-features = false, features = ["sha-2"] }
-base64 = "0.13"
-tokio = "0.3"
-futures = "0.3"
-itertools = "0.9"
-uuid = { version = "0.8", features = ["serde", "v4"] }
-sha2 = "0.9"
-async-trait = "0.1"
-anyhow = "1.0"
-thiserror = "1.0"
-background-jobs = " 0.8"
-reqwest = { version = "0.10", features = ["json"] }
-backtrace = "0.3"
+base64 = "0.13.0"
+tokio = "0.3.6"
+futures = "0.3.8"
+itertools = "0.9.0"
+uuid = { version = "0.8.1", features = ["serde", "v4"] }
+sha2 = "0.9.2"
+async-trait = "0.1.42"
+anyhow = "1.0.35"
+thiserror = "1.0.22"
+background-jobs = "0.8.0"
+reqwest = { version = "0.10.10", features = ["json"] }
+backtrace = "0.3.55"
index bf8de1ebd82f84acbcdf55983ba7d68f772fd7db..32eb8c0ca5290b5028032e2ac1fdbe284b46820f 100644 (file)
@@ -1,19 +1,15 @@
-use crate::{
-  activities::receive::get_actor_as_user,
-  fetcher::get_or_fetch_and_insert_comment,
-  ActorType,
-  FromApub,
-  NoteExt,
-};
+use crate::{activities::receive::get_actor_as_user, objects::FromApub, ActorType, NoteExt};
 use activitystreams::{
   activity::{ActorAndObjectRefExt, Create, Dislike, Like, Remove, Update},
   base::ExtendsExt,
 };
-use anyhow::{anyhow, Context};
+use anyhow::Context;
 use lemmy_db::{
-  comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
-  comment_view::CommentView,
-  post::Post,
+  source::{
+    comment::{Comment, CommentLike, CommentLikeForm},
+    post::Post,
+  },
+  views::comment_view::CommentView,
   Crud,
   Likeable,
 };
@@ -30,41 +26,27 @@ pub(crate) async fn receive_create_comment(
   let note = NoteExt::from_any_base(create.object().to_owned().one().context(location_info!())?)?
     .context(location_info!())?;
 
-  let comment =
-    CommentForm::from_apub(&note, context, Some(user.actor_id()?), request_counter).await?;
+  let comment = Comment::from_apub(&note, context, user.actor_id()?, request_counter).await?;
 
   let post_id = comment.post_id;
   let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
-  if post.locked {
-    return Err(anyhow!("Post is locked").into());
-  }
-
-  let inserted_comment =
-    blocking(context.pool(), move |conn| Comment::upsert(conn, &comment)).await??;
 
   // Note:
   // Although mentions could be gotten from the post tags (they are included there), or the ccs,
   // Its much easier to scrape them from the comment body, since the API has to do that
   // anyway.
-  let mentions = scrape_text_for_mentions(&inserted_comment.content);
-  let recipient_ids = send_local_notifs(
-    mentions,
-    inserted_comment.clone(),
-    &user,
-    post,
-    context.pool(),
-    true,
-  )
-  .await?;
+  let mentions = scrape_text_for_mentions(&comment.content);
+  let recipient_ids =
+    send_local_notifs(mentions, comment.clone(), &user, post, context.pool(), true).await?;
 
   // Refetch the view
   let comment_view = blocking(context.pool(), move |conn| {
-    CommentView::read(conn, inserted_comment.id, None)
+    CommentView::read(conn, comment.id, None)
   })
   .await??;
 
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -87,41 +69,24 @@ pub(crate) async fn receive_update_comment(
     .context(location_info!())?;
   let user = get_actor_as_user(&update, context, request_counter).await?;
 
-  let comment =
-    CommentForm::from_apub(&note, context, Some(user.actor_id()?), request_counter).await?;
-
-  let original_comment_id =
-    get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter)
-      .await?
-      .id;
-
-  let updated_comment = blocking(context.pool(), move |conn| {
-    Comment::update(conn, original_comment_id, &comment)
-  })
-  .await??;
+  let comment = Comment::from_apub(&note, context, user.actor_id()?, request_counter).await?;
 
-  let post_id = updated_comment.post_id;
+  let comment_id = comment.id;
+  let post_id = comment.post_id;
   let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
 
-  let mentions = scrape_text_for_mentions(&updated_comment.content);
-  let recipient_ids = send_local_notifs(
-    mentions,
-    updated_comment,
-    &user,
-    post,
-    context.pool(),
-    false,
-  )
-  .await?;
+  let mentions = scrape_text_for_mentions(&comment.content);
+  let recipient_ids =
+    send_local_notifs(mentions, comment, &user, post, context.pool(), false).await?;
 
   // Refetch the view
   let comment_view = blocking(context.pool(), move |conn| {
-    CommentView::read(conn, original_comment_id, None)
+    CommentView::read(conn, comment_id, None)
   })
   .await??;
 
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -137,19 +102,13 @@ pub(crate) async fn receive_update_comment(
 
 pub(crate) async fn receive_like_comment(
   like: Like,
+  comment: Comment,
   context: &LemmyContext,
   request_counter: &mut i32,
 ) -> Result<(), LemmyError> {
-  let note = NoteExt::from_any_base(like.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
   let user = get_actor_as_user(&like, context, request_counter).await?;
 
-  let comment = CommentForm::from_apub(&note, context, None, request_counter).await?;
-
-  let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter)
-    .await?
-    .id;
-
+  let comment_id = comment.id;
   let like_form = CommentLikeForm {
     comment_id,
     post_id: comment.post_id,
@@ -172,7 +131,7 @@ pub(crate) async fn receive_like_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -188,25 +147,13 @@ pub(crate) async fn receive_like_comment(
 
 pub(crate) async fn receive_dislike_comment(
   dislike: Dislike,
+  comment: Comment,
   context: &LemmyContext,
   request_counter: &mut i32,
 ) -> Result<(), LemmyError> {
-  let note = NoteExt::from_any_base(
-    dislike
-      .object()
-      .to_owned()
-      .one()
-      .context(location_info!())?,
-  )?
-  .context(location_info!())?;
   let user = get_actor_as_user(&dislike, context, request_counter).await?;
 
-  let comment = CommentForm::from_apub(&note, context, None, request_counter).await?;
-
-  let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter)
-    .await?
-    .id;
-
+  let comment_id = comment.id;
   let like_form = CommentLikeForm {
     comment_id,
     post_id: comment.post_id,
@@ -229,7 +176,7 @@ pub(crate) async fn receive_dislike_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -262,7 +209,7 @@ pub(crate) async fn receive_delete_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -295,7 +242,7 @@ pub(crate) async fn receive_remove_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
index f44604cc1e100d9a6344316be14817188e8bec99..85dcc143d8989e60c7badb68773ef18cfbf3dfff 100644 (file)
@@ -1,35 +1,23 @@
-use crate::{
-  activities::receive::get_actor_as_user,
-  fetcher::get_or_fetch_and_insert_comment,
-  FromApub,
-  NoteExt,
-};
-use activitystreams::{activity::*, prelude::*};
-use anyhow::Context;
+use crate::activities::receive::get_actor_as_user;
+use activitystreams::activity::{Dislike, Like};
 use lemmy_db::{
-  comment::{Comment, CommentForm, CommentLike},
-  comment_view::CommentView,
+  source::comment::{Comment, CommentLike},
+  views::comment_view::CommentView,
   Likeable,
 };
 use lemmy_structs::{blocking, comment::CommentResponse};
-use lemmy_utils::{location_info, LemmyError};
+use lemmy_utils::LemmyError;
 use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
 
 pub(crate) async fn receive_undo_like_comment(
   like: &Like,
+  comment: Comment,
   context: &LemmyContext,
   request_counter: &mut i32,
 ) -> Result<(), LemmyError> {
   let user = get_actor_as_user(like, context, request_counter).await?;
-  let note = NoteExt::from_any_base(like.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
-
-  let comment = CommentForm::from_apub(&note, context, None, request_counter).await?;
-
-  let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter)
-    .await?
-    .id;
 
+  let comment_id = comment.id;
   let user_id = user.id;
   blocking(context.pool(), move |conn| {
     CommentLike::remove(conn, user_id, comment_id)
@@ -45,7 +33,7 @@ pub(crate) async fn receive_undo_like_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -61,25 +49,13 @@ pub(crate) async fn receive_undo_like_comment(
 
 pub(crate) async fn receive_undo_dislike_comment(
   dislike: &Dislike,
+  comment: Comment,
   context: &LemmyContext,
   request_counter: &mut i32,
 ) -> Result<(), LemmyError> {
   let user = get_actor_as_user(dislike, context, request_counter).await?;
-  let note = NoteExt::from_any_base(
-    dislike
-      .object()
-      .to_owned()
-      .one()
-      .context(location_info!())?,
-  )?
-  .context(location_info!())?;
-
-  let comment = CommentForm::from_apub(&note, context, None, request_counter).await?;
-
-  let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter)
-    .await?
-    .id;
 
+  let comment_id = comment.id;
   let user_id = user.id;
   blocking(context.pool(), move |conn| {
     CommentLike::remove(conn, user_id, comment_id)
@@ -95,7 +71,7 @@ pub(crate) async fn receive_undo_dislike_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -128,7 +104,7 @@ pub(crate) async fn receive_undo_delete_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -161,7 +137,7 @@ pub(crate) async fn receive_undo_remove_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
index ed43b33e301c627817206ce440c6f8d99a392a61..534da5cb70dda19f00fe12e2d143182422304714 100644 (file)
@@ -4,7 +4,11 @@ use activitystreams::{
   base::{AnyBase, ExtendsExt},
 };
 use anyhow::Context;
-use lemmy_db::{community::Community, community_view::CommunityView};
+use lemmy_db::{
+  source::community::Community,
+  views::community::community_view::CommunityView,
+  ApubObject,
+};
 use lemmy_structs::{blocking, community::CommunityResponse};
 use lemmy_utils::{location_info, LemmyError};
 use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
@@ -21,13 +25,13 @@ pub(crate) async fn receive_delete_community(
 
   let community_id = deleted_community.id;
   let res = CommunityResponse {
-    community: blocking(context.pool(), move |conn| {
+    community_view: blocking(context.pool(), move |conn| {
       CommunityView::read(conn, community_id, None)
     })
     .await??,
   };
 
-  let community_id = res.community.id;
+  let community_id = res.community_view.community.id;
   context.chat_server().do_send(SendCommunityRoomMessage {
     op: UserOperation::EditCommunity,
     response: res,
@@ -53,7 +57,7 @@ pub(crate) async fn receive_remove_community(
     .single_xsd_any_uri()
     .context(location_info!())?;
   let community = blocking(context.pool(), move |conn| {
-    Community::read_from_actor_id(conn, community_uri.as_str())
+    Community::read_from_apub_id(conn, community_uri.as_str())
   })
   .await??;
 
@@ -64,13 +68,13 @@ pub(crate) async fn receive_remove_community(
 
   let community_id = removed_community.id;
   let res = CommunityResponse {
-    community: blocking(context.pool(), move |conn| {
+    community_view: blocking(context.pool(), move |conn| {
       CommunityView::read(conn, community_id, None)
     })
     .await??,
   };
 
-  let community_id = res.community.id;
+  let community_id = res.community_view.community.id;
   context.chat_server().do_send(SendCommunityRoomMessage {
     op: UserOperation::EditCommunity,
     response: res,
@@ -100,13 +104,13 @@ pub(crate) async fn receive_undo_delete_community(
 
   let community_id = deleted_community.id;
   let res = CommunityResponse {
-    community: blocking(context.pool(), move |conn| {
+    community_view: blocking(context.pool(), move |conn| {
       CommunityView::read(conn, community_id, None)
     })
     .await??,
   };
 
-  let community_id = res.community.id;
+  let community_id = res.community_view.community.id;
   context.chat_server().do_send(SendCommunityRoomMessage {
     op: UserOperation::EditCommunity,
     response: res,
@@ -135,7 +139,7 @@ pub(crate) async fn receive_undo_remove_community(
     .single_xsd_any_uri()
     .context(location_info!())?;
   let community = blocking(context.pool(), move |conn| {
-    Community::read_from_actor_id(conn, community_uri.as_str())
+    Community::read_from_apub_id(conn, community_uri.as_str())
   })
   .await??;
 
@@ -146,13 +150,13 @@ pub(crate) async fn receive_undo_remove_community(
 
   let community_id = removed_community.id;
   let res = CommunityResponse {
-    community: blocking(context.pool(), move |conn| {
+    community_view: blocking(context.pool(), move |conn| {
       CommunityView::read(conn, community_id, None)
     })
     .await??,
   };
 
-  let community_id = res.community.id;
+  let community_id = res.community_view.community.id;
 
   context.chat_server().do_send(SendCommunityRoomMessage {
     op: UserOperation::EditCommunity,
index 1f17fe9f3bc958b1cd772ad1db7f712b0c43e977..a66ddfb950a2bb6ac2c1a8181c92e181058074a2 100644 (file)
@@ -5,7 +5,7 @@ use activitystreams::{
   error::DomainError,
 };
 use anyhow::{anyhow, Context};
-use lemmy_db::user::User_;
+use lemmy_db::source::user::User_;
 use lemmy_utils::{location_info, LemmyError};
 use lemmy_websocket::LemmyContext;
 use log::debug;
index 80044237f65448a56f497cc08a79e5b51c33fccd..f09071129773077dcde6506cb992b9888f0c0017 100644 (file)
@@ -1,19 +1,12 @@
-use crate::{
-  activities::receive::get_actor_as_user,
-  fetcher::get_or_fetch_and_insert_post,
-  ActorType,
-  FromApub,
-  PageExt,
-};
+use crate::{activities::receive::get_actor_as_user, objects::FromApub, ActorType, PageExt};
 use activitystreams::{
   activity::{Create, Dislike, Like, Remove, Update},
   prelude::*,
 };
 use anyhow::Context;
 use lemmy_db::{
-  post::{Post, PostForm, PostLike, PostLikeForm},
-  post_view::PostView,
-  Crud,
+  source::post::{Post, PostLike, PostLikeForm},
+  views::post_view::PostView,
   Likeable,
 };
 use lemmy_structs::{blocking, post::PostResponse};
@@ -29,20 +22,16 @@ pub(crate) async fn receive_create_post(
   let page = PageExt::from_any_base(create.object().to_owned().one().context(location_info!())?)?
     .context(location_info!())?;
 
-  let post = PostForm::from_apub(&page, context, Some(user.actor_id()?), request_counter).await?;
-
-  // Using an upsert, since likes (which fetch the post), sometimes come in before the create
-  // resulting in double posts.
-  let inserted_post = blocking(context.pool(), move |conn| Post::upsert(conn, &post)).await??;
+  let post = Post::from_apub(&page, context, user.actor_id()?, request_counter).await?;
 
   // Refetch the view
-  let inserted_post_id = inserted_post.id;
+  let post_id = post.id;
   let post_view = blocking(context.pool(), move |conn| {
-    PostView::read(conn, inserted_post_id, None)
+    PostView::read(conn, post_id, None)
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
     op: UserOperation::CreatePost,
@@ -62,24 +51,16 @@ pub(crate) async fn receive_update_post(
   let page = PageExt::from_any_base(update.object().to_owned().one().context(location_info!())?)?
     .context(location_info!())?;
 
-  let post = PostForm::from_apub(&page, context, Some(user.actor_id()?), request_counter).await?;
-
-  let original_post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter)
-    .await?
-    .id;
-
-  blocking(context.pool(), move |conn| {
-    Post::update(conn, original_post_id, &post)
-  })
-  .await??;
+  let post = Post::from_apub(&page, context, user.actor_id()?, request_counter).await?;
 
+  let post_id = post.id;
   // Refetch the view
   let post_view = blocking(context.pool(), move |conn| {
-    PostView::read(conn, original_post_id, None)
+    PostView::read(conn, post_id, None)
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
     op: UserOperation::EditPost,
@@ -92,19 +73,13 @@ pub(crate) async fn receive_update_post(
 
 pub(crate) async fn receive_like_post(
   like: Like,
+  post: Post,
   context: &LemmyContext,
   request_counter: &mut i32,
 ) -> Result<(), LemmyError> {
   let user = get_actor_as_user(&like, context, request_counter).await?;
-  let page = PageExt::from_any_base(like.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
-
-  let post = PostForm::from_apub(&page, context, None, request_counter).await?;
-
-  let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter)
-    .await?
-    .id;
 
+  let post_id = post.id;
   let like_form = PostLikeForm {
     post_id,
     user_id: user.id,
@@ -123,7 +98,7 @@ pub(crate) async fn receive_like_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
     op: UserOperation::CreatePostLike,
@@ -136,25 +111,13 @@ pub(crate) async fn receive_like_post(
 
 pub(crate) async fn receive_dislike_post(
   dislike: Dislike,
+  post: Post,
   context: &LemmyContext,
   request_counter: &mut i32,
 ) -> Result<(), LemmyError> {
   let user = get_actor_as_user(&dislike, context, request_counter).await?;
-  let page = PageExt::from_any_base(
-    dislike
-      .object()
-      .to_owned()
-      .one()
-      .context(location_info!())?,
-  )?
-  .context(location_info!())?;
-
-  let post = PostForm::from_apub(&page, context, None, request_counter).await?;
-
-  let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter)
-    .await?
-    .id;
 
+  let post_id = post.id;
   let like_form = PostLikeForm {
     post_id,
     user_id: user.id,
@@ -173,7 +136,7 @@ pub(crate) async fn receive_dislike_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
     op: UserOperation::CreatePostLike,
@@ -200,7 +163,7 @@ pub(crate) async fn receive_delete_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
   context.chat_server().do_send(SendPost {
     op: UserOperation::EditPost,
     post: res,
@@ -227,7 +190,7 @@ pub(crate) async fn receive_remove_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
   context.chat_server().do_send(SendPost {
     op: UserOperation::EditPost,
     post: res,
index 99d0ed1d98b718c8dabbc5f567d47bfd10a76fbb..6827ded05f325bd8c8c95e1f9b092904c7f2d459 100644 (file)
@@ -1,35 +1,23 @@
-use crate::{
-  activities::receive::get_actor_as_user,
-  fetcher::get_or_fetch_and_insert_post,
-  FromApub,
-  PageExt,
-};
-use activitystreams::{activity::*, prelude::*};
-use anyhow::Context;
+use crate::activities::receive::get_actor_as_user;
+use activitystreams::activity::{Dislike, Like};
 use lemmy_db::{
-  post::{Post, PostForm, PostLike},
-  post_view::PostView,
+  source::post::{Post, PostLike},
+  views::post_view::PostView,
   Likeable,
 };
 use lemmy_structs::{blocking, post::PostResponse};
-use lemmy_utils::{location_info, LemmyError};
+use lemmy_utils::LemmyError;
 use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
 
 pub(crate) async fn receive_undo_like_post(
   like: &Like,
+  post: Post,
   context: &LemmyContext,
   request_counter: &mut i32,
 ) -> Result<(), LemmyError> {
   let user = get_actor_as_user(like, context, request_counter).await?;
-  let page = PageExt::from_any_base(like.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
-
-  let post = PostForm::from_apub(&page, context, None, request_counter).await?;
-
-  let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter)
-    .await?
-    .id;
 
+  let post_id = post.id;
   let user_id = user.id;
   blocking(context.pool(), move |conn| {
     PostLike::remove(conn, user_id, post_id)
@@ -42,7 +30,7 @@ pub(crate) async fn receive_undo_like_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
     op: UserOperation::CreatePostLike,
@@ -55,25 +43,13 @@ pub(crate) async fn receive_undo_like_post(
 
 pub(crate) async fn receive_undo_dislike_post(
   dislike: &Dislike,
+  post: Post,
   context: &LemmyContext,
   request_counter: &mut i32,
 ) -> Result<(), LemmyError> {
   let user = get_actor_as_user(dislike, context, request_counter).await?;
-  let page = PageExt::from_any_base(
-    dislike
-      .object()
-      .to_owned()
-      .one()
-      .context(location_info!())?,
-  )?
-  .context(location_info!())?;
-
-  let post = PostForm::from_apub(&page, context, None, request_counter).await?;
-
-  let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter)
-    .await?
-    .id;
 
+  let post_id = post.id;
   let user_id = user.id;
   blocking(context.pool(), move |conn| {
     PostLike::remove(conn, user_id, post_id)
@@ -86,7 +62,7 @@ pub(crate) async fn receive_undo_dislike_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
     op: UserOperation::CreatePostLike,
@@ -113,7 +89,7 @@ pub(crate) async fn receive_undo_delete_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
   context.chat_server().do_send(SendPost {
     op: UserOperation::EditPost,
     post: res,
@@ -139,7 +115,7 @@ pub(crate) async fn receive_undo_remove_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
     op: UserOperation::EditPost,
index 8f1c95b9d027b1e759a254c9b73d4178db736bf0..8f7952b6b067fa5bdf93a6039b75fe97217baad7 100644 (file)
@@ -3,7 +3,7 @@ use crate::{
   check_is_apub_id_valid,
   fetcher::get_or_fetch_and_upsert_user,
   inbox::get_activity_to_and_cc,
-  FromApub,
+  objects::FromApub,
   NoteExt,
 };
 use activitystreams::{
@@ -14,9 +14,8 @@ use activitystreams::{
 };
 use anyhow::{anyhow, Context};
 use lemmy_db::{
-  private_message::{PrivateMessage, PrivateMessageForm},
-  private_message_view::PrivateMessageView,
-  Crud,
+  source::private_message::PrivateMessage,
+  views::private_message_view::PrivateMessageView,
 };
 use lemmy_structs::{blocking, user::PrivateMessageResponse};
 use lemmy_utils::{location_info, LemmyError};
@@ -41,21 +40,18 @@ pub(crate) async fn receive_create_private_message(
   .context(location_info!())?;
 
   let private_message =
-    PrivateMessageForm::from_apub(&note, context, Some(expected_domain), request_counter).await?;
-
-  let inserted_private_message = blocking(&context.pool(), move |conn| {
-    PrivateMessage::create(conn, &private_message)
-  })
-  .await??;
+    PrivateMessage::from_apub(&note, context, expected_domain, request_counter).await?;
 
   let message = blocking(&context.pool(), move |conn| {
-    PrivateMessageView::read(conn, inserted_private_message.id)
+    PrivateMessageView::read(conn, private_message.id)
   })
   .await??;
 
-  let res = PrivateMessageResponse { message };
+  let res = PrivateMessageResponse {
+    private_message_view: message,
+  };
 
-  let recipient_id = res.message.recipient_id;
+  let recipient_id = res.private_message_view.recipient.id;
 
   context.chat_server().do_send(SendUserRoomMessage {
     op: UserOperation::CreatePrivateMessage,
@@ -82,24 +78,8 @@ pub(crate) async fn receive_update_private_message(
     .to_owned();
   let note = NoteExt::from_any_base(object)?.context(location_info!())?;
 
-  let private_message_form =
-    PrivateMessageForm::from_apub(&note, context, Some(expected_domain), request_counter).await?;
-
-  let private_message_ap_id = private_message_form
-    .ap_id
-    .as_ref()
-    .context(location_info!())?
-    .clone();
-  let private_message = blocking(&context.pool(), move |conn| {
-    PrivateMessage::read_from_apub_id(conn, &private_message_ap_id)
-  })
-  .await??;
-
-  let private_message_id = private_message.id;
-  blocking(&context.pool(), move |conn| {
-    PrivateMessage::update(conn, private_message_id, &private_message_form)
-  })
-  .await??;
+  let private_message =
+    PrivateMessage::from_apub(&note, context, expected_domain, request_counter).await?;
 
   let private_message_id = private_message.id;
   let message = blocking(&context.pool(), move |conn| {
@@ -107,9 +87,11 @@ pub(crate) async fn receive_update_private_message(
   })
   .await??;
 
-  let res = PrivateMessageResponse { message };
+  let res = PrivateMessageResponse {
+    private_message_view: message,
+  };
 
-  let recipient_id = res.message.recipient_id;
+  let recipient_id = res.private_message_view.recipient.id;
 
   context.chat_server().do_send(SendUserRoomMessage {
     op: UserOperation::EditPrivateMessage,
@@ -139,8 +121,10 @@ pub(crate) async fn receive_delete_private_message(
   })
   .await??;
 
-  let res = PrivateMessageResponse { message };
-  let recipient_id = res.message.recipient_id;
+  let res = PrivateMessageResponse {
+    private_message_view: message,
+  };
+  let recipient_id = res.private_message_view.recipient.id;
   context.chat_server().do_send(SendUserRoomMessage {
     op: UserOperation::EditPrivateMessage,
     response: res,
@@ -174,8 +158,10 @@ pub(crate) async fn receive_undo_delete_private_message(
   })
   .await??;
 
-  let res = PrivateMessageResponse { message };
-  let recipient_id = res.message.recipient_id;
+  let res = PrivateMessageResponse {
+    private_message_view: message,
+  };
+  let recipient_id = res.private_message_view.recipient.id;
   context.chat_server().do_send(SendUserRoomMessage {
     op: UserOperation::EditPrivateMessage,
     response: res,
@@ -194,7 +180,7 @@ async fn check_private_message_activity_valid<T, Kind>(
 where
   T: AsBase<Kind> + AsObject<Kind> + ActorAndObjectRefExt,
 {
-  let to_and_cc = get_activity_to_and_cc(activity)?;
+  let to_and_cc = get_activity_to_and_cc(activity);
   if to_and_cc.len() != 1 {
     return Err(anyhow!("Private message can only be addressed to one user").into());
   }
index 2aee7b6e30c5ef28d90ef28854d8c2285054ec6e..6b417c2591b7062eee496370aef6f35f109a86b4 100644 (file)
@@ -3,10 +3,10 @@ use crate::{
   activity_queue::{send_comment_mentions, send_to_community},
   extensions::context::lemmy_context,
   fetcher::get_or_fetch_and_upsert_user,
+  objects::ToApub,
   ActorType,
   ApubLikeableType,
   ApubObjectType,
-  ToApub,
 };
 use activitystreams::{
   activity::{
@@ -26,7 +26,11 @@ use activitystreams::{
 };
 use anyhow::anyhow;
 use itertools::Itertools;
-use lemmy_db::{comment::Comment, community::Community, post::Post, user::User_, Crud, DbPool};
+use lemmy_db::{
+  source::{comment::Comment, community::Community, post::Post, user::User_},
+  Crud,
+  DbPool,
+};
 use lemmy_structs::{blocking, WebFingerResponse};
 use lemmy_utils::{
   request::{retry, RecvError},
@@ -218,8 +222,6 @@ impl ApubObjectType for Comment {
 #[async_trait::async_trait(?Send)]
 impl ApubLikeableType for Comment {
   async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
-    let note = self.to_apub(context.pool()).await?;
-
     let post_id = self.post_id;
     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
 
@@ -229,7 +231,7 @@ impl ApubLikeableType for Comment {
     })
     .await??;
 
-    let mut like = Like::new(creator.actor_id.to_owned(), note.into_any_base()?);
+    let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
     like
       .set_many_contexts(lemmy_context()?)
       .set_id(generate_activity_id(LikeType::Like)?)
@@ -241,8 +243,6 @@ impl ApubLikeableType for Comment {
   }
 
   async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
-    let note = self.to_apub(context.pool()).await?;
-
     let post_id = self.post_id;
     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
 
@@ -252,7 +252,7 @@ impl ApubLikeableType for Comment {
     })
     .await??;
 
-    let mut dislike = Dislike::new(creator.actor_id.to_owned(), note.into_any_base()?);
+    let mut dislike = Dislike::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
     dislike
       .set_many_contexts(lemmy_context()?)
       .set_id(generate_activity_id(DislikeType::Dislike)?)
@@ -268,8 +268,6 @@ impl ApubLikeableType for Comment {
     creator: &User_,
     context: &LemmyContext,
   ) -> Result<(), LemmyError> {
-    let note = self.to_apub(context.pool()).await?;
-
     let post_id = self.post_id;
     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
 
@@ -279,7 +277,7 @@ impl ApubLikeableType for Comment {
     })
     .await??;
 
-    let mut like = Like::new(creator.actor_id.to_owned(), note.into_any_base()?);
+    let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
     like
       .set_many_contexts(lemmy_context()?)
       .set_id(generate_activity_id(DislikeType::Dislike)?)
index 775f8c25fa8dd011990add83c61c1a7f0f59a634..96152fa0d4b9d1e43caf84cc23a47ea5a27c4c40 100644 (file)
@@ -23,7 +23,11 @@ use activitystreams::{
 };
 use anyhow::Context;
 use itertools::Itertools;
-use lemmy_db::{community::Community, community_view::CommunityFollowerView, DbPool};
+use lemmy_db::{
+  source::community::Community,
+  views::community::community_follower_view::CommunityFollowerView,
+  DbPool,
+};
 use lemmy_structs::blocking;
 use lemmy_utils::{location_info, settings::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
@@ -179,9 +183,9 @@ impl ActorType for Community {
     .await??;
     let inboxes = inboxes
       .into_iter()
-      .filter(|i| !i.user_local)
+      .filter(|i| !i.follower.local)
       .map(|u| -> Result<Url, LemmyError> {
-        let url = Url::parse(&u.user_actor_id)?;
+        let url = Url::parse(&u.follower.actor_id)?;
         let domain = url.domain().context(location_info!())?;
         let port = if let Some(port) = url.port() {
           format!(":{}", port)
index da78667f0c824941ecacb56e402d8fd485b7c222..ec1ca67deb97f36f7a7b0fb9b947568d3db5d539 100644 (file)
@@ -2,10 +2,10 @@ use crate::{
   activities::send::generate_activity_id,
   activity_queue::send_to_community,
   extensions::context::lemmy_context,
+  objects::ToApub,
   ActorType,
   ApubLikeableType,
   ApubObjectType,
-  ToApub,
 };
 use activitystreams::{
   activity::{
@@ -21,7 +21,10 @@ use activitystreams::{
   prelude::*,
   public,
 };
-use lemmy_db::{community::Community, post::Post, user::User_, Crud};
+use lemmy_db::{
+  source::{community::Community, post::Post, user::User_},
+  Crud,
+};
 use lemmy_structs::blocking;
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
@@ -167,15 +170,13 @@ impl ApubObjectType for Post {
 #[async_trait::async_trait(?Send)]
 impl ApubLikeableType for Post {
   async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
-    let page = self.to_apub(context.pool()).await?;
-
     let community_id = self.community_id;
     let community = blocking(context.pool(), move |conn| {
       Community::read(conn, community_id)
     })
     .await??;
 
-    let mut like = Like::new(creator.actor_id.to_owned(), page.into_any_base()?);
+    let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
     like
       .set_many_contexts(lemmy_context()?)
       .set_id(generate_activity_id(LikeType::Like)?)
@@ -187,15 +188,13 @@ impl ApubLikeableType for Post {
   }
 
   async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
-    let page = self.to_apub(context.pool()).await?;
-
     let community_id = self.community_id;
     let community = blocking(context.pool(), move |conn| {
       Community::read(conn, community_id)
     })
     .await??;
 
-    let mut dislike = Dislike::new(creator.actor_id.to_owned(), page.into_any_base()?);
+    let mut dislike = Dislike::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
     dislike
       .set_many_contexts(lemmy_context()?)
       .set_id(generate_activity_id(DislikeType::Dislike)?)
@@ -211,15 +210,13 @@ impl ApubLikeableType for Post {
     creator: &User_,
     context: &LemmyContext,
   ) -> Result<(), LemmyError> {
-    let page = self.to_apub(context.pool()).await?;
-
     let community_id = self.community_id;
     let community = blocking(context.pool(), move |conn| {
       Community::read(conn, community_id)
     })
     .await??;
 
-    let mut like = Like::new(creator.actor_id.to_owned(), page.into_any_base()?);
+    let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
     like
       .set_many_contexts(lemmy_context()?)
       .set_id(generate_activity_id(LikeType::Like)?)
index 23528b5c7eaa36bfffac7c2516f6f458b491befa..9abe70d663ec4d9955c86525b866f5ac8053bafb 100644 (file)
@@ -2,9 +2,9 @@ use crate::{
   activities::send::generate_activity_id,
   activity_queue::send_activity_single_dest,
   extensions::context::lemmy_context,
+  objects::ToApub,
   ActorType,
   ApubObjectType,
-  ToApub,
 };
 use activitystreams::{
   activity::{
@@ -16,7 +16,10 @@ use activitystreams::{
   },
   prelude::*,
 };
-use lemmy_db::{private_message::PrivateMessage, user::User_, Crud};
+use lemmy_db::{
+  source::{private_message::PrivateMessage, user::User_},
+  Crud,
+};
 use lemmy_structs::blocking;
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
index a94f241dac715411cecc0bd7184833f1e7e9c37a..26c5a5d5611c23bd53976c05e96bed234f8675b6 100644 (file)
@@ -14,8 +14,11 @@ use activitystreams::{
   object::ObjectExt,
 };
 use lemmy_db::{
-  community::{Community, CommunityFollower, CommunityFollowerForm},
-  user::User_,
+  source::{
+    community::{Community, CommunityFollower, CommunityFollowerForm},
+    user::User_,
+  },
+  ApubObject,
   DbPool,
   Followable,
 };
@@ -46,7 +49,7 @@ impl ActorType for User_ {
   ) -> Result<(), LemmyError> {
     let follow_actor_id = follow_actor_id.to_string();
     let community = blocking(context.pool(), move |conn| {
-      Community::read_from_actor_id(conn, &follow_actor_id)
+      Community::read_from_apub_id(conn, &follow_actor_id)
     })
     .await??;
 
@@ -77,7 +80,7 @@ impl ActorType for User_ {
   ) -> Result<(), LemmyError> {
     let follow_actor_id = follow_actor_id.to_string();
     let community = blocking(context.pool(), move |conn| {
-      Community::read_from_actor_id(conn, &follow_actor_id)
+      Community::read_from_apub_id(conn, &follow_actor_id)
     })
     .await??;
 
index 6c6851a4cf3214333752e34f3cc797235a43fcc4..d94c7cbf75bed9ba207bc0e8d8d63c043b5b3dc9 100644 (file)
@@ -19,7 +19,10 @@ use background_jobs::{
   WorkerConfig,
 };
 use itertools::Itertools;
-use lemmy_db::{community::Community, user::User_, DbPool};
+use lemmy_db::{
+  source::{community::Community, user::User_},
+  DbPool,
+};
 use lemmy_utils::{location_info, settings::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
 use log::{debug, warn};
@@ -33,7 +36,7 @@ use url::Url;
 /// * `activity` the apub activity to be sent
 /// * `creator` the local actor which created the activity
 /// * `inbox` the inbox url where the activity should be delivered to
-pub async fn send_activity_single_dest<T, Kind>(
+pub(crate) async fn send_activity_single_dest<T, Kind>(
   activity: T,
   creator: &dyn ActorType,
   inbox: Url,
@@ -71,7 +74,7 @@ where
 /// * `community` the sending community
 /// * `sender_shared_inbox` in case of an announce, this should be the shared inbox of the inner
 ///                         activities creator, as receiving a known activity will cause an error
-pub async fn send_to_community_followers<T, Kind>(
+pub(crate) async fn send_to_community_followers<T, Kind>(
   activity: T,
   community: &Community,
   context: &LemmyContext,
@@ -116,7 +119,7 @@ where
 /// * `creator` the creator of the activity
 /// * `community` the destination community
 ///
-pub async fn send_to_community<T, Kind>(
+pub(crate) async fn send_to_community<T, Kind>(
   activity: T,
   creator: &User_,
   community: &Community,
@@ -160,7 +163,7 @@ where
 /// * `creator` user who created the comment
 /// * `mentions` list of inboxes of users which are mentioned in the comment
 /// * `activity` either a `Create/Note` or `Update/Note`
-pub async fn send_comment_mentions<T, Kind>(
+pub(crate) async fn send_comment_mentions<T, Kind>(
   creator: &User_,
   mentions: Vec<Url>,
   activity: T,
index cb15267200828ffe3001a3f1b410433b5868fdd8..ab7acf337d8c69672dce2caa06434218a720bcc1 100644 (file)
@@ -1,7 +1,7 @@
 use activitystreams::unparsed::UnparsedMutExt;
 use activitystreams_ext::UnparsedExtension;
 use diesel::PgConnection;
-use lemmy_db::{category::Category, Crud};
+use lemmy_db::{source::category::Category, Crud};
 use lemmy_utils::LemmyError;
 use serde::{Deserialize, Serialize};
 
index 6ff61df4598f20b669a217b4367cb9f45d524ada..67fe4b1cd3a9c910cecf461ee536476973044e94 100644 (file)
@@ -65,7 +65,10 @@ pub async fn sign_and_send(
 }
 
 /// Verifies the HTTP signature on an incoming inbox request.
-pub fn verify_signature(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), LemmyError> {
+pub(crate) fn verify_signature(
+  request: &HttpRequest,
+  actor: &dyn ActorType,
+) -> Result<(), LemmyError> {
   let public_key = actor.public_key().context(location_info!())?;
   let verified = CONFIG2
     .begin_verify(
index ec44bce17f0f11099e31d2e8c2510539482738a8..39188f138e361d51360562accfe8694b6b3553e3 100644 (file)
@@ -1,7 +1,7 @@
 use crate::{
   check_is_apub_id_valid,
+  objects::FromApub,
   ActorType,
-  FromApub,
   GroupExt,
   NoteExt,
   PageExt,
@@ -13,15 +13,20 @@ use anyhow::{anyhow, Context};
 use chrono::NaiveDateTime;
 use diesel::result::Error::NotFound;
 use lemmy_db::{
-  comment::{Comment, CommentForm},
-  comment_view::CommentView,
-  community::{Community, CommunityForm, CommunityModerator, CommunityModeratorForm},
-  community_view::CommunityView,
   naive_now,
-  post::{Post, PostForm},
-  post_view::PostView,
-  user::{UserForm, User_},
-  user_view::UserView,
+  source::{
+    comment::Comment,
+    community::{Community, CommunityModerator, CommunityModeratorForm},
+    post::Post,
+    user::User_,
+  },
+  views::{
+    comment_view::CommentView,
+    community::community_view::CommunityView,
+    post_view::PostView,
+    user_view::UserViewSafe,
+  },
+  ApubObject,
   Crud,
   Joinable,
   SearchType,
@@ -161,7 +166,7 @@ pub async fn search_by_apub_id(
 
       response.users = vec![
         blocking(context.pool(), move |conn| {
-          UserView::get_user_secure(conn, user.id)
+          UserViewSafe::read(conn, user.id)
         })
         .await??,
       ];
@@ -184,22 +189,16 @@ pub async fn search_by_apub_id(
       response
     }
     SearchAcceptedObjects::Page(p) => {
-      let post_form = PostForm::from_apub(&p, context, Some(query_url), recursion_counter).await?;
+      let p = Post::from_apub(&p, context, query_url, recursion_counter).await?;
 
-      let p = blocking(context.pool(), move |conn| Post::upsert(conn, &post_form)).await??;
       response.posts =
         vec![blocking(context.pool(), move |conn| PostView::read(conn, p.id, None)).await??];
 
       response
     }
     SearchAcceptedObjects::Comment(c) => {
-      let comment_form =
-        CommentForm::from_apub(&c, context, Some(query_url), recursion_counter).await?;
+      let c = Comment::from_apub(&c, context, query_url, recursion_counter).await?;
 
-      let c = blocking(context.pool(), move |conn| {
-        Comment::upsert(conn, &comment_form)
-      })
-      .await??;
       response.comments = vec![
         blocking(context.pool(), move |conn| {
           CommentView::read(conn, c.id, None)
@@ -243,7 +242,7 @@ pub(crate) async fn get_or_fetch_and_upsert_user(
 ) -> Result<User_, LemmyError> {
   let apub_id_owned = apub_id.to_owned();
   let user = blocking(context.pool(), move |conn| {
-    User_::read_from_actor_id(conn, apub_id_owned.as_ref())
+    User_::read_from_apub_id(conn, apub_id_owned.as_ref())
   })
   .await?;
 
@@ -258,15 +257,13 @@ pub(crate) async fn get_or_fetch_and_upsert_user(
         return Ok(u);
       }
 
-      let mut uf = UserForm::from_apub(
-        &person?,
-        context,
-        Some(apub_id.to_owned()),
-        recursion_counter,
-      )
-      .await?;
-      uf.last_refreshed_at = Some(naive_now());
-      let user = blocking(context.pool(), move |conn| User_::update(conn, u.id, &uf)).await??;
+      let user = User_::from_apub(&person?, context, apub_id.to_owned(), recursion_counter).await?;
+
+      let user_id = user.id;
+      blocking(context.pool(), move |conn| {
+        User_::mark_as_updated(conn, user_id)
+      })
+      .await??;
 
       Ok(user)
     }
@@ -276,14 +273,7 @@ pub(crate) async fn get_or_fetch_and_upsert_user(
       let person =
         fetch_remote_object::<PersonExt>(context.client(), apub_id, recursion_counter).await?;
 
-      let uf = UserForm::from_apub(
-        &person,
-        context,
-        Some(apub_id.to_owned()),
-        recursion_counter,
-      )
-      .await?;
-      let user = blocking(context.pool(), move |conn| User_::upsert(conn, &uf)).await??;
+      let user = User_::from_apub(&person, context, apub_id.to_owned(), recursion_counter).await?;
 
       Ok(user)
     }
@@ -318,7 +308,7 @@ pub(crate) async fn get_or_fetch_and_upsert_community(
 ) -> Result<Community, LemmyError> {
   let apub_id_owned = apub_id.to_owned();
   let community = blocking(context.pool(), move |conn| {
-    Community::read_from_actor_id(conn, apub_id_owned.as_str())
+    Community::read_from_apub_id(conn, apub_id_owned.as_str())
   })
   .await?;
 
@@ -354,9 +344,8 @@ async fn fetch_remote_community(
   }
 
   let group = group?;
-  let cf =
-    CommunityForm::from_apub(&group, context, Some(apub_id.to_owned()), recursion_counter).await?;
-  let community = blocking(context.pool(), move |conn| Community::upsert(conn, &cf)).await??;
+  let community =
+    Community::from_apub(&group, context, apub_id.to_owned(), recursion_counter).await?;
 
   // Also add the community moderators too
   let attributed_to = group.inner.attributed_to().context(location_info!())?;
@@ -406,23 +395,13 @@ async fn fetch_remote_community(
   }
   for o in outbox_items {
     let page = PageExt::from_any_base(o)?.context(location_info!())?;
+    let page_id = page.id_unchecked().context(location_info!())?;
 
-    // The post creator may be from a blocked instance,
-    // if it errors, then continue
-    let post = match PostForm::from_apub(&page, context, None, recursion_counter).await {
-      Ok(post) => post,
-      Err(_) => continue,
-    };
-    let post_ap_id = post.ap_id.as_ref().context(location_info!())?.clone();
-    // Check whether the post already exists in the local db
-    let existing = blocking(context.pool(), move |conn| {
-      Post::read_from_apub_id(conn, &post_ap_id)
-    })
-    .await?;
-    match existing {
-      Ok(e) => blocking(context.pool(), move |conn| Post::update(conn, e.id, &post)).await??,
-      Err(_) => blocking(context.pool(), move |conn| Post::upsert(conn, &post)).await??,
-    };
+    // The post creator may be from a blocked instance, if it errors, then skip it
+    if check_is_apub_id_valid(page_id).is_err() {
+      continue;
+    }
+    Post::from_apub(&page, context, page_id.to_owned(), recursion_counter).await?;
     // TODO: we need to send a websocket update here
   }
 
@@ -448,17 +427,9 @@ pub(crate) async fn get_or_fetch_and_insert_post(
     Ok(p) => Ok(p),
     Err(NotFound {}) => {
       debug!("Fetching and creating remote post: {}", post_ap_id);
-      let post =
+      let page =
         fetch_remote_object::<PageExt>(context.client(), post_ap_id, recursion_counter).await?;
-      let post_form = PostForm::from_apub(
-        &post,
-        context,
-        Some(post_ap_id.to_owned()),
-        recursion_counter,
-      )
-      .await?;
-
-      let post = blocking(context.pool(), move |conn| Post::upsert(conn, &post_form)).await??;
+      let post = Post::from_apub(&page, context, post_ap_id.to_owned(), recursion_counter).await?;
 
       Ok(post)
     }
@@ -490,25 +461,20 @@ pub(crate) async fn get_or_fetch_and_insert_comment(
       );
       let comment =
         fetch_remote_object::<NoteExt>(context.client(), comment_ap_id, recursion_counter).await?;
-      let comment_form = CommentForm::from_apub(
+      let comment = Comment::from_apub(
         &comment,
         context,
-        Some(comment_ap_id.to_owned()),
+        comment_ap_id.to_owned(),
         recursion_counter,
       )
       .await?;
 
-      let post_id = comment_form.post_id;
+      let post_id = comment.post_id;
       let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
       if post.locked {
         return Err(anyhow!("Post is locked").into());
       }
 
-      let comment = blocking(context.pool(), move |conn| {
-        Comment::upsert(conn, &comment_form)
-      })
-      .await??;
-
       Ok(comment)
     }
     Err(e) => Err(e.into()),
index 936cd98138160dd653e3f67fccb1615c9ed1d011..16f4119027dafda6cfadd6b8a02f29630d9191f6 100644 (file)
@@ -1,10 +1,10 @@
 use crate::{
   http::{create_apub_response, create_apub_tombstone_response},
-  ToApub,
+  objects::ToApub,
 };
 use actix_web::{body::Body, web, web::Path, HttpResponse};
 use diesel::result::Error::NotFound;
-use lemmy_db::{comment::Comment, Crud};
+use lemmy_db::{source::comment::Comment, Crud};
 use lemmy_structs::blocking;
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
index 0e2f2802e98e2a28f5e203879f91adc36d4cfac2..a17e2abf91e5b71bccbbe7a2a173e1d9a7feec80 100644 (file)
@@ -1,15 +1,18 @@
 use crate::{
   extensions::context::lemmy_context,
   http::{create_apub_response, create_apub_tombstone_response},
+  objects::ToApub,
   ActorType,
-  ToApub,
 };
 use activitystreams::{
   base::{AnyBase, BaseExt, ExtendsExt},
   collection::{CollectionExt, OrderedCollection, UnorderedCollection},
 };
 use actix_web::{body::Body, web, HttpResponse};
-use lemmy_db::{community::Community, community_view::CommunityFollowerView, post::Post};
+use lemmy_db::{
+  source::{community::Community, post::Post},
+  views::community::community_follower_view::CommunityFollowerView,
+};
 use lemmy_structs::blocking;
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
index 4f31f6a5e768aee9e8c92d5db65e7593e984f321..fee15b773594c11ba85904ad43ffa4d6b77fb706 100644 (file)
@@ -1,6 +1,6 @@
 use crate::APUB_JSON_CONTENT_TYPE;
 use actix_web::{body::Body, web, HttpResponse};
-use lemmy_db::activity::Activity;
+use lemmy_db::source::activity::Activity;
 use lemmy_structs::blocking;
 use lemmy_utils::{settings::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
index 7f4bb447cc915221700a2055093e3e026de53976..3d0c6ff8eb98a3c77e51c468c34b426732412ab7 100644 (file)
@@ -1,10 +1,10 @@
 use crate::{
   http::{create_apub_response, create_apub_tombstone_response},
-  ToApub,
+  objects::ToApub,
 };
 use actix_web::{body::Body, web, HttpResponse};
 use diesel::result::Error::NotFound;
-use lemmy_db::post::Post;
+use lemmy_db::{source::post::Post, Crud};
 use lemmy_structs::blocking;
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
index 0f2e1a71cc36898c59b0c4446f592076c8bec11e..8dcc8edabf7a889bd92176c21cb1046bcff13779 100644 (file)
@@ -1,10 +1,15 @@
-use crate::{extensions::context::lemmy_context, http::create_apub_response, ActorType, ToApub};
+use crate::{
+  extensions::context::lemmy_context,
+  http::create_apub_response,
+  objects::ToApub,
+  ActorType,
+};
 use activitystreams::{
   base::BaseExt,
   collection::{CollectionExt, OrderedCollection},
 };
 use actix_web::{body::Body, web, HttpResponse};
-use lemmy_db::user::User_;
+use lemmy_db::source::user::User_;
 use lemmy_structs::blocking;
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
index 7c144a00d5a7decafd377977144c693c527ba25a..a2bed621cc9a41eb69fe7dbc03df1891f6da9a6a 100644 (file)
@@ -27,9 +27,12 @@ use activitystreams::{
 use actix_web::{web, HttpRequest, HttpResponse};
 use anyhow::{anyhow, Context};
 use lemmy_db::{
-  community::{Community, CommunityFollower, CommunityFollowerForm},
-  community_view::CommunityUserBanView,
-  user::User_,
+  source::{
+    community::{Community, CommunityFollower, CommunityFollowerForm},
+    user::User_,
+  },
+  views::community::community_user_ban_view::CommunityUserBanView,
+  ApubObject,
   DbPool,
   Followable,
 };
@@ -81,7 +84,7 @@ pub async fn community_inbox(
     Community::read_from_name(&conn, &path)
   })
   .await??;
-  let to_and_cc = get_activity_to_and_cc(&activity)?;
+  let to_and_cc = get_activity_to_and_cc(&activity);
   if !to_and_cc.contains(&&community.actor_id()?) {
     return Err(anyhow!("Activity delivered to wrong community").into());
   }
@@ -118,7 +121,7 @@ pub(crate) async fn community_receive_message(
   // unconditionally.
   let actor_id = actor.actor_id_str();
   let user = blocking(&context.pool(), move |conn| {
-    User_::read_from_actor_id(&conn, &actor_id)
+    User_::read_from_apub_id(&conn, &actor_id)
   })
   .await??;
   check_community_or_site_ban(&user, &to_community, context.pool()).await?;
@@ -242,7 +245,7 @@ async fn handle_undo_follow(
   verify_activity_domains_valid(&follow, &user_url, false)?;
 
   let user = blocking(&context.pool(), move |conn| {
-    User_::read_from_actor_id(&conn, user_url.as_str())
+    User_::read_from_apub_id(&conn, user_url.as_str())
   })
   .await??;
   let community_follower_form = CommunityFollowerForm {
@@ -260,7 +263,7 @@ async fn handle_undo_follow(
   Ok(())
 }
 
-async fn check_community_or_site_ban(
+pub(crate) async fn check_community_or_site_ban(
   user: &User_,
   community: &Community,
   pool: &DbPool,
index ce6c7eded500cf8da924d935f091be6cf80c55b2..415851883d8bc78a652cabf5d8d6eb2cbf413517 100644 (file)
@@ -12,7 +12,11 @@ use activitystreams::{
 };
 use actix_web::HttpRequest;
 use anyhow::{anyhow, Context};
-use lemmy_db::{activity::Activity, community::Community, user::User_, DbPool};
+use lemmy_db::{
+  source::{activity::Activity, community::Community, user::User_},
+  ApubObject,
+  DbPool,
+};
 use lemmy_structs::blocking;
 use lemmy_utils::{location_info, settings::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
@@ -50,7 +54,7 @@ pub(crate) async fn is_activity_already_known(
   }
 }
 
-pub(crate) fn get_activity_to_and_cc<T, Kind>(activity: &T) -> Result<Vec<Url>, LemmyError>
+pub(crate) fn get_activity_to_and_cc<T, Kind>(activity: &T) -> Vec<Url>
 where
   T: AsBase<Kind> + AsObject<Kind> + ActorAndObjectRefExt,
 {
@@ -75,14 +79,14 @@ where
       .collect();
     to_and_cc.append(&mut cc);
   }
-  Ok(to_and_cc)
+  to_and_cc
 }
 
 pub(crate) fn is_addressed_to_public<T, Kind>(activity: &T) -> Result<(), LemmyError>
 where
   T: AsBase<Kind> + AsObject<Kind> + ActorAndObjectRefExt,
 {
-  let to_and_cc = get_activity_to_and_cc(activity)?;
+  let to_and_cc = get_activity_to_and_cc(activity);
   if to_and_cc.contains(&public()) {
     Ok(())
   } else {
@@ -119,7 +123,7 @@ pub(crate) async fn is_addressed_to_local_user(
 ) -> Result<bool, LemmyError> {
   for url in to_and_cc {
     let url = url.to_string();
-    let user = blocking(&pool, move |conn| User_::read_from_actor_id(&conn, &url)).await?;
+    let user = blocking(&pool, move |conn| User_::read_from_apub_id(&conn, &url)).await?;
     if let Ok(u) = user {
       if u.local {
         return Ok(true);
@@ -141,7 +145,7 @@ pub(crate) async fn is_addressed_to_community_followers(
     if url.ends_with("/followers") {
       let community_url = url.replace("/followers", "");
       let community = blocking(&pool, move |conn| {
-        Community::read_from_actor_id(&conn, &community_url)
+        Community::read_from_apub_id(&conn, &community_url)
       })
       .await??;
       if !community.local {
index b6dfa1e423683e942dc78290d512b272f6731b42..73deb97176e832b1c89e134c975e12828f1dfce9 100644 (file)
@@ -31,6 +31,7 @@ use crate::{
     receive_unhandled_activity,
     verify_activity_domains_valid,
   },
+  fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
   inbox::is_addressed_to_public,
 };
 use activitystreams::{
@@ -40,7 +41,11 @@ use activitystreams::{
 };
 use anyhow::Context;
 use diesel::result::Error::NotFound;
-use lemmy_db::{comment::Comment, post::Post, site::Site, Crud};
+use lemmy_db::{
+  source::{comment::Comment, post::Post, site::Site},
+  ApubObject,
+  Crud,
+};
 use lemmy_structs::blocking;
 use lemmy_utils::{location_info, LemmyError};
 use lemmy_websocket::LemmyContext;
@@ -96,10 +101,12 @@ pub(in crate::inbox) async fn receive_like_for_community(
   verify_activity_domains_valid(&like, &expected_domain, false)?;
   is_addressed_to_public(&like)?;
 
-  match like.object().as_single_kind_str() {
-    Some("Page") => receive_like_post(like, context, request_counter).await,
-    Some("Note") => receive_like_comment(like, context, request_counter).await,
-    _ => receive_unhandled_activity(like),
+  let object_id = get_like_object_id(&like)?;
+  match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
+    PostOrComment::Post(post) => receive_like_post(like, post, context, request_counter).await,
+    PostOrComment::Comment(comment) => {
+      receive_like_comment(like, comment, context, request_counter).await
+    }
   }
 }
 
@@ -122,10 +129,14 @@ pub(in crate::inbox) async fn receive_dislike_for_community(
   verify_activity_domains_valid(&dislike, &expected_domain, false)?;
   is_addressed_to_public(&dislike)?;
 
-  match dislike.object().as_single_kind_str() {
-    Some("Page") => receive_dislike_post(dislike, context, request_counter).await,
-    Some("Note") => receive_dislike_comment(dislike, context, request_counter).await,
-    _ => receive_unhandled_activity(dislike),
+  let object_id = get_like_object_id(&dislike)?;
+  match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
+    PostOrComment::Post(post) => {
+      receive_dislike_post(dislike, post, context, request_counter).await
+    }
+    PostOrComment::Comment(comment) => {
+      receive_dislike_comment(dislike, comment, context, request_counter).await
+    }
   }
 }
 
@@ -275,14 +286,14 @@ pub(in crate::inbox) async fn receive_undo_like_for_community(
   verify_activity_domains_valid(&like, &expected_domain, false)?;
   is_addressed_to_public(&like)?;
 
-  let type_ = like
-    .object()
-    .as_single_kind_str()
-    .context(location_info!())?;
-  match type_ {
-    "Note" => receive_undo_like_comment(&like, context, request_counter).await,
-    "Page" => receive_undo_like_post(&like, context, request_counter).await,
-    _ => receive_unhandled_activity(like),
+  let object_id = get_like_object_id(&like)?;
+  match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
+    PostOrComment::Post(post) => {
+      receive_undo_like_post(&like, post, context, request_counter).await
+    }
+    PostOrComment::Comment(comment) => {
+      receive_undo_like_comment(&like, comment, context, request_counter).await
+    }
   }
 }
 
@@ -298,14 +309,14 @@ pub(in crate::inbox) async fn receive_undo_dislike_for_community(
   verify_activity_domains_valid(&dislike, &expected_domain, false)?;
   is_addressed_to_public(&dislike)?;
 
-  let type_ = dislike
-    .object()
-    .as_single_kind_str()
-    .context(location_info!())?;
-  match type_ {
-    "Note" => receive_undo_dislike_comment(&dislike, context, request_counter).await,
-    "Page" => receive_undo_dislike_post(&dislike, context, request_counter).await,
-    _ => receive_unhandled_activity(dislike),
+  let object_id = get_like_object_id(&dislike)?;
+  match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
+    PostOrComment::Post(post) => {
+      receive_undo_dislike_post(&dislike, post, context, request_counter).await
+    }
+    PostOrComment::Comment(comment) => {
+      receive_undo_dislike_comment(&dislike, comment, context, request_counter).await
+    }
   }
 }
 
@@ -341,3 +352,42 @@ async fn find_post_or_comment_by_id(
 
   return Err(NotFound.into());
 }
+
+async fn fetch_post_or_comment_by_id(
+  apub_id: &Url,
+  context: &LemmyContext,
+  request_counter: &mut i32,
+) -> Result<PostOrComment, LemmyError> {
+  if let Ok(post) = get_or_fetch_and_insert_post(apub_id, context, request_counter).await {
+    return Ok(PostOrComment::Post(post));
+  }
+
+  if let Ok(comment) = get_or_fetch_and_insert_comment(apub_id, context, request_counter).await {
+    return Ok(PostOrComment::Comment(comment));
+  }
+
+  return Err(NotFound.into());
+}
+
+fn get_like_object_id<Activity>(like_or_dislike: &Activity) -> Result<Url, LemmyError>
+where
+  Activity: ActorAndObjectRefExt,
+{
+  // TODO: For backwards compatibility with older Lemmy versions where like.object contains a full
+  //       post/comment. This can be removed after some time, using
+  //       `activity.oject().as_single_xsd_any_uri()` instead.
+  let object = like_or_dislike.object();
+  if let Some(xsd_uri) = object.as_single_xsd_any_uri() {
+    Ok(xsd_uri.to_owned())
+  } else {
+    Ok(
+      object
+        .to_owned()
+        .one()
+        .context(location_info!())?
+        .id()
+        .context(location_info!())?
+        .to_owned(),
+    )
+  }
+}
index 2875696e250d66d914b9dab7ade19479dc67d62c..d94c54f253a0284f9b3a8bb16db9dc56f49375c4 100644 (file)
@@ -15,7 +15,7 @@ use crate::{
 use activitystreams::{activity::ActorAndObject, prelude::*};
 use actix_web::{web, HttpRequest, HttpResponse};
 use anyhow::Context;
-use lemmy_db::{community::Community, DbPool};
+use lemmy_db::{source::community::Community, ApubObject, DbPool};
 use lemmy_structs::blocking;
 use lemmy_utils::{location_info, LemmyError};
 use lemmy_websocket::LemmyContext;
@@ -66,7 +66,7 @@ pub async fn shared_inbox(
 
   let activity_any_base = activity.clone().into_any_base()?;
   let mut res: Option<HttpResponse> = None;
-  let to_and_cc = get_activity_to_and_cc(&activity)?;
+  let to_and_cc = get_activity_to_and_cc(&activity);
   // Handle community first, so in case the sender is banned by the community, it will error out.
   // If we handled the user receive first, the activity would be inserted to the database before the
   // community could check for bans.
@@ -137,10 +137,7 @@ async fn extract_local_community_from_destinations(
 ) -> Result<Option<Community>, LemmyError> {
   for url in to_and_cc {
     let url = url.to_string();
-    let community = blocking(&pool, move |conn| {
-      Community::read_from_actor_id(&conn, &url)
-    })
-    .await?;
+    let community = blocking(&pool, move |conn| Community::read_from_apub_id(&conn, &url)).await?;
     if let Ok(c) = community {
       if c.local {
         return Ok(Some(c));
index 2f847a5cd8c5643b5553227c83f54c77c25fec3b..374772d6d99986fc5883041d4dd2db6ab537f88c 100644 (file)
@@ -49,9 +49,12 @@ use actix_web::{web, HttpRequest, HttpResponse};
 use anyhow::{anyhow, Context};
 use diesel::NotFound;
 use lemmy_db::{
-  community::{Community, CommunityFollower},
-  private_message::PrivateMessage,
-  user::User_,
+  source::{
+    community::{Community, CommunityFollower},
+    private_message::PrivateMessage,
+    user::User_,
+  },
+  ApubObject,
   Followable,
 };
 use lemmy_structs::blocking;
@@ -101,7 +104,7 @@ pub async fn user_inbox(
     User_::read_from_name(&conn, &username)
   })
   .await??;
-  let to_and_cc = get_activity_to_and_cc(&activity)?;
+  let to_and_cc = get_activity_to_and_cc(&activity);
   // TODO: we should also accept activities that are sent to community followers
   if !to_and_cc.contains(&&user.actor_id()?) {
     return Err(anyhow!("Activity delivered to wrong user").into());
@@ -172,7 +175,7 @@ async fn is_for_user_inbox(
   context: &LemmyContext,
   activity: &UserAcceptedActivities,
 ) -> Result<(), LemmyError> {
-  let to_and_cc = get_activity_to_and_cc(activity)?;
+  let to_and_cc = get_activity_to_and_cc(activity);
   // Check if it is addressed directly to any local user
   if is_addressed_to_local_user(&to_and_cc, context.pool()).await? {
     return Ok(());
@@ -377,7 +380,7 @@ async fn find_community_or_private_message_by_id(
 ) -> Result<CommunityOrPrivateMessage, LemmyError> {
   let ap_id = apub_id.to_string();
   let community = blocking(context.pool(), move |conn| {
-    Community::read_from_actor_id(conn, &ap_id)
+    Community::read_from_apub_id(conn, &ap_id)
   })
   .await?;
   if let Ok(c) = community {
index 2e3f7bfc680e83c6468d57b7d927af3abf1ec5ed..78224c32ebd1ed867b970d6bcf176863ded76578 100644 (file)
@@ -18,11 +18,14 @@ use activitystreams::{
   activity::Follow,
   actor::{ApActor, Group, Person},
   base::AnyBase,
-  object::{ApObject, Note, Page, Tombstone},
+  object::{ApObject, Note, Page},
 };
 use activitystreams_ext::{Ext1, Ext2};
 use anyhow::{anyhow, Context};
-use lemmy_db::{activity::Activity, user::User_, DbPool};
+use lemmy_db::{
+  source::{activity::Activity, user::User_},
+  DbPool,
+};
 use lemmy_structs::blocking;
 use lemmy_utils::{location_info, settings::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
@@ -109,33 +112,6 @@ fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
   }
 }
 
-/// Trait for converting an object or actor into the respective ActivityPub type.
-#[async_trait::async_trait(?Send)]
-pub trait ToApub {
-  type ApubType;
-  async fn to_apub(&self, pool: &DbPool) -> Result<Self::ApubType, LemmyError>;
-  fn to_tombstone(&self) -> Result<Tombstone, LemmyError>;
-}
-
-#[async_trait::async_trait(?Send)]
-pub trait FromApub {
-  type ApubType;
-  /// Converts an object from ActivityPub type to Lemmy internal type.
-  ///
-  /// * `apub` The object to read from
-  /// * `context` LemmyContext which holds DB pool, HTTP client etc
-  /// * `expected_domain` If present, ensure that the domains of this and of the apub object ID are
-  ///                     identical
-  async fn from_apub(
-    apub: &Self::ApubType,
-    context: &LemmyContext,
-    expected_domain: Option<Url>,
-    request_counter: &mut i32,
-  ) -> Result<Self, LemmyError>
-  where
-    Self: Sized;
-}
-
 /// Common functions for ActivityPub objects, which are implemented by most (but not all) objects
 /// and actors in Lemmy.
 #[async_trait::async_trait(?Send)]
@@ -248,7 +224,7 @@ pub trait ActorType {
 
 /// Store a sent or received activity in the database, for logging purposes. These records are not
 /// persistent.
-pub async fn insert_activity<T>(
+pub(crate) async fn insert_activity<T>(
   ap_id: &Url,
   activity: T,
   local: bool,
index 277a55d06b8426ce704f24dab50a04e53c131898..957966d7fe2abac86318ce596a6b01d11b60e361 100644 (file)
@@ -7,24 +7,29 @@ use crate::{
   },
   objects::{
     check_object_domain,
+    check_object_for_community_or_site_ban,
     create_tombstone,
+    get_object_from_apub,
     get_source_markdown_value,
     set_content_and_source,
+    FromApub,
+    FromApubToForm,
+    ToApub,
   },
-  FromApub,
   NoteExt,
-  ToApub,
 };
 use activitystreams::{
   object::{kind::NoteType, ApObject, Note, Tombstone},
   prelude::*,
 };
-use anyhow::Context;
+use anyhow::{anyhow, Context};
 use lemmy_db::{
-  comment::{Comment, CommentForm},
-  community::Community,
-  post::Post,
-  user::User_,
+  source::{
+    comment::{Comment, CommentForm},
+    community::Community,
+    post::Post,
+    user::User_,
+  },
   Crud,
   DbPool,
 };
@@ -87,16 +92,45 @@ impl ToApub for Comment {
 }
 
 #[async_trait::async_trait(?Send)]
-impl FromApub for CommentForm {
+impl FromApub for Comment {
   type ApubType = NoteExt;
 
-  /// Converts a `Note` to `CommentForm`.
+  /// Converts a `Note` to `Comment`.
   ///
   /// If the parent community, post and comment(s) are not known locally, these are also fetched.
   async fn from_apub(
     note: &NoteExt,
     context: &LemmyContext,
-    expected_domain: Option<Url>,
+    expected_domain: Url,
+    request_counter: &mut i32,
+  ) -> Result<Comment, LemmyError> {
+    check_object_for_community_or_site_ban(note, context, request_counter).await?;
+
+    let comment: Comment =
+      get_object_from_apub(note, context, expected_domain, request_counter).await?;
+
+    let post_id = comment.post_id;
+    let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
+    if post.locked {
+      // This is not very efficient because a comment gets inserted just to be deleted right
+      // afterwards, but it seems to be the easiest way to implement it.
+      blocking(context.pool(), move |conn| {
+        Comment::delete(conn, comment.id)
+      })
+      .await??;
+      return Err(anyhow!("Post is locked").into());
+    } else {
+      Ok(comment)
+    }
+  }
+}
+
+#[async_trait::async_trait(?Send)]
+impl FromApubToForm<NoteExt> for CommentForm {
+  async fn from_apub(
+    note: &NoteExt,
+    context: &LemmyContext,
+    expected_domain: Url,
     request_counter: &mut i32,
   ) -> Result<CommentForm, LemmyError> {
     let creator_actor_id = &note
index 2b383ba5b818623c848cdc64063e0d5f1d3748ac..9d8210a6ac0112ce466868c12179ee1022fab67e 100644 (file)
@@ -4,13 +4,15 @@ use crate::{
   objects::{
     check_object_domain,
     create_tombstone,
+    get_object_from_apub,
     get_source_markdown_value,
     set_content_and_source,
+    FromApub,
+    FromApubToForm,
+    ToApub,
   },
   ActorType,
-  FromApub,
   GroupExt,
-  ToApub,
 };
 use activitystreams::{
   actor::{kind::GroupType, ApActor, Endpoints, Group},
@@ -21,9 +23,9 @@ use activitystreams::{
 use activitystreams_ext::Ext2;
 use anyhow::Context;
 use lemmy_db::{
-  community::{Community, CommunityForm},
-  community_view::CommunityModeratorView,
   naive_now,
+  source::community::{Community, CommunityForm},
+  views::community::community_moderator_view::CommunityModeratorView,
   DbPool,
 };
 use lemmy_structs::blocking;
@@ -49,7 +51,10 @@ impl ToApub for Community {
       CommunityModeratorView::for_community(&conn, id)
     })
     .await??;
-    let moderators: Vec<String> = moderators.into_iter().map(|m| m.user_actor_id).collect();
+    let moderators: Vec<String> = moderators
+      .into_iter()
+      .map(|m| m.moderator.actor_id)
+      .collect();
 
     let mut group = ApObject::new(Group::new());
     group
@@ -106,14 +111,28 @@ impl ToApub for Community {
     create_tombstone(self.deleted, &self.actor_id, self.updated, GroupType::Group)
   }
 }
+
 #[async_trait::async_trait(?Send)]
-impl FromApub for CommunityForm {
+impl FromApub for Community {
   type ApubType = GroupExt;
 
+  /// Converts a `Group` to `Community`.
+  async fn from_apub(
+    group: &GroupExt,
+    context: &LemmyContext,
+    expected_domain: Url,
+    request_counter: &mut i32,
+  ) -> Result<Community, LemmyError> {
+    get_object_from_apub(group, context, expected_domain, request_counter).await
+  }
+}
+
+#[async_trait::async_trait(?Send)]
+impl FromApubToForm<GroupExt> for CommunityForm {
   async fn from_apub(
     group: &GroupExt,
     context: &LemmyContext,
-    expected_domain: Option<Url>,
+    expected_domain: Url,
     request_counter: &mut i32,
   ) -> Result<Self, LemmyError> {
     let creator_and_moderator_uris = group.inner.attributed_to().context(location_info!())?;
index a162c165b4f0125870f036885c4997afb4592b4a..898c50f31421f122095e7875af7243d49789a4e9 100644 (file)
@@ -1,4 +1,8 @@
-use crate::check_is_apub_id_valid;
+use crate::{
+  check_is_apub_id_valid,
+  fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
+  inbox::community_inbox::check_community_or_site_ban,
+};
 use activitystreams::{
   base::{AsBase, BaseExt, ExtendsExt},
   markers::Base,
@@ -7,7 +11,10 @@ use activitystreams::{
 };
 use anyhow::{anyhow, Context};
 use chrono::NaiveDateTime;
-use lemmy_utils::{location_info, utils::convert_datetime, LemmyError};
+use lemmy_db::{ApubObject, Crud, DbPool};
+use lemmy_structs::blocking;
+use lemmy_utils::{location_info, settings::Settings, utils::convert_datetime, LemmyError};
+use lemmy_websocket::LemmyContext;
 use url::Url;
 
 pub(crate) mod comment;
@@ -16,6 +23,44 @@ pub(crate) mod post;
 pub(crate) mod private_message;
 pub(crate) mod user;
 
+/// Trait for converting an object or actor into the respective ActivityPub type.
+#[async_trait::async_trait(?Send)]
+pub(crate) trait ToApub {
+  type ApubType;
+  async fn to_apub(&self, pool: &DbPool) -> Result<Self::ApubType, LemmyError>;
+  fn to_tombstone(&self) -> Result<Tombstone, LemmyError>;
+}
+
+#[async_trait::async_trait(?Send)]
+pub(crate) trait FromApub {
+  type ApubType;
+  /// Converts an object from ActivityPub type to Lemmy internal type.
+  ///
+  /// * `apub` The object to read from
+  /// * `context` LemmyContext which holds DB pool, HTTP client etc
+  /// * `expected_domain` Domain where the object was received from
+  async fn from_apub(
+    apub: &Self::ApubType,
+    context: &LemmyContext,
+    expected_domain: Url,
+    request_counter: &mut i32,
+  ) -> Result<Self, LemmyError>
+  where
+    Self: Sized;
+}
+
+#[async_trait::async_trait(?Send)]
+pub(in crate::objects) trait FromApubToForm<ApubType> {
+  async fn from_apub(
+    apub: &ApubType,
+    context: &LemmyContext,
+    expected_domain: Url,
+    request_counter: &mut i32,
+  ) -> Result<Self, LemmyError>
+  where
+    Self: Sized;
+}
+
 /// Updated is actually the deletion time
 fn create_tombstone<T>(
   deleted: bool,
@@ -43,17 +88,13 @@ where
 
 pub(in crate::objects) fn check_object_domain<T, Kind>(
   apub: &T,
-  expected_domain: Option<Url>,
+  expected_domain: Url,
 ) -> Result<String, LemmyError>
 where
   T: Base + AsBase<Kind>,
 {
-  let object_id = if let Some(url) = expected_domain {
-    let domain = url.domain().context(location_info!())?;
-    apub.id(domain)?.context(location_info!())?
-  } else {
-    apub.id_unchecked().context(location_info!())?
-  };
+  let domain = expected_domain.domain().context(location_info!())?;
+  let object_id = apub.id(domain)?.context(location_info!())?;
   check_is_apub_id_valid(&object_id)?;
   Ok(object_id.to_string())
 }
@@ -127,3 +168,60 @@ pub(in crate::objects) fn check_is_markdown(mime: Option<&Mime>) -> Result<(), L
     Ok(())
   }
 }
+
+/// Converts an ActivityPub object (eg `Note`) to a database object (eg `Comment`). If an object
+/// with the same ActivityPub ID already exists in the database, it is returned directly. Otherwise
+/// the apub object is parsed, inserted and returned.
+pub(in crate::objects) async fn get_object_from_apub<From, Kind, To, ToForm>(
+  from: &From,
+  context: &LemmyContext,
+  expected_domain: Url,
+  request_counter: &mut i32,
+) -> Result<To, LemmyError>
+where
+  From: BaseExt<Kind>,
+  To: ApubObject<ToForm> + Crud<ToForm> + Send + 'static,
+  ToForm: FromApubToForm<From> + Send + 'static,
+{
+  let object_id = from.id_unchecked().context(location_info!())?.to_owned();
+  let domain = object_id.domain().context(location_info!())?;
+
+  // if its a local object, return it directly from the database
+  if Settings::get().hostname == domain {
+    let object = blocking(context.pool(), move |conn| {
+      To::read_from_apub_id(conn, object_id.as_str())
+    })
+    .await??;
+    Ok(object)
+  }
+  // otherwise parse and insert, assuring that it comes from the right domain
+  else {
+    let to_form = ToForm::from_apub(&from, context, expected_domain, request_counter).await?;
+
+    let to = blocking(context.pool(), move |conn| To::upsert(conn, &to_form)).await??;
+    Ok(to)
+  }
+}
+
+pub(in crate::objects) async fn check_object_for_community_or_site_ban<T, Kind>(
+  object: &T,
+  context: &LemmyContext,
+  request_counter: &mut i32,
+) -> Result<(), LemmyError>
+where
+  T: ObjectExt<Kind>,
+{
+  let user_id = object
+    .attributed_to()
+    .context(location_info!())?
+    .as_single_xsd_any_uri()
+    .context(location_info!())?;
+  let user = get_or_fetch_and_upsert_user(user_id, context, request_counter).await?;
+  let community_id = object
+    .to()
+    .context(location_info!())?
+    .as_single_xsd_any_uri()
+    .context(location_info!())?;
+  let community = get_or_fetch_and_upsert_community(community_id, context, request_counter).await?;
+  check_community_or_site_ban(&user, &community, context.pool()).await
+}
index a058d8b7ccbfeca48b7ab5adb112f4b8ae4cb638..9c9df5b1962cb3a2a97519f59d34d46b3bbcea90 100644 (file)
@@ -3,13 +3,16 @@ use crate::{
   fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
   objects::{
     check_object_domain,
+    check_object_for_community_or_site_ban,
     create_tombstone,
+    get_object_from_apub,
     get_source_markdown_value,
     set_content_and_source,
+    FromApub,
+    FromApubToForm,
+    ToApub,
   },
-  FromApub,
   PageExt,
-  ToApub,
 };
 use activitystreams::{
   object::{kind::PageType, ApObject, Image, Page, Tombstone},
@@ -17,11 +20,12 @@ use activitystreams::{
 };
 use activitystreams_ext::Ext1;
 use anyhow::Context;
-use backtrace::Backtrace;
 use lemmy_db::{
-  community::Community,
-  post::{Post, PostForm},
-  user::User_,
+  source::{
+    community::Community,
+    post::{Post, PostForm},
+    user::User_,
+  },
   Crud,
   DbPool,
 };
@@ -33,7 +37,6 @@ use lemmy_utils::{
   LemmyError,
 };
 use lemmy_websocket::LemmyContext;
-use log::error;
 use url::Url;
 
 #[async_trait::async_trait(?Send)]
@@ -98,7 +101,7 @@ impl ToApub for Post {
 }
 
 #[async_trait::async_trait(?Send)]
-impl FromApub for PostForm {
+impl FromApub for Post {
   type ApubType = PageExt;
 
   /// Converts a `PageExt` to `PostForm`.
@@ -107,7 +110,20 @@ impl FromApub for PostForm {
   async fn from_apub(
     page: &PageExt,
     context: &LemmyContext,
-    expected_domain: Option<Url>,
+    expected_domain: Url,
+    request_counter: &mut i32,
+  ) -> Result<Post, LemmyError> {
+    check_object_for_community_or_site_ban(page, context, request_counter).await?;
+    get_object_from_apub(page, context, expected_domain, request_counter).await
+  }
+}
+
+#[async_trait::async_trait(?Send)]
+impl FromApubToForm<PageExt> for PostForm {
+  async fn from_apub(
+    page: &PageExt,
+    context: &LemmyContext,
+    expected_domain: Url,
     request_counter: &mut i32,
   ) -> Result<PostForm, LemmyError> {
     let ext = &page.ext_one;
@@ -132,15 +148,6 @@ impl FromApub for PostForm {
     let community =
       get_or_fetch_and_upsert_community(community_actor_id, context, request_counter).await?;
 
-    if community.local && creator.local {
-      let page_id = page.id_unchecked().context(location_info!())?;
-      let bt = Backtrace::new();
-      error!(
-        "Lemmy is parsing a local post as remote, page id: {}, stack trace: {:?}",
-        page_id, bt
-      );
-    }
-
     let thumbnail_url = match &page.inner.image() {
       Some(any_image) => Image::from_any_base(
         any_image
index 4e9af09433754a93f02b0dc2965db0d0b3e8a255..e69c28110d4dee60a8912cedf933733146cdbf2f 100644 (file)
@@ -5,12 +5,14 @@ use crate::{
   objects::{
     check_object_domain,
     create_tombstone,
+    get_object_from_apub,
     get_source_markdown_value,
     set_content_and_source,
+    FromApub,
+    FromApubToForm,
+    ToApub,
   },
-  FromApub,
   NoteExt,
-  ToApub,
 };
 use activitystreams::{
   object::{kind::NoteType, ApObject, Note, Tombstone},
@@ -18,8 +20,10 @@ use activitystreams::{
 };
 use anyhow::Context;
 use lemmy_db::{
-  private_message::{PrivateMessage, PrivateMessageForm},
-  user::User_,
+  source::{
+    private_message::{PrivateMessage, PrivateMessageForm},
+    user::User_,
+  },
   Crud,
   DbPool,
 };
@@ -63,13 +67,25 @@ impl ToApub for PrivateMessage {
 }
 
 #[async_trait::async_trait(?Send)]
-impl FromApub for PrivateMessageForm {
+impl FromApub for PrivateMessage {
   type ApubType = NoteExt;
 
   async fn from_apub(
     note: &NoteExt,
     context: &LemmyContext,
-    expected_domain: Option<Url>,
+    expected_domain: Url,
+    request_counter: &mut i32,
+  ) -> Result<PrivateMessage, LemmyError> {
+    get_object_from_apub(note, context, expected_domain, request_counter).await
+  }
+}
+
+#[async_trait::async_trait(?Send)]
+impl FromApubToForm<NoteExt> for PrivateMessageForm {
+  async fn from_apub(
+    note: &NoteExt,
+    context: &LemmyContext,
+    expected_domain: Url,
     request_counter: &mut i32,
   ) -> Result<PrivateMessageForm, LemmyError> {
     let creator_actor_id = note
index ddf33656c7f7ddc0eee00cac5fbb4380d099875c..8c3312d1f8f8a27ac79e303f4ae479f5fb94e8c1 100644 (file)
@@ -1,10 +1,15 @@
 use crate::{
   extensions::context::lemmy_context,
-  objects::{check_object_domain, get_source_markdown_value, set_content_and_source},
+  objects::{
+    check_object_domain,
+    get_source_markdown_value,
+    set_content_and_source,
+    FromApub,
+    FromApubToForm,
+    ToApub,
+  },
   ActorType,
-  FromApub,
   PersonExt,
-  ToApub,
 };
 use activitystreams::{
   actor::{ApActor, Endpoints, Person},
@@ -15,11 +20,14 @@ use activitystreams_ext::Ext1;
 use anyhow::Context;
 use lemmy_db::{
   naive_now,
-  user::{UserForm, User_},
+  source::user::{UserForm, User_},
+  ApubObject,
   DbPool,
 };
+use lemmy_structs::blocking;
 use lemmy_utils::{
   location_info,
+  settings::Settings,
   utils::{check_slurs, check_slurs_opt, convert_datetime},
   LemmyError,
 };
@@ -81,13 +89,38 @@ impl ToApub for User_ {
 }
 
 #[async_trait::async_trait(?Send)]
-impl FromApub for UserForm {
+impl FromApub for User_ {
   type ApubType = PersonExt;
 
+  async fn from_apub(
+    person: &PersonExt,
+    context: &LemmyContext,
+    expected_domain: Url,
+    request_counter: &mut i32,
+  ) -> Result<User_, LemmyError> {
+    let user_id = person.id_unchecked().context(location_info!())?.to_owned();
+    let domain = user_id.domain().context(location_info!())?;
+    if domain == Settings::get().hostname {
+      let user = blocking(context.pool(), move |conn| {
+        User_::read_from_apub_id(conn, user_id.as_str())
+      })
+      .await??;
+      Ok(user)
+    } else {
+      let user_form =
+        UserForm::from_apub(person, context, expected_domain, request_counter).await?;
+      let user = blocking(context.pool(), move |conn| User_::upsert(conn, &user_form)).await??;
+      Ok(user)
+    }
+  }
+}
+
+#[async_trait::async_trait(?Send)]
+impl FromApubToForm<PersonExt> for UserForm {
   async fn from_apub(
     person: &PersonExt,
     _context: &LemmyContext,
-    expected_domain: Option<Url>,
+    expected_domain: Url,
     _request_counter: &mut i32,
   ) -> Result<Self, LemmyError> {
     let avatar = match person.icon() {
index f85225e09d6832f08571ccae333186d41c772cc9..5963f32cc331e8c81f77691417db884704627337 100644 (file)
@@ -9,16 +9,16 @@ path = "src/lib.rs"
 
 [dependencies]
 lemmy_utils = { path = "../lemmy_utils" }
-diesel = { version = "1.4", features = ["postgres","chrono","r2d2","64-column-tables","serde_json"] }
-diesel_migrations = "1.4"
-chrono = { version = "0.4", features = ["serde"] }
-serde = { version = "1.0", features = ["derive"] }
-serde_json = { version = "1.0", features = ["preserve_order"]}
-strum = "0.19"
-strum_macros = "0.19"
-log = "0.4"
-sha2 = "0.9"
-bcrypt = "0.8"
-url = { version = "2.1", features = ["serde"] }
-lazy_static = "1.3"
-regex = "1.3"
+diesel = { version = "1.4.5", features = ["postgres","chrono","r2d2","serde_json"] }
+diesel_migrations = "1.4.0"
+chrono = { version = "0.4.19", features = ["serde"] }
+serde = { version = "1.0.118", features = ["derive"] }
+serde_json = { version = "1.0.60", features = ["preserve_order"] }
+strum = "0.20.0"
+strum_macros = "0.20.1"
+log = "0.4.11"
+sha2 = "0.9.2"
+bcrypt = "0.9.0"
+url = { version = "2.2.0", features = ["serde"] }
+lazy_static = "1.4.0"
+regex = "1.4.2"
diff --git a/lemmy_db/src/aggregates/comment_aggregates.rs b/lemmy_db/src/aggregates/comment_aggregates.rs
new file mode 100644 (file)
index 0000000..7304dac
--- /dev/null
@@ -0,0 +1,229 @@
+use crate::schema::comment_aggregates;
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
+#[table_name = "comment_aggregates"]
+pub struct CommentAggregates {
+  pub id: i32,
+  pub comment_id: i32,
+  pub score: i64,
+  pub upvotes: i64,
+  pub downvotes: i64,
+}
+
+impl CommentAggregates {
+  pub fn read(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
+    comment_aggregates::table
+      .filter(comment_aggregates::comment_id.eq(comment_id))
+      .first::<Self>(conn)
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::{
+    aggregates::comment_aggregates::CommentAggregates,
+    source::{
+      comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
+      community::{Community, CommunityForm},
+      post::{Post, PostForm},
+      user::{UserForm, User_},
+    },
+    tests::establish_unpooled_connection,
+    Crud,
+    Likeable,
+    ListingType,
+    SortType,
+  };
+
+  #[test]
+  fn test_crud() {
+    let conn = establish_unpooled_connection();
+
+    let new_user = UserForm {
+      name: "thommy_comment_agg".into(),
+      preferred_username: None,
+      password_encrypted: "nope".into(),
+      email: None,
+      matrix_user_id: None,
+      avatar: None,
+      banner: None,
+      admin: false,
+      banned: Some(false),
+      published: None,
+      updated: None,
+      show_nsfw: false,
+      theme: "browser".into(),
+      default_sort_type: SortType::Hot as i16,
+      default_listing_type: ListingType::Subscribed as i16,
+      lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
+      actor_id: None,
+      bio: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+    };
+
+    let inserted_user = User_::create(&conn, &new_user).unwrap();
+
+    let another_user = UserForm {
+      name: "jerry_comment_agg".into(),
+      preferred_username: None,
+      password_encrypted: "nope".into(),
+      email: None,
+      matrix_user_id: None,
+      avatar: None,
+      banner: None,
+      admin: false,
+      banned: Some(false),
+      published: None,
+      updated: None,
+      show_nsfw: false,
+      theme: "browser".into(),
+      default_sort_type: SortType::Hot as i16,
+      default_listing_type: ListingType::Subscribed as i16,
+      lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
+      actor_id: None,
+      bio: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+    };
+
+    let another_inserted_user = User_::create(&conn, &another_user).unwrap();
+
+    let new_community = CommunityForm {
+      name: "TIL_comment_agg".into(),
+      creator_id: inserted_user.id,
+      title: "nada".to_owned(),
+      description: None,
+      category_id: 1,
+      nsfw: false,
+      removed: None,
+      deleted: None,
+      updated: None,
+      actor_id: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+      published: None,
+      icon: None,
+      banner: None,
+    };
+
+    let inserted_community = Community::create(&conn, &new_community).unwrap();
+
+    let new_post = PostForm {
+      name: "A test post".into(),
+      url: None,
+      body: None,
+      creator_id: inserted_user.id,
+      community_id: inserted_community.id,
+      removed: None,
+      deleted: None,
+      locked: None,
+      stickied: None,
+      nsfw: false,
+      updated: None,
+      embed_title: None,
+      embed_description: None,
+      embed_html: None,
+      thumbnail_url: None,
+      ap_id: None,
+      local: true,
+      published: None,
+    };
+
+    let inserted_post = Post::create(&conn, &new_post).unwrap();
+
+    let comment_form = CommentForm {
+      content: "A test comment".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      removed: None,
+      deleted: None,
+      read: None,
+      parent_id: None,
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
+
+    let child_comment_form = CommentForm {
+      content: "A test comment".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      removed: None,
+      deleted: None,
+      read: None,
+      parent_id: Some(inserted_comment.id),
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    let _inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
+
+    let comment_like = CommentLikeForm {
+      comment_id: inserted_comment.id,
+      post_id: inserted_post.id,
+      user_id: inserted_user.id,
+      score: 1,
+    };
+
+    CommentLike::like(&conn, &comment_like).unwrap();
+
+    let comment_aggs_before_delete = CommentAggregates::read(&conn, inserted_comment.id).unwrap();
+
+    assert_eq!(1, comment_aggs_before_delete.score);
+    assert_eq!(1, comment_aggs_before_delete.upvotes);
+    assert_eq!(0, comment_aggs_before_delete.downvotes);
+
+    // Add a post dislike from the other user
+    let comment_dislike = CommentLikeForm {
+      comment_id: inserted_comment.id,
+      post_id: inserted_post.id,
+      user_id: another_inserted_user.id,
+      score: -1,
+    };
+
+    CommentLike::like(&conn, &comment_dislike).unwrap();
+
+    let comment_aggs_after_dislike = CommentAggregates::read(&conn, inserted_comment.id).unwrap();
+
+    assert_eq!(0, comment_aggs_after_dislike.score);
+    assert_eq!(1, comment_aggs_after_dislike.upvotes);
+    assert_eq!(1, comment_aggs_after_dislike.downvotes);
+
+    // Remove the first comment like
+    CommentLike::remove(&conn, inserted_user.id, inserted_comment.id).unwrap();
+    let after_like_remove = CommentAggregates::read(&conn, inserted_comment.id).unwrap();
+    assert_eq!(-1, after_like_remove.score);
+    assert_eq!(0, after_like_remove.upvotes);
+    assert_eq!(1, after_like_remove.downvotes);
+
+    // Remove the parent post
+    Post::delete(&conn, inserted_post.id).unwrap();
+
+    // Should be none found, since the post was deleted
+    let after_delete = CommentAggregates::read(&conn, inserted_comment.id);
+    assert!(after_delete.is_err());
+
+    // This should delete all the associated rows, and fire triggers
+    User_::delete(&conn, another_inserted_user.id).unwrap();
+    let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
+    assert_eq!(1, user_num_deleted);
+  }
+}
diff --git a/lemmy_db/src/aggregates/community_aggregates.rs b/lemmy_db/src/aggregates/community_aggregates.rs
new file mode 100644 (file)
index 0000000..8c977bf
--- /dev/null
@@ -0,0 +1,268 @@
+use crate::schema::community_aggregates;
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
+#[table_name = "community_aggregates"]
+pub struct CommunityAggregates {
+  pub id: i32,
+  pub community_id: i32,
+  pub subscribers: i64,
+  pub posts: i64,
+  pub comments: i64,
+}
+
+impl CommunityAggregates {
+  pub fn read(conn: &PgConnection, community_id: i32) -> Result<Self, Error> {
+    community_aggregates::table
+      .filter(community_aggregates::community_id.eq(community_id))
+      .first::<Self>(conn)
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::{
+    aggregates::community_aggregates::CommunityAggregates,
+    source::{
+      comment::{Comment, CommentForm},
+      community::{Community, CommunityFollower, CommunityFollowerForm, CommunityForm},
+      post::{Post, PostForm},
+      user::{UserForm, User_},
+    },
+    tests::establish_unpooled_connection,
+    Crud,
+    Followable,
+    ListingType,
+    SortType,
+  };
+
+  #[test]
+  fn test_crud() {
+    let conn = establish_unpooled_connection();
+
+    let new_user = UserForm {
+      name: "thommy_community_agg".into(),
+      preferred_username: None,
+      password_encrypted: "nope".into(),
+      email: None,
+      matrix_user_id: None,
+      avatar: None,
+      banner: None,
+      admin: false,
+      banned: Some(false),
+      published: None,
+      updated: None,
+      show_nsfw: false,
+      theme: "browser".into(),
+      default_sort_type: SortType::Hot as i16,
+      default_listing_type: ListingType::Subscribed as i16,
+      lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
+      actor_id: None,
+      bio: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+    };
+
+    let inserted_user = User_::create(&conn, &new_user).unwrap();
+
+    let another_user = UserForm {
+      name: "jerry_community_agg".into(),
+      preferred_username: None,
+      password_encrypted: "nope".into(),
+      email: None,
+      matrix_user_id: None,
+      avatar: None,
+      banner: None,
+      admin: false,
+      banned: Some(false),
+      published: None,
+      updated: None,
+      show_nsfw: false,
+      theme: "browser".into(),
+      default_sort_type: SortType::Hot as i16,
+      default_listing_type: ListingType::Subscribed as i16,
+      lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
+      actor_id: None,
+      bio: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+    };
+
+    let another_inserted_user = User_::create(&conn, &another_user).unwrap();
+
+    let new_community = CommunityForm {
+      name: "TIL_community_agg".into(),
+      creator_id: inserted_user.id,
+      title: "nada".to_owned(),
+      description: None,
+      category_id: 1,
+      nsfw: false,
+      removed: None,
+      deleted: None,
+      updated: None,
+      actor_id: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+      published: None,
+      icon: None,
+      banner: None,
+    };
+
+    let inserted_community = Community::create(&conn, &new_community).unwrap();
+
+    let another_community = CommunityForm {
+      name: "TIL_community_agg_2".into(),
+      creator_id: inserted_user.id,
+      title: "nada".to_owned(),
+      description: None,
+      category_id: 1,
+      nsfw: false,
+      removed: None,
+      deleted: None,
+      updated: None,
+      actor_id: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+      published: None,
+      icon: None,
+      banner: None,
+    };
+
+    let another_inserted_community = Community::create(&conn, &another_community).unwrap();
+
+    let first_user_follow = CommunityFollowerForm {
+      community_id: inserted_community.id,
+      user_id: inserted_user.id,
+      pending: false,
+    };
+
+    CommunityFollower::follow(&conn, &first_user_follow).unwrap();
+
+    let second_user_follow = CommunityFollowerForm {
+      community_id: inserted_community.id,
+      user_id: another_inserted_user.id,
+      pending: false,
+    };
+
+    CommunityFollower::follow(&conn, &second_user_follow).unwrap();
+
+    let another_community_follow = CommunityFollowerForm {
+      community_id: another_inserted_community.id,
+      user_id: inserted_user.id,
+      pending: false,
+    };
+
+    CommunityFollower::follow(&conn, &another_community_follow).unwrap();
+
+    let new_post = PostForm {
+      name: "A test post".into(),
+      url: None,
+      body: None,
+      creator_id: inserted_user.id,
+      community_id: inserted_community.id,
+      removed: None,
+      deleted: None,
+      locked: None,
+      stickied: None,
+      nsfw: false,
+      updated: None,
+      embed_title: None,
+      embed_description: None,
+      embed_html: None,
+      thumbnail_url: None,
+      ap_id: None,
+      local: true,
+      published: None,
+    };
+
+    let inserted_post = Post::create(&conn, &new_post).unwrap();
+
+    let comment_form = CommentForm {
+      content: "A test comment".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      removed: None,
+      deleted: None,
+      read: None,
+      parent_id: None,
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
+
+    let child_comment_form = CommentForm {
+      content: "A test comment".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      removed: None,
+      deleted: None,
+      read: None,
+      parent_id: Some(inserted_comment.id),
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    let _inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
+
+    let community_aggregates_before_delete =
+      CommunityAggregates::read(&conn, inserted_community.id).unwrap();
+
+    assert_eq!(2, community_aggregates_before_delete.subscribers);
+    assert_eq!(1, community_aggregates_before_delete.posts);
+    assert_eq!(2, community_aggregates_before_delete.comments);
+
+    // Test the other community
+    let another_community_aggs =
+      CommunityAggregates::read(&conn, another_inserted_community.id).unwrap();
+    assert_eq!(1, another_community_aggs.subscribers);
+    assert_eq!(0, another_community_aggs.posts);
+    assert_eq!(0, another_community_aggs.comments);
+
+    // Unfollow test
+    CommunityFollower::unfollow(&conn, &second_user_follow).unwrap();
+    let after_unfollow = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
+    assert_eq!(1, after_unfollow.subscribers);
+
+    // Follow again just for the later tests
+    CommunityFollower::follow(&conn, &second_user_follow).unwrap();
+    let after_follow_again = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
+    assert_eq!(2, after_follow_again.subscribers);
+
+    // Remove a parent comment (the comment count should also be 0)
+    Post::delete(&conn, inserted_post.id).unwrap();
+    let after_parent_post_delete = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
+    assert_eq!(0, after_parent_post_delete.comments);
+    assert_eq!(0, after_parent_post_delete.posts);
+
+    // Remove the 2nd user
+    User_::delete(&conn, another_inserted_user.id).unwrap();
+    let after_user_delete = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
+    assert_eq!(1, after_user_delete.subscribers);
+
+    // This should delete all the associated rows, and fire triggers
+    let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
+    assert_eq!(1, user_num_deleted);
+
+    // Should be none found, since the creator was deleted
+    let after_delete = CommunityAggregates::read(&conn, inserted_community.id);
+    assert!(after_delete.is_err());
+  }
+}
diff --git a/lemmy_db/src/aggregates/mod.rs b/lemmy_db/src/aggregates/mod.rs
new file mode 100644 (file)
index 0000000..bdef659
--- /dev/null
@@ -0,0 +1,5 @@
+pub mod comment_aggregates;
+pub mod community_aggregates;
+pub mod post_aggregates;
+pub mod site_aggregates;
+pub mod user_aggregates;
diff --git a/lemmy_db/src/aggregates/post_aggregates.rs b/lemmy_db/src/aggregates/post_aggregates.rs
new file mode 100644 (file)
index 0000000..dff16f9
--- /dev/null
@@ -0,0 +1,237 @@
+use crate::schema::post_aggregates;
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
+#[table_name = "post_aggregates"]
+pub struct PostAggregates {
+  pub id: i32,
+  pub post_id: i32,
+  pub comments: i64,
+  pub score: i64,
+  pub upvotes: i64,
+  pub downvotes: i64,
+  pub newest_comment_time: chrono::NaiveDateTime,
+}
+
+impl PostAggregates {
+  pub fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
+    post_aggregates::table
+      .filter(post_aggregates::post_id.eq(post_id))
+      .first::<Self>(conn)
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::{
+    aggregates::post_aggregates::PostAggregates,
+    source::{
+      comment::{Comment, CommentForm},
+      community::{Community, CommunityForm},
+      post::{Post, PostForm, PostLike, PostLikeForm},
+      user::{UserForm, User_},
+    },
+    tests::establish_unpooled_connection,
+    Crud,
+    Likeable,
+    ListingType,
+    SortType,
+  };
+
+  #[test]
+  fn test_crud() {
+    let conn = establish_unpooled_connection();
+
+    let new_user = UserForm {
+      name: "thommy_community_agg".into(),
+      preferred_username: None,
+      password_encrypted: "nope".into(),
+      email: None,
+      matrix_user_id: None,
+      avatar: None,
+      banner: None,
+      admin: false,
+      banned: Some(false),
+      published: None,
+      updated: None,
+      show_nsfw: false,
+      theme: "browser".into(),
+      default_sort_type: SortType::Hot as i16,
+      default_listing_type: ListingType::Subscribed as i16,
+      lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
+      actor_id: None,
+      bio: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+    };
+
+    let inserted_user = User_::create(&conn, &new_user).unwrap();
+
+    let another_user = UserForm {
+      name: "jerry_community_agg".into(),
+      preferred_username: None,
+      password_encrypted: "nope".into(),
+      email: None,
+      matrix_user_id: None,
+      avatar: None,
+      banner: None,
+      admin: false,
+      banned: Some(false),
+      published: None,
+      updated: None,
+      show_nsfw: false,
+      theme: "browser".into(),
+      default_sort_type: SortType::Hot as i16,
+      default_listing_type: ListingType::Subscribed as i16,
+      lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
+      actor_id: None,
+      bio: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+    };
+
+    let another_inserted_user = User_::create(&conn, &another_user).unwrap();
+
+    let new_community = CommunityForm {
+      name: "TIL_community_agg".into(),
+      creator_id: inserted_user.id,
+      title: "nada".to_owned(),
+      description: None,
+      category_id: 1,
+      nsfw: false,
+      removed: None,
+      deleted: None,
+      updated: None,
+      actor_id: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+      published: None,
+      icon: None,
+      banner: None,
+    };
+
+    let inserted_community = Community::create(&conn, &new_community).unwrap();
+
+    let new_post = PostForm {
+      name: "A test post".into(),
+      url: None,
+      body: None,
+      creator_id: inserted_user.id,
+      community_id: inserted_community.id,
+      removed: None,
+      deleted: None,
+      locked: None,
+      stickied: None,
+      nsfw: false,
+      updated: None,
+      embed_title: None,
+      embed_description: None,
+      embed_html: None,
+      thumbnail_url: None,
+      ap_id: None,
+      local: true,
+      published: None,
+    };
+
+    let inserted_post = Post::create(&conn, &new_post).unwrap();
+
+    let comment_form = CommentForm {
+      content: "A test comment".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      removed: None,
+      deleted: None,
+      read: None,
+      parent_id: None,
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
+
+    let child_comment_form = CommentForm {
+      content: "A test comment".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      removed: None,
+      deleted: None,
+      read: None,
+      parent_id: Some(inserted_comment.id),
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    let _inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
+
+    let post_like = PostLikeForm {
+      post_id: inserted_post.id,
+      user_id: inserted_user.id,
+      score: 1,
+    };
+
+    PostLike::like(&conn, &post_like).unwrap();
+
+    let post_aggs_before_delete = PostAggregates::read(&conn, inserted_post.id).unwrap();
+
+    assert_eq!(2, post_aggs_before_delete.comments);
+    assert_eq!(1, post_aggs_before_delete.score);
+    assert_eq!(1, post_aggs_before_delete.upvotes);
+    assert_eq!(0, post_aggs_before_delete.downvotes);
+
+    // Add a post dislike from the other user
+    let post_dislike = PostLikeForm {
+      post_id: inserted_post.id,
+      user_id: another_inserted_user.id,
+      score: -1,
+    };
+
+    PostLike::like(&conn, &post_dislike).unwrap();
+
+    let post_aggs_after_dislike = PostAggregates::read(&conn, inserted_post.id).unwrap();
+
+    assert_eq!(2, post_aggs_after_dislike.comments);
+    assert_eq!(0, post_aggs_after_dislike.score);
+    assert_eq!(1, post_aggs_after_dislike.upvotes);
+    assert_eq!(1, post_aggs_after_dislike.downvotes);
+
+    // Remove the parent comment
+    Comment::delete(&conn, inserted_comment.id).unwrap();
+    let after_comment_delete = PostAggregates::read(&conn, inserted_post.id).unwrap();
+    assert_eq!(0, after_comment_delete.comments);
+    assert_eq!(0, after_comment_delete.score);
+    assert_eq!(1, after_comment_delete.upvotes);
+    assert_eq!(1, after_comment_delete.downvotes);
+
+    // Remove the first post like
+    PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
+    let after_like_remove = PostAggregates::read(&conn, inserted_post.id).unwrap();
+    assert_eq!(0, after_like_remove.comments);
+    assert_eq!(-1, after_like_remove.score);
+    assert_eq!(0, after_like_remove.upvotes);
+    assert_eq!(1, after_like_remove.downvotes);
+
+    // This should delete all the associated rows, and fire triggers
+    User_::delete(&conn, another_inserted_user.id).unwrap();
+    let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
+    assert_eq!(1, user_num_deleted);
+
+    // Should be none found, since the creator was deleted
+    let after_delete = PostAggregates::read(&conn, inserted_post.id);
+    assert!(after_delete.is_err());
+  }
+}
diff --git a/lemmy_db/src/aggregates/site_aggregates.rs b/lemmy_db/src/aggregates/site_aggregates.rs
new file mode 100644 (file)
index 0000000..df3cad6
--- /dev/null
@@ -0,0 +1,186 @@
+use crate::schema::site_aggregates;
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
+#[table_name = "site_aggregates"]
+pub struct SiteAggregates {
+  pub id: i32,
+  pub site_id: i32,
+  pub users: i64,
+  pub posts: i64,
+  pub comments: i64,
+  pub communities: i64,
+}
+
+impl SiteAggregates {
+  pub fn read(conn: &PgConnection) -> Result<Self, Error> {
+    site_aggregates::table.first::<Self>(conn)
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::{
+    aggregates::site_aggregates::SiteAggregates,
+    source::{
+      comment::{Comment, CommentForm},
+      community::{Community, CommunityForm},
+      post::{Post, PostForm},
+      site::{Site, SiteForm},
+      user::{UserForm, User_},
+    },
+    tests::establish_unpooled_connection,
+    Crud,
+    ListingType,
+    SortType,
+  };
+
+  #[test]
+  fn test_crud() {
+    let conn = establish_unpooled_connection();
+
+    let new_user = UserForm {
+      name: "thommy_site_agg".into(),
+      preferred_username: None,
+      password_encrypted: "nope".into(),
+      email: None,
+      matrix_user_id: None,
+      avatar: None,
+      banner: None,
+      admin: false,
+      banned: Some(false),
+      published: None,
+      updated: None,
+      show_nsfw: false,
+      theme: "browser".into(),
+      default_sort_type: SortType::Hot as i16,
+      default_listing_type: ListingType::Subscribed as i16,
+      lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
+      actor_id: None,
+      bio: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+    };
+
+    let inserted_user = User_::create(&conn, &new_user).unwrap();
+
+    let site_form = SiteForm {
+      name: "test_site".into(),
+      description: None,
+      icon: None,
+      banner: None,
+      creator_id: inserted_user.id,
+      enable_downvotes: true,
+      open_registration: true,
+      enable_nsfw: true,
+      updated: None,
+    };
+
+    Site::create(&conn, &site_form).unwrap();
+
+    let new_community = CommunityForm {
+      name: "TIL_site_agg".into(),
+      creator_id: inserted_user.id,
+      title: "nada".to_owned(),
+      description: None,
+      category_id: 1,
+      nsfw: false,
+      removed: None,
+      deleted: None,
+      updated: None,
+      actor_id: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+      published: None,
+      icon: None,
+      banner: None,
+    };
+
+    let inserted_community = Community::create(&conn, &new_community).unwrap();
+
+    let new_post = PostForm {
+      name: "A test post".into(),
+      url: None,
+      body: None,
+      creator_id: inserted_user.id,
+      community_id: inserted_community.id,
+      removed: None,
+      deleted: None,
+      locked: None,
+      stickied: None,
+      nsfw: false,
+      updated: None,
+      embed_title: None,
+      embed_description: None,
+      embed_html: None,
+      thumbnail_url: None,
+      ap_id: None,
+      local: true,
+      published: None,
+    };
+
+    // Insert two of those posts
+    let inserted_post = Post::create(&conn, &new_post).unwrap();
+    let _inserted_post_again = Post::create(&conn, &new_post).unwrap();
+
+    let comment_form = CommentForm {
+      content: "A test comment".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      removed: None,
+      deleted: None,
+      read: None,
+      parent_id: None,
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    // Insert two of those comments
+    let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
+
+    let child_comment_form = CommentForm {
+      content: "A test comment".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      removed: None,
+      deleted: None,
+      read: None,
+      parent_id: Some(inserted_comment.id),
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    let _inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
+
+    let site_aggregates_before_delete = SiteAggregates::read(&conn).unwrap();
+
+    assert_eq!(1, site_aggregates_before_delete.users);
+    assert_eq!(1, site_aggregates_before_delete.communities);
+    assert_eq!(2, site_aggregates_before_delete.posts);
+    assert_eq!(2, site_aggregates_before_delete.comments);
+
+    // Try a post delete
+    Post::delete(&conn, inserted_post.id).unwrap();
+    let site_aggregates_after_post_delete = SiteAggregates::read(&conn).unwrap();
+    assert_eq!(1, site_aggregates_after_post_delete.posts);
+    assert_eq!(0, site_aggregates_after_post_delete.comments);
+
+    // This shouuld delete all the associated rows, and fire triggers
+    let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
+    assert_eq!(1, user_num_deleted);
+
+    let after_delete = SiteAggregates::read(&conn);
+    assert!(after_delete.is_err());
+  }
+}
diff --git a/lemmy_db/src/aggregates/user_aggregates.rs b/lemmy_db/src/aggregates/user_aggregates.rs
new file mode 100644 (file)
index 0000000..104bf6f
--- /dev/null
@@ -0,0 +1,250 @@
+use crate::schema::user_aggregates;
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
+#[table_name = "user_aggregates"]
+pub struct UserAggregates {
+  pub id: i32,
+  pub user_id: i32,
+  pub post_count: i64,
+  pub post_score: i64,
+  pub comment_count: i64,
+  pub comment_score: i64,
+}
+
+impl UserAggregates {
+  pub fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
+    user_aggregates::table
+      .filter(user_aggregates::user_id.eq(user_id))
+      .first::<Self>(conn)
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::{
+    aggregates::user_aggregates::UserAggregates,
+    source::{
+      comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
+      community::{Community, CommunityForm},
+      post::{Post, PostForm, PostLike, PostLikeForm},
+      user::{UserForm, User_},
+    },
+    tests::establish_unpooled_connection,
+    Crud,
+    Likeable,
+    ListingType,
+    SortType,
+  };
+
+  #[test]
+  fn test_crud() {
+    let conn = establish_unpooled_connection();
+
+    let new_user = UserForm {
+      name: "thommy_user_agg".into(),
+      preferred_username: None,
+      password_encrypted: "nope".into(),
+      email: None,
+      matrix_user_id: None,
+      avatar: None,
+      banner: None,
+      admin: false,
+      banned: Some(false),
+      published: None,
+      updated: None,
+      show_nsfw: false,
+      theme: "browser".into(),
+      default_sort_type: SortType::Hot as i16,
+      default_listing_type: ListingType::Subscribed as i16,
+      lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
+      actor_id: None,
+      bio: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+    };
+
+    let inserted_user = User_::create(&conn, &new_user).unwrap();
+
+    let another_user = UserForm {
+      name: "jerry_user_agg".into(),
+      preferred_username: None,
+      password_encrypted: "nope".into(),
+      email: None,
+      matrix_user_id: None,
+      avatar: None,
+      banner: None,
+      admin: false,
+      banned: Some(false),
+      published: None,
+      updated: None,
+      show_nsfw: false,
+      theme: "browser".into(),
+      default_sort_type: SortType::Hot as i16,
+      default_listing_type: ListingType::Subscribed as i16,
+      lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
+      actor_id: None,
+      bio: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+    };
+
+    let another_inserted_user = User_::create(&conn, &another_user).unwrap();
+
+    let new_community = CommunityForm {
+      name: "TIL_site_agg".into(),
+      creator_id: inserted_user.id,
+      title: "nada".to_owned(),
+      description: None,
+      category_id: 1,
+      nsfw: false,
+      removed: None,
+      deleted: None,
+      updated: None,
+      actor_id: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+      published: None,
+      icon: None,
+      banner: None,
+    };
+
+    let inserted_community = Community::create(&conn, &new_community).unwrap();
+
+    let new_post = PostForm {
+      name: "A test post".into(),
+      url: None,
+      body: None,
+      creator_id: inserted_user.id,
+      community_id: inserted_community.id,
+      removed: None,
+      deleted: None,
+      locked: None,
+      stickied: None,
+      nsfw: false,
+      updated: None,
+      embed_title: None,
+      embed_description: None,
+      embed_html: None,
+      thumbnail_url: None,
+      ap_id: None,
+      local: true,
+      published: None,
+    };
+
+    let inserted_post = Post::create(&conn, &new_post).unwrap();
+
+    let post_like = PostLikeForm {
+      post_id: inserted_post.id,
+      user_id: inserted_user.id,
+      score: 1,
+    };
+
+    let _inserted_post_like = PostLike::like(&conn, &post_like).unwrap();
+
+    let comment_form = CommentForm {
+      content: "A test comment".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      removed: None,
+      deleted: None,
+      read: None,
+      parent_id: None,
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
+
+    let mut comment_like = CommentLikeForm {
+      comment_id: inserted_comment.id,
+      user_id: inserted_user.id,
+      post_id: inserted_post.id,
+      score: 1,
+    };
+
+    let _inserted_comment_like = CommentLike::like(&conn, &comment_like).unwrap();
+
+    let mut child_comment_form = CommentForm {
+      content: "A test comment".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      removed: None,
+      deleted: None,
+      read: None,
+      parent_id: Some(inserted_comment.id),
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    let inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
+
+    let child_comment_like = CommentLikeForm {
+      comment_id: inserted_child_comment.id,
+      user_id: another_inserted_user.id,
+      post_id: inserted_post.id,
+      score: 1,
+    };
+
+    let _inserted_child_comment_like = CommentLike::like(&conn, &child_comment_like).unwrap();
+
+    let user_aggregates_before_delete = UserAggregates::read(&conn, inserted_user.id).unwrap();
+
+    assert_eq!(1, user_aggregates_before_delete.post_count);
+    assert_eq!(1, user_aggregates_before_delete.post_score);
+    assert_eq!(2, user_aggregates_before_delete.comment_count);
+    assert_eq!(2, user_aggregates_before_delete.comment_score);
+
+    // Remove a post like
+    PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
+    let after_post_like_remove = UserAggregates::read(&conn, inserted_user.id).unwrap();
+    assert_eq!(0, after_post_like_remove.post_score);
+
+    // Remove a parent comment (the scores should also be removed)
+    Comment::delete(&conn, inserted_comment.id).unwrap();
+    let after_parent_comment_delete = UserAggregates::read(&conn, inserted_user.id).unwrap();
+    assert_eq!(0, after_parent_comment_delete.comment_count);
+    assert_eq!(0, after_parent_comment_delete.comment_score);
+
+    // Add in the two comments again, then delete the post.
+    let new_parent_comment = Comment::create(&conn, &comment_form).unwrap();
+    child_comment_form.parent_id = Some(new_parent_comment.id);
+    Comment::create(&conn, &child_comment_form).unwrap();
+    comment_like.comment_id = new_parent_comment.id;
+    CommentLike::like(&conn, &comment_like).unwrap();
+    let after_comment_add = UserAggregates::read(&conn, inserted_user.id).unwrap();
+    assert_eq!(2, after_comment_add.comment_count);
+    assert_eq!(1, after_comment_add.comment_score);
+
+    Post::delete(&conn, inserted_post.id).unwrap();
+    let after_post_delete = UserAggregates::read(&conn, inserted_user.id).unwrap();
+    assert_eq!(0, after_post_delete.comment_score);
+    assert_eq!(0, after_post_delete.comment_count);
+    assert_eq!(0, after_post_delete.post_score);
+    assert_eq!(0, after_post_delete.post_count);
+
+    // This should delete all the associated rows, and fire triggers
+    let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
+    assert_eq!(1, user_num_deleted);
+    User_::delete(&conn, another_inserted_user.id).unwrap();
+
+    // Should be none found
+    let after_delete = UserAggregates::read(&conn, inserted_user.id);
+    assert!(after_delete.is_err());
+  }
+}
diff --git a/lemmy_db/src/comment_report.rs b/lemmy_db/src/comment_report.rs
deleted file mode 100644 (file)
index a243891..0000000
+++ /dev/null
@@ -1,235 +0,0 @@
-use diesel::{dsl::*, pg::Pg, result::Error, *};
-use serde::{Deserialize, Serialize};
-
-use crate::{
-  comment::Comment,
-  limit_and_offset,
-  naive_now,
-  schema::comment_report,
-  MaybeOptional,
-  Reportable,
-};
-
-table! {
-    comment_report_view (id) {
-      id -> Int4,
-      creator_id -> Int4,
-      comment_id -> Int4,
-      original_comment_text -> Text,
-      reason -> Text,
-      resolved -> Bool,
-      resolver_id -> Nullable<Int4>,
-      published -> Timestamp,
-      updated -> Nullable<Timestamp>,
-      post_id -> Int4,
-      current_comment_text -> Text,
-      community_id -> Int4,
-      creator_actor_id -> Text,
-      creator_name -> Varchar,
-      creator_preferred_username -> Nullable<Varchar>,
-      creator_avatar -> Nullable<Text>,
-      creator_local -> Bool,
-      comment_creator_id -> Int4,
-      comment_creator_actor_id -> Text,
-      comment_creator_name -> Varchar,
-      comment_creator_preferred_username -> Nullable<Varchar>,
-      comment_creator_avatar -> Nullable<Text>,
-      comment_creator_local -> Bool,
-      resolver_actor_id -> Nullable<Text>,
-      resolver_name -> Nullable<Varchar>,
-      resolver_preferred_username -> Nullable<Varchar>,
-      resolver_avatar -> Nullable<Text>,
-      resolver_local -> Nullable<Bool>,
-    }
-}
-
-#[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Serialize)]
-#[belongs_to(Comment)]
-#[table_name = "comment_report"]
-pub struct CommentReport {
-  pub id: i32,
-  pub creator_id: i32,
-  pub comment_id: i32,
-  pub original_comment_text: String,
-  pub reason: String,
-  pub resolved: bool,
-  pub resolver_id: Option<i32>,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-}
-
-#[derive(Insertable, AsChangeset, Clone)]
-#[table_name = "comment_report"]
-pub struct CommentReportForm {
-  pub creator_id: i32,
-  pub comment_id: i32,
-  pub original_comment_text: String,
-  pub reason: String,
-}
-
-impl Reportable<CommentReportForm> for CommentReport {
-  /// creates a comment report and returns it
-  ///
-  /// * `conn` - the postgres connection
-  /// * `comment_report_form` - the filled CommentReportForm to insert
-  fn report(conn: &PgConnection, comment_report_form: &CommentReportForm) -> Result<Self, Error> {
-    use crate::schema::comment_report::dsl::*;
-    insert_into(comment_report)
-      .values(comment_report_form)
-      .get_result::<Self>(conn)
-  }
-
-  /// resolve a comment report
-  ///
-  /// * `conn` - the postgres connection
-  /// * `report_id` - the id of the report to resolve
-  /// * `by_resolver_id` - the id of the user resolving the report
-  fn resolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
-    use crate::schema::comment_report::dsl::*;
-    update(comment_report.find(report_id))
-      .set((
-        resolved.eq(true),
-        resolver_id.eq(by_resolver_id),
-        updated.eq(naive_now()),
-      ))
-      .execute(conn)
-  }
-
-  /// unresolve a comment report
-  ///
-  /// * `conn` - the postgres connection
-  /// * `report_id` - the id of the report to unresolve
-  /// * `by_resolver_id` - the id of the user unresolving the report
-  fn unresolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
-    use crate::schema::comment_report::dsl::*;
-    update(comment_report.find(report_id))
-      .set((
-        resolved.eq(false),
-        resolver_id.eq(by_resolver_id),
-        updated.eq(naive_now()),
-      ))
-      .execute(conn)
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone)]
-#[table_name = "comment_report_view"]
-pub struct CommentReportView {
-  pub id: i32,
-  pub creator_id: i32,
-  pub comment_id: i32,
-  pub original_comment_text: String,
-  pub reason: String,
-  pub resolved: bool,
-  pub resolver_id: Option<i32>,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub post_id: i32,
-  pub current_comment_text: String,
-  pub community_id: i32,
-  pub creator_actor_id: String,
-  pub creator_name: String,
-  pub creator_preferred_username: Option<String>,
-  pub creator_avatar: Option<String>,
-  pub creator_local: bool,
-  pub comment_creator_id: i32,
-  pub comment_creator_actor_id: String,
-  pub comment_creator_name: String,
-  pub comment_creator_preferred_username: Option<String>,
-  pub comment_creator_avatar: Option<String>,
-  pub comment_creator_local: bool,
-  pub resolver_actor_id: Option<String>,
-  pub resolver_name: Option<String>,
-  pub resolver_preferred_username: Option<String>,
-  pub resolver_avatar: Option<String>,
-  pub resolver_local: Option<bool>,
-}
-
-pub struct CommentReportQueryBuilder<'a> {
-  conn: &'a PgConnection,
-  query: comment_report_view::BoxedQuery<'a, Pg>,
-  for_community_ids: Option<Vec<i32>>,
-  page: Option<i64>,
-  limit: Option<i64>,
-  resolved: Option<bool>,
-}
-
-impl CommentReportView {
-  /// returns the CommentReportView for the provided report_id
-  ///
-  /// * `report_id` - the report id to obtain
-  pub fn read(conn: &PgConnection, report_id: i32) -> Result<Self, Error> {
-    use super::comment_report::comment_report_view::dsl::*;
-    comment_report_view.find(report_id).first::<Self>(conn)
-  }
-
-  /// returns the current unresolved comment report count for the supplied community ids
-  ///
-  /// * `community_ids` - a Vec<i32> of community_ids to get a count for
-  pub fn get_report_count(conn: &PgConnection, community_ids: &[i32]) -> Result<i64, Error> {
-    use super::comment_report::comment_report_view::dsl::*;
-    comment_report_view
-      .filter(resolved.eq(false).and(community_id.eq_any(community_ids)))
-      .select(count(id))
-      .first::<i64>(conn)
-  }
-}
-
-impl<'a> CommentReportQueryBuilder<'a> {
-  pub fn create(conn: &'a PgConnection) -> Self {
-    use super::comment_report::comment_report_view::dsl::*;
-
-    let query = comment_report_view.into_boxed();
-
-    CommentReportQueryBuilder {
-      conn,
-      query,
-      for_community_ids: None,
-      page: None,
-      limit: None,
-      resolved: Some(false),
-    }
-  }
-
-  pub fn community_ids<T: MaybeOptional<Vec<i32>>>(mut self, community_ids: T) -> Self {
-    self.for_community_ids = community_ids.get_optional();
-    self
-  }
-
-  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
-    self.page = page.get_optional();
-    self
-  }
-
-  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
-    self.limit = limit.get_optional();
-    self
-  }
-
-  pub fn resolved<T: MaybeOptional<bool>>(mut self, resolved: T) -> Self {
-    self.resolved = resolved.get_optional();
-    self
-  }
-
-  pub fn list(self) -> Result<Vec<CommentReportView>, Error> {
-    use super::comment_report::comment_report_view::dsl::*;
-
-    let mut query = self.query;
-
-    if let Some(comm_ids) = self.for_community_ids {
-      query = query.filter(community_id.eq_any(comm_ids));
-    }
-
-    if let Some(resolved_flag) = self.resolved {
-      query = query.filter(resolved.eq(resolved_flag));
-    }
-
-    let (limit, offset) = limit_and_offset(self.page, self.limit);
-
-    query
-      .order_by(published.asc())
-      .limit(limit)
-      .offset(offset)
-      .load::<CommentReportView>(self.conn)
-  }
-}
diff --git a/lemmy_db/src/comment_view.rs b/lemmy_db/src/comment_view.rs
deleted file mode 100644 (file)
index 4b6dc19..0000000
+++ /dev/null
@@ -1,719 +0,0 @@
-// TODO, remove the cross join here, just join to user directly
-use crate::{fuzzy_search, limit_and_offset, ListingType, MaybeOptional, SortType};
-use diesel::{dsl::*, pg::Pg, result::Error, *};
-use serde::{Deserialize, Serialize};
-
-// The faked schema since diesel doesn't do views
-table! {
-  comment_view (id) {
-    id -> Int4,
-    creator_id -> Int4,
-    post_id -> Int4,
-    post_name -> Varchar,
-    parent_id -> Nullable<Int4>,
-    content -> Text,
-    removed -> Bool,
-    read -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    ap_id -> Text,
-    local -> Bool,
-    community_id -> Int4,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-    banned -> Bool,
-    banned_from_community -> Bool,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_published -> Timestamp,
-    creator_avatar -> Nullable<Text>,
-    score -> BigInt,
-    upvotes -> BigInt,
-    downvotes -> BigInt,
-    hot_rank -> Int4,
-    hot_rank_active -> Int4,
-    user_id -> Nullable<Int4>,
-    my_vote -> Nullable<Int4>,
-    subscribed -> Nullable<Bool>,
-    saved -> Nullable<Bool>,
-  }
-}
-
-table! {
-  comment_fast_view (id) {
-    id -> Int4,
-    creator_id -> Int4,
-    post_id -> Int4,
-    post_name -> Varchar,
-    parent_id -> Nullable<Int4>,
-    content -> Text,
-    removed -> Bool,
-    read -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    ap_id -> Text,
-    local -> Bool,
-    community_id -> Int4,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-    banned -> Bool,
-    banned_from_community -> Bool,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_published -> Timestamp,
-    creator_avatar -> Nullable<Text>,
-    score -> BigInt,
-    upvotes -> BigInt,
-    downvotes -> BigInt,
-    hot_rank -> Int4,
-    hot_rank_active -> Int4,
-    user_id -> Nullable<Int4>,
-    my_vote -> Nullable<Int4>,
-    subscribed -> Nullable<Bool>,
-    saved -> Nullable<Bool>,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "comment_fast_view"]
-pub struct CommentView {
-  pub id: i32,
-  pub creator_id: i32,
-  pub post_id: i32,
-  pub post_name: String,
-  pub parent_id: Option<i32>,
-  pub content: String,
-  pub removed: bool,
-  pub read: bool,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub deleted: bool,
-  pub ap_id: String,
-  pub local: bool,
-  pub community_id: i32,
-  pub community_actor_id: String,
-  pub community_local: bool,
-  pub community_name: String,
-  pub community_icon: Option<String>,
-  pub banned: bool,
-  pub banned_from_community: bool,
-  pub creator_actor_id: String,
-  pub creator_local: bool,
-  pub creator_name: String,
-  pub creator_preferred_username: Option<String>,
-  pub creator_published: chrono::NaiveDateTime,
-  pub creator_avatar: Option<String>,
-  pub score: i64,
-  pub upvotes: i64,
-  pub downvotes: i64,
-  pub hot_rank: i32,
-  pub hot_rank_active: i32,
-  pub user_id: Option<i32>,
-  pub my_vote: Option<i32>,
-  pub subscribed: Option<bool>,
-  pub saved: Option<bool>,
-}
-
-pub struct CommentQueryBuilder<'a> {
-  conn: &'a PgConnection,
-  query: super::comment_view::comment_fast_view::BoxedQuery<'a, Pg>,
-  listing_type: ListingType,
-  sort: &'a SortType,
-  for_community_id: Option<i32>,
-  for_community_name: Option<String>,
-  for_post_id: Option<i32>,
-  for_creator_id: Option<i32>,
-  search_term: Option<String>,
-  my_user_id: Option<i32>,
-  saved_only: bool,
-  page: Option<i64>,
-  limit: Option<i64>,
-}
-
-impl<'a> CommentQueryBuilder<'a> {
-  pub fn create(conn: &'a PgConnection) -> Self {
-    use super::comment_view::comment_fast_view::dsl::*;
-
-    let query = comment_fast_view.into_boxed();
-
-    CommentQueryBuilder {
-      conn,
-      query,
-      listing_type: ListingType::All,
-      sort: &SortType::New,
-      for_community_id: None,
-      for_community_name: None,
-      for_post_id: None,
-      for_creator_id: None,
-      search_term: None,
-      my_user_id: None,
-      saved_only: false,
-      page: None,
-      limit: None,
-    }
-  }
-
-  pub fn listing_type(mut self, listing_type: ListingType) -> Self {
-    self.listing_type = listing_type;
-    self
-  }
-
-  pub fn sort(mut self, sort: &'a SortType) -> Self {
-    self.sort = sort;
-    self
-  }
-
-  pub fn for_post_id<T: MaybeOptional<i32>>(mut self, for_post_id: T) -> Self {
-    self.for_post_id = for_post_id.get_optional();
-    self
-  }
-
-  pub fn for_creator_id<T: MaybeOptional<i32>>(mut self, for_creator_id: T) -> Self {
-    self.for_creator_id = for_creator_id.get_optional();
-    self
-  }
-
-  pub fn for_community_id<T: MaybeOptional<i32>>(mut self, for_community_id: T) -> Self {
-    self.for_community_id = for_community_id.get_optional();
-    self
-  }
-
-  pub fn for_community_name<T: MaybeOptional<String>>(mut self, for_community_name: T) -> Self {
-    self.for_community_name = for_community_name.get_optional();
-    self
-  }
-
-  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
-    self.search_term = search_term.get_optional();
-    self
-  }
-
-  pub fn my_user_id<T: MaybeOptional<i32>>(mut self, my_user_id: T) -> Self {
-    self.my_user_id = my_user_id.get_optional();
-    self
-  }
-
-  pub fn saved_only(mut self, saved_only: bool) -> Self {
-    self.saved_only = saved_only;
-    self
-  }
-
-  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
-    self.page = page.get_optional();
-    self
-  }
-
-  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
-    self.limit = limit.get_optional();
-    self
-  }
-
-  pub fn list(self) -> Result<Vec<CommentView>, Error> {
-    use super::comment_view::comment_fast_view::dsl::*;
-
-    let mut query = self.query;
-
-    // The view lets you pass a null user_id, if you're not logged in
-    if let Some(my_user_id) = self.my_user_id {
-      query = query.filter(user_id.eq(my_user_id));
-    } else {
-      query = query.filter(user_id.is_null());
-    }
-
-    if let Some(for_creator_id) = self.for_creator_id {
-      query = query.filter(creator_id.eq(for_creator_id));
-    };
-
-    if let Some(for_community_id) = self.for_community_id {
-      query = query.filter(community_id.eq(for_community_id));
-    }
-
-    if let Some(for_community_name) = self.for_community_name {
-      query = query
-        .filter(community_name.eq(for_community_name))
-        .filter(local.eq(true));
-    }
-
-    if let Some(for_post_id) = self.for_post_id {
-      query = query.filter(post_id.eq(for_post_id));
-    };
-
-    if let Some(search_term) = self.search_term {
-      query = query.filter(content.ilike(fuzzy_search(&search_term)));
-    };
-
-    query = match self.listing_type {
-      ListingType::Subscribed => query.filter(subscribed.eq(true)),
-      ListingType::Local => query.filter(community_local.eq(true)),
-      _ => query,
-    };
-
-    if self.saved_only {
-      query = query.filter(saved.eq(true));
-    }
-
-    query = match self.sort {
-      SortType::Hot => query
-        .order_by(hot_rank.desc())
-        .then_order_by(published.desc()),
-      SortType::Active => query
-        .order_by(hot_rank_active.desc())
-        .then_order_by(published.desc()),
-      SortType::New => query.order_by(published.desc()),
-      SortType::TopAll => query.order_by(score.desc()),
-      SortType::TopYear => query
-        .filter(published.gt(now - 1.years()))
-        .order_by(score.desc()),
-      SortType::TopMonth => query
-        .filter(published.gt(now - 1.months()))
-        .order_by(score.desc()),
-      SortType::TopWeek => query
-        .filter(published.gt(now - 1.weeks()))
-        .order_by(score.desc()),
-      SortType::TopDay => query
-        .filter(published.gt(now - 1.days()))
-        .order_by(score.desc()),
-      // _ => query.order_by(published.desc()),
-    };
-
-    let (limit, offset) = limit_and_offset(self.page, self.limit);
-
-    // Note: deleted and removed comments are done on the front side
-    query
-      .limit(limit)
-      .offset(offset)
-      .load::<CommentView>(self.conn)
-  }
-}
-
-impl CommentView {
-  pub fn read(
-    conn: &PgConnection,
-    from_comment_id: i32,
-    my_user_id: Option<i32>,
-  ) -> Result<Self, Error> {
-    use super::comment_view::comment_fast_view::dsl::*;
-    let mut query = comment_fast_view.into_boxed();
-
-    // The view lets you pass a null user_id, if you're not logged in
-    if let Some(my_user_id) = my_user_id {
-      query = query.filter(user_id.eq(my_user_id));
-    } else {
-      query = query.filter(user_id.is_null());
-    }
-
-    query = query
-      .filter(id.eq(from_comment_id))
-      .order_by(published.desc());
-
-    query.first::<Self>(conn)
-  }
-}
-
-// The faked schema since diesel doesn't do views
-table! {
-  reply_fast_view (id) {
-    id -> Int4,
-    creator_id -> Int4,
-    post_id -> Int4,
-    post_name -> Varchar,
-    parent_id -> Nullable<Int4>,
-    content -> Text,
-    removed -> Bool,
-    read -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    ap_id -> Text,
-    local -> Bool,
-    community_id -> Int4,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Varchar>,
-    banned -> Bool,
-    banned_from_community -> Bool,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_avatar -> Nullable<Text>,
-    creator_published -> Timestamp,
-    score -> BigInt,
-    upvotes -> BigInt,
-    downvotes -> BigInt,
-    hot_rank -> Int4,
-    hot_rank_active -> Int4,
-    user_id -> Nullable<Int4>,
-    my_vote -> Nullable<Int4>,
-    subscribed -> Nullable<Bool>,
-    saved -> Nullable<Bool>,
-    recipient_id -> Int4,
-  }
-}
-
-#[derive(
-  Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
-)]
-#[table_name = "reply_fast_view"]
-pub struct ReplyView {
-  pub id: i32,
-  pub creator_id: i32,
-  pub post_id: i32,
-  pub post_name: String,
-  pub parent_id: Option<i32>,
-  pub content: String,
-  pub removed: bool,
-  pub read: bool,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub deleted: bool,
-  pub ap_id: String,
-  pub local: bool,
-  pub community_id: i32,
-  pub community_actor_id: String,
-  pub community_local: bool,
-  pub community_name: String,
-  pub community_icon: Option<String>,
-  pub banned: bool,
-  pub banned_from_community: bool,
-  pub creator_actor_id: String,
-  pub creator_local: bool,
-  pub creator_name: String,
-  pub creator_preferred_username: Option<String>,
-  pub creator_avatar: Option<String>,
-  pub creator_published: chrono::NaiveDateTime,
-  pub score: i64,
-  pub upvotes: i64,
-  pub downvotes: i64,
-  pub hot_rank: i32,
-  pub hot_rank_active: i32,
-  pub user_id: Option<i32>,
-  pub my_vote: Option<i32>,
-  pub subscribed: Option<bool>,
-  pub saved: Option<bool>,
-  pub recipient_id: i32,
-}
-
-pub struct ReplyQueryBuilder<'a> {
-  conn: &'a PgConnection,
-  query: super::comment_view::reply_fast_view::BoxedQuery<'a, Pg>,
-  for_user_id: i32,
-  sort: &'a SortType,
-  unread_only: bool,
-  page: Option<i64>,
-  limit: Option<i64>,
-}
-
-impl<'a> ReplyQueryBuilder<'a> {
-  pub fn create(conn: &'a PgConnection, for_user_id: i32) -> Self {
-    use super::comment_view::reply_fast_view::dsl::*;
-
-    let query = reply_fast_view.into_boxed();
-
-    ReplyQueryBuilder {
-      conn,
-      query,
-      for_user_id,
-      sort: &SortType::New,
-      unread_only: false,
-      page: None,
-      limit: None,
-    }
-  }
-
-  pub fn sort(mut self, sort: &'a SortType) -> Self {
-    self.sort = sort;
-    self
-  }
-
-  pub fn unread_only(mut self, unread_only: bool) -> Self {
-    self.unread_only = unread_only;
-    self
-  }
-
-  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
-    self.page = page.get_optional();
-    self
-  }
-
-  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
-    self.limit = limit.get_optional();
-    self
-  }
-
-  pub fn list(self) -> Result<Vec<ReplyView>, Error> {
-    use super::comment_view::reply_fast_view::dsl::*;
-
-    let mut query = self.query;
-
-    query = query
-      .filter(user_id.eq(self.for_user_id))
-      .filter(recipient_id.eq(self.for_user_id))
-      .filter(deleted.eq(false))
-      .filter(removed.eq(false));
-
-    if self.unread_only {
-      query = query.filter(read.eq(false));
-    }
-
-    query = match self.sort {
-      // SortType::Hot => query.order_by(hot_rank.desc()), // TODO why is this commented
-      SortType::New => query.order_by(published.desc()),
-      SortType::TopAll => query.order_by(score.desc()),
-      SortType::TopYear => query
-        .filter(published.gt(now - 1.years()))
-        .order_by(score.desc()),
-      SortType::TopMonth => query
-        .filter(published.gt(now - 1.months()))
-        .order_by(score.desc()),
-      SortType::TopWeek => query
-        .filter(published.gt(now - 1.weeks()))
-        .order_by(score.desc()),
-      SortType::TopDay => query
-        .filter(published.gt(now - 1.days()))
-        .order_by(score.desc()),
-      _ => query.order_by(published.desc()),
-    };
-
-    let (limit, offset) = limit_and_offset(self.page, self.limit);
-    query
-      .limit(limit)
-      .offset(offset)
-      .load::<ReplyView>(self.conn)
-  }
-}
-
-#[cfg(test)]
-mod tests {
-  use crate::{
-    comment::*,
-    comment_view::*,
-    community::*,
-    post::*,
-    tests::establish_unpooled_connection,
-    user::*,
-    Crud,
-    Likeable,
-    *,
-  };
-
-  #[test]
-  fn test_crud() {
-    let conn = establish_unpooled_connection();
-
-    let new_user = UserForm {
-      name: "timmy".into(),
-      preferred_username: None,
-      password_encrypted: "nope".into(),
-      email: None,
-      matrix_user_id: None,
-      avatar: None,
-      banner: None,
-      admin: false,
-      banned: Some(false),
-      published: None,
-      updated: None,
-      show_nsfw: false,
-      theme: "browser".into(),
-      default_sort_type: SortType::Hot as i16,
-      default_listing_type: ListingType::Subscribed as i16,
-      lang: "browser".into(),
-      show_avatars: true,
-      send_notifications_to_email: false,
-      actor_id: None,
-      bio: None,
-      local: true,
-      private_key: None,
-      public_key: None,
-      last_refreshed_at: None,
-    };
-
-    let inserted_user = User_::create(&conn, &new_user).unwrap();
-
-    let new_community = CommunityForm {
-      name: "test community 5".to_string(),
-      title: "nada".to_owned(),
-      description: None,
-      category_id: 1,
-      creator_id: inserted_user.id,
-      removed: None,
-      deleted: None,
-      updated: None,
-      nsfw: false,
-      actor_id: None,
-      local: true,
-      private_key: None,
-      public_key: None,
-      last_refreshed_at: None,
-      published: None,
-      icon: None,
-      banner: None,
-    };
-
-    let inserted_community = Community::create(&conn, &new_community).unwrap();
-
-    let new_post = PostForm {
-      name: "A test post 2".into(),
-      creator_id: inserted_user.id,
-      url: None,
-      body: None,
-      community_id: inserted_community.id,
-      removed: None,
-      deleted: None,
-      locked: None,
-      stickied: None,
-      updated: None,
-      nsfw: false,
-      embed_title: None,
-      embed_description: None,
-      embed_html: None,
-      thumbnail_url: None,
-      ap_id: None,
-      local: true,
-      published: None,
-    };
-
-    let inserted_post = Post::create(&conn, &new_post).unwrap();
-
-    let comment_form = CommentForm {
-      content: "A test comment 32".into(),
-      creator_id: inserted_user.id,
-      post_id: inserted_post.id,
-      parent_id: None,
-      removed: None,
-      deleted: None,
-      read: None,
-      published: None,
-      updated: None,
-      ap_id: None,
-      local: true,
-    };
-
-    let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
-
-    let comment_like_form = CommentLikeForm {
-      comment_id: inserted_comment.id,
-      post_id: inserted_post.id,
-      user_id: inserted_user.id,
-      score: 1,
-    };
-
-    let _inserted_comment_like = CommentLike::like(&conn, &comment_like_form).unwrap();
-
-    let expected_comment_view_no_user = CommentView {
-      id: inserted_comment.id,
-      content: "A test comment 32".into(),
-      creator_id: inserted_user.id,
-      post_id: inserted_post.id,
-      post_name: inserted_post.name.to_owned(),
-      community_id: inserted_community.id,
-      community_name: inserted_community.name.to_owned(),
-      community_icon: None,
-      parent_id: None,
-      removed: false,
-      deleted: false,
-      read: false,
-      banned: false,
-      banned_from_community: false,
-      published: inserted_comment.published,
-      updated: None,
-      creator_name: inserted_user.name.to_owned(),
-      creator_preferred_username: None,
-      creator_published: inserted_user.published,
-      creator_avatar: None,
-      score: 1,
-      downvotes: 0,
-      hot_rank: 0,
-      hot_rank_active: 0,
-      upvotes: 1,
-      user_id: None,
-      my_vote: None,
-      subscribed: None,
-      saved: None,
-      ap_id: inserted_comment.ap_id.to_owned(),
-      local: true,
-      community_actor_id: inserted_community.actor_id.to_owned(),
-      community_local: true,
-      creator_actor_id: inserted_user.actor_id.to_owned(),
-      creator_local: true,
-    };
-
-    let expected_comment_view_with_user = CommentView {
-      id: inserted_comment.id,
-      content: "A test comment 32".into(),
-      creator_id: inserted_user.id,
-      post_id: inserted_post.id,
-      post_name: inserted_post.name.to_owned(),
-      community_id: inserted_community.id,
-      community_name: inserted_community.name.to_owned(),
-      community_icon: None,
-      parent_id: None,
-      removed: false,
-      deleted: false,
-      read: false,
-      banned: false,
-      banned_from_community: false,
-      published: inserted_comment.published,
-      updated: None,
-      creator_name: inserted_user.name.to_owned(),
-      creator_preferred_username: None,
-      creator_published: inserted_user.published,
-      creator_avatar: None,
-      score: 1,
-      downvotes: 0,
-      hot_rank: 0,
-      hot_rank_active: 0,
-      upvotes: 1,
-      user_id: Some(inserted_user.id),
-      my_vote: Some(1),
-      subscribed: Some(false),
-      saved: Some(false),
-      ap_id: inserted_comment.ap_id.to_owned(),
-      local: true,
-      community_actor_id: inserted_community.actor_id.to_owned(),
-      community_local: true,
-      creator_actor_id: inserted_user.actor_id.to_owned(),
-      creator_local: true,
-    };
-
-    let mut read_comment_views_no_user = CommentQueryBuilder::create(&conn)
-      .for_post_id(inserted_post.id)
-      .list()
-      .unwrap();
-    read_comment_views_no_user[0].hot_rank = 0;
-    read_comment_views_no_user[0].hot_rank_active = 0;
-
-    let mut read_comment_views_with_user = CommentQueryBuilder::create(&conn)
-      .for_post_id(inserted_post.id)
-      .my_user_id(inserted_user.id)
-      .list()
-      .unwrap();
-    read_comment_views_with_user[0].hot_rank = 0;
-    read_comment_views_with_user[0].hot_rank_active = 0;
-
-    let like_removed = CommentLike::remove(&conn, inserted_user.id, inserted_comment.id).unwrap();
-    let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
-    Post::delete(&conn, inserted_post.id).unwrap();
-    Community::delete(&conn, inserted_community.id).unwrap();
-    User_::delete(&conn, inserted_user.id).unwrap();
-
-    assert_eq!(expected_comment_view_no_user, read_comment_views_no_user[0]);
-    assert_eq!(
-      expected_comment_view_with_user,
-      read_comment_views_with_user[0]
-    );
-    assert_eq!(1, num_deleted);
-    assert_eq!(1, like_removed);
-  }
-}
diff --git a/lemmy_db/src/community_view.rs b/lemmy_db/src/community_view.rs
deleted file mode 100644 (file)
index a635550..0000000
+++ /dev/null
@@ -1,398 +0,0 @@
-use super::community_view::community_fast_view::BoxedQuery;
-use crate::{fuzzy_search, limit_and_offset, MaybeOptional, SortType};
-use diesel::{pg::Pg, result::Error, *};
-use serde::{Deserialize, Serialize};
-
-table! {
-  community_view (id) {
-    id -> Int4,
-    name -> Varchar,
-    title -> Varchar,
-    icon -> Nullable<Text>,
-    banner -> Nullable<Text>,
-    description -> Nullable<Text>,
-    category_id -> Int4,
-    creator_id -> Int4,
-    removed -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    nsfw -> Bool,
-    actor_id -> Text,
-    local -> Bool,
-    last_refreshed_at -> Timestamp,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_avatar -> Nullable<Text>,
-    category_name -> Varchar,
-    number_of_subscribers -> BigInt,
-    number_of_posts -> BigInt,
-    number_of_comments -> BigInt,
-    hot_rank -> Int4,
-    user_id -> Nullable<Int4>,
-    subscribed -> Nullable<Bool>,
-  }
-}
-
-table! {
-  community_fast_view (id) {
-    id -> Int4,
-    name -> Varchar,
-    title -> Varchar,
-    icon -> Nullable<Text>,
-    banner -> Nullable<Text>,
-    description -> Nullable<Text>,
-    category_id -> Int4,
-    creator_id -> Int4,
-    removed -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    nsfw -> Bool,
-    actor_id -> Text,
-    local -> Bool,
-    last_refreshed_at -> Timestamp,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_avatar -> Nullable<Text>,
-    category_name -> Varchar,
-    number_of_subscribers -> BigInt,
-    number_of_posts -> BigInt,
-    number_of_comments -> BigInt,
-    hot_rank -> Int4,
-    user_id -> Nullable<Int4>,
-    subscribed -> Nullable<Bool>,
-  }
-}
-
-table! {
-  community_moderator_view (id) {
-    id -> Int4,
-    community_id -> Int4,
-    user_id -> Int4,
-    published -> Timestamp,
-    user_actor_id -> Text,
-    user_local -> Bool,
-    user_name -> Varchar,
-    user_preferred_username -> Nullable<Varchar>,
-    avatar -> Nullable<Text>,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-  }
-}
-
-table! {
-  community_follower_view (id) {
-    id -> Int4,
-    community_id -> Int4,
-    user_id -> Int4,
-    published -> Timestamp,
-    user_actor_id -> Text,
-    user_local -> Bool,
-    user_name -> Varchar,
-    user_preferred_username -> Nullable<Varchar>,
-    avatar -> Nullable<Text>,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-  }
-}
-
-table! {
-  community_user_ban_view (id) {
-    id -> Int4,
-    community_id -> Int4,
-    user_id -> Int4,
-    published -> Timestamp,
-    user_actor_id -> Text,
-    user_local -> Bool,
-    user_name -> Varchar,
-    user_preferred_username -> Nullable<Varchar>,
-    avatar -> Nullable<Text>,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "community_fast_view"]
-pub struct CommunityView {
-  pub id: i32,
-  pub name: String,
-  pub title: String,
-  pub icon: Option<String>,
-  pub banner: Option<String>,
-  pub description: Option<String>,
-  pub category_id: i32,
-  pub creator_id: i32,
-  pub removed: bool,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub deleted: bool,
-  pub nsfw: bool,
-  pub actor_id: String,
-  pub local: bool,
-  pub last_refreshed_at: chrono::NaiveDateTime,
-  pub creator_actor_id: String,
-  pub creator_local: bool,
-  pub creator_name: String,
-  pub creator_preferred_username: Option<String>,
-  pub creator_avatar: Option<String>,
-  pub category_name: String,
-  pub number_of_subscribers: i64,
-  pub number_of_posts: i64,
-  pub number_of_comments: i64,
-  pub hot_rank: i32,
-  pub user_id: Option<i32>,
-  pub subscribed: Option<bool>,
-}
-
-pub struct CommunityQueryBuilder<'a> {
-  conn: &'a PgConnection,
-  query: BoxedQuery<'a, Pg>,
-  sort: &'a SortType,
-  from_user_id: Option<i32>,
-  show_nsfw: bool,
-  search_term: Option<String>,
-  page: Option<i64>,
-  limit: Option<i64>,
-}
-
-impl<'a> CommunityQueryBuilder<'a> {
-  pub fn create(conn: &'a PgConnection) -> Self {
-    use super::community_view::community_fast_view::dsl::*;
-
-    let query = community_fast_view.into_boxed();
-
-    CommunityQueryBuilder {
-      conn,
-      query,
-      sort: &SortType::Hot,
-      from_user_id: None,
-      show_nsfw: true,
-      search_term: None,
-      page: None,
-      limit: None,
-    }
-  }
-
-  pub fn sort(mut self, sort: &'a SortType) -> Self {
-    self.sort = sort;
-    self
-  }
-
-  pub fn for_user<T: MaybeOptional<i32>>(mut self, from_user_id: T) -> Self {
-    self.from_user_id = from_user_id.get_optional();
-    self
-  }
-
-  pub fn show_nsfw(mut self, show_nsfw: bool) -> Self {
-    self.show_nsfw = show_nsfw;
-    self
-  }
-
-  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
-    self.search_term = search_term.get_optional();
-    self
-  }
-
-  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
-    self.page = page.get_optional();
-    self
-  }
-
-  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
-    self.limit = limit.get_optional();
-    self
-  }
-
-  pub fn list(self) -> Result<Vec<CommunityView>, Error> {
-    use super::community_view::community_fast_view::dsl::*;
-
-    let mut query = self.query;
-
-    if let Some(search_term) = self.search_term {
-      let searcher = fuzzy_search(&search_term);
-      query = query
-        .filter(name.ilike(searcher.to_owned()))
-        .or_filter(title.ilike(searcher.to_owned()))
-        .or_filter(description.ilike(searcher));
-    };
-
-    // The view lets you pass a null user_id, if you're not logged in
-    match self.sort {
-      SortType::New => query = query.order_by(published.desc()).filter(user_id.is_null()),
-      SortType::TopAll => match self.from_user_id {
-        Some(from_user_id) => {
-          query = query
-            .filter(user_id.eq(from_user_id))
-            .order_by((subscribed.asc(), number_of_subscribers.desc()))
-        }
-        None => {
-          query = query
-            .order_by(number_of_subscribers.desc())
-            .filter(user_id.is_null())
-        }
-      },
-      // Covers all other sorts, including hot
-      _ => {
-        query = query
-          .order_by(hot_rank.desc())
-          .then_order_by(number_of_subscribers.desc())
-          .filter(user_id.is_null())
-      }
-    };
-
-    if !self.show_nsfw {
-      query = query.filter(nsfw.eq(false));
-    };
-
-    let (limit, offset) = limit_and_offset(self.page, self.limit);
-    query
-      .limit(limit)
-      .offset(offset)
-      .filter(removed.eq(false))
-      .filter(deleted.eq(false))
-      .load::<CommunityView>(self.conn)
-  }
-}
-
-impl CommunityView {
-  pub fn read(
-    conn: &PgConnection,
-    from_community_id: i32,
-    from_user_id: Option<i32>,
-  ) -> Result<Self, Error> {
-    use super::community_view::community_fast_view::dsl::*;
-
-    let mut query = community_fast_view.into_boxed();
-
-    query = query.filter(id.eq(from_community_id));
-
-    // The view lets you pass a null user_id, if you're not logged in
-    if let Some(from_user_id) = from_user_id {
-      query = query.filter(user_id.eq(from_user_id));
-    } else {
-      query = query.filter(user_id.is_null());
-    };
-
-    query.first::<Self>(conn)
-  }
-}
-
-#[derive(
-  Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
-)]
-#[table_name = "community_moderator_view"]
-pub struct CommunityModeratorView {
-  pub id: i32,
-  pub community_id: i32,
-  pub user_id: i32,
-  pub published: chrono::NaiveDateTime,
-  pub user_actor_id: String,
-  pub user_local: bool,
-  pub user_name: String,
-  pub user_preferred_username: Option<String>,
-  pub avatar: Option<String>,
-  pub community_actor_id: String,
-  pub community_local: bool,
-  pub community_name: String,
-  pub community_icon: Option<String>,
-}
-
-impl CommunityModeratorView {
-  pub fn for_community(conn: &PgConnection, for_community_id: i32) -> Result<Vec<Self>, Error> {
-    use super::community_view::community_moderator_view::dsl::*;
-    community_moderator_view
-      .filter(community_id.eq(for_community_id))
-      .order_by(published)
-      .load::<Self>(conn)
-  }
-
-  pub fn for_user(conn: &PgConnection, for_user_id: i32) -> Result<Vec<Self>, Error> {
-    use super::community_view::community_moderator_view::dsl::*;
-    community_moderator_view
-      .filter(user_id.eq(for_user_id))
-      .order_by(published)
-      .load::<Self>(conn)
-  }
-}
-
-#[derive(
-  Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
-)]
-#[table_name = "community_follower_view"]
-pub struct CommunityFollowerView {
-  pub id: i32,
-  pub community_id: i32,
-  pub user_id: i32,
-  pub published: chrono::NaiveDateTime,
-  pub user_actor_id: String,
-  pub user_local: bool,
-  pub user_name: String,
-  pub user_preferred_username: Option<String>,
-  pub avatar: Option<String>,
-  pub community_actor_id: String,
-  pub community_local: bool,
-  pub community_name: String,
-  pub community_icon: Option<String>,
-}
-
-impl CommunityFollowerView {
-  pub fn for_community(conn: &PgConnection, from_community_id: i32) -> Result<Vec<Self>, Error> {
-    use super::community_view::community_follower_view::dsl::*;
-    community_follower_view
-      .filter(community_id.eq(from_community_id))
-      .load::<Self>(conn)
-  }
-
-  pub fn for_user(conn: &PgConnection, from_user_id: i32) -> Result<Vec<Self>, Error> {
-    use super::community_view::community_follower_view::dsl::*;
-    community_follower_view
-      .filter(user_id.eq(from_user_id))
-      .load::<Self>(conn)
-  }
-}
-
-#[derive(
-  Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
-)]
-#[table_name = "community_user_ban_view"]
-pub struct CommunityUserBanView {
-  pub id: i32,
-  pub community_id: i32,
-  pub user_id: i32,
-  pub published: chrono::NaiveDateTime,
-  pub user_actor_id: String,
-  pub user_local: bool,
-  pub user_name: String,
-  pub user_preferred_username: Option<String>,
-  pub avatar: Option<String>,
-  pub community_actor_id: String,
-  pub community_local: bool,
-  pub community_name: String,
-  pub community_icon: Option<String>,
-}
-
-impl CommunityUserBanView {
-  pub fn get(
-    conn: &PgConnection,
-    from_user_id: i32,
-    from_community_id: i32,
-  ) -> Result<Self, Error> {
-    use super::community_view::community_user_ban_view::dsl::*;
-    community_user_ban_view
-      .filter(user_id.eq(from_user_id))
-      .filter(community_id.eq(from_community_id))
-      .first::<Self>(conn)
-  }
-}
index 57ca24eb3c36d07d3866f5b376456bedbc148810..4df69ca432fb9da21e4d20a34a2c7afe4a2ebae4 100644 (file)
@@ -15,28 +15,10 @@ use regex::Regex;
 use serde::{Deserialize, Serialize};
 use std::{env, env::VarError};
 
-pub mod activity;
-pub mod category;
-pub mod comment;
-pub mod comment_report;
-pub mod comment_view;
-pub mod community;
-pub mod community_view;
-pub mod moderator;
-pub mod moderator_views;
-pub mod password_reset_request;
-pub mod post;
-pub mod post_report;
-pub mod post_view;
-pub mod private_message;
-pub mod private_message_view;
+pub mod aggregates;
 pub mod schema;
-pub mod site;
-pub mod site_view;
-pub mod user;
-pub mod user_mention;
-pub mod user_mention_view;
-pub mod user_view;
+pub mod source;
+pub mod views;
 
 pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
 
@@ -128,6 +110,15 @@ pub trait Reportable<T> {
     Self: Sized;
 }
 
+pub trait ApubObject<T> {
+  fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error>
+  where
+    Self: Sized;
+  fn upsert(conn: &PgConnection, user_form: &T) -> Result<Self, Error>
+  where
+    Self: Sized;
+}
+
 pub trait MaybeOptional<T> {
   fn get_optional(self) -> Option<T>;
 }
@@ -144,6 +135,11 @@ impl<T> MaybeOptional<T> for Option<T> {
   }
 }
 
+pub(crate) trait ToSafe {
+  type SafeColumns;
+  fn safe_columns_tuple() -> Self::SafeColumns;
+}
+
 pub fn get_database_url_from_env() -> Result<String, VarError> {
   env::var("LEMMY_DATABASE_URL")
 }
@@ -217,6 +213,14 @@ lazy_static! {
     Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
 }
 
+pub(crate) mod functions {
+  use diesel::sql_types::*;
+
+  sql_function! {
+    fn hot_rank(score: BigInt, time: Timestamp) -> Integer;
+  }
+}
+
 #[cfg(test)]
 mod tests {
   use super::fuzzy_search;
diff --git a/lemmy_db/src/moderator_views.rs b/lemmy_db/src/moderator_views.rs
deleted file mode 100644 (file)
index efa949a..0000000
+++ /dev/null
@@ -1,513 +0,0 @@
-use crate::limit_and_offset;
-use diesel::{result::Error, *};
-use serde::Serialize;
-
-table! {
-  mod_remove_post_view (id) {
-    id -> Int4,
-    mod_user_id -> Int4,
-    post_id -> Int4,
-    reason -> Nullable<Text>,
-    removed -> Nullable<Bool>,
-    when_ -> Timestamp,
-    mod_user_name -> Varchar,
-    post_name -> Varchar,
-    community_id -> Int4,
-    community_name -> Varchar,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "mod_remove_post_view"]
-pub struct ModRemovePostView {
-  pub id: i32,
-  pub mod_user_id: i32,
-  pub post_id: i32,
-  pub reason: Option<String>,
-  pub removed: Option<bool>,
-  pub when_: chrono::NaiveDateTime,
-  pub mod_user_name: String,
-  pub post_name: String,
-  pub community_id: i32,
-  pub community_name: String,
-}
-
-impl ModRemovePostView {
-  pub fn list(
-    conn: &PgConnection,
-    from_community_id: Option<i32>,
-    from_mod_user_id: Option<i32>,
-    page: Option<i64>,
-    limit: Option<i64>,
-  ) -> Result<Vec<Self>, Error> {
-    use super::moderator_views::mod_remove_post_view::dsl::*;
-    let mut query = mod_remove_post_view.into_boxed();
-
-    let (limit, offset) = limit_and_offset(page, limit);
-
-    if let Some(from_community_id) = from_community_id {
-      query = query.filter(community_id.eq(from_community_id));
-    };
-
-    if let Some(from_mod_user_id) = from_mod_user_id {
-      query = query.filter(mod_user_id.eq(from_mod_user_id));
-    };
-
-    query
-      .limit(limit)
-      .offset(offset)
-      .order_by(when_.desc())
-      .load::<Self>(conn)
-  }
-}
-
-table! {
-  mod_lock_post_view (id) {
-    id -> Int4,
-    mod_user_id -> Int4,
-    post_id -> Int4,
-    locked -> Nullable<Bool>,
-    when_ -> Timestamp,
-    mod_user_name -> Varchar,
-    post_name -> Varchar,
-    community_id -> Int4,
-    community_name -> Varchar,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "mod_lock_post_view"]
-pub struct ModLockPostView {
-  pub id: i32,
-  pub mod_user_id: i32,
-  pub post_id: i32,
-  pub locked: Option<bool>,
-  pub when_: chrono::NaiveDateTime,
-  pub mod_user_name: String,
-  pub post_name: String,
-  pub community_id: i32,
-  pub community_name: String,
-}
-
-impl ModLockPostView {
-  pub fn list(
-    conn: &PgConnection,
-    from_community_id: Option<i32>,
-    from_mod_user_id: Option<i32>,
-    page: Option<i64>,
-    limit: Option<i64>,
-  ) -> Result<Vec<Self>, Error> {
-    use super::moderator_views::mod_lock_post_view::dsl::*;
-    let mut query = mod_lock_post_view.into_boxed();
-
-    let (limit, offset) = limit_and_offset(page, limit);
-
-    if let Some(from_community_id) = from_community_id {
-      query = query.filter(community_id.eq(from_community_id));
-    };
-
-    if let Some(from_mod_user_id) = from_mod_user_id {
-      query = query.filter(mod_user_id.eq(from_mod_user_id));
-    };
-
-    query
-      .limit(limit)
-      .offset(offset)
-      .order_by(when_.desc())
-      .load::<Self>(conn)
-  }
-}
-
-table! {
-  mod_sticky_post_view (id) {
-    id -> Int4,
-    mod_user_id -> Int4,
-    post_id -> Int4,
-    stickied -> Nullable<Bool>,
-    when_ -> Timestamp,
-    mod_user_name -> Varchar,
-    post_name -> Varchar,
-    community_id -> Int4,
-    community_name -> Varchar,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "mod_sticky_post_view"]
-pub struct ModStickyPostView {
-  pub id: i32,
-  pub mod_user_id: i32,
-  pub post_id: i32,
-  pub stickied: Option<bool>,
-  pub when_: chrono::NaiveDateTime,
-  pub mod_user_name: String,
-  pub post_name: String,
-  pub community_id: i32,
-  pub community_name: String,
-}
-
-impl ModStickyPostView {
-  pub fn list(
-    conn: &PgConnection,
-    from_community_id: Option<i32>,
-    from_mod_user_id: Option<i32>,
-    page: Option<i64>,
-    limit: Option<i64>,
-  ) -> Result<Vec<Self>, Error> {
-    use super::moderator_views::mod_sticky_post_view::dsl::*;
-    let mut query = mod_sticky_post_view.into_boxed();
-
-    let (limit, offset) = limit_and_offset(page, limit);
-
-    if let Some(from_community_id) = from_community_id {
-      query = query.filter(community_id.eq(from_community_id));
-    };
-
-    if let Some(from_mod_user_id) = from_mod_user_id {
-      query = query.filter(mod_user_id.eq(from_mod_user_id));
-    };
-
-    query
-      .limit(limit)
-      .offset(offset)
-      .order_by(when_.desc())
-      .load::<Self>(conn)
-  }
-}
-
-table! {
-  mod_remove_comment_view (id) {
-    id -> Int4,
-    mod_user_id -> Int4,
-    comment_id -> Int4,
-    reason -> Nullable<Text>,
-    removed -> Nullable<Bool>,
-    when_ -> Timestamp,
-    mod_user_name -> Varchar,
-    comment_user_id -> Int4,
-    comment_user_name -> Varchar,
-    comment_content -> Text,
-    post_id -> Int4,
-    post_name -> Varchar,
-    community_id -> Int4,
-    community_name -> Varchar,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "mod_remove_comment_view"]
-pub struct ModRemoveCommentView {
-  pub id: i32,
-  pub mod_user_id: i32,
-  pub comment_id: i32,
-  pub reason: Option<String>,
-  pub removed: Option<bool>,
-  pub when_: chrono::NaiveDateTime,
-  pub mod_user_name: String,
-  pub comment_user_id: i32,
-  pub comment_user_name: String,
-  pub comment_content: String,
-  pub post_id: i32,
-  pub post_name: String,
-  pub community_id: i32,
-  pub community_name: String,
-}
-
-impl ModRemoveCommentView {
-  pub fn list(
-    conn: &PgConnection,
-    from_community_id: Option<i32>,
-    from_mod_user_id: Option<i32>,
-    page: Option<i64>,
-    limit: Option<i64>,
-  ) -> Result<Vec<Self>, Error> {
-    use super::moderator_views::mod_remove_comment_view::dsl::*;
-    let mut query = mod_remove_comment_view.into_boxed();
-
-    let (limit, offset) = limit_and_offset(page, limit);
-
-    if let Some(from_community_id) = from_community_id {
-      query = query.filter(community_id.eq(from_community_id));
-    };
-
-    if let Some(from_mod_user_id) = from_mod_user_id {
-      query = query.filter(mod_user_id.eq(from_mod_user_id));
-    };
-
-    query
-      .limit(limit)
-      .offset(offset)
-      .order_by(when_.desc())
-      .load::<Self>(conn)
-  }
-}
-
-table! {
-  mod_remove_community_view (id) {
-    id -> Int4,
-    mod_user_id -> Int4,
-    community_id -> Int4,
-    reason -> Nullable<Text>,
-    removed -> Nullable<Bool>,
-    expires -> Nullable<Timestamp>,
-    when_ -> Timestamp,
-    mod_user_name -> Varchar,
-    community_name -> Varchar,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "mod_remove_community_view"]
-pub struct ModRemoveCommunityView {
-  pub id: i32,
-  pub mod_user_id: i32,
-  pub community_id: i32,
-  pub reason: Option<String>,
-  pub removed: Option<bool>,
-  pub expires: Option<chrono::NaiveDateTime>,
-  pub when_: chrono::NaiveDateTime,
-  pub mod_user_name: String,
-  pub community_name: String,
-}
-
-impl ModRemoveCommunityView {
-  pub fn list(
-    conn: &PgConnection,
-    from_mod_user_id: Option<i32>,
-    page: Option<i64>,
-    limit: Option<i64>,
-  ) -> Result<Vec<Self>, Error> {
-    use super::moderator_views::mod_remove_community_view::dsl::*;
-    let mut query = mod_remove_community_view.into_boxed();
-
-    let (limit, offset) = limit_and_offset(page, limit);
-
-    if let Some(from_mod_user_id) = from_mod_user_id {
-      query = query.filter(mod_user_id.eq(from_mod_user_id));
-    };
-
-    query
-      .limit(limit)
-      .offset(offset)
-      .order_by(when_.desc())
-      .load::<Self>(conn)
-  }
-}
-
-table! {
-  mod_ban_from_community_view (id) {
-    id -> Int4,
-    mod_user_id -> Int4,
-    other_user_id -> Int4,
-    community_id -> Int4,
-    reason -> Nullable<Text>,
-    banned -> Nullable<Bool>,
-    expires -> Nullable<Timestamp>,
-    when_ -> Timestamp,
-    mod_user_name -> Varchar,
-    other_user_name -> Varchar,
-    community_name -> Varchar,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "mod_ban_from_community_view"]
-pub struct ModBanFromCommunityView {
-  pub id: i32,
-  pub mod_user_id: i32,
-  pub other_user_id: i32,
-  pub community_id: i32,
-  pub reason: Option<String>,
-  pub banned: Option<bool>,
-  pub expires: Option<chrono::NaiveDateTime>,
-  pub when_: chrono::NaiveDateTime,
-  pub mod_user_name: String,
-  pub other_user_name: String,
-  pub community_name: String,
-}
-
-impl ModBanFromCommunityView {
-  pub fn list(
-    conn: &PgConnection,
-    from_community_id: Option<i32>,
-    from_mod_user_id: Option<i32>,
-    page: Option<i64>,
-    limit: Option<i64>,
-  ) -> Result<Vec<Self>, Error> {
-    use super::moderator_views::mod_ban_from_community_view::dsl::*;
-    let mut query = mod_ban_from_community_view.into_boxed();
-
-    let (limit, offset) = limit_and_offset(page, limit);
-
-    if let Some(from_community_id) = from_community_id {
-      query = query.filter(community_id.eq(from_community_id));
-    };
-
-    if let Some(from_mod_user_id) = from_mod_user_id {
-      query = query.filter(mod_user_id.eq(from_mod_user_id));
-    };
-
-    query
-      .limit(limit)
-      .offset(offset)
-      .order_by(when_.desc())
-      .load::<Self>(conn)
-  }
-}
-
-table! {
-  mod_ban_view (id) {
-    id -> Int4,
-    mod_user_id -> Int4,
-    other_user_id -> Int4,
-    reason -> Nullable<Text>,
-    banned -> Nullable<Bool>,
-    expires -> Nullable<Timestamp>,
-    when_ -> Timestamp,
-    mod_user_name -> Varchar,
-    other_user_name -> Varchar,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "mod_ban_view"]
-pub struct ModBanView {
-  pub id: i32,
-  pub mod_user_id: i32,
-  pub other_user_id: i32,
-  pub reason: Option<String>,
-  pub banned: Option<bool>,
-  pub expires: Option<chrono::NaiveDateTime>,
-  pub when_: chrono::NaiveDateTime,
-  pub mod_user_name: String,
-  pub other_user_name: String,
-}
-
-impl ModBanView {
-  pub fn list(
-    conn: &PgConnection,
-    from_mod_user_id: Option<i32>,
-    page: Option<i64>,
-    limit: Option<i64>,
-  ) -> Result<Vec<Self>, Error> {
-    use super::moderator_views::mod_ban_view::dsl::*;
-    let mut query = mod_ban_view.into_boxed();
-
-    let (limit, offset) = limit_and_offset(page, limit);
-
-    if let Some(from_mod_user_id) = from_mod_user_id {
-      query = query.filter(mod_user_id.eq(from_mod_user_id));
-    };
-
-    query
-      .limit(limit)
-      .offset(offset)
-      .order_by(when_.desc())
-      .load::<Self>(conn)
-  }
-}
-
-table! {
-  mod_add_community_view (id) {
-    id -> Int4,
-    mod_user_id -> Int4,
-    other_user_id -> Int4,
-    community_id -> Int4,
-    removed -> Nullable<Bool>,
-    when_ -> Timestamp,
-    mod_user_name -> Varchar,
-    other_user_name -> Varchar,
-    community_name -> Varchar,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "mod_add_community_view"]
-pub struct ModAddCommunityView {
-  pub id: i32,
-  pub mod_user_id: i32,
-  pub other_user_id: i32,
-  pub community_id: i32,
-  pub removed: Option<bool>,
-  pub when_: chrono::NaiveDateTime,
-  pub mod_user_name: String,
-  pub other_user_name: String,
-  pub community_name: String,
-}
-
-impl ModAddCommunityView {
-  pub fn list(
-    conn: &PgConnection,
-    from_community_id: Option<i32>,
-    from_mod_user_id: Option<i32>,
-    page: Option<i64>,
-    limit: Option<i64>,
-  ) -> Result<Vec<Self>, Error> {
-    use super::moderator_views::mod_add_community_view::dsl::*;
-    let mut query = mod_add_community_view.into_boxed();
-
-    let (limit, offset) = limit_and_offset(page, limit);
-
-    if let Some(from_community_id) = from_community_id {
-      query = query.filter(community_id.eq(from_community_id));
-    };
-
-    if let Some(from_mod_user_id) = from_mod_user_id {
-      query = query.filter(mod_user_id.eq(from_mod_user_id));
-    };
-
-    query
-      .limit(limit)
-      .offset(offset)
-      .order_by(when_.desc())
-      .load::<Self>(conn)
-  }
-}
-
-table! {
-  mod_add_view (id) {
-    id -> Int4,
-    mod_user_id -> Int4,
-    other_user_id -> Int4,
-    removed -> Nullable<Bool>,
-    when_ -> Timestamp,
-    mod_user_name -> Varchar,
-    other_user_name -> Varchar,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "mod_add_view"]
-pub struct ModAddView {
-  pub id: i32,
-  pub mod_user_id: i32,
-  pub other_user_id: i32,
-  pub removed: Option<bool>,
-  pub when_: chrono::NaiveDateTime,
-  pub mod_user_name: String,
-  pub other_user_name: String,
-}
-
-impl ModAddView {
-  pub fn list(
-    conn: &PgConnection,
-    from_mod_user_id: Option<i32>,
-    page: Option<i64>,
-    limit: Option<i64>,
-  ) -> Result<Vec<Self>, Error> {
-    use super::moderator_views::mod_add_view::dsl::*;
-    let mut query = mod_add_view.into_boxed();
-
-    let (limit, offset) = limit_and_offset(page, limit);
-
-    if let Some(from_mod_user_id) = from_mod_user_id {
-      query = query.filter(mod_user_id.eq(from_mod_user_id));
-    };
-
-    query
-      .limit(limit)
-      .offset(offset)
-      .order_by(when_.desc())
-      .load::<Self>(conn)
-  }
-}
diff --git a/lemmy_db/src/post_report.rs b/lemmy_db/src/post_report.rs
deleted file mode 100644 (file)
index 5f8aa5e..0000000
+++ /dev/null
@@ -1,245 +0,0 @@
-use diesel::{dsl::*, pg::Pg, result::Error, *};
-use serde::{Deserialize, Serialize};
-
-use crate::{
-  limit_and_offset,
-  naive_now,
-  post::Post,
-  schema::post_report,
-  MaybeOptional,
-  Reportable,
-};
-
-table! {
-    post_report_view (id) {
-        id -> Int4,
-        creator_id -> Int4,
-        post_id -> Int4,
-        original_post_name -> Varchar,
-        original_post_url -> Nullable<Text>,
-        original_post_body -> Nullable<Text>,
-        reason -> Text,
-        resolved -> Bool,
-        resolver_id -> Nullable<Int4>,
-        published -> Timestamp,
-        updated -> Nullable<Timestamp>,
-        current_post_name -> Varchar,
-        current_post_url -> Nullable<Text>,
-        current_post_body -> Nullable<Text>,
-        community_id -> Int4,
-        creator_actor_id -> Text,
-        creator_name -> Varchar,
-        creator_preferred_username -> Nullable<Varchar>,
-        creator_avatar -> Nullable<Text>,
-        creator_local -> Bool,
-        post_creator_id -> Int4,
-        post_creator_actor_id -> Text,
-        post_creator_name -> Varchar,
-        post_creator_preferred_username -> Nullable<Varchar>,
-        post_creator_avatar -> Nullable<Text>,
-        post_creator_local -> Bool,
-        resolver_actor_id -> Nullable<Text>,
-        resolver_name -> Nullable<Varchar>,
-        resolver_preferred_username -> Nullable<Varchar>,
-        resolver_avatar -> Nullable<Text>,
-        resolver_local -> Nullable<Bool>,
-    }
-}
-
-#[derive(Identifiable, Queryable, Associations, PartialEq, Serialize, Deserialize, Debug)]
-#[belongs_to(Post)]
-#[table_name = "post_report"]
-pub struct PostReport {
-  pub id: i32,
-  pub creator_id: i32,
-  pub post_id: i32,
-  pub original_post_name: String,
-  pub original_post_url: Option<String>,
-  pub original_post_body: Option<String>,
-  pub reason: String,
-  pub resolved: bool,
-  pub resolver_id: Option<i32>,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-}
-
-#[derive(Insertable, AsChangeset, Clone)]
-#[table_name = "post_report"]
-pub struct PostReportForm {
-  pub creator_id: i32,
-  pub post_id: i32,
-  pub original_post_name: String,
-  pub original_post_url: Option<String>,
-  pub original_post_body: Option<String>,
-  pub reason: String,
-}
-
-impl Reportable<PostReportForm> for PostReport {
-  /// creates a post report and returns it
-  ///
-  /// * `conn` - the postgres connection
-  /// * `post_report_form` - the filled CommentReportForm to insert
-  fn report(conn: &PgConnection, post_report_form: &PostReportForm) -> Result<Self, Error> {
-    use crate::schema::post_report::dsl::*;
-    insert_into(post_report)
-      .values(post_report_form)
-      .get_result::<Self>(conn)
-  }
-
-  /// resolve a post report
-  ///
-  /// * `conn` - the postgres connection
-  /// * `report_id` - the id of the report to resolve
-  /// * `by_resolver_id` - the id of the user resolving the report
-  fn resolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
-    use crate::schema::post_report::dsl::*;
-    update(post_report.find(report_id))
-      .set((
-        resolved.eq(true),
-        resolver_id.eq(by_resolver_id),
-        updated.eq(naive_now()),
-      ))
-      .execute(conn)
-  }
-
-  /// resolve a post report
-  ///
-  /// * `conn` - the postgres connection
-  /// * `report_id` - the id of the report to unresolve
-  /// * `by_resolver_id` - the id of the user unresolving the report
-  fn unresolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
-    use crate::schema::post_report::dsl::*;
-    update(post_report.find(report_id))
-      .set((
-        resolved.eq(false),
-        resolver_id.eq(by_resolver_id),
-        updated.eq(naive_now()),
-      ))
-      .execute(conn)
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone)]
-#[table_name = "post_report_view"]
-pub struct PostReportView {
-  pub id: i32,
-  pub creator_id: i32,
-  pub post_id: i32,
-  pub original_post_name: String,
-  pub original_post_url: Option<String>,
-  pub original_post_body: Option<String>,
-  pub reason: String,
-  pub resolved: bool,
-  pub resolver_id: Option<i32>,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub current_post_name: String,
-  pub current_post_url: Option<String>,
-  pub current_post_body: Option<String>,
-  pub community_id: i32,
-  pub creator_actor_id: String,
-  pub creator_name: String,
-  pub creator_preferred_username: Option<String>,
-  pub creator_avatar: Option<String>,
-  pub creator_local: bool,
-  pub post_creator_id: i32,
-  pub post_creator_actor_id: String,
-  pub post_creator_name: String,
-  pub post_creator_preferred_username: Option<String>,
-  pub post_creator_avatar: Option<String>,
-  pub post_creator_local: bool,
-  pub resolver_actor_id: Option<String>,
-  pub resolver_name: Option<String>,
-  pub resolver_preferred_username: Option<String>,
-  pub resolver_avatar: Option<String>,
-  pub resolver_local: Option<bool>,
-}
-
-impl PostReportView {
-  /// returns the PostReportView for the provided report_id
-  ///
-  /// * `report_id` - the report id to obtain
-  pub fn read(conn: &PgConnection, report_id: i32) -> Result<Self, Error> {
-    use super::post_report::post_report_view::dsl::*;
-    post_report_view.find(report_id).first::<Self>(conn)
-  }
-
-  /// returns the current unresolved post report count for the supplied community ids
-  ///
-  /// * `community_ids` - a Vec<i32> of community_ids to get a count for
-  pub fn get_report_count(conn: &PgConnection, community_ids: &[i32]) -> Result<i64, Error> {
-    use super::post_report::post_report_view::dsl::*;
-    post_report_view
-      .filter(resolved.eq(false).and(community_id.eq_any(community_ids)))
-      .select(count(id))
-      .first::<i64>(conn)
-  }
-}
-
-pub struct PostReportQueryBuilder<'a> {
-  conn: &'a PgConnection,
-  query: post_report_view::BoxedQuery<'a, Pg>,
-  for_community_ids: Option<Vec<i32>>,
-  page: Option<i64>,
-  limit: Option<i64>,
-  resolved: Option<bool>,
-}
-
-impl<'a> PostReportQueryBuilder<'a> {
-  pub fn create(conn: &'a PgConnection) -> Self {
-    use super::post_report::post_report_view::dsl::*;
-
-    let query = post_report_view.into_boxed();
-
-    PostReportQueryBuilder {
-      conn,
-      query,
-      for_community_ids: None,
-      page: None,
-      limit: None,
-      resolved: Some(false),
-    }
-  }
-
-  pub fn community_ids<T: MaybeOptional<Vec<i32>>>(mut self, community_ids: T) -> Self {
-    self.for_community_ids = community_ids.get_optional();
-    self
-  }
-
-  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
-    self.page = page.get_optional();
-    self
-  }
-
-  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
-    self.limit = limit.get_optional();
-    self
-  }
-
-  pub fn resolved<T: MaybeOptional<bool>>(mut self, resolved: T) -> Self {
-    self.resolved = resolved.get_optional();
-    self
-  }
-
-  pub fn list(self) -> Result<Vec<PostReportView>, Error> {
-    use super::post_report::post_report_view::dsl::*;
-
-    let mut query = self.query;
-
-    if let Some(comm_ids) = self.for_community_ids {
-      query = query.filter(community_id.eq_any(comm_ids));
-    }
-
-    if let Some(resolved_flag) = self.resolved {
-      query = query.filter(resolved.eq(resolved_flag));
-    }
-
-    let (limit, offset) = limit_and_offset(self.page, self.limit);
-
-    query
-      .order_by(published.asc())
-      .limit(limit)
-      .offset(offset)
-      .load::<PostReportView>(self.conn)
-  }
-}
diff --git a/lemmy_db/src/post_view.rs b/lemmy_db/src/post_view.rs
deleted file mode 100644 (file)
index ea03c3a..0000000
+++ /dev/null
@@ -1,641 +0,0 @@
-use super::post_view::post_fast_view::BoxedQuery;
-use crate::{fuzzy_search, limit_and_offset, ListingType, MaybeOptional, SortType};
-use diesel::{dsl::*, pg::Pg, result::Error, *};
-use serde::Serialize;
-
-// The faked schema since diesel doesn't do views
-table! {
-  post_view (id) {
-    id -> Int4,
-    name -> Varchar,
-    url -> Nullable<Text>,
-    body -> Nullable<Text>,
-    creator_id -> Int4,
-    community_id -> Int4,
-    removed -> Bool,
-    locked -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    nsfw -> Bool,
-    stickied -> Bool,
-    embed_title -> Nullable<Text>,
-    embed_description -> Nullable<Text>,
-    embed_html -> Nullable<Text>,
-    thumbnail_url -> Nullable<Text>,
-    ap_id -> Text,
-    local -> Bool,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_published -> Timestamp,
-    creator_avatar -> Nullable<Text>,
-    banned -> Bool,
-    banned_from_community -> Bool,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-    community_removed -> Bool,
-    community_deleted -> Bool,
-    community_nsfw -> Bool,
-    number_of_comments -> BigInt,
-    score -> BigInt,
-    upvotes -> BigInt,
-    downvotes -> BigInt,
-    hot_rank -> Int4,
-    hot_rank_active -> Int4,
-    newest_activity_time -> Timestamp,
-    user_id -> Nullable<Int4>,
-    my_vote -> Nullable<Int4>,
-    subscribed -> Nullable<Bool>,
-    read -> Nullable<Bool>,
-    saved -> Nullable<Bool>,
-  }
-}
-
-table! {
-  post_fast_view (id) {
-    id -> Int4,
-    name -> Varchar,
-    url -> Nullable<Text>,
-    body -> Nullable<Text>,
-    creator_id -> Int4,
-    community_id -> Int4,
-    removed -> Bool,
-    locked -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    nsfw -> Bool,
-    stickied -> Bool,
-    embed_title -> Nullable<Text>,
-    embed_description -> Nullable<Text>,
-    embed_html -> Nullable<Text>,
-    thumbnail_url -> Nullable<Text>,
-    ap_id -> Text,
-    local -> Bool,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_published -> Timestamp,
-    creator_avatar -> Nullable<Text>,
-    banned -> Bool,
-    banned_from_community -> Bool,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-    community_removed -> Bool,
-    community_deleted -> Bool,
-    community_nsfw -> Bool,
-    number_of_comments -> BigInt,
-    score -> BigInt,
-    upvotes -> BigInt,
-    downvotes -> BigInt,
-    hot_rank -> Int4,
-    hot_rank_active -> Int4,
-    newest_activity_time -> Timestamp,
-    user_id -> Nullable<Int4>,
-    my_vote -> Nullable<Int4>,
-    subscribed -> Nullable<Bool>,
-    read -> Nullable<Bool>,
-    saved -> Nullable<Bool>,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "post_fast_view"]
-pub struct PostView {
-  pub id: i32,
-  pub name: String,
-  pub url: Option<String>,
-  pub body: Option<String>,
-  pub creator_id: i32,
-  pub community_id: i32,
-  pub removed: bool,
-  pub locked: bool,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub deleted: bool,
-  pub nsfw: bool,
-  pub stickied: bool,
-  pub embed_title: Option<String>,
-  pub embed_description: Option<String>,
-  pub embed_html: Option<String>,
-  pub thumbnail_url: Option<String>,
-  pub ap_id: String,
-  pub local: bool,
-  pub creator_actor_id: String,
-  pub creator_local: bool,
-  pub creator_name: String,
-  pub creator_preferred_username: Option<String>,
-  pub creator_published: chrono::NaiveDateTime,
-  pub creator_avatar: Option<String>,
-  pub banned: bool,
-  pub banned_from_community: bool,
-  pub community_actor_id: String,
-  pub community_local: bool,
-  pub community_name: String,
-  pub community_icon: Option<String>,
-  pub community_removed: bool,
-  pub community_deleted: bool,
-  pub community_nsfw: bool,
-  pub number_of_comments: i64,
-  pub score: i64,
-  pub upvotes: i64,
-  pub downvotes: i64,
-  pub hot_rank: i32,
-  pub hot_rank_active: i32,
-  pub newest_activity_time: chrono::NaiveDateTime,
-  pub user_id: Option<i32>,
-  pub my_vote: Option<i32>,
-  pub subscribed: Option<bool>,
-  pub read: Option<bool>,
-  pub saved: Option<bool>,
-}
-
-pub struct PostQueryBuilder<'a> {
-  conn: &'a PgConnection,
-  query: BoxedQuery<'a, Pg>,
-  listing_type: &'a ListingType,
-  sort: &'a SortType,
-  my_user_id: Option<i32>,
-  for_creator_id: Option<i32>,
-  for_community_id: Option<i32>,
-  for_community_name: Option<String>,
-  search_term: Option<String>,
-  url_search: Option<String>,
-  show_nsfw: bool,
-  saved_only: bool,
-  unread_only: bool,
-  page: Option<i64>,
-  limit: Option<i64>,
-}
-
-impl<'a> PostQueryBuilder<'a> {
-  pub fn create(conn: &'a PgConnection) -> Self {
-    use super::post_view::post_fast_view::dsl::*;
-
-    let query = post_fast_view.into_boxed();
-
-    PostQueryBuilder {
-      conn,
-      query,
-      listing_type: &ListingType::All,
-      sort: &SortType::Hot,
-      my_user_id: None,
-      for_creator_id: None,
-      for_community_id: None,
-      for_community_name: None,
-      search_term: None,
-      url_search: None,
-      show_nsfw: true,
-      saved_only: false,
-      unread_only: false,
-      page: None,
-      limit: None,
-    }
-  }
-
-  pub fn listing_type(mut self, listing_type: &'a ListingType) -> Self {
-    self.listing_type = listing_type;
-    self
-  }
-
-  pub fn sort(mut self, sort: &'a SortType) -> Self {
-    self.sort = sort;
-    self
-  }
-
-  pub fn for_community_id<T: MaybeOptional<i32>>(mut self, for_community_id: T) -> Self {
-    self.for_community_id = for_community_id.get_optional();
-    self
-  }
-
-  pub fn for_community_name<T: MaybeOptional<String>>(mut self, for_community_name: T) -> Self {
-    self.for_community_name = for_community_name.get_optional();
-    self
-  }
-
-  pub fn for_creator_id<T: MaybeOptional<i32>>(mut self, for_creator_id: T) -> Self {
-    self.for_creator_id = for_creator_id.get_optional();
-    self
-  }
-
-  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
-    self.search_term = search_term.get_optional();
-    self
-  }
-
-  pub fn url_search<T: MaybeOptional<String>>(mut self, url_search: T) -> Self {
-    self.url_search = url_search.get_optional();
-    self
-  }
-
-  pub fn my_user_id<T: MaybeOptional<i32>>(mut self, my_user_id: T) -> Self {
-    self.my_user_id = my_user_id.get_optional();
-    self
-  }
-
-  pub fn show_nsfw(mut self, show_nsfw: bool) -> Self {
-    self.show_nsfw = show_nsfw;
-    self
-  }
-
-  pub fn saved_only(mut self, saved_only: bool) -> Self {
-    self.saved_only = saved_only;
-    self
-  }
-
-  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
-    self.page = page.get_optional();
-    self
-  }
-
-  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
-    self.limit = limit.get_optional();
-    self
-  }
-
-  pub fn list(self) -> Result<Vec<PostView>, Error> {
-    use super::post_view::post_fast_view::dsl::*;
-
-    let mut query = self.query;
-
-    query = match self.listing_type {
-      ListingType::Subscribed => query.filter(subscribed.eq(true)),
-      ListingType::Local => query.filter(community_local.eq(true)),
-      _ => query,
-    };
-
-    if let Some(for_community_id) = self.for_community_id {
-      query = query
-        .filter(community_id.eq(for_community_id))
-        .then_order_by(stickied.desc());
-    }
-
-    if let Some(for_community_name) = self.for_community_name {
-      query = query
-        .filter(community_name.eq(for_community_name))
-        .filter(community_local.eq(true))
-        .then_order_by(stickied.desc());
-    }
-
-    if let Some(url_search) = self.url_search {
-      query = query.filter(url.eq(url_search));
-    }
-
-    if let Some(search_term) = self.search_term {
-      let searcher = fuzzy_search(&search_term);
-      query = query.filter(name.ilike(searcher.to_owned()).or(body.ilike(searcher)));
-    }
-
-    query = match self.sort {
-      SortType::Active => query
-        .then_order_by(hot_rank_active.desc())
-        .then_order_by(published.desc()),
-      SortType::Hot => query
-        .then_order_by(hot_rank.desc())
-        .then_order_by(published.desc()),
-      SortType::New => query.then_order_by(published.desc()),
-      SortType::TopAll => query.then_order_by(score.desc()),
-      SortType::TopYear => query
-        .filter(published.gt(now - 1.years()))
-        .then_order_by(score.desc()),
-      SortType::TopMonth => query
-        .filter(published.gt(now - 1.months()))
-        .then_order_by(score.desc()),
-      SortType::TopWeek => query
-        .filter(published.gt(now - 1.weeks()))
-        .then_order_by(score.desc()),
-      SortType::TopDay => query
-        .filter(published.gt(now - 1.days()))
-        .then_order_by(score.desc()),
-    };
-
-    // The view lets you pass a null user_id, if you're not logged in
-    query = if let Some(my_user_id) = self.my_user_id {
-      query.filter(user_id.eq(my_user_id))
-    } else {
-      query.filter(user_id.is_null())
-    };
-
-    // If its for a specific user, show the removed / deleted
-    if let Some(for_creator_id) = self.for_creator_id {
-      query = query.filter(creator_id.eq(for_creator_id));
-    } else {
-      query = query
-        .filter(removed.eq(false))
-        .filter(deleted.eq(false))
-        .filter(community_removed.eq(false))
-        .filter(community_deleted.eq(false));
-    }
-
-    if !self.show_nsfw {
-      query = query
-        .filter(nsfw.eq(false))
-        .filter(community_nsfw.eq(false));
-    };
-
-    // TODO these are wrong, bc they'll only show saved for your logged in user, not theirs
-    if self.saved_only {
-      query = query.filter(saved.eq(true));
-    };
-
-    if self.unread_only {
-      query = query.filter(read.eq(false));
-    };
-
-    let (limit, offset) = limit_and_offset(self.page, self.limit);
-    query = query
-      .limit(limit)
-      .offset(offset)
-      .filter(removed.eq(false))
-      .filter(deleted.eq(false))
-      .filter(community_removed.eq(false))
-      .filter(community_deleted.eq(false));
-
-    query.load::<PostView>(self.conn)
-  }
-}
-
-impl PostView {
-  pub fn read(
-    conn: &PgConnection,
-    from_post_id: i32,
-    my_user_id: Option<i32>,
-  ) -> Result<Self, Error> {
-    use super::post_view::post_fast_view::dsl::*;
-    use diesel::prelude::*;
-
-    let mut query = post_fast_view.into_boxed();
-
-    query = query.filter(id.eq(from_post_id));
-
-    if let Some(my_user_id) = my_user_id {
-      query = query.filter(user_id.eq(my_user_id));
-    } else {
-      query = query.filter(user_id.is_null());
-    };
-
-    query.first::<Self>(conn)
-  }
-}
-
-#[cfg(test)]
-mod tests {
-  use crate::{
-    community::*,
-    post::*,
-    post_view::*,
-    tests::establish_unpooled_connection,
-    user::*,
-    Crud,
-    Likeable,
-    *,
-  };
-
-  #[test]
-  fn test_crud() {
-    let conn = establish_unpooled_connection();
-
-    let user_name = "tegan".to_string();
-    let community_name = "test_community_3".to_string();
-    let post_name = "test post 3".to_string();
-
-    let new_user = UserForm {
-      name: user_name.to_owned(),
-      preferred_username: None,
-      password_encrypted: "nope".into(),
-      email: None,
-      matrix_user_id: None,
-      avatar: None,
-      banner: None,
-      published: None,
-      updated: None,
-      admin: false,
-      banned: Some(false),
-      show_nsfw: false,
-      theme: "browser".into(),
-      default_sort_type: SortType::Hot as i16,
-      default_listing_type: ListingType::Subscribed as i16,
-      lang: "browser".into(),
-      show_avatars: true,
-      send_notifications_to_email: false,
-      actor_id: None,
-      bio: None,
-      local: true,
-      private_key: None,
-      public_key: None,
-      last_refreshed_at: None,
-    };
-
-    let inserted_user = User_::create(&conn, &new_user).unwrap();
-
-    let new_community = CommunityForm {
-      name: community_name.to_owned(),
-      title: "nada".to_owned(),
-      description: None,
-      creator_id: inserted_user.id,
-      category_id: 1,
-      removed: None,
-      deleted: None,
-      updated: None,
-      nsfw: false,
-      actor_id: None,
-      local: true,
-      private_key: None,
-      public_key: None,
-      last_refreshed_at: None,
-      published: None,
-      icon: None,
-      banner: None,
-    };
-
-    let inserted_community = Community::create(&conn, &new_community).unwrap();
-
-    let new_post = PostForm {
-      name: post_name.to_owned(),
-      url: None,
-      body: None,
-      creator_id: inserted_user.id,
-      community_id: inserted_community.id,
-      removed: None,
-      deleted: None,
-      locked: None,
-      stickied: None,
-      updated: None,
-      nsfw: false,
-      embed_title: None,
-      embed_description: None,
-      embed_html: None,
-      thumbnail_url: None,
-      ap_id: None,
-      local: true,
-      published: None,
-    };
-
-    let inserted_post = Post::create(&conn, &new_post).unwrap();
-
-    let post_like_form = PostLikeForm {
-      post_id: inserted_post.id,
-      user_id: inserted_user.id,
-      score: 1,
-    };
-
-    let inserted_post_like = PostLike::like(&conn, &post_like_form).unwrap();
-
-    let expected_post_like = PostLike {
-      id: inserted_post_like.id,
-      post_id: inserted_post.id,
-      user_id: inserted_user.id,
-      published: inserted_post_like.published,
-      score: 1,
-    };
-
-    let read_post_listings_with_user = PostQueryBuilder::create(&conn)
-      .listing_type(&ListingType::Community)
-      .sort(&SortType::New)
-      .for_community_id(inserted_community.id)
-      .my_user_id(inserted_user.id)
-      .list()
-      .unwrap();
-
-    let read_post_listings_no_user = PostQueryBuilder::create(&conn)
-      .listing_type(&ListingType::Community)
-      .sort(&SortType::New)
-      .for_community_id(inserted_community.id)
-      .list()
-      .unwrap();
-
-    let read_post_listing_no_user = PostView::read(&conn, inserted_post.id, None).unwrap();
-    let read_post_listing_with_user =
-      PostView::read(&conn, inserted_post.id, Some(inserted_user.id)).unwrap();
-
-    // the non user version
-    let expected_post_listing_no_user = PostView {
-      user_id: None,
-      my_vote: None,
-      id: inserted_post.id,
-      name: post_name.to_owned(),
-      url: None,
-      body: None,
-      creator_id: inserted_user.id,
-      creator_name: user_name.to_owned(),
-      creator_preferred_username: None,
-      creator_published: inserted_user.published,
-      creator_avatar: None,
-      banned: false,
-      banned_from_community: false,
-      community_id: inserted_community.id,
-      removed: false,
-      deleted: false,
-      locked: false,
-      stickied: false,
-      community_name: community_name.to_owned(),
-      community_icon: None,
-      community_removed: false,
-      community_deleted: false,
-      community_nsfw: false,
-      number_of_comments: 0,
-      score: 1,
-      upvotes: 1,
-      downvotes: 0,
-      hot_rank: read_post_listing_no_user.hot_rank,
-      hot_rank_active: read_post_listing_no_user.hot_rank_active,
-      published: inserted_post.published,
-      newest_activity_time: inserted_post.published,
-      updated: None,
-      subscribed: None,
-      read: None,
-      saved: None,
-      nsfw: false,
-      embed_title: None,
-      embed_description: None,
-      embed_html: None,
-      thumbnail_url: None,
-      ap_id: inserted_post.ap_id.to_owned(),
-      local: true,
-      creator_actor_id: inserted_user.actor_id.to_owned(),
-      creator_local: true,
-      community_actor_id: inserted_community.actor_id.to_owned(),
-      community_local: true,
-    };
-
-    let expected_post_listing_with_user = PostView {
-      user_id: Some(inserted_user.id),
-      my_vote: Some(1),
-      id: inserted_post.id,
-      name: post_name,
-      url: None,
-      body: None,
-      removed: false,
-      deleted: false,
-      locked: false,
-      stickied: false,
-      creator_id: inserted_user.id,
-      creator_name: user_name,
-      creator_preferred_username: None,
-      creator_published: inserted_user.published,
-      creator_avatar: None,
-      banned: false,
-      banned_from_community: false,
-      community_id: inserted_community.id,
-      community_name,
-      community_icon: None,
-      community_removed: false,
-      community_deleted: false,
-      community_nsfw: false,
-      number_of_comments: 0,
-      score: 1,
-      upvotes: 1,
-      downvotes: 0,
-      hot_rank: read_post_listing_with_user.hot_rank,
-      hot_rank_active: read_post_listing_with_user.hot_rank_active,
-      published: inserted_post.published,
-      newest_activity_time: inserted_post.published,
-      updated: None,
-      subscribed: Some(false),
-      read: Some(false),
-      saved: Some(false),
-      nsfw: false,
-      embed_title: None,
-      embed_description: None,
-      embed_html: None,
-      thumbnail_url: None,
-      ap_id: inserted_post.ap_id.to_owned(),
-      local: true,
-      creator_actor_id: inserted_user.actor_id.to_owned(),
-      creator_local: true,
-      community_actor_id: inserted_community.actor_id.to_owned(),
-      community_local: true,
-    };
-
-    let like_removed = PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
-    let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
-    Community::delete(&conn, inserted_community.id).unwrap();
-    User_::delete(&conn, inserted_user.id).unwrap();
-
-    // The with user
-    assert_eq!(
-      expected_post_listing_with_user,
-      read_post_listings_with_user[0]
-    );
-    assert_eq!(expected_post_listing_with_user, read_post_listing_with_user);
-    assert_eq!(1, read_post_listings_with_user.len());
-
-    // Without the user
-    assert_eq!(expected_post_listing_no_user, read_post_listings_no_user[0]);
-    assert_eq!(expected_post_listing_no_user, read_post_listing_no_user);
-    assert_eq!(1, read_post_listings_no_user.len());
-
-    // assert_eq!(expected_post, inserted_post);
-    // assert_eq!(expected_post, updated_post);
-    assert_eq!(expected_post_like, inserted_post_like);
-    assert_eq!(1, like_removed);
-    assert_eq!(1, num_deleted);
-  }
-}
diff --git a/lemmy_db/src/private_message_view.rs b/lemmy_db/src/private_message_view.rs
deleted file mode 100644 (file)
index 68f7df4..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-use crate::{limit_and_offset, MaybeOptional};
-use diesel::{pg::Pg, result::Error, *};
-use serde::Serialize;
-
-// The faked schema since diesel doesn't do views
-table! {
-  private_message_view (id) {
-    id -> Int4,
-    creator_id -> Int4,
-    recipient_id -> Int4,
-    content -> Text,
-    deleted -> Bool,
-    read -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    ap_id -> Text,
-    local -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_avatar -> Nullable<Text>,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    recipient_name -> Varchar,
-    recipient_preferred_username -> Nullable<Varchar>,
-    recipient_avatar -> Nullable<Text>,
-    recipient_actor_id -> Text,
-    recipient_local -> Bool,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "private_message_view"]
-pub struct PrivateMessageView {
-  pub id: i32,
-  pub creator_id: i32,
-  pub recipient_id: i32,
-  pub content: String,
-  pub deleted: bool,
-  pub read: bool,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub ap_id: String,
-  pub local: bool,
-  pub creator_name: String,
-  pub creator_preferred_username: Option<String>,
-  pub creator_avatar: Option<String>,
-  pub creator_actor_id: String,
-  pub creator_local: bool,
-  pub recipient_name: String,
-  pub recipient_preferred_username: Option<String>,
-  pub recipient_avatar: Option<String>,
-  pub recipient_actor_id: String,
-  pub recipient_local: bool,
-}
-
-pub struct PrivateMessageQueryBuilder<'a> {
-  conn: &'a PgConnection,
-  query: super::private_message_view::private_message_view::BoxedQuery<'a, Pg>,
-  for_recipient_id: i32,
-  unread_only: bool,
-  page: Option<i64>,
-  limit: Option<i64>,
-}
-
-impl<'a> PrivateMessageQueryBuilder<'a> {
-  pub fn create(conn: &'a PgConnection, for_recipient_id: i32) -> Self {
-    use super::private_message_view::private_message_view::dsl::*;
-
-    let query = private_message_view.into_boxed();
-
-    PrivateMessageQueryBuilder {
-      conn,
-      query,
-      for_recipient_id,
-      unread_only: false,
-      page: None,
-      limit: None,
-    }
-  }
-
-  pub fn unread_only(mut self, unread_only: bool) -> Self {
-    self.unread_only = unread_only;
-    self
-  }
-
-  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
-    self.page = page.get_optional();
-    self
-  }
-
-  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
-    self.limit = limit.get_optional();
-    self
-  }
-
-  pub fn list(self) -> Result<Vec<PrivateMessageView>, Error> {
-    use super::private_message_view::private_message_view::dsl::*;
-
-    let mut query = self.query.filter(deleted.eq(false));
-
-    // If its unread, I only want the ones to me
-    if self.unread_only {
-      query = query
-        .filter(read.eq(false))
-        .filter(recipient_id.eq(self.for_recipient_id));
-    }
-    // Otherwise, I want the ALL view to show both sent and received
-    else {
-      query = query.filter(
-        recipient_id
-          .eq(self.for_recipient_id)
-          .or(creator_id.eq(self.for_recipient_id)),
-      )
-    }
-
-    let (limit, offset) = limit_and_offset(self.page, self.limit);
-
-    query
-      .limit(limit)
-      .offset(offset)
-      .order_by(published.desc())
-      .load::<PrivateMessageView>(self.conn)
-  }
-}
-
-impl PrivateMessageView {
-  pub fn read(conn: &PgConnection, from_private_message_id: i32) -> Result<Self, Error> {
-    use super::private_message_view::private_message_view::dsl::*;
-
-    let mut query = private_message_view.into_boxed();
-
-    query = query
-      .filter(id.eq(from_private_message_id))
-      .order_by(published.desc());
-
-    query.first::<Self>(conn)
-  }
-}
index 49bbc46fb1c74fa29af44032c3d603ca43efc73d..f0aca2db90acb8c032cbd55a3b4c46dda6bd2b0d 100644 (file)
@@ -35,38 +35,12 @@ table! {
 }
 
 table! {
-    comment_aggregates_fast (id) {
+    comment_aggregates (id) {
         id -> Int4,
-        creator_id -> Nullable<Int4>,
-        post_id -> Nullable<Int4>,
-        parent_id -> Nullable<Int4>,
-        content -> Nullable<Text>,
-        removed -> Nullable<Bool>,
-        read -> Nullable<Bool>,
-        published -> Nullable<Timestamp>,
-        updated -> Nullable<Timestamp>,
-        deleted -> Nullable<Bool>,
-        ap_id -> Nullable<Varchar>,
-        local -> Nullable<Bool>,
-        post_name -> Nullable<Varchar>,
-        community_id -> Nullable<Int4>,
-        community_actor_id -> Nullable<Varchar>,
-        community_local -> Nullable<Bool>,
-        community_name -> Nullable<Varchar>,
-        community_icon -> Nullable<Text>,
-        banned -> Nullable<Bool>,
-        banned_from_community -> Nullable<Bool>,
-        creator_actor_id -> Nullable<Varchar>,
-        creator_local -> Nullable<Bool>,
-        creator_name -> Nullable<Varchar>,
-        creator_preferred_username -> Nullable<Varchar>,
-        creator_published -> Nullable<Timestamp>,
-        creator_avatar -> Nullable<Text>,
-        score -> Nullable<Int8>,
-        upvotes -> Nullable<Int8>,
-        downvotes -> Nullable<Int8>,
-        hot_rank -> Nullable<Int4>,
-        hot_rank_active -> Nullable<Int4>,
+        comment_id -> Int4,
+        score -> Int8,
+        upvotes -> Int8,
+        downvotes -> Int8,
     }
 }
 
@@ -128,33 +102,12 @@ table! {
 }
 
 table! {
-    community_aggregates_fast (id) {
+    community_aggregates (id) {
         id -> Int4,
-        name -> Nullable<Varchar>,
-        title -> Nullable<Varchar>,
-        icon -> Nullable<Text>,
-        banner -> Nullable<Text>,
-        description -> Nullable<Text>,
-        category_id -> Nullable<Int4>,
-        creator_id -> Nullable<Int4>,
-        removed -> Nullable<Bool>,
-        published -> Nullable<Timestamp>,
-        updated -> Nullable<Timestamp>,
-        deleted -> Nullable<Bool>,
-        nsfw -> Nullable<Bool>,
-        actor_id -> Nullable<Varchar>,
-        local -> Nullable<Bool>,
-        last_refreshed_at -> Nullable<Timestamp>,
-        creator_actor_id -> Nullable<Varchar>,
-        creator_local -> Nullable<Bool>,
-        creator_name -> Nullable<Varchar>,
-        creator_preferred_username -> Nullable<Varchar>,
-        creator_avatar -> Nullable<Text>,
-        category_name -> Nullable<Varchar>,
-        number_of_subscribers -> Nullable<Int8>,
-        number_of_posts -> Nullable<Int8>,
-        number_of_comments -> Nullable<Int8>,
-        hot_rank -> Nullable<Int4>,
+        community_id -> Int4,
+        subscribers -> Int8,
+        posts -> Int8,
+        comments -> Int8,
     }
 }
 
@@ -320,48 +273,14 @@ table! {
 }
 
 table! {
-    post_aggregates_fast (id) {
+    post_aggregates (id) {
         id -> Int4,
-        name -> Nullable<Varchar>,
-        url -> Nullable<Text>,
-        body -> Nullable<Text>,
-        creator_id -> Nullable<Int4>,
-        community_id -> Nullable<Int4>,
-        removed -> Nullable<Bool>,
-        locked -> Nullable<Bool>,
-        published -> Nullable<Timestamp>,
-        updated -> Nullable<Timestamp>,
-        deleted -> Nullable<Bool>,
-        nsfw -> Nullable<Bool>,
-        stickied -> Nullable<Bool>,
-        embed_title -> Nullable<Text>,
-        embed_description -> Nullable<Text>,
-        embed_html -> Nullable<Text>,
-        thumbnail_url -> Nullable<Text>,
-        ap_id -> Nullable<Varchar>,
-        local -> Nullable<Bool>,
-        creator_actor_id -> Nullable<Varchar>,
-        creator_local -> Nullable<Bool>,
-        creator_name -> Nullable<Varchar>,
-        creator_preferred_username -> Nullable<Varchar>,
-        creator_published -> Nullable<Timestamp>,
-        creator_avatar -> Nullable<Text>,
-        banned -> Nullable<Bool>,
-        banned_from_community -> Nullable<Bool>,
-        community_actor_id -> Nullable<Varchar>,
-        community_local -> Nullable<Bool>,
-        community_name -> Nullable<Varchar>,
-        community_icon -> Nullable<Text>,
-        community_removed -> Nullable<Bool>,
-        community_deleted -> Nullable<Bool>,
-        community_nsfw -> Nullable<Bool>,
-        number_of_comments -> Nullable<Int8>,
-        score -> Nullable<Int8>,
-        upvotes -> Nullable<Int8>,
-        downvotes -> Nullable<Int8>,
-        hot_rank -> Nullable<Int4>,
-        hot_rank_active -> Nullable<Int4>,
-        newest_activity_time -> Nullable<Timestamp>,
+        post_id -> Int4,
+        comments -> Int8,
+        score -> Int8,
+        upvotes -> Int8,
+        downvotes -> Int8,
+        newest_comment_time -> Timestamp,
     }
 }
 
@@ -440,6 +359,17 @@ table! {
     }
 }
 
+table! {
+    site_aggregates (id) {
+        id -> Int4,
+        site_id -> Int4,
+        users -> Int8,
+        posts -> Int8,
+        comments -> Int8,
+        communities -> Int8,
+    }
+}
+
 table! {
     user_ (id) {
         id -> Int4,
@@ -471,6 +401,17 @@ table! {
     }
 }
 
+table! {
+    user_aggregates (id) {
+        id -> Int4,
+        user_id -> Int4,
+        post_count -> Int8,
+        post_score -> Int8,
+        comment_count -> Int8,
+        comment_score -> Int8,
+    }
+}
+
 table! {
     user_ban (id) {
         id -> Int4,
@@ -480,41 +421,107 @@ table! {
 }
 
 table! {
-    user_fast (id) {
+    user_mention (id) {
+        id -> Int4,
+        recipient_id -> Int4,
+        comment_id -> Int4,
+        read -> Bool,
+        published -> Timestamp,
+    }
+}
+
+// These are necessary since diesel doesn't have self joins / aliases
+table! {
+    comment_alias_1 (id) {
+        id -> Int4,
+        creator_id -> Int4,
+        post_id -> Int4,
+        parent_id -> Nullable<Int4>,
+        content -> Text,
+        removed -> Bool,
+        read -> Bool,
+        published -> Timestamp,
+        updated -> Nullable<Timestamp>,
+        deleted -> Bool,
+        ap_id -> Varchar,
+        local -> Bool,
+    }
+}
+
+table! {
+    user_alias_1 (id) {
         id -> Int4,
-        actor_id -> Nullable<Varchar>,
-        name -> Nullable<Varchar>,
+        name -> Varchar,
         preferred_username -> Nullable<Varchar>,
-        avatar -> Nullable<Text>,
-        banner -> Nullable<Text>,
+        password_encrypted -> Text,
         email -> Nullable<Text>,
+        avatar -> Nullable<Text>,
+        admin -> Bool,
+        banned -> Bool,
+        published -> Timestamp,
+        updated -> Nullable<Timestamp>,
+        show_nsfw -> Bool,
+        theme -> Varchar,
+        default_sort_type -> Int2,
+        default_listing_type -> Int2,
+        lang -> Varchar,
+        show_avatars -> Bool,
+        send_notifications_to_email -> Bool,
         matrix_user_id -> Nullable<Text>,
+        actor_id -> Varchar,
         bio -> Nullable<Text>,
-        local -> Nullable<Bool>,
-        admin -> Nullable<Bool>,
-        banned -> Nullable<Bool>,
-        show_avatars -> Nullable<Bool>,
-        send_notifications_to_email -> Nullable<Bool>,
-        published -> Nullable<Timestamp>,
-        number_of_posts -> Nullable<Int8>,
-        post_score -> Nullable<Int8>,
-        number_of_comments -> Nullable<Int8>,
-        comment_score -> Nullable<Int8>,
+        local -> Bool,
+        private_key -> Nullable<Text>,
+        public_key -> Nullable<Text>,
+        last_refreshed_at -> Timestamp,
+        banner -> Nullable<Text>,
+        deleted -> Bool,
     }
 }
 
 table! {
-    user_mention (id) {
+    user_alias_2 (id) {
         id -> Int4,
-        recipient_id -> Int4,
-        comment_id -> Int4,
-        read -> Bool,
+        name -> Varchar,
+        preferred_username -> Nullable<Varchar>,
+        password_encrypted -> Text,
+        email -> Nullable<Text>,
+        avatar -> Nullable<Text>,
+        admin -> Bool,
+        banned -> Bool,
         published -> Timestamp,
+        updated -> Nullable<Timestamp>,
+        show_nsfw -> Bool,
+        theme -> Varchar,
+        default_sort_type -> Int2,
+        default_listing_type -> Int2,
+        lang -> Varchar,
+        show_avatars -> Bool,
+        send_notifications_to_email -> Bool,
+        matrix_user_id -> Nullable<Text>,
+        actor_id -> Varchar,
+        bio -> Nullable<Text>,
+        local -> Bool,
+        private_key -> Nullable<Text>,
+        public_key -> Nullable<Text>,
+        last_refreshed_at -> Timestamp,
+        banner -> Nullable<Text>,
+        deleted -> Bool,
     }
 }
 
+joinable!(comment_alias_1 -> user_alias_1 (creator_id));
+joinable!(comment -> comment_alias_1 (parent_id));
+joinable!(user_mention -> user_alias_1 (recipient_id));
+joinable!(post -> user_alias_1 (creator_id));
+joinable!(comment -> user_alias_1 (creator_id));
+
+joinable!(post_report -> user_alias_2 (resolver_id));
+joinable!(comment_report -> user_alias_2 (resolver_id));
+
 joinable!(comment -> post (post_id));
 joinable!(comment -> user_ (creator_id));
+joinable!(comment_aggregates -> comment (comment_id));
 joinable!(comment_like -> comment (comment_id));
 joinable!(comment_like -> post (post_id));
 joinable!(comment_like -> user_ (user_id));
@@ -523,6 +530,7 @@ joinable!(comment_saved -> comment (comment_id));
 joinable!(comment_saved -> user_ (user_id));
 joinable!(community -> category (category_id));
 joinable!(community -> user_ (creator_id));
+joinable!(community_aggregates -> community (community_id));
 joinable!(community_follower -> community (community_id));
 joinable!(community_follower -> user_ (user_id));
 joinable!(community_moderator -> community (community_id));
@@ -544,6 +552,7 @@ joinable!(mod_sticky_post -> user_ (mod_user_id));
 joinable!(password_reset_request -> user_ (user_id));
 joinable!(post -> community (community_id));
 joinable!(post -> user_ (creator_id));
+joinable!(post_aggregates -> post (post_id));
 joinable!(post_like -> post (post_id));
 joinable!(post_like -> user_ (user_id));
 joinable!(post_read -> post (post_id));
@@ -552,6 +561,8 @@ joinable!(post_report -> post (post_id));
 joinable!(post_saved -> post (post_id));
 joinable!(post_saved -> user_ (user_id));
 joinable!(site -> user_ (creator_id));
+joinable!(site_aggregates -> site (site_id));
+joinable!(user_aggregates -> user_ (user_id));
 joinable!(user_ban -> user_ (user_id));
 joinable!(user_mention -> comment (comment_id));
 joinable!(user_mention -> user_ (recipient_id));
@@ -560,12 +571,12 @@ allow_tables_to_appear_in_same_query!(
   activity,
   category,
   comment,
-  comment_aggregates_fast,
+  comment_aggregates,
   comment_like,
   comment_report,
   comment_saved,
   community,
-  community_aggregates_fast,
+  community_aggregates,
   community_follower,
   community_moderator,
   community_user_ban,
@@ -580,15 +591,19 @@ allow_tables_to_appear_in_same_query!(
   mod_sticky_post,
   password_reset_request,
   post,
-  post_aggregates_fast,
+  post_aggregates,
   post_like,
   post_read,
   post_report,
   post_saved,
   private_message,
   site,
+  site_aggregates,
   user_,
+  user_aggregates,
   user_ban,
-  user_fast,
   user_mention,
+  comment_alias_1,
+  user_alias_1,
+  user_alias_2,
 );
diff --git a/lemmy_db/src/site_view.rs b/lemmy_db/src/site_view.rs
deleted file mode 100644 (file)
index fd15ac7..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-use diesel::{result::Error, *};
-use serde::Serialize;
-
-table! {
-  site_view (id) {
-    id -> Int4,
-    name -> Varchar,
-    description -> Nullable<Text>,
-    creator_id -> Int4,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    enable_downvotes -> Bool,
-    open_registration -> Bool,
-    enable_nsfw -> Bool,
-    icon -> Nullable<Text>,
-    banner -> Nullable<Text>,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_avatar -> Nullable<Text>,
-    number_of_users -> BigInt,
-    number_of_posts -> BigInt,
-    number_of_comments -> BigInt,
-    number_of_communities -> BigInt,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "site_view"]
-pub struct SiteView {
-  pub id: i32,
-  pub name: String,
-  pub description: Option<String>,
-  pub creator_id: i32,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub enable_downvotes: bool,
-  pub open_registration: bool,
-  pub enable_nsfw: bool,
-  pub icon: Option<String>,
-  pub banner: Option<String>,
-  pub creator_name: String,
-  pub creator_preferred_username: Option<String>,
-  pub creator_avatar: Option<String>,
-  pub number_of_users: i64,
-  pub number_of_posts: i64,
-  pub number_of_comments: i64,
-  pub number_of_communities: i64,
-}
-
-impl SiteView {
-  pub fn read(conn: &PgConnection) -> Result<Self, Error> {
-    use super::site_view::site_view::dsl::*;
-    site_view.first::<Self>(conn)
-  }
-}
similarity index 98%
rename from lemmy_db/src/activity.rs
rename to lemmy_db/src/source/activity.rs
index 190dd411c5c37e60fabd7b082131335e506d9fb2..b4b54c6ed293ad2681af14e75b16bfad4102eeaa 100644 (file)
@@ -97,9 +97,11 @@ impl Activity {
 #[cfg(test)]
 mod tests {
   use crate::{
-    activity::{Activity, ActivityForm},
+    source::{
+      activity::{Activity, ActivityForm},
+      user::{UserForm, User_},
+    },
     tests::establish_unpooled_connection,
-    user::{UserForm, User_},
     Crud,
     ListingType,
     SortType,
similarity index 89%
rename from lemmy_db/src/category.rs
rename to lemmy_db/src/source/category.rs
index 36beb9ff638b1a2070d9af70ecb7f4a2c2c6c6d3..95b65dc823ea31dc51f0bd8e6aafdb3193948565 100644 (file)
@@ -5,7 +5,7 @@ use crate::{
 use diesel::{dsl::*, result::Error, *};
 use serde::Serialize;
 
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Clone)]
 #[table_name = "category"]
 pub struct Category {
   pub id: i32,
@@ -48,7 +48,7 @@ impl Category {
 
 #[cfg(test)]
 mod tests {
-  use crate::{category::Category, tests::establish_unpooled_connection};
+  use crate::{source::category::Category, tests::establish_unpooled_connection};
 
   #[test]
   fn test_crud() {
similarity index 91%
rename from lemmy_db/src/comment.rs
rename to lemmy_db/src/source/comment.rs
index 9b0928257a34e86fa4585c4ec647a605efdc4018..380bdb3d157db6b1a259cc81dcf8416549c3cfa3 100644 (file)
@@ -1,12 +1,14 @@
 use super::post::Post;
 use crate::{
   naive_now,
-  schema::{comment, comment_like, comment_saved},
+  schema::{comment, comment_alias_1, comment_like, comment_saved},
+  ApubObject,
   Crud,
   Likeable,
   Saveable,
 };
 use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
 use url::{ParseError, Url};
 
 // WITH RECURSIVE MyTree AS (
@@ -16,7 +18,7 @@ use url::{ParseError, Url};
 // )
 // SELECT * FROM MyTree;
 
-#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
 #[belongs_to(Post)]
 #[table_name = "comment"]
 pub struct Comment {
@@ -34,6 +36,24 @@ pub struct Comment {
   pub local: bool,
 }
 
+#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
+#[belongs_to(Post)]
+#[table_name = "comment_alias_1"]
+pub struct CommentAlias1 {
+  pub id: i32,
+  pub creator_id: i32,
+  pub post_id: i32,
+  pub parent_id: Option<i32>,
+  pub content: String,
+  pub removed: bool,
+  pub read: bool, // Whether the recipient has read the comment or not
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub deleted: bool,
+  pub ap_id: String,
+  pub local: bool,
+}
+
 #[derive(Insertable, AsChangeset, Clone)]
 #[table_name = "comment"]
 pub struct CommentForm {
@@ -86,6 +106,23 @@ impl Crud<CommentForm> for Comment {
   }
 }
 
+impl ApubObject<CommentForm> for Comment {
+  fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
+    use crate::schema::comment::dsl::*;
+    comment.filter(ap_id.eq(object_id)).first::<Self>(conn)
+  }
+
+  fn upsert(conn: &PgConnection, comment_form: &CommentForm) -> Result<Self, Error> {
+    use crate::schema::comment::dsl::*;
+    insert_into(comment)
+      .values(comment_form)
+      .on_conflict(ap_id)
+      .do_update()
+      .set(comment_form)
+      .get_result::<Self>(conn)
+  }
+}
+
 impl Comment {
   pub fn update_ap_id(
     conn: &PgConnection,
@@ -99,11 +136,6 @@ impl Comment {
       .get_result::<Self>(conn)
   }
 
-  pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
-    use crate::schema::comment::dsl::*;
-    comment.filter(ap_id.eq(object_id)).first::<Self>(conn)
-  }
-
   pub fn permadelete_for_creator(
     conn: &PgConnection,
     for_creator_id: i32,
@@ -168,16 +200,6 @@ impl Comment {
       .set((content.eq(new_content), updated.eq(naive_now())))
       .get_result::<Self>(conn)
   }
-
-  pub fn upsert(conn: &PgConnection, comment_form: &CommentForm) -> Result<Self, Error> {
-    use crate::schema::comment::dsl::*;
-    insert_into(comment)
-      .values(comment_form)
-      .on_conflict(ap_id)
-      .do_update()
-      .set(comment_form)
-      .get_result::<Self>(conn)
-  }
 }
 
 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Clone)]
@@ -187,7 +209,7 @@ pub struct CommentLike {
   pub id: i32,
   pub user_id: i32,
   pub comment_id: i32,
-  pub post_id: i32,
+  pub post_id: i32, // TODO this is redundant
   pub score: i16,
   pub published: chrono::NaiveDateTime,
 }
@@ -197,7 +219,7 @@ pub struct CommentLike {
 pub struct CommentLikeForm {
   pub user_id: i32,
   pub comment_id: i32,
-  pub post_id: i32,
+  pub post_id: i32, // TODO this is redundant
   pub score: i16,
 }
 
@@ -206,6 +228,9 @@ impl Likeable<CommentLikeForm> for CommentLike {
     use crate::schema::comment_like::dsl::*;
     insert_into(comment_like)
       .values(comment_like_form)
+      .on_conflict((comment_id, user_id))
+      .do_update()
+      .set(comment_like_form)
       .get_result::<Self>(conn)
   }
   fn remove(conn: &PgConnection, user_id: i32, comment_id: i32) -> Result<usize, Error> {
@@ -241,6 +266,9 @@ impl Saveable<CommentSavedForm> for CommentSaved {
     use crate::schema::comment_saved::dsl::*;
     insert_into(comment_saved)
       .values(comment_saved_form)
+      .on_conflict((comment_id, user_id))
+      .do_update()
+      .set(comment_saved_form)
       .get_result::<Self>(conn)
   }
   fn unsave(conn: &PgConnection, comment_saved_form: &CommentSavedForm) -> Result<usize, Error> {
@@ -257,11 +285,8 @@ impl Saveable<CommentSavedForm> for CommentSaved {
 #[cfg(test)]
 mod tests {
   use crate::{
-    comment::*,
-    community::*,
-    post::*,
+    source::{comment::*, community::*, post::*, user::*},
     tests::establish_unpooled_connection,
-    user::*,
     Crud,
     ListingType,
     SortType,
diff --git a/lemmy_db/src/source/comment_report.rs b/lemmy_db/src/source/comment_report.rs
new file mode 100644 (file)
index 0000000..7a28089
--- /dev/null
@@ -0,0 +1,75 @@
+use diesel::{dsl::*, result::Error, *};
+use serde::{Deserialize, Serialize};
+
+use crate::{naive_now, schema::comment_report, source::comment::Comment, Reportable};
+
+#[derive(
+  Identifiable, Queryable, Associations, PartialEq, Serialize, Deserialize, Debug, Clone,
+)]
+#[belongs_to(Comment)]
+#[table_name = "comment_report"]
+pub struct CommentReport {
+  pub id: i32,
+  pub creator_id: i32,
+  pub comment_id: i32,
+  pub original_comment_text: String,
+  pub reason: String,
+  pub resolved: bool,
+  pub resolver_id: Option<i32>,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+}
+
+#[derive(Insertable, AsChangeset, Clone)]
+#[table_name = "comment_report"]
+pub struct CommentReportForm {
+  pub creator_id: i32,
+  pub comment_id: i32,
+  pub original_comment_text: String,
+  pub reason: String,
+}
+
+impl Reportable<CommentReportForm> for CommentReport {
+  /// creates a comment report and returns it
+  ///
+  /// * `conn` - the postgres connection
+  /// * `comment_report_form` - the filled CommentReportForm to insert
+  fn report(conn: &PgConnection, comment_report_form: &CommentReportForm) -> Result<Self, Error> {
+    use crate::schema::comment_report::dsl::*;
+    insert_into(comment_report)
+      .values(comment_report_form)
+      .get_result::<Self>(conn)
+  }
+
+  /// resolve a comment report
+  ///
+  /// * `conn` - the postgres connection
+  /// * `report_id` - the id of the report to resolve
+  /// * `by_resolver_id` - the id of the user resolving the report
+  fn resolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
+    use crate::schema::comment_report::dsl::*;
+    update(comment_report.find(report_id))
+      .set((
+        resolved.eq(true),
+        resolver_id.eq(by_resolver_id),
+        updated.eq(naive_now()),
+      ))
+      .execute(conn)
+  }
+
+  /// unresolve a comment report
+  ///
+  /// * `conn` - the postgres connection
+  /// * `report_id` - the id of the report to unresolve
+  /// * `by_resolver_id` - the id of the user unresolving the report
+  fn unresolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
+    use crate::schema::comment_report::dsl::*;
+    update(comment_report.find(report_id))
+      .set((
+        resolved.eq(false),
+        resolver_id.eq(by_resolver_id),
+        updated.eq(naive_now()),
+      ))
+      .execute(conn)
+  }
+}
similarity index 87%
rename from lemmy_db/src/community.rs
rename to lemmy_db/src/source/community.rs
index 5f76d5143c01019a015cb51cd109ddca28b8fd9a..0ad90da287c9f99fd1c6c691f80f5cdd562196cd 100644 (file)
@@ -1,14 +1,17 @@
 use crate::{
   naive_now,
   schema::{community, community_follower, community_moderator, community_user_ban},
+  views::{community::community_moderator_view::CommunityModeratorView, user_view::UserViewSafe},
+  ApubObject,
   Bannable,
   Crud,
   Followable,
   Joinable,
 };
 use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
 
-#[derive(Clone, Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
 #[table_name = "community"]
 pub struct Community {
   pub id: i32,
@@ -31,6 +34,71 @@ pub struct Community {
   pub banner: Option<String>,
 }
 
+/// A safe representation of community, without the sensitive info
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "community"]
+pub struct CommunitySafe {
+  pub id: i32,
+  pub name: String,
+  pub title: String,
+  pub description: Option<String>,
+  pub category_id: i32,
+  pub creator_id: i32,
+  pub removed: bool,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub deleted: bool,
+  pub nsfw: bool,
+  pub actor_id: String,
+  pub local: bool,
+  pub icon: Option<String>,
+  pub banner: Option<String>,
+}
+
+mod safe_type {
+  use crate::{schema::community::columns::*, source::community::Community, ToSafe};
+  type Columns = (
+    id,
+    name,
+    title,
+    description,
+    category_id,
+    creator_id,
+    removed,
+    published,
+    updated,
+    deleted,
+    nsfw,
+    actor_id,
+    local,
+    icon,
+    banner,
+  );
+
+  impl ToSafe for Community {
+    type SafeColumns = Columns;
+    fn safe_columns_tuple() -> Self::SafeColumns {
+      (
+        id,
+        name,
+        title,
+        description,
+        category_id,
+        creator_id,
+        removed,
+        published,
+        updated,
+        deleted,
+        nsfw,
+        actor_id,
+        local,
+        icon,
+        banner,
+      )
+    }
+  }
+}
+
 #[derive(Insertable, AsChangeset, Debug)]
 #[table_name = "community"]
 pub struct CommunityForm {
@@ -83,19 +151,31 @@ impl Crud<CommunityForm> for Community {
   }
 }
 
-impl Community {
-  pub fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Self, Error> {
+impl ApubObject<CommunityForm> for Community {
+  fn read_from_apub_id(conn: &PgConnection, for_actor_id: &str) -> Result<Self, Error> {
     use crate::schema::community::dsl::*;
     community
-      .filter(local.eq(true))
-      .filter(name.eq(community_name))
+      .filter(actor_id.eq(for_actor_id))
       .first::<Self>(conn)
   }
 
-  pub fn read_from_actor_id(conn: &PgConnection, for_actor_id: &str) -> Result<Self, Error> {
+  fn upsert(conn: &PgConnection, community_form: &CommunityForm) -> Result<Community, Error> {
+    use crate::schema::community::dsl::*;
+    insert_into(community)
+      .values(community_form)
+      .on_conflict(actor_id)
+      .do_update()
+      .set(community_form)
+      .get_result::<Self>(conn)
+  }
+}
+
+impl Community {
+  pub fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Self, Error> {
     use crate::schema::community::dsl::*;
     community
-      .filter(actor_id.eq(for_actor_id))
+      .filter(local.eq(true))
+      .filter(name.eq(community_name))
       .first::<Self>(conn)
   }
 
@@ -144,14 +224,13 @@ impl Community {
   }
 
   fn community_mods_and_admins(conn: &PgConnection, community_id: i32) -> Result<Vec<i32>, Error> {
-    use crate::{community_view::CommunityModeratorView, user_view::UserView};
     let mut mods_and_admins: Vec<i32> = Vec::new();
     mods_and_admins.append(
       &mut CommunityModeratorView::for_community(conn, community_id)
-        .map(|v| v.into_iter().map(|m| m.user_id).collect())?,
+        .map(|v| v.into_iter().map(|m| m.moderator.id).collect())?,
     );
     mods_and_admins
-      .append(&mut UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect())?);
+      .append(&mut UserViewSafe::admins(conn).map(|v| v.into_iter().map(|a| a.user.id).collect())?);
     Ok(mods_and_admins)
   }
 
@@ -165,16 +244,6 @@ impl Community {
       .unwrap_or_default()
       .contains(&user_id)
   }
-
-  pub fn upsert(conn: &PgConnection, community_form: &CommunityForm) -> Result<Community, Error> {
-    use crate::schema::community::dsl::*;
-    insert_into(community)
-      .values(community_form)
-      .on_conflict(actor_id)
-      .do_update()
-      .set(community_form)
-      .get_result::<Self>(conn)
-  }
 }
 
 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
@@ -306,6 +375,9 @@ impl Followable<CommunityFollowerForm> for CommunityFollower {
     use crate::schema::community_follower::dsl::*;
     insert_into(community_follower)
       .values(community_follower_form)
+      .on_conflict((community_id, user_id))
+      .do_update()
+      .set(community_follower_form)
       .get_result::<Self>(conn)
   }
   fn follow_accepted(conn: &PgConnection, community_id_: i32, user_id_: i32) -> Result<Self, Error>
@@ -346,7 +418,12 @@ impl Followable<CommunityFollowerForm> for CommunityFollower {
 
 #[cfg(test)]
 mod tests {
-  use crate::{community::*, tests::establish_unpooled_connection, user::*, ListingType, SortType};
+  use crate::{
+    source::{community::*, user::*},
+    tests::establish_unpooled_connection,
+    ListingType,
+    SortType,
+  };
 
   #[test]
   fn test_crud() {
diff --git a/lemmy_db/src/source/mod.rs b/lemmy_db/src/source/mod.rs
new file mode 100644 (file)
index 0000000..211194a
--- /dev/null
@@ -0,0 +1,13 @@
+pub mod activity;
+pub mod category;
+pub mod comment;
+pub mod comment_report;
+pub mod community;
+pub mod moderator;
+pub mod password_reset_request;
+pub mod post;
+pub mod post_report;
+pub mod private_message;
+pub mod site;
+pub mod user;
+pub mod user_mention;
similarity index 96%
rename from lemmy_db/src/moderator.rs
rename to lemmy_db/src/source/moderator.rs
index c0c0ff1067b3858153261f2edd2e08d97dd014ba..766c17fc57860dbafff4752206acab85b13ce1fe 100644 (file)
@@ -13,8 +13,9 @@ use crate::{
   Crud,
 };
 use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
 
-#[derive(Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
 #[table_name = "mod_remove_post"]
 pub struct ModRemovePost {
   pub id: i32,
@@ -55,7 +56,7 @@ impl Crud<ModRemovePostForm> for ModRemovePost {
   }
 }
 
-#[derive(Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
 #[table_name = "mod_lock_post"]
 pub struct ModLockPost {
   pub id: i32,
@@ -94,7 +95,7 @@ impl Crud<ModLockPostForm> for ModLockPost {
   }
 }
 
-#[derive(Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
 #[table_name = "mod_sticky_post"]
 pub struct ModStickyPost {
   pub id: i32,
@@ -133,7 +134,7 @@ impl Crud<ModStickyPostForm> for ModStickyPost {
   }
 }
 
-#[derive(Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
 #[table_name = "mod_remove_comment"]
 pub struct ModRemoveComment {
   pub id: i32,
@@ -174,7 +175,7 @@ impl Crud<ModRemoveCommentForm> for ModRemoveComment {
   }
 }
 
-#[derive(Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
 #[table_name = "mod_remove_community"]
 pub struct ModRemoveCommunity {
   pub id: i32,
@@ -221,7 +222,7 @@ impl Crud<ModRemoveCommunityForm> for ModRemoveCommunity {
   }
 }
 
-#[derive(Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
 #[table_name = "mod_ban_from_community"]
 pub struct ModBanFromCommunity {
   pub id: i32,
@@ -270,7 +271,7 @@ impl Crud<ModBanFromCommunityForm> for ModBanFromCommunity {
   }
 }
 
-#[derive(Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
 #[table_name = "mod_ban"]
 pub struct ModBan {
   pub id: i32,
@@ -311,7 +312,7 @@ impl Crud<ModBanForm> for ModBan {
   }
 }
 
-#[derive(Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
 #[table_name = "mod_add_community"]
 pub struct ModAddCommunity {
   pub id: i32,
@@ -352,7 +353,7 @@ impl Crud<ModAddCommunityForm> for ModAddCommunity {
   }
 }
 
-#[derive(Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
 #[table_name = "mod_add"]
 pub struct ModAdd {
   pub id: i32,
@@ -392,12 +393,8 @@ impl Crud<ModAddForm> for ModAdd {
 #[cfg(test)]
 mod tests {
   use crate::{
-    comment::*,
-    community::*,
-    moderator::*,
-    post::*,
+    source::{comment::*, community::*, moderator::*, post::*, user::*},
     tests::establish_unpooled_connection,
-    user::*,
     ListingType,
     SortType,
   };
similarity index 98%
rename from lemmy_db/src/password_reset_request.rs
rename to lemmy_db/src/source/password_reset_request.rs
index 8ae18cbd4fd18e796be459fa72ae175a31aa6d20..0cf0169f0ad4a18c79afd3d475d3ad5d5b0d428f 100644 (file)
@@ -80,7 +80,7 @@ impl PasswordResetRequest {
 mod tests {
   use super::super::user::*;
   use crate::{
-    password_reset_request::PasswordResetRequest,
+    source::password_reset_request::PasswordResetRequest,
     tests::establish_unpooled_connection,
     Crud,
     ListingType,
similarity index 96%
rename from lemmy_db/src/post.rs
rename to lemmy_db/src/source/post.rs
index 787d5e6c418a08148fc124422e95890c3817c1d1..948791ea4ae1f8abfd75b40a06a8a0e30e5113d1 100644 (file)
@@ -1,15 +1,17 @@
 use crate::{
   naive_now,
   schema::{post, post_like, post_read, post_saved},
+  ApubObject,
   Crud,
   Likeable,
   Readable,
   Saveable,
 };
 use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
 use url::{ParseError, Url};
 
-#[derive(Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
 #[table_name = "post"]
 pub struct Post {
   pub id: i32,
@@ -62,12 +64,48 @@ impl PostForm {
   }
 }
 
-impl Post {
-  pub fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
+impl Crud<PostForm> for Post {
+  fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
+    use crate::schema::post::dsl::*;
+    post.find(post_id).first::<Self>(conn)
+  }
+
+  fn delete(conn: &PgConnection, post_id: i32) -> Result<usize, Error> {
+    use crate::schema::post::dsl::*;
+    diesel::delete(post.find(post_id)).execute(conn)
+  }
+
+  fn create(conn: &PgConnection, new_post: &PostForm) -> Result<Self, Error> {
+    use crate::schema::post::dsl::*;
+    insert_into(post).values(new_post).get_result::<Self>(conn)
+  }
+
+  fn update(conn: &PgConnection, post_id: i32, new_post: &PostForm) -> Result<Self, Error> {
+    use crate::schema::post::dsl::*;
+    diesel::update(post.find(post_id))
+      .set(new_post)
+      .get_result::<Self>(conn)
+  }
+}
+
+impl ApubObject<PostForm> for Post {
+  fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
     use crate::schema::post::dsl::*;
-    post.filter(id.eq(post_id)).first::<Self>(conn)
+    post.filter(ap_id.eq(object_id)).first::<Self>(conn)
   }
 
+  fn upsert(conn: &PgConnection, post_form: &PostForm) -> Result<Post, Error> {
+    use crate::schema::post::dsl::*;
+    insert_into(post)
+      .values(post_form)
+      .on_conflict(ap_id)
+      .do_update()
+      .set(post_form)
+      .get_result::<Self>(conn)
+  }
+}
+
+impl Post {
   pub fn list_for_community(
     conn: &PgConnection,
     the_community_id: i32,
@@ -81,11 +119,6 @@ impl Post {
       .load::<Self>(conn)
   }
 
-  pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
-    use crate::schema::post::dsl::*;
-    post.filter(ap_id.eq(object_id)).first::<Self>(conn)
-  }
-
   pub fn update_ap_id(conn: &PgConnection, post_id: i32, apub_id: String) -> Result<Self, Error> {
     use crate::schema::post::dsl::*;
 
@@ -177,40 +210,6 @@ impl Post {
   pub fn is_post_creator(user_id: i32, post_creator_id: i32) -> bool {
     user_id == post_creator_id
   }
-
-  pub fn upsert(conn: &PgConnection, post_form: &PostForm) -> Result<Post, Error> {
-    use crate::schema::post::dsl::*;
-    insert_into(post)
-      .values(post_form)
-      .on_conflict(ap_id)
-      .do_update()
-      .set(post_form)
-      .get_result::<Self>(conn)
-  }
-}
-
-impl Crud<PostForm> for Post {
-  fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
-    use crate::schema::post::dsl::*;
-    post.find(post_id).first::<Self>(conn)
-  }
-
-  fn delete(conn: &PgConnection, post_id: i32) -> Result<usize, Error> {
-    use crate::schema::post::dsl::*;
-    diesel::delete(post.find(post_id)).execute(conn)
-  }
-
-  fn create(conn: &PgConnection, new_post: &PostForm) -> Result<Self, Error> {
-    use crate::schema::post::dsl::*;
-    insert_into(post).values(new_post).get_result::<Self>(conn)
-  }
-
-  fn update(conn: &PgConnection, post_id: i32, new_post: &PostForm) -> Result<Self, Error> {
-    use crate::schema::post::dsl::*;
-    diesel::update(post.find(post_id))
-      .set(new_post)
-      .get_result::<Self>(conn)
-  }
 }
 
 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
@@ -237,6 +236,9 @@ impl Likeable<PostLikeForm> for PostLike {
     use crate::schema::post_like::dsl::*;
     insert_into(post_like)
       .values(post_like_form)
+      .on_conflict((post_id, user_id))
+      .do_update()
+      .set(post_like_form)
       .get_result::<Self>(conn)
   }
   fn remove(conn: &PgConnection, user_id: i32, post_id: i32) -> Result<usize, Error> {
@@ -272,6 +274,9 @@ impl Saveable<PostSavedForm> for PostSaved {
     use crate::schema::post_saved::dsl::*;
     insert_into(post_saved)
       .values(post_saved_form)
+      .on_conflict((post_id, user_id))
+      .do_update()
+      .set(post_saved_form)
       .get_result::<Self>(conn)
   }
   fn unsave(conn: &PgConnection, post_saved_form: &PostSavedForm) -> Result<usize, Error> {
@@ -328,10 +333,8 @@ impl Readable<PostReadForm> for PostRead {
 #[cfg(test)]
 mod tests {
   use crate::{
-    community::*,
-    post::*,
+    source::{community::*, post::*, user::*},
     tests::establish_unpooled_connection,
-    user::*,
     ListingType,
     SortType,
   };
diff --git a/lemmy_db/src/source/post_report.rs b/lemmy_db/src/source/post_report.rs
new file mode 100644 (file)
index 0000000..0e59795
--- /dev/null
@@ -0,0 +1,79 @@
+use diesel::{dsl::*, result::Error, *};
+use serde::{Deserialize, Serialize};
+
+use crate::{naive_now, schema::post_report, source::post::Post, Reportable};
+
+#[derive(
+  Identifiable, Queryable, Associations, PartialEq, Serialize, Deserialize, Debug, Clone,
+)]
+#[belongs_to(Post)]
+#[table_name = "post_report"]
+pub struct PostReport {
+  pub id: i32,
+  pub creator_id: i32,
+  pub post_id: i32,
+  pub original_post_name: String,
+  pub original_post_url: Option<String>,
+  pub original_post_body: Option<String>,
+  pub reason: String,
+  pub resolved: bool,
+  pub resolver_id: Option<i32>,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+}
+
+#[derive(Insertable, AsChangeset, Clone)]
+#[table_name = "post_report"]
+pub struct PostReportForm {
+  pub creator_id: i32,
+  pub post_id: i32,
+  pub original_post_name: String,
+  pub original_post_url: Option<String>,
+  pub original_post_body: Option<String>,
+  pub reason: String,
+}
+
+impl Reportable<PostReportForm> for PostReport {
+  /// creates a post report and returns it
+  ///
+  /// * `conn` - the postgres connection
+  /// * `post_report_form` - the filled CommentReportForm to insert
+  fn report(conn: &PgConnection, post_report_form: &PostReportForm) -> Result<Self, Error> {
+    use crate::schema::post_report::dsl::*;
+    insert_into(post_report)
+      .values(post_report_form)
+      .get_result::<Self>(conn)
+  }
+
+  /// resolve a post report
+  ///
+  /// * `conn` - the postgres connection
+  /// * `report_id` - the id of the report to resolve
+  /// * `by_resolver_id` - the id of the user resolving the report
+  fn resolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
+    use crate::schema::post_report::dsl::*;
+    update(post_report.find(report_id))
+      .set((
+        resolved.eq(true),
+        resolver_id.eq(by_resolver_id),
+        updated.eq(naive_now()),
+      ))
+      .execute(conn)
+  }
+
+  /// resolve a post report
+  ///
+  /// * `conn` - the postgres connection
+  /// * `report_id` - the id of the report to unresolve
+  /// * `by_resolver_id` - the id of the user unresolving the report
+  fn unresolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
+    use crate::schema::post_report::dsl::*;
+    update(post_report.find(report_id))
+      .set((
+        resolved.eq(false),
+        resolver_id.eq(by_resolver_id),
+        updated.eq(naive_now()),
+      ))
+      .execute(conn)
+  }
+}
similarity index 93%
rename from lemmy_db/src/private_message.rs
rename to lemmy_db/src/source/private_message.rs
index 503a26abf748d799a4f9d38f64768bf2f36e3fa2..f474cf9ac303d3607b6a2de940151bcac0df116b 100644 (file)
@@ -1,7 +1,8 @@
-use crate::{naive_now, schema::private_message, Crud};
+use crate::{naive_now, schema::private_message, ApubObject, Crud};
 use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
 
-#[derive(Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
 #[table_name = "private_message"]
 pub struct PrivateMessage {
   pub id: i32,
@@ -55,6 +56,28 @@ impl Crud<PrivateMessageForm> for PrivateMessage {
   }
 }
 
+impl ApubObject<PrivateMessageForm> for PrivateMessage {
+  fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error>
+  where
+    Self: Sized,
+  {
+    use crate::schema::private_message::dsl::*;
+    private_message
+      .filter(ap_id.eq(object_id))
+      .first::<Self>(conn)
+  }
+
+  fn upsert(conn: &PgConnection, private_message_form: &PrivateMessageForm) -> Result<Self, Error> {
+    use crate::schema::private_message::dsl::*;
+    insert_into(private_message)
+      .values(private_message_form)
+      .on_conflict(ap_id)
+      .do_update()
+      .set(private_message_form)
+      .get_result::<Self>(conn)
+  }
+}
+
 impl PrivateMessage {
   pub fn update_ap_id(
     conn: &PgConnection,
@@ -68,13 +91,6 @@ impl PrivateMessage {
       .get_result::<Self>(conn)
   }
 
-  pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
-    use crate::schema::private_message::dsl::*;
-    private_message
-      .filter(ap_id.eq(object_id))
-      .first::<Self>(conn)
-  }
-
   pub fn update_content(
     conn: &PgConnection,
     private_message_id: i32,
@@ -118,28 +134,13 @@ impl PrivateMessage {
     .set(read.eq(true))
     .get_results::<Self>(conn)
   }
-
-  // TODO use this
-  pub fn upsert(
-    conn: &PgConnection,
-    private_message_form: &PrivateMessageForm,
-  ) -> Result<Self, Error> {
-    use crate::schema::private_message::dsl::*;
-    insert_into(private_message)
-      .values(private_message_form)
-      .on_conflict(ap_id)
-      .do_update()
-      .set(private_message_form)
-      .get_result::<Self>(conn)
-  }
 }
 
 #[cfg(test)]
 mod tests {
   use crate::{
-    private_message::*,
+    source::{private_message::*, user::*},
     tests::establish_unpooled_connection,
-    user::*,
     ListingType,
     SortType,
   };
similarity index 82%
rename from lemmy_db/src/site.rs
rename to lemmy_db/src/source/site.rs
index 5e68fead8afbb1078c791678e747fe8fa2d31095..8775191e1b3bf2773c38db16375f8434d6457524 100644 (file)
@@ -1,7 +1,8 @@
 use crate::{naive_now, schema::site, Crud};
 use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
 
-#[derive(Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Queryable, Identifiable, PartialEq, Debug, Clone, Serialize)]
 #[table_name = "site"]
 pub struct Site {
   pub id: i32,
@@ -49,6 +50,10 @@ impl Crud<SiteForm> for Site {
       .set(new_site)
       .get_result::<Self>(conn)
   }
+  fn delete(conn: &PgConnection, site_id: i32) -> Result<usize, Error> {
+    use crate::schema::site::dsl::*;
+    diesel::delete(site.find(site_id)).execute(conn)
+  }
 }
 
 impl Site {
@@ -58,4 +63,9 @@ impl Site {
       .set((creator_id.eq(new_creator_id), updated.eq(naive_now())))
       .get_result::<Self>(conn)
   }
+
+  pub fn read_simple(conn: &PgConnection) -> Result<Self, Error> {
+    use crate::schema::site::dsl::*;
+    site.first::<Self>(conn)
+  }
 }
similarity index 56%
rename from lemmy_db/src/user.rs
rename to lemmy_db/src/source/user.rs
index 0210c3b0b1d95d658d878a94969d03cd0390bedb..601e6e8c680f261f56a89d06329bf0bee9355417 100644 (file)
@@ -1,7 +1,8 @@
 use crate::{
   is_email_regex,
   naive_now,
-  schema::{user_, user_::dsl::*},
+  schema::{user_, user_::dsl::*, user_alias_1, user_alias_2},
+  ApubObject,
   Crud,
 };
 use bcrypt::{hash, DEFAULT_COST};
@@ -40,6 +41,252 @@ pub struct User_ {
   pub deleted: bool,
 }
 
+/// A safe representation of user, without the sensitive info
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "user_"]
+pub struct UserSafe {
+  pub id: i32,
+  pub name: String,
+  pub preferred_username: Option<String>,
+  pub avatar: Option<String>,
+  pub admin: bool,
+  pub banned: bool,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub matrix_user_id: Option<String>,
+  pub actor_id: String,
+  pub bio: Option<String>,
+  pub local: bool,
+  pub banner: Option<String>,
+  pub deleted: bool,
+}
+
+mod safe_type {
+  use crate::{schema::user_::columns::*, source::user::User_, ToSafe};
+  type Columns = (
+    id,
+    name,
+    preferred_username,
+    avatar,
+    admin,
+    banned,
+    published,
+    updated,
+    matrix_user_id,
+    actor_id,
+    bio,
+    local,
+    banner,
+    deleted,
+  );
+
+  impl ToSafe for User_ {
+    type SafeColumns = Columns;
+    fn safe_columns_tuple() -> Self::SafeColumns {
+      (
+        id,
+        name,
+        preferred_username,
+        avatar,
+        admin,
+        banned,
+        published,
+        updated,
+        matrix_user_id,
+        actor_id,
+        bio,
+        local,
+        banner,
+        deleted,
+      )
+    }
+  }
+}
+
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "user_alias_1"]
+pub struct UserAlias1 {
+  pub id: i32,
+  pub name: String,
+  pub preferred_username: Option<String>,
+  pub password_encrypted: String,
+  pub email: Option<String>,
+  pub avatar: Option<String>,
+  pub admin: bool,
+  pub banned: bool,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub show_nsfw: bool,
+  pub theme: String,
+  pub default_sort_type: i16,
+  pub default_listing_type: i16,
+  pub lang: String,
+  pub show_avatars: bool,
+  pub send_notifications_to_email: bool,
+  pub matrix_user_id: Option<String>,
+  pub actor_id: String,
+  pub bio: Option<String>,
+  pub local: bool,
+  pub private_key: Option<String>,
+  pub public_key: Option<String>,
+  pub last_refreshed_at: chrono::NaiveDateTime,
+  pub banner: Option<String>,
+  pub deleted: bool,
+}
+
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "user_alias_1"]
+pub struct UserSafeAlias1 {
+  pub id: i32,
+  pub name: String,
+  pub preferred_username: Option<String>,
+  pub avatar: Option<String>,
+  pub admin: bool,
+  pub banned: bool,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub matrix_user_id: Option<String>,
+  pub actor_id: String,
+  pub bio: Option<String>,
+  pub local: bool,
+  pub banner: Option<String>,
+  pub deleted: bool,
+}
+
+mod safe_type_alias_1 {
+  use crate::{schema::user_alias_1::columns::*, source::user::UserAlias1, ToSafe};
+  type Columns = (
+    id,
+    name,
+    preferred_username,
+    avatar,
+    admin,
+    banned,
+    published,
+    updated,
+    matrix_user_id,
+    actor_id,
+    bio,
+    local,
+    banner,
+    deleted,
+  );
+
+  impl ToSafe for UserAlias1 {
+    type SafeColumns = Columns;
+    fn safe_columns_tuple() -> Self::SafeColumns {
+      (
+        id,
+        name,
+        preferred_username,
+        avatar,
+        admin,
+        banned,
+        published,
+        updated,
+        matrix_user_id,
+        actor_id,
+        bio,
+        local,
+        banner,
+        deleted,
+      )
+    }
+  }
+}
+
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "user_alias_2"]
+pub struct UserAlias2 {
+  pub id: i32,
+  pub name: String,
+  pub preferred_username: Option<String>,
+  pub password_encrypted: String,
+  pub email: Option<String>,
+  pub avatar: Option<String>,
+  pub admin: bool,
+  pub banned: bool,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub show_nsfw: bool,
+  pub theme: String,
+  pub default_sort_type: i16,
+  pub default_listing_type: i16,
+  pub lang: String,
+  pub show_avatars: bool,
+  pub send_notifications_to_email: bool,
+  pub matrix_user_id: Option<String>,
+  pub actor_id: String,
+  pub bio: Option<String>,
+  pub local: bool,
+  pub private_key: Option<String>,
+  pub public_key: Option<String>,
+  pub last_refreshed_at: chrono::NaiveDateTime,
+  pub banner: Option<String>,
+  pub deleted: bool,
+}
+
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "user_alias_2"]
+pub struct UserSafeAlias2 {
+  pub id: i32,
+  pub name: String,
+  pub preferred_username: Option<String>,
+  pub avatar: Option<String>,
+  pub admin: bool,
+  pub banned: bool,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub matrix_user_id: Option<String>,
+  pub actor_id: String,
+  pub bio: Option<String>,
+  pub local: bool,
+  pub banner: Option<String>,
+  pub deleted: bool,
+}
+
+mod safe_type_alias_2 {
+  use crate::{schema::user_alias_2::columns::*, source::user::UserAlias2, ToSafe};
+  type Columns = (
+    id,
+    name,
+    preferred_username,
+    avatar,
+    admin,
+    banned,
+    published,
+    updated,
+    matrix_user_id,
+    actor_id,
+    bio,
+    local,
+    banner,
+    deleted,
+  );
+
+  impl ToSafe for UserAlias2 {
+    type SafeColumns = Columns;
+    fn safe_columns_tuple() -> Self::SafeColumns {
+      (
+        id,
+        name,
+        preferred_username,
+        avatar,
+        admin,
+        banned,
+        published,
+        updated,
+        matrix_user_id,
+        actor_id,
+        bio,
+        local,
+        banner,
+        deleted,
+      )
+    }
+  }
+}
+
 #[derive(Insertable, AsChangeset, Clone)]
 #[table_name = "user_"]
 pub struct UserForm {
@@ -89,6 +336,25 @@ impl Crud<UserForm> for User_ {
   }
 }
 
+impl ApubObject<UserForm> for User_ {
+  fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
+    use crate::schema::user_::dsl::*;
+    user_
+      .filter(deleted.eq(false))
+      .filter(actor_id.eq(object_id))
+      .first::<Self>(conn)
+  }
+
+  fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result<User_, Error> {
+    insert_into(user_)
+      .values(user_form)
+      .on_conflict(actor_id)
+      .do_update()
+      .set(user_form)
+      .get_result::<Self>(conn)
+  }
+}
+
 impl User_ {
   pub fn register(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
     let mut edited_user = form.clone();
@@ -135,14 +401,6 @@ impl User_ {
       .get_result::<Self>(conn)
   }
 
-  pub fn read_from_actor_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
-    use crate::schema::user_::dsl::*;
-    user_
-      .filter(deleted.eq(false))
-      .filter(actor_id.eq(object_id))
-      .first::<Self>(conn)
-  }
-
   pub fn find_by_email_or_username(
     conn: &PgConnection,
     username_or_email: &str,
@@ -179,12 +437,9 @@ impl User_ {
     )
   }
 
-  pub fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result<User_, Error> {
-    insert_into(user_)
-      .values(user_form)
-      .on_conflict(actor_id)
-      .do_update()
-      .set(user_form)
+  pub fn mark_as_updated(conn: &PgConnection, user_id: i32) -> Result<User_, Error> {
+    diesel::update(user_.find(user_id))
+      .set((last_refreshed_at.eq(naive_now()),))
       .get_result::<Self>(conn)
   }
 
@@ -204,7 +459,7 @@ impl User_ {
 
 #[cfg(test)]
 mod tests {
-  use crate::{tests::establish_unpooled_connection, user::*, ListingType, SortType};
+  use crate::{source::user::*, tests::establish_unpooled_connection, ListingType, SortType};
 
   #[test]
   fn test_crud() {
similarity index 93%
rename from lemmy_db/src/user_mention.rs
rename to lemmy_db/src/source/user_mention.rs
index 68f566332dd8969f30c7fb141dc24eb10644bbed..bf53cb4200504854fbdaecce9515158fe1eec54a 100644 (file)
@@ -1,8 +1,9 @@
 use super::comment::Comment;
 use crate::{schema::user_mention, Crud};
 use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
 
-#[derive(Queryable, Associations, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
 #[belongs_to(Comment)]
 #[table_name = "user_mention"]
 pub struct UserMention {
@@ -29,8 +30,13 @@ impl Crud<UserMentionForm> for UserMention {
 
   fn create(conn: &PgConnection, user_mention_form: &UserMentionForm) -> Result<Self, Error> {
     use crate::schema::user_mention::dsl::*;
+    // since the return here isnt utilized, we dont need to do an update
+    // but get_result doesnt return the existing row here
     insert_into(user_mention)
       .values(user_mention_form)
+      .on_conflict((recipient_id, comment_id))
+      .do_update()
+      .set(user_mention_form)
       .get_result::<Self>(conn)
   }
 
@@ -73,12 +79,8 @@ impl UserMention {
 #[cfg(test)]
 mod tests {
   use crate::{
-    comment::*,
-    community::*,
-    post::*,
+    source::{comment::*, community::*, post::*, user::*, user_mention::*},
     tests::establish_unpooled_connection,
-    user::*,
-    user_mention::*,
     ListingType,
     SortType,
   };
diff --git a/lemmy_db/src/user_mention_view.rs b/lemmy_db/src/user_mention_view.rs
deleted file mode 100644 (file)
index d1ce5eb..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-use crate::{limit_and_offset, MaybeOptional, SortType};
-use diesel::{dsl::*, pg::Pg, result::Error, *};
-use serde::Serialize;
-
-// The faked schema since diesel doesn't do views
-table! {
-  user_mention_view (id) {
-    id -> Int4,
-    user_mention_id -> Int4,
-    creator_id -> Int4,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    post_id -> Int4,
-    post_name -> Varchar,
-    parent_id -> Nullable<Int4>,
-    content -> Text,
-    removed -> Bool,
-    read -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    community_id -> Int4,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-    banned -> Bool,
-    banned_from_community -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_avatar -> Nullable<Text>,
-    score -> BigInt,
-    upvotes -> BigInt,
-    downvotes -> BigInt,
-    hot_rank -> Int4,
-    hot_rank_active -> Int4,
-    user_id -> Nullable<Int4>,
-    my_vote -> Nullable<Int4>,
-    saved -> Nullable<Bool>,
-    recipient_id -> Int4,
-    recipient_actor_id -> Text,
-    recipient_local -> Bool,
-  }
-}
-
-table! {
-  user_mention_fast_view (id) {
-    id -> Int4,
-    user_mention_id -> Int4,
-    creator_id -> Int4,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    post_id -> Int4,
-    post_name -> Varchar,
-    parent_id -> Nullable<Int4>,
-    content -> Text,
-    removed -> Bool,
-    read -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    community_id -> Int4,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-    banned -> Bool,
-    banned_from_community -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_avatar -> Nullable<Text>,
-    score -> BigInt,
-    upvotes -> BigInt,
-    downvotes -> BigInt,
-    hot_rank -> Int4,
-    hot_rank_active -> Int4,
-    user_id -> Nullable<Int4>,
-    my_vote -> Nullable<Int4>,
-    saved -> Nullable<Bool>,
-    recipient_id -> Int4,
-    recipient_actor_id -> Text,
-    recipient_local -> Bool,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "user_mention_fast_view"]
-pub struct UserMentionView {
-  pub id: i32,
-  pub user_mention_id: i32,
-  pub creator_id: i32,
-  pub creator_actor_id: String,
-  pub creator_local: bool,
-  pub post_id: i32,
-  pub post_name: String,
-  pub parent_id: Option<i32>,
-  pub content: String,
-  pub removed: bool,
-  pub read: bool,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub deleted: bool,
-  pub community_id: i32,
-  pub community_actor_id: String,
-  pub community_local: bool,
-  pub community_name: String,
-  pub community_icon: Option<String>,
-  pub banned: bool,
-  pub banned_from_community: bool,
-  pub creator_name: String,
-  pub creator_preferred_username: Option<String>,
-  pub creator_avatar: Option<String>,
-  pub score: i64,
-  pub upvotes: i64,
-  pub downvotes: i64,
-  pub hot_rank: i32,
-  pub hot_rank_active: i32,
-  pub user_id: Option<i32>,
-  pub my_vote: Option<i32>,
-  pub saved: Option<bool>,
-  pub recipient_id: i32,
-  pub recipient_actor_id: String,
-  pub recipient_local: bool,
-}
-
-pub struct UserMentionQueryBuilder<'a> {
-  conn: &'a PgConnection,
-  query: super::user_mention_view::user_mention_fast_view::BoxedQuery<'a, Pg>,
-  for_user_id: i32,
-  sort: &'a SortType,
-  unread_only: bool,
-  page: Option<i64>,
-  limit: Option<i64>,
-}
-
-impl<'a> UserMentionQueryBuilder<'a> {
-  pub fn create(conn: &'a PgConnection, for_user_id: i32) -> Self {
-    use super::user_mention_view::user_mention_fast_view::dsl::*;
-
-    let query = user_mention_fast_view.into_boxed();
-
-    UserMentionQueryBuilder {
-      conn,
-      query,
-      for_user_id,
-      sort: &SortType::New,
-      unread_only: false,
-      page: None,
-      limit: None,
-    }
-  }
-
-  pub fn sort(mut self, sort: &'a SortType) -> Self {
-    self.sort = sort;
-    self
-  }
-
-  pub fn unread_only(mut self, unread_only: bool) -> Self {
-    self.unread_only = unread_only;
-    self
-  }
-
-  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
-    self.page = page.get_optional();
-    self
-  }
-
-  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
-    self.limit = limit.get_optional();
-    self
-  }
-
-  pub fn list(self) -> Result<Vec<UserMentionView>, Error> {
-    use super::user_mention_view::user_mention_fast_view::dsl::*;
-
-    let mut query = self.query;
-
-    if self.unread_only {
-      query = query.filter(read.eq(false));
-    }
-
-    query = query
-      .filter(user_id.eq(self.for_user_id))
-      .filter(recipient_id.eq(self.for_user_id));
-
-    query = match self.sort {
-      SortType::Hot => query
-        .order_by(hot_rank.desc())
-        .then_order_by(published.desc()),
-      SortType::Active => query
-        .order_by(hot_rank_active.desc())
-        .then_order_by(published.desc()),
-      SortType::New => query.order_by(published.desc()),
-      SortType::TopAll => query.order_by(score.desc()),
-      SortType::TopYear => query
-        .filter(published.gt(now - 1.years()))
-        .order_by(score.desc()),
-      SortType::TopMonth => query
-        .filter(published.gt(now - 1.months()))
-        .order_by(score.desc()),
-      SortType::TopWeek => query
-        .filter(published.gt(now - 1.weeks()))
-        .order_by(score.desc()),
-      SortType::TopDay => query
-        .filter(published.gt(now - 1.days()))
-        .order_by(score.desc()),
-      // _ => query.order_by(published.desc()),
-    };
-
-    let (limit, offset) = limit_and_offset(self.page, self.limit);
-    query
-      .limit(limit)
-      .offset(offset)
-      .load::<UserMentionView>(self.conn)
-  }
-}
-
-impl UserMentionView {
-  pub fn read(
-    conn: &PgConnection,
-    from_user_mention_id: i32,
-    from_recipient_id: i32,
-  ) -> Result<Self, Error> {
-    use super::user_mention_view::user_mention_fast_view::dsl::*;
-
-    user_mention_fast_view
-      .filter(user_mention_id.eq(from_user_mention_id))
-      .filter(user_id.eq(from_recipient_id))
-      .first::<Self>(conn)
-  }
-}
diff --git a/lemmy_db/src/user_view.rs b/lemmy_db/src/user_view.rs
deleted file mode 100644 (file)
index bf85280..0000000
+++ /dev/null
@@ -1,279 +0,0 @@
-use super::user_view::user_fast::BoxedQuery;
-use crate::{fuzzy_search, limit_and_offset, MaybeOptional, SortType};
-use diesel::{dsl::*, pg::Pg, result::Error, *};
-use serde::Serialize;
-
-table! {
-  user_view (id) {
-    id -> Int4,
-    actor_id -> Text,
-    name -> Varchar,
-    preferred_username -> Nullable<Varchar>,
-    avatar -> Nullable<Text>,
-    banner -> Nullable<Text>,
-    email -> Nullable<Text>,
-    matrix_user_id -> Nullable<Text>,
-    bio -> Nullable<Text>,
-    local -> Bool,
-    admin -> Bool,
-    banned -> Bool,
-    show_avatars -> Bool,
-    send_notifications_to_email -> Bool,
-    published -> Timestamp,
-    number_of_posts -> BigInt,
-    post_score -> BigInt,
-    number_of_comments -> BigInt,
-    comment_score -> BigInt,
-  }
-}
-
-table! {
-  user_fast (id) {
-    id -> Int4,
-    actor_id -> Text,
-    name -> Varchar,
-    preferred_username -> Nullable<Varchar>,
-    avatar -> Nullable<Text>,
-    banner -> Nullable<Text>,
-    email -> Nullable<Text>,
-    matrix_user_id -> Nullable<Text>,
-    bio -> Nullable<Text>,
-    local -> Bool,
-    admin -> Bool,
-    banned -> Bool,
-    show_avatars -> Bool,
-    send_notifications_to_email -> Bool,
-    published -> Timestamp,
-    number_of_posts -> BigInt,
-    post_score -> BigInt,
-    number_of_comments -> BigInt,
-    comment_score -> BigInt,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "user_fast"]
-pub struct UserView {
-  pub id: i32,
-  pub actor_id: String,
-  pub name: String,
-  pub preferred_username: Option<String>,
-  pub avatar: Option<String>,
-  pub banner: Option<String>,
-  pub email: Option<String>, // TODO this shouldn't be in this view
-  pub matrix_user_id: Option<String>,
-  pub bio: Option<String>,
-  pub local: bool,
-  pub admin: bool,
-  pub banned: bool,
-  pub show_avatars: bool, // TODO this is a setting, probably doesn't need to be here
-  pub send_notifications_to_email: bool, // TODO also never used
-  pub published: chrono::NaiveDateTime,
-  pub number_of_posts: i64,
-  pub post_score: i64,
-  pub number_of_comments: i64,
-  pub comment_score: i64,
-}
-
-pub struct UserQueryBuilder<'a> {
-  conn: &'a PgConnection,
-  query: BoxedQuery<'a, Pg>,
-  sort: &'a SortType,
-  page: Option<i64>,
-  limit: Option<i64>,
-}
-
-impl<'a> UserQueryBuilder<'a> {
-  pub fn create(conn: &'a PgConnection) -> Self {
-    use super::user_view::user_fast::dsl::*;
-
-    let query = user_fast.into_boxed();
-
-    UserQueryBuilder {
-      conn,
-      query,
-      sort: &SortType::Hot,
-      page: None,
-      limit: None,
-    }
-  }
-
-  pub fn sort(mut self, sort: &'a SortType) -> Self {
-    self.sort = sort;
-    self
-  }
-
-  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
-    use super::user_view::user_fast::dsl::*;
-    if let Some(search_term) = search_term.get_optional() {
-      self.query = self.query.filter(name.ilike(fuzzy_search(&search_term)));
-    }
-    self
-  }
-
-  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
-    self.page = page.get_optional();
-    self
-  }
-
-  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
-    self.limit = limit.get_optional();
-    self
-  }
-
-  pub fn list(self) -> Result<Vec<UserView>, Error> {
-    use super::user_view::user_fast::dsl::*;
-    use diesel::sql_types::{Nullable, Text};
-
-    let mut query = self.query;
-
-    query = match self.sort {
-      SortType::Hot => query
-        .order_by(comment_score.desc())
-        .then_order_by(published.desc()),
-      SortType::Active => query
-        .order_by(comment_score.desc())
-        .then_order_by(published.desc()),
-      SortType::New => query.order_by(published.desc()),
-      SortType::TopAll => query.order_by(comment_score.desc()),
-      SortType::TopYear => query
-        .filter(published.gt(now - 1.years()))
-        .order_by(comment_score.desc()),
-      SortType::TopMonth => query
-        .filter(published.gt(now - 1.months()))
-        .order_by(comment_score.desc()),
-      SortType::TopWeek => query
-        .filter(published.gt(now - 1.weeks()))
-        .order_by(comment_score.desc()),
-      SortType::TopDay => query
-        .filter(published.gt(now - 1.days()))
-        .order_by(comment_score.desc()),
-    };
-
-    let (limit, offset) = limit_and_offset(self.page, self.limit);
-    query = query.limit(limit).offset(offset);
-
-    // The select is necessary here to not get back emails
-    query = query.select((
-      id,
-      actor_id,
-      name,
-      preferred_username,
-      avatar,
-      banner,
-      "".into_sql::<Nullable<Text>>(),
-      matrix_user_id,
-      bio,
-      local,
-      admin,
-      banned,
-      show_avatars,
-      send_notifications_to_email,
-      published,
-      number_of_posts,
-      post_score,
-      number_of_comments,
-      comment_score,
-    ));
-    query.load::<UserView>(self.conn)
-  }
-}
-
-impl UserView {
-  pub fn admins(conn: &PgConnection) -> Result<Vec<Self>, Error> {
-    use super::user_view::user_fast::dsl::*;
-    use diesel::sql_types::{Nullable, Text};
-    user_fast
-      // The select is necessary here to not get back emails
-      .select((
-        id,
-        actor_id,
-        name,
-        preferred_username,
-        avatar,
-        banner,
-        "".into_sql::<Nullable<Text>>(),
-        matrix_user_id,
-        bio,
-        local,
-        admin,
-        banned,
-        show_avatars,
-        send_notifications_to_email,
-        published,
-        number_of_posts,
-        post_score,
-        number_of_comments,
-        comment_score,
-      ))
-      .filter(admin.eq(true))
-      .order_by(published)
-      .load::<Self>(conn)
-  }
-
-  pub fn banned(conn: &PgConnection) -> Result<Vec<Self>, Error> {
-    use super::user_view::user_fast::dsl::*;
-    use diesel::sql_types::{Nullable, Text};
-    user_fast
-      .select((
-        id,
-        actor_id,
-        name,
-        preferred_username,
-        avatar,
-        banner,
-        "".into_sql::<Nullable<Text>>(),
-        matrix_user_id,
-        bio,
-        local,
-        admin,
-        banned,
-        show_avatars,
-        send_notifications_to_email,
-        published,
-        number_of_posts,
-        post_score,
-        number_of_comments,
-        comment_score,
-      ))
-      .filter(banned.eq(true))
-      .load::<Self>(conn)
-  }
-
-  // WARNING!!! this method WILL return sensitive user information and should only be called
-  // if the user requesting these details is also the authenticated user.
-  // please use get_user_secure to obtain user rows in most cases.
-  pub fn get_user_dangerous(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
-    use super::user_view::user_fast::dsl::*;
-    user_fast.find(user_id).first::<Self>(conn)
-  }
-
-  pub fn get_user_secure(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
-    use super::user_view::user_fast::dsl::*;
-    use diesel::sql_types::{Nullable, Text};
-    user_fast
-      .select((
-        id,
-        actor_id,
-        name,
-        preferred_username,
-        avatar,
-        banner,
-        "".into_sql::<Nullable<Text>>(),
-        matrix_user_id,
-        bio,
-        local,
-        admin,
-        banned,
-        show_avatars,
-        send_notifications_to_email,
-        published,
-        number_of_posts,
-        post_score,
-        number_of_comments,
-        comment_score,
-      ))
-      .find(user_id)
-      .first::<Self>(conn)
-  }
-}
diff --git a/lemmy_db/src/views/comment_report_view.rs b/lemmy_db/src/views/comment_report_view.rs
new file mode 100644 (file)
index 0000000..540bb75
--- /dev/null
@@ -0,0 +1,193 @@
+use crate::{
+  limit_and_offset,
+  schema::{comment, comment_report, community, post, user_, user_alias_1, user_alias_2},
+  source::{
+    comment::Comment,
+    comment_report::CommentReport,
+    community::{Community, CommunitySafe},
+    post::Post,
+    user::{UserAlias1, UserAlias2, UserSafe, UserSafeAlias1, UserSafeAlias2, User_},
+  },
+  views::ViewToVec,
+  MaybeOptional,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, PartialEq, Serialize, Clone)]
+pub struct CommentReportView {
+  pub comment_report: CommentReport,
+  pub comment: Comment,
+  pub post: Post,
+  pub community: CommunitySafe,
+  pub creator: UserSafe,
+  pub comment_creator: UserSafeAlias1,
+  pub resolver: Option<UserSafeAlias2>,
+}
+
+type CommentReportViewTuple = (
+  CommentReport,
+  Comment,
+  Post,
+  CommunitySafe,
+  UserSafe,
+  UserSafeAlias1,
+  Option<UserSafeAlias2>,
+);
+
+impl CommentReportView {
+  /// returns the CommentReportView for the provided report_id
+  ///
+  /// * `report_id` - the report id to obtain
+  pub fn read(conn: &PgConnection, report_id: i32) -> Result<Self, Error> {
+    let (comment_report, comment, post, community, creator, comment_creator, resolver) =
+      comment_report::table
+        .find(report_id)
+        .inner_join(comment::table)
+        .inner_join(post::table.on(comment::post_id.eq(post::id)))
+        .inner_join(community::table.on(post::community_id.eq(community::id)))
+        .inner_join(user_::table.on(comment_report::creator_id.eq(user_::id)))
+        .inner_join(user_alias_1::table.on(post::creator_id.eq(user_alias_1::id)))
+        .left_join(
+          user_alias_2::table.on(comment_report::resolver_id.eq(user_alias_2::id.nullable())),
+        )
+        .select((
+          comment_report::all_columns,
+          comment::all_columns,
+          post::all_columns,
+          Community::safe_columns_tuple(),
+          User_::safe_columns_tuple(),
+          UserAlias1::safe_columns_tuple(),
+          UserAlias2::safe_columns_tuple().nullable(),
+        ))
+        .first::<CommentReportViewTuple>(conn)?;
+
+    Ok(Self {
+      comment_report,
+      comment,
+      post,
+      community,
+      creator,
+      comment_creator,
+      resolver,
+    })
+  }
+
+  /// returns the current unresolved post report count for the supplied community ids
+  ///
+  /// * `community_ids` - a Vec<i32> of community_ids to get a count for
+  /// TODO this eq_any is a bad way to do this, would be better to join to communitymoderator
+  /// for a user id
+  pub fn get_report_count(conn: &PgConnection, community_ids: &[i32]) -> Result<i64, Error> {
+    use diesel::dsl::*;
+    comment_report::table
+      .inner_join(comment::table)
+      .inner_join(post::table.on(comment::post_id.eq(post::id)))
+      .filter(
+        comment_report::resolved
+          .eq(false)
+          .and(post::community_id.eq_any(community_ids)),
+      )
+      .select(count(comment_report::id))
+      .first::<i64>(conn)
+  }
+}
+
+pub struct CommentReportQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  community_ids: Option<Vec<i32>>, // TODO bad way to do this
+  page: Option<i64>,
+  limit: Option<i64>,
+  resolved: Option<bool>,
+}
+
+impl<'a> CommentReportQueryBuilder<'a> {
+  pub fn create(conn: &'a PgConnection) -> Self {
+    CommentReportQueryBuilder {
+      conn,
+      community_ids: None,
+      page: None,
+      limit: None,
+      resolved: Some(false),
+    }
+  }
+
+  pub fn community_ids<T: MaybeOptional<Vec<i32>>>(mut self, community_ids: T) -> Self {
+    self.community_ids = community_ids.get_optional();
+    self
+  }
+
+  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
+    self.page = page.get_optional();
+    self
+  }
+
+  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
+    self.limit = limit.get_optional();
+    self
+  }
+
+  pub fn resolved<T: MaybeOptional<bool>>(mut self, resolved: T) -> Self {
+    self.resolved = resolved.get_optional();
+    self
+  }
+
+  pub fn list(self) -> Result<Vec<CommentReportView>, Error> {
+    let mut query = comment_report::table
+      .inner_join(comment::table)
+      .inner_join(post::table.on(comment::post_id.eq(post::id)))
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .inner_join(user_::table.on(comment_report::creator_id.eq(user_::id)))
+      .inner_join(user_alias_1::table.on(post::creator_id.eq(user_alias_1::id)))
+      .left_join(
+        user_alias_2::table.on(comment_report::resolver_id.eq(user_alias_2::id.nullable())),
+      )
+      .select((
+        comment_report::all_columns,
+        comment::all_columns,
+        post::all_columns,
+        Community::safe_columns_tuple(),
+        User_::safe_columns_tuple(),
+        UserAlias1::safe_columns_tuple(),
+        UserAlias2::safe_columns_tuple().nullable(),
+      ))
+      .into_boxed();
+
+    if let Some(comm_ids) = self.community_ids {
+      query = query.filter(post::community_id.eq_any(comm_ids));
+    }
+
+    if let Some(resolved_flag) = self.resolved {
+      query = query.filter(comment_report::resolved.eq(resolved_flag));
+    }
+
+    let (limit, offset) = limit_and_offset(self.page, self.limit);
+
+    let res = query
+      .order_by(comment_report::published.asc())
+      .limit(limit)
+      .offset(offset)
+      .load::<CommentReportViewTuple>(self.conn)?;
+
+    Ok(CommentReportView::to_vec(res))
+  }
+}
+
+impl ViewToVec for CommentReportView {
+  type DbTuple = CommentReportViewTuple;
+  fn to_vec(posts: Vec<Self::DbTuple>) -> Vec<Self> {
+    posts
+      .iter()
+      .map(|a| Self {
+        comment_report: a.0.to_owned(),
+        comment: a.1.to_owned(),
+        post: a.2.to_owned(),
+        community: a.3.to_owned(),
+        creator: a.4.to_owned(),
+        comment_creator: a.5.to_owned(),
+        resolver: a.6.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/comment_view.rs b/lemmy_db/src/views/comment_view.rs
new file mode 100644 (file)
index 0000000..8c75844
--- /dev/null
@@ -0,0 +1,644 @@
+use crate::{
+  aggregates::comment_aggregates::CommentAggregates,
+  functions::hot_rank,
+  fuzzy_search,
+  limit_and_offset,
+  schema::{
+    comment,
+    comment_aggregates,
+    comment_alias_1,
+    comment_like,
+    comment_saved,
+    community,
+    community_follower,
+    community_user_ban,
+    post,
+    user_,
+    user_alias_1,
+  },
+  source::{
+    comment::{Comment, CommentAlias1, CommentSaved},
+    community::{Community, CommunityFollower, CommunitySafe, CommunityUserBan},
+    post::Post,
+    user::{UserAlias1, UserSafe, UserSafeAlias1, User_},
+  },
+  views::ViewToVec,
+  ListingType,
+  MaybeOptional,
+  SortType,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, PartialEq, Serialize, Clone)]
+pub struct CommentView {
+  pub comment: Comment,
+  pub creator: UserSafe,
+  pub recipient: Option<UserSafeAlias1>, // Left joins to comment and user
+  pub post: Post,
+  pub community: CommunitySafe,
+  pub counts: CommentAggregates,
+  pub creator_banned_from_community: bool, // Left Join to CommunityUserBan
+  pub subscribed: bool,                    // Left join to CommunityFollower
+  pub saved: bool,                         // Left join to CommentSaved
+  pub my_vote: Option<i16>,                // Left join to CommentLike
+}
+
+type CommentViewTuple = (
+  Comment,
+  UserSafe,
+  Option<CommentAlias1>,
+  Option<UserSafeAlias1>,
+  Post,
+  CommunitySafe,
+  CommentAggregates,
+  Option<CommunityUserBan>,
+  Option<CommunityFollower>,
+  Option<CommentSaved>,
+  Option<i16>,
+);
+
+impl CommentView {
+  pub fn read(
+    conn: &PgConnection,
+    comment_id: i32,
+    my_user_id: Option<i32>,
+  ) -> Result<Self, Error> {
+    // The left join below will return None in this case
+    let user_id_join = my_user_id.unwrap_or(-1);
+
+    let (
+      comment,
+      creator,
+      _parent_comment,
+      recipient,
+      post,
+      community,
+      counts,
+      creator_banned_from_community,
+      subscribed,
+      saved,
+      my_vote,
+    ) = comment::table
+      .find(comment_id)
+      .inner_join(user_::table)
+      // recipient here
+      .left_join(comment_alias_1::table.on(comment_alias_1::id.nullable().eq(comment::parent_id)))
+      .left_join(user_alias_1::table.on(user_alias_1::id.eq(comment_alias_1::creator_id)))
+      .inner_join(post::table)
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .inner_join(comment_aggregates::table)
+      .left_join(
+        community_user_ban::table.on(
+          community::id
+            .eq(community_user_ban::community_id)
+            .and(community_user_ban::user_id.eq(comment::creator_id)),
+        ),
+      )
+      .left_join(
+        community_follower::table.on(
+          post::community_id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_saved::table.on(
+          comment::id
+            .eq(comment_saved::comment_id)
+            .and(comment_saved::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_like::table.on(
+          comment::id
+            .eq(comment_like::comment_id)
+            .and(comment_like::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        comment::all_columns,
+        User_::safe_columns_tuple(),
+        comment_alias_1::all_columns.nullable(),
+        UserAlias1::safe_columns_tuple().nullable(),
+        post::all_columns,
+        Community::safe_columns_tuple(),
+        comment_aggregates::all_columns,
+        community_user_ban::all_columns.nullable(),
+        community_follower::all_columns.nullable(),
+        comment_saved::all_columns.nullable(),
+        comment_like::score.nullable(),
+      ))
+      .first::<CommentViewTuple>(conn)?;
+
+    Ok(CommentView {
+      comment,
+      recipient,
+      post,
+      creator,
+      community,
+      counts,
+      creator_banned_from_community: creator_banned_from_community.is_some(),
+      subscribed: subscribed.is_some(),
+      saved: saved.is_some(),
+      my_vote,
+    })
+  }
+
+  /// Gets the recipient user id.
+  /// If there is no parent comment, its the post creator
+  pub fn get_recipient_id(&self) -> i32 {
+    match &self.recipient {
+      Some(parent_commenter) => parent_commenter.id,
+      None => self.post.creator_id,
+    }
+  }
+}
+
+pub struct CommentQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  listing_type: ListingType,
+  sort: &'a SortType,
+  community_id: Option<i32>,
+  community_name: Option<String>,
+  post_id: Option<i32>,
+  creator_id: Option<i32>,
+  recipient_id: Option<i32>,
+  my_user_id: Option<i32>,
+  search_term: Option<String>,
+  saved_only: bool,
+  unread_only: bool,
+  page: Option<i64>,
+  limit: Option<i64>,
+}
+
+impl<'a> CommentQueryBuilder<'a> {
+  pub fn create(conn: &'a PgConnection) -> Self {
+    CommentQueryBuilder {
+      conn,
+      listing_type: ListingType::All,
+      sort: &SortType::New,
+      community_id: None,
+      community_name: None,
+      post_id: None,
+      creator_id: None,
+      recipient_id: None,
+      my_user_id: None,
+      search_term: None,
+      saved_only: false,
+      unread_only: false,
+      page: None,
+      limit: None,
+    }
+  }
+
+  pub fn listing_type(mut self, listing_type: ListingType) -> Self {
+    self.listing_type = listing_type;
+    self
+  }
+
+  pub fn sort(mut self, sort: &'a SortType) -> Self {
+    self.sort = sort;
+    self
+  }
+
+  pub fn post_id<T: MaybeOptional<i32>>(mut self, post_id: T) -> Self {
+    self.post_id = post_id.get_optional();
+    self
+  }
+
+  pub fn creator_id<T: MaybeOptional<i32>>(mut self, creator_id: T) -> Self {
+    self.creator_id = creator_id.get_optional();
+    self
+  }
+
+  pub fn recipient_id<T: MaybeOptional<i32>>(mut self, recipient_id: T) -> Self {
+    self.recipient_id = recipient_id.get_optional();
+    self
+  }
+
+  pub fn community_id<T: MaybeOptional<i32>>(mut self, community_id: T) -> Self {
+    self.community_id = community_id.get_optional();
+    self
+  }
+
+  pub fn my_user_id<T: MaybeOptional<i32>>(mut self, my_user_id: T) -> Self {
+    self.my_user_id = my_user_id.get_optional();
+    self
+  }
+
+  pub fn community_name<T: MaybeOptional<String>>(mut self, community_name: T) -> Self {
+    self.community_name = community_name.get_optional();
+    self
+  }
+
+  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
+    self.search_term = search_term.get_optional();
+    self
+  }
+
+  pub fn saved_only(mut self, saved_only: bool) -> Self {
+    self.saved_only = saved_only;
+    self
+  }
+
+  pub fn unread_only(mut self, unread_only: bool) -> Self {
+    self.unread_only = unread_only;
+    self
+  }
+
+  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
+    self.page = page.get_optional();
+    self
+  }
+
+  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
+    self.limit = limit.get_optional();
+    self
+  }
+
+  pub fn list(self) -> Result<Vec<CommentView>, Error> {
+    use diesel::dsl::*;
+
+    // The left join below will return None in this case
+    let user_id_join = self.my_user_id.unwrap_or(-1);
+
+    let mut query = comment::table
+      .inner_join(user_::table)
+      // recipient here
+      .left_join(comment_alias_1::table.on(comment_alias_1::id.nullable().eq(comment::parent_id)))
+      .left_join(user_alias_1::table.on(user_alias_1::id.eq(comment_alias_1::creator_id)))
+      .inner_join(post::table)
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .inner_join(comment_aggregates::table)
+      .left_join(
+        community_user_ban::table.on(
+          community::id
+            .eq(community_user_ban::community_id)
+            .and(community_user_ban::user_id.eq(comment::creator_id)),
+        ),
+      )
+      .left_join(
+        community_follower::table.on(
+          post::community_id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_saved::table.on(
+          comment::id
+            .eq(comment_saved::comment_id)
+            .and(comment_saved::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_like::table.on(
+          comment::id
+            .eq(comment_like::comment_id)
+            .and(comment_like::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        comment::all_columns,
+        User_::safe_columns_tuple(),
+        comment_alias_1::all_columns.nullable(),
+        UserAlias1::safe_columns_tuple().nullable(),
+        post::all_columns,
+        Community::safe_columns_tuple(),
+        comment_aggregates::all_columns,
+        community_user_ban::all_columns.nullable(),
+        community_follower::all_columns.nullable(),
+        comment_saved::all_columns.nullable(),
+        comment_like::score.nullable(),
+      ))
+      .into_boxed();
+
+    // The replies
+    if let Some(recipient_id) = self.recipient_id {
+      query = query
+        // TODO needs lots of testing
+        .filter(user_alias_1::id.eq(recipient_id))
+        .filter(comment::deleted.eq(false))
+        .filter(comment::removed.eq(false));
+    }
+
+    if self.unread_only {
+      query = query.filter(comment::read.eq(false));
+    }
+
+    if let Some(creator_id) = self.creator_id {
+      query = query.filter(comment::creator_id.eq(creator_id));
+    };
+
+    if let Some(community_id) = self.community_id {
+      query = query.filter(post::community_id.eq(community_id));
+    }
+
+    if let Some(community_name) = self.community_name {
+      query = query
+        .filter(community::name.eq(community_name))
+        .filter(comment::local.eq(true));
+    }
+
+    if let Some(post_id) = self.post_id {
+      query = query.filter(comment::post_id.eq(post_id));
+    };
+
+    if let Some(search_term) = self.search_term {
+      query = query.filter(comment::content.ilike(fuzzy_search(&search_term)));
+    };
+
+    query = match self.listing_type {
+      // ListingType::Subscribed => query.filter(community_follower::subscribed.eq(true)),
+      ListingType::Subscribed => query.filter(community_follower::user_id.is_not_null()), // TODO could be this: and(community_follower::user_id.eq(user_id_join)),
+      ListingType::Local => query.filter(community::local.eq(true)),
+      _ => query,
+    };
+
+    if self.saved_only {
+      query = query.filter(comment_saved::id.is_not_null());
+    }
+
+    query = match self.sort {
+      SortType::Hot | SortType::Active => query
+        .order_by(hot_rank(comment_aggregates::score, comment::published).desc())
+        .then_order_by(comment::published.desc()),
+      SortType::New => query.order_by(comment::published.desc()),
+      SortType::TopAll => query.order_by(comment_aggregates::score.desc()),
+      SortType::TopYear => query
+        .filter(comment::published.gt(now - 1.years()))
+        .order_by(comment_aggregates::score.desc()),
+      SortType::TopMonth => query
+        .filter(comment::published.gt(now - 1.months()))
+        .order_by(comment_aggregates::score.desc()),
+      SortType::TopWeek => query
+        .filter(comment::published.gt(now - 1.weeks()))
+        .order_by(comment_aggregates::score.desc()),
+      SortType::TopDay => query
+        .filter(comment::published.gt(now - 1.days()))
+        .order_by(comment_aggregates::score.desc()),
+    };
+
+    let (limit, offset) = limit_and_offset(self.page, self.limit);
+
+    // Note: deleted and removed comments are done on the front side
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .load::<CommentViewTuple>(self.conn)?;
+
+    Ok(CommentView::to_vec(res))
+  }
+}
+
+impl ViewToVec for CommentView {
+  type DbTuple = CommentViewTuple;
+  fn to_vec(posts: Vec<Self::DbTuple>) -> Vec<Self> {
+    posts
+      .iter()
+      .map(|a| Self {
+        comment: a.0.to_owned(),
+        creator: a.1.to_owned(),
+        recipient: a.3.to_owned(),
+        post: a.4.to_owned(),
+        community: a.5.to_owned(),
+        counts: a.6.to_owned(),
+        creator_banned_from_community: a.7.is_some(),
+        subscribed: a.8.is_some(),
+        saved: a.9.is_some(),
+        my_vote: a.10,
+      })
+      .collect::<Vec<Self>>()
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::{
+    source::{comment::*, community::*, post::*, user::*},
+    tests::establish_unpooled_connection,
+    views::comment_view::*,
+    Crud,
+    Likeable,
+    *,
+  };
+
+  #[test]
+  fn test_crud() {
+    let conn = establish_unpooled_connection();
+
+    let new_user = UserForm {
+      name: "timmy".into(),
+      preferred_username: None,
+      password_encrypted: "nope".into(),
+      email: None,
+      matrix_user_id: None,
+      avatar: None,
+      banner: None,
+      admin: false,
+      banned: Some(false),
+      published: None,
+      updated: None,
+      show_nsfw: false,
+      theme: "browser".into(),
+      default_sort_type: SortType::Hot as i16,
+      default_listing_type: ListingType::Subscribed as i16,
+      lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
+      actor_id: None,
+      bio: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+    };
+
+    let inserted_user = User_::create(&conn, &new_user).unwrap();
+
+    let new_community = CommunityForm {
+      name: "test community 5".to_string(),
+      title: "nada".to_owned(),
+      description: None,
+      category_id: 1,
+      creator_id: inserted_user.id,
+      removed: None,
+      deleted: None,
+      updated: None,
+      nsfw: false,
+      actor_id: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+      published: None,
+      icon: None,
+      banner: None,
+    };
+
+    let inserted_community = Community::create(&conn, &new_community).unwrap();
+
+    let new_post = PostForm {
+      name: "A test post 2".into(),
+      creator_id: inserted_user.id,
+      url: None,
+      body: None,
+      community_id: inserted_community.id,
+      removed: None,
+      deleted: None,
+      locked: None,
+      stickied: None,
+      updated: None,
+      nsfw: false,
+      embed_title: None,
+      embed_description: None,
+      embed_html: None,
+      thumbnail_url: None,
+      ap_id: None,
+      local: true,
+      published: None,
+    };
+
+    let inserted_post = Post::create(&conn, &new_post).unwrap();
+
+    let comment_form = CommentForm {
+      content: "A test comment 32".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      parent_id: None,
+      removed: None,
+      deleted: None,
+      read: None,
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
+
+    let comment_like_form = CommentLikeForm {
+      comment_id: inserted_comment.id,
+      post_id: inserted_post.id,
+      user_id: inserted_user.id,
+      score: 1,
+    };
+
+    let _inserted_comment_like = CommentLike::like(&conn, &comment_like_form).unwrap();
+
+    let agg = CommentAggregates::read(&conn, inserted_comment.id).unwrap();
+
+    let expected_comment_view_no_user = CommentView {
+      creator_banned_from_community: false,
+      my_vote: None,
+      subscribed: false,
+      saved: false,
+      comment: Comment {
+        id: inserted_comment.id,
+        content: "A test comment 32".into(),
+        creator_id: inserted_user.id,
+        post_id: inserted_post.id,
+        parent_id: None,
+        removed: false,
+        deleted: false,
+        read: false,
+        published: inserted_comment.published,
+        ap_id: inserted_comment.ap_id,
+        updated: None,
+        local: true,
+      },
+      creator: UserSafe {
+        id: inserted_user.id,
+        name: "timmy".into(),
+        preferred_username: None,
+        published: inserted_user.published,
+        avatar: None,
+        actor_id: inserted_user.actor_id.to_owned(),
+        local: true,
+        banned: false,
+        deleted: false,
+        bio: None,
+        banner: None,
+        admin: false,
+        updated: None,
+        matrix_user_id: None,
+      },
+      recipient: None,
+      post: Post {
+        id: inserted_post.id,
+        name: inserted_post.name.to_owned(),
+        creator_id: inserted_user.id,
+        url: None,
+        body: None,
+        published: inserted_post.published,
+        updated: None,
+        community_id: inserted_community.id,
+        removed: false,
+        deleted: false,
+        locked: false,
+        stickied: false,
+        nsfw: false,
+        embed_title: None,
+        embed_description: None,
+        embed_html: None,
+        thumbnail_url: None,
+        ap_id: inserted_post.ap_id.to_owned(),
+        local: true,
+      },
+      community: CommunitySafe {
+        id: inserted_community.id,
+        name: "test community 5".to_string(),
+        icon: None,
+        removed: false,
+        deleted: false,
+        nsfw: false,
+        actor_id: inserted_community.actor_id.to_owned(),
+        local: true,
+        title: "nada".to_owned(),
+        description: None,
+        creator_id: inserted_user.id,
+        category_id: 1,
+        updated: None,
+        banner: None,
+        published: inserted_community.published,
+      },
+      counts: CommentAggregates {
+        id: agg.id,
+        comment_id: inserted_comment.id,
+        score: 1,
+        upvotes: 1,
+        downvotes: 0,
+      },
+    };
+
+    let mut expected_comment_view_with_user = expected_comment_view_no_user.to_owned();
+    expected_comment_view_with_user.my_vote = Some(1);
+
+    let read_comment_views_no_user = CommentQueryBuilder::create(&conn)
+      .post_id(inserted_post.id)
+      .list()
+      .unwrap();
+
+    let read_comment_views_with_user = CommentQueryBuilder::create(&conn)
+      .post_id(inserted_post.id)
+      .my_user_id(inserted_user.id)
+      .list()
+      .unwrap();
+
+    let like_removed = CommentLike::remove(&conn, inserted_user.id, inserted_comment.id).unwrap();
+    let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
+    Post::delete(&conn, inserted_post.id).unwrap();
+    Community::delete(&conn, inserted_community.id).unwrap();
+    User_::delete(&conn, inserted_user.id).unwrap();
+
+    assert_eq!(expected_comment_view_no_user, read_comment_views_no_user[0]);
+    assert_eq!(
+      expected_comment_view_with_user,
+      read_comment_views_with_user[0]
+    );
+    assert_eq!(1, num_deleted);
+    assert_eq!(1, like_removed);
+  }
+}
diff --git a/lemmy_db/src/views/community/community_follower_view.rs b/lemmy_db/src/views/community/community_follower_view.rs
new file mode 100644 (file)
index 0000000..7de9cc3
--- /dev/null
@@ -0,0 +1,58 @@
+use crate::{
+  schema::{community, community_follower, user_},
+  source::{
+    community::{Community, CommunitySafe},
+    user::{UserSafe, User_},
+  },
+  views::ViewToVec,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct CommunityFollowerView {
+  pub community: CommunitySafe,
+  pub follower: UserSafe,
+}
+
+type CommunityFollowerViewTuple = (CommunitySafe, UserSafe);
+
+impl CommunityFollowerView {
+  pub fn for_community(conn: &PgConnection, community_id: i32) -> Result<Vec<Self>, Error> {
+    let res = community_follower::table
+      .inner_join(community::table)
+      .inner_join(user_::table)
+      .select((Community::safe_columns_tuple(), User_::safe_columns_tuple()))
+      .filter(community_follower::community_id.eq(community_id))
+      .order_by(community_follower::published)
+      .load::<CommunityFollowerViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+
+  pub fn for_user(conn: &PgConnection, user_id: i32) -> Result<Vec<Self>, Error> {
+    let res = community_follower::table
+      .inner_join(community::table)
+      .inner_join(user_::table)
+      .select((Community::safe_columns_tuple(), User_::safe_columns_tuple()))
+      .filter(community_follower::user_id.eq(user_id))
+      .order_by(community_follower::published)
+      .load::<CommunityFollowerViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+}
+
+impl ViewToVec for CommunityFollowerView {
+  type DbTuple = CommunityFollowerViewTuple;
+  fn to_vec(users: Vec<Self::DbTuple>) -> Vec<Self> {
+    users
+      .iter()
+      .map(|a| Self {
+        community: a.0.to_owned(),
+        follower: a.1.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/community/community_moderator_view.rs b/lemmy_db/src/views/community/community_moderator_view.rs
new file mode 100644 (file)
index 0000000..751e462
--- /dev/null
@@ -0,0 +1,58 @@
+use crate::{
+  schema::{community, community_moderator, user_},
+  source::{
+    community::{Community, CommunitySafe},
+    user::{UserSafe, User_},
+  },
+  views::ViewToVec,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct CommunityModeratorView {
+  pub community: CommunitySafe,
+  pub moderator: UserSafe,
+}
+
+type CommunityModeratorViewTuple = (CommunitySafe, UserSafe);
+
+impl CommunityModeratorView {
+  pub fn for_community(conn: &PgConnection, community_id: i32) -> Result<Vec<Self>, Error> {
+    let res = community_moderator::table
+      .inner_join(community::table)
+      .inner_join(user_::table)
+      .select((Community::safe_columns_tuple(), User_::safe_columns_tuple()))
+      .filter(community_moderator::community_id.eq(community_id))
+      .order_by(community_moderator::published)
+      .load::<CommunityModeratorViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+
+  pub fn for_user(conn: &PgConnection, user_id: i32) -> Result<Vec<Self>, Error> {
+    let res = community_moderator::table
+      .inner_join(community::table)
+      .inner_join(user_::table)
+      .select((Community::safe_columns_tuple(), User_::safe_columns_tuple()))
+      .filter(community_moderator::user_id.eq(user_id))
+      .order_by(community_moderator::published)
+      .load::<CommunityModeratorViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+}
+
+impl ViewToVec for CommunityModeratorView {
+  type DbTuple = CommunityModeratorViewTuple;
+  fn to_vec(community_moderators: Vec<Self::DbTuple>) -> Vec<Self> {
+    community_moderators
+      .iter()
+      .map(|a| Self {
+        community: a.0.to_owned(),
+        moderator: a.1.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/community/community_user_ban_view.rs b/lemmy_db/src/views/community/community_user_ban_view.rs
new file mode 100644 (file)
index 0000000..3358f01
--- /dev/null
@@ -0,0 +1,35 @@
+use crate::{
+  schema::{community, community_user_ban, user_},
+  source::{
+    community::{Community, CommunitySafe},
+    user::{UserSafe, User_},
+  },
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct CommunityUserBanView {
+  pub community: CommunitySafe,
+  pub user: UserSafe,
+}
+
+impl CommunityUserBanView {
+  pub fn get(
+    conn: &PgConnection,
+    from_user_id: i32,
+    from_community_id: i32,
+  ) -> Result<Self, Error> {
+    let (community, user) = community_user_ban::table
+      .inner_join(community::table)
+      .inner_join(user_::table)
+      .select((Community::safe_columns_tuple(), User_::safe_columns_tuple()))
+      .filter(community_user_ban::community_id.eq(from_community_id))
+      .filter(community_user_ban::user_id.eq(from_user_id))
+      .order_by(community_user_ban::published)
+      .first::<(CommunitySafe, UserSafe)>(conn)?;
+
+    Ok(CommunityUserBanView { community, user })
+  }
+}
diff --git a/lemmy_db/src/views/community/community_view.rs b/lemmy_db/src/views/community/community_view.rs
new file mode 100644 (file)
index 0000000..fcc707c
--- /dev/null
@@ -0,0 +1,204 @@
+use crate::{
+  aggregates::community_aggregates::CommunityAggregates,
+  functions::hot_rank,
+  fuzzy_search,
+  limit_and_offset,
+  schema::{category, community, community_aggregates, community_follower, user_},
+  source::{
+    category::Category,
+    community::{Community, CommunityFollower, CommunitySafe},
+    user::{UserSafe, User_},
+  },
+  views::ViewToVec,
+  MaybeOptional,
+  SortType,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct CommunityView {
+  pub community: CommunitySafe,
+  pub creator: UserSafe,
+  pub category: Category,
+  pub subscribed: bool,
+  pub counts: CommunityAggregates,
+}
+
+type CommunityViewTuple = (
+  CommunitySafe,
+  UserSafe,
+  Category,
+  CommunityAggregates,
+  Option<CommunityFollower>,
+);
+
+impl CommunityView {
+  pub fn read(
+    conn: &PgConnection,
+    community_id: i32,
+    my_user_id: Option<i32>,
+  ) -> Result<Self, Error> {
+    // The left join below will return None in this case
+    let user_id_join = my_user_id.unwrap_or(-1);
+
+    let (community, creator, category, counts, follower) = community::table
+      .find(community_id)
+      .inner_join(user_::table)
+      .inner_join(category::table)
+      .inner_join(community_aggregates::table)
+      .left_join(
+        community_follower::table.on(
+          community::id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        Community::safe_columns_tuple(),
+        User_::safe_columns_tuple(),
+        category::all_columns,
+        community_aggregates::all_columns,
+        community_follower::all_columns.nullable(),
+      ))
+      .first::<CommunityViewTuple>(conn)?;
+
+    Ok(CommunityView {
+      community,
+      creator,
+      category,
+      subscribed: follower.is_some(),
+      counts,
+    })
+  }
+}
+
+pub struct CommunityQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  sort: &'a SortType,
+  my_user_id: Option<i32>,
+  show_nsfw: bool,
+  search_term: Option<String>,
+  page: Option<i64>,
+  limit: Option<i64>,
+}
+
+impl<'a> CommunityQueryBuilder<'a> {
+  pub fn create(conn: &'a PgConnection) -> Self {
+    CommunityQueryBuilder {
+      conn,
+      my_user_id: None,
+      sort: &SortType::Hot,
+      show_nsfw: true,
+      search_term: None,
+      page: None,
+      limit: None,
+    }
+  }
+
+  pub fn sort(mut self, sort: &'a SortType) -> Self {
+    self.sort = sort;
+    self
+  }
+
+  pub fn show_nsfw(mut self, show_nsfw: bool) -> Self {
+    self.show_nsfw = show_nsfw;
+    self
+  }
+
+  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
+    self.search_term = search_term.get_optional();
+    self
+  }
+
+  pub fn my_user_id<T: MaybeOptional<i32>>(mut self, my_user_id: T) -> Self {
+    self.my_user_id = my_user_id.get_optional();
+    self
+  }
+
+  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
+    self.page = page.get_optional();
+    self
+  }
+
+  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
+    self.limit = limit.get_optional();
+    self
+  }
+
+  pub fn list(self) -> Result<Vec<CommunityView>, Error> {
+    // The left join below will return None in this case
+    let user_id_join = self.my_user_id.unwrap_or(-1);
+
+    let mut query = community::table
+      .inner_join(user_::table)
+      .inner_join(category::table)
+      .inner_join(community_aggregates::table)
+      .left_join(
+        community_follower::table.on(
+          community::id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        Community::safe_columns_tuple(),
+        User_::safe_columns_tuple(),
+        category::all_columns,
+        community_aggregates::all_columns,
+        community_follower::all_columns.nullable(),
+      ))
+      .into_boxed();
+
+    if let Some(search_term) = self.search_term {
+      let searcher = fuzzy_search(&search_term);
+      query = query
+        .filter(community::name.ilike(searcher.to_owned()))
+        .or_filter(community::title.ilike(searcher.to_owned()))
+        .or_filter(community::description.ilike(searcher));
+    };
+
+    match self.sort {
+      SortType::New => query = query.order_by(community::published.desc()),
+      SortType::TopAll => query = query.order_by(community_aggregates::subscribers.desc()),
+      // Covers all other sorts, including hot
+      _ => {
+        query = query
+          // TODO do custom sql function for hot_rank, make sure this works
+          .order_by(hot_rank(community_aggregates::subscribers, community::published).desc())
+          .then_order_by(community_aggregates::subscribers.desc())
+      }
+    };
+
+    if !self.show_nsfw {
+      query = query.filter(community::nsfw.eq(false));
+    };
+
+    let (limit, offset) = limit_and_offset(self.page, self.limit);
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .filter(community::removed.eq(false))
+      .filter(community::deleted.eq(false))
+      .load::<CommunityViewTuple>(self.conn)?;
+
+    Ok(CommunityView::to_vec(res))
+  }
+}
+
+impl ViewToVec for CommunityView {
+  type DbTuple = CommunityViewTuple;
+  fn to_vec(communities: Vec<Self::DbTuple>) -> Vec<Self> {
+    communities
+      .iter()
+      .map(|a| Self {
+        community: a.0.to_owned(),
+        creator: a.1.to_owned(),
+        category: a.2.to_owned(),
+        counts: a.3.to_owned(),
+        subscribed: a.4.is_some(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/community/mod.rs b/lemmy_db/src/views/community/mod.rs
new file mode 100644 (file)
index 0000000..491dde7
--- /dev/null
@@ -0,0 +1,4 @@
+pub mod community_follower_view;
+pub mod community_moderator_view;
+pub mod community_user_ban_view;
+pub mod community_view;
diff --git a/lemmy_db/src/views/mod.rs b/lemmy_db/src/views/mod.rs
new file mode 100644 (file)
index 0000000..3cac0bd
--- /dev/null
@@ -0,0 +1,17 @@
+pub mod comment_report_view;
+pub mod comment_view;
+pub mod community;
+pub mod moderator;
+pub mod post_report_view;
+pub mod post_view;
+pub mod private_message_view;
+pub mod site_view;
+pub mod user_mention_view;
+pub mod user_view;
+
+pub(crate) trait ViewToVec {
+  type DbTuple;
+  fn to_vec(tuple: Vec<Self::DbTuple>) -> Vec<Self>
+  where
+    Self: Sized;
+}
diff --git a/lemmy_db/src/views/moderator/mod.rs b/lemmy_db/src/views/moderator/mod.rs
new file mode 100644 (file)
index 0000000..827dd14
--- /dev/null
@@ -0,0 +1,9 @@
+pub mod mod_add_community_view;
+pub mod mod_add_view;
+pub mod mod_ban_from_community_view;
+pub mod mod_ban_view;
+pub mod mod_lock_post_view;
+pub mod mod_remove_comment_view;
+pub mod mod_remove_community_view;
+pub mod mod_remove_post_view;
+pub mod mod_sticky_post_view;
diff --git a/lemmy_db/src/views/moderator/mod_add_community_view.rs b/lemmy_db/src/views/moderator/mod_add_community_view.rs
new file mode 100644 (file)
index 0000000..402c5fe
--- /dev/null
@@ -0,0 +1,78 @@
+use crate::{
+  limit_and_offset,
+  schema::{community, mod_add_community, user_, user_alias_1},
+  source::{
+    community::{Community, CommunitySafe},
+    moderator::ModAddCommunity,
+    user::{UserAlias1, UserSafe, UserSafeAlias1, User_},
+  },
+  views::ViewToVec,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct ModAddCommunityView {
+  pub mod_add_community: ModAddCommunity,
+  pub moderator: UserSafe,
+  pub community: CommunitySafe,
+  pub modded_user: UserSafeAlias1,
+}
+
+type ModAddCommunityViewTuple = (ModAddCommunity, UserSafe, CommunitySafe, UserSafeAlias1);
+
+impl ModAddCommunityView {
+  pub fn list(
+    conn: &PgConnection,
+    community_id: Option<i32>,
+    mod_user_id: Option<i32>,
+    page: Option<i64>,
+    limit: Option<i64>,
+  ) -> Result<Vec<Self>, Error> {
+    let mut query = mod_add_community::table
+      .inner_join(user_::table.on(mod_add_community::mod_user_id.eq(user_::id)))
+      .inner_join(community::table)
+      .inner_join(user_alias_1::table.on(mod_add_community::other_user_id.eq(user_::id)))
+      .select((
+        mod_add_community::all_columns,
+        User_::safe_columns_tuple(),
+        Community::safe_columns_tuple(),
+        UserAlias1::safe_columns_tuple(),
+      ))
+      .into_boxed();
+
+    if let Some(mod_user_id) = mod_user_id {
+      query = query.filter(mod_add_community::mod_user_id.eq(mod_user_id));
+    };
+
+    if let Some(community_id) = community_id {
+      query = query.filter(mod_add_community::community_id.eq(community_id));
+    };
+
+    let (limit, offset) = limit_and_offset(page, limit);
+
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .order_by(mod_add_community::when_.desc())
+      .load::<ModAddCommunityViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+}
+
+impl ViewToVec for ModAddCommunityView {
+  type DbTuple = ModAddCommunityViewTuple;
+  fn to_vec(mrp: Vec<Self::DbTuple>) -> Vec<Self> {
+    mrp
+      .iter()
+      .map(|a| Self {
+        mod_add_community: a.0.to_owned(),
+        moderator: a.1.to_owned(),
+        community: a.2.to_owned(),
+        modded_user: a.3.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/moderator/mod_add_view.rs b/lemmy_db/src/views/moderator/mod_add_view.rs
new file mode 100644 (file)
index 0000000..fc1993d
--- /dev/null
@@ -0,0 +1,68 @@
+use crate::{
+  limit_and_offset,
+  schema::{mod_add, user_, user_alias_1},
+  source::{
+    moderator::ModAdd,
+    user::{UserAlias1, UserSafe, UserSafeAlias1, User_},
+  },
+  views::ViewToVec,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct ModAddView {
+  pub mod_add: ModAdd,
+  pub moderator: UserSafe,
+  pub modded_user: UserSafeAlias1,
+}
+
+type ModAddViewTuple = (ModAdd, UserSafe, UserSafeAlias1);
+
+impl ModAddView {
+  pub fn list(
+    conn: &PgConnection,
+    mod_user_id: Option<i32>,
+    page: Option<i64>,
+    limit: Option<i64>,
+  ) -> Result<Vec<Self>, Error> {
+    let mut query = mod_add::table
+      .inner_join(user_::table.on(mod_add::mod_user_id.eq(user_::id)))
+      .inner_join(user_alias_1::table.on(mod_add::other_user_id.eq(user_::id)))
+      .select((
+        mod_add::all_columns,
+        User_::safe_columns_tuple(),
+        UserAlias1::safe_columns_tuple(),
+      ))
+      .into_boxed();
+
+    if let Some(mod_user_id) = mod_user_id {
+      query = query.filter(mod_add::mod_user_id.eq(mod_user_id));
+    };
+
+    let (limit, offset) = limit_and_offset(page, limit);
+
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .order_by(mod_add::when_.desc())
+      .load::<ModAddViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+}
+
+impl ViewToVec for ModAddView {
+  type DbTuple = ModAddViewTuple;
+  fn to_vec(mrp: Vec<Self::DbTuple>) -> Vec<Self> {
+    mrp
+      .iter()
+      .map(|a| Self {
+        mod_add: a.0.to_owned(),
+        moderator: a.1.to_owned(),
+        modded_user: a.2.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/moderator/mod_ban_from_community_view.rs b/lemmy_db/src/views/moderator/mod_ban_from_community_view.rs
new file mode 100644 (file)
index 0000000..6ad232e
--- /dev/null
@@ -0,0 +1,78 @@
+use crate::{
+  limit_and_offset,
+  schema::{community, mod_ban_from_community, user_, user_alias_1},
+  source::{
+    community::{Community, CommunitySafe},
+    moderator::ModBanFromCommunity,
+    user::{UserAlias1, UserSafe, UserSafeAlias1, User_},
+  },
+  views::ViewToVec,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct ModBanFromCommunityView {
+  pub mod_ban_from_community: ModBanFromCommunity,
+  pub moderator: UserSafe,
+  pub community: CommunitySafe,
+  pub banned_user: UserSafeAlias1,
+}
+
+type ModBanFromCommunityViewTuple = (ModBanFromCommunity, UserSafe, CommunitySafe, UserSafeAlias1);
+
+impl ModBanFromCommunityView {
+  pub fn list(
+    conn: &PgConnection,
+    community_id: Option<i32>,
+    mod_user_id: Option<i32>,
+    page: Option<i64>,
+    limit: Option<i64>,
+  ) -> Result<Vec<Self>, Error> {
+    let mut query = mod_ban_from_community::table
+      .inner_join(user_::table.on(mod_ban_from_community::mod_user_id.eq(user_::id)))
+      .inner_join(community::table)
+      .inner_join(user_alias_1::table.on(mod_ban_from_community::other_user_id.eq(user_::id)))
+      .select((
+        mod_ban_from_community::all_columns,
+        User_::safe_columns_tuple(),
+        Community::safe_columns_tuple(),
+        UserAlias1::safe_columns_tuple(),
+      ))
+      .into_boxed();
+
+    if let Some(mod_user_id) = mod_user_id {
+      query = query.filter(mod_ban_from_community::mod_user_id.eq(mod_user_id));
+    };
+
+    if let Some(community_id) = community_id {
+      query = query.filter(mod_ban_from_community::community_id.eq(community_id));
+    };
+
+    let (limit, offset) = limit_and_offset(page, limit);
+
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .order_by(mod_ban_from_community::when_.desc())
+      .load::<ModBanFromCommunityViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+}
+
+impl ViewToVec for ModBanFromCommunityView {
+  type DbTuple = ModBanFromCommunityViewTuple;
+  fn to_vec(mrp: Vec<Self::DbTuple>) -> Vec<Self> {
+    mrp
+      .iter()
+      .map(|a| Self {
+        mod_ban_from_community: a.0.to_owned(),
+        moderator: a.1.to_owned(),
+        community: a.2.to_owned(),
+        banned_user: a.3.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/moderator/mod_ban_view.rs b/lemmy_db/src/views/moderator/mod_ban_view.rs
new file mode 100644 (file)
index 0000000..28214d2
--- /dev/null
@@ -0,0 +1,68 @@
+use crate::{
+  limit_and_offset,
+  schema::{mod_ban, user_, user_alias_1},
+  source::{
+    moderator::ModBan,
+    user::{UserAlias1, UserSafe, UserSafeAlias1, User_},
+  },
+  views::ViewToVec,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct ModBanView {
+  pub mod_ban: ModBan,
+  pub moderator: UserSafe,
+  pub banned_user: UserSafeAlias1,
+}
+
+type ModBanViewTuple = (ModBan, UserSafe, UserSafeAlias1);
+
+impl ModBanView {
+  pub fn list(
+    conn: &PgConnection,
+    mod_user_id: Option<i32>,
+    page: Option<i64>,
+    limit: Option<i64>,
+  ) -> Result<Vec<Self>, Error> {
+    let mut query = mod_ban::table
+      .inner_join(user_::table.on(mod_ban::mod_user_id.eq(user_::id)))
+      .inner_join(user_alias_1::table.on(mod_ban::other_user_id.eq(user_::id)))
+      .select((
+        mod_ban::all_columns,
+        User_::safe_columns_tuple(),
+        UserAlias1::safe_columns_tuple(),
+      ))
+      .into_boxed();
+
+    if let Some(mod_user_id) = mod_user_id {
+      query = query.filter(mod_ban::mod_user_id.eq(mod_user_id));
+    };
+
+    let (limit, offset) = limit_and_offset(page, limit);
+
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .order_by(mod_ban::when_.desc())
+      .load::<ModBanViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+}
+
+impl ViewToVec for ModBanView {
+  type DbTuple = ModBanViewTuple;
+  fn to_vec(mrp: Vec<Self::DbTuple>) -> Vec<Self> {
+    mrp
+      .iter()
+      .map(|a| Self {
+        mod_ban: a.0.to_owned(),
+        moderator: a.1.to_owned(),
+        banned_user: a.2.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/moderator/mod_lock_post_view.rs b/lemmy_db/src/views/moderator/mod_lock_post_view.rs
new file mode 100644 (file)
index 0000000..8182b54
--- /dev/null
@@ -0,0 +1,79 @@
+use crate::{
+  limit_and_offset,
+  schema::{community, mod_lock_post, post, user_},
+  source::{
+    community::{Community, CommunitySafe},
+    moderator::ModLockPost,
+    post::Post,
+    user::{UserSafe, User_},
+  },
+  views::ViewToVec,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct ModLockPostView {
+  pub mod_lock_post: ModLockPost,
+  pub moderator: UserSafe,
+  pub post: Post,
+  pub community: CommunitySafe,
+}
+
+type ModLockPostViewTuple = (ModLockPost, UserSafe, Post, CommunitySafe);
+
+impl ModLockPostView {
+  pub fn list(
+    conn: &PgConnection,
+    community_id: Option<i32>,
+    mod_user_id: Option<i32>,
+    page: Option<i64>,
+    limit: Option<i64>,
+  ) -> Result<Vec<Self>, Error> {
+    let mut query = mod_lock_post::table
+      .inner_join(user_::table)
+      .inner_join(post::table)
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .select((
+        mod_lock_post::all_columns,
+        User_::safe_columns_tuple(),
+        post::all_columns,
+        Community::safe_columns_tuple(),
+      ))
+      .into_boxed();
+
+    if let Some(community_id) = community_id {
+      query = query.filter(post::community_id.eq(community_id));
+    };
+
+    if let Some(mod_user_id) = mod_user_id {
+      query = query.filter(mod_lock_post::mod_user_id.eq(mod_user_id));
+    };
+
+    let (limit, offset) = limit_and_offset(page, limit);
+
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .order_by(mod_lock_post::when_.desc())
+      .load::<ModLockPostViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+}
+
+impl ViewToVec for ModLockPostView {
+  type DbTuple = ModLockPostViewTuple;
+  fn to_vec(mrp: Vec<Self::DbTuple>) -> Vec<Self> {
+    mrp
+      .iter()
+      .map(|a| Self {
+        mod_lock_post: a.0.to_owned(),
+        moderator: a.1.to_owned(),
+        post: a.2.to_owned(),
+        community: a.3.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/moderator/mod_remove_comment_view.rs b/lemmy_db/src/views/moderator/mod_remove_comment_view.rs
new file mode 100644 (file)
index 0000000..fb4b777
--- /dev/null
@@ -0,0 +1,95 @@
+use crate::{
+  limit_and_offset,
+  schema::{comment, community, mod_remove_comment, post, user_, user_alias_1},
+  source::{
+    comment::Comment,
+    community::{Community, CommunitySafe},
+    moderator::ModRemoveComment,
+    post::Post,
+    user::{UserAlias1, UserSafe, UserSafeAlias1, User_},
+  },
+  views::ViewToVec,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct ModRemoveCommentView {
+  pub mod_remove_comment: ModRemoveComment,
+  pub moderator: UserSafe,
+  pub comment: Comment,
+  pub commenter: UserSafeAlias1,
+  pub post: Post,
+  pub community: CommunitySafe,
+}
+
+type ModRemoveCommentViewTuple = (
+  ModRemoveComment,
+  UserSafe,
+  Comment,
+  UserSafeAlias1,
+  Post,
+  CommunitySafe,
+);
+
+impl ModRemoveCommentView {
+  pub fn list(
+    conn: &PgConnection,
+    community_id: Option<i32>,
+    mod_user_id: Option<i32>,
+    page: Option<i64>,
+    limit: Option<i64>,
+  ) -> Result<Vec<Self>, Error> {
+    let mut query = mod_remove_comment::table
+      .inner_join(user_::table)
+      .inner_join(comment::table)
+      .inner_join(user_alias_1::table.on(comment::creator_id.eq(user_alias_1::id)))
+      .inner_join(post::table.on(comment::post_id.eq(post::id)))
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .select((
+        mod_remove_comment::all_columns,
+        User_::safe_columns_tuple(),
+        comment::all_columns,
+        UserAlias1::safe_columns_tuple(),
+        post::all_columns,
+        Community::safe_columns_tuple(),
+      ))
+      .into_boxed();
+
+    if let Some(community_id) = community_id {
+      query = query.filter(post::community_id.eq(community_id));
+    };
+
+    if let Some(mod_user_id) = mod_user_id {
+      query = query.filter(mod_remove_comment::mod_user_id.eq(mod_user_id));
+    };
+
+    let (limit, offset) = limit_and_offset(page, limit);
+
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .order_by(mod_remove_comment::when_.desc())
+      .load::<ModRemoveCommentViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+}
+
+impl ViewToVec for ModRemoveCommentView {
+  type DbTuple = ModRemoveCommentViewTuple;
+  fn to_vec(mrp: Vec<Self::DbTuple>) -> Vec<Self> {
+    mrp
+      .iter()
+      .map(|a| Self {
+        mod_remove_comment: a.0.to_owned(),
+        moderator: a.1.to_owned(),
+        comment: a.2.to_owned(),
+        commenter: a.3.to_owned(),
+        post: a.4.to_owned(),
+        community: a.5.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/moderator/mod_remove_community_view.rs b/lemmy_db/src/views/moderator/mod_remove_community_view.rs
new file mode 100644 (file)
index 0000000..daaf6d7
--- /dev/null
@@ -0,0 +1,69 @@
+use crate::{
+  limit_and_offset,
+  schema::{community, mod_remove_community, user_},
+  source::{
+    community::{Community, CommunitySafe},
+    moderator::ModRemoveCommunity,
+    user::{UserSafe, User_},
+  },
+  views::ViewToVec,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct ModRemoveCommunityView {
+  pub mod_remove_community: ModRemoveCommunity,
+  pub moderator: UserSafe,
+  pub community: CommunitySafe,
+}
+
+type ModRemoveCommunityTuple = (ModRemoveCommunity, UserSafe, CommunitySafe);
+
+impl ModRemoveCommunityView {
+  pub fn list(
+    conn: &PgConnection,
+    mod_user_id: Option<i32>,
+    page: Option<i64>,
+    limit: Option<i64>,
+  ) -> Result<Vec<Self>, Error> {
+    let mut query = mod_remove_community::table
+      .inner_join(user_::table)
+      .inner_join(community::table)
+      .select((
+        mod_remove_community::all_columns,
+        User_::safe_columns_tuple(),
+        Community::safe_columns_tuple(),
+      ))
+      .into_boxed();
+
+    if let Some(mod_user_id) = mod_user_id {
+      query = query.filter(mod_remove_community::mod_user_id.eq(mod_user_id));
+    };
+
+    let (limit, offset) = limit_and_offset(page, limit);
+
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .order_by(mod_remove_community::when_.desc())
+      .load::<ModRemoveCommunityTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+}
+
+impl ViewToVec for ModRemoveCommunityView {
+  type DbTuple = ModRemoveCommunityTuple;
+  fn to_vec(mrp: Vec<Self::DbTuple>) -> Vec<Self> {
+    mrp
+      .iter()
+      .map(|a| Self {
+        mod_remove_community: a.0.to_owned(),
+        moderator: a.1.to_owned(),
+        community: a.2.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/moderator/mod_remove_post_view.rs b/lemmy_db/src/views/moderator/mod_remove_post_view.rs
new file mode 100644 (file)
index 0000000..613a8a5
--- /dev/null
@@ -0,0 +1,79 @@
+use crate::{
+  limit_and_offset,
+  schema::{community, mod_remove_post, post, user_},
+  source::{
+    community::{Community, CommunitySafe},
+    moderator::ModRemovePost,
+    post::Post,
+    user::{UserSafe, User_},
+  },
+  views::ViewToVec,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct ModRemovePostView {
+  pub mod_remove_post: ModRemovePost,
+  pub moderator: UserSafe,
+  pub post: Post,
+  pub community: CommunitySafe,
+}
+
+type ModRemovePostViewTuple = (ModRemovePost, UserSafe, Post, CommunitySafe);
+
+impl ModRemovePostView {
+  pub fn list(
+    conn: &PgConnection,
+    community_id: Option<i32>,
+    mod_user_id: Option<i32>,
+    page: Option<i64>,
+    limit: Option<i64>,
+  ) -> Result<Vec<Self>, Error> {
+    let mut query = mod_remove_post::table
+      .inner_join(user_::table)
+      .inner_join(post::table)
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .select((
+        mod_remove_post::all_columns,
+        User_::safe_columns_tuple(),
+        post::all_columns,
+        Community::safe_columns_tuple(),
+      ))
+      .into_boxed();
+
+    if let Some(community_id) = community_id {
+      query = query.filter(post::community_id.eq(community_id));
+    };
+
+    if let Some(mod_user_id) = mod_user_id {
+      query = query.filter(mod_remove_post::mod_user_id.eq(mod_user_id));
+    };
+
+    let (limit, offset) = limit_and_offset(page, limit);
+
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .order_by(mod_remove_post::when_.desc())
+      .load::<ModRemovePostViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+}
+
+impl ViewToVec for ModRemovePostView {
+  type DbTuple = ModRemovePostViewTuple;
+  fn to_vec(mrp: Vec<Self::DbTuple>) -> Vec<Self> {
+    mrp
+      .iter()
+      .map(|a| Self {
+        mod_remove_post: a.0.to_owned(),
+        moderator: a.1.to_owned(),
+        post: a.2.to_owned(),
+        community: a.3.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/moderator/mod_sticky_post_view.rs b/lemmy_db/src/views/moderator/mod_sticky_post_view.rs
new file mode 100644 (file)
index 0000000..9a3d118
--- /dev/null
@@ -0,0 +1,79 @@
+use crate::{
+  limit_and_offset,
+  schema::{community, mod_sticky_post, post, user_},
+  source::{
+    community::{Community, CommunitySafe},
+    moderator::ModStickyPost,
+    post::Post,
+    user::{UserSafe, User_},
+  },
+  views::ViewToVec,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct ModStickyPostView {
+  pub mod_sticky_post: ModStickyPost,
+  pub moderator: UserSafe,
+  pub post: Post,
+  pub community: CommunitySafe,
+}
+
+type ModStickyPostViewTuple = (ModStickyPost, UserSafe, Post, CommunitySafe);
+
+impl ModStickyPostView {
+  pub fn list(
+    conn: &PgConnection,
+    community_id: Option<i32>,
+    mod_user_id: Option<i32>,
+    page: Option<i64>,
+    limit: Option<i64>,
+  ) -> Result<Vec<Self>, Error> {
+    let mut query = mod_sticky_post::table
+      .inner_join(user_::table)
+      .inner_join(post::table)
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .select((
+        mod_sticky_post::all_columns,
+        User_::safe_columns_tuple(),
+        post::all_columns,
+        Community::safe_columns_tuple(),
+      ))
+      .into_boxed();
+
+    if let Some(community_id) = community_id {
+      query = query.filter(post::community_id.eq(community_id));
+    };
+
+    if let Some(mod_user_id) = mod_user_id {
+      query = query.filter(mod_sticky_post::mod_user_id.eq(mod_user_id));
+    };
+
+    let (limit, offset) = limit_and_offset(page, limit);
+
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .order_by(mod_sticky_post::when_.desc())
+      .load::<ModStickyPostViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+}
+
+impl ViewToVec for ModStickyPostView {
+  type DbTuple = ModStickyPostViewTuple;
+  fn to_vec(mrp: Vec<Self::DbTuple>) -> Vec<Self> {
+    mrp
+      .iter()
+      .map(|a| Self {
+        mod_sticky_post: a.0.to_owned(),
+        moderator: a.1.to_owned(),
+        post: a.2.to_owned(),
+        community: a.3.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/post_report_view.rs b/lemmy_db/src/views/post_report_view.rs
new file mode 100644 (file)
index 0000000..d39adfd
--- /dev/null
@@ -0,0 +1,178 @@
+use crate::{
+  limit_and_offset,
+  schema::{community, post, post_report, user_, user_alias_1, user_alias_2},
+  source::{
+    community::{Community, CommunitySafe},
+    post::Post,
+    post_report::PostReport,
+    user::{UserAlias1, UserAlias2, UserSafe, UserSafeAlias1, UserSafeAlias2, User_},
+  },
+  views::ViewToVec,
+  MaybeOptional,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, PartialEq, Serialize, Clone)]
+pub struct PostReportView {
+  pub post_report: PostReport,
+  pub post: Post,
+  pub community: CommunitySafe,
+  pub creator: UserSafe,
+  pub post_creator: UserSafeAlias1,
+  pub resolver: Option<UserSafeAlias2>,
+}
+
+type PostReportViewTuple = (
+  PostReport,
+  Post,
+  CommunitySafe,
+  UserSafe,
+  UserSafeAlias1,
+  Option<UserSafeAlias2>,
+);
+
+impl PostReportView {
+  /// returns the PostReportView for the provided report_id
+  ///
+  /// * `report_id` - the report id to obtain
+  pub fn read(conn: &PgConnection, report_id: i32) -> Result<Self, Error> {
+    let (post_report, post, community, creator, post_creator, resolver) = post_report::table
+      .find(report_id)
+      .inner_join(post::table)
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .inner_join(user_::table.on(post_report::creator_id.eq(user_::id)))
+      .inner_join(user_alias_1::table.on(post::creator_id.eq(user_alias_1::id)))
+      .left_join(user_alias_2::table.on(post_report::resolver_id.eq(user_alias_2::id.nullable())))
+      .select((
+        post_report::all_columns,
+        post::all_columns,
+        Community::safe_columns_tuple(),
+        User_::safe_columns_tuple(),
+        UserAlias1::safe_columns_tuple(),
+        UserAlias2::safe_columns_tuple().nullable(),
+      ))
+      .first::<PostReportViewTuple>(conn)?;
+
+    Ok(Self {
+      post_report,
+      post,
+      community,
+      creator,
+      post_creator,
+      resolver,
+    })
+  }
+
+  /// returns the current unresolved post report count for the supplied community ids
+  ///
+  /// * `community_ids` - a Vec<i32> of community_ids to get a count for
+  /// TODO this eq_any is a bad way to do this, would be better to join to communitymoderator
+  /// for a user id
+  pub fn get_report_count(conn: &PgConnection, community_ids: &[i32]) -> Result<i64, Error> {
+    use diesel::dsl::*;
+    post_report::table
+      .inner_join(post::table)
+      .filter(
+        post_report::resolved
+          .eq(false)
+          .and(post::community_id.eq_any(community_ids)),
+      )
+      .select(count(post_report::id))
+      .first::<i64>(conn)
+  }
+}
+
+pub struct PostReportQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  community_ids: Option<Vec<i32>>, // TODO bad way to do this
+  page: Option<i64>,
+  limit: Option<i64>,
+  resolved: Option<bool>,
+}
+
+impl<'a> PostReportQueryBuilder<'a> {
+  pub fn create(conn: &'a PgConnection) -> Self {
+    PostReportQueryBuilder {
+      conn,
+      community_ids: None,
+      page: None,
+      limit: None,
+      resolved: Some(false),
+    }
+  }
+
+  pub fn community_ids<T: MaybeOptional<Vec<i32>>>(mut self, community_ids: T) -> Self {
+    self.community_ids = community_ids.get_optional();
+    self
+  }
+
+  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
+    self.page = page.get_optional();
+    self
+  }
+
+  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
+    self.limit = limit.get_optional();
+    self
+  }
+
+  pub fn resolved<T: MaybeOptional<bool>>(mut self, resolved: T) -> Self {
+    self.resolved = resolved.get_optional();
+    self
+  }
+
+  pub fn list(self) -> Result<Vec<PostReportView>, Error> {
+    let mut query = post_report::table
+      .inner_join(post::table)
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .inner_join(user_::table.on(post_report::creator_id.eq(user_::id)))
+      .inner_join(user_alias_1::table.on(post::creator_id.eq(user_alias_1::id)))
+      .left_join(user_alias_2::table.on(post_report::resolver_id.eq(user_alias_2::id.nullable())))
+      .select((
+        post_report::all_columns,
+        post::all_columns,
+        Community::safe_columns_tuple(),
+        User_::safe_columns_tuple(),
+        UserAlias1::safe_columns_tuple(),
+        UserAlias2::safe_columns_tuple().nullable(),
+      ))
+      .into_boxed();
+
+    if let Some(comm_ids) = self.community_ids {
+      query = query.filter(post::community_id.eq_any(comm_ids));
+    }
+
+    if let Some(resolved_flag) = self.resolved {
+      query = query.filter(post_report::resolved.eq(resolved_flag));
+    }
+
+    let (limit, offset) = limit_and_offset(self.page, self.limit);
+
+    let res = query
+      .order_by(post_report::published.asc())
+      .limit(limit)
+      .offset(offset)
+      .load::<PostReportViewTuple>(self.conn)?;
+
+    Ok(PostReportView::to_vec(res))
+  }
+}
+
+impl ViewToVec for PostReportView {
+  type DbTuple = PostReportViewTuple;
+  fn to_vec(posts: Vec<Self::DbTuple>) -> Vec<Self> {
+    posts
+      .iter()
+      .map(|a| Self {
+        post_report: a.0.to_owned(),
+        post: a.1.to_owned(),
+        community: a.2.to_owned(),
+        creator: a.3.to_owned(),
+        post_creator: a.4.to_owned(),
+        resolver: a.5.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/post_view.rs b/lemmy_db/src/views/post_view.rs
new file mode 100644 (file)
index 0000000..9a4dbba
--- /dev/null
@@ -0,0 +1,636 @@
+use crate::{
+  aggregates::post_aggregates::PostAggregates,
+  functions::hot_rank,
+  fuzzy_search,
+  limit_and_offset,
+  schema::{
+    community,
+    community_follower,
+    community_user_ban,
+    post,
+    post_aggregates,
+    post_like,
+    post_read,
+    post_saved,
+    user_,
+  },
+  source::{
+    community::{Community, CommunityFollower, CommunitySafe, CommunityUserBan},
+    post::{Post, PostRead, PostSaved},
+    user::{UserSafe, User_},
+  },
+  views::ViewToVec,
+  ListingType,
+  MaybeOptional,
+  SortType,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, PartialEq, Serialize, Clone)]
+pub struct PostView {
+  pub post: Post,
+  pub creator: UserSafe,
+  pub community: CommunitySafe,
+  pub creator_banned_from_community: bool, // Left Join to CommunityUserBan
+  pub counts: PostAggregates,
+  pub subscribed: bool,     // Left join to CommunityFollower
+  pub saved: bool,          // Left join to PostSaved
+  pub read: bool,           // Left join to PostRead
+  pub my_vote: Option<i16>, // Left join to PostLike
+}
+
+type PostViewTuple = (
+  Post,
+  UserSafe,
+  CommunitySafe,
+  Option<CommunityUserBan>,
+  PostAggregates,
+  Option<CommunityFollower>,
+  Option<PostSaved>,
+  Option<PostRead>,
+  Option<i16>,
+);
+
+impl PostView {
+  pub fn read(conn: &PgConnection, post_id: i32, my_user_id: Option<i32>) -> Result<Self, Error> {
+    // The left join below will return None in this case
+    let user_id_join = my_user_id.unwrap_or(-1);
+
+    let (
+      post,
+      creator,
+      community,
+      creator_banned_from_community,
+      counts,
+      follower,
+      saved,
+      read,
+      my_vote,
+    ) = post::table
+      .find(post_id)
+      .inner_join(user_::table)
+      .inner_join(community::table)
+      .left_join(
+        community_user_ban::table.on(
+          post::community_id
+            .eq(community_user_ban::community_id)
+            .and(community_user_ban::user_id.eq(post::creator_id)),
+        ),
+      )
+      .inner_join(post_aggregates::table)
+      .left_join(
+        community_follower::table.on(
+          post::community_id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        post_saved::table.on(
+          post::id
+            .eq(post_saved::post_id)
+            .and(post_saved::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        post_read::table.on(
+          post::id
+            .eq(post_read::post_id)
+            .and(post_read::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        post_like::table.on(
+          post::id
+            .eq(post_like::post_id)
+            .and(post_like::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        post::all_columns,
+        User_::safe_columns_tuple(),
+        Community::safe_columns_tuple(),
+        community_user_ban::all_columns.nullable(),
+        post_aggregates::all_columns,
+        community_follower::all_columns.nullable(),
+        post_saved::all_columns.nullable(),
+        post_read::all_columns.nullable(),
+        post_like::score.nullable(),
+      ))
+      .first::<PostViewTuple>(conn)?;
+
+    Ok(PostView {
+      post,
+      creator,
+      community,
+      creator_banned_from_community: creator_banned_from_community.is_some(),
+      counts,
+      subscribed: follower.is_some(),
+      saved: saved.is_some(),
+      read: read.is_some(),
+      my_vote,
+    })
+  }
+}
+
+pub struct PostQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  listing_type: &'a ListingType,
+  sort: &'a SortType,
+  creator_id: Option<i32>,
+  community_id: Option<i32>,
+  community_name: Option<String>,
+  my_user_id: Option<i32>,
+  search_term: Option<String>,
+  url_search: Option<String>,
+  show_nsfw: bool,
+  saved_only: bool,
+  unread_only: bool,
+  page: Option<i64>,
+  limit: Option<i64>,
+}
+
+impl<'a> PostQueryBuilder<'a> {
+  pub fn create(conn: &'a PgConnection) -> Self {
+    PostQueryBuilder {
+      conn,
+      listing_type: &ListingType::All,
+      sort: &SortType::Hot,
+      creator_id: None,
+      community_id: None,
+      community_name: None,
+      my_user_id: None,
+      search_term: None,
+      url_search: None,
+      show_nsfw: true,
+      saved_only: false,
+      unread_only: false,
+      page: None,
+      limit: None,
+    }
+  }
+
+  pub fn listing_type(mut self, listing_type: &'a ListingType) -> Self {
+    self.listing_type = listing_type;
+    self
+  }
+
+  pub fn sort(mut self, sort: &'a SortType) -> Self {
+    self.sort = sort;
+    self
+  }
+
+  pub fn community_id<T: MaybeOptional<i32>>(mut self, community_id: T) -> Self {
+    self.community_id = community_id.get_optional();
+    self
+  }
+
+  pub fn my_user_id<T: MaybeOptional<i32>>(mut self, my_user_id: T) -> Self {
+    self.my_user_id = my_user_id.get_optional();
+    self
+  }
+
+  pub fn community_name<T: MaybeOptional<String>>(mut self, community_name: T) -> Self {
+    self.community_name = community_name.get_optional();
+    self
+  }
+
+  pub fn creator_id<T: MaybeOptional<i32>>(mut self, creator_id: T) -> Self {
+    self.creator_id = creator_id.get_optional();
+    self
+  }
+
+  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
+    self.search_term = search_term.get_optional();
+    self
+  }
+
+  pub fn url_search<T: MaybeOptional<String>>(mut self, url_search: T) -> Self {
+    self.url_search = url_search.get_optional();
+    self
+  }
+
+  pub fn show_nsfw(mut self, show_nsfw: bool) -> Self {
+    self.show_nsfw = show_nsfw;
+    self
+  }
+
+  pub fn saved_only(mut self, saved_only: bool) -> Self {
+    self.saved_only = saved_only;
+    self
+  }
+
+  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
+    self.page = page.get_optional();
+    self
+  }
+
+  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
+    self.limit = limit.get_optional();
+    self
+  }
+
+  pub fn list(self) -> Result<Vec<PostView>, Error> {
+    use diesel::dsl::*;
+
+    // The left join below will return None in this case
+    let user_id_join = self.my_user_id.unwrap_or(-1);
+
+    let mut query = post::table
+      .inner_join(user_::table)
+      .inner_join(community::table)
+      .left_join(
+        community_user_ban::table.on(
+          post::community_id
+            .eq(community_user_ban::community_id)
+            .and(community_user_ban::user_id.eq(community::creator_id)),
+        ),
+      )
+      .inner_join(post_aggregates::table)
+      .left_join(
+        community_follower::table.on(
+          post::community_id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        post_saved::table.on(
+          post::id
+            .eq(post_saved::post_id)
+            .and(post_saved::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        post_read::table.on(
+          post::id
+            .eq(post_read::post_id)
+            .and(post_read::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        post_like::table.on(
+          post::id
+            .eq(post_like::post_id)
+            .and(post_like::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        post::all_columns,
+        User_::safe_columns_tuple(),
+        Community::safe_columns_tuple(),
+        community_user_ban::all_columns.nullable(),
+        post_aggregates::all_columns,
+        community_follower::all_columns.nullable(),
+        post_saved::all_columns.nullable(),
+        post_read::all_columns.nullable(),
+        post_like::score.nullable(),
+      ))
+      .into_boxed();
+
+    query = match self.listing_type {
+      ListingType::Subscribed => query.filter(community_follower::user_id.is_not_null()), // TODO could be this: and(community_follower::user_id.eq(user_id_join)),
+      ListingType::Local => query.filter(community::local.eq(true)),
+      _ => query,
+    };
+
+    if let Some(community_id) = self.community_id {
+      query = query
+        .filter(post::community_id.eq(community_id))
+        .then_order_by(post::stickied.desc());
+    }
+
+    if let Some(community_name) = self.community_name {
+      query = query
+        .filter(community::name.eq(community_name))
+        .filter(community::local.eq(true))
+        .then_order_by(post::stickied.desc());
+    }
+
+    if let Some(url_search) = self.url_search {
+      query = query.filter(post::url.eq(url_search));
+    }
+
+    if let Some(search_term) = self.search_term {
+      let searcher = fuzzy_search(&search_term);
+      query = query.filter(
+        post::name
+          .ilike(searcher.to_owned())
+          .or(post::body.ilike(searcher)),
+      );
+    }
+
+    // If its for a specific user, show the removed / deleted
+    if let Some(creator_id) = self.creator_id {
+      query = query.filter(post::creator_id.eq(creator_id));
+    }
+
+    if !self.show_nsfw {
+      query = query
+        .filter(post::nsfw.eq(false))
+        .filter(community::nsfw.eq(false));
+    };
+
+    // TODO  These two might be wrong
+    if self.saved_only {
+      query = query.filter(post_saved::id.is_not_null());
+    };
+
+    if self.unread_only {
+      query = query.filter(post_read::id.is_not_null());
+    };
+
+    query = match self.sort {
+      SortType::Active => query
+        .then_order_by(
+          hot_rank(post_aggregates::score, post_aggregates::newest_comment_time).desc(),
+        )
+        .then_order_by(post::published.desc()),
+      SortType::Hot => query
+        .then_order_by(hot_rank(post_aggregates::score, post::published).desc())
+        .then_order_by(post::published.desc()),
+      SortType::New => query.then_order_by(post::published.desc()),
+      SortType::TopAll => query.then_order_by(post_aggregates::score.desc()),
+      SortType::TopYear => query
+        .filter(post::published.gt(now - 1.years()))
+        .then_order_by(post_aggregates::score.desc()),
+      SortType::TopMonth => query
+        .filter(post::published.gt(now - 1.months()))
+        .then_order_by(post_aggregates::score.desc()),
+      SortType::TopWeek => query
+        .filter(post::published.gt(now - 1.weeks()))
+        .then_order_by(post_aggregates::score.desc()),
+      SortType::TopDay => query
+        .filter(post::published.gt(now - 1.days()))
+        .then_order_by(post_aggregates::score.desc()),
+    };
+
+    let (limit, offset) = limit_and_offset(self.page, self.limit);
+
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .filter(post::removed.eq(false))
+      .filter(post::deleted.eq(false))
+      .filter(community::removed.eq(false))
+      .filter(community::deleted.eq(false))
+      .load::<PostViewTuple>(self.conn)?;
+
+    Ok(PostView::to_vec(res))
+  }
+}
+
+impl ViewToVec for PostView {
+  type DbTuple = PostViewTuple;
+  fn to_vec(posts: Vec<Self::DbTuple>) -> Vec<Self> {
+    posts
+      .iter()
+      .map(|a| Self {
+        post: a.0.to_owned(),
+        creator: a.1.to_owned(),
+        community: a.2.to_owned(),
+        creator_banned_from_community: a.3.is_some(),
+        counts: a.4.to_owned(),
+        subscribed: a.5.is_some(),
+        saved: a.6.is_some(),
+        read: a.7.is_some(),
+        my_vote: a.8,
+      })
+      .collect::<Vec<Self>>()
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::{
+    aggregates::post_aggregates::PostAggregates,
+    source::{community::*, post::*, user::*},
+    tests::establish_unpooled_connection,
+    views::post_view::{PostQueryBuilder, PostView},
+    Crud,
+    Likeable,
+    *,
+  };
+
+  #[test]
+  fn test_crud() {
+    let conn = establish_unpooled_connection();
+
+    let user_name = "tegan".to_string();
+    let community_name = "test_community_3".to_string();
+    let post_name = "test post 3".to_string();
+
+    let new_user = UserForm {
+      name: user_name.to_owned(),
+      preferred_username: None,
+      password_encrypted: "nope".into(),
+      email: None,
+      matrix_user_id: None,
+      avatar: None,
+      banner: None,
+      published: None,
+      updated: None,
+      admin: false,
+      banned: Some(false),
+      show_nsfw: false,
+      theme: "browser".into(),
+      default_sort_type: SortType::Hot as i16,
+      default_listing_type: ListingType::Subscribed as i16,
+      lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
+      actor_id: None,
+      bio: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+    };
+
+    let inserted_user = User_::create(&conn, &new_user).unwrap();
+
+    let new_community = CommunityForm {
+      name: community_name.to_owned(),
+      title: "nada".to_owned(),
+      description: None,
+      creator_id: inserted_user.id,
+      category_id: 1,
+      removed: None,
+      deleted: None,
+      updated: None,
+      nsfw: false,
+      actor_id: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+      published: None,
+      icon: None,
+      banner: None,
+    };
+
+    let inserted_community = Community::create(&conn, &new_community).unwrap();
+
+    let new_post = PostForm {
+      name: post_name.to_owned(),
+      url: None,
+      body: None,
+      creator_id: inserted_user.id,
+      community_id: inserted_community.id,
+      removed: None,
+      deleted: None,
+      locked: None,
+      stickied: None,
+      updated: None,
+      nsfw: false,
+      embed_title: None,
+      embed_description: None,
+      embed_html: None,
+      thumbnail_url: None,
+      ap_id: None,
+      local: true,
+      published: None,
+    };
+
+    let inserted_post = Post::create(&conn, &new_post).unwrap();
+
+    let post_like_form = PostLikeForm {
+      post_id: inserted_post.id,
+      user_id: inserted_user.id,
+      score: 1,
+    };
+
+    let inserted_post_like = PostLike::like(&conn, &post_like_form).unwrap();
+
+    let expected_post_like = PostLike {
+      id: inserted_post_like.id,
+      post_id: inserted_post.id,
+      user_id: inserted_user.id,
+      published: inserted_post_like.published,
+      score: 1,
+    };
+
+    let read_post_listings_with_user = PostQueryBuilder::create(&conn)
+      .listing_type(&ListingType::Community)
+      .sort(&SortType::New)
+      .community_id(inserted_community.id)
+      .my_user_id(inserted_user.id)
+      .list()
+      .unwrap();
+
+    let read_post_listings_no_user = PostQueryBuilder::create(&conn)
+      .listing_type(&ListingType::Community)
+      .sort(&SortType::New)
+      .community_id(inserted_community.id)
+      .list()
+      .unwrap();
+
+    let read_post_listing_no_user = PostView::read(&conn, inserted_post.id, None).unwrap();
+    let read_post_listing_with_user =
+      PostView::read(&conn, inserted_post.id, Some(inserted_user.id)).unwrap();
+
+    let agg = PostAggregates::read(&conn, inserted_post.id).unwrap();
+
+    // the non user version
+    let expected_post_listing_no_user = PostView {
+      post: Post {
+        id: inserted_post.id,
+        name: post_name.to_owned(),
+        creator_id: inserted_user.id,
+        url: None,
+        body: None,
+        published: inserted_post.published,
+        updated: None,
+        community_id: inserted_community.id,
+        removed: false,
+        deleted: false,
+        locked: false,
+        stickied: false,
+        nsfw: false,
+        embed_title: None,
+        embed_description: None,
+        embed_html: None,
+        thumbnail_url: None,
+        ap_id: inserted_post.ap_id.to_owned(),
+        local: true,
+      },
+      my_vote: None,
+      creator: UserSafe {
+        id: inserted_user.id,
+        name: user_name.to_owned(),
+        preferred_username: None,
+        published: inserted_user.published,
+        avatar: None,
+        actor_id: inserted_user.actor_id.to_owned(),
+        local: true,
+        banned: false,
+        deleted: false,
+        bio: None,
+        banner: None,
+        admin: false,
+        updated: None,
+        matrix_user_id: None,
+      },
+      creator_banned_from_community: false,
+      community: CommunitySafe {
+        id: inserted_community.id,
+        name: community_name.to_owned(),
+        icon: None,
+        removed: false,
+        deleted: false,
+        nsfw: false,
+        actor_id: inserted_community.actor_id.to_owned(),
+        local: true,
+        title: "nada".to_owned(),
+        description: None,
+        creator_id: inserted_user.id,
+        category_id: 1,
+        updated: None,
+        banner: None,
+        published: inserted_community.published,
+      },
+      counts: PostAggregates {
+        id: agg.id,
+        post_id: inserted_post.id,
+        comments: 0,
+        score: 1,
+        upvotes: 1,
+        downvotes: 0,
+        newest_comment_time: inserted_post.published,
+      },
+      subscribed: false,
+      read: false,
+      saved: false,
+    };
+
+    // TODO More needs to be added here
+    let mut expected_post_listing_with_user = expected_post_listing_no_user.to_owned();
+    expected_post_listing_with_user.my_vote = Some(1);
+
+    let like_removed = PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
+    let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
+    Community::delete(&conn, inserted_community.id).unwrap();
+    User_::delete(&conn, inserted_user.id).unwrap();
+
+    // The with user
+    assert_eq!(
+      expected_post_listing_with_user,
+      read_post_listings_with_user[0]
+    );
+    assert_eq!(expected_post_listing_with_user, read_post_listing_with_user);
+    assert_eq!(1, read_post_listings_with_user.len());
+
+    // Without the user
+    assert_eq!(expected_post_listing_no_user, read_post_listings_no_user[0]);
+    assert_eq!(expected_post_listing_no_user, read_post_listing_no_user);
+    assert_eq!(1, read_post_listings_no_user.len());
+
+    // assert_eq!(expected_post, inserted_post);
+    // assert_eq!(expected_post, updated_post);
+    assert_eq!(expected_post_like, inserted_post_like);
+    assert_eq!(1, like_removed);
+    assert_eq!(1, num_deleted);
+  }
+}
diff --git a/lemmy_db/src/views/private_message_view.rs b/lemmy_db/src/views/private_message_view.rs
new file mode 100644 (file)
index 0000000..43d960a
--- /dev/null
@@ -0,0 +1,130 @@
+use crate::{
+  limit_and_offset,
+  schema::{private_message, user_, user_alias_1},
+  source::{
+    private_message::PrivateMessage,
+    user::{UserAlias1, UserSafe, UserSafeAlias1, User_},
+  },
+  views::ViewToVec,
+  MaybeOptional,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, PartialEq, Serialize, Clone)]
+pub struct PrivateMessageView {
+  pub private_message: PrivateMessage,
+  pub creator: UserSafe,
+  pub recipient: UserSafeAlias1,
+}
+
+type PrivateMessageViewTuple = (PrivateMessage, UserSafe, UserSafeAlias1);
+
+impl PrivateMessageView {
+  pub fn read(conn: &PgConnection, private_message_id: i32) -> Result<Self, Error> {
+    let (private_message, creator, recipient) = private_message::table
+      .find(private_message_id)
+      .inner_join(user_::table.on(private_message::creator_id.eq(user_::id)))
+      .inner_join(user_alias_1::table.on(private_message::recipient_id.eq(user_alias_1::id)))
+      .order_by(private_message::published.desc())
+      .select((
+        private_message::all_columns,
+        User_::safe_columns_tuple(),
+        UserAlias1::safe_columns_tuple(),
+      ))
+      .first::<PrivateMessageViewTuple>(conn)?;
+
+    Ok(PrivateMessageView {
+      private_message,
+      creator,
+      recipient,
+    })
+  }
+}
+
+pub struct PrivateMessageQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  recipient_id: i32,
+  unread_only: bool,
+  page: Option<i64>,
+  limit: Option<i64>,
+}
+
+impl<'a> PrivateMessageQueryBuilder<'a> {
+  pub fn create(conn: &'a PgConnection, recipient_id: i32) -> Self {
+    PrivateMessageQueryBuilder {
+      conn,
+      recipient_id,
+      unread_only: false,
+      page: None,
+      limit: None,
+    }
+  }
+
+  pub fn unread_only(mut self, unread_only: bool) -> Self {
+    self.unread_only = unread_only;
+    self
+  }
+
+  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
+    self.page = page.get_optional();
+    self
+  }
+
+  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
+    self.limit = limit.get_optional();
+    self
+  }
+
+  pub fn list(self) -> Result<Vec<PrivateMessageView>, Error> {
+    let mut query = private_message::table
+      .inner_join(user_::table.on(private_message::creator_id.eq(user_::id)))
+      .inner_join(user_alias_1::table.on(private_message::recipient_id.eq(user_alias_1::id)))
+      .select((
+        private_message::all_columns,
+        User_::safe_columns_tuple(),
+        UserAlias1::safe_columns_tuple(),
+      ))
+      .into_boxed();
+
+    // If its unread, I only want the ones to me
+    if self.unread_only {
+      query = query
+        .filter(private_message::read.eq(false))
+        .filter(private_message::recipient_id.eq(self.recipient_id));
+    }
+    // Otherwise, I want the ALL view to show both sent and received
+    else {
+      query = query.filter(
+        private_message::recipient_id
+          .eq(self.recipient_id)
+          .or(private_message::creator_id.eq(self.recipient_id)),
+      )
+    }
+
+    let (limit, offset) = limit_and_offset(self.page, self.limit);
+
+    let res = query
+      .filter(private_message::deleted.eq(false))
+      .limit(limit)
+      .offset(offset)
+      .order_by(private_message::published.desc())
+      .load::<PrivateMessageViewTuple>(self.conn)?;
+
+    Ok(PrivateMessageView::to_vec(res))
+  }
+}
+
+impl ViewToVec for PrivateMessageView {
+  type DbTuple = PrivateMessageViewTuple;
+  fn to_vec(pm: Vec<Self::DbTuple>) -> Vec<Self> {
+    pm.iter()
+      .map(|a| Self {
+        private_message: a.0.to_owned(),
+        creator: a.1.to_owned(),
+        recipient: a.2.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/site_view.rs b/lemmy_db/src/views/site_view.rs
new file mode 100644 (file)
index 0000000..3c60527
--- /dev/null
@@ -0,0 +1,38 @@
+use crate::{
+  aggregates::site_aggregates::SiteAggregates,
+  schema::{site, site_aggregates, user_},
+  source::{
+    site::Site,
+    user::{UserSafe, User_},
+  },
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct SiteView {
+  pub site: Site,
+  pub creator: UserSafe,
+  pub counts: SiteAggregates,
+}
+
+impl SiteView {
+  pub fn read(conn: &PgConnection) -> Result<Self, Error> {
+    let (site, creator, counts) = site::table
+      .inner_join(user_::table)
+      .inner_join(site_aggregates::table)
+      .select((
+        site::all_columns,
+        User_::safe_columns_tuple(),
+        site_aggregates::all_columns,
+      ))
+      .first::<(Site, UserSafe, SiteAggregates)>(conn)?;
+
+    Ok(SiteView {
+      site,
+      creator,
+      counts,
+    })
+  }
+}
diff --git a/lemmy_db/src/views/user_mention_view.rs b/lemmy_db/src/views/user_mention_view.rs
new file mode 100644 (file)
index 0000000..67616fb
--- /dev/null
@@ -0,0 +1,318 @@
+use crate::{
+  aggregates::comment_aggregates::CommentAggregates,
+  functions::hot_rank,
+  limit_and_offset,
+  schema::{
+    comment,
+    comment_aggregates,
+    comment_like,
+    comment_saved,
+    community,
+    community_follower,
+    community_user_ban,
+    post,
+    user_,
+    user_alias_1,
+    user_mention,
+  },
+  source::{
+    comment::{Comment, CommentSaved},
+    community::{Community, CommunityFollower, CommunitySafe, CommunityUserBan},
+    post::Post,
+    user::{UserAlias1, UserSafe, UserSafeAlias1, User_},
+    user_mention::UserMention,
+  },
+  views::ViewToVec,
+  MaybeOptional,
+  SortType,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, PartialEq, Serialize, Clone)]
+pub struct UserMentionView {
+  pub user_mention: UserMention,
+  pub comment: Comment,
+  pub creator: UserSafe,
+  pub post: Post,
+  pub community: CommunitySafe,
+  pub recipient: UserSafeAlias1,
+  pub counts: CommentAggregates,
+  pub creator_banned_from_community: bool, // Left Join to CommunityUserBan
+  pub subscribed: bool,                    // Left join to CommunityFollower
+  pub saved: bool,                         // Left join to CommentSaved
+  pub my_vote: Option<i16>,                // Left join to CommentLike
+}
+
+type UserMentionViewTuple = (
+  UserMention,
+  Comment,
+  UserSafe,
+  Post,
+  CommunitySafe,
+  UserSafeAlias1,
+  CommentAggregates,
+  Option<CommunityUserBan>,
+  Option<CommunityFollower>,
+  Option<CommentSaved>,
+  Option<i16>,
+);
+
+impl UserMentionView {
+  pub fn read(
+    conn: &PgConnection,
+    user_mention_id: i32,
+    my_user_id: Option<i32>,
+  ) -> Result<Self, Error> {
+    // The left join below will return None in this case
+    let user_id_join = my_user_id.unwrap_or(-1);
+
+    let (
+      user_mention,
+      comment,
+      creator,
+      post,
+      community,
+      recipient,
+      counts,
+      creator_banned_from_community,
+      subscribed,
+      saved,
+      my_vote,
+    ) = user_mention::table
+      .find(user_mention_id)
+      .inner_join(comment::table)
+      .inner_join(user_::table.on(comment::creator_id.eq(user_::id)))
+      .inner_join(post::table.on(comment::post_id.eq(post::id)))
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .inner_join(user_alias_1::table)
+      .inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
+      .left_join(
+        community_user_ban::table.on(
+          community::id
+            .eq(community_user_ban::community_id)
+            .and(community_user_ban::user_id.eq(comment::creator_id)),
+        ),
+      )
+      .left_join(
+        community_follower::table.on(
+          post::community_id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_saved::table.on(
+          comment::id
+            .eq(comment_saved::comment_id)
+            .and(comment_saved::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_like::table.on(
+          comment::id
+            .eq(comment_like::comment_id)
+            .and(comment_like::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        user_mention::all_columns,
+        comment::all_columns,
+        User_::safe_columns_tuple(),
+        post::all_columns,
+        Community::safe_columns_tuple(),
+        UserAlias1::safe_columns_tuple(),
+        comment_aggregates::all_columns,
+        community_user_ban::all_columns.nullable(),
+        community_follower::all_columns.nullable(),
+        comment_saved::all_columns.nullable(),
+        comment_like::score.nullable(),
+      ))
+      .first::<UserMentionViewTuple>(conn)?;
+
+    Ok(UserMentionView {
+      user_mention,
+      comment,
+      creator,
+      post,
+      community,
+      recipient,
+      counts,
+      creator_banned_from_community: creator_banned_from_community.is_some(),
+      subscribed: subscribed.is_some(),
+      saved: saved.is_some(),
+      my_vote,
+    })
+  }
+}
+
+pub struct UserMentionQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  my_user_id: Option<i32>,
+  recipient_id: Option<i32>,
+  sort: &'a SortType,
+  unread_only: bool,
+  page: Option<i64>,
+  limit: Option<i64>,
+}
+
+impl<'a> UserMentionQueryBuilder<'a> {
+  pub fn create(conn: &'a PgConnection) -> Self {
+    UserMentionQueryBuilder {
+      conn,
+      my_user_id: None,
+      recipient_id: None,
+      sort: &SortType::New,
+      unread_only: false,
+      page: None,
+      limit: None,
+    }
+  }
+
+  pub fn sort(mut self, sort: &'a SortType) -> Self {
+    self.sort = sort;
+    self
+  }
+
+  pub fn unread_only(mut self, unread_only: bool) -> Self {
+    self.unread_only = unread_only;
+    self
+  }
+
+  pub fn recipient_id<T: MaybeOptional<i32>>(mut self, recipient_id: T) -> Self {
+    self.recipient_id = recipient_id.get_optional();
+    self
+  }
+
+  pub fn my_user_id<T: MaybeOptional<i32>>(mut self, my_user_id: T) -> Self {
+    self.my_user_id = my_user_id.get_optional();
+    self
+  }
+
+  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
+    self.page = page.get_optional();
+    self
+  }
+
+  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
+    self.limit = limit.get_optional();
+    self
+  }
+
+  pub fn list(self) -> Result<Vec<UserMentionView>, Error> {
+    use diesel::dsl::*;
+
+    // The left join below will return None in this case
+    let user_id_join = self.my_user_id.unwrap_or(-1);
+
+    let mut query = user_mention::table
+      .inner_join(comment::table)
+      .inner_join(user_::table.on(comment::creator_id.eq(user_::id)))
+      .inner_join(post::table.on(comment::post_id.eq(post::id)))
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .inner_join(user_alias_1::table)
+      .inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
+      .left_join(
+        community_user_ban::table.on(
+          community::id
+            .eq(community_user_ban::community_id)
+            .and(community_user_ban::user_id.eq(comment::creator_id)),
+        ),
+      )
+      .left_join(
+        community_follower::table.on(
+          post::community_id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_saved::table.on(
+          comment::id
+            .eq(comment_saved::comment_id)
+            .and(comment_saved::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_like::table.on(
+          comment::id
+            .eq(comment_like::comment_id)
+            .and(comment_like::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        user_mention::all_columns,
+        comment::all_columns,
+        User_::safe_columns_tuple(),
+        post::all_columns,
+        Community::safe_columns_tuple(),
+        UserAlias1::safe_columns_tuple(),
+        comment_aggregates::all_columns,
+        community_user_ban::all_columns.nullable(),
+        community_follower::all_columns.nullable(),
+        comment_saved::all_columns.nullable(),
+        comment_like::score.nullable(),
+      ))
+      .into_boxed();
+
+    if let Some(recipient_id) = self.recipient_id {
+      query = query.filter(user_mention::recipient_id.eq(recipient_id));
+    }
+
+    if self.unread_only {
+      query = query.filter(user_mention::read.eq(false));
+    }
+
+    query = match self.sort {
+      SortType::Hot | SortType::Active => query
+        .order_by(hot_rank(comment_aggregates::score, comment::published).desc())
+        .then_order_by(comment::published.desc()),
+      SortType::New => query.order_by(comment::published.desc()),
+      SortType::TopAll => query.order_by(comment_aggregates::score.desc()),
+      SortType::TopYear => query
+        .filter(comment::published.gt(now - 1.years()))
+        .order_by(comment_aggregates::score.desc()),
+      SortType::TopMonth => query
+        .filter(comment::published.gt(now - 1.months()))
+        .order_by(comment_aggregates::score.desc()),
+      SortType::TopWeek => query
+        .filter(comment::published.gt(now - 1.weeks()))
+        .order_by(comment_aggregates::score.desc()),
+      SortType::TopDay => query
+        .filter(comment::published.gt(now - 1.days()))
+        .order_by(comment_aggregates::score.desc()),
+    };
+
+    let (limit, offset) = limit_and_offset(self.page, self.limit);
+
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .load::<UserMentionViewTuple>(self.conn)?;
+
+    Ok(UserMentionView::to_vec(res))
+  }
+}
+
+impl ViewToVec for UserMentionView {
+  type DbTuple = UserMentionViewTuple;
+  fn to_vec(posts: Vec<Self::DbTuple>) -> Vec<Self> {
+    posts
+      .iter()
+      .map(|a| Self {
+        user_mention: a.0.to_owned(),
+        comment: a.1.to_owned(),
+        creator: a.2.to_owned(),
+        post: a.3.to_owned(),
+        community: a.4.to_owned(),
+        recipient: a.5.to_owned(),
+        counts: a.6.to_owned(),
+        creator_banned_from_community: a.7.is_some(),
+        subscribed: a.8.is_some(),
+        saved: a.9.is_some(),
+        my_vote: a.10,
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/user_view.rs b/lemmy_db/src/views/user_view.rs
new file mode 100644 (file)
index 0000000..587ebf6
--- /dev/null
@@ -0,0 +1,165 @@
+use crate::{
+  aggregates::user_aggregates::UserAggregates,
+  fuzzy_search,
+  limit_and_offset,
+  schema::{user_, user_aggregates},
+  source::user::{UserSafe, User_},
+  views::ViewToVec,
+  MaybeOptional,
+  SortType,
+  ToSafe,
+};
+use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct UserViewSafe {
+  pub user: UserSafe,
+  pub counts: UserAggregates,
+}
+
+type UserViewSafeTuple = (UserSafe, UserAggregates);
+
+#[derive(Debug, Serialize, Clone)]
+pub struct UserViewDangerous {
+  pub user: User_,
+  pub counts: UserAggregates,
+}
+
+type UserViewDangerousTuple = (User_, UserAggregates);
+
+impl UserViewDangerous {
+  pub fn read(conn: &PgConnection, id: i32) -> Result<Self, Error> {
+    let (user, counts) = user_::table
+      .find(id)
+      .inner_join(user_aggregates::table)
+      .first::<UserViewDangerousTuple>(conn)?;
+    Ok(Self { user, counts })
+  }
+}
+
+impl UserViewSafe {
+  pub fn read(conn: &PgConnection, id: i32) -> Result<Self, Error> {
+    let (user, counts) = user_::table
+      .find(id)
+      .inner_join(user_aggregates::table)
+      .select((User_::safe_columns_tuple(), user_aggregates::all_columns))
+      .first::<UserViewSafeTuple>(conn)?;
+    Ok(Self { user, counts })
+  }
+
+  pub fn admins(conn: &PgConnection) -> Result<Vec<Self>, Error> {
+    let admins = user_::table
+      .inner_join(user_aggregates::table)
+      .select((User_::safe_columns_tuple(), user_aggregates::all_columns))
+      .filter(user_::admin.eq(true))
+      .order_by(user_::published)
+      .load::<UserViewSafeTuple>(conn)?;
+
+    Ok(Self::to_vec(admins))
+  }
+
+  pub fn banned(conn: &PgConnection) -> Result<Vec<Self>, Error> {
+    let banned = user_::table
+      .inner_join(user_aggregates::table)
+      .select((User_::safe_columns_tuple(), user_aggregates::all_columns))
+      .filter(user_::banned.eq(true))
+      .load::<UserViewSafeTuple>(conn)?;
+
+    Ok(Self::to_vec(banned))
+  }
+}
+
+pub struct UserQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  sort: &'a SortType,
+  search_term: Option<String>,
+  page: Option<i64>,
+  limit: Option<i64>,
+}
+
+impl<'a> UserQueryBuilder<'a> {
+  pub fn create(conn: &'a PgConnection) -> Self {
+    UserQueryBuilder {
+      conn,
+      search_term: None,
+      sort: &SortType::Hot,
+      page: None,
+      limit: None,
+    }
+  }
+
+  pub fn sort(mut self, sort: &'a SortType) -> Self {
+    self.sort = sort;
+    self
+  }
+
+  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
+    self.search_term = search_term.get_optional();
+    self
+  }
+
+  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
+    self.page = page.get_optional();
+    self
+  }
+
+  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
+    self.limit = limit.get_optional();
+    self
+  }
+
+  pub fn list(self) -> Result<Vec<UserViewSafe>, Error> {
+    let mut query = user_::table
+      .inner_join(user_aggregates::table)
+      .select((User_::safe_columns_tuple(), user_aggregates::all_columns))
+      .into_boxed();
+
+    if let Some(search_term) = self.search_term {
+      query = query.filter(user_::name.ilike(fuzzy_search(&search_term)));
+    }
+
+    query = match self.sort {
+      SortType::Hot => query
+        .order_by(user_aggregates::comment_score.desc())
+        .then_order_by(user_::published.desc()),
+      SortType::Active => query
+        .order_by(user_aggregates::comment_score.desc())
+        .then_order_by(user_::published.desc()),
+      SortType::New => query.order_by(user_::published.desc()),
+      SortType::TopAll => query.order_by(user_aggregates::comment_score.desc()),
+      SortType::TopYear => query
+        .filter(user_::published.gt(now - 1.years()))
+        .order_by(user_aggregates::comment_score.desc()),
+      SortType::TopMonth => query
+        .filter(user_::published.gt(now - 1.months()))
+        .order_by(user_aggregates::comment_score.desc()),
+      SortType::TopWeek => query
+        .filter(user_::published.gt(now - 1.weeks()))
+        .order_by(user_aggregates::comment_score.desc()),
+      SortType::TopDay => query
+        .filter(user_::published.gt(now - 1.days()))
+        .order_by(user_aggregates::comment_score.desc()),
+    };
+
+    let (limit, offset) = limit_and_offset(self.page, self.limit);
+    query = query.limit(limit).offset(offset);
+
+    let res = query.load::<UserViewSafeTuple>(self.conn)?;
+
+    Ok(UserViewSafe::to_vec(res))
+  }
+}
+
+impl ViewToVec for UserViewSafe {
+  type DbTuple = UserViewSafeTuple;
+  fn to_vec(users: Vec<Self::DbTuple>) -> Vec<Self> {
+    users
+      .iter()
+      .map(|a| Self {
+        user: a.0.to_owned(),
+        counts: a.1.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
index 351cdc0fc9cfc2cada783b150b9683ef8d3f1920..5574efadb5576c8c92605eb3504c7c2f102b9aa5 100644 (file)
@@ -10,9 +10,9 @@ path = "src/lib.rs"
 
 [dependencies]
 lemmy_utils = { path = "../lemmy_utils" }
-tokio = { version = "0.3", features = ["sync"] }
-strum = "0.19"
-strum_macros = "0.19"
-futures = "0.3.5"
-actix-web = { version = "3.0", default-features = false, features = ["rustls"] }
-log = "0.4"
+tokio = { version = "0.3.6", features = ["sync"] }
+strum = "0.20.0"
+strum_macros = "0.20.1"
+futures = "0.3.8"
+actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] }
+log = "0.4.11"
index 8cf522c3962a4a7ee58dc96348f007e3ffe32b87..e146230640265e4a8a4c697157043e6df8798ff4 100644 (file)
@@ -11,9 +11,9 @@ path = "src/lib.rs"
 [dependencies]
 lemmy_db = { path = "../lemmy_db" }
 lemmy_utils = { path = "../lemmy_utils" }
-serde = { version = "1.0", features = ["derive"] }
-log = "0.4"
-diesel = "1.4"
-actix-web = { version = "3.0" }
-chrono = { version = "0.4", features = ["serde"] }
-serde_json = { version = "1.0", features = ["preserve_order"]}
+serde = { version = "1.0.118", features = ["derive"] }
+log = "0.4.11"
+diesel = "1.4.5"
+actix-web = "3.3.2"
+chrono = { version = "0.4.19", features = ["serde"] }
+serde_json = { version = "1.0.60", features = ["preserve_order"] }
index 6479124f8eca606ba290e1b6fcd53f0e08c4cd44..fe65738d873d801d2f71b020f29994856e05ed7f 100644 (file)
@@ -1,4 +1,4 @@
-use lemmy_db::{comment_report::CommentReportView, comment_view::CommentView};
+use lemmy_db::views::{comment_report_view::CommentReportView, comment_view::CommentView};
 use serde::{Deserialize, Serialize};
 
 #[derive(Deserialize)]
@@ -35,7 +35,7 @@ pub struct RemoveComment {
 
 #[derive(Deserialize)]
 pub struct MarkCommentAsRead {
-  pub edit_id: i32,
+  pub comment_id: i32,
   pub read: bool,
   pub auth: String,
 }
@@ -49,9 +49,9 @@ pub struct SaveComment {
 
 #[derive(Serialize, Clone)]
 pub struct CommentResponse {
-  pub comment: CommentView,
-  pub recipient_ids: Vec<i32>,
-  pub form_id: Option<String>,
+  pub comment_view: CommentView,
+  pub recipient_ids: Vec<i32>, // TODO another way to do this? Maybe a UserMention belongs to Comment
+  pub form_id: Option<String>, // An optional front end ID, to tell which is coming back
 }
 
 #[derive(Deserialize)]
@@ -98,6 +98,7 @@ pub struct ResolveCommentReport {
 
 #[derive(Serialize, Deserialize, Clone, Debug)]
 pub struct ResolveCommentReportResponse {
+  // TODO this should probably return the view
   pub report_id: i32,
   pub resolved: bool,
 }
@@ -111,7 +112,7 @@ pub struct ListCommentReports {
   pub auth: String,
 }
 
-#[derive(Serialize, Deserialize, Clone, Debug)]
+#[derive(Serialize, Clone, Debug)]
 pub struct ListCommentReportsResponse {
   pub comments: Vec<CommentReportView>,
 }
index 3535c05a9fb029a54126dba188b064ee5dc1c884..65ea0aaaff0a97b6ccf785541702488c6fb7bb9c 100644 (file)
@@ -1,6 +1,10 @@
-use lemmy_db::{
-  community_view::{CommunityFollowerView, CommunityModeratorView, CommunityView},
-  user_view::UserView,
+use lemmy_db::views::{
+  community::{
+    community_follower_view::CommunityFollowerView,
+    community_moderator_view::CommunityModeratorView,
+    community_view::CommunityView,
+  },
+  user_view::UserViewSafe,
 };
 use serde::{Deserialize, Serialize};
 
@@ -13,7 +17,7 @@ pub struct GetCommunity {
 
 #[derive(Serialize)]
 pub struct GetCommunityResponse {
-  pub community: CommunityView,
+  pub community_view: CommunityView,
   pub moderators: Vec<CommunityModeratorView>,
   pub online: usize,
 }
@@ -32,7 +36,7 @@ pub struct CreateCommunity {
 
 #[derive(Serialize, Clone)]
 pub struct CommunityResponse {
-  pub community: CommunityView,
+  pub community_view: CommunityView,
 }
 
 #[derive(Deserialize, Debug)]
@@ -53,7 +57,7 @@ pub struct BanFromCommunity {
   pub community_id: i32,
   pub user_id: i32,
   pub ban: bool,
-  pub remove_data: Option<bool>,
+  pub remove_data: bool,
   pub reason: Option<String>,
   pub expires: Option<i64>,
   pub auth: String,
@@ -61,7 +65,7 @@ pub struct BanFromCommunity {
 
 #[derive(Serialize, Clone)]
 pub struct BanFromCommunityResponse {
-  pub user: UserView,
+  pub user_view: UserViewSafe,
   pub banned: bool,
 }
 
index 5d2e42733e193985105ba8bf65fc89b5c98cddc8..d19127384b773ab04864fd6e821aba4a4c5df388 100644 (file)
@@ -3,14 +3,15 @@ pub mod community;
 pub mod post;
 pub mod site;
 pub mod user;
-pub mod websocket;
 
 use diesel::PgConnection;
 use lemmy_db::{
-  comment::Comment,
-  post::Post,
-  user::User_,
-  user_mention::{UserMention, UserMentionForm},
+  source::{
+    comment::Comment,
+    post::Post,
+    user::User_,
+    user_mention::{UserMention, UserMentionForm},
+  },
   Crud,
   DbPool,
 };
@@ -98,10 +99,7 @@ fn do_send_local_notifs(
 
       // Allow this to fail softly, since comment edits might re-update or replace it
       // Let the uniqueness handle this fail
-      match UserMention::create(&conn, &user_mention_form) {
-        Ok(_mention) => (),
-        Err(_e) => error!("{}", &_e),
-      };
+      let _ = UserMention::create(&conn, &user_mention_form);
 
       // Send an email to those users that have notifications on
       if do_send_email && mention_user.send_notifications_to_email {
index 331c2dca45e944636d101b65697db5920af8176c..ac29d8f7807d373ad0f24ad697bc141fbedffa93 100644 (file)
@@ -1,7 +1,7 @@
-use lemmy_db::{
+use lemmy_db::views::{
   comment_view::CommentView,
-  community_view::{CommunityModeratorView, CommunityView},
-  post_report::PostReportView,
+  community::community_moderator_view::CommunityModeratorView,
+  post_report_view::PostReportView,
   post_view::PostView,
 };
 use serde::{Deserialize, Serialize};
@@ -18,7 +18,7 @@ pub struct CreatePost {
 
 #[derive(Serialize, Clone)]
 pub struct PostResponse {
-  pub post: PostView,
+  pub post_view: PostView,
 }
 
 #[derive(Deserialize)]
@@ -29,9 +29,8 @@ pub struct GetPost {
 
 #[derive(Serialize)]
 pub struct GetPostResponse {
-  pub post: PostView,
+  pub post_view: PostView,
   pub comments: Vec<CommentView>,
-  pub community: CommunityView,
   pub moderators: Vec<CommunityModeratorView>,
   pub online: usize,
 }
@@ -148,7 +147,7 @@ pub struct ListPostReports {
   pub auth: String,
 }
 
-#[derive(Serialize, Deserialize, Clone, Debug)]
+#[derive(Serialize, Clone, Debug)]
 pub struct ListPostReportsResponse {
   pub posts: Vec<PostReportView>,
 }
index 3f185928b4b2c1fb61322fb8a74da9a96b03f6e4..ff6c8a3912cb81a40b713043202d18ea8f89cea4 100644 (file)
@@ -1,12 +1,23 @@
 use lemmy_db::{
-  category::*,
-  comment_view::*,
-  community_view::*,
-  moderator_views::*,
-  post_view::*,
-  site_view::*,
-  user::*,
-  user_view::*,
+  source::{category::*, user::*},
+  views::{
+    comment_view::CommentView,
+    community::community_view::CommunityView,
+    moderator::{
+      mod_add_community_view::ModAddCommunityView,
+      mod_add_view::ModAddView,
+      mod_ban_from_community_view::ModBanFromCommunityView,
+      mod_ban_view::ModBanView,
+      mod_lock_post_view::ModLockPostView,
+      mod_remove_comment_view::ModRemoveCommentView,
+      mod_remove_community_view::ModRemoveCommunityView,
+      mod_remove_post_view::ModRemovePostView,
+      mod_sticky_post_view::ModStickyPostView,
+    },
+    post_view::PostView,
+    site_view::SiteView,
+    user_view::UserViewSafe,
+  },
 };
 use serde::{Deserialize, Serialize};
 
@@ -36,7 +47,7 @@ pub struct SearchResponse {
   pub comments: Vec<CommentView>,
   pub posts: Vec<PostView>,
   pub communities: Vec<CommunityView>,
-  pub users: Vec<UserView>,
+  pub users: Vec<UserViewSafe>,
 }
 
 #[derive(Deserialize)]
@@ -91,14 +102,14 @@ pub struct GetSite {
 
 #[derive(Serialize, Clone)]
 pub struct SiteResponse {
-  pub site: SiteView,
+  pub site_view: SiteView,
 }
 
 #[derive(Serialize)]
 pub struct GetSiteResponse {
-  pub site: Option<SiteView>,
-  pub admins: Vec<UserView>,
-  pub banned: Vec<UserView>,
+  pub site_view: Option<SiteView>, // Because the site might not be set up yet
+  pub admins: Vec<UserViewSafe>,
+  pub banned: Vec<UserViewSafe>,
   pub online: usize,
   pub version: String,
   pub my_user: Option<User_>,
index bf4a362860557365dedfe8c8f1ec2967ab1371c7..52871696cf71cabd7ce4b080fe94280604536283 100644 (file)
@@ -1,10 +1,13 @@
-use lemmy_db::{
-  comment_view::{CommentView, ReplyView},
-  community_view::{CommunityFollowerView, CommunityModeratorView},
+use lemmy_db::views::{
+  comment_view::CommentView,
+  community::{
+    community_follower_view::CommunityFollowerView,
+    community_moderator_view::CommunityModeratorView,
+  },
   post_view::PostView,
   private_message_view::PrivateMessageView,
   user_mention_view::UserMentionView,
-  user_view::UserView,
+  user_view::{UserViewDangerous, UserViewSafe},
 };
 use serde::{Deserialize, Serialize};
 
@@ -45,8 +48,8 @@ pub struct CaptchaResponse {
 pub struct SaveUserSettings {
   pub show_nsfw: bool,
   pub theme: String,
-  pub default_sort_type: i16,
-  pub default_listing_type: i16,
+  pub default_sort_type: String,
+  pub default_listing_type: String,
   pub lang: String,
   pub avatar: Option<String>,
   pub banner: Option<String>,
@@ -81,7 +84,8 @@ pub struct GetUserDetails {
 
 #[derive(Serialize)]
 pub struct GetUserDetailsResponse {
-  pub user: UserView,
+  pub user_view: Option<UserViewSafe>,
+  pub user_view_dangerous: Option<UserViewDangerous>,
   pub follows: Vec<CommunityFollowerView>,
   pub moderates: Vec<CommunityModeratorView>,
   pub comments: Vec<CommentView>,
@@ -90,7 +94,7 @@ pub struct GetUserDetailsResponse {
 
 #[derive(Serialize)]
 pub struct GetRepliesResponse {
-  pub replies: Vec<ReplyView>,
+  pub replies: Vec<CommentView>,
 }
 
 #[derive(Serialize)]
@@ -112,14 +116,14 @@ pub struct AddAdmin {
 
 #[derive(Serialize, Clone)]
 pub struct AddAdminResponse {
-  pub admins: Vec<UserView>,
+  pub admins: Vec<UserViewSafe>,
 }
 
 #[derive(Deserialize)]
 pub struct BanUser {
   pub user_id: i32,
   pub ban: bool,
-  pub remove_data: Option<bool>,
+  pub remove_data: bool,
   pub reason: Option<String>,
   pub expires: Option<i64>,
   pub auth: String,
@@ -127,7 +131,7 @@ pub struct BanUser {
 
 #[derive(Serialize, Clone)]
 pub struct BanUserResponse {
-  pub user: UserView,
+  pub user_view: UserViewSafe,
   pub banned: bool,
 }
 
@@ -158,7 +162,7 @@ pub struct MarkUserMentionAsRead {
 
 #[derive(Serialize, Clone)]
 pub struct UserMentionResponse {
-  pub mention: UserMentionView,
+  pub user_mention_view: UserMentionView,
 }
 
 #[derive(Deserialize)]
@@ -220,12 +224,12 @@ pub struct GetPrivateMessages {
 
 #[derive(Serialize, Clone)]
 pub struct PrivateMessagesResponse {
-  pub messages: Vec<PrivateMessageView>,
+  pub private_messages: Vec<PrivateMessageView>,
 }
 
 #[derive(Serialize, Clone)]
 pub struct PrivateMessageResponse {
-  pub message: PrivateMessageView,
+  pub private_message_view: PrivateMessageView,
 }
 
 #[derive(Deserialize, Debug)]
diff --git a/lemmy_structs/src/websocket.rs b/lemmy_structs/src/websocket.rs
deleted file mode 100644 (file)
index 8b13789..0000000
+++ /dev/null
@@ -1 +0,0 @@
-
index b0a0f3e6b5430b1c93dd101fd9e93ea49e4b9019..e90015f0ebfaff7a60406d40ceb6e6521f4aeb76 100644 (file)
@@ -8,22 +8,22 @@ name = "lemmy_utils"
 path = "src/lib.rs"
 
 [dependencies]
-regex = "1.3"
-config = { version = "0.10", default-features = false, features = ["hjson"] }
-chrono = { version = "0.4", features = ["serde"] }
-lettre = "0.10.0-alpha.3"
-log = "0.4"
-itertools = "0.9"
-rand = "0.7"
-percent-encoding = "2.1"
-serde = { version = "1.0", features = ["derive"] }
-serde_json = { version = "1.0", features = ["preserve_order"]}
-thiserror = "1.0"
-comrak = { version = "0.8", default-features = false }
-lazy_static = "1.3"
-openssl = "0.10"
-url = { version = "2.1", features = ["serde"] }
-actix-web = { version = "3.0", default-features = false, features = ["rustls"] }
-actix-rt = { version = "1.1", default-features = false  }
-anyhow = "1.0"
-reqwest = { version = "0.10", features = ["json"] }
+regex = "1.4.2"
+config = { version = "0.10.1", default-features = false, features = ["hjson"] }
+chrono = { version = "0.4.19", features = ["serde"] }
+lettre = "0.10.0-alpha.4"
+log = "0.4.11"
+itertools = "0.9.0"
+rand = "0.7.3"
+percent-encoding = "2.1.0"
+serde = { version = "1.0.118", features = ["derive"] }
+serde_json = { version = "1.0.60", features = ["preserve_order"] }
+thiserror = "1.0.22"
+comrak = { version = "0.9.0", default-features = false }
+lazy_static = "1.4.0"
+openssl = "0.10.31"
+url = { version = "2.2.0", features = ["serde"] }
+actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] }
+actix-rt = { version = "1.1.1", default-features = false }
+anyhow = "1.0.35"
+reqwest = { version = "0.10.10", features = ["json"] }
index 43b6cdaf0f6863b74290d794d790f14b99aaa4eb..ed0ba4ce0dcd9f85fe5295a78d9568423379dab2 100644 (file)
@@ -13,16 +13,16 @@ lemmy_utils = { path = "../lemmy_utils" }
 lemmy_structs = { path = "../lemmy_structs" }
 lemmy_db = { path = "../lemmy_db" }
 lemmy_rate_limit = { path = "../lemmy_rate_limit" }
-reqwest = { version = "0.10", features = ["json"] }
-log = "0.4"
-rand = "0.7"
-serde = { version = "1.0", features = ["derive"] }
-serde_json = { version = "1.0", features = ["preserve_order"]}
-actix = "0.10"
-anyhow = "1.0"
-diesel = "1.4"
-background-jobs = " 0.8"
-tokio = "0.3"
-strum = "0.19"
-strum_macros = "0.19"
-chrono = { version = "0.4", features = ["serde"] }
+reqwest = { version = "0.10.10", features = ["json"] }
+log = "0.4.11"
+rand = "0.7.3"
+serde = { version = "1.0.118", features = ["derive"] }
+serde_json = { version = "1.0.60", features = ["preserve_order"] }
+actix = "0.10.0"
+anyhow = "1.0.35"
+diesel = "1.4.5"
+background-jobs = "0.8.0"
+tokio = "0.3.6"
+strum = "0.20.0"
+strum_macros = "0.20.1"
+chrono = { version = "0.4.19", features = ["serde"] }
index 0be54c33f8fb2241bf3ac41811aad652195d8699..ece5d3534fccec3e423b6c997bbd81b25689b885 100644 (file)
@@ -328,9 +328,10 @@ impl ChatServer {
     comment: &CommentResponse,
     websocket_id: Option<ConnectionId>,
   ) -> Result<(), LemmyError> {
-    let mut comment_reply_sent = comment.clone();
-    comment_reply_sent.comment.my_vote = None;
-    comment_reply_sent.comment.user_id = None;
+    let comment_reply_sent = comment.clone();
+    // TODO what is this here
+    // comment_reply_sent.comment_view.my_vote = None;
+    // comment_reply_sent.comment.user_id = None;
 
     let mut comment_post_sent = comment_reply_sent.clone();
     comment_post_sent.recipient_ids = Vec::new();
@@ -339,7 +340,7 @@ impl ChatServer {
     self.send_post_room_message(
       user_operation,
       &comment_post_sent,
-      comment_post_sent.comment.post_id,
+      comment_post_sent.comment_view.post.id,
       websocket_id,
     )?;
 
@@ -358,7 +359,7 @@ impl ChatServer {
     self.send_community_room_message(
       user_operation,
       &comment_post_sent,
-      comment.comment.community_id,
+      comment.comment_view.community.id,
       websocket_id,
     )?;
 
@@ -368,22 +369,28 @@ impl ChatServer {
   pub fn send_post(
     &self,
     user_operation: &UserOperation,
-    post: &PostResponse,
+    post_res: &PostResponse,
     websocket_id: Option<ConnectionId>,
   ) -> Result<(), LemmyError> {
-    let community_id = post.post.community_id;
+    let community_id = post_res.post_view.community.id;
 
     // Don't send my data with it
-    let mut post_sent = post.clone();
-    post_sent.post.my_vote = None;
-    post_sent.post.user_id = None;
+    // TODO no idea what to do here
+    // let mut post_sent = post_res.clone();
+    // post_sent.post.my_vote = None;
+    // post_sent.post.user_id = None;
 
     // Send it to /c/all and that community
-    self.send_community_room_message(user_operation, &post_sent, 0, websocket_id)?;
-    self.send_community_room_message(user_operation, &post_sent, community_id, websocket_id)?;
+    self.send_community_room_message(user_operation, &post_res, 0, websocket_id)?;
+    self.send_community_room_message(user_operation, &post_res, community_id, websocket_id)?;
 
     // Send it to the post room
-    self.send_post_room_message(user_operation, &post_sent, post.post.id, websocket_id)?;
+    self.send_post_room_message(
+      user_operation,
+      &post_res,
+      post_res.post_view.post.id,
+      websocket_id,
+    )?;
 
     Ok(())
   }
diff --git a/migrations/2020-12-02-152437_create_site_aggregates/down.sql b/migrations/2020-12-02-152437_create_site_aggregates/down.sql
new file mode 100644 (file)
index 0000000..2a2aa97
--- /dev/null
@@ -0,0 +1,13 @@
+-- Site aggregates
+drop table site_aggregates;
+drop trigger site_aggregates_site on site;
+drop trigger site_aggregates_user on user_;
+drop trigger site_aggregates_post on post;
+drop trigger site_aggregates_comment on comment;
+drop trigger site_aggregates_community on community;
+drop function 
+  site_aggregates_site,
+  site_aggregates_user,
+  site_aggregates_post,
+  site_aggregates_comment,
+  site_aggregates_community;
diff --git a/migrations/2020-12-02-152437_create_site_aggregates/up.sql b/migrations/2020-12-02-152437_create_site_aggregates/up.sql
new file mode 100644 (file)
index 0000000..b10a5f4
--- /dev/null
@@ -0,0 +1,126 @@
+-- Add site aggregates
+create table site_aggregates (
+  id serial primary key,
+  site_id int references site on update cascade on delete cascade not null,
+  users bigint not null default 1,
+  posts bigint not null default 0,
+  comments bigint not null default 0,
+  communities bigint not null default 0
+);
+
+insert into site_aggregates (site_id, users, posts, comments, communities)
+  select id as site_id,
+  ( select coalesce(count(*), 0) from user_) as users, 
+  ( select coalesce(count(*), 0) from post) as posts,
+  ( select coalesce(count(*), 0) from comment) as comments,
+  ( select coalesce(count(*), 0) from community) as communities
+  from site;
+
+-- initial site add
+create function site_aggregates_site()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    insert into site_aggregates (site_id) values (NEW.id);
+  ELSIF (TG_OP = 'DELETE') THEN
+    delete from site_aggregates where site_id = OLD.id;
+  END IF;
+  return null;
+end $$;
+
+create trigger site_aggregates_site
+after insert or delete on site
+for each row
+execute procedure site_aggregates_site();
+
+-- Add site aggregate triggers
+-- user
+create or replace function site_aggregates_user()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update site_aggregates 
+    set users = users + 1;
+  ELSIF (TG_OP = 'DELETE') THEN
+    -- Join to site since the creator might not be there anymore
+    update site_aggregates sa
+    set users = users - 1
+    from site s
+    where sa.site_id = s.id;
+  END IF;
+  return null;
+end $$;
+
+create trigger site_aggregates_user
+after insert or delete on user_
+for each row
+execute procedure site_aggregates_user();
+
+-- post
+create function site_aggregates_post()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update site_aggregates 
+    set posts = posts + 1;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update site_aggregates sa
+    set posts = posts - 1
+    from site s
+    where sa.site_id = s.id;
+  END IF;
+  return null;
+end $$;
+
+create trigger site_aggregates_post
+after insert or delete on post
+for each row
+execute procedure site_aggregates_post();
+
+-- comment
+create function site_aggregates_comment()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update site_aggregates 
+    set comments = comments + 1;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update site_aggregates sa
+    set comments = comments - 1
+    from site s
+    where sa.site_id = s.id;
+  END IF;
+  return null;
+end $$;
+
+create trigger site_aggregates_comment
+after insert or delete on comment
+for each row
+execute procedure site_aggregates_comment();
+
+-- community
+create function site_aggregates_community()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update site_aggregates 
+    set communities = communities + 1;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update site_aggregates sa
+    set communities = communities - 1
+    from site s
+    where sa.site_id = s.id;
+  END IF;
+  return null;
+end $$;
+
+create trigger site_aggregates_community
+after insert or delete on community
+for each row
+execute procedure site_aggregates_community();
+
diff --git a/migrations/2020-12-03-035643_create_user_aggregates/down.sql b/migrations/2020-12-03-035643_create_user_aggregates/down.sql
new file mode 100644 (file)
index 0000000..a7b5e47
--- /dev/null
@@ -0,0 +1,13 @@
+-- User aggregates
+drop table user_aggregates;
+drop trigger user_aggregates_user on user_;
+drop trigger user_aggregates_post_count on post;
+drop trigger user_aggregates_post_score on post_like;
+drop trigger user_aggregates_comment_count on comment;
+drop trigger user_aggregates_comment_score on comment_like;
+drop function 
+  user_aggregates_user, 
+  user_aggregates_post_count,
+  user_aggregates_post_score,
+  user_aggregates_comment_count,
+  user_aggregates_comment_score;
diff --git a/migrations/2020-12-03-035643_create_user_aggregates/up.sql b/migrations/2020-12-03-035643_create_user_aggregates/up.sql
new file mode 100644 (file)
index 0000000..7b4c83a
--- /dev/null
@@ -0,0 +1,178 @@
+-- Add user aggregates
+create table user_aggregates (
+  id serial primary key,
+  user_id int references user_ on update cascade on delete cascade not null,
+  post_count bigint not null default 0,
+  post_score bigint not null default 0,
+  comment_count bigint not null default 0,
+  comment_score bigint not null default 0,
+  unique (user_id)
+);
+
+insert into user_aggregates (user_id, post_count, post_score, comment_count, comment_score)
+  select u.id,
+  coalesce(pd.posts, 0),
+  coalesce(pd.score, 0),
+  coalesce(cd.comments, 0),
+  coalesce(cd.score, 0)
+  from user_ u
+  left join (
+    select p.creator_id,
+      count(distinct p.id) as posts,
+      sum(pl.score) as score
+      from post p
+      left join post_like pl on p.id = pl.post_id
+      group by p.creator_id
+    ) pd on u.id = pd.creator_id
+  left join ( 
+    select c.creator_id,
+    count(distinct c.id) as comments,
+    sum(cl.score) as score
+    from comment c
+    left join comment_like cl on c.id = cl.comment_id
+    group by c.creator_id
+  ) cd on u.id = cd.creator_id;
+
+
+-- Add user aggregate triggers
+
+-- initial user add
+create function user_aggregates_user()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    insert into user_aggregates (user_id) values (NEW.id);
+  ELSIF (TG_OP = 'DELETE') THEN
+    delete from user_aggregates where user_id = OLD.id;
+  END IF;
+  return null;
+end $$;
+
+create trigger user_aggregates_user
+after insert or delete on user_
+for each row
+execute procedure user_aggregates_user();
+
+-- post count
+create function user_aggregates_post_count()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update user_aggregates 
+    set post_count = post_count + 1 where user_id = NEW.creator_id;
+
+  ELSIF (TG_OP = 'DELETE') THEN
+    update user_aggregates 
+    set post_count = post_count - 1 where user_id = OLD.creator_id;
+
+    -- If the post gets deleted, the score calculation trigger won't fire, 
+    -- so you need to re-calculate
+    update user_aggregates ua
+    set post_score = pd.score
+    from (
+      select u.id,
+      coalesce(0, sum(pl.score)) as score
+      -- User join because posts could be empty
+      from user_ u 
+      left join post p on u.id = p.creator_id
+      left join post_like pl on p.id = pl.post_id
+      group by u.id
+    ) pd 
+    where ua.user_id = OLD.creator_id;
+
+  END IF;
+  return null;
+end $$;
+
+create trigger user_aggregates_post_count
+after insert or delete on post
+for each row
+execute procedure user_aggregates_post_count();
+
+-- post score
+create function user_aggregates_post_score()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    -- Need to get the post creator, not the voter
+    update user_aggregates ua
+    set post_score = post_score + NEW.score
+    from post p
+    where ua.user_id = p.creator_id and p.id = NEW.post_id;
+    
+  ELSIF (TG_OP = 'DELETE') THEN
+    update user_aggregates ua
+    set post_score = post_score - OLD.score
+    from post p
+    where ua.user_id = p.creator_id and p.id = OLD.post_id;
+  END IF;
+  return null;
+end $$;
+
+create trigger user_aggregates_post_score
+after insert or delete on post_like
+for each row
+execute procedure user_aggregates_post_score();
+
+-- comment count
+create function user_aggregates_comment_count()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update user_aggregates 
+    set comment_count = comment_count + 1 where user_id = NEW.creator_id;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update user_aggregates 
+    set comment_count = comment_count - 1 where user_id = OLD.creator_id;
+
+    -- If the comment gets deleted, the score calculation trigger won't fire, 
+    -- so you need to re-calculate
+    update user_aggregates ua
+    set comment_score = cd.score
+    from (
+      select u.id,
+      coalesce(0, sum(cl.score)) as score
+      -- User join because comments could be empty
+      from user_ u 
+      left join comment c on u.id = c.creator_id
+      left join comment_like cl on c.id = cl.comment_id
+      group by u.id
+    ) cd 
+    where ua.user_id = OLD.creator_id;
+  END IF;
+  return null;
+end $$;
+
+create trigger user_aggregates_comment_count
+after insert or delete on comment
+for each row
+execute procedure user_aggregates_comment_count();
+
+-- comment score
+create function user_aggregates_comment_score()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    -- Need to get the post creator, not the voter
+    update user_aggregates ua
+    set comment_score = comment_score + NEW.score
+    from comment c
+    where ua.user_id = c.creator_id and c.id = NEW.comment_id;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update user_aggregates ua
+    set comment_score = comment_score - OLD.score
+    from comment c
+    where ua.user_id = c.creator_id and c.id = OLD.comment_id;
+  END IF;
+  return null;
+end $$;
+
+create trigger user_aggregates_comment_score
+after insert or delete on comment_like
+for each row
+execute procedure user_aggregates_comment_score();
diff --git a/migrations/2020-12-04-183345_create_community_aggregates/down.sql b/migrations/2020-12-04-183345_create_community_aggregates/down.sql
new file mode 100644 (file)
index 0000000..fc0ffd2
--- /dev/null
@@ -0,0 +1,11 @@
+-- community aggregates
+drop table community_aggregates;
+drop trigger community_aggregates_community on community;
+drop trigger community_aggregates_post_count on post;
+drop trigger community_aggregates_comment_count on comment;
+drop trigger community_aggregates_subscriber_count on community_follower;
+drop function 
+  community_aggregates_community,
+  community_aggregates_post_count,
+  community_aggregates_comment_count,
+  community_aggregates_subscriber_count;
diff --git a/migrations/2020-12-04-183345_create_community_aggregates/up.sql b/migrations/2020-12-04-183345_create_community_aggregates/up.sql
new file mode 100644 (file)
index 0000000..18a6229
--- /dev/null
@@ -0,0 +1,136 @@
+-- Add community aggregates
+create table community_aggregates (
+  id serial primary key,
+  community_id int references community on update cascade on delete cascade not null,
+  subscribers bigint not null default 0,
+  posts bigint not null default 0,
+  comments bigint not null default 0,
+  unique (community_id)
+);
+
+insert into community_aggregates (community_id, subscribers, posts, comments)
+  select 
+    c.id,
+    coalesce(cf.subs, 0) as subscribers,
+    coalesce(cd.posts, 0) as posts,
+    coalesce(cd.comments, 0) as comments
+  from community c
+  left join ( 
+    select 
+      p.community_id,
+      count(distinct p.id) as posts,
+      count(distinct ct.id) as comments
+    from post p
+    left join comment ct on p.id = ct.post_id
+    group by p.community_id
+  ) cd on cd.community_id = c.id
+  left join ( 
+    select 
+      community_follower.community_id,
+      count(*) as subs
+    from community_follower
+    group by community_follower.community_id
+  ) cf on cf.community_id = c.id;
+
+-- Add community aggregate triggers
+
+-- initial community add
+create function community_aggregates_community()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    insert into community_aggregates (community_id) values (NEW.id);
+  ELSIF (TG_OP = 'DELETE') THEN
+    delete from community_aggregates where community_id = OLD.id;
+  END IF;
+  return null;
+end $$;
+
+create trigger community_aggregates_community
+after insert or delete on community
+for each row
+execute procedure community_aggregates_community();
+-- post count
+create function community_aggregates_post_count()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update community_aggregates 
+    set posts = posts + 1 where community_id = NEW.community_id;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update community_aggregates 
+    set posts = posts - 1 where community_id = OLD.community_id;
+
+    -- Update the counts if the post got deleted
+    update community_aggregates ca
+    set posts = coalesce(cd.posts, 0),
+    comments = coalesce(cd.comments, 0)
+    from ( 
+      select 
+      c.id,
+      count(distinct p.id) as posts,
+      count(distinct ct.id) as comments
+      from community c
+      left join post p on c.id = p.community_id
+      left join comment ct on p.id = ct.post_id
+      group by c.id
+    ) cd 
+    where ca.community_id = OLD.community_id;
+  END IF;
+  return null;
+end $$;
+
+create trigger community_aggregates_post_count
+after insert or delete on post
+for each row
+execute procedure community_aggregates_post_count();
+
+-- comment count
+create function community_aggregates_comment_count()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update community_aggregates ca
+    set comments = comments + 1 from comment c, post p
+    where p.id = c.post_id 
+    and p.id = NEW.post_id 
+    and ca.community_id = p.community_id;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update community_aggregates ca
+    set comments = comments - 1 from comment c, post p
+    where p.id = c.post_id 
+    and p.id = OLD.post_id 
+    and ca.community_id = p.community_id;
+
+  END IF;
+  return null;
+end $$;
+
+create trigger community_aggregates_comment_count
+after insert or delete on comment
+for each row
+execute procedure community_aggregates_comment_count();
+
+-- subscriber count
+create function community_aggregates_subscriber_count()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update community_aggregates 
+    set subscribers = subscribers + 1 where community_id = NEW.community_id;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update community_aggregates 
+    set subscribers = subscribers - 1 where community_id = OLD.community_id;
+  END IF;
+  return null;
+end $$;
+
+create trigger community_aggregates_subscriber_count
+after insert or delete on community_follower
+for each row
+execute procedure community_aggregates_subscriber_count();
+
diff --git a/migrations/2020-12-10-152350_create_post_aggregates/down.sql b/migrations/2020-12-10-152350_create_post_aggregates/down.sql
new file mode 100644 (file)
index 0000000..113f26d
--- /dev/null
@@ -0,0 +1,9 @@
+-- post aggregates
+drop table post_aggregates;
+drop trigger post_aggregates_post on post;
+drop trigger post_aggregates_comment_count on comment;
+drop trigger post_aggregates_score on post_like;
+drop function 
+  post_aggregates_post,
+  post_aggregates_comment_count,
+  post_aggregates_score;
diff --git a/migrations/2020-12-10-152350_create_post_aggregates/up.sql b/migrations/2020-12-10-152350_create_post_aggregates/up.sql
new file mode 100644 (file)
index 0000000..b3dc627
--- /dev/null
@@ -0,0 +1,113 @@
+-- Add post aggregates
+create table post_aggregates (
+  id serial primary key,
+  post_id int references post on update cascade on delete cascade not null,
+  comments bigint not null default 0,
+  score bigint not null default 0,
+  upvotes bigint not null default 0,
+  downvotes bigint not null default 0,
+  newest_comment_time timestamp not null default now(),
+  unique (post_id)
+);
+
+insert into post_aggregates (post_id, comments, score, upvotes, downvotes, newest_comment_time)
+  select 
+    p.id,
+    coalesce(ct.comments, 0::bigint) as comments,
+    coalesce(pl.score, 0::bigint) as score,
+    coalesce(pl.upvotes, 0::bigint) as upvotes,
+    coalesce(pl.downvotes, 0::bigint) as downvotes,
+    greatest(ct.recent_comment_time, p.published) as newest_activity_time
+  from post p
+  left join ( 
+    select comment.post_id,
+    count(*) as comments,
+    max(comment.published) as recent_comment_time
+    from comment
+    group by comment.post_id
+  ) ct on ct.post_id = p.id
+  left join ( 
+    select post_like.post_id,
+    sum(post_like.score) as score,
+    sum(post_like.score) filter (where post_like.score = 1) as upvotes,
+    -sum(post_like.score) filter (where post_like.score = '-1'::integer) as downvotes
+    from post_like
+    group by post_like.post_id
+  ) pl on pl.post_id = p.id;
+
+-- Add community aggregate triggers
+
+-- initial post add
+create function post_aggregates_post()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    insert into post_aggregates (post_id) values (NEW.id);
+  ELSIF (TG_OP = 'DELETE') THEN
+    delete from post_aggregates where post_id = OLD.id;
+  END IF;
+  return null;
+end $$;
+
+create trigger post_aggregates_post
+after insert or delete on post
+for each row
+execute procedure post_aggregates_post();
+
+-- comment count
+create function post_aggregates_comment_count()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update post_aggregates pa
+    set comments = comments + 1,
+    newest_comment_time = NEW.published
+    where pa.post_id = NEW.post_id;
+  ELSIF (TG_OP = 'DELETE') THEN
+    -- Join to post because that post may not exist anymore
+    update post_aggregates pa
+    set comments = comments - 1
+    from post p
+    where pa.post_id = p.id
+    and pa.post_id = OLD.post_id;
+  END IF;
+  return null;
+end $$;
+
+create trigger post_aggregates_comment_count
+after insert or delete on comment
+for each row
+execute procedure post_aggregates_comment_count();
+
+-- post score
+create function post_aggregates_score()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update post_aggregates pa
+    set score = score + NEW.score,
+    upvotes = case when NEW.score = 1 then upvotes + 1 else upvotes end,
+    downvotes = case when NEW.score = -1 then downvotes + 1 else downvotes end
+    where pa.post_id = NEW.post_id;
+
+  ELSIF (TG_OP = 'DELETE') THEN
+    -- Join to post because that post may not exist anymore
+    update post_aggregates pa
+    set score = score - OLD.score,
+    upvotes = case when OLD.score = 1 then upvotes - 1 else upvotes end,
+    downvotes = case when OLD.score = -1 then downvotes - 1 else downvotes end
+    from post p
+    where pa.post_id = p.id
+    and pa.post_id = OLD.post_id;
+
+  END IF;
+  return null;
+end $$;
+
+create trigger post_aggregates_score
+after insert or delete on post_like
+for each row
+execute procedure post_aggregates_score();
diff --git a/migrations/2020-12-14-020038_create_comment_aggregates/down.sql b/migrations/2020-12-14-020038_create_comment_aggregates/down.sql
new file mode 100644 (file)
index 0000000..6fd9ddc
--- /dev/null
@@ -0,0 +1,7 @@
+-- comment aggregates
+drop table comment_aggregates;
+drop trigger comment_aggregates_comment on comment;
+drop trigger comment_aggregates_score on comment_like;
+drop function 
+  comment_aggregates_comment,
+  comment_aggregates_score;
diff --git a/migrations/2020-12-14-020038_create_comment_aggregates/up.sql b/migrations/2020-12-14-020038_create_comment_aggregates/up.sql
new file mode 100644 (file)
index 0000000..1a168be
--- /dev/null
@@ -0,0 +1,82 @@
+-- Add comment aggregates
+create table comment_aggregates (
+  id serial primary key,
+  comment_id int references comment on update cascade on delete cascade not null,
+  score bigint not null default 0,
+  upvotes bigint not null default 0,
+  downvotes bigint not null default 0,
+  unique (comment_id)
+);
+
+insert into comment_aggregates (comment_id, score, upvotes, downvotes)
+  select 
+    c.id,
+    COALESCE(cl.total, 0::bigint) AS score,
+    COALESCE(cl.up, 0::bigint) AS upvotes,
+    COALESCE(cl.down, 0::bigint) AS downvotes
+  from comment c
+  left join ( select l.comment_id as id,
+    sum(l.score) as total,
+    count(
+      case
+      when l.score = 1 then 1
+      else null::integer
+      end) as up,
+    count(
+      case
+      when l.score = '-1'::integer then 1
+      else null::integer
+      end) as down
+    from comment_like l
+    group by l.comment_id) cl on cl.id = c.id;
+
+-- Add comment aggregate triggers
+
+-- initial comment add
+create function comment_aggregates_comment()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    insert into comment_aggregates (comment_id) values (NEW.id);
+  ELSIF (TG_OP = 'DELETE') THEN
+    delete from comment_aggregates where comment_id = OLD.id;
+  END IF;
+  return null;
+end $$;
+
+create trigger comment_aggregates_comment
+after insert or delete on comment
+for each row
+execute procedure comment_aggregates_comment();
+
+-- comment score
+create function comment_aggregates_score()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update comment_aggregates ca
+    set score = score + NEW.score,
+    upvotes = case when NEW.score = 1 then upvotes + 1 else upvotes end,
+    downvotes = case when NEW.score = -1 then downvotes + 1 else downvotes end
+    where ca.comment_id = NEW.comment_id;
+
+  ELSIF (TG_OP = 'DELETE') THEN
+    -- Join to comment because that comment may not exist anymore
+    update comment_aggregates ca
+    set score = score - OLD.score,
+    upvotes = case when OLD.score = 1 then upvotes - 1 else upvotes end,
+    downvotes = case when OLD.score = -1 then downvotes - 1 else downvotes end
+    from comment c
+    where ca.comment_id = c.id
+    and ca.comment_id = OLD.comment_id;
+
+  END IF;
+  return null;
+end $$;
+
+create trigger comment_aggregates_score
+after insert or delete on comment_like
+for each row
+execute procedure comment_aggregates_score();
diff --git a/migrations/2020-12-17-030456_create_alias_views/down.sql b/migrations/2020-12-17-030456_create_alias_views/down.sql
new file mode 100644 (file)
index 0000000..66ded96
--- /dev/null
@@ -0,0 +1 @@
+drop view user_alias_1, user_alias_2, comment_alias_1;
diff --git a/migrations/2020-12-17-030456_create_alias_views/up.sql b/migrations/2020-12-17-030456_create_alias_views/up.sql
new file mode 100644 (file)
index 0000000..3d3b1b4
--- /dev/null
@@ -0,0 +1,7 @@
+-- Some view that act as aliases 
+-- unfortunately necessary, since diesel doesn't have self joins
+-- or alias support yet
+create view user_alias_1 as select * from user_;
+create view user_alias_2 as select * from user_;
+create view comment_alias_1 as select * from comment;
+
diff --git a/migrations/2020-12-17-031053_remove_fast_tables_and_views/down.sql b/migrations/2020-12-17-031053_remove_fast_tables_and_views/down.sql
new file mode 100644 (file)
index 0000000..1c5d776
--- /dev/null
@@ -0,0 +1,4 @@
+-- There is no restore for this, it would require every view, table, index, etc.
+-- If you want to save past this point, you should make a DB backup.
+
+select * from user_ limit 1;
diff --git a/migrations/2020-12-17-031053_remove_fast_tables_and_views/up.sql b/migrations/2020-12-17-031053_remove_fast_tables_and_views/up.sql
new file mode 100644 (file)
index 0000000..cafa48c
--- /dev/null
@@ -0,0 +1,64 @@
+-- Drop triggers
+drop trigger if exists refresh_comment on comment;
+drop trigger if exists refresh_comment_like on comment_like;
+drop trigger if exists refresh_community on community;
+drop trigger if exists refresh_community_follower on community_follower;
+drop trigger if exists refresh_community_user_ban on community_user_ban;
+drop trigger if exists refresh_post on post;
+drop trigger if exists refresh_post_like on post_like;
+drop trigger if exists refresh_user on user_;
+
+-- Drop functions
+drop function if exists
+refresh_comment,
+refresh_comment_like,
+refresh_community,
+refresh_community_follower,
+refresh_community_user_ban,
+refresh_post,
+refresh_post_like,
+refresh_private_message,
+refresh_user
+cascade;
+
+-- Drop views
+drop view if exists
+comment_aggregates_view, 
+comment_fast_view,
+comment_report_view,
+comment_view,
+community_aggregates_view,
+community_fast_view,
+community_follower_view,
+community_moderator_view,
+community_user_ban_view,
+community_view,
+mod_add_community_view,
+mod_add_view,
+mod_ban_from_community_view,
+mod_ban_view,
+mod_lock_post_view,
+mod_remove_comment_view,
+mod_remove_community_view,
+mod_remove_post_view,
+mod_sticky_post_view,
+post_aggregates_view,
+post_fast_view,
+post_report_view,
+post_view,
+private_message_view,
+reply_fast_view,
+site_view,
+user_mention_fast_view,
+user_mention_view,
+user_view
+cascade;
+
+-- Drop fast tables
+drop table if exists
+comment_aggregates_fast,
+community_aggregates_fast,
+post_aggregates_fast,
+user_fast
+cascade;
+
index c41f5bd966ae53c9a0bf90ecc7a6c3f0f4c43edf..7a749e9b4a54c4aec610e7dfed46ffc7ff5afea0 100644 (file)
@@ -4,12 +4,14 @@ use diesel::{
   *,
 };
 use lemmy_db::{
-  comment::Comment,
-  community::{Community, CommunityForm},
   naive_now,
-  post::Post,
-  private_message::PrivateMessage,
-  user::{UserForm, User_},
+  source::{
+    comment::Comment,
+    community::{Community, CommunityForm},
+    post::Post,
+    private_message::PrivateMessage,
+    user::{UserForm, User_},
+  },
   Crud,
 };
 use lemmy_utils::{
index 167797d7da90e088e6924e5d87bee8cdbcb8d47b..199e14ac9b391dd561f1b21d33e86d1006d6215a 100644 (file)
@@ -7,7 +7,7 @@ use serde::Deserialize;
 
 pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
   cfg.service(
-    web::scope("/api/v1")
+    web::scope("/api/v2")
       // Websockets
       .service(web::resource("/ws").to(super::websocket::chat_route))
       // Site
index fc4a313724eca25014441bd82f921efe3dcfc73b..7a4801f403dd014f28a2b2606d39cdf4dfc4bdcb 100644 (file)
@@ -4,12 +4,13 @@ use chrono::{DateTime, NaiveDateTime, Utc};
 use diesel::PgConnection;
 use lemmy_api::claims::Claims;
 use lemmy_db::{
-  comment_view::{ReplyQueryBuilder, ReplyView},
-  community::Community,
-  post_view::{PostQueryBuilder, PostView},
-  site_view::SiteView,
-  user::User_,
-  user_mention_view::{UserMentionQueryBuilder, UserMentionView},
+  source::{community::Community, user::User_},
+  views::{
+    comment_view::{CommentQueryBuilder, CommentView},
+    post_view::{PostQueryBuilder, PostView},
+    site_view::SiteView,
+    user_mention_view::{UserMentionQueryBuilder, UserMentionView},
+  },
   ListingType,
   SortType,
 };
@@ -96,13 +97,13 @@ async fn get_feed_data(
     .namespaces(RSS_NAMESPACE.to_owned())
     .title(&format!(
       "{} - {}",
-      site_view.name,
+      site_view.site.name,
       listing_type.to_string()
     ))
     .link(Settings::get().get_protocol_and_hostname())
     .items(items);
 
-  if let Some(site_desc) = site_view.description {
+  if let Some(site_desc) = site_view.site.description {
     channel_builder.description(&site_desc);
   }
 
@@ -167,7 +168,7 @@ fn get_feed_user(
   let posts = PostQueryBuilder::create(&conn)
     .listing_type(&ListingType::All)
     .sort(sort_type)
-    .for_creator_id(user.id)
+    .creator_id(user.id)
     .list()?;
 
   let items = create_post_items(posts)?;
@@ -175,7 +176,7 @@ fn get_feed_user(
   let mut channel_builder = ChannelBuilder::default();
   channel_builder
     .namespaces(RSS_NAMESPACE.to_owned())
-    .title(&format!("{} - {}", site_view.name, user.name))
+    .title(&format!("{} - {}", site_view.site.name, user.name))
     .link(user_url)
     .items(items);
 
@@ -193,7 +194,7 @@ fn get_feed_community(
   let posts = PostQueryBuilder::create(&conn)
     .listing_type(&ListingType::All)
     .sort(sort_type)
-    .for_community_id(community.id)
+    .community_id(community.id)
     .list()?;
 
   let items = create_post_items(posts)?;
@@ -201,7 +202,7 @@ fn get_feed_community(
   let mut channel_builder = ChannelBuilder::default();
   channel_builder
     .namespaces(RSS_NAMESPACE.to_owned())
-    .title(&format!("{} - {}", site_view.name, community.name))
+    .title(&format!("{} - {}", site_view.site.name, community.name))
     .link(community.actor_id)
     .items(items);
 
@@ -222,8 +223,8 @@ fn get_feed_front(
 
   let posts = PostQueryBuilder::create(&conn)
     .listing_type(&ListingType::Subscribed)
-    .sort(sort_type)
     .my_user_id(user_id)
+    .sort(sort_type)
     .list()?;
 
   let items = create_post_items(posts)?;
@@ -231,11 +232,11 @@ fn get_feed_front(
   let mut channel_builder = ChannelBuilder::default();
   channel_builder
     .namespaces(RSS_NAMESPACE.to_owned())
-    .title(&format!("{} - Subscribed", site_view.name))
+    .title(&format!("{} - Subscribed", site_view.site.name))
     .link(Settings::get().get_protocol_and_hostname())
     .items(items);
 
-  if let Some(site_desc) = site_view.description {
+  if let Some(site_desc) = site_view.site.description {
     channel_builder.description(&site_desc);
   }
 
@@ -248,11 +249,15 @@ fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<ChannelBuilder, Le
 
   let sort = SortType::New;
 
-  let replies = ReplyQueryBuilder::create(&conn, user_id)
+  let replies = CommentQueryBuilder::create(&conn)
+    .recipient_id(user_id)
+    .my_user_id(user_id)
     .sort(&sort)
     .list()?;
 
-  let mentions = UserMentionQueryBuilder::create(&conn, user_id)
+  let mentions = UserMentionQueryBuilder::create(&conn)
+    .recipient_id(user_id)
+    .my_user_id(user_id)
     .sort(&sort)
     .list()?;
 
@@ -261,14 +266,14 @@ fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<ChannelBuilder, Le
   let mut channel_builder = ChannelBuilder::default();
   channel_builder
     .namespaces(RSS_NAMESPACE.to_owned())
-    .title(&format!("{} - Inbox", site_view.name))
+    .title(&format!("{} - Inbox", site_view.site.name))
     .link(format!(
       "{}/inbox",
       Settings::get().get_protocol_and_hostname()
     ))
     .items(items);
 
-  if let Some(site_desc) = site_view.description {
+  if let Some(site_desc) = site_view.site.description {
     channel_builder.description(&site_desc);
   }
 
@@ -276,7 +281,7 @@ fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<ChannelBuilder, Le
 }
 
 fn create_reply_and_mention_items(
-  replies: Vec<ReplyView>,
+  replies: Vec<CommentView>,
   mentions: Vec<UserMentionView>,
 ) -> Result<Vec<Item>, LemmyError> {
   let mut reply_items: Vec<Item> = replies
@@ -285,10 +290,15 @@ fn create_reply_and_mention_items(
       let reply_url = format!(
         "{}/post/{}/comment/{}",
         Settings::get().get_protocol_and_hostname(),
-        r.post_id,
-        r.id
+        r.post.id,
+        r.comment.id
       );
-      build_item(&r.creator_name, &r.published, &reply_url, &r.content)
+      build_item(
+        &r.creator.name,
+        &r.comment.published,
+        &reply_url,
+        &r.comment.content,
+      )
     })
     .collect::<Result<Vec<Item>, LemmyError>>()?;
 
@@ -298,10 +308,15 @@ fn create_reply_and_mention_items(
       let mention_url = format!(
         "{}/post/{}/comment/{}",
         Settings::get().get_protocol_and_hostname(),
-        m.post_id,
-        m.id
+        m.post.id,
+        m.comment.id
       );
-      build_item(&m.creator_name, &m.published, &mention_url, &m.content)
+      build_item(
+        &m.creator.name,
+        &m.comment.published,
+        &mention_url,
+        &m.comment.content,
+      )
     })
     .collect::<Result<Vec<Item>, LemmyError>>()?;
 
@@ -349,17 +364,17 @@ fn create_post_items(posts: Vec<PostView>) -> Result<Vec<Item>, LemmyError> {
     let mut i = ItemBuilder::default();
     let mut dc_extension = DublinCoreExtensionBuilder::default();
 
-    i.title(p.name);
+    i.title(p.post.name);
 
-    dc_extension.creators(vec![p.creator_actor_id.to_owned()]);
+    dc_extension.creators(vec![p.creator.actor_id.to_owned()]);
 
-    let dt = DateTime::<Utc>::from_utc(p.published, Utc);
+    let dt = DateTime::<Utc>::from_utc(p.post.published, Utc);
     i.pub_date(dt.to_rfc2822());
 
     let post_url = format!(
       "{}/post/{}",
       Settings::get().get_protocol_and_hostname(),
-      p.id
+      p.post.id
     );
     i.comments(post_url.to_owned());
     let guid = GuidBuilder::default()
@@ -372,27 +387,27 @@ fn create_post_items(posts: Vec<PostView>) -> Result<Vec<Item>, LemmyError> {
     let community_url = format!(
       "{}/c/{}",
       Settings::get().get_protocol_and_hostname(),
-      p.community_name
+      p.community.name
     );
 
     // TODO: for category we should just put the name of the category, but then we would have
     //       to read each community from the db
 
-    if let Some(url) = p.url {
+    if let Some(url) = p.post.url {
       i.link(url);
     }
 
     // TODO add images
     let mut description = format!("submitted by <a href=\"{}\">{}</a> to <a href=\"{}\">{}</a><br>{} points | <a href=\"{}\">{} comments</a>",
-    p.creator_actor_id,
-    p.creator_name,
+    p.creator.actor_id,
+    p.creator.name,
     community_url,
-    p.community_name,
-    p.score,
+    p.community.name,
+    p.counts.score,
     post_url,
-    p.number_of_comments);
+    p.counts.comments);
 
-    if let Some(body) = p.body {
+    if let Some(body) = p.post.body {
       let html = markdown_to_html(&body);
       description.push_str(&html);
     }
index eb3a773f97639c604247c92eeebcf56e9886d50e..e6f32b41009c153071e92765880f2cfc51e3e2e3 100644 (file)
@@ -56,13 +56,14 @@ async fn upload(
     return Ok(HttpResponse::Unauthorized().finish());
   };
 
-  let mut res = client
-    .request_from(format!("{}/image", Settings::get().pictrs_url), req.head())
-    .if_some(req.head().peer_addr, |addr, req| {
-      req.header("X-Forwarded-For", addr.to_string())
-    })
-    .send_stream(body)
-    .await?;
+  let mut client_req =
+    client.request_from(format!("{}/image", Settings::get().pictrs_url), req.head());
+
+  if let Some(addr) = req.head().peer_addr {
+    client_req = client_req.header("X-Forwarded-For", addr.to_string())
+  };
+
+  let mut res = client_req.send_stream(body).await?;
 
   let images = res.json::<Images>().await?;
 
@@ -105,14 +106,13 @@ async fn image(
   req: HttpRequest,
   client: web::Data<Client>,
 ) -> Result<HttpResponse, Error> {
-  let res = client
-    .request_from(url, req.head())
-    .if_some(req.head().peer_addr, |addr, req| {
-      req.header("X-Forwarded-For", addr.to_string())
-    })
-    .no_decompress()
-    .send()
-    .await?;
+  let mut client_req = client.request_from(url, req.head());
+
+  if let Some(addr) = req.head().peer_addr {
+    client_req = client_req.header("X-Forwarded-For", addr.to_string())
+  };
+
+  let res = client_req.no_decompress().send().await?;
 
   if res.status() == StatusCode::NOT_FOUND {
     return Ok(HttpResponse::NotFound().finish());
@@ -140,14 +140,14 @@ async fn delete(
     &token,
     &file
   );
-  let res = client
-    .request_from(url, req.head())
-    .if_some(req.head().peer_addr, |addr, req| {
-      req.header("X-Forwarded-For", addr.to_string())
-    })
-    .no_decompress()
-    .send()
-    .await?;
+
+  let mut client_req = client.request_from(url, req.head());
+
+  if let Some(addr) = req.head().peer_addr {
+    client_req = client_req.header("X-Forwarded-For", addr.to_string())
+  };
+
+  let res = client_req.no_decompress().send().await?;
 
   Ok(HttpResponse::build(res.status()).body(BodyStream::new(res)))
 }
index 1d9525ef2ba21bea072ca240b034a4171082f3ff..9f63a523b1632dc9bc950d1b6dc7c4e51c4be040 100644 (file)
@@ -1,7 +1,7 @@
 use actix_web::{body::Body, error::ErrorBadRequest, *};
 use anyhow::anyhow;
 use lemmy_api::version;
-use lemmy_db::site_view::SiteView;
+use lemmy_db::views::site_view::SiteView;
 use lemmy_structs::blocking;
 use lemmy_utils::{settings::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
@@ -46,12 +46,11 @@ async fn node_info(context: web::Data<LemmyContext>) -> Result<HttpResponse, Err
     },
     protocols,
     usage: NodeInfoUsage {
-      users: NodeInfoUsers {
-        total: site_view.number_of_users,
-      },
-      local_posts: site_view.number_of_posts,
-      local_comments: site_view.number_of_comments,
-      open_registrations: site_view.open_registration,
+      // TODO get these again
+      users: NodeInfoUsers { total: 0 },
+      local_posts: 0,
+      local_comments: 0,
+      open_registrations: site_view.site.open_registration,
     },
   };
 
index ba687abdf47507293da56d256634da52aa95be92..d59b4e38909aec88f2bb5605339e97d87db23c14 100644 (file)
@@ -1,6 +1,6 @@
 use actix_web::{error::ErrorBadRequest, web::Query, *};
 use anyhow::anyhow;
-use lemmy_db::{community::Community, user::User_};
+use lemmy_db::source::{community::Community, user::User_};
 use lemmy_structs::{blocking, WebFingerLink, WebFingerResponse};
 use lemmy_utils::{
   settings::Settings,
diff --git a/test.sh b/test.sh
index ef2e608f4574c66377ec2e8fd400dba8500c99ef..21093d0cf03b3b1cb0abed64eeb0b6423b633093 100755 (executable)
--- a/test.sh
+++ b/test.sh
@@ -2,7 +2,9 @@
 set -e
 
 export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy
+# Commenting since this will overwrite schema.rs, which will break things now
+# diesel migration run
 # Integration tests only work on stable due to a bug in config-rs
 # https://github.com/mehcode/config-rs/issues/158
 RUST_BACKTRACE=1 RUST_TEST_THREADS=1 \
-  cargo +stable test --workspace --no-fail-fast
+  cargo +1.47.0 test --workspace --no-fail-fast
index 69f2d5f5baedcb3246413e6fe1186abcd00ddc97..75753d28089bb0821c3768b1cefe54f854bcc608 100644 (file)
@@ -29,8 +29,10 @@ use lemmy_apub::{
   },
 };
 use lemmy_db::{
-  community::{Community, CommunityForm},
-  user::{User_, *},
+  source::{
+    community::{Community, CommunityForm},
+    user::{User_, *},
+  },
   Crud,
   ListingType,
   SortType,