diff --git a/README.md b/README.md index 8aa5fe38a..57b3d63e5 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ Each Lemmy server can set its own moderation policy; appointing site-wide admins ### Libraries +- [lemmy-js-client](https://github.com/LemmyNet/lemmy-js-client) - [Kotlin API ( under development )](https://github.com/eiknat/lemmy-client) ## Support / Donate diff --git a/docker/federation-test/run-tests.sh b/docker/federation-test/run-tests.sh index 3848414b9..f166d4903 100755 --- a/docker/federation-test/run-tests.sh +++ b/docker/federation-test/run-tests.sh @@ -13,8 +13,8 @@ pushd ../../ui yarn popd -mkdir -p volumes/pictrs_{alpha,beta,gamma} -sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma} +mkdir -p volumes/pictrs_{alpha,beta,gamma,delta,epsilon} +sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma,delta,epsilon} sudo docker build ../../ --file ../federation/Dockerfile --tag lemmy-federation:latest @@ -28,6 +28,8 @@ echo "Waiting for Lemmy to start..." while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8560/api/v1/site')" != "200" ]]; do sleep 1; done +while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8570/api/v1/site')" != "200" ]]; do sleep 1; done +while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8580/api/v1/site')" != "200" ]]; do sleep 1; done yarn api-test || true popd diff --git a/docker/federation-test/servers.sh b/docker/federation-test/servers.sh index b34e8c4ef..5b09bc952 100755 --- a/docker/federation-test/servers.sh +++ b/docker/federation-test/servers.sh @@ -12,8 +12,8 @@ pushd ../../ui yarn popd -mkdir -p volumes/pictrs_{alpha,beta,gamma} -sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma} +mkdir -p volumes/pictrs_{alpha,beta,gamma,delta,epsilon} +sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma,delta,epsilon} sudo docker build ../../ --file ../federation/Dockerfile --tag lemmy-federation:latest diff --git a/docker/federation-test/tests.sh b/docker/federation-test/tests.sh index 2e88ffb25..58472e95c 100755 --- a/docker/federation-test/tests.sh +++ b/docker/federation-test/tests.sh @@ -6,5 +6,7 @@ echo "Waiting for Lemmy to start..." while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8560/api/v1/site')" != "200" ]]; do sleep 1; done +while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8570/api/v1/site')" != "200" ]]; do sleep 1; done +while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8580/api/v1/site')" != "200" ]]; do sleep 1; done yarn api-test || true popd diff --git a/docker/federation/docker-compose.yml b/docker/federation/docker-compose.yml index a3d0cf431..32fee74ab 100644 --- a/docker/federation/docker-compose.yml +++ b/docker/federation/docker-compose.yml @@ -7,16 +7,20 @@ services: - "8540:8540" - "8550:8550" - "8560:8560" + - "8570:8570" + - "8580:8580" volumes: # Hack to make this work from both docker/federation/ and docker/federation-test/ - ../federation/nginx.conf:/etc/nginx/nginx.conf restart: on-failure depends_on: - - lemmy-alpha - pictrs + - iframely + - lemmy-alpha - lemmy-beta - lemmy-gamma - - iframely + - lemmy-delta + - lemmy-epsilon pictrs: restart: always @@ -34,7 +38,7 @@ services: - LEMMY_FRONT_END_DIR=/app/dist - LEMMY_FEDERATION__ENABLED=true - LEMMY_FEDERATION__TLS_ENABLED=false - - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma + - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma,lemmy-delta,lemmy-epsilon - LEMMY_PORT=8540 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha - LEMMY_SETUP__ADMIN_PASSWORD=lemmy @@ -64,7 +68,7 @@ services: - LEMMY_FRONT_END_DIR=/app/dist - LEMMY_FEDERATION__ENABLED=true - LEMMY_FEDERATION__TLS_ENABLED=false - - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma + - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma,lemmy-delta,lemmy-epsilon - LEMMY_PORT=8550 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta - LEMMY_SETUP__ADMIN_PASSWORD=lemmy @@ -94,7 +98,7 @@ services: - LEMMY_FRONT_END_DIR=/app/dist - LEMMY_FEDERATION__ENABLED=true - LEMMY_FEDERATION__TLS_ENABLED=false - - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta + - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta,lemmy-delta,lemmy-epsilon - LEMMY_PORT=8560 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma - LEMMY_SETUP__ADMIN_PASSWORD=lemmy @@ -115,6 +119,68 @@ services: volumes: - ./volumes/postgres_gamma:/var/lib/postgresql/data + # An instance with only an allowlist for beta + lemmy-delta: + image: lemmy-federation:latest + environment: + - LEMMY_HOSTNAME=lemmy-delta:8570 + - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_delta:5432/lemmy + - LEMMY_JWT_SECRET=changeme + - LEMMY_FRONT_END_DIR=/app/dist + - LEMMY_FEDERATION__ENABLED=true + - LEMMY_FEDERATION__TLS_ENABLED=false + - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta + - LEMMY_PORT=8570 + - LEMMY_SETUP__ADMIN_USERNAME=lemmy_delta + - LEMMY_SETUP__ADMIN_PASSWORD=lemmy + - LEMMY_SETUP__SITE_NAME=lemmy-delta + - LEMMY_RATE_LIMIT__POST=99999 + - LEMMY_RATE_LIMIT__REGISTER=99999 + - LEMMY_CAPTCHA__ENABLED=false + - RUST_BACKTRACE=1 + - RUST_LOG=debug + depends_on: + - postgres_delta + postgres_delta: + image: postgres:12-alpine + environment: + - POSTGRES_USER=lemmy + - POSTGRES_PASSWORD=password + - POSTGRES_DB=lemmy + volumes: + - ./volumes/postgres_delta:/var/lib/postgresql/data + + # An instance who has a blocklist, with lemmy-alpha blocked + lemmy-epsilon: + image: lemmy-federation:latest + environment: + - LEMMY_HOSTNAME=lemmy-epsilon:8580 + - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_epsilon:5432/lemmy + - LEMMY_JWT_SECRET=changeme + - LEMMY_FRONT_END_DIR=/app/dist + - LEMMY_FEDERATION__ENABLED=true + - LEMMY_FEDERATION__TLS_ENABLED=false + - LEMMY_FEDERATION__BLOCKED_INSTANCES=lemmy-alpha + - LEMMY_PORT=8580 + - LEMMY_SETUP__ADMIN_USERNAME=lemmy_epsilon + - LEMMY_SETUP__ADMIN_PASSWORD=lemmy + - LEMMY_SETUP__SITE_NAME=lemmy-epsilon + - LEMMY_RATE_LIMIT__POST=99999 + - LEMMY_RATE_LIMIT__REGISTER=99999 + - LEMMY_CAPTCHA__ENABLED=false + - RUST_BACKTRACE=1 + - RUST_LOG=debug + depends_on: + - postgres_epsilon + postgres_epsilon: + image: postgres:12-alpine + environment: + - POSTGRES_USER=lemmy + - POSTGRES_PASSWORD=password + - POSTGRES_DB=lemmy + volumes: + - ./volumes/postgres_epsilon:/var/lib/postgresql/data + iframely: image: dogbin/iframely:latest volumes: diff --git a/docker/federation/nginx.conf b/docker/federation/nginx.conf index b7901c19c..6d062f70b 100644 --- a/docker/federation/nginx.conf +++ b/docker/federation/nginx.conf @@ -95,4 +95,66 @@ http { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } + + server { + listen 8570; + server_name 127.0.0.1; + access_log off; + + # Upload limit for pictshare + client_max_body_size 50M; + + location / { + proxy_pass http://lemmy-delta:8570; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Cuts off the trailing slash on URLs to make them valid + rewrite ^(.+)/+$ $1 permanent; + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + location /iframely/ { + proxy_pass http://iframely:80/; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + } + + server { + listen 8580; + server_name 127.0.0.1; + access_log off; + + # Upload limit for pictshare + client_max_body_size 50M; + + location / { + proxy_pass http://lemmy-epsilon:8580; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Cuts off the trailing slash on URLs to make them valid + rewrite ^(.+)/+$ $1 permanent; + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + location /iframely/ { + proxy_pass http://iframely:80/; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + } } diff --git a/docker/federation/run-federation-test.bash b/docker/federation/run-federation-test.bash index 77cc981f4..0fe03aa17 100755 --- a/docker/federation/run-federation-test.bash +++ b/docker/federation/run-federation-test.bash @@ -20,7 +20,7 @@ popd || exit sudo docker build ../../ --file Dockerfile -t lemmy-federation:latest -for Item in alpha beta gamma ; do +for Item in alpha beta gamma delta epsilon ; do sudo mkdir -p volumes/pictrs_$Item sudo chown -R 991:991 volumes/pictrs_$Item done diff --git a/docker/travis/docker-compose.yml b/docker/travis/docker-compose.yml index 7314e495d..9aa7750c0 100644 --- a/docker/travis/docker-compose.yml +++ b/docker/travis/docker-compose.yml @@ -7,16 +7,20 @@ services: - "8540:8540" - "8550:8550" - "8560:8560" + - "8570:8570" + - "8580:8580" volumes: # Hack to make this work from both docker/federation/ and docker/federation-test/ - ../federation/nginx.conf:/etc/nginx/nginx.conf restart: on-failure depends_on: - - lemmy-alpha - pictrs + - iframely + - lemmy-alpha - lemmy-beta - lemmy-gamma - - iframely + - lemmy-delta + - lemmy-epsilon pictrs: restart: always @@ -34,7 +38,7 @@ services: - LEMMY_FRONT_END_DIR=/app/dist - LEMMY_FEDERATION__ENABLED=true - LEMMY_FEDERATION__TLS_ENABLED=false - - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma + - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma,lemmy-delta,lemmy-epsilon - LEMMY_PORT=8540 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha - LEMMY_SETUP__ADMIN_PASSWORD=lemmy @@ -64,7 +68,7 @@ services: - LEMMY_FRONT_END_DIR=/app/dist - LEMMY_FEDERATION__ENABLED=true - LEMMY_FEDERATION__TLS_ENABLED=false - - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma + - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma,lemmy-delta,lemmy-epsilon - LEMMY_PORT=8550 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta - LEMMY_SETUP__ADMIN_PASSWORD=lemmy @@ -94,7 +98,7 @@ services: - LEMMY_FRONT_END_DIR=/app/dist - LEMMY_FEDERATION__ENABLED=true - LEMMY_FEDERATION__TLS_ENABLED=false - - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta + - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta,lemmy-delta,lemmy-epsilon - LEMMY_PORT=8560 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma - LEMMY_SETUP__ADMIN_PASSWORD=lemmy @@ -115,6 +119,68 @@ services: volumes: - ./volumes/postgres_gamma:/var/lib/postgresql/data + # An instance with only an allowlist for beta + lemmy-delta: + image: dessalines/lemmy:travis + environment: + - LEMMY_HOSTNAME=lemmy-delta:8570 + - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_delta:5432/lemmy + - LEMMY_JWT_SECRET=changeme + - LEMMY_FRONT_END_DIR=/app/dist + - LEMMY_FEDERATION__ENABLED=true + - LEMMY_FEDERATION__TLS_ENABLED=false + - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta + - LEMMY_PORT=8570 + - LEMMY_SETUP__ADMIN_USERNAME=lemmy_delta + - LEMMY_SETUP__ADMIN_PASSWORD=lemmy + - LEMMY_SETUP__SITE_NAME=lemmy-delta + - LEMMY_RATE_LIMIT__POST=99999 + - LEMMY_RATE_LIMIT__REGISTER=99999 + - LEMMY_CAPTCHA__ENABLED=false + - RUST_BACKTRACE=1 + - RUST_LOG=debug + depends_on: + - postgres_delta + postgres_delta: + image: postgres:12-alpine + environment: + - POSTGRES_USER=lemmy + - POSTGRES_PASSWORD=password + - POSTGRES_DB=lemmy + volumes: + - ./volumes/postgres_delta:/var/lib/postgresql/data + + # An instance who has a blocklist, with lemmy-alpha blocked + lemmy-epsilon: + image: dessalines/lemmy:travis + environment: + - LEMMY_HOSTNAME=lemmy-epsilon:8580 + - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_epsilon:5432/lemmy + - LEMMY_JWT_SECRET=changeme + - LEMMY_FRONT_END_DIR=/app/dist + - LEMMY_FEDERATION__ENABLED=true + - LEMMY_FEDERATION__TLS_ENABLED=false + - LEMMY_FEDERATION__BLOCKED_INSTANCES=lemmy-alpha + - LEMMY_PORT=8580 + - LEMMY_SETUP__ADMIN_USERNAME=lemmy_epsilon + - LEMMY_SETUP__ADMIN_PASSWORD=lemmy + - LEMMY_SETUP__SITE_NAME=lemmy-epsilon + - LEMMY_RATE_LIMIT__POST=99999 + - LEMMY_RATE_LIMIT__REGISTER=99999 + - LEMMY_CAPTCHA__ENABLED=false + - RUST_BACKTRACE=1 + - RUST_LOG=debug + depends_on: + - postgres_epsilon + postgres_epsilon: + image: postgres:12-alpine + environment: + - POSTGRES_USER=lemmy + - POSTGRES_PASSWORD=password + - POSTGRES_DB=lemmy + volumes: + - ./volumes/postgres_epsilon:/var/lib/postgresql/data + iframely: image: dogbin/iframely:latest volumes: diff --git a/docker/travis/run-tests.sh b/docker/travis/run-tests.sh index f09b73c2b..658ffc0e4 100755 --- a/docker/travis/run-tests.sh +++ b/docker/travis/run-tests.sh @@ -5,8 +5,8 @@ set -e sudo docker-compose down sudo rm -rf volumes -mkdir -p volumes/pictrs_{alpha,beta,gamma} -sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma} +mkdir -p volumes/pictrs_{alpha,beta,gamma,delta,epsilon} +sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma,delta,epsilon} sudo docker build ../../ --file ../prod/Dockerfile --tag dessalines/lemmy:travis @@ -17,6 +17,8 @@ echo "Waiting for Lemmy to start..." while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8560/api/v1/site')" != "200" ]]; do sleep 1; done +while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8570/api/v1/site')" != "200" ]]; do sleep 1; done +while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8580/api/v1/site')" != "200" ]]; do sleep 1; done yarn yarn api-test popd diff --git a/server/src/api/comment.rs b/server/src/api/comment.rs index 63f53a4d7..3384993f8 100644 --- a/server/src/api/comment.rs +++ b/server/src/api/comment.rs @@ -1,6 +1,7 @@ use crate::{ api::{ check_community_ban, + get_post, get_user_from_jwt, get_user_from_jwt_opt, is_mod_or_admin, @@ -12,8 +13,8 @@ use crate::{ websocket::{ server::{JoinCommunityRoom, SendComment}, UserOperation, - WebsocketInfo, }, + ConnectionId, DbPool, LemmyContext, LemmyError, @@ -128,7 +129,7 @@ impl Perform for CreateComment { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &CreateComment = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -151,7 +152,7 @@ impl Perform for CreateComment { // Check for a community ban let post_id = data.post_id; - let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; + let post = get_post(post_id, context.pool()).await?; check_community_ban(user.id, post.community_id, context.pool()).await?; @@ -225,17 +226,15 @@ impl Perform for CreateComment { form_id: data.form_id.to_owned(), }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendComment { - op: UserOperation::CreateComment, - comment: res.clone(), - my_id: ws.id, - }); + context.chat_server().do_send(SendComment { + op: UserOperation::CreateComment, + comment: res.clone(), + websocket_id, + }); - // strip out the recipient_ids, so that - // users don't get double notifs - res.recipient_ids = Vec::new(); - } + // strip out the recipient_ids, so that + // users don't get double notifs + res.recipient_ids = Vec::new(); Ok(res) } @@ -248,7 +247,7 @@ impl Perform for EditComment { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &EditComment = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -283,7 +282,7 @@ impl Perform for EditComment { // Do the mentions / recipients let post_id = orig_comment.post_id; - let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; + 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); @@ -310,17 +309,15 @@ impl Perform for EditComment { form_id: data.form_id.to_owned(), }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendComment { - op: UserOperation::EditComment, - comment: res.clone(), - my_id: ws.id, - }); + context.chat_server().do_send(SendComment { + op: UserOperation::EditComment, + comment: res.clone(), + websocket_id, + }); - // strip out the recipient_ids, so that - // users don't get double notifs - res.recipient_ids = Vec::new(); - } + // strip out the recipient_ids, so that + // users don't get double notifs + res.recipient_ids = Vec::new(); Ok(res) } @@ -333,7 +330,7 @@ impl Perform for DeleteComment { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &DeleteComment = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -379,7 +376,7 @@ impl Perform for DeleteComment { // Build the recipients let post_id = comment_view.post_id; - let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; + let post = get_post(post_id, context.pool()).await?; let mentions = vec![]; let recipient_ids = send_local_notifs( mentions, @@ -397,17 +394,15 @@ impl Perform for DeleteComment { form_id: None, }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendComment { - op: UserOperation::DeleteComment, - comment: res.clone(), - my_id: ws.id, - }); + context.chat_server().do_send(SendComment { + op: UserOperation::DeleteComment, + comment: res.clone(), + websocket_id, + }); - // strip out the recipient_ids, so that - // users don't get double notifs - res.recipient_ids = Vec::new(); - } + // strip out the recipient_ids, so that + // users don't get double notifs + res.recipient_ids = Vec::new(); Ok(res) } @@ -420,7 +415,7 @@ impl Perform for RemoveComment { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &RemoveComment = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -476,7 +471,7 @@ impl Perform for RemoveComment { // Build the recipients let post_id = comment_view.post_id; - let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; + let post = get_post(post_id, context.pool()).await?; let mentions = vec![]; let recipient_ids = send_local_notifs( mentions, @@ -494,17 +489,15 @@ impl Perform for RemoveComment { form_id: None, }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendComment { - op: UserOperation::RemoveComment, - comment: res.clone(), - my_id: ws.id, - }); + context.chat_server().do_send(SendComment { + op: UserOperation::RemoveComment, + comment: res.clone(), + websocket_id, + }); - // strip out the recipient_ids, so that - // users don't get double notifs - res.recipient_ids = Vec::new(); - } + // strip out the recipient_ids, so that + // users don't get double notifs + res.recipient_ids = Vec::new(); Ok(res) } @@ -517,7 +510,7 @@ impl Perform for MarkCommentAsRead { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &MarkCommentAsRead = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -589,7 +582,7 @@ impl Perform for SaveComment { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &SaveComment = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -633,7 +626,7 @@ impl Perform for CreateCommentLike { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &CreateCommentLike = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -655,7 +648,7 @@ impl Perform for CreateCommentLike { .await??; let post_id = orig_comment.post_id; - let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; + let post = get_post(post_id, context.pool()).await?; check_community_ban(user.id, post.community_id, context.pool()).await?; let comment_id = data.comment_id; @@ -725,17 +718,15 @@ impl Perform for CreateCommentLike { form_id: None, }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendComment { - op: UserOperation::CreateCommentLike, - comment: res.clone(), - my_id: ws.id, - }); + context.chat_server().do_send(SendComment { + op: UserOperation::CreateCommentLike, + comment: res.clone(), + websocket_id, + }); - // strip out the recipient_ids, so that - // users don't get double notifs - res.recipient_ids = Vec::new(); - } + // strip out the recipient_ids, so that + // users don't get double notifs + res.recipient_ids = Vec::new(); Ok(res) } @@ -748,7 +739,7 @@ impl Perform for GetComments { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &GetComments = &self; let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?; @@ -776,17 +767,15 @@ impl Perform for GetComments { Err(_) => return Err(APIError::err("couldnt_get_comments").into()), }; - if let Some(ws) = websocket_info { + if let Some(id) = websocket_id { // You don't need to join the specific community room, bc this is already handled by // GetCommunity if data.community_id.is_none() { - if let Some(id) = ws.id { - // 0 is the "all" community - ws.chatserver.do_send(JoinCommunityRoom { - community_id: 0, - id, - }); - } + // 0 is the "all" community + context.chat_server().do_send(JoinCommunityRoom { + community_id: 0, + id, + }); } } diff --git a/server/src/api/community.rs b/server/src/api/community.rs index f5fffb7c2..7b63c6726 100644 --- a/server/src/api/community.rs +++ b/server/src/api/community.rs @@ -6,8 +6,8 @@ use crate::{ websocket::{ server::{GetCommunityUsersOnline, JoinCommunityRoom, SendCommunityRoomMessage}, UserOperation, - WebsocketInfo, }, + ConnectionId, }; use anyhow::Context; use lemmy_db::{ @@ -166,7 +166,7 @@ impl Perform for GetCommunity { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &GetCommunity = &self; let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?; @@ -205,20 +205,17 @@ impl Perform for GetCommunity { Err(_e) => return Err(APIError::err("couldnt_find_community").into()), }; - let online = if let Some(ws) = websocket_info { - if let Some(id) = ws.id { - ws.chatserver.do_send(JoinCommunityRoom { - community_id: community.id, - id, - }); - } - ws.chatserver - .send(GetCommunityUsersOnline { community_id }) - .await - .unwrap_or(1) - } else { - 0 - }; + if let Some(id) = websocket_id { + context + .chat_server() + .do_send(JoinCommunityRoom { community_id, id }); + } + + let online = context + .chat_server() + .send(GetCommunityUsersOnline { community_id }) + .await + .unwrap_or(1); let res = GetCommunityResponse { community: community_view, @@ -238,7 +235,7 @@ impl Perform for CreateCommunity { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &CreateCommunity = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -333,7 +330,7 @@ impl Perform for EditCommunity { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &EditCommunity = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -403,7 +400,7 @@ impl Perform for EditCommunity { community: community_view, }; - send_community_websocket(&res, websocket_info, UserOperation::EditCommunity); + send_community_websocket(&res, context, websocket_id, UserOperation::EditCommunity); Ok(res) } @@ -416,7 +413,7 @@ impl Perform for DeleteCommunity { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &DeleteCommunity = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -459,7 +456,7 @@ impl Perform for DeleteCommunity { community: community_view, }; - send_community_websocket(&res, websocket_info, UserOperation::DeleteCommunity); + send_community_websocket(&res, context, websocket_id, UserOperation::DeleteCommunity); Ok(res) } @@ -472,7 +469,7 @@ impl Perform for RemoveCommunity { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &RemoveCommunity = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -527,7 +524,7 @@ impl Perform for RemoveCommunity { community: community_view, }; - send_community_websocket(&res, websocket_info, UserOperation::RemoveCommunity); + send_community_websocket(&res, context, websocket_id, UserOperation::RemoveCommunity); Ok(res) } @@ -540,7 +537,7 @@ impl Perform for ListCommunities { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &ListCommunities = &self; let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?; @@ -582,7 +579,7 @@ impl Perform for FollowCommunity { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &FollowCommunity = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -643,7 +640,7 @@ impl Perform for GetFollowedCommunities { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &GetFollowedCommunities = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -670,7 +667,7 @@ impl Perform for BanFromCommunity { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &BanFromCommunity = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -757,14 +754,12 @@ impl Perform for BanFromCommunity { banned: data.ban, }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendCommunityRoomMessage { - op: UserOperation::BanFromCommunity, - response: res.clone(), - community_id: data.community_id, - my_id: ws.id, - }); - } + context.chat_server().do_send(SendCommunityRoomMessage { + op: UserOperation::BanFromCommunity, + response: res.clone(), + community_id, + websocket_id, + }); Ok(res) } @@ -777,7 +772,7 @@ impl Perform for AddModToCommunity { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &AddModToCommunity = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -824,14 +819,12 @@ impl Perform for AddModToCommunity { let res = AddModToCommunityResponse { moderators }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendCommunityRoomMessage { - op: UserOperation::AddModToCommunity, - response: res.clone(), - community_id: data.community_id, - my_id: ws.id, - }); - } + context.chat_server().do_send(SendCommunityRoomMessage { + op: UserOperation::AddModToCommunity, + response: res.clone(), + community_id, + websocket_id, + }); Ok(res) } @@ -844,7 +837,7 @@ impl Perform for TransferCommunity { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &TransferCommunity = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -957,20 +950,19 @@ impl Perform for TransferCommunity { pub fn send_community_websocket( res: &CommunityResponse, - websocket_info: Option, + context: &Data, + websocket_id: Option, op: UserOperation, ) { - if let Some(ws) = websocket_info { - // 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; + // 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; - ws.chatserver.do_send(SendCommunityRoomMessage { - op, - response: res_sent, - community_id: res.community.id, - my_id: ws.id, - }); - } + context.chat_server().do_send(SendCommunityRoomMessage { + op, + response: res_sent, + community_id: res.community.id, + websocket_id, + }); } diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs index 6fed9d05d..5f8706e00 100644 --- a/server/src/api/mod.rs +++ b/server/src/api/mod.rs @@ -1,16 +1,10 @@ -use crate::{ - api::claims::Claims, - blocking, - websocket::WebsocketInfo, - DbPool, - LemmyContext, - LemmyError, -}; +use crate::{api::claims::Claims, blocking, ConnectionId, DbPool, LemmyContext, LemmyError}; use actix_web::web::Data; use lemmy_db::{ community::*, community_view::*, moderator::*, + post::Post, site::*, user::*, user_view::*, @@ -47,7 +41,7 @@ pub trait Perform { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result; } @@ -73,6 +67,13 @@ pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> { Ok(()) } +pub(in crate::api) async fn get_post(post_id: i32, pool: &DbPool) -> Result { + match blocking(pool, move |conn| Post::read(conn, post_id)).await? { + Ok(post) => Ok(post), + Err(_e) => Err(APIError::err("couldnt_find_post").into()), + } +} + pub(in crate::api) async fn get_user_from_jwt( jwt: &str, pool: &DbPool, diff --git a/server/src/api/post.rs b/server/src/api/post.rs index fa3c73be3..5cb7e3222 100644 --- a/server/src/api/post.rs +++ b/server/src/api/post.rs @@ -15,8 +15,8 @@ use crate::{ websocket::{ server::{GetPostUsersOnline, JoinCommunityRoom, JoinPostRoom, SendPost}, UserOperation, - WebsocketInfo, }, + ConnectionId, LemmyContext, LemmyError, }; @@ -146,7 +146,7 @@ impl Perform for CreatePost { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &CreatePost = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -247,13 +247,11 @@ impl Perform for CreatePost { let res = PostResponse { post: post_view }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendPost { - op: UserOperation::CreatePost, - post: res.clone(), - my_id: ws.id, - }); - } + context.chat_server().do_send(SendPost { + op: UserOperation::CreatePost, + post: res.clone(), + websocket_id, + }); Ok(res) } @@ -266,7 +264,7 @@ impl Perform for GetPost { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &GetPost = &self; let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?; @@ -304,20 +302,18 @@ impl Perform for GetPost { }) .await??; - let online = if let Some(ws) = websocket_info { - if let Some(id) = ws.id { - ws.chatserver.do_send(JoinPostRoom { - post_id: data.id, - id, - }); - } - ws.chatserver - .send(GetPostUsersOnline { post_id: data.id }) - .await - .unwrap_or(1) - } else { - 0 - }; + if let Some(id) = websocket_id { + context.chat_server().do_send(JoinPostRoom { + post_id: data.id, + id, + }); + } + + let online = context + .chat_server() + .send(GetPostUsersOnline { post_id: data.id }) + .await + .unwrap_or(1); // Return the jwt Ok(GetPostResponse { @@ -337,7 +333,7 @@ impl Perform for GetPosts { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &GetPosts = &self; let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?; @@ -377,17 +373,15 @@ impl Perform for GetPosts { Err(_e) => return Err(APIError::err("couldnt_get_posts").into()), }; - if let Some(ws) = websocket_info { + if let Some(id) = websocket_id { // You don't need to join the specific community room, bc this is already handled by // GetCommunity if data.community_id.is_none() { - if let Some(id) = ws.id { - // 0 is the "all" community - ws.chatserver.do_send(JoinCommunityRoom { - community_id: 0, - id, - }); - } + // 0 is the "all" community + context.chat_server().do_send(JoinCommunityRoom { + community_id: 0, + id, + }); } } @@ -402,7 +396,7 @@ impl Perform for CreatePostLike { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &CreatePostLike = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -465,13 +459,11 @@ impl Perform for CreatePostLike { let res = PostResponse { post: post_view }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendPost { - op: UserOperation::CreatePostLike, - post: res.clone(), - my_id: ws.id, - }); - } + context.chat_server().do_send(SendPost { + op: UserOperation::CreatePostLike, + post: res.clone(), + websocket_id, + }); Ok(res) } @@ -484,7 +476,7 @@ impl Perform for EditPost { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &EditPost = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -560,13 +552,11 @@ impl Perform for EditPost { let res = PostResponse { post: post_view }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendPost { - op: UserOperation::EditPost, - post: res.clone(), - my_id: ws.id, - }); - } + context.chat_server().do_send(SendPost { + op: UserOperation::EditPost, + post: res.clone(), + websocket_id, + }); Ok(res) } @@ -579,7 +569,7 @@ impl Perform for DeletePost { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &DeletePost = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -618,13 +608,11 @@ impl Perform for DeletePost { let res = PostResponse { post: post_view }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendPost { - op: UserOperation::DeletePost, - post: res.clone(), - my_id: ws.id, - }); - } + context.chat_server().do_send(SendPost { + op: UserOperation::DeletePost, + post: res.clone(), + websocket_id, + }); Ok(res) } @@ -637,7 +625,7 @@ impl Perform for RemovePost { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &RemovePost = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -687,13 +675,11 @@ impl Perform for RemovePost { let res = PostResponse { post: post_view }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendPost { - op: UserOperation::RemovePost, - post: res.clone(), - my_id: ws.id, - }); - } + context.chat_server().do_send(SendPost { + op: UserOperation::RemovePost, + post: res.clone(), + websocket_id, + }); Ok(res) } @@ -706,7 +692,7 @@ impl Perform for LockPost { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &LockPost = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -747,13 +733,11 @@ impl Perform for LockPost { let res = PostResponse { post: post_view }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendPost { - op: UserOperation::LockPost, - post: res.clone(), - my_id: ws.id, - }); - } + context.chat_server().do_send(SendPost { + op: UserOperation::LockPost, + post: res.clone(), + websocket_id, + }); Ok(res) } @@ -766,7 +750,7 @@ impl Perform for StickyPost { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &StickyPost = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -811,13 +795,11 @@ impl Perform for StickyPost { let res = PostResponse { post: post_view }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendPost { - op: UserOperation::StickyPost, - post: res.clone(), - my_id: ws.id, - }); - } + context.chat_server().do_send(SendPost { + op: UserOperation::StickyPost, + post: res.clone(), + websocket_id, + }); Ok(res) } @@ -830,7 +812,7 @@ impl Perform for SavePost { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &SavePost = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; diff --git a/server/src/api/site.rs b/server/src/api/site.rs index a9e393d8a..8f5f0e930 100644 --- a/server/src/api/site.rs +++ b/server/src/api/site.rs @@ -15,8 +15,8 @@ use crate::{ websocket::{ server::{GetUsersOnline, SendAllMessage}, UserOperation, - WebsocketInfo, }, + ConnectionId, LemmyContext, LemmyError, }; @@ -167,7 +167,7 @@ impl Perform for ListCategories { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let _data: &ListCategories = &self; @@ -185,7 +185,7 @@ impl Perform for GetModlog { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &GetModlog = &self; @@ -259,7 +259,7 @@ impl Perform for CreateSite { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &CreateSite = &self; @@ -300,7 +300,7 @@ impl Perform for EditSite { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &EditSite = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -337,13 +337,11 @@ impl Perform for EditSite { let res = SiteResponse { site: site_view }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendAllMessage { - op: UserOperation::EditSite, - response: res.clone(), - my_id: ws.id, - }); - } + context.chat_server().do_send(SendAllMessage { + op: UserOperation::EditSite, + response: res.clone(), + websocket_id, + }); Ok(res) } @@ -356,7 +354,7 @@ impl Perform for GetSite { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &GetSite = &self; @@ -375,7 +373,7 @@ impl Perform for GetSite { captcha_uuid: None, captcha_answer: None, }; - let login_response = register.perform(context, websocket_info.clone()).await?; + let login_response = register.perform(context, websocket_id).await?; info!("Admin {} created", setup.admin_username); let create_site = CreateSite { @@ -388,7 +386,7 @@ impl Perform for GetSite { enable_nsfw: true, auth: login_response.jwt, }; - create_site.perform(context, websocket_info.clone()).await?; + create_site.perform(context, websocket_id).await?; info!("Site {} created", setup.site_name); Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??) } else { @@ -410,11 +408,11 @@ impl Perform for GetSite { let banned = blocking(context.pool(), move |conn| UserView::banned(conn)).await??; - let online = if let Some(ws) = websocket_info { - ws.chatserver.send(GetUsersOnline).await.unwrap_or(1) - } else { - 0 - }; + let online = context + .chat_server() + .send(GetUsersOnline) + .await + .unwrap_or(1); let my_user = get_user_from_jwt_opt(&data.auth, context.pool()) .await? @@ -444,7 +442,7 @@ impl Perform for Search { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &Search = &self; @@ -608,7 +606,7 @@ impl Perform for TransferSite { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &TransferSite = &self; let mut user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -671,7 +669,7 @@ impl Perform for GetSiteConfig { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &GetSiteConfig = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -692,7 +690,7 @@ impl Perform for SaveSiteConfig { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &SaveSiteConfig = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; diff --git a/server/src/api/user.rs b/server/src/api/user.rs index a84b89873..e97a6d33b 100644 --- a/server/src/api/user.rs +++ b/server/src/api/user.rs @@ -14,8 +14,8 @@ use crate::{ websocket::{ server::{CaptchaItem, CheckCaptcha, JoinUserRoom, SendAllMessage, SendUserRoomMessage}, UserOperation, - WebsocketInfo, }, + ConnectionId, LemmyContext, LemmyError, }; @@ -303,7 +303,7 @@ impl Perform for Login { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &Login = &self; @@ -338,7 +338,7 @@ impl Perform for Register { async fn perform( &self, context: &Data, - websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &Register = &self; @@ -357,27 +357,22 @@ impl Perform for Register { // If its not the admin, check the captcha if !data.admin && Settings::get().captcha.enabled { - match websocket_info { - Some(ws) => { - let check = ws - .chatserver - .send(CheckCaptcha { - uuid: data - .captcha_uuid - .to_owned() - .unwrap_or_else(|| "".to_string()), - answer: data - .captcha_answer - .to_owned() - .unwrap_or_else(|| "".to_string()), - }) - .await?; - if !check { - return Err(APIError::err("captcha_incorrect").into()); - } - } - None => return Err(APIError::err("captcha_incorrect").into()), - }; + let check = context + .chat_server() + .send(CheckCaptcha { + uuid: data + .captcha_uuid + .to_owned() + .unwrap_or_else(|| "".to_string()), + answer: data + .captcha_answer + .to_owned() + .unwrap_or_else(|| "".to_string()), + }) + .await?; + if !check { + return Err(APIError::err("captcha_incorrect").into()); + } } check_slurs(&data.username)?; @@ -515,8 +510,8 @@ impl Perform for GetCaptcha { async fn perform( &self, - _context: &Data, - websocket_info: Option, + context: &Data, + _websocket_id: Option, ) -> Result { let captcha_settings = Settings::get().captcha; @@ -547,9 +542,8 @@ impl Perform for GetCaptcha { expires: naive_now() + Duration::minutes(10), // expires in 10 minutes }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(captcha_item); - } + // Stores the captcha item on the queue + context.chat_server().do_send(captcha_item); Ok(GetCaptchaResponse { ok: Some(CaptchaResponse { png, uuid, wav }), @@ -564,7 +558,7 @@ impl Perform for SaveUserSettings { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &SaveUserSettings = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -690,7 +684,7 @@ impl Perform for GetUserDetails { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &GetUserDetails = &self; let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?; @@ -788,7 +782,7 @@ impl Perform for AddAdmin { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &AddAdmin = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -827,13 +821,11 @@ impl Perform for AddAdmin { let res = AddAdminResponse { admins }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendAllMessage { - op: UserOperation::AddAdmin, - response: res.clone(), - my_id: ws.id, - }); - } + context.chat_server().do_send(SendAllMessage { + op: UserOperation::AddAdmin, + response: res.clone(), + websocket_id, + }); Ok(res) } @@ -846,7 +838,7 @@ impl Perform for BanUser { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &BanUser = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -909,13 +901,11 @@ impl Perform for BanUser { banned: data.ban, }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendAllMessage { - op: UserOperation::BanUser, - response: res.clone(), - my_id: ws.id, - }); - } + context.chat_server().do_send(SendAllMessage { + op: UserOperation::BanUser, + response: res.clone(), + websocket_id, + }); Ok(res) } @@ -928,7 +918,7 @@ impl Perform for GetReplies { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &GetReplies = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -960,7 +950,7 @@ impl Perform for GetUserMentions { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &GetUserMentions = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -992,7 +982,7 @@ impl Perform for MarkUserMentionAsRead { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &MarkUserMentionAsRead = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -1034,7 +1024,7 @@ impl Perform for MarkAllAsRead { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &MarkAllAsRead = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -1086,7 +1076,7 @@ impl Perform for DeleteAccount { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &DeleteAccount = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -1123,7 +1113,7 @@ impl Perform for PasswordReset { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &PasswordReset = &self; @@ -1171,7 +1161,7 @@ impl Perform for PasswordChange { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &PasswordChange = &self; @@ -1212,7 +1202,7 @@ impl Perform for CreatePrivateMessage { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &CreatePrivateMessage = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -1290,14 +1280,12 @@ impl Perform for CreatePrivateMessage { let res = PrivateMessageResponse { message }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendUserRoomMessage { - op: UserOperation::CreatePrivateMessage, - response: res.clone(), - recipient_id: recipient_user.id, - my_id: ws.id, - }); - } + context.chat_server().do_send(SendUserRoomMessage { + op: UserOperation::CreatePrivateMessage, + response: res.clone(), + recipient_id, + websocket_id, + }); Ok(res) } @@ -1310,7 +1298,7 @@ impl Perform for EditPrivateMessage { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &EditPrivateMessage = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -1349,14 +1337,12 @@ impl Perform for EditPrivateMessage { let res = PrivateMessageResponse { message }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendUserRoomMessage { - op: UserOperation::EditPrivateMessage, - response: res.clone(), - recipient_id, - my_id: ws.id, - }); - } + context.chat_server().do_send(SendUserRoomMessage { + op: UserOperation::EditPrivateMessage, + response: res.clone(), + recipient_id, + websocket_id, + }); Ok(res) } @@ -1369,7 +1355,7 @@ impl Perform for DeletePrivateMessage { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &DeletePrivateMessage = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -1414,14 +1400,12 @@ impl Perform for DeletePrivateMessage { let res = PrivateMessageResponse { message }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendUserRoomMessage { - op: UserOperation::DeletePrivateMessage, - response: res.clone(), - recipient_id, - my_id: ws.id, - }); - } + context.chat_server().do_send(SendUserRoomMessage { + op: UserOperation::DeletePrivateMessage, + response: res.clone(), + recipient_id, + websocket_id, + }); Ok(res) } @@ -1434,7 +1418,7 @@ impl Perform for MarkPrivateMessageAsRead { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &MarkPrivateMessageAsRead = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -1472,14 +1456,12 @@ impl Perform for MarkPrivateMessageAsRead { let res = PrivateMessageResponse { message }; - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendUserRoomMessage { - op: UserOperation::MarkPrivateMessageAsRead, - response: res.clone(), - recipient_id, - my_id: ws.id, - }); - } + context.chat_server().do_send(SendUserRoomMessage { + op: UserOperation::MarkPrivateMessageAsRead, + response: res.clone(), + recipient_id, + websocket_id, + }); Ok(res) } @@ -1492,7 +1474,7 @@ impl Perform for GetPrivateMessages { async fn perform( &self, context: &Data, - _websocket_info: Option, + _websocket_id: Option, ) -> Result { let data: &GetPrivateMessages = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -1521,18 +1503,16 @@ impl Perform for UserJoin { async fn perform( &self, context: &Data, - websocket_info: Option, + websocket_id: Option, ) -> Result { let data: &UserJoin = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; - if let Some(ws) = websocket_info { - if let Some(id) = ws.id { - ws.chatserver.do_send(JoinUserRoom { - user_id: user.id, - id, - }); - } + if let Some(ws_id) = websocket_id { + context.chat_server().do_send(JoinUserRoom { + user_id: user.id, + id: ws_id, + }); } Ok(UserJoinResponse { user_id: user.id }) diff --git a/server/src/apub/inbox/activities/create.rs b/server/src/apub/inbox/activities/create.rs index 696aca9ee..caba560dc 100644 --- a/server/src/apub/inbox/activities/create.rs +++ b/server/src/apub/inbox/activities/create.rs @@ -75,7 +75,7 @@ async fn receive_create_post( context.chat_server().do_send(SendPost { op: UserOperation::CreatePost, post: res, - my_id: None, + websocket_id: None, }); announce_if_community_is_local(create, &user, context).await?; @@ -128,7 +128,7 @@ async fn receive_create_comment( context.chat_server().do_send(SendComment { op: UserOperation::CreateComment, comment: res, - my_id: None, + websocket_id: None, }); announce_if_community_is_local(create, &user, context).await?; diff --git a/server/src/apub/inbox/activities/delete.rs b/server/src/apub/inbox/activities/delete.rs index c9ab042ae..2a6689dbf 100644 --- a/server/src/apub/inbox/activities/delete.rs +++ b/server/src/apub/inbox/activities/delete.rs @@ -100,7 +100,7 @@ async fn receive_delete_post( context.chat_server().do_send(SendPost { op: UserOperation::EditPost, post: res, - my_id: None, + websocket_id: None, }); announce_if_community_is_local(delete, &user, context).await?; @@ -158,7 +158,7 @@ async fn receive_delete_comment( context.chat_server().do_send(SendComment { op: UserOperation::EditComment, comment: res, - my_id: None, + websocket_id: None, }); announce_if_community_is_local(delete, &user, context).await?; @@ -222,7 +222,7 @@ async fn receive_delete_community( op: UserOperation::EditCommunity, response: res, community_id, - my_id: None, + websocket_id: None, }); announce_if_community_is_local(delete, &user, context).await?; diff --git a/server/src/apub/inbox/activities/dislike.rs b/server/src/apub/inbox/activities/dislike.rs index e1d3006a3..4d59dd470 100644 --- a/server/src/apub/inbox/activities/dislike.rs +++ b/server/src/apub/inbox/activities/dislike.rs @@ -85,7 +85,7 @@ async fn receive_dislike_post( context.chat_server().do_send(SendPost { op: UserOperation::CreatePostLike, post: res, - my_id: None, + websocket_id: None, }); announce_if_community_is_local(dislike, &user, context).await?; @@ -142,7 +142,7 @@ async fn receive_dislike_comment( context.chat_server().do_send(SendComment { op: UserOperation::CreateCommentLike, comment: res, - my_id: None, + websocket_id: None, }); announce_if_community_is_local(dislike, &user, context).await?; diff --git a/server/src/apub/inbox/activities/like.rs b/server/src/apub/inbox/activities/like.rs index 804c33c39..a3f19b3ce 100644 --- a/server/src/apub/inbox/activities/like.rs +++ b/server/src/apub/inbox/activities/like.rs @@ -76,7 +76,7 @@ async fn receive_like_post(like: Like, context: &LemmyContext) -> Result Result<(), LemmyError> { settings.bind, settings.port ); + let activity_queue = create_activity_queue(); + let chat_server = + ChatServer::startup(pool.clone(), rate_limiter.clone(), Client::default(), activity_queue.clone()).start(); + // Create Http server with websocket support HttpServer::new(move || { - let activity_queue = create_activity_queue(); - let chat_server = ChatServer::startup( - pool.clone(), - rate_limiter.clone(), - Client::default(), - activity_queue.clone(), - ) - .start(); let context = - LemmyContext::create(pool.clone(), chat_server, Client::default(), activity_queue); + LemmyContext::create(pool.clone(), chat_server.to_owned(), Client::default(), activity_queue.to_owned()); let settings = Settings::get(); let rate_limiter = rate_limiter.clone(); App::new() diff --git a/server/src/routes/api.rs b/server/src/routes/api.rs index 1c88ffa24..f2ee38d25 100644 --- a/server/src/routes/api.rs +++ b/server/src/routes/api.rs @@ -1,7 +1,6 @@ use crate::{ api::{comment::*, community::*, post::*, site::*, user::*, Perform}, rate_limit::RateLimit, - websocket::WebsocketInfo, LemmyContext, }; use actix_web::{error::ErrorBadRequest, *}; @@ -94,7 +93,8 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { web::post().to(route_post::), ) .route("/like", web::post().to(route_post::)) - .route("/save", web::put().to(route_post::)), + .route("/save", web::put().to(route_post::)) + .route("/list", web::get().to(route_get::)), ) // Private Message .service( @@ -136,6 +136,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { "/followed_communities", web::get().to(route_get::), ) + .route("/join", web::post().to(route_post::)) // Admin action. I don't like that it's in /user .route("/ban", web::post().to(route_post::)) // Account actions. I don't like that they're in /user maybe /accounts @@ -180,13 +181,8 @@ where Request: Perform, Request: Send + 'static, { - let ws_info = WebsocketInfo { - chatserver: context.chat_server().to_owned(), - id: None, - }; - let res = data - .perform(&context, Some(ws_info)) + .perform(&context, None) .await .map(|json| HttpResponse::Ok().json(json)) .map_err(ErrorBadRequest)?; diff --git a/server/src/websocket/mod.rs b/server/src/websocket/mod.rs index 2b0cd1bde..1430d89ae 100644 --- a/server/src/websocket/mod.rs +++ b/server/src/websocket/mod.rs @@ -1,6 +1,5 @@ pub mod server; -use crate::ConnectionId; use actix::prelude::*; use diesel::{ r2d2::{ConnectionManager, Pool}, @@ -10,7 +9,6 @@ use log::{error, info}; use rand::{rngs::ThreadRng, Rng}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use server::ChatServer; use std::{ collections::{HashMap, HashSet}, str::FromStr, @@ -77,9 +75,3 @@ pub enum UserOperation { GetSiteConfig, SaveSiteConfig, } - -#[derive(Clone)] -pub struct WebsocketInfo { - pub chatserver: Addr, - pub id: Option, -} diff --git a/server/src/websocket/server.rs b/server/src/websocket/server.rs index c76b248ef..69ae0f2f4 100644 --- a/server/src/websocket/server.rs +++ b/server/src/websocket/server.rs @@ -59,7 +59,7 @@ pub struct StandardMessage { pub struct SendAllMessage { pub op: UserOperation, pub response: Response, - pub my_id: Option, + pub websocket_id: Option, } #[derive(Message)] @@ -68,7 +68,7 @@ pub struct SendUserRoomMessage { pub op: UserOperation, pub response: Response, pub recipient_id: UserId, - pub my_id: Option, + pub websocket_id: Option, } #[derive(Message)] @@ -77,7 +77,7 @@ pub struct SendCommunityRoomMessage { pub op: UserOperation, pub response: Response, pub community_id: CommunityId, - pub my_id: Option, + pub websocket_id: Option, } #[derive(Message)] @@ -85,7 +85,7 @@ pub struct SendCommunityRoomMessage { pub struct SendPost { pub op: UserOperation, pub post: PostResponse, - pub my_id: Option, + pub websocket_id: Option, } #[derive(Message)] @@ -93,7 +93,7 @@ pub struct SendPost { pub struct SendComment { pub op: UserOperation, pub comment: CommentResponse, - pub my_id: Option, + pub websocket_id: Option, } #[derive(Message)] @@ -290,7 +290,7 @@ impl ChatServer { op: &UserOperation, response: &Response, post_id: PostId, - my_id: Option, + websocket_id: Option, ) -> Result<(), LemmyError> where Response: Serialize, @@ -298,7 +298,7 @@ impl ChatServer { let res_str = &to_json_string(op, response)?; if let Some(sessions) = self.post_rooms.get(&post_id) { for id in sessions { - if let Some(my_id) = my_id { + if let Some(my_id) = websocket_id { if *id == my_id { continue; } @@ -314,7 +314,7 @@ impl ChatServer { op: &UserOperation, response: &Response, community_id: CommunityId, - my_id: Option, + websocket_id: Option, ) -> Result<(), LemmyError> where Response: Serialize, @@ -322,7 +322,7 @@ impl ChatServer { let res_str = &to_json_string(op, response)?; if let Some(sessions) = self.community_rooms.get(&community_id) { for id in sessions { - if let Some(my_id) = my_id { + if let Some(my_id) = websocket_id { if *id == my_id { continue; } @@ -337,14 +337,14 @@ impl ChatServer { &self, op: &UserOperation, response: &Response, - my_id: Option, + websocket_id: Option, ) -> Result<(), LemmyError> where Response: Serialize, { let res_str = &to_json_string(op, response)?; for id in self.sessions.keys() { - if let Some(my_id) = my_id { + if let Some(my_id) = websocket_id { if *id == my_id { continue; } @@ -359,7 +359,7 @@ impl ChatServer { op: &UserOperation, response: &Response, recipient_id: UserId, - my_id: Option, + websocket_id: Option, ) -> Result<(), LemmyError> where Response: Serialize, @@ -367,7 +367,7 @@ impl ChatServer { let res_str = &to_json_string(op, response)?; if let Some(sessions) = self.user_rooms.get(&recipient_id) { for id in sessions { - if let Some(my_id) = my_id { + if let Some(my_id) = websocket_id { if *id == my_id { continue; } @@ -382,7 +382,7 @@ impl ChatServer { &self, user_operation: &UserOperation, comment: &CommentResponse, - my_id: Option, + websocket_id: Option, ) -> Result<(), LemmyError> { let mut comment_reply_sent = comment.clone(); comment_reply_sent.comment.my_vote = None; @@ -396,21 +396,26 @@ impl ChatServer { user_operation, &comment_post_sent, comment_post_sent.comment.post_id, - my_id, + websocket_id, )?; // Send it to the recipient(s) including the mentioned users for recipient_id in &comment_reply_sent.recipient_ids { - self.send_user_room_message(user_operation, &comment_reply_sent, *recipient_id, my_id)?; + self.send_user_room_message( + user_operation, + &comment_reply_sent, + *recipient_id, + websocket_id, + )?; } // Send it to the community too - self.send_community_room_message(user_operation, &comment_post_sent, 0, my_id)?; + self.send_community_room_message(user_operation, &comment_post_sent, 0, websocket_id)?; self.send_community_room_message( user_operation, &comment_post_sent, comment.comment.community_id, - my_id, + websocket_id, )?; Ok(()) @@ -420,7 +425,7 @@ impl ChatServer { &self, user_operation: &UserOperation, post: &PostResponse, - my_id: Option, + websocket_id: Option, ) -> Result<(), LemmyError> { let community_id = post.post.community_id; @@ -430,11 +435,11 @@ impl ChatServer { post_sent.post.user_id = None; // Send it to /c/all and that community - self.send_community_room_message(user_operation, &post_sent, 0, my_id)?; - self.send_community_room_message(user_operation, &post_sent, community_id, my_id)?; + 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)?; // Send it to the post room - self.send_post_room_message(user_operation, &post_sent, post.post.id, my_id)?; + self.send_post_room_message(user_operation, &post_sent, post.post.id, websocket_id)?; Ok(()) } @@ -478,7 +483,7 @@ impl ChatServer { activity_queue, }; let args = Args { - context: &context, + context, rate_limiter, id: msg.id, ip, @@ -572,7 +577,7 @@ impl ChatServer { } struct Args<'a> { - context: &'a LemmyContext, + context: LemmyContext, rate_limiter: RateLimit, id: ConnectionId, ip: IPAddr, @@ -594,18 +599,13 @@ where data, } = args; - let ws_info = WebsocketInfo { - chatserver: context.chat_server().to_owned(), - id: Some(id), - }; - let data = data.to_string(); let op2 = op.clone(); let fut = async move { let parsed_data: Data = serde_json::from_str(&data)?; let res = parsed_data - .perform(&web::Data::new(context.to_owned()), Some(ws_info)) + .perform(&web::Data::new(context), Some(id)) .await?; to_json_string(&op, &res) }; @@ -699,7 +699,7 @@ where fn handle(&mut self, msg: SendAllMessage, _: &mut Context) { self - .send_all_message(&msg.op, &msg.response, msg.my_id) + .send_all_message(&msg.op, &msg.response, msg.websocket_id) .ok(); } } @@ -712,7 +712,7 @@ where fn handle(&mut self, msg: SendUserRoomMessage, _: &mut Context) { self - .send_user_room_message(&msg.op, &msg.response, msg.recipient_id, msg.my_id) + .send_user_room_message(&msg.op, &msg.response, msg.recipient_id, msg.websocket_id) .ok(); } } @@ -725,7 +725,7 @@ where fn handle(&mut self, msg: SendCommunityRoomMessage, _: &mut Context) { self - .send_community_room_message(&msg.op, &msg.response, msg.community_id, msg.my_id) + .send_community_room_message(&msg.op, &msg.response, msg.community_id, msg.websocket_id) .ok(); } } @@ -734,7 +734,7 @@ impl Handler for ChatServer { type Result = (); fn handle(&mut self, msg: SendPost, _: &mut Context) { - self.send_post(&msg.op, &msg.post, msg.my_id).ok(); + self.send_post(&msg.op, &msg.post, msg.websocket_id).ok(); } } @@ -742,7 +742,9 @@ impl Handler for ChatServer { type Result = (); fn handle(&mut self, msg: SendComment, _: &mut Context) { - self.send_comment(&msg.op, &msg.comment, msg.my_id).ok(); + self + .send_comment(&msg.op, &msg.comment, msg.websocket_id) + .ok(); } } diff --git a/ui/package.json b/ui/package.json index 06c52807a..74740b76f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -37,6 +37,7 @@ "inferno-router": "^7.4.2", "js-cookie": "^2.2.0", "jwt-decode": "^2.2.0", + "lemmy-js-client": "^1.0.8", "markdown-it": "^11.0.0", "markdown-it-container": "^3.0.0", "markdown-it-emoji": "^1.4.0", diff --git a/ui/src/api_tests/comment.spec.ts b/ui/src/api_tests/comment.spec.ts index 02cc683c4..747ec9109 100644 --- a/ui/src/api_tests/comment.spec.ts +++ b/ui/src/api_tests/comment.spec.ts @@ -21,7 +21,7 @@ import { API, } from './shared'; -import { PostResponse } from '../interfaces'; +import { PostResponse } from 'lemmy-js-client'; let postRes: PostResponse; @@ -57,6 +57,11 @@ test('Create a comment', async () => { expect(betaComment.score).toBe(1); }); +test('Create a comment in a non-existent post', async () => { + let commentRes = await createComment(alpha, -1); + expect(commentRes).toStrictEqual({ error: 'couldnt_find_post' }); +}); + test('Update a comment', async () => { let commentRes = await createComment(alpha, postRes.post.id); let updateCommentRes = await updateComment(alpha, commentRes.comment.id); @@ -131,7 +136,7 @@ test('Remove a comment from admin and community on the same instance', async () test('Remove a comment from admin and community on different instance', async () => { let alphaUser = await registerUser(alpha); let newAlphaApi: API = { - url: alpha.url, + client: alpha.client, auth: alphaUser.jwt, }; diff --git a/ui/src/api_tests/post.spec.ts b/ui/src/api_tests/post.spec.ts index 5da72e5a1..ab9c63fb1 100644 --- a/ui/src/api_tests/post.spec.ts +++ b/ui/src/api_tests/post.spec.ts @@ -2,6 +2,8 @@ import { alpha, beta, gamma, + delta, + epsilon, setupLogins, createPost, updatePost, @@ -22,11 +24,15 @@ beforeAll(async () => { await setupLogins(); await followBeta(alpha); await followBeta(gamma); + await followBeta(delta); + await followBeta(epsilon); }); afterAll(async () => { await unfollowRemotes(alpha); await unfollowRemotes(gamma); + await unfollowRemotes(delta); + await unfollowRemotes(epsilon); }); test('Create a post', async () => { @@ -45,6 +51,19 @@ test('Create a post', async () => { expect(betaPost.community_local).toBe(true); expect(betaPost.creator_local).toBe(false); expect(betaPost.score).toBe(1); + + // Delta only follows beta, so it should not see an alpha ap_id + let searchDelta = await searchPost(delta, postRes.post); + expect(searchDelta.posts[0]).toBeUndefined(); + + // Epsilon has alpha blocked, it should not see the alpha post + let searchEpsilon = await searchPost(epsilon, postRes.post); + expect(searchEpsilon.posts[0]).toBeUndefined(); +}); + +test('Create a post in a non-existent community', async () => { + let postRes = await createPost(alpha, -2); + expect(postRes).toStrictEqual({ error: 'couldnt_create_post' }); }); test('Unlike a post', async () => { @@ -53,6 +72,10 @@ test('Unlike a post', async () => { let unlike = await likePost(alpha, 0, postRes.post); expect(unlike.post.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); + // Make sure that post is unliked on beta let searchBeta = await searchPost(beta, postRes.post); let betaPost = searchBeta.posts[0]; @@ -67,10 +90,22 @@ test('Update a post', async () => { let search = await searchForBetaCommunity(alpha); let postRes = await createPost(alpha, search.communities[0].id); + let updatedName = 'A jest test federated post, updated'; let updatedPost = await updatePost(alpha, postRes.post); - expect(updatedPost.post.name).toBe('A jest test federated post, updated'); + expect(updatedPost.post.name).toBe(updatedName); expect(updatedPost.post.community_local).toBe(false); expect(updatedPost.post.creator_local).toBe(true); + + // Make sure that post is updated on beta + let searchBeta = await searchPost(beta, postRes.post); + let betaPost = searchBeta.posts[0]; + expect(betaPost.community_local).toBe(true); + expect(betaPost.creator_local).toBe(false); + expect(betaPost.name).toBe(updatedName); + + // Make sure lemmy beta cannot update the post + let updatedPostBeta = await updatePost(beta, betaPost); + expect(updatedPostBeta).toStrictEqual({ error: 'no_post_edit_allowed' }); }); test('Sticky a post', async () => { @@ -97,6 +132,15 @@ test('Sticky a post', async () => { expect(betaPost2.community_local).toBe(true); expect(betaPost2.creator_local).toBe(false); expect(betaPost2.stickied).toBe(false); + + // Make sure that gamma cannot sticky the post on beta + let searchGamma = await searchPost(gamma, postRes.post); + let gammaPost = searchGamma.posts[0]; + let gammaTrySticky = await stickyPost(gamma, true, gammaPost); + let searchBeta3 = await searchPost(beta, postRes.post); + let betaPost3 = searchBeta3.posts[0]; + expect(gammaTrySticky.post.stickied).toBe(true); + expect(betaPost3.stickied).toBe(false); }); test('Lock a post', async () => { @@ -152,6 +196,10 @@ test('Delete a post', async () => { // Make sure lemmy beta sees post is undeleted let betaPost2 = await getPost(beta, createFakeBetaPostToGetId); expect(betaPost2.post.deleted).toBe(false); + + // Make sure lemmy beta cannot delete the post + 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 () => { diff --git a/ui/src/api_tests/shared.ts b/ui/src/api_tests/shared.ts index 31530ef7e..710671c0e 100644 --- a/ui/src/api_tests/shared.ts +++ b/ui/src/api_tests/shared.ts @@ -1,5 +1,3 @@ -import fetch from 'node-fetch'; - import { LoginForm, LoginResponse, @@ -20,15 +18,21 @@ import { CommentForm, DeleteCommentForm, RemoveCommentForm, + SearchForm, CommentResponse, CommunityForm, DeleteCommunityForm, RemoveCommunityForm, + GetUserMentionsForm, CommentLikeForm, CreatePostLikeForm, PrivateMessageForm, EditPrivateMessageForm, DeletePrivateMessageForm, + GetFollowedCommunitiesForm, + GetPrivateMessagesForm, + GetSiteForm, + GetPostForm, PrivateMessageResponse, PrivateMessagesResponse, GetUserMentionsResponse, @@ -36,73 +40,79 @@ import { SortType, ListingType, GetSiteResponse, -} from '../interfaces'; + SearchType, + LemmyHttp, +} from 'lemmy-js-client'; export interface API { - url: string; + client: LemmyHttp; auth?: string; } -function apiUrl(api: API) { - return `${api.url}/api/v1`; -} - export let alpha: API = { - url: 'http://localhost:8540', + client: new LemmyHttp('http://localhost:8540/api/v1'), }; export let beta: API = { - url: 'http://localhost:8550', + client: new LemmyHttp('http://localhost:8550/api/v1'), }; export let gamma: API = { - url: 'http://localhost:8560', + client: new LemmyHttp('http://localhost:8560/api/v1'), +}; + +export let delta: API = { + client: new LemmyHttp('http://localhost:8570/api/v1'), +}; + +export let epsilon: API = { + client: new LemmyHttp('http://localhost:8580/api/v1'), }; export async function setupLogins() { - let form: LoginForm = { + let formAlpha: LoginForm = { username_or_email: 'lemmy_alpha', password: 'lemmy', }; + let resAlpha = alpha.client.login(formAlpha); - let resA: Promise = fetch(`${apiUrl(alpha)}/user/login`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(form), - }).then(d => d.json()); - - let formB = { + let formBeta = { username_or_email: 'lemmy_beta', password: 'lemmy', }; + let resBeta = beta.client.login(formBeta); - let resB: Promise = fetch(`${apiUrl(beta)}/user/login`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(formB), - }).then(d => d.json()); - - let formC = { + let formGamma = { username_or_email: 'lemmy_gamma', password: 'lemmy', }; + let resGamma = gamma.client.login(formGamma); - let resG: Promise = fetch(`${apiUrl(gamma)}/user/login`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(formC), - }).then(d => d.json()); + let formDelta = { + username_or_email: 'lemmy_delta', + password: 'lemmy', + }; + let resDelta = delta.client.login(formDelta); + + let formEpsilon = { + username_or_email: 'lemmy_epsilon', + password: 'lemmy', + }; + let resEpsilon = epsilon.client.login(formEpsilon); + + let res = await Promise.all([ + resAlpha, + resBeta, + resGamma, + resDelta, + resEpsilon, + ]); - let res = await Promise.all([resA, resB, resG]); alpha.auth = res[0].jwt; beta.auth = res[1].jwt; gamma.auth = res[2].jwt; + delta.auth = res[3].jwt; + epsilon.auth = res[4].jwt; } export async function createPost( @@ -110,40 +120,24 @@ export async function createPost( community_id: number ): Promise { let name = 'A jest test post'; - let postForm: PostForm = { + let form: PostForm = { name, auth: api.auth, community_id, nsfw: false, }; - - let createPostRes: PostResponse = await fetch(`${apiUrl(api)}/post`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(postForm), - }).then(d => d.json()); - return createPostRes; + return api.client.createPost(form); } export async function updatePost(api: API, post: Post): Promise { let name = 'A jest test federated post, updated'; - let postForm: PostForm = { + let form: PostForm = { name, edit_id: post.id, auth: api.auth, nsfw: false, }; - - let updateResponse: PostResponse = await fetch(`${apiUrl(api)}/post`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(postForm), - }).then(d => d.json()); - return updateResponse; + return api.client.editPost(form); } export async function deletePost( @@ -151,20 +145,12 @@ export async function deletePost( deleted: boolean, post: Post ): Promise { - let deletePostForm: DeletePostForm = { + let form: DeletePostForm = { edit_id: post.id, deleted: deleted, auth: api.auth, }; - - let deletePostRes: PostResponse = await fetch(`${apiUrl(api)}/post/delete`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(deletePostForm), - }).then(d => d.json()); - return deletePostRes; + return api.client.deletePost(form); } export async function removePost( @@ -172,20 +158,12 @@ export async function removePost( removed: boolean, post: Post ): Promise { - let removePostForm: RemovePostForm = { + let form: RemovePostForm = { edit_id: post.id, removed, auth: api.auth, }; - - let removePostRes: PostResponse = await fetch(`${apiUrl(api)}/post/remove`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(removePostForm), - }).then(d => d.json()); - return removePostRes; + return api.client.removePost(form); } export async function stickyPost( @@ -193,21 +171,12 @@ export async function stickyPost( stickied: boolean, post: Post ): Promise { - let stickyPostForm: StickyPostForm = { + let form: StickyPostForm = { edit_id: post.id, stickied, auth: api.auth, }; - - let stickyRes: PostResponse = await fetch(`${apiUrl(api)}/post/sticky`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(stickyPostForm), - }).then(d => d.json()); - - return stickyRes; + return api.client.stickyPost(form); } export async function lockPost( @@ -215,57 +184,46 @@ export async function lockPost( locked: boolean, post: Post ): Promise { - let lockPostForm: LockPostForm = { + let form: LockPostForm = { edit_id: post.id, locked, auth: api.auth, }; - - let lockRes: PostResponse = await fetch(`${apiUrl(api)}/post/lock`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(lockPostForm), - }).then(d => d.json()); - - return lockRes; + return api.client.lockPost(form); } export async function searchPost( api: API, post: Post ): Promise { - let searchUrl = `${apiUrl(api)}/search?q=${post.ap_id}&type_=All&sort=TopAll`; - let searchResponse: SearchResponse = await fetch(searchUrl, { - method: 'GET', - }).then(d => d.json()); - return searchResponse; + let form: SearchForm = { + q: post.ap_id, + type_: SearchType.All, + sort: SortType.TopAll, + }; + return api.client.search(form); } export async function getPost( api: API, post_id: number ): Promise { - let getPostUrl = `${apiUrl(api)}/post?id=${post_id}`; - let getPostRes: GetPostResponse = await fetch(getPostUrl, { - method: 'GET', - }).then(d => d.json()); - - return getPostRes; + let form: GetPostForm = { + id: post_id, + }; + return api.client.getPost(form); } export async function searchComment( api: API, comment: Comment ): Promise { - let searchUrl = `${apiUrl(api)}/search?q=${ - comment.ap_id - }&type_=All&sort=TopAll`; - let searchResponse: SearchResponse = await fetch(searchUrl, { - method: 'GET', - }).then(d => d.json()); - return searchResponse; + let form: SearchForm = { + q: comment.ap_id, + type_: SearchType.All, + sort: SortType.TopAll, + }; + return api.client.search(form); } export async function searchForBetaCommunity( @@ -273,14 +231,12 @@ export async function searchForBetaCommunity( ): Promise { // Make sure lemmy-beta/c/main is cached on lemmy_alpha // Use short-hand search url - let searchUrl = `${apiUrl( - api - )}/search?q=!main@lemmy-beta:8550&type_=All&sort=TopAll`; - - let searchResponse: SearchResponse = await fetch(searchUrl, { - method: 'GET', - }).then(d => d.json()); - return searchResponse; + let form: SearchForm = { + q: '!main@lemmy-beta:8550', + type_: SearchType.All, + sort: SortType.TopAll, + }; + return api.client.search(form); } export async function searchForUser( @@ -289,14 +245,12 @@ export async function searchForUser( ): Promise { // Make sure lemmy-beta/c/main is cached on lemmy_alpha // Use short-hand search url - let searchUrl = `${apiUrl( - api - )}/search?q=${apShortname}&type_=All&sort=TopAll`; - - let searchResponse: SearchResponse = await fetch(searchUrl, { - method: 'GET', - }).then(d => d.json()); - return searchResponse; + let form: SearchForm = { + q: apShortname, + type_: SearchType.All, + sort: SortType.TopAll, + }; + return api.client.search(form); } export async function followCommunity( @@ -304,41 +258,21 @@ export async function followCommunity( follow: boolean, community_id: number ): Promise { - let followForm: FollowCommunityForm = { + let form: FollowCommunityForm = { community_id, follow, auth: api.auth, }; - - let followRes: CommunityResponse = await fetch( - `${apiUrl(api)}/community/follow`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(followForm), - } - ) - .then(d => d.json()) - .catch(_e => {}); - - return followRes; + return api.client.followCommunity(form); } export async function checkFollowedCommunities( api: API ): Promise { - let followedCommunitiesUrl = `${apiUrl( - api - )}/user/followed_communities?&auth=${api.auth}`; - let followedCommunitiesRes: GetFollowedCommunitiesResponse = await fetch( - followedCommunitiesUrl, - { - method: 'GET', - } - ).then(d => d.json()); - return followedCommunitiesRes; + let form: GetFollowedCommunitiesForm = { + auth: api.auth, + }; + return api.client.getFollowedCommunities(form); } export async function likePost( @@ -346,21 +280,13 @@ export async function likePost( score: number, post: Post ): Promise { - let likePostForm: CreatePostLikeForm = { + let form: CreatePostLikeForm = { post_id: post.id, score: score, auth: api.auth, }; - let likePostRes: PostResponse = await fetch(`${apiUrl(api)}/post/like`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(likePostForm), - }).then(d => d.json()); - - return likePostRes; + return api.client.likePost(form); } export async function createComment( @@ -369,21 +295,13 @@ export async function createComment( parent_id?: number, content = 'a jest test comment' ): Promise { - let commentForm: CommentForm = { + let form: CommentForm = { content, post_id, parent_id, auth: api.auth, }; - - let createResponse: CommentResponse = await fetch(`${apiUrl(api)}/comment`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(commentForm), - }).then(d => d.json()); - return createResponse; + return api.client.createComment(form); } export async function updateComment( @@ -391,20 +309,12 @@ export async function updateComment( edit_id: number, content = 'A jest test federated comment update' ): Promise { - let commentForm: CommentForm = { + let form: CommentForm = { content, edit_id, auth: api.auth, }; - - let updateResponse: CommentResponse = await fetch(`${apiUrl(api)}/comment`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(commentForm), - }).then(d => d.json()); - return updateResponse; + return api.client.editComment(form); } export async function deleteComment( @@ -412,23 +322,12 @@ export async function deleteComment( deleted: boolean, edit_id: number ): Promise { - let deleteCommentForm: DeleteCommentForm = { + let form: DeleteCommentForm = { edit_id, deleted, auth: api.auth, }; - - let deleteCommentRes: CommentResponse = await fetch( - `${apiUrl(api)}/comment/delete`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(deleteCommentForm), - } - ).then(d => d.json()); - return deleteCommentRes; + return api.client.deleteComment(form); } export async function removeComment( @@ -436,33 +335,21 @@ export async function removeComment( removed: boolean, edit_id: number ): Promise { - let removeCommentForm: RemoveCommentForm = { + let form: RemoveCommentForm = { edit_id, removed, auth: api.auth, }; - - let removeCommentRes: CommentResponse = await fetch( - `${apiUrl(api)}/comment/remove`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(removeCommentForm), - } - ).then(d => d.json()); - return removeCommentRes; + return api.client.removeComment(form); } export async function getMentions(api: API): Promise { - let getMentionUrl = `${apiUrl( - api - )}/user/mention?sort=New&unread_only=false&auth=${api.auth}`; - let getMentionsRes: GetUserMentionsResponse = await fetch(getMentionUrl, { - method: 'GET', - }).then(d => d.json()); - return getMentionsRes; + let form: GetUserMentionsForm = { + sort: SortType.New, + unread_only: false, + auth: api.auth, + }; + return api.client.getUserMentions(form); } export async function likeComment( @@ -470,48 +357,26 @@ export async function likeComment( score: number, comment: Comment ): Promise { - let likeCommentForm: CommentLikeForm = { + let form: CommentLikeForm = { comment_id: comment.id, score, auth: api.auth, }; - - let likeCommentRes: CommentResponse = await fetch( - `${apiUrl(api)}/comment/like`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(likeCommentForm), - } - ).then(d => d.json()); - return likeCommentRes; + return api.client.likeComment(form); } export async function createCommunity( api: API, name_: string = randomString(5) ): Promise { - let communityForm: CommunityForm = { + let form: CommunityForm = { name: name_, title: name_, category_id: 1, nsfw: false, auth: api.auth, }; - - let createCommunityRes: CommunityResponse = await fetch( - `${apiUrl(api)}/community`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(communityForm), - } - ).then(d => d.json()); - return createCommunityRes; + return api.client.createCommunity(form); } export async function deleteCommunity( @@ -519,23 +384,12 @@ export async function deleteCommunity( deleted: boolean, edit_id: number ): Promise { - let deleteCommunityForm: DeleteCommunityForm = { + let form: DeleteCommunityForm = { edit_id, deleted, auth: api.auth, }; - - let deleteResponse: CommunityResponse = await fetch( - `${apiUrl(api)}/community/delete`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(deleteCommunityForm), - } - ).then(d => d.json()); - return deleteResponse; + return api.client.deleteCommunity(form); } export async function removeCommunity( @@ -543,23 +397,12 @@ export async function removeCommunity( removed: boolean, edit_id: number ): Promise { - let removeCommunityForm: RemoveCommunityForm = { + let form: RemoveCommunityForm = { edit_id, removed, auth: api.auth, }; - - let removeResponse: CommunityResponse = await fetch( - `${apiUrl(api)}/community/remove`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(removeCommunityForm), - } - ).then(d => d.json()); - return removeResponse; + return api.client.removeCommunity(form); } export async function createPrivateMessage( @@ -567,23 +410,12 @@ export async function createPrivateMessage( recipient_id: number ): Promise { let content = 'A jest test federated private message'; - let privateMessageForm: PrivateMessageForm = { + let form: PrivateMessageForm = { content, recipient_id, auth: api.auth, }; - - let createRes: PrivateMessageResponse = await fetch( - `${apiUrl(api)}/private_message`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(privateMessageForm), - } - ).then(d => d.json()); - return createRes; + return api.client.createPrivateMessage(form); } export async function updatePrivateMessage( @@ -591,23 +423,12 @@ export async function updatePrivateMessage( edit_id: number ): Promise { let updatedContent = 'A jest test federated private message edited'; - let updatePrivateMessageForm: EditPrivateMessageForm = { + let form: EditPrivateMessageForm = { content: updatedContent, edit_id, auth: api.auth, }; - - let updateRes: PrivateMessageResponse = await fetch( - `${apiUrl(api)}/private_message`, - { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(updatePrivateMessageForm), - } - ).then(d => d.json()); - return updateRes; + return api.client.editPrivateMessage(form); } export async function deletePrivateMessage( @@ -615,50 +436,26 @@ export async function deletePrivateMessage( deleted: boolean, edit_id: number ): Promise { - let deletePrivateMessageForm: DeletePrivateMessageForm = { + let form: DeletePrivateMessageForm = { deleted, edit_id, auth: api.auth, }; - - let deleteRes: PrivateMessageResponse = await fetch( - `${apiUrl(api)}/private_message/delete`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(deletePrivateMessageForm), - } - ).then(d => d.json()); - - return deleteRes; + return api.client.deletePrivateMessage(form); } export async function registerUser( api: API, username: string = randomString(5) ): Promise { - let registerForm: RegisterForm = { + let form: RegisterForm = { username, password: 'test', password_verify: 'test', admin: false, show_nsfw: true, }; - - let registerRes: Promise = fetch( - `${apiUrl(api)}/user/register`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(registerForm), - } - ).then(d => d.json()); - - return registerRes; + return api.client.register(form); } export async function saveUserSettingsBio( @@ -668,54 +465,36 @@ export async function saveUserSettingsBio( let form: UserSettingsForm = { show_nsfw: true, theme: 'darkly', - default_sort_type: SortType.Active, - default_listing_type: ListingType.All, + default_sort_type: Object.keys(SortType).indexOf(SortType.Active), + default_listing_type: Object.keys(ListingType).indexOf(ListingType.All), lang: 'en', show_avatars: true, send_notifications_to_email: false, bio: 'a changed bio', auth, }; - - let res: Promise = fetch( - `${apiUrl(api)}/user/save_user_settings`, - { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(form), - } - ).then(d => d.json()); - return res; + return api.client.saveUserSettings(form); } export async function getSite( api: API, auth: string ): Promise { - let siteUrl = `${apiUrl(api)}/site?auth=${auth}`; - - let res: GetSiteResponse = await fetch(siteUrl, { - method: 'GET', - }).then(d => d.json()); - return res; + let form: GetSiteForm = { + auth, + }; + return api.client.getSite(form); } export async function listPrivateMessages( api: API ): Promise { - let getPrivateMessagesUrl = `${apiUrl(api)}/private_message/list?auth=${ - api.auth - }&unread_only=false&limit=999`; - - let getPrivateMessagesRes: PrivateMessagesResponse = await fetch( - getPrivateMessagesUrl, - { - method: 'GET', - } - ).then(d => d.json()); - return getPrivateMessagesRes; + let form: GetPrivateMessagesForm = { + auth: api.auth, + unread_only: false, + limit: 999, + }; + return api.client.getPrivateMessages(form); } export async function unfollowRemotes( diff --git a/ui/src/components/admin-settings.tsx b/ui/src/components/admin-settings.tsx index fe50b1e94..a3bfdd813 100644 --- a/ui/src/components/admin-settings.tsx +++ b/ui/src/components/admin-settings.tsx @@ -9,7 +9,7 @@ import { SiteConfigForm, GetSiteConfigResponse, WebSocketJsonResponse, -} from '../interfaces'; +} from 'lemmy-js-client'; import { WebSocketService } from '../services'; import { wsJsonToRes, capitalizeFirstLetter, toast, randomStr } from '../utils'; import autosize from 'autosize'; diff --git a/ui/src/components/comment-form.tsx b/ui/src/components/comment-form.tsx index 5597f58ee..dbd14dc76 100644 --- a/ui/src/components/comment-form.tsx +++ b/ui/src/components/comment-form.tsx @@ -8,7 +8,7 @@ import { WebSocketJsonResponse, UserOperation, CommentResponse, -} from '../interfaces'; +} from 'lemmy-js-client'; import { capitalizeFirstLetter, wsJsonToRes } from '../utils'; import { WebSocketService, UserService } from '../services'; import { i18n } from '../i18next'; diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx index 13263b822..1992c4fc8 100644 --- a/ui/src/components/comment-node.tsx +++ b/ui/src/components/comment-node.tsx @@ -16,10 +16,9 @@ import { AddAdminForm, TransferCommunityForm, TransferSiteForm, - BanType, - CommentSortType, SortType, -} from '../interfaces'; +} from 'lemmy-js-client'; +import { CommentSortType, BanType } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { mdToHtml, diff --git a/ui/src/components/comment-nodes.tsx b/ui/src/components/comment-nodes.tsx index 62693f766..bdb8a545e 100644 --- a/ui/src/components/comment-nodes.tsx +++ b/ui/src/components/comment-nodes.tsx @@ -1,11 +1,11 @@ import { Component } from 'inferno'; +import { CommentSortType } from '../interfaces'; import { CommentNode as CommentNodeI, CommunityUser, UserView, - CommentSortType, SortType, -} from '../interfaces'; +} from 'lemmy-js-client'; import { commentSort, commentSortSortType } from '../utils'; import { CommentNode } from './comment-node'; diff --git a/ui/src/components/communities.tsx b/ui/src/components/communities.tsx index 038e4517f..5be032c5e 100644 --- a/ui/src/components/communities.tsx +++ b/ui/src/components/communities.tsx @@ -13,7 +13,7 @@ import { WebSocketJsonResponse, GetSiteResponse, Site, -} from '../interfaces'; +} from 'lemmy-js-client'; import { WebSocketService } from '../services'; import { wsJsonToRes, toast, getPageFromProps } from '../utils'; import { CommunityLink } from './community-link'; @@ -218,7 +218,7 @@ export class Communities extends Component { refetch() { let listCommunitiesForm: ListCommunitiesForm = { - sort: SortType[SortType.TopAll], + sort: SortType.TopAll, limit: communityLimit, page: this.state.page, }; diff --git a/ui/src/components/community-form.tsx b/ui/src/components/community-form.tsx index 1ae96ac8b..7b8c379ba 100644 --- a/ui/src/components/community-form.tsx +++ b/ui/src/components/community-form.tsx @@ -9,12 +9,12 @@ import { ListCategoriesResponse, CommunityResponse, WebSocketJsonResponse, -} from '../interfaces'; + Community, +} from 'lemmy-js-client'; import { WebSocketService } from '../services'; import { wsJsonToRes, capitalizeFirstLetter, toast, randomStr } from '../utils'; import { i18n } from '../i18next'; -import { Community } from '../interfaces'; import { MarkdownTextArea } from './markdown-textarea'; import { ImageUploadForm } from './image-upload-form'; diff --git a/ui/src/components/community-link.tsx b/ui/src/components/community-link.tsx index 293ded046..003f61e14 100644 --- a/ui/src/components/community-link.tsx +++ b/ui/src/components/community-link.tsx @@ -1,6 +1,6 @@ import { Component } from 'inferno'; import { Link } from 'inferno-router'; -import { Community } from '../interfaces'; +import { Community } from 'lemmy-js-client'; import { hostname, pictrsAvatarThumbnail, showAvatars } from '../utils'; interface CommunityOther { diff --git a/ui/src/components/community.tsx b/ui/src/components/community.tsx index 1fe75c598..f86562f8c 100644 --- a/ui/src/components/community.tsx +++ b/ui/src/components/community.tsx @@ -2,6 +2,7 @@ import { Component, linkEvent } from 'inferno'; import { Helmet } from 'inferno-helmet'; import { Subscription } from 'rxjs'; import { retryWhen, delay, take } from 'rxjs/operators'; +import { DataType } from '../interfaces'; import { UserOperation, Community as CommunityI, @@ -14,7 +15,6 @@ import { GetPostsForm, GetCommunityForm, ListingType, - DataType, GetPostsResponse, PostResponse, AddModToCommunityResponse, @@ -26,7 +26,7 @@ import { WebSocketJsonResponse, GetSiteResponse, Site, -} from '../interfaces'; +} from 'lemmy-js-client'; import { WebSocketService } from '../services'; import { PostListings } from './post-listings'; import { CommentNodes } from './comment-nodes'; @@ -78,7 +78,7 @@ interface CommunityProps { interface UrlParams { dataType?: string; - sort?: string; + sort?: SortType; page?: number; } @@ -287,9 +287,7 @@ export class Community extends Component { { } handleSortChange(val: SortType) { - this.updateUrl({ sort: SortType[val].toLowerCase(), page: 1 }); + this.updateUrl({ sort: val, page: 1 }); window.scrollTo(0, 0); } handleDataTypeChange(val: DataType) { - this.updateUrl({ dataType: DataType[val].toLowerCase(), page: 1 }); + this.updateUrl({ dataType: DataType[val], page: 1 }); window.scrollTo(0, 0); } updateUrl(paramUpdates: UrlParams) { - const dataTypeStr = - paramUpdates.dataType || DataType[this.state.dataType].toLowerCase(); - const sortStr = - paramUpdates.sort || SortType[this.state.sort].toLowerCase(); + const dataTypeStr = paramUpdates.dataType || DataType[this.state.dataType]; + const sortStr = paramUpdates.sort || this.state.sort; const page = paramUpdates.page || this.state.page; this.props.history.push( `/c/${this.state.community.name}/data_type/${dataTypeStr}/sort/${sortStr}/page/${page}` @@ -361,8 +357,8 @@ export class Community extends Component { let getPostsForm: GetPostsForm = { page: this.state.page, limit: fetchLimit, - sort: SortType[this.state.sort], - type_: ListingType[ListingType.Community], + sort: this.state.sort, + type_: ListingType.Community, community_id: this.state.community.id, }; WebSocketService.Instance.getPosts(getPostsForm); @@ -370,8 +366,8 @@ export class Community extends Component { let getCommentsForm: GetCommentsForm = { page: this.state.page, limit: fetchLimit, - sort: SortType[this.state.sort], - type_: ListingType[ListingType.Community], + sort: this.state.sort, + type_: ListingType.Community, community_id: this.state.community.id, }; WebSocketService.Instance.getComments(getCommentsForm); diff --git a/ui/src/components/create-community.tsx b/ui/src/components/create-community.tsx index 8317ffbe8..6f156211d 100644 --- a/ui/src/components/create-community.tsx +++ b/ui/src/components/create-community.tsx @@ -9,7 +9,7 @@ import { WebSocketJsonResponse, GetSiteResponse, Site, -} from '../interfaces'; +} from 'lemmy-js-client'; import { toast, wsJsonToRes } from '../utils'; import { WebSocketService, UserService } from '../services'; import { i18n } from '../i18next'; diff --git a/ui/src/components/create-post.tsx b/ui/src/components/create-post.tsx index eb86d8f88..f4c03b653 100644 --- a/ui/src/components/create-post.tsx +++ b/ui/src/components/create-post.tsx @@ -11,7 +11,7 @@ import { WebSocketJsonResponse, GetSiteResponse, Site, -} from '../interfaces'; +} from 'lemmy-js-client'; import { i18n } from '../i18next'; interface CreatePostState { diff --git a/ui/src/components/create-private-message.tsx b/ui/src/components/create-private-message.tsx index ed06a66ae..98c69d5b7 100644 --- a/ui/src/components/create-private-message.tsx +++ b/ui/src/components/create-private-message.tsx @@ -10,7 +10,7 @@ import { GetSiteResponse, Site, PrivateMessageFormParams, -} from '../interfaces'; +} from 'lemmy-js-client'; import { toast, wsJsonToRes } from '../utils'; import { i18n } from '../i18next'; diff --git a/ui/src/components/footer.tsx b/ui/src/components/footer.tsx index 6e7acb7a0..62585ff30 100644 --- a/ui/src/components/footer.tsx +++ b/ui/src/components/footer.tsx @@ -9,7 +9,7 @@ import { UserOperation, WebSocketJsonResponse, GetSiteResponse, -} from '../interfaces'; +} from 'lemmy-js-client'; interface FooterState { version: string; diff --git a/ui/src/components/iframely-card.tsx b/ui/src/components/iframely-card.tsx index 1a47f377a..6a604f7c5 100644 --- a/ui/src/components/iframely-card.tsx +++ b/ui/src/components/iframely-card.tsx @@ -1,5 +1,5 @@ import { Component, linkEvent } from 'inferno'; -import { Post } from '../interfaces'; +import { Post } from 'lemmy-js-client'; import { mdToHtml } from '../utils'; import { i18n } from '../i18next'; diff --git a/ui/src/components/inbox.tsx b/ui/src/components/inbox.tsx index ecc9223c3..4da86e351 100644 --- a/ui/src/components/inbox.tsx +++ b/ui/src/components/inbox.tsx @@ -19,7 +19,7 @@ import { PrivateMessageResponse, GetSiteResponse, Site, -} from '../interfaces'; +} from 'lemmy-js-client'; import { WebSocketService, UserService } from '../services'; import { wsJsonToRes, @@ -399,7 +399,7 @@ export class Inbox extends Component { refetch() { let repliesForm: GetRepliesForm = { - sort: SortType[this.state.sort], + sort: this.state.sort, unread_only: this.state.unreadOrAll == UnreadOrAll.Unread, page: this.state.page, limit: fetchLimit, @@ -407,7 +407,7 @@ export class Inbox extends Component { WebSocketService.Instance.getReplies(repliesForm); let userMentionsForm: GetUserMentionsForm = { - sort: SortType[this.state.sort], + sort: this.state.sort, unread_only: this.state.unreadOrAll == UnreadOrAll.Unread, page: this.state.page, limit: fetchLimit, diff --git a/ui/src/components/instances.tsx b/ui/src/components/instances.tsx index ae4e3f13b..bcc02480b 100644 --- a/ui/src/components/instances.tsx +++ b/ui/src/components/instances.tsx @@ -6,7 +6,7 @@ import { UserOperation, WebSocketJsonResponse, GetSiteResponse, -} from '../interfaces'; +} from 'lemmy-js-client'; import { WebSocketService } from '../services'; import { wsJsonToRes, toast } from '../utils'; import { i18n } from '../i18next'; diff --git a/ui/src/components/listing-type-select.tsx b/ui/src/components/listing-type-select.tsx index 6bdf457c4..3d12d4343 100644 --- a/ui/src/components/listing-type-select.tsx +++ b/ui/src/components/listing-type-select.tsx @@ -1,7 +1,7 @@ import { Component, linkEvent } from 'inferno'; -import { ListingType } from '../interfaces'; +import { ListingType } from 'lemmy-js-client'; import { UserService } from '../services'; - +import { randomStr } from '../utils'; import { i18n } from '../i18next'; interface ListingTypeSelectProps { @@ -17,6 +17,8 @@ export class ListingTypeSelect extends Component< ListingTypeSelectProps, ListingTypeSelectState > { + private id = `listing-type-input-${randomStr()}`; + private emptyState: ListingTypeSelectState = { type_: this.props.type_, }; @@ -42,6 +44,7 @@ export class ListingTypeSelect extends Component< `} > { } let listCommunitiesForm: ListCommunitiesForm = { - sort: SortType[SortType.Hot], + sort: SortType.Hot, limit: 6, }; @@ -334,13 +334,9 @@ export class Main extends Component { } updateUrl(paramUpdates: UrlParams) { - const listingTypeStr = - paramUpdates.listingType || - ListingType[this.state.listingType].toLowerCase(); - const dataTypeStr = - paramUpdates.dataType || DataType[this.state.dataType].toLowerCase(); - const sortStr = - paramUpdates.sort || SortType[this.state.sort].toLowerCase(); + const listingTypeStr = paramUpdates.listingType || this.state.listingType; + const dataTypeStr = paramUpdates.dataType || DataType[this.state.dataType]; + const sortStr = paramUpdates.sort || this.state.sort; const page = paramUpdates.page || this.state.page; this.props.history.push( `/home/data_type/${dataTypeStr}/listing_type/${listingTypeStr}/sort/${sortStr}/page/${page}` @@ -549,7 +545,7 @@ export class Main extends Component { {this.state.listingType == ListingType.All && ( { {UserService.Instance.user && this.state.listingType == ListingType.Subscribed && ( { } handleSortChange(val: SortType) { - this.updateUrl({ sort: SortType[val].toLowerCase(), page: 1 }); + this.updateUrl({ sort: val, page: 1 }); window.scrollTo(0, 0); } handleListingTypeChange(val: ListingType) { - this.updateUrl({ listingType: ListingType[val].toLowerCase(), page: 1 }); + this.updateUrl({ listingType: val, page: 1 }); window.scrollTo(0, 0); } handleDataTypeChange(val: DataType) { - this.updateUrl({ dataType: DataType[val].toLowerCase(), page: 1 }); + this.updateUrl({ dataType: DataType[val], page: 1 }); window.scrollTo(0, 0); } @@ -650,16 +644,16 @@ export class Main extends Component { let getPostsForm: GetPostsForm = { page: this.state.page, limit: fetchLimit, - sort: SortType[this.state.sort], - type_: ListingType[this.state.listingType], + sort: this.state.sort, + type_: this.state.listingType, }; WebSocketService.Instance.getPosts(getPostsForm); } else { let getCommentsForm: GetCommentsForm = { page: this.state.page, limit: fetchLimit, - sort: SortType[this.state.sort], - type_: ListingType[this.state.listingType], + sort: this.state.sort, + type_: this.state.listingType, }; WebSocketService.Instance.getComments(getCommentsForm); } diff --git a/ui/src/components/modlog.tsx b/ui/src/components/modlog.tsx index 0cc78da73..106015a4a 100644 --- a/ui/src/components/modlog.tsx +++ b/ui/src/components/modlog.tsx @@ -19,7 +19,7 @@ import { WebSocketJsonResponse, GetSiteResponse, Site, -} from '../interfaces'; +} from 'lemmy-js-client'; import { WebSocketService } from '../services'; import { wsJsonToRes, addTypeInfo, fetchLimit, toast } from '../utils'; import { MomentTime } from './moment-time'; diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx index 0ddcbb4cd..4ef5276c3 100644 --- a/ui/src/components/navbar.tsx +++ b/ui/src/components/navbar.tsx @@ -18,7 +18,7 @@ import { PrivateMessage, PrivateMessageResponse, WebSocketJsonResponse, -} from '../interfaces'; +} from 'lemmy-js-client'; import { wsJsonToRes, pictrsAvatarThumbnail, @@ -137,7 +137,7 @@ export class Navbar extends Component { this.context.router.history.push(`/search/`); } else { this.context.router.history.push( - `/search/q/${searchParam}/type/all/sort/topall/page/1` + `/search/q/${searchParam}/type/All/sort/TopAll/page/1` ); } } @@ -477,14 +477,14 @@ export class Navbar extends Component { fetchUnreads() { console.log('Fetching unreads...'); let repliesForm: GetRepliesForm = { - sort: SortType[SortType.New], + sort: SortType.New, unread_only: true, page: 1, limit: fetchLimit, }; let userMentionsForm: GetUserMentionsForm = { - sort: SortType[SortType.New], + sort: SortType.New, unread_only: true, page: 1, limit: fetchLimit, diff --git a/ui/src/components/password_change.tsx b/ui/src/components/password_change.tsx index 5b157f7f6..527f21e04 100644 --- a/ui/src/components/password_change.tsx +++ b/ui/src/components/password_change.tsx @@ -9,7 +9,7 @@ import { WebSocketJsonResponse, GetSiteResponse, Site, -} from '../interfaces'; +} from 'lemmy-js-client'; import { WebSocketService, UserService } from '../services'; import { wsJsonToRes, capitalizeFirstLetter, toast } from '../utils'; import { i18n } from '../i18next'; diff --git a/ui/src/components/post-form.tsx b/ui/src/components/post-form.tsx index 854cff6e9..97b44f5fa 100644 --- a/ui/src/components/post-form.tsx +++ b/ui/src/components/post-form.tsx @@ -18,7 +18,7 @@ import { SearchType, SearchResponse, WebSocketJsonResponse, -} from '../interfaces'; +} from 'lemmy-js-client'; import { WebSocketService, UserService } from '../services'; import { wsJsonToRes, @@ -121,7 +121,7 @@ export class PostForm extends Component { ); let listCommunitiesForm: ListCommunitiesForm = { - sort: SortType[SortType.TopAll], + sort: SortType.TopAll, limit: 9999, }; @@ -405,8 +405,8 @@ export class PostForm extends Component { if (validURL(this.state.postForm.url)) { let form: SearchForm = { q: this.state.postForm.url, - type_: SearchType[SearchType.Url], - sort: SortType[SortType.TopAll], + type_: SearchType.Url, + sort: SortType.TopAll, page: 1, limit: 6, }; @@ -433,8 +433,8 @@ export class PostForm extends Component { fetchSimilarPosts() { let form: SearchForm = { q: this.state.postForm.name, - type_: SearchType[SearchType.Posts], - sort: SortType[SortType.TopAll], + type_: SearchType.Posts, + sort: SortType.TopAll, community_id: this.state.postForm.community_id, page: 1, limit: 6, diff --git a/ui/src/components/post-listing.tsx b/ui/src/components/post-listing.tsx index e3e19e99c..fa4bf3911 100644 --- a/ui/src/components/post-listing.tsx +++ b/ui/src/components/post-listing.tsx @@ -11,14 +11,14 @@ import { SavePostForm, CommunityUser, UserView, - BanType, BanFromCommunityForm, BanUserForm, AddModToCommunityForm, AddAdminForm, TransferSiteForm, TransferCommunityForm, -} from '../interfaces'; +} from 'lemmy-js-client'; +import { BanType } from '../interfaces'; import { MomentTime } from './moment-time'; import { PostForm } from './post-form'; import { IFramelyCard } from './iframely-card'; diff --git a/ui/src/components/post-listings.tsx b/ui/src/components/post-listings.tsx index cd65d9340..2c9b4a882 100644 --- a/ui/src/components/post-listings.tsx +++ b/ui/src/components/post-listings.tsx @@ -1,6 +1,6 @@ import { Component } from 'inferno'; import { Link } from 'inferno-router'; -import { Post, SortType } from '../interfaces'; +import { Post, SortType } from 'lemmy-js-client'; import { postSort } from '../utils'; import { PostListing } from './post-listing'; import { i18n } from '../i18next'; diff --git a/ui/src/components/post.tsx b/ui/src/components/post.tsx index 06f461f3a..e9427a5eb 100644 --- a/ui/src/components/post.tsx +++ b/ui/src/components/post.tsx @@ -11,8 +11,6 @@ import { Comment, MarkCommentAsReadForm, CommentResponse, - CommentSortType, - CommentViewType, CommunityUser, CommunityResponse, CommentNode as CommentNodeI, @@ -28,7 +26,8 @@ import { GetSiteResponse, GetCommunityResponse, WebSocketJsonResponse, -} from '../interfaces'; +} from 'lemmy-js-client'; +import { CommentSortType, CommentViewType } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { wsJsonToRes, @@ -439,8 +438,8 @@ export class Post extends Component { if (this.state.post.url) { let form: SearchForm = { q: this.state.post.url, - type_: SearchType[SearchType.Url], - sort: SortType[SortType.TopAll], + type_: SearchType.Url, + sort: SortType.TopAll, page: 1, limit: 6, }; diff --git a/ui/src/components/private-message-form.tsx b/ui/src/components/private-message-form.tsx index ff889c240..6d7825cd0 100644 --- a/ui/src/components/private-message-form.tsx +++ b/ui/src/components/private-message-form.tsx @@ -14,7 +14,7 @@ import { GetUserDetailsForm, SortType, WebSocketJsonResponse, -} from '../interfaces'; +} from 'lemmy-js-client'; import { WebSocketService } from '../services'; import { capitalizeFirstLetter, @@ -77,7 +77,7 @@ export class PrivateMessageForm extends Component< this.state.privateMessageForm.recipient_id = this.props.params.recipient_id; let form: GetUserDetailsForm = { user_id: this.state.privateMessageForm.recipient_id, - sort: SortType[SortType.New], + sort: SortType.New, saved_only: false, }; WebSocketService.Instance.getUserDetails(form); diff --git a/ui/src/components/private-message.tsx b/ui/src/components/private-message.tsx index bb6aca4c1..243d12e57 100644 --- a/ui/src/components/private-message.tsx +++ b/ui/src/components/private-message.tsx @@ -4,7 +4,7 @@ import { PrivateMessage as PrivateMessageI, DeletePrivateMessageForm, MarkPrivateMessageAsReadForm, -} from '../interfaces'; +} from 'lemmy-js-client'; import { WebSocketService, UserService } from '../services'; import { mdToHtml, pictrsAvatarThumbnail, showAvatars, toast } from '../utils'; import { MomentTime } from './moment-time'; diff --git a/ui/src/components/search.tsx b/ui/src/components/search.tsx index fc19cab97..a18cc2d8e 100644 --- a/ui/src/components/search.tsx +++ b/ui/src/components/search.tsx @@ -17,7 +17,7 @@ import { WebSocketJsonResponse, GetSiteResponse, Site, -} from '../interfaces'; +} from 'lemmy-js-client'; import { WebSocketService } from '../services'; import { wsJsonToRes, @@ -57,8 +57,8 @@ interface SearchProps { interface UrlParams { q?: string; - type_?: string; - sort?: string; + type_?: SearchType; + sort?: SortType; page?: number; } @@ -461,8 +461,8 @@ export class Search extends Component { search() { let form: SearchForm = { q: this.state.q, - type_: SearchType[this.state.type_], - sort: SortType[this.state.sort], + type_: this.state.type_, + sort: this.state.sort, page: this.state.page, limit: fetchLimit, }; @@ -473,12 +473,12 @@ export class Search extends Component { } handleSortChange(val: SortType) { - this.updateUrl({ sort: SortType[val].toLowerCase(), page: 1 }); + this.updateUrl({ sort: val, page: 1 }); } handleTypeChange(i: Search, event: any) { i.updateUrl({ - type_: SearchType[Number(event.target.value)].toLowerCase(), + type_: SearchType[event.target.value], page: 1, }); } @@ -487,8 +487,8 @@ export class Search extends Component { event.preventDefault(); i.updateUrl({ q: i.state.searchText, - type_: SearchType[i.state.type_].toLowerCase(), - sort: SortType[i.state.sort].toLowerCase(), + type_: i.state.type_, + sort: i.state.sort, page: i.state.page, }); } @@ -499,10 +499,8 @@ export class Search extends Component { updateUrl(paramUpdates: UrlParams) { const qStr = paramUpdates.q || this.state.q; - const typeStr = - paramUpdates.type_ || SearchType[this.state.type_].toLowerCase(); - const sortStr = - paramUpdates.sort || SortType[this.state.sort].toLowerCase(); + const typeStr = paramUpdates.type_ || this.state.type_; + const sortStr = paramUpdates.sort || this.state.sort; const page = paramUpdates.page || this.state.page; this.props.history.push( `/search/q/${qStr}/type/${typeStr}/sort/${sortStr}/page/${page}` diff --git a/ui/src/components/setup.tsx b/ui/src/components/setup.tsx index 7da143791..6360ec5a3 100644 --- a/ui/src/components/setup.tsx +++ b/ui/src/components/setup.tsx @@ -7,7 +7,7 @@ import { LoginResponse, UserOperation, WebSocketJsonResponse, -} from '../interfaces'; +} from 'lemmy-js-client'; import { WebSocketService, UserService } from '../services'; import { wsJsonToRes, toast } from '../utils'; import { SiteForm } from './site-form'; diff --git a/ui/src/components/sidebar.tsx b/ui/src/components/sidebar.tsx index b434bb874..25cbd7972 100644 --- a/ui/src/components/sidebar.tsx +++ b/ui/src/components/sidebar.tsx @@ -8,7 +8,7 @@ import { RemoveCommunityForm, UserView, AddModToCommunityForm, -} from '../interfaces'; +} from 'lemmy-js-client'; import { WebSocketService, UserService } from '../services'; import { mdToHtml, getUnixTime } from '../utils'; import { CommunityForm } from './community-form'; diff --git a/ui/src/components/site-form.tsx b/ui/src/components/site-form.tsx index 98f1259b1..9b572f57e 100644 --- a/ui/src/components/site-form.tsx +++ b/ui/src/components/site-form.tsx @@ -2,7 +2,7 @@ import { Component, linkEvent } from 'inferno'; import { Prompt } from 'inferno-router'; import { MarkdownTextArea } from './markdown-textarea'; import { ImageUploadForm } from './image-upload-form'; -import { Site, SiteForm as SiteFormI } from '../interfaces'; +import { Site, SiteForm as SiteFormI } from 'lemmy-js-client'; import { WebSocketService } from '../services'; import { capitalizeFirstLetter, randomStr } from '../utils'; import { i18n } from '../i18next'; diff --git a/ui/src/components/sort-select.tsx b/ui/src/components/sort-select.tsx index 778ed65ce..1f0fb0557 100644 --- a/ui/src/components/sort-select.tsx +++ b/ui/src/components/sort-select.tsx @@ -1,6 +1,6 @@ import { Component, linkEvent } from 'inferno'; -import { SortType } from '../interfaces'; -import { sortingHelpUrl } from '../utils'; +import { SortType } from 'lemmy-js-client'; +import { sortingHelpUrl, randomStr } from '../utils'; import { i18n } from '../i18next'; interface SortSelectProps { @@ -14,6 +14,7 @@ interface SortSelectState { } export class SortSelect extends Component { + private id = `sort-select-${randomStr()}`; private emptyState: SortSelectState = { sort: this.props.sort, }; @@ -33,6 +34,8 @@ export class SortSelect extends Component { return ( <>