From 92b0536520bc1b4d87a1b7f718725a4654d85f30 Mon Sep 17 00:00:00 2001 From: yes Date: Tue, 30 Jun 2020 13:20:59 +0000 Subject: [PATCH 01/26] Translated using Weblate (German) Currently translated at 100.0% (249 of 249 strings) Translation: Lemmy/lemmy Translate-URL: http://weblate.yerbamate.dev/projects/lemmy/lemmy/de/ --- ui/translations/de.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ui/translations/de.json b/ui/translations/de.json index ef42d4189..3199bc7ad 100644 --- a/ui/translations/de.json +++ b/ui/translations/de.json @@ -50,14 +50,14 @@ "remove_as_admin": "Als Administrator entfernen", "appoint_as_admin": "Zum Administrator ernennen", "remove": "entfernen", - "removed": "entfernt", + "removed": "entfernt durch die Moderation", "locked": "gesperrt", "stickied": "angeheftet", "reason": "Grund", "mark_as_read": "als gelesen markieren", "mark_as_unread": "als ungelesen markieren", "delete": "löschen", - "deleted": "gelöscht", + "deleted": "vom Ersteller gelöscht", "delete_account": "Konto löschen", "delete_account_confirm": "Achtung: Dadurch werden alle Ihre Daten dauerhaft gelöscht. Geben Sie zur Bestätigung Ihr Passwort ein.", "restore": "wiederherstellen", @@ -150,7 +150,7 @@ "theme": "Aussehen", "sponsors": "Sponsoren", "sponsors_of_lemmy": "Sponsoren von Lemmy", - "sponsor_message": "Lemmy ist freie <1>Open-Source Software, also ohne Werbung, Monetarisierung oder Venturekapital, Punkt. Deine Spenden gehen direkt an die Vollzeit Entwicklung des Projekts. Vielen Dank an die folgenden Personen:", + "sponsor_message": "Lemmy ist freie <1>Open-Source Software, ohne Werbung, Monetarisierung oder Venturekapital, Punkt. Deine Spenden gehen direkt an die Vollzeit Entwicklung des Projekts. Vielen Dank an die folgenden Personen:", "support_on_patreon": "Auf Patreon unterstützen", "support_on_liberapay": "Auf Liberapay unterstützen", "general_sponsors": "Allgemeine Sponsoren sind die, die zwischen $10 und $39 zu Lemmy beitragen.", @@ -251,5 +251,10 @@ "number_of_upvotes": "{{count}} Stimme", "number_of_upvotes_plural": "{{count}} Stimmen", "number_of_downvotes": "{{count}} Gegenstimme", - "number_of_downvotes_plural": "{{count}} Gegenstimmen" + "number_of_downvotes_plural": "{{count}} Gegenstimmen", + "invalid_community_name": "Ungültiger Name.", + "click_to_delete_picture": "Klicke, um das Bild zu löschen.", + "picture_deleted": "Bild gelöscht.", + "select_a_community": "Wähle eine Community aus", + "invalid_username": "Ungültiger Benutzername." } From afb7e055e1b9366c7d5cad457dc503df0426dd77 Mon Sep 17 00:00:00 2001 From: riccardo Date: Tue, 30 Jun 2020 13:20:59 +0000 Subject: [PATCH 02/26] Translated using Weblate (Italian) Currently translated at 100.0% (249 of 249 strings) Translation: Lemmy/lemmy Translate-URL: http://weblate.yerbamate.dev/projects/lemmy/lemmy/it/ --- ui/translations/it.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/translations/it.json b/ui/translations/it.json index 2e9a48d8f..cf8c0ea69 100644 --- a/ui/translations/it.json +++ b/ui/translations/it.json @@ -56,7 +56,7 @@ "mark_as_read": "segna come letto", "mark_as_unread": "segna come non letto", "delete": "cancella", - "deleted": "eliminato dall'autore del commento", + "deleted": "eliminato dal creatore", "delete_account": "Cancella Account", "delete_account_confirm": "Attenzione: stai per cancellare permanentemente tutti i tuoi dati. Inserisci la tua password per confermare questa azione.", "restore": "ripristina", @@ -151,7 +151,7 @@ "ethereum": "Ethereum", "monero": "Monero", "code": "Codice", - "joined": "Iscritto da", + "joined": "Iscritto", "by": "di", "to": "su", "transfer_community": "trasferisci comunità", @@ -175,7 +175,7 @@ "couldnt_update_community": "Impossibile aggiornare la comunità.", "community_already_exists": "La comunità esiste già.", "community_moderator_already_exists": "Questo utente è già moderatore della comunità.", - "community_follower_already_exists": "Questo utente è già moderatore della comunità.", + "community_follower_already_exists": "Questo utente è già membro della comunità.", "community_user_already_banned": "L'utente della comunità è già stato espulso.", "couldnt_create_post": "Impossibile creare la pubblicazione.", "couldnt_like_post": "Impossibile apprezzare la pubblicazione.", From 09a05f72e38d995e193e94bf740e8d8794c139b2 Mon Sep 17 00:00:00 2001 From: Dmytro Kyrychuk Date: Tue, 30 Jun 2020 13:20:59 +0000 Subject: [PATCH 03/26] Translated using Weblate (Ukrainian) Currently translated at 29.7% (74 of 249 strings) Translation: Lemmy/lemmy Translate-URL: http://weblate.yerbamate.dev/projects/lemmy/lemmy/uk/ --- ui/translations/uk.json | 83 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/ui/translations/uk.json b/ui/translations/uk.json index 0967ef424..1192fa9f9 100644 --- a/ui/translations/uk.json +++ b/ui/translations/uk.json @@ -1 +1,82 @@ -{} +{ + "invalid_community_name": "Невідповідне ім'я.", + "preview": "Перегляд", + "upload_image": "завантажити зображення", + "avatar": "Аватар", + "upload_avatar": "Завантажити аватар", + "show_avatars": "Показати аватари", + "show_context": "Показати контекст", + "formatting_help": "про форматування", + "sorting_help": "про сортування", + "view_source": "дивитися джерело", + "unlock": "розблокувати", + "lock": "заблокувати", + "unsticky": "відкріпити", + "sticky": "прикріпити", + "link": "посилання", + "archive_link": "архівувати посилання", + "settings": "Налаштування", + "site_config": "Налаштування сайту", + "mod": "модератор", + "mods": "модератори", + "moderates": "Модерує", + "admin_settings": "Адміністративні налаштування", + "remove_as_mod": "звільнити модератора", + "appoint_as_mod": "призначити модератора", + "modlog": "Журнал модерування", + "remove_as_admin": "звільнити адміністратора", + "remove": "видалити", + "removed": "видалено модератором", + "locked": "заблоковано", + "stickied": "прикріплено", + "reason": "Причина", + "mark_as_read": "позначити прочитаним", + "delete": "видалити", + "deleted": "видалено автором", + "delete_account": "Видалити акаунт", + "click_to_delete_picture": "Натисніть, щоб видалити зображення.", + "picture_deleted": "Зображення видалено.", + "restore": "відновити", + "related_posts": "Ці записи можуть бути пов'язаними", + "cross_posts": "Це посилання також було опубліковано тут:", + "cross_post": "перепублікувати", + "cross_posted_to": "Переопубліковано до: ", + "comments": "Коментарі", + "number_of_comments_0": "{{count}} коментар", + "number_of_comments_1": "{{count}} коментарі", + "number_of_comments_2": "{{count}} коментарів", + "remove_comment": "Видалити коментар", + "communities": "Спільноти", + "users": "Користувачі", + "create_a_community": "Створити спільноту", + "select_a_community": "Обрати спільноту", + "create_community": "Створити спільноту", + "list_of_communities": "Список спільнот", + "send_message": "Відправити повідомлення", + "remove_community": "Видалити спільноту", + "number_of_communities_0": "{{count}} спільнота", + "number_of_communities_1": "{{count}} спільноти", + "number_of_communities_2": "{{count}} спільнот", + "community_reqs": "малі літери, символ підкреслення, без пробілів.", + "create_private_message": "Створити особисте повідомлення", + "send_secure_message": "Відправити безпечне повідомлення", + "message": "Повідомлення", + "edit": "редагувати", + "reply": "відповісти", + "more": "ще", + "post": "пост", + "remove_post": "Видалити пост", + "no_posts": "Немає постів.", + "create_a_post": "Створити пост", + "create_post": "Створити пост", + "number_of_posts_0": "{{count}} пост", + "number_of_posts_1": "{{count}} пости", + "number_of_posts_2": "{{count}} постів", + "posts": "Пости", + "subscribed_to_communities": "Відлідковувані <1>спільноти", + "trending_communities": "Відомі <1>спільноти", + "cancel": "Скасувати", + "appoint_as_admin": "призначити адміністратора", + "mark_as_unread": "позначити непрочитаним", + "delete_account_confirm": "Увага: ця дія незворотно знищить усю інформацію цього акаунта. Ведіть пароль для підтвердження." +} From 0d53c8dd0e37e53bc7432afca152c7d1c855476a Mon Sep 17 00:00:00 2001 From: Panos Alevropoulos Date: Tue, 30 Jun 2020 15:27:06 +0000 Subject: [PATCH 04/26] Translated using Weblate (Greek) Currently translated at 100.0% (249 of 249 strings) Translation: Lemmy/lemmy Translate-URL: http://weblate.yerbamate.dev/projects/lemmy/lemmy/el/ --- ui/translations/el.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/translations/el.json b/ui/translations/el.json index 7d7d14978..4dab7c884 100644 --- a/ui/translations/el.json +++ b/ui/translations/el.json @@ -102,7 +102,7 @@ "category": "Κατηγορία", "subscribers": "Εγγεγραμμένοι", "both": "Και οι δύο", - "saved": "Αποθηκεύτηκε", + "saved": "Αποθηκευμένα", "prev": "Προηγούμενο", "next": "Επόμενο", "sidebar": "Πλευρικό μενού", @@ -116,7 +116,7 @@ "mark_all_as_read": "επισήμανση όλων ως διαβασμένα", "type": "Είδος", "unread": "Μη διαβασμένα", - "url": "Ενιαίος Εντοπιστής Πόρων (URL)", + "url": "URL", "subscribed": "Εγγεγραμμένος", "week": "Εβδομάδα", "month": "Μήνας", @@ -151,8 +151,8 @@ "reset_password_mail_sent": "Μόλις στάλθηκε ένα μήνυμα ηλεκτρονικού ταχυδρομείου για την επαναφορά του κωδικού σας.", "password_change": "Αλλαγή κωδικού", "new_password": "Νέος κωδικός", - "no_email_setup": "Αυτός ο διακομιστής δεν έχει εγκαταστήσει σωστά το ηλεκτρονικό ταχυδρομείο.", - "email": "Ηλεκτρονικό ταχυδρομείο", + "no_email_setup": "Αυτός ο διακομιστής δεν έχει εγκαταστήσει σωστά το email.", + "email": "Email", "matrix_user_id": "Χρήστης Matrix", "private_message_disclaimer": "Προσοχή: τα προσωπικά μηνύματα στο Lemmy δεν είναι ασφαλή. Παρακαλούμε δημιουργήστε έναν λογαριασμό στο <1>Riot.im για ασφαλή επικοινωνία.", "send_notifications_to_email": "Αποστολή ειδοποιήσεων στη διεύθυνση ηλεκτρονικού ταχυδρομείου", @@ -200,7 +200,7 @@ "monero": "Monero", "code": "Κώδικας", "by": "από", - "to": "μέχρι", + "to": "προς", "from": "από", "transfer_community": "μεταφορά κοινότητας", "transfer_site": "μεταφορά ιστότοπου", From c239a5f0e5cc6c5f9fe243adbb79af49d4a0d6d4 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 1 Jul 2020 08:22:41 -0400 Subject: [PATCH 05/26] Fixing ban user bug. Fixes #876 --- server/src/api/user.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/api/user.rs b/server/src/api/user.rs index 0b6458e75..8f09ea5fc 100644 --- a/server/src/api/user.rs +++ b/server/src/api/user.rs @@ -710,7 +710,7 @@ impl Perform for Oper { return Err(APIError::err("not_an_admin").into()); } - match User_::ban_user(&conn, user_id, data.ban) { + match User_::ban_user(&conn, data.user_id, data.ban) { Ok(user) => user, Err(_e) => return Err(APIError::err("couldnt_update_user").into()), }; From 5c03e9e9ee53639c93e63253d99998a85b282ab8 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 1 Jul 2020 08:25:55 -0400 Subject: [PATCH 06/26] Version v0.7.6 --- ansible/VERSION | 2 +- docker/prod/docker-compose.yml | 2 +- server/src/version.rs | 2 +- ui/src/version.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ansible/VERSION b/ansible/VERSION index aed5a7df6..378c127dd 100644 --- a/ansible/VERSION +++ b/ansible/VERSION @@ -1 +1 @@ -v0.7.5 +v0.7.6 diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index 5cb3f9adb..cd364a3cc 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -12,7 +12,7 @@ services: restart: always lemmy: - image: dessalines/lemmy:v0.7.5 + image: dessalines/lemmy:v0.7.6 ports: - "127.0.0.1:8536:8536" restart: always diff --git a/server/src/version.rs b/server/src/version.rs index 0970c1759..b72c81989 100644 --- a/server/src/version.rs +++ b/server/src/version.rs @@ -1 +1 @@ -pub const VERSION: &str = "v0.7.5"; +pub const VERSION: &str = "v0.7.6"; diff --git a/ui/src/version.ts b/ui/src/version.ts index 01d3be811..8ad27e7cc 100644 --- a/ui/src/version.ts +++ b/ui/src/version.ts @@ -1 +1 @@ -export const version: string = 'v0.7.5'; +export const version: string = 'v0.7.6'; From 95f59a027248440b16107192cb507b7c182514de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20T=C3=B6tterman?= Date: Wed, 1 Jul 2020 15:44:56 +0300 Subject: [PATCH 07/26] Put Lemmy in /srv/lemmy instead of /lemmy as recommended by FHS (#857) * Put Lemmy in /srv/lemmy instead of /lemmy as recommended by FHS * Extract lemmy_base_dir * Complain if lemmy_base_dir not set --- ansible/ansible.cfg | 4 +- ansible/inventory.example | 3 +- ansible/lemmy.yml | 66 +++++++++++++++++++++++-------- ansible/lemmy_dev.yml | 81 +++++++++++++++++++++++++++------------ ansible/uninstall.yml | 26 ++++++++----- 5 files changed, 126 insertions(+), 54 deletions(-) diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg index 74b6ab2f1..696466297 100644 --- a/ansible/ansible.cfg +++ b/ansible/ansible.cfg @@ -1,6 +1,6 @@ [defaults] -inventory=inventory -interpreter_python=/usr/bin/python3 +inventory = inventory +interpreter_python = /usr/bin/python3 [ssh_connection] pipelining = True diff --git a/ansible/inventory.example b/ansible/inventory.example index 52b45d3c3..139e4ca34 100644 --- a/ansible/inventory.example +++ b/ansible/inventory.example @@ -1,6 +1,7 @@ [lemmy] # define the username and hostname that you use for ssh connection, and specify the domain -myuser@example.com domain=example.com letsencrypt_contact_email=your@email.com +# old default for lemmy_base_dir was /lemmy +myuser@example.com domain=example.com letsencrypt_contact_email=your@email.com lemmy_base_dir=/srv/lemmy [all:vars] ansible_connection=ssh diff --git a/ansible/lemmy.yml b/ansible/lemmy.yml index 7b78ab8d3..242d9043e 100644 --- a/ansible/lemmy.yml +++ b/ansible/lemmy.yml @@ -5,18 +5,29 @@ # https://www.josharcher.uk/code/ansible-python-connection-failure-ubuntu-server-1604/ gather_facts: False pre_tasks: + - name: check lemmy_base_dir + fail: + msg: "lemmy_base_dir is unset, old default value was '/lemmy'" + when: lemmy_base_dir is not defined + - name: install python for Ansible raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal python-setuptools) args: executable: /bin/bash register: output - changed_when: output.stdout != "" + changed_when: output.stdout != '' + - setup: # gather facts tasks: - name: install dependencies apt: - pkg: ['nginx', 'docker-compose', 'docker.io', 'certbot', 'python-certbot-nginx'] + pkg: + - 'nginx' + - 'docker-compose' + - 'docker.io' + - 'certbot' + - 'python-certbot-nginx' - name: request initial letsencrypt certificate command: certbot certonly --nginx --agree-tos -d '{{ domain }}' -m '{{ letsencrypt_contact_email }}' @@ -24,27 +35,48 @@ creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem' - name: create lemmy folder - file: path={{item.path}} {{item.owner}} state=directory + file: + path: '{{item.path}}' + owner: '{{item.owner}}' + state: directory with_items: - - { path: '/lemmy/', owner: 'root' } - - { path: '/lemmy/volumes/', owner: 'root' } - - { path: '/lemmy/volumes/pictrs/', owner: '991' } + - path: '{{lemmy_base_dir}}' + owner: 'root' + - path: '{{lemmy_base_dir}}/volumes/' + owner: 'root' + - path: '{{lemmy_base_dir}}/volumes/pictrs/' + owner: '991' - block: - name: add template files - template: src={{item.src}} dest={{item.dest}} mode={{item.mode}} + template: + src: '{{item.src}}' + dest: '{{item.dest}}' + mode: '{{item.mode}}' with_items: - - { src: 'templates/docker-compose.yml', dest: '/lemmy/docker-compose.yml', mode: '0600' } - - { src: 'templates/nginx.conf', dest: '/etc/nginx/sites-enabled/lemmy.conf', mode: '0644' } - - { src: '../docker/iframely.config.local.js', dest: '/lemmy/iframely.config.local.js', mode: '0600' } - vars: + - src: 'templates/docker-compose.yml' + dest: '{{lemmy_base_dir}}/docker-compose.yml' + mode: '0600' + - src: 'templates/nginx.conf' + dest: '/etc/nginx/sites-enabled/lemmy.conf' + mode: '0644' + - src: '../docker/iframely.config.local.js' + dest: '{{lemmy_base_dir}}/iframely.config.local.js' + mode: '0600' + vars: lemmy_docker_image: "dessalines/lemmy:{{ lookup('file', 'VERSION') }}" lemmy_port: "8536" pictshare_port: "8537" iframely_port: "8538" - name: add config file (only during initial setup) - template: src='templates/config.hjson' dest='/lemmy/lemmy.hjson' mode='0600' force='no' owner='1000' group='1000' + template: + src: 'templates/config.hjson' + dest: '{{lemmy_base_dir}}/lemmy.hjson' + mode: '0600' + force: false + owner: '1000' + group: '1000' vars: postgres_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/postgres chars=ascii_letters,digits') }}" jwt_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/jwt chars=ascii_letters,digits') }}" @@ -57,7 +89,7 @@ - name: start docker-compose docker_compose: - project_src: /lemmy/ + project_src: '{{lemmy_base_dir}}' state: present pull: yes remove_orphans: yes @@ -67,7 +99,7 @@ - name: certbot renewal cronjob cron: - special_time=daily - name=certbot-renew-lemmy - user=root - job="certbot certonly --nginx -d '{{ domain }}' --deploy-hook 'nginx -s reload'" + special_time: daily + name: certbot-renew-lemmy + user: root + job: "certbot certonly --nginx -d '{{ domain }}' --deploy-hook 'nginx -s reload'" diff --git a/ansible/lemmy_dev.yml b/ansible/lemmy_dev.yml index 7a3683610..1e7369da4 100644 --- a/ansible/lemmy_dev.yml +++ b/ansible/lemmy_dev.yml @@ -1,7 +1,7 @@ --- - hosts: all vars: - lemmy_docker_image: "lemmy:dev" + lemmy_docker_image: 'lemmy:dev' # Install python if required # https://www.josharcher.uk/code/ansible-python-connection-failure-ubuntu-server-1604/ @@ -12,13 +12,18 @@ args: executable: /bin/bash register: output - changed_when: output.stdout != "" + changed_when: output.stdout != '' - setup: # gather facts tasks: - name: install dependencies apt: - pkg: ['nginx', 'docker-compose', 'docker.io', 'certbot', 'python-certbot-nginx'] + pkg: + - 'nginx' + - 'docker-compose' + - 'docker.io' + - 'certbot' + - 'python-certbot-nginx' - name: request initial letsencrypt certificate command: certbot certonly --nginx --agree-tos -d '{{ domain }}' -m '{{ letsencrypt_contact_email }}' @@ -26,25 +31,46 @@ creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem' - name: create lemmy folder - file: path={{item.path}} owner={{item.owner}} state=directory + file: + path: '{{item.path}}' + owner: '{{item.owner}}' + state: directory with_items: - - { path: '/lemmy/', owner: 'root' } - - { path: '/lemmy/volumes/', owner: 'root' } - - { path: '/lemmy/volumes/pictrs/', owner: '991' } + - path: '{{lemmy_base_dir}}/lemmy/' + owner: 'root' + - path: '{{lemmy_base_dir}}/volumes/' + owner: 'root' + - path: '{{lemmy_base_dir}}/volumes/pictrs/' + owner: '991' - block: - name: add template files - template: src={{item.src}} dest={{item.dest}} mode={{item.mode}} + template: + src: '{{item.src}}' + dest: '{{item.dest}}' + mode: '{{item.mode}}' with_items: - - { src: 'templates/docker-compose.yml', dest: '/lemmy/docker-compose.yml', mode: '0600' } - - { src: 'templates/nginx.conf', dest: '/etc/nginx/sites-enabled/lemmy.conf', mode: '0644' } - - { src: '../docker/iframely.config.local.js', dest: '/lemmy/iframely.config.local.js', mode: '0600' } + - src: 'templates/docker-compose.yml' + dest: '{{lemmy_base_dir}}/docker-compose.yml' + mode: '0600' + - src: 'templates/nginx.conf' + dest: '/etc/nginx/sites-enabled/lemmy.conf' + mode: '0644' + - src: '../docker/iframely.config.local.js' + dest: '{{lemmy_base_dir}}/iframely.config.local.js' + mode: '0600' - name: add config file (only during initial setup) - template: src='templates/config.hjson' dest='/lemmy/lemmy.hjson' mode='0600' force='no' owner='1000' group='1000' - vars: - postgres_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/postgres chars=ascii_letters,digits') }}" - jwt_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/jwt chars=ascii_letters,digits') }}" + template: + src: 'templates/config.hjson' + dest: '{{lemmy_base_dir}}/lemmy.hjson' + mode: '0600' + force: false + owner: '1000' + group: '1000' + vars: + postgres_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/postgres chars=ascii_letters,digits') }}" + jwt_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/jwt chars=ascii_letters,digits') }}" - name: build the dev docker image local_action: shell cd .. && sudo docker build . -f docker/dev/Dockerfile -t lemmy:dev @@ -59,22 +85,29 @@ local_action: shell sudo docker save lemmy:dev > lemmy-dev.tar - name: copy dev docker image to server - copy: src=lemmy-dev.tar dest=/lemmy/lemmy-dev.tar + copy: + src: lemmy-dev.tar + dest: '{{lemmy_base_dir}}/lemmy-dev.tar' - name: import docker image docker_image: name: lemmy tag: dev - load_path: /lemmy/lemmy-dev.tar + load_path: '{{lemmy_base_dir}}/lemmy-dev.tar' source: load force_source: yes register: image_import - name: delete remote image file - file: path=/lemmy/lemmy-dev.tar state=absent + file: + path: '{{lemmy_base_dir}}/lemmy-dev.tar' + state: absent - name: delete local image file - local_action: file path=lemmy-dev.tar state=absent + local_action: + module: file + path: lemmy-dev.tar + state: absent - name: enable and start docker service systemd: @@ -86,7 +119,7 @@ # be a problem for testing - name: start docker-compose docker_compose: - project_src: /lemmy/ + project_src: '{{lemmy_base_dir}}' state: present recreate: always remove_orphans: yes @@ -97,7 +130,7 @@ - name: certbot renewal cronjob cron: - special_time=daily - name=certbot-renew-lemmy - user=root - job="certbot certonly --nginx -d '{{ domain }}' --deploy-hook 'nginx -s reload'" + special_time: daily + name: certbot-renew-lemmy + user: root + job: "certbot certonly --nginx -d '{{ domain }}' --deploy-hook 'nginx -s reload'" diff --git a/ansible/uninstall.yml b/ansible/uninstall.yml index 252c5bd1f..34c24d2ed 100644 --- a/ansible/uninstall.yml +++ b/ansible/uninstall.yml @@ -22,27 +22,33 @@ - name: stop docker-compose docker_compose: - project_src: /lemmy/ + project_src: '{{lemmy_base_dir}}' state: absent - name: delete data - file: path={{item.path}} state=absent + file: + path: '{{item.path}}' + state: absent with_items: - - { path: '/lemmy/' } - - { path: '/etc/nginx/sites-enabled/lemmy.conf' } + - path: '{{lemmy_base_dir}}' + - path: '/etc/nginx/sites-enabled/lemmy.conf' - name: Remove a volume - docker_volume: name={{item.name}} state=absent + docker_volume: + name: '{{item.name}}' + state: absent with_items: - - { name: 'lemmy_lemmy_db' } - - { name: 'lemmy_lemmy_pictshare' } + - name: 'lemmy_lemmy_db' + - name: 'lemmy_lemmy_pictshare' - name: delete entire ecloud folder - file: path='/mnt/repo-base/' state=absent + file: + path: '/mnt/repo-base/' + state: absent when: delete_certs|bool - name: remove certbot cronjob cron: - name=certbot-renew-lemmy - state=absent + name: certbot-renew-lemmy + state: absent From fb2b8994c7b17b8cae0b9d562fce53cb7dbe660c Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Wed, 1 Jul 2020 14:51:52 +0200 Subject: [PATCH 08/26] Improve comments in ansible inventory --- ansible/inventory.example | 9 +++++++-- ansible/lemmy.yml | 2 +- ansible/lemmy_dev.yml | 5 +++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ansible/inventory.example b/ansible/inventory.example index 139e4ca34..c5f98653d 100644 --- a/ansible/inventory.example +++ b/ansible/inventory.example @@ -1,6 +1,11 @@ [lemmy] -# define the username and hostname that you use for ssh connection, and specify the domain -# old default for lemmy_base_dir was /lemmy +# to get started, copy this file to `inventory` and adjust the values below. +# - `myuser@example.com`: replace with the destination you use to connect to your server via ssh +# - `domain=example.com`: replace `example.com` with your lemmy domain +# - `letsencrypt_contact_email=your@email.com` replace `your@email.com` with your email address, +# to get notifications if your ssl cert expires +# - `lemmy_base_dir=/srv/lemmy`: the location on the server where lemmy can be installed, can be any folder +# if you are upgrading from a previous version, set this to `/lemmy` myuser@example.com domain=example.com letsencrypt_contact_email=your@email.com lemmy_base_dir=/srv/lemmy [all:vars] diff --git a/ansible/lemmy.yml b/ansible/lemmy.yml index 242d9043e..5c8a5f911 100644 --- a/ansible/lemmy.yml +++ b/ansible/lemmy.yml @@ -7,7 +7,7 @@ pre_tasks: - name: check lemmy_base_dir fail: - msg: "lemmy_base_dir is unset, old default value was '/lemmy'" + msg: "`lemmy_base_dir` is unset. if you are upgrading from an older version, add `lemmy_base_dir=/lemmy` to your inventory file." when: lemmy_base_dir is not defined - name: install python for Ansible diff --git a/ansible/lemmy_dev.yml b/ansible/lemmy_dev.yml index 1e7369da4..e85566653 100644 --- a/ansible/lemmy_dev.yml +++ b/ansible/lemmy_dev.yml @@ -7,6 +7,11 @@ # https://www.josharcher.uk/code/ansible-python-connection-failure-ubuntu-server-1604/ gather_facts: False pre_tasks: + - name: check lemmy_base_dir + fail: + msg: "`lemmy_base_dir` is unset. if you are upgrading from an older version, add `lemmy_base_dir=/lemmy` to your inventory file." + when: lemmy_base_dir is not defined + - name: install python for Ansible raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal python-setuptools) args: From a074564458b8a108b77d98e5e8ce24168656763a Mon Sep 17 00:00:00 2001 From: Riley Date: Wed, 1 Jul 2020 07:54:29 -0500 Subject: [PATCH 09/26] Federation async (#848) * Asyncify more * I guess these changed * Clean PR a bit * Convert more away from failure error * config changes for testing federation * It was DNS So actix-web's client relies on TRust DNS Resolver to figure out where to send data, but TRust DNS Resolver seems to not play nice with docker, which expressed itself as not resolving the name to an IP address _the first time_ when making a request. The fix was literally to make the request again (which I limited to 3 times total, and not exceeding the request timeout in total) * Only retry for connecterror Since TRust DNS Resolver was causing ConnectError::Timeout, this change limits the retry to only this error, returning immediately for any other error * Use http sig norm 0.4.0-alpha for actix-web 3.0 support * Blocking function, retry http requests * cargo +nightly fmt * Only create one pictrs dir * Don't yarn build * cargo +nightly fmt --- docker/dev/docker-compose.yml | 2 + docker/federation-test/run-tests.sh | 14 +- docker/federation-test/servers.sh | 19 + docker/federation-test/tests.sh | 10 + docker/federation/Dockerfile | 2 +- docker/federation/docker-compose.yml | 56 +- docker/federation/nginx.conf | 12 +- server/Cargo.lock | 569 +++++++----- server/Cargo.toml | 15 +- server/src/api/comment.rs | 312 ++++--- server/src/api/community.rs | 387 ++++---- server/src/api/mod.rs | 20 +- server/src/api/post.rs | 280 +++--- server/src/api/site.rs | 416 +++++---- server/src/api/user.rs | 617 +++++++----- server/src/apub/activities.rs | 59 +- server/src/apub/comment.rs | 285 ++++-- server/src/apub/community.rs | 202 ++-- server/src/apub/community_inbox.rs | 81 +- .../src/apub/extensions/group_extensions.rs | 8 +- server/src/apub/extensions/signatures.rs | 87 +- server/src/apub/fetcher.rs | 302 ++++-- server/src/apub/mod.rs | 181 +++- server/src/apub/post.rs | 233 +++-- server/src/apub/private_message.rs | 117 ++- server/src/apub/shared_inbox.rs | 877 +++++++++++------- server/src/apub/user.rs | 87 +- server/src/apub/user_inbox.rs | 208 +++-- server/src/db/activity.rs | 24 +- server/src/db/code_migrations.rs | 15 +- server/src/db/comment.rs | 2 +- server/src/db/community.rs | 2 +- server/src/db/password_reset_request.rs | 8 +- server/src/db/user.rs | 2 +- server/src/lib.rs | 118 ++- server/src/main.rs | 25 +- server/src/rate_limit/mod.rs | 19 +- server/src/rate_limit/rate_limiter.rs | 5 +- server/src/request.rs | 51 + server/src/routes/api.rs | 14 +- server/src/routes/feeds.rs | 78 +- server/src/routes/nodeinfo.rs | 62 +- server/src/routes/webfinger.rs | 108 ++- server/src/settings.rs | 6 +- server/src/websocket/mod.rs | 1 - server/src/websocket/server.rs | 565 ++--------- ui/src/api_tests/api.spec.ts | 18 +- 47 files changed, 3846 insertions(+), 2735 deletions(-) create mode 100755 docker/federation-test/servers.sh create mode 100755 docker/federation-test/tests.sh create mode 100644 server/src/request.rs diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index bdcb4308a..51a3ecdab 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -20,6 +20,8 @@ services: postgres: image: postgres:12-alpine + ports: + - "127.0.0.1:5432:5432" environment: - POSTGRES_USER=lemmy - POSTGRES_PASSWORD=password diff --git a/docker/federation-test/run-tests.sh b/docker/federation-test/run-tests.sh index fdb0e129b..57c6cc8ff 100755 --- a/docker/federation-test/run-tests.sh +++ b/docker/federation-test/run-tests.sh @@ -5,17 +5,21 @@ pushd ../../server/ cargo build popd +pushd ../../ui +yarn +popd + +mkdir -p volumes/pictrs_{alpha,beta,gamma} +sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma} + sudo docker build ../../ --file ../federation/Dockerfile --tag lemmy-federation:latest -for Item in alpha beta gamma ; do - sudo mkdir -p volumes/pictrs_$Item - sudo chown -R 991:991 volumes/pictrs_$Item -done +sudo mkdir -p volumes/pictrs_alpha +sudo chown -R 991:991 volumes/pictrs_alpha sudo docker-compose --file ../federation/docker-compose.yml --project-directory . up -d pushd ../../ui -yarn 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 diff --git a/docker/federation-test/servers.sh b/docker/federation-test/servers.sh new file mode 100755 index 000000000..36f10cd82 --- /dev/null +++ b/docker/federation-test/servers.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +sudo rm -rf volumes + +pushd ../../server/ +cargo build +popd + +pushd ../../ui +yarn +popd + +mkdir -p volumes/pictrs_{alpha,beta,gamma} +sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma} + +sudo docker build ../../ --file ../federation/Dockerfile --tag lemmy-federation:latest + +sudo docker-compose --file ../federation/docker-compose.yml --project-directory . up diff --git a/docker/federation-test/tests.sh b/docker/federation-test/tests.sh new file mode 100755 index 000000000..2e88ffb25 --- /dev/null +++ b/docker/federation-test/tests.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -xe + +pushd ../../ui +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 +yarn api-test || true +popd diff --git a/docker/federation/Dockerfile b/docker/federation/Dockerfile index ec7bf2d22..caf081758 100644 --- a/docker/federation/Dockerfile +++ b/docker/federation/Dockerfile @@ -3,7 +3,7 @@ FROM ekidd/rust-musl-builder:1.42.0-openssl11 USER root RUN mkdir /app/dist/documentation/ -p \ && addgroup --gid 1001 lemmy \ - && adduser --disabled-password --shell /bin/sh -u 1001 --ingroup lemmy lemmy + && adduser --gecos "" --disabled-password --shell /bin/sh -u 1001 --ingroup lemmy lemmy # Copy resources COPY server/config/defaults.hjson /app/config/defaults.hjson diff --git a/docker/federation/docker-compose.yml b/docker/federation/docker-compose.yml index d5f9a5225..cbc648e65 100644 --- a/docker/federation/docker-compose.yml +++ b/docker/federation/docker-compose.yml @@ -12,28 +12,33 @@ services: - ../federation/nginx.conf:/etc/nginx/nginx.conf restart: on-failure depends_on: - - lemmy_alpha - - pictrs_alpha - - lemmy_beta - - pictrs_beta - - lemmy_gamma - - pictrs_gamma + - lemmy-alpha + - pictrs + - lemmy-beta + - lemmy-gamma - iframely - lemmy_alpha: + pictrs: + restart: always + image: asonix/pictrs:v0.1.13-r0 + user: 991:991 + volumes: + - ./volumes/pictrs_alpha:/mnt + + lemmy-alpha: image: lemmy-federation:latest environment: - - LEMMY_HOSTNAME=lemmy_alpha:8540 + - LEMMY_HOSTNAME=lemmy-alpha:8540 - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_alpha: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_gamma + - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma - LEMMY_PORT=8540 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha - LEMMY_SETUP__ADMIN_PASSWORD=lemmy - - LEMMY_SETUP__SITE_NAME=lemmy_alpha + - LEMMY_SETUP__SITE_NAME=lemmy-alpha - RUST_BACKTRACE=1 - RUST_LOG=debug depends_on: @@ -46,26 +51,21 @@ services: - POSTGRES_DB=lemmy volumes: - ./volumes/postgres_alpha:/var/lib/postgresql/data - pictrs_alpha: - image: asonix/pictrs:v0.1.13-r0 - user: 991:991 - volumes: - - ./volumes/pictrs_alpha:/mnt - lemmy_beta: + lemmy-beta: image: lemmy-federation:latest environment: - - LEMMY_HOSTNAME=lemmy_beta:8550 + - LEMMY_HOSTNAME=lemmy-beta:8550 - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_beta: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_alpha,lemmy_gamma + - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma - LEMMY_PORT=8550 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta - LEMMY_SETUP__ADMIN_PASSWORD=lemmy - - LEMMY_SETUP__SITE_NAME=lemmy_beta + - LEMMY_SETUP__SITE_NAME=lemmy-beta - RUST_BACKTRACE=1 - RUST_LOG=debug depends_on: @@ -78,26 +78,21 @@ services: - POSTGRES_DB=lemmy volumes: - ./volumes/postgres_beta:/var/lib/postgresql/data - pictrs_beta: - image: asonix/pictrs:v0.1.13-r0 - user: 991:991 - volumes: - - ./volumes/pictrs_beta:/mnt - lemmy_gamma: + lemmy-gamma: image: lemmy-federation:latest environment: - - LEMMY_HOSTNAME=lemmy_gamma:8560 + - LEMMY_HOSTNAME=lemmy-gamma:8560 - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_gamma: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_alpha,lemmy_beta + - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta - LEMMY_PORT=8560 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma - LEMMY_SETUP__ADMIN_PASSWORD=lemmy - - LEMMY_SETUP__SITE_NAME=lemmy_gamma + - LEMMY_SETUP__SITE_NAME=lemmy-gamma - RUST_BACKTRACE=1 - RUST_LOG=debug depends_on: @@ -110,11 +105,6 @@ services: - POSTGRES_DB=lemmy volumes: - ./volumes/postgres_gamma:/var/lib/postgresql/data - pictrs_gamma: - image: asonix/pictrs:v0.1.13-r0 - user: 991:991 - volumes: - - ./volumes/pictrs_gamma:/mnt iframely: image: dogbin/iframely:latest diff --git a/docker/federation/nginx.conf b/docker/federation/nginx.conf index 25160eb6c..2093297eb 100644 --- a/docker/federation/nginx.conf +++ b/docker/federation/nginx.conf @@ -12,7 +12,7 @@ http { client_max_body_size 50M; location / { - proxy_pass http://lemmy_alpha:8540; + proxy_pass http://lemmy-alpha:8540; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -26,7 +26,7 @@ http { # pict-rs images location /pictrs { location /pictrs/image { - proxy_pass http://pictrs_alpha:8080/image; + proxy_pass http://pictrs:8080/image; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -52,7 +52,7 @@ http { client_max_body_size 50M; location / { - proxy_pass http://lemmy_beta:8550; + proxy_pass http://lemmy-beta:8550; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -66,7 +66,7 @@ http { # pict-rs images location /pictrs { location /pictrs/image { - proxy_pass http://pictrs_beta:8080/image; + proxy_pass http://pictrs:8080/image; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -92,7 +92,7 @@ http { client_max_body_size 50M; location / { - proxy_pass http://lemmy_gamma:8560; + proxy_pass http://lemmy-gamma:8560; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -106,7 +106,7 @@ http { # pict-rs images location /pictrs { location /pictrs/image { - proxy_pass http://pictrs_gamma:8080/image; + proxy_pass http://pictrs:8080/image; 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/server/Cargo.lock b/server/Cargo.lock index a8f5c8805..c54419e10 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -49,25 +49,25 @@ dependencies = [ [[package]] name = "actix" -version = "0.9.0" +version = "0.10.0-alpha.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4af87564ff659dee8f9981540cac9418c45e910c8072fdedd643a262a38fcaf" +checksum = "a9028932f36d45df020c92317ccb879ab77d8f066f57ff143dd5bee93ba3de0d" dependencies = [ - "actix-http", "actix-rt", "actix_derive", "bitflags", "bytes", "crossbeam-channel", "derive_more", - "futures", - "lazy_static", + "futures-channel", + "futures-util", "log", + "once_cell", "parking_lot", "pin-project", "smallvec", "tokio", - "tokio-util 0.2.0", + "tokio-util 0.3.1", "trust-dns-proto", "trust-dns-resolver", ] @@ -89,9 +89,9 @@ dependencies = [ [[package]] name = "actix-connect" -version = "1.0.2" +version = "2.0.0-alpha.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95cc9569221e9802bf4c377f6c18b90ef10227d787611decf79fd47d2a8e76c" +checksum = "2551ed85d5e157c13f8f523cdb13a6292d948049eb2dc2072bbee3ec350399a2" dependencies = [ "actix-codec", "actix-rt", @@ -99,18 +99,21 @@ dependencies = [ "actix-utils", "derive_more", "either", - "futures", + "futures-util", "http", "log", + "rustls", + "tokio-rustls", "trust-dns-proto", "trust-dns-resolver", + "webpki", ] [[package]] name = "actix-files" -version = "0.2.2" +version = "0.3.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193b22cb1f7b4ff12a4eb2415d6d19e47e44ea93e05930b30d05375ea29d3529" +checksum = "23b32e0fdd5998c2712549cbc39dff46c8754d55e3dd9f4d017d9e28de30cac6" dependencies = [ "actix-http", "actix-service", @@ -129,26 +132,25 @@ dependencies = [ [[package]] name = "actix-http" -version = "1.0.1" +version = "2.0.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c16664cc4fdea8030837ad5a845eb231fb93fc3c5c171edfefb52fad92ce9019" +checksum = "fd7ea0568480d199952a51de70271946da57c33cc0e8b83f54383e70958dff21" dependencies = [ "actix-codec", "actix-connect", "actix-rt", "actix-service", "actix-threadpool", + "actix-tls", "actix-utils", - "base64 0.11.0", + "base64 0.12.3", "bitflags", "brotli2", "bytes", - "chrono", "copyless", "derive_more", "either", "encoding_rs", - "failure", "flate2", "futures-channel", "futures-core", @@ -169,9 +171,9 @@ dependencies = [ "serde 1.0.114", "serde_json", "serde_urlencoded", - "sha1", + "sha-1", "slab", - "time", + "time 0.2.16", ] [[package]] @@ -273,9 +275,9 @@ dependencies = [ [[package]] name = "actix-tls" -version = "1.0.0" +version = "2.0.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e5b4faaf105e9a6d389c606c298dcdb033061b00d532af9df56ff3a54995a8" +checksum = "dd2d9f3e70cbad0f06c6922950c5997ba0fd44c82e143d1c374023eb50457588" dependencies = [ "actix-codec", "actix-rt", @@ -285,6 +287,10 @@ dependencies = [ "either", "futures", "log", + "rustls", + "tokio-rustls", + "webpki", + "webpki-roots", ] [[package]] @@ -307,9 +313,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "2.0.0" +version = "3.0.0-alpha.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3158e822461040822f0dbf1735b9c2ce1f95f93b651d7a7aded00b1efbb1f635" +checksum = "8bd6df56ec5f9a1a0d8335f156f36e1e8f76dbd736fa0cc0f6bc3a69be1e6124" dependencies = [ "actix-codec", "actix-http", @@ -327,25 +333,29 @@ dependencies = [ "bytes", "derive_more", "encoding_rs", - "futures", + "futures-channel", + "futures-core", + "futures-util", "fxhash", "log", "mime", - "net2", "pin-project", "regex", + "rustls", "serde 1.0.114", "serde_json", "serde_urlencoded", - "time", + "socket2", + "time 0.2.16", + "tinyvec", "url", ] [[package]] name = "actix-web-actors" -version = "2.0.0" +version = "3.0.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1bd41bd66c4e9b5274cec87aac30168e63d64e96fd19db38edef6b46ba2982" +checksum = "2b5efeb3907582f9c724ce27be093ab8aafabd97be828bc6750c0d467f5e1aa3" dependencies = [ "actix", "actix-codec", @@ -408,7 +418,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -448,7 +458,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -465,15 +475,15 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "awc" -version = "1.0.1" +version = "2.0.0-alpha.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7601d4d1d7ef2335d6597a41b5fe069f6ab799b85f53565ab390e7b7065aac5" +checksum = "a7038a9747cd5159b9f0550895eaf865c0143baa7e4eee834e9294d0a7e0e4be" dependencies = [ "actix-codec", "actix-http", "actix-rt", "actix-service", - "base64 0.11.0", + "base64 0.12.3", "bytes", "derive_more", "futures-core", @@ -481,6 +491,7 @@ dependencies = [ "mime", "percent-encoding", "rand 0.7.3", + "rustls", "serde 1.0.114", "serde_json", "serde_urlencoded", @@ -500,6 +511,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base-x" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1" + [[package]] name = "base64" version = "0.9.3" @@ -527,17 +544,17 @@ checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" [[package]] name = "base64" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e223af0dc48c96d4f8342ec01a4974f139df863896b316681efd36742f22cc67" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" [[package]] name = "bcrypt" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b70db86f3c560199b0dada79a22b9a924622384abb2a756a9707ffcce077f2" +checksum = "6378bd17c4830c1b7ed644dde88f247b1560d46c68ff3da1b788984b09c0df31" dependencies = [ - "base64 0.12.2", + "base64 0.12.3", "blowfish", "byteorder", "getrandom", @@ -558,16 +575,25 @@ dependencies = [ "block-padding", "byte-tools", "byteorder", - "generic-array", + "generic-array 0.12.3", ] [[package]] -name = "block-cipher-trait" -version = "0.6.2" +name = "block-buffer" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array", + "generic-array 0.14.2", +] + +[[package]] +name = "block-cipher" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa136449e765dc7faa244561ccae839c394048667929af599b5d931ebe7b7f10" +dependencies = [ + "generic-array 0.14.2", ] [[package]] @@ -581,13 +607,13 @@ dependencies = [ [[package]] name = "blowfish" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeb80d00f2688459b8542068abd974cfb101e7a82182414a99b5026c0d85cc3" +checksum = "91d01392750dd899a2528948d6b856afe2df508d627fc7c339868c0bd0141b4b" dependencies = [ - "block-cipher-trait", + "block-cipher", "byteorder", - "opaque-debug", + "opaque-debug 0.2.3", ] [[package]] @@ -654,9 +680,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.54" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" +checksum = "b1be3409f94d7bdceeb5f5fac551039d9b3f00e25da7a74fc4d33400a0d96368" [[package]] name = "cfg-if" @@ -673,7 +699,7 @@ dependencies = [ "num-integer", "num-traits 0.2.12", "serde 1.0.114", - "time", + "time 0.1.43", ] [[package]] @@ -751,6 +777,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" +[[package]] +name = "cpuid-bool" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d375c433320f6c5057ae04a04376eef4d04ce2801448cf8863a78da99107be4" + [[package]] name = "crc32fast" version = "1.2.0" @@ -781,37 +813,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "curl" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0447a642435be046540f042950d874a4907f9fee28c0513a0beb3ba89f91eb7" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2", - "winapi 0.3.8", -] - -[[package]] -name = "curl-sys" -version = "0.4.32+curl-7.70.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "834425a2f22fdd621434196965bf99fbfd9eaed96348488e27b7ac40736c560b" -dependencies = [ - "cc", - "libc", - "libnghttp2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi 0.3.8", -] - [[package]] name = "darling" version = "0.10.2" @@ -874,9 +875,9 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.8" +version = "0.99.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc655351f820d774679da6cdc23355a93de496867d8203496675162e17b1d671" +checksum = "298998b1cf6b5b2c8a7b023dfd45821825ce3ba8a8af55c921a0e734e4653f76" dependencies = [ "proc-macro2", "quote", @@ -925,9 +926,24 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" dependencies = [ - "generic-array", + "generic-array 0.12.3", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.2", +] + +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + [[package]] name = "dotenv" version = "0.15.0" @@ -957,7 +973,7 @@ dependencies = [ "encoding", "lazy_static", "rand 0.4.6", - "time", + "time 0.1.43", "version_check 0.1.5", ] @@ -1271,7 +1287,7 @@ dependencies = [ "libc", "log", "rustc_version", - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -1283,6 +1299,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "generic-array" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac746a5f3bbfdadd6106868134545e684693d54d9d44f6e9588a7d54af0bf980" +dependencies = [ + "typenum", + "version_check 0.9.2", +] + [[package]] name = "getrandom" version = "0.1.14" @@ -1355,7 +1381,7 @@ checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ "libc", "match_cfg", - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -1385,6 +1411,24 @@ dependencies = [ "thiserror", ] +[[package]] +name = "http-signature-normalization-actix" +version = "0.4.0-alpha.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09afff6987c7edbed101d1cddd2185786fb0af0dd9c06b654aca73a0a763680f" +dependencies = [ + "actix-http", + "actix-web", + "base64 0.12.3", + "bytes", + "chrono", + "futures", + "http-signature-normalization", + "log", + "sha2", + "thiserror", +] + [[package]] name = "httparse" version = "1.3.4" @@ -1443,35 +1487,10 @@ checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" dependencies = [ "socket2", "widestring", - "winapi 0.3.8", + "winapi 0.3.9", "winreg", ] -[[package]] -name = "isahc" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54e7cf252df9a36605ccfabea2a754ad30c24b51b77f830486e555ac8e76bce" -dependencies = [ - "bytes", - "crossbeam-channel", - "crossbeam-utils", - "curl", - "curl-sys", - "encoding_rs", - "futures-channel", - "futures-io", - "futures-util", - "http", - "lazy_static", - "log", - "mime", - "slab", - "sluice", - "tracing", - "tracing-futures", -] - [[package]] name = "itertools" version = "0.9.0" @@ -1502,7 +1521,7 @@ version = "7.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f325ae57ddcf609f02d891486ce740f5bbd0cc3e93f9bffaacdf6594b21404" dependencies = [ - "base64 0.12.2", + "base64 0.12.3", "pem", "ring", "serde 1.0.114", @@ -1544,7 +1563,9 @@ dependencies = [ "actix-rt", "actix-web", "actix-web-actors", - "base64 0.12.2", + "async-trait", + "awc", + "base64 0.12.3", "bcrypt", "chrono", "comrak", @@ -1557,8 +1578,7 @@ dependencies = [ "futures", "htmlescape", "http", - "http-signature-normalization", - "isahc", + "http-signature-normalization-actix", "itertools", "jsonwebtoken", "lazy_static", @@ -1608,7 +1628,7 @@ dependencies = [ "email", "lettre", "mime", - "time", + "time 0.1.43", "uuid 0.7.4", ] @@ -1631,28 +1651,6 @@ version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" -[[package]] -name = "libnghttp2-sys" -version = "0.1.4+1.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03624ec6df166e79e139a2310ca213283d6b3c30810c54844f307086d4488df1" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "libz-sys" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linked-hash-map" version = "0.3.0" @@ -1851,7 +1849,7 @@ checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" dependencies = [ "cfg-if", "libc", - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -1943,10 +1941,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] -name = "openssl" -version = "0.10.29" +name = "opaque-debug" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" dependencies = [ "bitflags", "cfg-if", @@ -1996,7 +2000,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -2005,7 +2009,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59698ea79df9bf77104aefd39cc3ec990cb9693fb59c3b0a70ddf2646fdffb4b" dependencies = [ - "base64 0.12.2", + "base64 0.12.3", "once_cell", "regex", ] @@ -2179,7 +2183,7 @@ dependencies = [ "libc", "rand_core 0.3.1", "rdrand", - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -2198,7 +2202,7 @@ dependencies = [ "rand_os", "rand_pcg", "rand_xorshift", - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -2293,7 +2297,7 @@ checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" dependencies = [ "libc", "rand_core 0.4.2", - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -2307,7 +2311,7 @@ dependencies = [ "libc", "rand_core 0.4.2", "rdrand", - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -2368,7 +2372,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -2393,7 +2397,7 @@ dependencies = [ "spin", "untrusted", "web-sys", - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -2421,6 +2425,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustls" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1" +dependencies = [ + "base64 0.11.0", + "log", + "ring", + "sct", + "webpki", +] + [[package]] name = "ryu" version = "1.0.5" @@ -2440,7 +2457,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" dependencies = [ "lazy_static", - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -2464,6 +2481,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "security-framework" version = "0.4.4" @@ -2580,10 +2607,10 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.7.3", + "digest 0.8.1", "fake-simd", - "opaque-debug", + "opaque-debug 0.2.3", ] [[package]] @@ -2594,14 +2621,15 @@ checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" [[package]] name = "sha2" -version = "0.8.2" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1" dependencies = [ - "block-buffer", - "digest", - "fake-simd", - "opaque-debug", + "block-buffer 0.9.0", + "cfg-if", + "cpuid-bool", + "digest 0.9.0", + "opaque-debug 0.3.0", ] [[package]] @@ -2631,18 +2659,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -[[package]] -name = "sluice" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed13b7cb46f13a15db2c4740f087a848acc8b31af89f95844d40137451f89b1" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", -] - [[package]] name = "smallvec" version = "1.4.0" @@ -2658,7 +2674,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall", - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -2667,12 +2683,70 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "standback" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0437cfb83762844799a60e1e3b489d5ceb6a650fbacb86437badc1b6d87b246" +dependencies = [ + "version_check 0.9.2", +] + [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde 1.0.114", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde 1.0.114", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + [[package]] name = "strsim" version = "0.8.0" @@ -2737,7 +2811,7 @@ dependencies = [ "rand 0.7.3", "redox_syscall", "remove_dir_all", - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -2803,7 +2877,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" dependencies = [ "libc", - "winapi 0.3.8", + "winapi 0.3.9", +] + +[[package]] +name = "time" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a51cadc5b1eec673a685ff7c33192ff7b7603d0b75446fb354939ee615acb15" +dependencies = [ + "cfg-if", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check 0.9.2", + "winapi 0.3.9", +] + +[[package]] +name = "time-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9b6e9f095bc105e183e3cd493d72579be3181ad4004fceb01adbe9eecab2d" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", ] [[package]] @@ -2830,7 +2942,19 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "slab", - "winapi 0.3.8", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-rustls" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15cb62a0d2770787abc96e99c1cd98fcf17f94959f3af63ca85bdfb203f051b4" +dependencies = [ + "futures-core", + "rustls", + "tokio", + "webpki", ] [[package]] @@ -2855,82 +2979,41 @@ checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "log", "pin-project-lite", "tokio", ] -[[package]] -name = "tracing" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41f40ed0e162c911ac6fcb53ecdc8134c46905fdbbae8c50add462a538b495f" -dependencies = [ - "cfg-if", - "log", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99bbad0de3fd923c9c3232ead88510b783e5a4d16a6154adffa3d53308de984c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa83a9a47081cd522c09c81b31aec2c9273424976f922ad61c053b58350b715" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "tracing-futures" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "trust-dns-proto" -version = "0.18.0-alpha.2" +version = "0.19.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a7f3a2ab8a919f5eca52a468866a67ed7d3efa265d48a652a9a3452272b413f" +checksum = "cdd7061ba6f4d4d9721afedffbfd403f20f39a4301fee1b70d6fcd09cca69f28" dependencies = [ "async-trait", + "backtrace", "enum-as-inner", - "failure", "futures", "idna", "lazy_static", "log", "rand 0.7.3", "smallvec", - "socket2", + "thiserror", "tokio", "url", ] [[package]] name = "trust-dns-resolver" -version = "0.18.0-alpha.2" +version = "0.19.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f90b1502b226f8b2514c6d5b37bafa8c200d7ca4102d57dc36ee0f3b7a04a2f" +checksum = "0f23cdfdc3d8300b3c50c9e84302d3bd6d860fb9529af84ace6cf9665f181b77" dependencies = [ + "backtrace", "cfg-if", - "failure", "futures", "ipconfig", "lazy_static", @@ -2938,6 +3021,7 @@ dependencies = [ "lru-cache", "resolv-conf", "smallvec", + "thiserror", "tokio", "trust-dns-proto", ] @@ -3028,9 +3112,9 @@ checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" [[package]] name = "unicode-xid" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "unicode_categories" @@ -3200,6 +3284,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eff4b7516a57307f9349c64bf34caa34b940b66fed4b2fb3136cb7386e5739" +dependencies = [ + "webpki", +] + [[package]] name = "widestring" version = "0.4.2" @@ -3214,9 +3317,9 @@ checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" [[package]] name = "winapi" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", @@ -3240,7 +3343,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -3255,7 +3358,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" dependencies = [ - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] @@ -3264,7 +3367,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e" dependencies = [ - "winapi 0.3.8", + "winapi 0.3.9", ] [[package]] diff --git a/server/Cargo.toml b/server/Cargo.toml index df6c90afd..8daf72c4a 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -19,11 +19,12 @@ chrono = { version = "0.4.7", features = ["serde"] } serde_json = { version = "1.0.52", features = ["preserve_order"]} failure = "0.1.8" serde = { version = "1.0.105", features = ["derive"] } -actix = "0.9.0" -actix-web = "2.0.0" -actix-files = "0.2.1" -actix-web-actors = "2.0.0" +actix = "0.10.0-alpha.2" +actix-web = { version = "3.0.0-alpha.3", features = ["rustls"] } +actix-files = "0.3.0-alpha.1" +actix-web-actors = "3.0.0-alpha.1" actix-rt = "1.1.1" +awc = "2.0.0-alpha.2" log = "0.4.0" env_logger = "0.7.1" rand = "0.7.3" @@ -34,19 +35,19 @@ regex = "1.3.5" lazy_static = "1.3.0" lettre = "0.9.3" lettre_email = "0.9.4" -sha2 = "0.8.1" rss = "1.9.0" htmlescape = "0.3.1" url = { version = "2.1.1", features = ["serde"] } config = {version = "0.10.1", default-features = false, features = ["hjson"] } percent-encoding = "2.1.0" -isahc = "0.9.2" comrak = "0.7" openssl = "0.10" http = "0.2.1" -http-signature-normalization = "0.5.1" +http-signature-normalization-actix = { version = "0.4.0-alpha.0", default-features = false, features = ["sha-2"] } base64 = "0.12.1" tokio = "0.2.21" futures = "0.3.5" itertools = "0.9.0" uuid = { version = "0.8", features = ["serde", "v4"] } +sha2 = "0.9" +async-trait = "0.1.36" diff --git a/server/src/api/comment.rs b/server/src/api/comment.rs index 562174587..c7406b370 100644 --- a/server/src/api/comment.rs +++ b/server/src/api/comment.rs @@ -1,6 +1,7 @@ use crate::{ api::{APIError, Oper, Perform}, apub::{ApubLikeableType, ApubObjectType}, + blocking, db::{ comment::*, comment_view::*, @@ -27,13 +28,10 @@ use crate::{ UserOperation, WebsocketInfo, }, + DbPool, + LemmyError, MentionData, }; -use diesel::{ - r2d2::{ConnectionManager, Pool}, - PgConnection, -}; -use failure::Error; use log::error; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -97,14 +95,15 @@ pub struct GetCommentsResponse { comments: Vec, } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = CommentResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &CreateComment = &self.data; let claims = match Claims::decode(&data.auth) { @@ -114,20 +113,6 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - - // Check for a community ban - let post = Post::read(&conn, data.post_id)?; - if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { - return Err(APIError::err("community_ban").into()); - } - - // Check for a site ban - let user = User_::read(&conn, user_id)?; - if user.banned { - return Err(APIError::err("site_ban").into()); - } - let content_slurs_removed = remove_slurs(&data.content.to_owned()); let comment_form = CommentForm { @@ -144,21 +129,48 @@ impl Perform for Oper { local: true, }; - let inserted_comment = match Comment::create(&conn, &comment_form) { + // Check for a community ban + let post_id = data.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + + let community_id = post.community_id; + let is_banned = + move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); + if blocking(pool, is_banned).await? { + return Err(APIError::err("community_ban").into()); + } + + // Check for a site ban + let user = blocking(pool, move |conn| User_::read(&conn, user_id)).await??; + if user.banned { + return Err(APIError::err("site_ban").into()); + } + + let comment_form2 = comment_form.clone(); + let inserted_comment = + match blocking(pool, move |conn| Comment::create(&conn, &comment_form2)).await? { + Ok(comment) => comment, + Err(_e) => return Err(APIError::err("couldnt_create_comment").into()), + }; + + let inserted_comment_id = inserted_comment.id; + let updated_comment: Comment = match blocking(pool, move |conn| { + Comment::update_ap_id(&conn, inserted_comment_id) + }) + .await? + { Ok(comment) => comment, Err(_e) => return Err(APIError::err("couldnt_create_comment").into()), }; - let updated_comment = match Comment::update_ap_id(&conn, inserted_comment.id) { - Ok(comment) => comment, - Err(_e) => return Err(APIError::err("couldnt_create_comment").into()), - }; - - updated_comment.send_create(&user, &conn)?; + updated_comment + .send_create(&user, &self.client, pool) + .await?; // Scan the comment for user mentions, add those rows let mentions = scrape_text_for_mentions(&comment_form.content); - let recipient_ids = send_local_notifs(&conn, &mentions, &updated_comment, &user, &post); + let recipient_ids = + send_local_notifs(mentions, updated_comment.clone(), user.clone(), post, pool).await?; // You like your own comment by default let like_form = CommentLikeForm { @@ -168,14 +180,17 @@ impl Perform for Oper { score: 1, }; - let _inserted_like = match CommentLike::like(&conn, &like_form) { - Ok(like) => like, - Err(_e) => return Err(APIError::err("couldnt_like_comment").into()), - }; + let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form); + if blocking(pool, like).await?.is_err() { + return Err(APIError::err("couldnt_like_comment").into()); + } - updated_comment.send_like(&user, &conn)?; + updated_comment.send_like(&user, &self.client, pool).await?; - let comment_view = CommentView::read(&conn, inserted_comment.id, Some(user_id))?; + let comment_view = blocking(pool, move |conn| { + CommentView::read(&conn, inserted_comment.id, Some(user_id)) + }) + .await??; let mut res = CommentResponse { comment: comment_view, @@ -198,14 +213,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = CommentResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &EditComment = &self.data; let claims = match Claims::decode(&data.auth) { @@ -215,30 +231,44 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; + let user = blocking(pool, move |conn| User_::read(&conn, user_id)).await??; - let user = User_::read(&conn, user_id)?; - - let orig_comment = CommentView::read(&conn, data.edit_id, None)?; + let edit_id = data.edit_id; + let orig_comment = + blocking(pool, move |conn| CommentView::read(&conn, edit_id, None)).await??; // You are allowed to mark the comment as read even if you're banned. if data.read.is_none() { // Verify its the creator or a mod, or an admin let mut editors: Vec = vec![data.creator_id]; + let community_id = orig_comment.community_id; editors.append( - &mut CommunityModeratorView::for_community(&conn, orig_comment.community_id)? - .into_iter() - .map(|m| m.user_id) - .collect(), + &mut blocking(pool, move |conn| { + Ok( + CommunityModeratorView::for_community(&conn, community_id)? + .into_iter() + .map(|m| m.user_id) + .collect(), + ) as Result<_, LemmyError> + }) + .await??, + ); + editors.append( + &mut blocking(pool, move |conn| { + Ok(UserView::admins(conn)?.into_iter().map(|a| a.id).collect()) as Result<_, LemmyError> + }) + .await??, ); - editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect()); if !editors.contains(&user_id) { return Err(APIError::err("no_comment_edit_allowed").into()); } // Check for a community ban - if CommunityUserBanView::get(&conn, user_id, orig_comment.community_id).is_ok() { + let community_id = orig_comment.community_id; + let is_banned = + move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); + if blocking(pool, is_banned).await? { return Err(APIError::err("community_ban").into()); } @@ -250,7 +280,8 @@ impl Perform for Oper { let content_slurs_removed = remove_slurs(&data.content.to_owned()); - let read_comment = Comment::read(&conn, data.edit_id)?; + let edit_id = data.edit_id; + let read_comment = blocking(pool, move |conn| Comment::read(conn, edit_id)).await??; let comment_form = CommentForm { content: content_slurs_removed, @@ -270,31 +301,48 @@ impl Perform for Oper { local: read_comment.local, }; - let updated_comment = match Comment::update(&conn, data.edit_id, &comment_form) { + let edit_id = data.edit_id; + let comment_form2 = comment_form.clone(); + let updated_comment = match blocking(pool, move |conn| { + Comment::update(conn, edit_id, &comment_form2) + }) + .await? + { Ok(comment) => comment, Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), }; if let Some(deleted) = data.deleted.to_owned() { if deleted { - updated_comment.send_delete(&user, &conn)?; + updated_comment + .send_delete(&user, &self.client, pool) + .await?; } else { - updated_comment.send_undo_delete(&user, &conn)?; + updated_comment + .send_undo_delete(&user, &self.client, pool) + .await?; } } else if let Some(removed) = data.removed.to_owned() { if removed { - updated_comment.send_remove(&user, &conn)?; + updated_comment + .send_remove(&user, &self.client, pool) + .await?; } else { - updated_comment.send_undo_remove(&user, &conn)?; + updated_comment + .send_undo_remove(&user, &self.client, pool) + .await?; } } else { - updated_comment.send_update(&user, &conn)?; + updated_comment + .send_update(&user, &self.client, pool) + .await?; } - let post = Post::read(&conn, data.post_id)?; + let post_id = data.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; let mentions = scrape_text_for_mentions(&comment_form.content); - let recipient_ids = send_local_notifs(&conn, &mentions, &updated_comment, &user, &post); + let recipient_ids = send_local_notifs(mentions, updated_comment, user, post, pool).await?; // Mod tables if let Some(removed) = data.removed.to_owned() { @@ -304,10 +352,14 @@ impl Perform for Oper { removed: Some(removed), reason: data.reason.to_owned(), }; - ModRemoveComment::create(&conn, &form)?; + blocking(pool, move |conn| ModRemoveComment::create(conn, &form)).await??; } - let comment_view = CommentView::read(&conn, data.edit_id, Some(user_id))?; + let edit_id = data.edit_id; + let comment_view = blocking(pool, move |conn| { + CommentView::read(conn, edit_id, Some(user_id)) + }) + .await??; let mut res = CommentResponse { comment: comment_view, @@ -330,14 +382,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = CommentResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &SaveComment = &self.data; let claims = match Claims::decode(&data.auth) { @@ -352,21 +405,23 @@ impl Perform for Oper { user_id, }; - let conn = pool.get()?; - if data.save { - match CommentSaved::save(&conn, &comment_saved_form) { - Ok(comment) => comment, - Err(_e) => return Err(APIError::err("couldnt_save_comment").into()), - }; + let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form); + if blocking(pool, save_comment).await?.is_err() { + return Err(APIError::err("couldnt_save_comment").into()); + } } else { - match CommentSaved::unsave(&conn, &comment_saved_form) { - Ok(comment) => comment, - Err(_e) => return Err(APIError::err("couldnt_save_comment").into()), - }; + let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form); + if blocking(pool, unsave_comment).await?.is_err() { + return Err(APIError::err("couldnt_save_comment").into()); + } } - let comment_view = CommentView::read(&conn, data.comment_id, Some(user_id))?; + let comment_id = data.comment_id; + let comment_view = blocking(pool, move |conn| { + CommentView::read(conn, comment_id, Some(user_id)) + }) + .await??; Ok(CommentResponse { comment: comment_view, @@ -375,14 +430,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = CommentResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &CreateCommentLike = &self.data; let claims = match Claims::decode(&data.auth) { @@ -394,36 +450,42 @@ impl Perform for Oper { let mut recipient_ids = Vec::new(); - let conn = pool.get()?; - // Don't do a downvote if site has downvotes disabled if data.score == -1 { - let site = SiteView::read(&conn)?; + let site = blocking(pool, move |conn| SiteView::read(conn)).await??; if !site.enable_downvotes { return Err(APIError::err("downvotes_disabled").into()); } } // Check for a community ban - let post = Post::read(&conn, data.post_id)?; - if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { + let post_id = data.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + let community_id = post.community_id; + let is_banned = + move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); + if blocking(pool, is_banned).await? { return Err(APIError::err("community_ban").into()); } // Check for a site ban - let user = User_::read(&conn, user_id)?; + let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; if user.banned { return Err(APIError::err("site_ban").into()); } - let comment = Comment::read(&conn, data.comment_id)?; + let comment_id = data.comment_id; + let comment = blocking(pool, move |conn| Comment::read(conn, comment_id)).await??; // Add to recipient ids match comment.parent_id { Some(parent_id) => { - let parent_comment = Comment::read(&conn, parent_id)?; + let parent_comment = blocking(pool, move |conn| Comment::read(conn, parent_id)).await??; if parent_comment.creator_id != user_id { - let parent_user = User_::read(&conn, parent_comment.creator_id)?; + let parent_user = blocking(pool, move |conn| { + User_::read(conn, parent_comment.creator_id) + }) + .await??; recipient_ids.push(parent_user.id); } } @@ -440,27 +502,33 @@ impl Perform for Oper { }; // Remove any likes first - CommentLike::remove(&conn, &like_form)?; + let like_form2 = like_form.clone(); + blocking(pool, move |conn| CommentLike::remove(conn, &like_form2)).await??; // Only add the like if the score isnt 0 let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); if do_add { - let _inserted_like = match CommentLike::like(&conn, &like_form) { - Ok(like) => like, - Err(_e) => return Err(APIError::err("couldnt_like_comment").into()), - }; + let like_form2 = like_form.clone(); + let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2); + if blocking(pool, like).await?.is_err() { + return Err(APIError::err("couldnt_like_comment").into()); + } if like_form.score == 1 { - comment.send_like(&user, &conn)?; + comment.send_like(&user, &self.client, pool).await?; } else if like_form.score == -1 { - comment.send_dislike(&user, &conn)?; + comment.send_dislike(&user, &self.client, pool).await?; } } else { - comment.send_undo_like(&user, &conn)?; + comment.send_undo_like(&user, &self.client, pool).await?; } // Have to refetch the comment to get the current state - let liked_comment = CommentView::read(&conn, data.comment_id, Some(user_id))?; + let comment_id = data.comment_id; + let liked_comment = blocking(pool, move |conn| { + CommentView::read(conn, comment_id, Some(user_id)) + }) + .await??; let mut res = CommentResponse { comment: liked_comment, @@ -483,14 +551,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = GetCommentsResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &GetComments = &self.data; let user_claims: Option = match &data.auth { @@ -509,19 +578,23 @@ impl Perform for Oper { let type_ = ListingType::from_str(&data.type_)?; let sort = SortType::from_str(&data.sort)?; - let conn = pool.get()?; - - let comments = match CommentQueryBuilder::create(&conn) - .listing_type(type_) - .sort(&sort) - .for_community_id(data.community_id) - .my_user_id(user_id) - .page(data.page) - .limit(data.limit) - .list() - { + let community_id = data.community_id; + let page = data.page; + let limit = data.limit; + let comments = blocking(pool, move |conn| { + CommentQueryBuilder::create(conn) + .listing_type(type_) + .sort(&sort) + .for_community_id(community_id) + .my_user_id(user_id) + .page(page) + .limit(limit) + .list() + }) + .await?; + let comments = match comments { Ok(comments) => comments, - Err(_e) => return Err(APIError::err("couldnt_get_comments").into()), + Err(_) => return Err(APIError::err("couldnt_get_comments").into()), }; if let Some(ws) = websocket_info { @@ -542,8 +615,23 @@ impl Perform for Oper { } } -pub fn send_local_notifs( - conn: &PgConnection, +pub async fn send_local_notifs( + mentions: Vec, + comment: Comment, + user: User_, + post: Post, + pool: &DbPool, +) -> Result, LemmyError> { + let ids = blocking(pool, move |conn| { + do_send_local_notifs(conn, &mentions, &comment, &user, &post) + }) + .await?; + + Ok(ids) +} + +fn do_send_local_notifs( + conn: &diesel::PgConnection, mentions: &[MentionData], comment: &Comment, user: &User_, diff --git a/server/src/api/community.rs b/server/src/api/community.rs index 3fc67eb34..02071c577 100644 --- a/server/src/api/community.rs +++ b/server/src/api/community.rs @@ -7,6 +7,7 @@ use crate::{ ActorType, EndpointType, }, + blocking, db::{Bannable, Crud, Followable, Joinable, SortType}, is_valid_community_name, naive_from_unix, @@ -18,12 +19,9 @@ use crate::{ UserOperation, WebsocketInfo, }, + DbPool, + LemmyError, }; -use diesel::{ - r2d2::{ConnectionManager, Pool}, - PgConnection, -}; -use failure::Error; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -138,14 +136,15 @@ pub struct TransferCommunity { auth: String, } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = GetCommunityResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &GetCommunity = &self.data; let user_id: Option = match &data.auth { @@ -159,33 +158,38 @@ impl Perform for Oper { None => None, }; - let conn = pool.get()?; - + let name = data.name.to_owned().unwrap_or_else(|| "main".to_string()); let community = match data.id { - Some(id) => Community::read(&conn, id)?, - None => { - match Community::read_from_name( - &conn, - &data.name.to_owned().unwrap_or_else(|| "main".to_string()), - ) { - Ok(community) => community, - Err(_e) => return Err(APIError::err("couldnt_find_community").into()), - } - } + Some(id) => blocking(pool, move |conn| Community::read(conn, id)).await??, + None => match blocking(pool, move |conn| Community::read_from_name(conn, &name)).await? { + Ok(community) => community, + Err(_e) => return Err(APIError::err("couldnt_find_community").into()), + }, }; - let community_view = match CommunityView::read(&conn, community.id, user_id) { + let community_id = community.id; + let community_view = match blocking(pool, move |conn| { + CommunityView::read(conn, community_id, user_id) + }) + .await? + { Ok(community) => community, Err(_e) => return Err(APIError::err("couldnt_find_community").into()), }; - let moderators = match CommunityModeratorView::for_community(&conn, community.id) { + let community_id = community.id; + let moderators: Vec = match blocking(pool, move |conn| { + CommunityModeratorView::for_community(conn, community_id) + }) + .await? + { Ok(moderators) => moderators, Err(_e) => return Err(APIError::err("couldnt_find_community").into()), }; - let site_creator_id = Site::read(&conn, 1)?.creator_id; - let mut admins = UserView::admins(&conn)?; + let site = blocking(pool, move |conn| Site::read(conn, 1)).await??; + let site_creator_id = site.creator_id; + let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??; let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap(); let creator_user = admins.remove(creator_index); admins.insert(0, creator_user); @@ -220,14 +224,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = CommunityResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &CreateCommunity = &self.data; let claims = match Claims::decode(&data.auth) { @@ -255,10 +260,9 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - // Check for a site ban - if UserView::read(&conn, user_id)?.banned { + let user_view = blocking(pool, move |conn| UserView::read(conn, user_id)).await??; + if user_view.banned { return Err(APIError::err("site_ban").into()); } @@ -283,34 +287,36 @@ impl Perform for Oper { published: None, }; - let inserted_community = match Community::create(&conn, &community_form) { - Ok(community) => community, - Err(_e) => return Err(APIError::err("community_already_exists").into()), - }; + let inserted_community = + match blocking(pool, move |conn| Community::create(conn, &community_form)).await? { + Ok(community) => community, + Err(_e) => return Err(APIError::err("community_already_exists").into()), + }; let community_moderator_form = CommunityModeratorForm { community_id: inserted_community.id, user_id, }; - let _inserted_community_moderator = - match CommunityModerator::join(&conn, &community_moderator_form) { - Ok(user) => user, - Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()), - }; + let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form); + if blocking(pool, join).await?.is_err() { + return Err(APIError::err("community_moderator_already_exists").into()); + } let community_follower_form = CommunityFollowerForm { community_id: inserted_community.id, user_id, }; - let _inserted_community_follower = - match CommunityFollower::follow(&conn, &community_follower_form) { - Ok(user) => user, - Err(_e) => return Err(APIError::err("community_follower_already_exists").into()), - }; + let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form); + if blocking(pool, follow).await?.is_err() { + return Err(APIError::err("community_follower_already_exists").into()); + } - let community_view = CommunityView::read(&conn, inserted_community.id, Some(user_id))?; + let community_view = blocking(pool, move |conn| { + CommunityView::read(conn, inserted_community.id, Some(user_id)) + }) + .await??; Ok(CommunityResponse { community: community_view, @@ -318,14 +324,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = CommunityResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &EditCommunity = &self.data; if let Err(slurs) = slur_check(&data.name) { @@ -353,28 +360,34 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - // Check for a site ban - let user = User_::read(&conn, user_id)?; + let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; if user.banned { return Err(APIError::err("site_ban").into()); } // Verify its a mod + let edit_id = data.edit_id; let mut editors: Vec = Vec::new(); editors.append( - &mut CommunityModeratorView::for_community(&conn, data.edit_id)? - .into_iter() - .map(|m| m.user_id) - .collect(), + &mut blocking(pool, move |conn| { + CommunityModeratorView::for_community(conn, edit_id) + .map(|v| v.into_iter().map(|m| m.user_id).collect()) + }) + .await??, + ); + editors.append( + &mut blocking(pool, move |conn| { + UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect()) + }) + .await??, ); - editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect()); if !editors.contains(&user_id) { return Err(APIError::err("no_community_edit_allowed").into()); } - let read_community = Community::read(&conn, data.edit_id)?; + let edit_id = data.edit_id; + let read_community = blocking(pool, move |conn| Community::read(conn, edit_id)).await??; let community_form = CommunityForm { name: data.name.to_owned(), @@ -394,7 +407,12 @@ impl Perform for Oper { published: None, }; - let updated_community = match Community::update(&conn, data.edit_id, &community_form) { + let edit_id = data.edit_id; + let updated_community = match blocking(pool, move |conn| { + Community::update(conn, edit_id, &community_form) + }) + .await? + { Ok(community) => community, Err(_e) => return Err(APIError::err("couldnt_update_community").into()), }; @@ -412,24 +430,36 @@ impl Perform for Oper { reason: data.reason.to_owned(), expires, }; - ModRemoveCommunity::create(&conn, &form)?; + blocking(pool, move |conn| ModRemoveCommunity::create(conn, &form)).await??; } if let Some(deleted) = data.deleted.to_owned() { if deleted { - updated_community.send_delete(&user, &conn)?; + updated_community + .send_delete(&user, &self.client, pool) + .await?; } else { - updated_community.send_undo_delete(&user, &conn)?; + updated_community + .send_undo_delete(&user, &self.client, pool) + .await?; } } else if let Some(removed) = data.removed.to_owned() { if removed { - updated_community.send_remove(&user, &conn)?; + updated_community + .send_remove(&user, &self.client, pool) + .await?; } else { - updated_community.send_undo_remove(&user, &conn)?; + updated_community + .send_undo_remove(&user, &self.client, pool) + .await?; } } - let community_view = CommunityView::read(&conn, data.edit_id, Some(user_id))?; + let edit_id = data.edit_id; + let community_view = blocking(pool, move |conn| { + CommunityView::read(conn, edit_id, Some(user_id)) + }) + .await??; let res = CommunityResponse { community: community_view, @@ -453,14 +483,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = ListCommunitiesResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &ListCommunities = &self.data; let user_claims: Option = match &data.auth { @@ -483,29 +514,33 @@ impl Perform for Oper { let sort = SortType::from_str(&data.sort)?; - let conn = pool.get()?; - - let communities = CommunityQueryBuilder::create(&conn) - .sort(&sort) - .for_user(user_id) - .show_nsfw(show_nsfw) - .page(data.page) - .limit(data.limit) - .list()?; + let page = data.page; + let limit = data.limit; + let communities = blocking(pool, move |conn| { + CommunityQueryBuilder::create(conn) + .sort(&sort) + .for_user(user_id) + .show_nsfw(show_nsfw) + .page(page) + .limit(limit) + .list() + }) + .await??; // Return the jwt Ok(ListCommunitiesResponse { communities }) } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = CommunityResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &FollowCommunity = &self.data; let claims = match Claims::decode(&data.auth) { @@ -515,9 +550,8 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - - let community = Community::read(&conn, data.community_id)?; + let community_id = data.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; let community_follower_form = CommunityFollowerForm { community_id: data.community_id, user_id, @@ -525,34 +559,44 @@ impl Perform for Oper { if community.local { if data.follow { - match CommunityFollower::follow(&conn, &community_follower_form) { - Ok(user) => user, - Err(_e) => return Err(APIError::err("community_follower_already_exists").into()), - }; + let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form); + if blocking(pool, follow).await?.is_err() { + return Err(APIError::err("community_follower_already_exists").into()); + } } else { - match CommunityFollower::unfollow(&conn, &community_follower_form) { - Ok(user) => user, - Err(_e) => return Err(APIError::err("community_follower_already_exists").into()), - }; + let unfollow = + move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form); + if blocking(pool, unfollow).await?.is_err() { + return Err(APIError::err("community_follower_already_exists").into()); + } } } else { - let user = User_::read(&conn, user_id)?; + let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; if data.follow { // Dont actually add to the community followers here, because you need // to wait for the accept - user.send_follow(&community.actor_id, &conn)?; + user + .send_follow(&community.actor_id, &self.client, pool) + .await?; } else { - user.send_unfollow(&community.actor_id, &conn)?; - match CommunityFollower::unfollow(&conn, &community_follower_form) { - Ok(user) => user, - Err(_e) => return Err(APIError::err("community_follower_already_exists").into()), - }; + user + .send_unfollow(&community.actor_id, &self.client, pool) + .await?; + let unfollow = + move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form); + if blocking(pool, unfollow).await?.is_err() { + return Err(APIError::err("community_follower_already_exists").into()); + } } // TODO: this needs to return a "pending" state, until Accept is received from the remote server } - let community_view = CommunityView::read(&conn, data.community_id, Some(user_id))?; + let community_id = data.community_id; + let community_view = blocking(pool, move |conn| { + CommunityView::read(conn, community_id, Some(user_id)) + }) + .await??; Ok(CommunityResponse { community: community_view, @@ -560,14 +604,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = GetFollowedCommunitiesResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &GetFollowedCommunities = &self.data; let claims = match Claims::decode(&data.auth) { @@ -577,27 +622,29 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - - let communities: Vec = - match CommunityFollowerView::for_user(&conn, user_id) { - Ok(communities) => communities, - Err(_e) => return Err(APIError::err("system_err_login").into()), - }; + let communities = match blocking(pool, move |conn| { + CommunityFollowerView::for_user(conn, user_id) + }) + .await? + { + Ok(communities) => communities, + _ => return Err(APIError::err("system_err_login").into()), + }; // Return the jwt Ok(GetFollowedCommunitiesResponse { communities }) } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = BanFromCommunityResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &BanFromCommunity = &self.data; let claims = match Claims::decode(&data.auth) { @@ -612,18 +659,16 @@ impl Perform for Oper { user_id: data.user_id, }; - let conn = pool.get()?; - if data.ban { - match CommunityUserBan::ban(&conn, &community_user_ban_form) { - Ok(user) => user, - Err(_e) => return Err(APIError::err("community_user_already_banned").into()), - }; + let ban = move |conn: &'_ _| CommunityUserBan::ban(conn, &community_user_ban_form); + if blocking(pool, ban).await?.is_err() { + return Err(APIError::err("community_user_already_banned").into()); + } } else { - match CommunityUserBan::unban(&conn, &community_user_ban_form) { - Ok(user) => user, - Err(_e) => return Err(APIError::err("community_user_already_banned").into()), - }; + let unban = move |conn: &'_ _| CommunityUserBan::unban(conn, &community_user_ban_form); + if blocking(pool, unban).await?.is_err() { + return Err(APIError::err("community_user_already_banned").into()); + } } // Mod tables @@ -640,9 +685,10 @@ impl Perform for Oper { banned: Some(data.ban), expires, }; - ModBanFromCommunity::create(&conn, &form)?; + blocking(pool, move |conn| ModBanFromCommunity::create(conn, &form)).await??; - let user_view = UserView::read(&conn, data.user_id)?; + let user_id = data.user_id; + let user_view = blocking(pool, move |conn| UserView::read(conn, user_id)).await??; let res = BanFromCommunityResponse { user: user_view, @@ -662,14 +708,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = AddModToCommunityResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &AddModToCommunity = &self.data; let claims = match Claims::decode(&data.auth) { @@ -684,18 +731,16 @@ impl Perform for Oper { user_id: data.user_id, }; - let conn = pool.get()?; - if data.added { - match CommunityModerator::join(&conn, &community_moderator_form) { - Ok(user) => user, - Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()), - }; + let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form); + if blocking(pool, join).await?.is_err() { + return Err(APIError::err("community_moderator_already_exists").into()); + } } else { - match CommunityModerator::leave(&conn, &community_moderator_form) { - Ok(user) => user, - Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()), - }; + let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form); + if blocking(pool, leave).await?.is_err() { + return Err(APIError::err("community_moderator_already_exists").into()); + } } // Mod tables @@ -705,9 +750,13 @@ impl Perform for Oper { community_id: data.community_id, removed: Some(!data.added), }; - ModAddCommunity::create(&conn, &form)?; + blocking(pool, move |conn| ModAddCommunity::create(conn, &form)).await??; - let moderators = CommunityModeratorView::for_community(&conn, data.community_id)?; + let community_id = data.community_id; + let moderators = blocking(pool, move |conn| { + CommunityModeratorView::for_community(conn, community_id) + }) + .await??; let res = AddModToCommunityResponse { moderators }; @@ -724,14 +773,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = GetCommunityResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &TransferCommunity = &self.data; let claims = match Claims::decode(&data.auth) { @@ -741,12 +791,14 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; + let community_id = data.community_id; + let read_community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; - let read_community = Community::read(&conn, data.community_id)?; + let site_creator_id = + blocking(pool, move |conn| Site::read(conn, 1).map(|s| s.creator_id)).await??; + + let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??; - let site_creator_id = Site::read(&conn, 1)?.creator_id; - let mut admins = UserView::admins(&conn)?; let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap(); let creator_user = admins.remove(creator_index); admins.insert(0, creator_user); @@ -774,13 +826,18 @@ impl Perform for Oper { published: None, }; - let _updated_community = match Community::update(&conn, data.community_id, &community_form) { - Ok(community) => community, - Err(_e) => return Err(APIError::err("couldnt_update_community").into()), + let community_id = data.community_id; + let update = move |conn: &'_ _| Community::update(conn, community_id, &community_form); + if blocking(pool, update).await?.is_err() { + return Err(APIError::err("couldnt_update_community").into()); }; // You also have to re-do the community_moderator table, reordering it. - let mut community_mods = CommunityModeratorView::for_community(&conn, data.community_id)?; + let community_id = data.community_id; + let mut community_mods = blocking(pool, move |conn| { + CommunityModeratorView::for_community(conn, community_id) + }) + .await??; let creator_index = community_mods .iter() .position(|r| r.user_id == data.user_id) @@ -788,19 +845,23 @@ impl Perform for Oper { let creator_user = community_mods.remove(creator_index); community_mods.insert(0, creator_user); - CommunityModerator::delete_for_community(&conn, data.community_id)?; + let community_id = data.community_id; + blocking(pool, move |conn| { + CommunityModerator::delete_for_community(conn, community_id) + }) + .await??; + // TODO: this should probably be a bulk operation for cmod in &community_mods { let community_moderator_form = CommunityModeratorForm { community_id: cmod.community_id, user_id: cmod.user_id, }; - let _inserted_community_moderator = - match CommunityModerator::join(&conn, &community_moderator_form) { - Ok(user) => user, - Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()), - }; + let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form); + if blocking(pool, join).await?.is_err() { + return Err(APIError::err("community_moderator_already_exists").into()); + } } // Mod tables @@ -810,14 +871,24 @@ impl Perform for Oper { community_id: data.community_id, removed: Some(false), }; - ModAddCommunity::create(&conn, &form)?; + blocking(pool, move |conn| ModAddCommunity::create(conn, &form)).await??; - let community_view = match CommunityView::read(&conn, data.community_id, Some(user_id)) { + let community_id = data.community_id; + let community_view = match blocking(pool, move |conn| { + CommunityView::read(conn, community_id, Some(user_id)) + }) + .await? + { Ok(community) => community, Err(_e) => return Err(APIError::err("couldnt_find_community").into()), }; - let moderators = match CommunityModeratorView::for_community(&conn, data.community_id) { + let community_id = data.community_id; + let moderators = match blocking(pool, move |conn| { + CommunityModeratorView::for_community(conn, community_id) + }) + .await? + { Ok(moderators) => moderators, Err(_e) => return Err(APIError::err("couldnt_find_community").into()), }; diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs index afd62aff8..6df9909c5 100644 --- a/server/src/api/mod.rs +++ b/server/src/api/mod.rs @@ -1,12 +1,10 @@ use crate::{ db::{community::*, community_view::*, moderator::*, site::*, user::*, user_view::*}, websocket::WebsocketInfo, + DbPool, + LemmyError, }; -use diesel::{ - r2d2::{ConnectionManager, Pool}, - PgConnection, -}; -use failure::Error; +use actix_web::client::Client; pub mod comment; pub mod community; @@ -30,20 +28,22 @@ impl APIError { pub struct Oper { data: T, + client: Client, } impl Oper { - pub fn new(data: Data) -> Oper { - Oper { data } + pub fn new(data: Data, client: Client) -> Oper { + Oper { data, client } } } +#[async_trait::async_trait(?Send)] pub trait Perform { type Response: serde::ser::Serialize + Send; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result; + ) -> Result; } diff --git a/server/src/api/post.rs b/server/src/api/post.rs index a3ac4915e..840f15305 100644 --- a/server/src/api/post.rs +++ b/server/src/api/post.rs @@ -1,6 +1,7 @@ use crate::{ api::{APIError, Oper, Perform}, apub::{ApubLikeableType, ApubObjectType}, + blocking, db::{ comment_view::*, community_view::*, @@ -26,12 +27,9 @@ use crate::{ UserOperation, WebsocketInfo, }, + DbPool, + LemmyError, }; -use diesel::{ - r2d2::{ConnectionManager, Pool}, - PgConnection, -}; -use failure::Error; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -112,14 +110,15 @@ pub struct SavePost { auth: String, } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = PostResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &CreatePost = &self.data; let claims = match Claims::decode(&data.auth) { @@ -139,22 +138,23 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - // Check for a community ban - if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() { + let community_id = data.community_id; + let is_banned = + move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); + if blocking(pool, is_banned).await? { return Err(APIError::err("community_ban").into()); } // Check for a site ban - let user = User_::read(&conn, user_id)?; + let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; if user.banned { return Err(APIError::err("site_ban").into()); } // Fetch Iframely and pictrs cached image let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) = - fetch_iframely_and_pictrs_data(data.url.to_owned()); + fetch_iframely_and_pictrs_data(&self.client, data.url.to_owned()).await; let post_form = PostForm { name: data.name.to_owned(), @@ -177,7 +177,7 @@ impl Perform for Oper { published: None, }; - let inserted_post = match Post::create(&conn, &post_form) { + let inserted_post = match blocking(pool, move |conn| Post::create(conn, &post_form)).await? { Ok(post) => post, Err(e) => { let err_type = if e.to_string() == "value too long for type character varying(200)" { @@ -190,12 +190,14 @@ impl Perform for Oper { } }; - let updated_post = match Post::update_ap_id(&conn, inserted_post.id) { - Ok(post) => post, - Err(_e) => return Err(APIError::err("couldnt_create_post").into()), - }; + let inserted_post_id = inserted_post.id; + let updated_post = + match blocking(pool, move |conn| Post::update_ap_id(conn, inserted_post_id)).await? { + Ok(post) => post, + Err(_e) => return Err(APIError::err("couldnt_create_post").into()), + }; - updated_post.send_create(&user, &conn)?; + updated_post.send_create(&user, &self.client, pool).await?; // They like their own post by default let like_form = PostLikeForm { @@ -204,15 +206,20 @@ impl Perform for Oper { score: 1, }; - let _inserted_like = match PostLike::like(&conn, &like_form) { - Ok(like) => like, - Err(_e) => return Err(APIError::err("couldnt_like_post").into()), - }; + let like = move |conn: &'_ _| PostLike::like(conn, &like_form); + if blocking(pool, like).await?.is_err() { + return Err(APIError::err("couldnt_like_post").into()); + } - updated_post.send_like(&user, &conn)?; + updated_post.send_like(&user, &self.client, pool).await?; // Refetch the view - let post_view = match PostView::read(&conn, inserted_post.id, Some(user_id)) { + let inserted_post_id = inserted_post.id; + let post_view = match blocking(pool, move |conn| { + PostView::read(conn, inserted_post_id, Some(user_id)) + }) + .await? + { Ok(post) => post, Err(_e) => return Err(APIError::err("couldnt_find_post").into()), }; @@ -231,14 +238,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = GetPostResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &GetPost = &self.data; let user_id: Option = match &data.auth { @@ -252,25 +260,38 @@ impl Perform for Oper { None => None, }; - let conn = pool.get()?; - - let post_view = match PostView::read(&conn, data.id, user_id) { + let id = data.id; + let post_view = match blocking(pool, move |conn| PostView::read(conn, id, user_id)).await? { Ok(post) => post, Err(_e) => return Err(APIError::err("couldnt_find_post").into()), }; - let comments = CommentQueryBuilder::create(&conn) - .for_post_id(data.id) - .my_user_id(user_id) - .limit(9999) - .list()?; + let id = data.id; + let comments = blocking(pool, move |conn| { + CommentQueryBuilder::create(conn) + .for_post_id(id) + .my_user_id(user_id) + .limit(9999) + .list() + }) + .await??; - let community = CommunityView::read(&conn, post_view.community_id, user_id)?; + let community_id = post_view.community_id; + let community = blocking(pool, move |conn| { + CommunityView::read(conn, community_id, user_id) + }) + .await??; - let moderators = CommunityModeratorView::for_community(&conn, post_view.community_id)?; + let community_id = post_view.community_id; + let moderators = blocking(pool, move |conn| { + CommunityModeratorView::for_community(conn, community_id) + }) + .await??; - let site_creator_id = Site::read(&conn, 1)?.creator_id; - let mut admins = UserView::admins(&conn)?; + let site_creator_id = + blocking(pool, move |conn| Site::read(conn, 1).map(|s| s.creator_id)).await??; + + let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??; let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap(); let creator_user = admins.remove(creator_index); admins.insert(0, creator_user); @@ -305,14 +326,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = GetPostsResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &GetPosts = &self.data; let user_claims: Option = match &data.auth { @@ -336,17 +358,21 @@ impl Perform for Oper { let type_ = ListingType::from_str(&data.type_)?; let sort = SortType::from_str(&data.sort)?; - let conn = pool.get()?; - - let posts = match PostQueryBuilder::create(&conn) - .listing_type(type_) - .sort(&sort) - .show_nsfw(show_nsfw) - .for_community_id(data.community_id) - .my_user_id(user_id) - .page(data.page) - .limit(data.limit) - .list() + let page = data.page; + let limit = data.limit; + let community_id = data.community_id; + let posts = match blocking(pool, move |conn| { + PostQueryBuilder::create(conn) + .listing_type(type_) + .sort(&sort) + .show_nsfw(show_nsfw) + .for_community_id(community_id) + .my_user_id(user_id) + .page(page) + .limit(limit) + .list() + }) + .await? { Ok(posts) => posts, Err(_e) => return Err(APIError::err("couldnt_get_posts").into()), @@ -370,14 +396,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = PostResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &CreatePostLike = &self.data; let claims = match Claims::decode(&data.auth) { @@ -387,24 +414,27 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - // Don't do a downvote if site has downvotes disabled if data.score == -1 { - let site = SiteView::read(&conn)?; + let site = blocking(pool, move |conn| SiteView::read(conn)).await??; if !site.enable_downvotes { return Err(APIError::err("downvotes_disabled").into()); } } // Check for a community ban - let post = Post::read(&conn, data.post_id)?; - if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { + let post_id = data.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + + let community_id = post.community_id; + let is_banned = + move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); + if blocking(pool, is_banned).await? { return Err(APIError::err("community_ban").into()); } // Check for a site ban - let user = User_::read(&conn, user_id)?; + let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; if user.banned { return Err(APIError::err("site_ban").into()); } @@ -416,26 +446,33 @@ impl Perform for Oper { }; // Remove any likes first - PostLike::remove(&conn, &like_form)?; + let like_form2 = like_form.clone(); + blocking(pool, move |conn| PostLike::remove(conn, &like_form2)).await??; // Only add the like if the score isnt 0 let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); if do_add { - let _inserted_like = match PostLike::like(&conn, &like_form) { - Ok(like) => like, - Err(_e) => return Err(APIError::err("couldnt_like_post").into()), - }; + let like_form2 = like_form.clone(); + let like = move |conn: &'_ _| PostLike::like(conn, &like_form2); + if blocking(pool, like).await?.is_err() { + return Err(APIError::err("couldnt_like_post").into()); + } if like_form.score == 1 { - post.send_like(&user, &conn)?; + post.send_like(&user, &self.client, pool).await?; } else if like_form.score == -1 { - post.send_dislike(&user, &conn)?; + post.send_dislike(&user, &self.client, pool).await?; } } else { - post.send_undo_like(&user, &conn)?; + post.send_undo_like(&user, &self.client, pool).await?; } - let post_view = match PostView::read(&conn, data.post_id, Some(user_id)) { + let post_id = data.post_id; + let post_view = match blocking(pool, move |conn| { + PostView::read(conn, post_id, Some(user_id)) + }) + .await? + { Ok(post) => post, Err(_e) => return Err(APIError::err("couldnt_find_post").into()), }; @@ -454,14 +491,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = PostResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &EditPost = &self.data; if let Err(slurs) = slur_check(&data.name) { @@ -481,37 +519,46 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - // Verify its the creator or a mod or admin + let community_id = data.community_id; let mut editors: Vec = vec![data.creator_id]; editors.append( - &mut CommunityModeratorView::for_community(&conn, data.community_id)? - .into_iter() - .map(|m| m.user_id) - .collect(), + &mut blocking(pool, move |conn| { + CommunityModeratorView::for_community(conn, community_id) + .map(|v| v.into_iter().map(|m| m.user_id).collect()) + }) + .await??, + ); + editors.append( + &mut blocking(pool, move |conn| { + UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect()) + }) + .await??, ); - editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect()); if !editors.contains(&user_id) { return Err(APIError::err("no_post_edit_allowed").into()); } // Check for a community ban - if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() { + let community_id = data.community_id; + let is_banned = + move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); + if blocking(pool, is_banned).await? { return Err(APIError::err("community_ban").into()); } // Check for a site ban - let user = User_::read(&conn, user_id)?; + let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; if user.banned { return Err(APIError::err("site_ban").into()); } // Fetch Iframely and Pictrs cached image let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) = - fetch_iframely_and_pictrs_data(data.url.to_owned()); + fetch_iframely_and_pictrs_data(&self.client, data.url.to_owned()).await; - let read_post = Post::read(&conn, data.edit_id)?; + let edit_id = data.edit_id; + let read_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??; let post_form = PostForm { name: data.name.to_owned(), @@ -534,7 +581,9 @@ impl Perform for Oper { published: None, }; - let updated_post = match Post::update(&conn, data.edit_id, &post_form) { + let edit_id = data.edit_id; + let res = blocking(pool, move |conn| Post::update(conn, edit_id, &post_form)).await?; + let updated_post: Post = match res { Ok(post) => post, Err(e) => { let err_type = if e.to_string() == "value too long for type character varying(200)" { @@ -555,7 +604,7 @@ impl Perform for Oper { removed: Some(removed), reason: data.reason.to_owned(), }; - ModRemovePost::create(&conn, &form)?; + blocking(pool, move |conn| ModRemovePost::create(conn, &form)).await??; } if let Some(locked) = data.locked.to_owned() { @@ -564,7 +613,7 @@ impl Perform for Oper { post_id: data.edit_id, locked: Some(locked), }; - ModLockPost::create(&conn, &form)?; + blocking(pool, move |conn| ModLockPost::create(conn, &form)).await??; } if let Some(stickied) = data.stickied.to_owned() { @@ -573,26 +622,34 @@ impl Perform for Oper { post_id: data.edit_id, stickied: Some(stickied), }; - ModStickyPost::create(&conn, &form)?; + blocking(pool, move |conn| ModStickyPost::create(conn, &form)).await??; } if let Some(deleted) = data.deleted.to_owned() { if deleted { - updated_post.send_delete(&user, &conn)?; + updated_post.send_delete(&user, &self.client, pool).await?; } else { - updated_post.send_undo_delete(&user, &conn)?; + updated_post + .send_undo_delete(&user, &self.client, pool) + .await?; } } else if let Some(removed) = data.removed.to_owned() { if removed { - updated_post.send_remove(&user, &conn)?; + updated_post.send_remove(&user, &self.client, pool).await?; } else { - updated_post.send_undo_remove(&user, &conn)?; + updated_post + .send_undo_remove(&user, &self.client, pool) + .await?; } } else { - updated_post.send_update(&user, &conn)?; + updated_post.send_update(&user, &self.client, pool).await?; } - let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?; + let edit_id = data.edit_id; + let post_view = blocking(pool, move |conn| { + PostView::read(conn, edit_id, Some(user_id)) + }) + .await??; let res = PostResponse { post: post_view }; @@ -608,14 +665,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = PostResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &SavePost = &self.data; let claims = match Claims::decode(&data.auth) { @@ -630,21 +688,23 @@ impl Perform for Oper { user_id, }; - let conn = pool.get()?; - if data.save { - match PostSaved::save(&conn, &post_saved_form) { - Ok(post) => post, - Err(_e) => return Err(APIError::err("couldnt_save_post").into()), - }; + let save = move |conn: &'_ _| PostSaved::save(conn, &post_saved_form); + if blocking(pool, save).await?.is_err() { + return Err(APIError::err("couldnt_save_post").into()); + } } else { - match PostSaved::unsave(&conn, &post_saved_form) { - Ok(post) => post, - Err(_e) => return Err(APIError::err("couldnt_save_post").into()), - }; + let unsave = move |conn: &'_ _| PostSaved::unsave(conn, &post_saved_form); + if blocking(pool, unsave).await?.is_err() { + return Err(APIError::err("couldnt_save_post").into()); + } } - let post_view = PostView::read(&conn, data.post_id, Some(user_id))?; + let post_id = data.post_id; + let post_view = blocking(pool, move |conn| { + PostView::read(conn, post_id, Some(user_id)) + }) + .await??; Ok(PostResponse { post: post_view }) } diff --git a/server/src/api/site.rs b/server/src/api/site.rs index faee30cbb..f45561a82 100644 --- a/server/src/api/site.rs +++ b/server/src/api/site.rs @@ -2,6 +2,7 @@ use super::user::Register; use crate::{ api::{APIError, Oper, Perform}, apub::fetcher::search_by_apub_id, + blocking, db::{ category::*, comment_view::*, @@ -22,12 +23,9 @@ use crate::{ slur_check, slurs_vec_to_str, websocket::{server::SendAllMessage, UserOperation, WebsocketInfo}, + DbPool, + LemmyError, }; -use diesel::{ - r2d2::{ConnectionManager, Pool}, - PgConnection, -}; -use failure::Error; use log::{debug, info}; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -139,87 +137,79 @@ pub struct SaveSiteConfig { auth: String, } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = ListCategoriesResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let _data: &ListCategories = &self.data; - let conn = pool.get()?; - - let categories: Vec = Category::list_all(&conn)?; + let categories = blocking(pool, move |conn| Category::list_all(conn)).await??; // Return the jwt Ok(ListCategoriesResponse { categories }) } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = GetModlogResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &GetModlog = &self.data; - let conn = pool.get()?; + let community_id = data.community_id; + let mod_user_id = data.mod_user_id; + let page = data.page; + let limit = data.limit; + let removed_posts = blocking(pool, move |conn| { + ModRemovePostView::list(conn, community_id, mod_user_id, page, limit) + }) + .await??; - let removed_posts = ModRemovePostView::list( - &conn, - data.community_id, - data.mod_user_id, - data.page, - data.limit, - )?; - let locked_posts = ModLockPostView::list( - &conn, - data.community_id, - data.mod_user_id, - data.page, - data.limit, - )?; - let stickied_posts = ModStickyPostView::list( - &conn, - data.community_id, - data.mod_user_id, - data.page, - data.limit, - )?; - let removed_comments = ModRemoveCommentView::list( - &conn, - data.community_id, - data.mod_user_id, - data.page, - data.limit, - )?; - let banned_from_community = ModBanFromCommunityView::list( - &conn, - data.community_id, - data.mod_user_id, - data.page, - data.limit, - )?; - let added_to_community = ModAddCommunityView::list( - &conn, - data.community_id, - data.mod_user_id, - data.page, - data.limit, - )?; + let locked_posts = blocking(pool, move |conn| { + ModLockPostView::list(conn, community_id, mod_user_id, page, limit) + }) + .await??; + + let stickied_posts = blocking(pool, move |conn| { + ModStickyPostView::list(conn, community_id, mod_user_id, page, limit) + }) + .await??; + + let removed_comments = blocking(pool, move |conn| { + ModRemoveCommentView::list(conn, community_id, mod_user_id, page, limit) + }) + .await??; + + let banned_from_community = blocking(pool, move |conn| { + ModBanFromCommunityView::list(conn, community_id, mod_user_id, page, limit) + }) + .await??; + + let added_to_community = blocking(pool, move |conn| { + ModAddCommunityView::list(conn, community_id, mod_user_id, page, limit) + }) + .await??; // These arrays are only for the full modlog, when a community isn't given let (removed_communities, banned, added) = if data.community_id.is_none() { - ( - ModRemoveCommunityView::list(&conn, data.mod_user_id, data.page, data.limit)?, - ModBanView::list(&conn, data.mod_user_id, data.page, data.limit)?, - ModAddView::list(&conn, data.mod_user_id, data.page, data.limit)?, - ) + blocking(pool, move |conn| { + Ok(( + ModRemoveCommunityView::list(conn, mod_user_id, page, limit)?, + ModBanView::list(conn, mod_user_id, page, limit)?, + ModAddView::list(conn, mod_user_id, page, limit)?, + )) as Result<_, LemmyError> + }) + .await?? } else { (Vec::new(), Vec::new(), Vec::new()) }; @@ -239,14 +229,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = SiteResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &CreateSite = &self.data; let claims = match Claims::decode(&data.auth) { @@ -266,10 +257,9 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - // Make sure user is an admin - if !UserView::read(&conn, user_id)?.admin { + let user = blocking(pool, move |conn| UserView::read(conn, user_id)).await??; + if !user.admin { return Err(APIError::err("not_an_admin").into()); } @@ -283,24 +273,25 @@ impl Perform for Oper { updated: None, }; - match Site::create(&conn, &site_form) { - Ok(site) => site, - Err(_e) => return Err(APIError::err("site_already_exists").into()), - }; + let create_site = move |conn: &'_ _| Site::create(conn, &site_form); + if blocking(pool, create_site).await?.is_err() { + return Err(APIError::err("site_already_exists").into()); + } - let site_view = SiteView::read(&conn)?; + let site_view = blocking(pool, move |conn| SiteView::read(conn)).await??; Ok(SiteResponse { site: site_view }) } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = SiteResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &EditSite = &self.data; let claims = match Claims::decode(&data.auth) { @@ -320,14 +311,13 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - // Make sure user is an admin - if !UserView::read(&conn, user_id)?.admin { + let user = blocking(pool, move |conn| UserView::read(conn, user_id)).await??; + if !user.admin { return Err(APIError::err("not_an_admin").into()); } - let found_site = Site::read(&conn, 1)?; + let found_site = blocking(pool, move |conn| Site::read(conn, 1)).await??; let site_form = SiteForm { name: data.name.to_owned(), @@ -339,12 +329,12 @@ impl Perform for Oper { enable_nsfw: data.enable_nsfw, }; - match Site::update(&conn, 1, &site_form) { - Ok(site) => site, - Err(_e) => return Err(APIError::err("couldnt_update_site").into()), - }; + let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form); + if blocking(pool, update_site).await?.is_err() { + return Err(APIError::err("couldnt_update_site").into()); + } - let site_view = SiteView::read(&conn)?; + let site_view = blocking(pool, move |conn| SiteView::read(conn)).await??; let res = SiteResponse { site: site_view }; @@ -360,21 +350,21 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = GetSiteResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let _data: &GetSite = &self.data; - let conn = pool.get()?; - // TODO refactor this a little - let site_view = if let Ok(_site) = Site::read(&conn, 1) { - Some(SiteView::read(&conn)?) + let res = blocking(pool, move |conn| Site::read(conn, 1)).await?; + let site_view = if res.is_ok() { + Some(blocking(pool, move |conn| SiteView::read(conn)).await??) } else if let Some(setup) = Settings::get().setup.as_ref() { let register = Register { username: setup.admin_username.to_owned(), @@ -384,7 +374,9 @@ impl Perform for Oper { admin: true, show_nsfw: true, }; - let login_response = Oper::new(register).perform(pool.clone(), websocket_info.clone())?; + let login_response = Oper::new(register, self.client.clone()) + .perform(pool, websocket_info.clone()) + .await?; info!("Admin {} created", setup.admin_username); let create_site = CreateSite { @@ -395,14 +387,16 @@ impl Perform for Oper { enable_nsfw: true, auth: login_response.jwt, }; - Oper::new(create_site).perform(pool, websocket_info.clone())?; + Oper::new(create_site, self.client.clone()) + .perform(pool, websocket_info.clone()) + .await?; info!("Site {} created", setup.site_name); - Some(SiteView::read(&conn)?) + Some(blocking(pool, move |conn| SiteView::read(conn)).await??) } else { None }; - let mut admins = UserView::admins(&conn)?; + let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??; // Make sure the site creator is the top admin if let Some(site_view) = site_view.to_owned() { @@ -415,7 +409,7 @@ impl Perform for Oper { } } - let banned = UserView::banned(&conn)?; + let banned = blocking(pool, move |conn| UserView::banned(conn)).await??; let online = if let Some(_ws) = websocket_info { // TODO @@ -437,21 +431,20 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = SearchResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &Search = &self.data; dbg!(&data); - let conn = pool.get()?; - - match search_by_apub_id(&data.q, &conn) { + match search_by_apub_id(&data.q, &self.client, pool).await { Ok(r) => return Ok(r), Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e), } @@ -467,7 +460,6 @@ impl Perform for Oper { None => None, }; - let sort = SortType::from_str(&data.sort)?; let type_ = SearchType::from_str(&data.type_)?; let mut posts = Vec::new(); @@ -477,85 +469,126 @@ impl Perform for Oper { // TODO no clean / non-nsfw searching rn + let q = data.q.to_owned(); + let page = data.page; + let limit = data.limit; + let sort = SortType::from_str(&data.sort)?; + let community_id = data.community_id; match type_ { SearchType::Posts => { - posts = PostQueryBuilder::create(&conn) - .sort(&sort) - .show_nsfw(true) - .for_community_id(data.community_id) - .search_term(data.q.to_owned()) - .my_user_id(user_id) - .page(data.page) - .limit(data.limit) - .list()?; + posts = blocking(pool, move |conn| { + PostQueryBuilder::create(conn) + .sort(&sort) + .show_nsfw(true) + .for_community_id(community_id) + .search_term(q) + .my_user_id(user_id) + .page(page) + .limit(limit) + .list() + }) + .await??; } SearchType::Comments => { - comments = CommentQueryBuilder::create(&conn) - .sort(&sort) - .search_term(data.q.to_owned()) - .my_user_id(user_id) - .page(data.page) - .limit(data.limit) - .list()?; + comments = blocking(pool, move |conn| { + CommentQueryBuilder::create(&conn) + .sort(&sort) + .search_term(q) + .my_user_id(user_id) + .page(page) + .limit(limit) + .list() + }) + .await??; } SearchType::Communities => { - communities = CommunityQueryBuilder::create(&conn) - .sort(&sort) - .search_term(data.q.to_owned()) - .page(data.page) - .limit(data.limit) - .list()?; + communities = blocking(pool, move |conn| { + CommunityQueryBuilder::create(conn) + .sort(&sort) + .search_term(q) + .page(page) + .limit(limit) + .list() + }) + .await??; } SearchType::Users => { - users = UserQueryBuilder::create(&conn) - .sort(&sort) - .search_term(data.q.to_owned()) - .page(data.page) - .limit(data.limit) - .list()?; + users = blocking(pool, move |conn| { + UserQueryBuilder::create(conn) + .sort(&sort) + .search_term(q) + .page(page) + .limit(limit) + .list() + }) + .await??; } SearchType::All => { - posts = PostQueryBuilder::create(&conn) - .sort(&sort) - .show_nsfw(true) - .for_community_id(data.community_id) - .search_term(data.q.to_owned()) - .my_user_id(user_id) - .page(data.page) - .limit(data.limit) - .list()?; + posts = blocking(pool, move |conn| { + PostQueryBuilder::create(conn) + .sort(&sort) + .show_nsfw(true) + .for_community_id(community_id) + .search_term(q) + .my_user_id(user_id) + .page(page) + .limit(limit) + .list() + }) + .await??; - comments = CommentQueryBuilder::create(&conn) - .sort(&sort) - .search_term(data.q.to_owned()) - .my_user_id(user_id) - .page(data.page) - .limit(data.limit) - .list()?; + let q = data.q.to_owned(); + let sort = SortType::from_str(&data.sort)?; - communities = CommunityQueryBuilder::create(&conn) - .sort(&sort) - .search_term(data.q.to_owned()) - .page(data.page) - .limit(data.limit) - .list()?; + comments = blocking(pool, move |conn| { + CommentQueryBuilder::create(conn) + .sort(&sort) + .search_term(q) + .my_user_id(user_id) + .page(page) + .limit(limit) + .list() + }) + .await??; - users = UserQueryBuilder::create(&conn) - .sort(&sort) - .search_term(data.q.to_owned()) - .page(data.page) - .limit(data.limit) - .list()?; + let q = data.q.to_owned(); + let sort = SortType::from_str(&data.sort)?; + + communities = blocking(pool, move |conn| { + CommunityQueryBuilder::create(conn) + .sort(&sort) + .search_term(q) + .page(page) + .limit(limit) + .list() + }) + .await??; + + let q = data.q.to_owned(); + let sort = SortType::from_str(&data.sort)?; + + users = blocking(pool, move |conn| { + UserQueryBuilder::create(conn) + .sort(&sort) + .search_term(q) + .page(page) + .limit(limit) + .list() + }) + .await??; } SearchType::Url => { - posts = PostQueryBuilder::create(&conn) - .sort(&sort) - .show_nsfw(true) - .for_community_id(data.community_id) - .url_search(data.q.to_owned()) - .page(data.page) - .limit(data.limit) - .list()?; + posts = blocking(pool, move |conn| { + PostQueryBuilder::create(conn) + .sort(&sort) + .show_nsfw(true) + .for_community_id(community_id) + .url_search(q) + .page(page) + .limit(limit) + .list() + }) + .await??; } }; @@ -570,14 +603,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = GetSiteResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &TransferSite = &self.data; let claims = match Claims::decode(&data.auth) { @@ -587,9 +621,7 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - - let read_site = Site::read(&conn, 1)?; + let read_site = blocking(pool, move |conn| Site::read(conn, 1)).await??; // Make sure user is the creator if read_site.creator_id != user_id { @@ -606,9 +638,9 @@ impl Perform for Oper { enable_nsfw: read_site.enable_nsfw, }; - match Site::update(&conn, 1, &site_form) { - Ok(site) => site, - Err(_e) => return Err(APIError::err("couldnt_update_site").into()), + let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form); + if blocking(pool, update_site).await?.is_err() { + return Err(APIError::err("couldnt_update_site").into()); }; // Mod tables @@ -618,11 +650,11 @@ impl Perform for Oper { removed: Some(false), }; - ModAdd::create(&conn, &form)?; + blocking(pool, move |conn| ModAdd::create(conn, &form)).await??; - let site_view = SiteView::read(&conn)?; + let site_view = blocking(pool, move |conn| SiteView::read(conn)).await??; - let mut admins = UserView::admins(&conn)?; + let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??; let creator_index = admins .iter() .position(|r| r.id == site_view.creator_id) @@ -630,7 +662,7 @@ impl Perform for Oper { let creator_user = admins.remove(creator_index); admins.insert(0, creator_user); - let banned = UserView::banned(&conn)?; + let banned = blocking(pool, move |conn| UserView::banned(conn)).await??; Ok(GetSiteResponse { site: Some(site_view), @@ -641,14 +673,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = GetSiteConfigResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &GetSiteConfig = &self.data; let claims = match Claims::decode(&data.auth) { @@ -658,10 +691,8 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - // Only let admins read this - let admins = UserView::admins(&conn)?; + let admins = blocking(pool, move |conn| UserView::admins(conn)).await??; let admin_ids: Vec = admins.into_iter().map(|m| m.id).collect(); if !admin_ids.contains(&user_id) { @@ -674,14 +705,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = GetSiteConfigResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &SaveSiteConfig = &self.data; let claims = match Claims::decode(&data.auth) { @@ -691,10 +723,8 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - // Only let admins read this - let admins = UserView::admins(&conn)?; + let admins = blocking(pool, move |conn| UserView::admins(conn)).await??; let admin_ids: Vec = admins.into_iter().map(|m| m.id).collect(); if !admin_ids.contains(&user_id) { diff --git a/server/src/api/user.rs b/server/src/api/user.rs index 0b6458e75..e7c27def6 100644 --- a/server/src/api/user.rs +++ b/server/src/api/user.rs @@ -6,6 +6,7 @@ use crate::{ ApubObjectType, EndpointType, }, + blocking, db::{ comment::*, comment_view::*, @@ -43,13 +44,10 @@ use crate::{ UserOperation, WebsocketInfo, }, + DbPool, + LemmyError, }; use bcrypt::verify; -use diesel::{ - r2d2::{ConnectionManager, Pool}, - PgConnection, -}; -use failure::Error; use log::error; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -252,20 +250,24 @@ pub struct UserJoinResponse { pub user_id: i32, } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = LoginResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &Login = &self.data; - let conn = pool.get()?; - // Fetch that username / email - let user: User_ = match User_::find_by_email_or_username(&conn, &data.username_or_email) { + let username_or_email = data.username_or_email.clone(); + let user = match blocking(pool, move |conn| { + User_::find_by_email_or_username(conn, &username_or_email) + }) + .await? + { Ok(user) => user, Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()), }; @@ -281,20 +283,20 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = LoginResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &Register = &self.data; - let conn = pool.get()?; - // Make sure site has open registration - if let Ok(site) = SiteView::read(&conn) { + if let Ok(site) = blocking(pool, move |conn| SiteView::read(conn)).await? { + let site: SiteView = site; if !site.open_registration { return Err(APIError::err("registration_closed").into()); } @@ -310,7 +312,11 @@ impl Perform for Oper { } // Make sure there are no admins - if data.admin && !UserView::admins(&conn)?.is_empty() { + let any_admins = blocking(pool, move |conn| { + UserView::admins(conn).map(|a| a.is_empty()) + }) + .await??; + if data.admin && !any_admins { return Err(APIError::err("admin_already_created").into()); } @@ -346,7 +352,7 @@ impl Perform for Oper { }; // Create the user - let inserted_user = match User_::register(&conn, &user_form) { + let inserted_user = match blocking(pool, move |conn| User_::register(conn, &user_form)).await? { Ok(user) => user, Err(e) => { let err_type = if e.to_string() @@ -364,7 +370,7 @@ impl Perform for Oper { let main_community_keypair = generate_actor_keypair()?; // Create the main community if it doesn't exist - let main_community: Community = match Community::read(&conn, 2) { + let main_community = match blocking(pool, move |conn| Community::read(conn, 2)).await? { Ok(c) => c, Err(_e) => { let default_community_name = "main"; @@ -385,7 +391,7 @@ impl Perform for Oper { last_refreshed_at: None, published: None, }; - Community::create(&conn, &community_form).unwrap() + blocking(pool, move |conn| Community::create(conn, &community_form)).await?? } }; @@ -395,11 +401,10 @@ impl Perform for Oper { user_id: inserted_user.id, }; - let _inserted_community_follower = - match CommunityFollower::follow(&conn, &community_follower_form) { - Ok(user) => user, - Err(_e) => return Err(APIError::err("community_follower_already_exists").into()), - }; + let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form); + if blocking(pool, follow).await?.is_err() { + return Err(APIError::err("community_follower_already_exists").into()); + }; // If its an admin, add them as a mod and follower to main if data.admin { @@ -408,11 +413,10 @@ impl Perform for Oper { user_id: inserted_user.id, }; - let _inserted_community_moderator = - match CommunityModerator::join(&conn, &community_moderator_form) { - Ok(user) => user, - Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()), - }; + let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form); + if blocking(pool, join).await?.is_err() { + return Err(APIError::err("community_moderator_already_exists").into()); + } } // Return the jwt @@ -422,14 +426,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = LoginResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &SaveUserSettings = &self.data; let claims = match Claims::decode(&data.auth) { @@ -439,9 +444,7 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - - let read_user = User_::read(&conn, user_id)?; + let read_user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; let email = match &data.email { Some(email) => Some(email.to_owned()), @@ -465,7 +468,12 @@ impl Perform for Oper { if !valid { return Err(APIError::err("password_incorrect").into()); } - User_::update_password(&conn, user_id, &new_password)?.password_encrypted + let new_password = new_password.to_owned(); + let user = blocking(pool, move |conn| { + User_::update_password(conn, user_id, &new_password) + }) + .await??; + user.password_encrypted } None => return Err(APIError::err("password_incorrect").into()), } @@ -501,7 +509,8 @@ impl Perform for Oper { last_refreshed_at: None, }; - let updated_user = match User_::update(&conn, user_id, &user_form) { + let res = blocking(pool, move |conn| User_::update(conn, user_id, &user_form)).await?; + let updated_user: User_ = match res { Ok(user) => user, Err(e) => { let err_type = if e.to_string() @@ -523,18 +532,17 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = GetUserDetailsResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &GetUserDetails = &self.data; - let conn = pool.get()?; - let user_claims: Option = match &data.auth { Some(auth) => match Claims::decode(&auth) { Ok(claims) => Some(claims.claims), @@ -555,54 +563,71 @@ impl Perform for Oper { let sort = SortType::from_str(&data.sort)?; + let username = data + .username + .to_owned() + .unwrap_or_else(|| "admin".to_string()); let user_details_id = match data.user_id { Some(id) => id, None => { - match User_::read_from_name( - &conn, - &data - .username - .to_owned() - .unwrap_or_else(|| "admin".to_string()), - ) { + let user = blocking(pool, move |conn| User_::read_from_name(conn, &username)).await?; + match user { Ok(user) => user.id, Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()), } } }; - let mut user_view = UserView::read(&conn, user_details_id)?; + let mut user_view = blocking(pool, move |conn| UserView::read(conn, user_details_id)).await??; - let mut posts_query = PostQueryBuilder::create(&conn) - .sort(&sort) - .show_nsfw(show_nsfw) - .saved_only(data.saved_only) - .for_community_id(data.community_id) - .my_user_id(user_id) - .page(data.page) - .limit(data.limit); + let page = data.page; + let limit = data.limit; + let saved_only = data.saved_only; + let community_id = data.community_id; + let (posts, comments) = blocking(pool, move |conn| { + let mut posts_query = PostQueryBuilder::create(conn) + .sort(&sort) + .show_nsfw(show_nsfw) + .saved_only(saved_only) + .for_community_id(community_id) + .my_user_id(user_id) + .page(page) + .limit(limit); - let mut comments_query = CommentQueryBuilder::create(&conn) - .sort(&sort) - .saved_only(data.saved_only) - .my_user_id(user_id) - .page(data.page) - .limit(data.limit); + let mut comments_query = CommentQueryBuilder::create(conn) + .sort(&sort) + .saved_only(saved_only) + .my_user_id(user_id) + .page(page) + .limit(limit); - // If its saved only, you don't care what creator it was - // Or, if its not saved, then you only want it for that specific creator - if !data.saved_only { - posts_query = posts_query.for_creator_id(user_details_id); - comments_query = comments_query.for_creator_id(user_details_id); - } + // If its saved only, you don't care what creator it was + // Or, if its not saved, then you only want it for that specific creator + if !saved_only { + posts_query = posts_query.for_creator_id(user_details_id); + comments_query = comments_query.for_creator_id(user_details_id); + } - let posts = posts_query.list()?; - let comments = comments_query.list()?; + let posts = posts_query.list()?; + let comments = comments_query.list()?; - let follows = CommunityFollowerView::for_user(&conn, user_details_id)?; - let moderates = CommunityModeratorView::for_user(&conn, user_details_id)?; - let site_creator_id = Site::read(&conn, 1)?.creator_id; - let mut admins = UserView::admins(&conn)?; + Ok((posts, comments)) as Result<_, LemmyError> + }) + .await??; + + let follows = blocking(pool, move |conn| { + CommunityFollowerView::for_user(conn, user_details_id) + }) + .await??; + let moderates = blocking(pool, move |conn| { + CommunityModeratorView::for_user(conn, user_details_id) + }) + .await??; + + let site_creator_id = + blocking(pool, move |conn| Site::read(conn, 1).map(|s| s.creator_id)).await??; + + let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??; let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap(); let creator_user = admins.remove(creator_index); admins.insert(0, creator_user); @@ -628,14 +653,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = AddAdminResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &AddAdmin = &self.data; let claims = match Claims::decode(&data.auth) { @@ -645,17 +671,17 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - // Make sure user is an admin - if !UserView::read(&conn, user_id)?.admin { + let is_admin = move |conn: &'_ _| UserView::read(conn, user_id).map(|u| u.admin); + if !blocking(pool, is_admin).await?? { return Err(APIError::err("not_an_admin").into()); } - match User_::add_admin(&conn, user_id, data.added) { - Ok(user) => user, - Err(_e) => return Err(APIError::err("couldnt_update_user").into()), - }; + let added = data.added; + let add_admin = move |conn: &'_ _| User_::add_admin(conn, user_id, added); + if blocking(pool, add_admin).await?.is_err() { + return Err(APIError::err("couldnt_update_user").into()); + } // Mod tables let form = ModAddForm { @@ -664,10 +690,12 @@ impl Perform for Oper { removed: Some(!data.added), }; - ModAdd::create(&conn, &form)?; + blocking(pool, move |conn| ModAdd::create(conn, &form)).await??; - let site_creator_id = Site::read(&conn, 1)?.creator_id; - let mut admins = UserView::admins(&conn)?; + let site_creator_id = + blocking(pool, move |conn| Site::read(conn, 1).map(|s| s.creator_id)).await??; + + let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??; let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap(); let creator_user = admins.remove(creator_index); admins.insert(0, creator_user); @@ -686,14 +714,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = BanUserResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &BanUser = &self.data; let claims = match Claims::decode(&data.auth) { @@ -703,17 +732,17 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - // Make sure user is an admin - if !UserView::read(&conn, user_id)?.admin { + let is_admin = move |conn: &'_ _| UserView::read(conn, user_id).map(|u| u.admin); + if !blocking(pool, is_admin).await?? { return Err(APIError::err("not_an_admin").into()); } - match User_::ban_user(&conn, user_id, data.ban) { - Ok(user) => user, - Err(_e) => return Err(APIError::err("couldnt_update_user").into()), - }; + let ban = data.ban; + let ban_user = move |conn: &'_ _| User_::ban_user(conn, user_id, ban); + if blocking(pool, ban_user).await?.is_err() { + return Err(APIError::err("couldnt_update_user").into()); + } // Mod tables let expires = match data.expires { @@ -729,9 +758,10 @@ impl Perform for Oper { expires, }; - ModBan::create(&conn, &form)?; + blocking(pool, move |conn| ModBan::create(conn, &form)).await??; - let user_view = UserView::read(&conn, data.user_id)?; + let user_id = data.user_id; + let user_view = blocking(pool, move |conn| UserView::read(conn, user_id)).await??; let res = BanUserResponse { user: user_view, @@ -750,14 +780,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = GetRepliesResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &GetReplies = &self.data; let claims = match Claims::decode(&data.auth) { @@ -769,27 +800,32 @@ impl Perform for Oper { let sort = SortType::from_str(&data.sort)?; - let conn = pool.get()?; - - let replies = ReplyQueryBuilder::create(&conn, user_id) - .sort(&sort) - .unread_only(data.unread_only) - .page(data.page) - .limit(data.limit) - .list()?; + let page = data.page; + let limit = data.limit; + let unread_only = data.unread_only; + let replies = blocking(pool, move |conn| { + ReplyQueryBuilder::create(conn, user_id) + .sort(&sort) + .unread_only(unread_only) + .page(page) + .limit(limit) + .list() + }) + .await??; Ok(GetRepliesResponse { replies }) } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = GetUserMentionsResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &GetUserMentions = &self.data; let claims = match Claims::decode(&data.auth) { @@ -801,27 +837,32 @@ impl Perform for Oper { let sort = SortType::from_str(&data.sort)?; - let conn = pool.get()?; - - let mentions = UserMentionQueryBuilder::create(&conn, user_id) - .sort(&sort) - .unread_only(data.unread_only) - .page(data.page) - .limit(data.limit) - .list()?; + let page = data.page; + let limit = data.limit; + let unread_only = data.unread_only; + let mentions = blocking(pool, move |conn| { + UserMentionQueryBuilder::create(conn, user_id) + .sort(&sort) + .unread_only(unread_only) + .page(page) + .limit(limit) + .list() + }) + .await??; Ok(GetUserMentionsResponse { mentions }) } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = UserMentionResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &EditUserMention = &self.data; let claims = match Claims::decode(&data.auth) { @@ -831,9 +872,9 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - - let user_mention = UserMention::read(&conn, data.user_mention_id)?; + let user_mention_id = data.user_mention_id; + let user_mention = + blocking(pool, move |conn| UserMention::read(conn, user_mention_id)).await??; let user_mention_form = UserMentionForm { recipient_id: user_id, @@ -841,13 +882,18 @@ impl Perform for Oper { read: data.read.to_owned(), }; - let _updated_user_mention = - match UserMention::update(&conn, user_mention.id, &user_mention_form) { - Ok(comment) => comment, - Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), - }; + let user_mention_id = user_mention.id; + let update_mention = + move |conn: &'_ _| UserMention::update(conn, user_mention_id, &user_mention_form); + if blocking(pool, update_mention).await?.is_err() { + return Err(APIError::err("couldnt_update_comment").into()); + }; - let user_mention_view = UserMentionView::read(&conn, user_mention.id, user_id)?; + let user_mention_id = user_mention.id; + let user_mention_view = blocking(pool, move |conn| { + UserMentionView::read(conn, user_mention_id, user_id) + }) + .await??; Ok(UserMentionResponse { mention: user_mention_view, @@ -855,14 +901,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = GetRepliesResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &MarkAllAsRead = &self.data; let claims = match Claims::decode(&data.auth) { @@ -872,28 +919,35 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - - let replies = ReplyQueryBuilder::create(&conn, user_id) - .unread_only(true) - .page(1) - .limit(999) - .list()?; + let replies = blocking(pool, move |conn| { + ReplyQueryBuilder::create(conn, user_id) + .unread_only(true) + .page(1) + .limit(999) + .list() + }) + .await??; + // TODO: this should probably be a bulk operation for reply in &replies { - match Comment::mark_as_read(&conn, reply.id) { - Ok(comment) => comment, - Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), - }; + let reply_id = reply.id; + let mark_as_read = move |conn: &'_ _| Comment::mark_as_read(conn, reply_id); + if blocking(pool, mark_as_read).await?.is_err() { + return Err(APIError::err("couldnt_update_comment").into()); + } } // Mentions - let mentions = UserMentionQueryBuilder::create(&conn, user_id) - .unread_only(true) - .page(1) - .limit(999) - .list()?; + let mentions = blocking(pool, move |conn| { + UserMentionQueryBuilder::create(conn, user_id) + .unread_only(true) + .page(1) + .limit(999) + .list() + }) + .await??; + // TODO: this should probably be a bulk operation for mention in &mentions { let mention_form = UserMentionForm { recipient_id: mention.to_owned().recipient_id, @@ -901,20 +955,25 @@ impl Perform for Oper { read: Some(true), }; - let _updated_mention = - match UserMention::update(&conn, mention.user_mention_id, &mention_form) { - Ok(mention) => mention, - Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), - }; + let user_mention_id = mention.user_mention_id; + let update_mention = + move |conn: &'_ _| UserMention::update(conn, user_mention_id, &mention_form); + if blocking(pool, update_mention).await?.is_err() { + return Err(APIError::err("couldnt_update_comment").into()); + } } // messages - let messages = PrivateMessageQueryBuilder::create(&conn, user_id) - .page(1) - .limit(999) - .unread_only(true) - .list()?; + let messages = blocking(pool, move |conn| { + PrivateMessageQueryBuilder::create(conn, user_id) + .page(1) + .limit(999) + .unread_only(true) + .list() + }) + .await??; + // TODO: this should probably be a bulk operation for message in &messages { let private_message_form = PrivateMessageForm { content: message.to_owned().content, @@ -928,25 +987,27 @@ impl Perform for Oper { published: None, }; - let _updated_message = match PrivateMessage::update(&conn, message.id, &private_message_form) - { - Ok(message) => message, - Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()), - }; + let message_id = message.id; + let update_pm = + move |conn: &'_ _| PrivateMessage::update(conn, message_id, &private_message_form); + if blocking(pool, update_pm).await?.is_err() { + return Err(APIError::err("couldnt_update_private_message").into()); + } } Ok(GetRepliesResponse { replies: vec![] }) } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = LoginResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &DeleteAccount = &self.data; let claims = match Claims::decode(&data.auth) { @@ -956,9 +1017,7 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - - let user: User_ = User_::read(&conn, user_id)?; + let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; // Verify the password let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false); @@ -967,30 +1026,40 @@ impl Perform for Oper { } // Comments - let comments = CommentQueryBuilder::create(&conn) - .for_creator_id(user_id) - .limit(std::i64::MAX) - .list()?; + let comments = blocking(pool, move |conn| { + CommentQueryBuilder::create(conn) + .for_creator_id(user_id) + .limit(std::i64::MAX) + .list() + }) + .await??; + // TODO: this should probably be a bulk operation for comment in &comments { - let _updated_comment = match Comment::permadelete(&conn, comment.id) { - Ok(comment) => comment, - Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), - }; + let comment_id = comment.id; + let permadelete = move |conn: &'_ _| Comment::permadelete(conn, comment_id); + if blocking(pool, permadelete).await?.is_err() { + return Err(APIError::err("couldnt_update_comment").into()); + } } // Posts - let posts = PostQueryBuilder::create(&conn) - .sort(&SortType::New) - .for_creator_id(user_id) - .limit(std::i64::MAX) - .list()?; + let posts = blocking(pool, move |conn| { + PostQueryBuilder::create(conn) + .sort(&SortType::New) + .for_creator_id(user_id) + .limit(std::i64::MAX) + .list() + }) + .await??; + // TODO: this should probably be a bulk operation for post in &posts { - let _updated_post = match Post::permadelete(&conn, post.id) { - Ok(post) => post, - Err(_e) => return Err(APIError::err("couldnt_update_post").into()), - }; + let post_id = post.id; + let permadelete = move |conn: &'_ _| Post::permadelete(conn, post_id); + if blocking(pool, permadelete).await?.is_err() { + return Err(APIError::err("couldnt_update_post").into()); + } } Ok(LoginResponse { @@ -999,20 +1068,20 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = PasswordResetResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &PasswordReset = &self.data; - let conn = pool.get()?; - // Fetch that email - let user: User_ = match User_::find_by_email(&conn, &data.email) { + let email = data.email.clone(); + let user = match blocking(pool, move |conn| User_::find_by_email(conn, &email)).await? { Ok(user) => user, Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()), }; @@ -1021,7 +1090,12 @@ impl Perform for Oper { let token = generate_random_string(); // Insert the row - PasswordResetRequest::create_token(&conn, user.id, &token)?; + let token2 = token.clone(); + let user_id = user.id; + blocking(pool, move |conn| { + PasswordResetRequest::create_token(conn, user_id, &token2) + }) + .await??; // Email the pure token to the user. // TODO no i18n support here. @@ -1038,20 +1112,23 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = LoginResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &PasswordChange = &self.data; - let conn = pool.get()?; - // Fetch the user_id from the token - let user_id = PasswordResetRequest::read_from_token(&conn, &data.token)?.user_id; + let token = data.token.clone(); + let user_id = blocking(pool, move |conn| { + PasswordResetRequest::read_from_token(conn, &token).map(|p| p.user_id) + }) + .await??; // Make sure passwords match if data.password != data.password_verify { @@ -1059,7 +1136,12 @@ impl Perform for Oper { } // Update the user with the new password - let updated_user = match User_::update_password(&conn, user_id, &data.password) { + let password = data.password.clone(); + let updated_user = match blocking(pool, move |conn| { + User_::update_password(conn, user_id, &password) + }) + .await? + { Ok(user) => user, Err(_e) => return Err(APIError::err("couldnt_update_user").into()), }; @@ -1071,14 +1153,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = PrivateMessageResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &CreatePrivateMessage = &self.data; let claims = match Claims::decode(&data.auth) { @@ -1090,10 +1173,8 @@ impl Perform for Oper { let hostname = &format!("https://{}", Settings::get().hostname); - let conn = pool.get()?; - // Check for a site ban - let user = User_::read(&conn, user_id)?; + let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; if user.banned { return Err(APIError::err("site_ban").into()); } @@ -1112,23 +1193,34 @@ impl Perform for Oper { published: None, }; - let inserted_private_message = match PrivateMessage::create(&conn, &private_message_form) { + let inserted_private_message = match blocking(pool, move |conn| { + PrivateMessage::create(conn, &private_message_form) + }) + .await? + { Ok(private_message) => private_message, Err(_e) => { return Err(APIError::err("couldnt_create_private_message").into()); } }; - let updated_private_message = - match PrivateMessage::update_ap_id(&conn, inserted_private_message.id) { - Ok(private_message) => private_message, - Err(_e) => return Err(APIError::err("couldnt_create_private_message").into()), - }; + let inserted_private_message_id = inserted_private_message.id; + let updated_private_message = match blocking(pool, move |conn| { + PrivateMessage::update_ap_id(&conn, inserted_private_message_id) + }) + .await? + { + Ok(private_message) => private_message, + Err(_e) => return Err(APIError::err("couldnt_create_private_message").into()), + }; - updated_private_message.send_create(&user, &conn)?; + updated_private_message + .send_create(&user, &self.client, pool) + .await?; // Send notifications to the recipient - let recipient_user = User_::read(&conn, data.recipient_id)?; + let recipient_id = data.recipient_id; + let recipient_user = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??; if recipient_user.send_notifications_to_email { if let Some(email) = recipient_user.email { let subject = &format!( @@ -1147,7 +1239,10 @@ impl Perform for Oper { } } - let message = PrivateMessageView::read(&conn, inserted_private_message.id)?; + let message = blocking(pool, move |conn| { + PrivateMessageView::read(conn, inserted_private_message.id) + }) + .await??; let res = PrivateMessageResponse { message }; @@ -1164,14 +1259,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = PrivateMessageResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &EditPrivateMessage = &self.data; let claims = match Claims::decode(&data.auth) { @@ -1181,12 +1277,12 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - - let orig_private_message = PrivateMessage::read(&conn, data.edit_id)?; + let edit_id = data.edit_id; + let orig_private_message = + blocking(pool, move |conn| PrivateMessage::read(conn, edit_id)).await??; // Check for a site ban - let user = User_::read(&conn, user_id)?; + let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; if user.banned { return Err(APIError::err("site_ban").into()); } @@ -1219,23 +1315,34 @@ impl Perform for Oper { published: None, }; - let updated_private_message = - match PrivateMessage::update(&conn, data.edit_id, &private_message_form) { - Ok(private_message) => private_message, - Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()), - }; + let edit_id = data.edit_id; + let updated_private_message = match blocking(pool, move |conn| { + PrivateMessage::update(conn, edit_id, &private_message_form) + }) + .await? + { + Ok(private_message) => private_message, + Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()), + }; if let Some(deleted) = data.deleted.to_owned() { if deleted { - updated_private_message.send_delete(&user, &conn)?; + updated_private_message + .send_delete(&user, &self.client, pool) + .await?; } else { - updated_private_message.send_undo_delete(&user, &conn)?; + updated_private_message + .send_undo_delete(&user, &self.client, pool) + .await?; } } else { - updated_private_message.send_update(&user, &conn)?; + updated_private_message + .send_update(&user, &self.client, pool) + .await?; } - let message = PrivateMessageView::read(&conn, data.edit_id)?; + let edit_id = data.edit_id; + let message = blocking(pool, move |conn| PrivateMessageView::read(conn, edit_id)).await??; let res = PrivateMessageResponse { message }; @@ -1252,14 +1359,15 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = PrivateMessagesResponse; - fn perform( + async fn perform( &self, - pool: Pool>, + pool: &DbPool, _websocket_info: Option, - ) -> Result { + ) -> Result { let data: &GetPrivateMessages = &self.data; let claims = match Claims::decode(&data.auth) { @@ -1269,26 +1377,31 @@ impl Perform for Oper { let user_id = claims.id; - let conn = pool.get()?; - - let messages = PrivateMessageQueryBuilder::create(&conn, user_id) - .page(data.page) - .limit(data.limit) - .unread_only(data.unread_only) - .list()?; + let page = data.page; + let limit = data.limit; + let unread_only = data.unread_only; + let messages = blocking(pool, move |conn| { + PrivateMessageQueryBuilder::create(&conn, user_id) + .page(page) + .limit(limit) + .unread_only(unread_only) + .list() + }) + .await??; Ok(PrivateMessagesResponse { messages }) } } +#[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = UserJoinResponse; - fn perform( + async fn perform( &self, - _pool: Pool>, + _pool: &DbPool, websocket_info: Option, - ) -> Result { + ) -> Result { let data: &UserJoin = &self.data; let claims = match Claims::decode(&data.auth) { diff --git a/server/src/apub/activities.rs b/server/src/apub/activities.rs index b5bb9d76c..e5dc70457 100644 --- a/server/src/apub/activities.rs +++ b/server/src/apub/activities.rs @@ -1,20 +1,22 @@ use crate::{ apub::{extensions::signatures::sign, is_apub_id_valid, ActorType}, db::{activity::insert_activity, community::Community, user::User_}, + request::retry_custom, + DbPool, + LemmyError, }; use activitystreams::{context, object::properties::ObjectProperties, public, Activity, Base}; -use diesel::PgConnection; -use failure::{Error, _core::fmt::Debug}; -use isahc::prelude::*; +use actix_web::client::Client; use log::debug; use serde::Serialize; +use std::fmt::Debug; use url::Url; pub fn populate_object_props( props: &mut ObjectProperties, addressed_ccs: Vec, object_id: &str, -) -> Result<(), Error> { +) -> Result<(), LemmyError> { props .set_context_xsd_any_uri(context())? // TODO: the activity needs a seperate id from the object @@ -26,48 +28,61 @@ pub fn populate_object_props( Ok(()) } -pub fn send_activity_to_community( +pub async fn send_activity_to_community( creator: &User_, - conn: &PgConnection, community: &Community, to: Vec, activity: A, -) -> Result<(), Error> + client: &Client, + pool: &DbPool, +) -> Result<(), LemmyError> where - A: Activity + Base + Serialize + Debug, + A: Activity + Base + Serialize + Debug + Clone + Send + 'static, { - insert_activity(&conn, creator.id, &activity, true)?; + insert_activity(creator.id, activity.clone(), true, pool).await?; // if this is a local community, we need to do an announce from the community instead if community.local { - Community::do_announce(activity, &community, creator, conn)?; + Community::do_announce(activity, &community, creator, client, pool).await?; } else { - send_activity(&activity, creator, to)?; + send_activity(client, &activity, creator, to).await?; } + Ok(()) } /// Send an activity to a list of recipients, using the correct headers etc. -pub fn send_activity(activity: &A, actor: &dyn ActorType, to: Vec) -> Result<(), Error> +pub async fn send_activity( + client: &Client, + activity: &A, + actor: &dyn ActorType, + to: Vec, +) -> Result<(), LemmyError> where - A: Serialize + Debug, + A: Serialize, { - let json = serde_json::to_string(&activity)?; - debug!("Sending activitypub activity {} to {:?}", json, to); + let activity = serde_json::to_string(&activity)?; + debug!("Sending activitypub activity {} to {:?}", activity, to); + for t in to { let to_url = Url::parse(&t)?; if !is_apub_id_valid(&to_url) { debug!("Not sending activity to {} (invalid or blocklisted)", t); continue; } - let request = Request::post(t).header("Host", to_url.domain().unwrap()); - let signature = sign(&request, actor)?; - let res = request - .header("Signature", signature) - .header("Content-Type", "application/json") - .body(json.to_owned())? - .send()?; + + let res = retry_custom(|| async { + let request = client.post(&t).header("Content-Type", "application/json"); + + match sign(request, actor, activity.clone()).await { + Ok(signed) => Ok(signed.send().await), + Err(e) => Err(e), + } + }) + .await?; + debug!("Result for activity send: {:?}", res); } + Ok(()) } diff --git a/server/src/apub/comment.rs b/server/src/apub/comment.rs index 0a513f332..a42a52c2e 100644 --- a/server/src/apub/comment.rs +++ b/server/src/apub/comment.rs @@ -16,6 +16,7 @@ use crate::{ FromApub, ToApub, }, + blocking, convert_datetime, db::{ comment::{Comment, CommentForm}, @@ -26,6 +27,8 @@ use crate::{ }, routes::DbPoolParam, scrape_text_for_mentions, + DbPool, + LemmyError, MentionData, }; use activitystreams::{ @@ -35,9 +38,7 @@ use activitystreams::{ object::{kind::NoteType, properties::ObjectProperties, Note}, }; use activitystreams_new::object::Tombstone; -use actix_web::{body::Body, web::Path, HttpResponse, Result}; -use diesel::PgConnection; -use failure::Error; +use actix_web::{body::Body, client::Client, web::Path, HttpResponse}; use itertools::Itertools; use log::debug; use serde::Deserialize; @@ -51,32 +52,41 @@ pub struct CommentQuery { pub async fn get_apub_comment( info: Path, db: DbPoolParam, -) -> Result, Error> { +) -> Result, LemmyError> { let id = info.comment_id.parse::()?; - let comment = Comment::read(&&db.get()?, id)?; + let comment = blocking(&db, move |conn| Comment::read(conn, id)).await??; + if !comment.deleted { - Ok(create_apub_response(&comment.to_apub(&db.get().unwrap())?)) + Ok(create_apub_response(&comment.to_apub(&db).await?)) } else { Ok(create_apub_tombstone_response(&comment.to_tombstone()?)) } } +#[async_trait::async_trait(?Send)] impl ToApub for Comment { type Response = Note; - fn to_apub(&self, conn: &PgConnection) -> Result { + async fn to_apub(&self, pool: &DbPool) -> Result { let mut comment = Note::default(); let oprops: &mut ObjectProperties = comment.as_mut(); - let creator = User_::read(&conn, self.creator_id)?; - let post = Post::read(&conn, self.post_id)?; - let community = Community::read(&conn, post.community_id)?; + + let creator_id = self.creator_id; + let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??; + + let post_id = self.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + + let community_id = post.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; // Add a vector containing some important info to the "in_reply_to" field // [post_ap_id, Option(parent_comment_ap_id)] let mut in_reply_to_vec = vec![post.ap_id]; if let Some(parent_id) = self.parent_id { - let parent_comment = Comment::read(&conn, parent_id)?; + let parent_comment = blocking(pool, move |conn| Comment::read(conn, parent_id)).await??; + in_reply_to_vec.push(parent_comment.ap_id); } @@ -97,7 +107,7 @@ impl ToApub for Comment { Ok(comment) } - fn to_tombstone(&self) -> Result { + fn to_tombstone(&self) -> Result { create_tombstone( self.deleted, &self.ap_id, @@ -107,27 +117,34 @@ impl ToApub for Comment { } } +#[async_trait::async_trait(?Send)] impl FromApub for CommentForm { type ApubType = Note; /// Parse an ActivityPub note received from another instance into a Lemmy comment - fn from_apub(note: &Note, conn: &PgConnection) -> Result { + async fn from_apub( + note: &Note, + client: &Client, + pool: &DbPool, + ) -> Result { let oprops = ¬e.object_props; let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string(); - let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, &conn)?; + + let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, client, pool).await?; let mut in_reply_tos = oprops.get_many_in_reply_to_xsd_any_uris().unwrap(); let post_ap_id = in_reply_tos.next().unwrap().to_string(); // This post, or the parent comment might not yet exist on this server yet, fetch them. - let post = get_or_fetch_and_insert_remote_post(&post_ap_id, &conn)?; + let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?; // The 2nd item, if it exists, is the parent comment apub_id // For deeply nested comments, FromApub automatically gets called recursively let parent_id: Option = match in_reply_tos.next() { Some(parent_comment_uri) => { let parent_comment_ap_id = &parent_comment_uri.to_string(); - let parent_comment = get_or_fetch_and_insert_remote_comment(&parent_comment_ap_id, &conn)?; + let parent_comment = + get_or_fetch_and_insert_remote_comment(&parent_comment_ap_id, client, pool).await?; Some(parent_comment.id) } @@ -157,17 +174,27 @@ impl FromApub for CommentForm { } } +#[async_trait::async_trait(?Send)] impl ApubObjectType for Comment { /// Send out information about a newly created comment, to the followers of the community. - fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let note = self.to_apub(conn)?; - let post = Post::read(&conn, self.post_id)?; - let community = Community::read(conn, post.community_id)?; + async fn send_create( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let note = self.to_apub(pool).await?; + + let post_id = self.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + + let community_id = post.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + + let maa = + collect_non_local_mentions_and_addresses(&self.content, &community, client, pool).await?; + let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4()); - - let maa: MentionsAndAddresses = - collect_non_local_mentions_and_addresses(&conn, &self.content, &community)?; - let mut create = Create::new(); populate_object_props(&mut create.object_props, maa.addressed_ccs, &id)?; @@ -179,20 +206,29 @@ impl ApubObjectType for Comment { .set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_object_base_box(note)?; - send_activity_to_community(&creator, &conn, &community, maa.inboxes, create)?; + send_activity_to_community(&creator, &community, maa.inboxes, create, client, pool).await?; Ok(()) } /// Send out information about an edited post, to the followers of the community. - fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let note = self.to_apub(&conn)?; - let post = Post::read(&conn, self.post_id)?; - let community = Community::read(&conn, post.community_id)?; + async fn send_update( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let note = self.to_apub(pool).await?; + + let post_id = self.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + + let community_id = post.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + + let maa = + collect_non_local_mentions_and_addresses(&self.content, &community, client, pool).await?; + let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4()); - - let maa: MentionsAndAddresses = - collect_non_local_mentions_and_addresses(&conn, &self.content, &community)?; - let mut update = Update::new(); populate_object_props(&mut update.object_props, maa.addressed_ccs, &id)?; @@ -204,14 +240,24 @@ impl ApubObjectType for Comment { .set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_object_base_box(note)?; - send_activity_to_community(&creator, &conn, &community, maa.inboxes, update)?; + send_activity_to_community(&creator, &community, maa.inboxes, update, client, pool).await?; Ok(()) } - fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let note = self.to_apub(&conn)?; - let post = Post::read(&conn, self.post_id)?; - let community = Community::read(&conn, post.community_id)?; + async fn send_delete( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let note = self.to_apub(pool).await?; + + let post_id = self.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + + let community_id = post.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4()); let mut delete = Delete::default(); @@ -228,18 +274,29 @@ impl ApubObjectType for Comment { send_activity_to_community( &creator, - &conn, &community, vec![community.get_shared_inbox_url()], delete, - )?; + client, + pool, + ) + .await?; Ok(()) } - fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let note = self.to_apub(&conn)?; - let post = Post::read(&conn, self.post_id)?; - let community = Community::read(&conn, post.community_id)?; + async fn send_undo_delete( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let note = self.to_apub(pool).await?; + + let post_id = self.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + + let community_id = post.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; // Generate a fake delete activity, with the correct object let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4()); @@ -274,18 +331,30 @@ impl ApubObjectType for Comment { send_activity_to_community( &creator, - &conn, &community, vec![community.get_shared_inbox_url()], undo, - )?; + client, + pool, + ) + .await?; Ok(()) } - fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> { - let note = self.to_apub(&conn)?; - let post = Post::read(&conn, self.post_id)?; - let community = Community::read(&conn, post.community_id)?; + async fn send_remove( + &self, + mod_: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let note = self.to_apub(pool).await?; + + let post_id = self.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + + let community_id = post.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4()); let mut remove = Remove::default(); @@ -302,18 +371,29 @@ impl ApubObjectType for Comment { send_activity_to_community( &mod_, - &conn, &community, vec![community.get_shared_inbox_url()], remove, - )?; + client, + pool, + ) + .await?; Ok(()) } - fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> { - let note = self.to_apub(&conn)?; - let post = Post::read(&conn, self.post_id)?; - let community = Community::read(&conn, post.community_id)?; + async fn send_undo_remove( + &self, + mod_: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let note = self.to_apub(pool).await?; + + let post_id = self.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + + let community_id = post.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; // Generate a fake delete activity, with the correct object let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4()); @@ -347,20 +427,33 @@ impl ApubObjectType for Comment { send_activity_to_community( &mod_, - &conn, &community, vec![community.get_shared_inbox_url()], undo, - )?; + client, + pool, + ) + .await?; Ok(()) } } +#[async_trait::async_trait(?Send)] impl ApubLikeableType for Comment { - fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let note = self.to_apub(&conn)?; - let post = Post::read(&conn, self.post_id)?; - let community = Community::read(&conn, post.community_id)?; + async fn send_like( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let note = self.to_apub(pool).await?; + + let post_id = self.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + + let community_id = post.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4()); let mut like = Like::new(); @@ -376,18 +469,30 @@ impl ApubLikeableType for Comment { send_activity_to_community( &creator, - &conn, &community, vec![community.get_shared_inbox_url()], like, - )?; + client, + pool, + ) + .await?; Ok(()) } - fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let note = self.to_apub(&conn)?; - let post = Post::read(&conn, self.post_id)?; - let community = Community::read(&conn, post.community_id)?; + async fn send_dislike( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let note = self.to_apub(pool).await?; + + let post_id = self.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + + let community_id = post.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4()); let mut dislike = Dislike::new(); @@ -403,18 +508,30 @@ impl ApubLikeableType for Comment { send_activity_to_community( &creator, - &conn, &community, vec![community.get_shared_inbox_url()], dislike, - )?; + client, + pool, + ) + .await?; Ok(()) } - fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let note = self.to_apub(&conn)?; - let post = Post::read(&conn, self.post_id)?; - let community = Community::read(&conn, post.community_id)?; + async fn send_undo_like( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let note = self.to_apub(pool).await?; + + let post_id = self.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + + let community_id = post.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4()); let mut like = Like::new(); @@ -446,11 +563,13 @@ impl ApubLikeableType for Comment { send_activity_to_community( &creator, - &conn, &community, vec![community.get_shared_inbox_url()], undo, - )?; + client, + pool, + ) + .await?; Ok(()) } } @@ -464,11 +583,12 @@ struct MentionsAndAddresses { /// This takes a comment, and builds a list of to_addresses, inboxes, /// and mention tags, so they know where to be sent to. /// Addresses are the users / addresses that go in the cc field. -fn collect_non_local_mentions_and_addresses( - conn: &PgConnection, +async fn collect_non_local_mentions_and_addresses( content: &str, community: &Community, -) -> Result { + client: &Client, + pool: &DbPool, +) -> Result { let mut addressed_ccs = vec![community.get_followers_url()]; // Add the mention tag @@ -480,14 +600,17 @@ fn collect_non_local_mentions_and_addresses( // Filter only the non-local ones .filter(|m| !m.is_local()) .collect::>(); + let mut mention_inboxes = Vec::new(); for mention in &mentions { // TODO should it be fetching it every time? - if let Ok(actor_id) = fetch_webfinger_url(mention) { + if let Ok(actor_id) = fetch_webfinger_url(mention, client).await { debug!("mention actor_id: {}", actor_id); addressed_ccs.push(actor_id.to_owned()); - let mention_user = get_or_fetch_and_upsert_remote_user(&actor_id, &conn)?; + + let mention_user = get_or_fetch_and_upsert_remote_user(&actor_id, client, pool).await?; let shared_inbox = mention_user.get_shared_inbox_url(); + mention_inboxes.push(shared_inbox); let mut mention_tag = Mention::new(); mention_tag diff --git a/server/src/apub/community.rs b/server/src/apub/community.rs index 8c8c3b280..f866511c8 100644 --- a/server/src/apub/community.rs +++ b/server/src/apub/community.rs @@ -12,6 +12,7 @@ use crate::{ GroupExt, ToApub, }, + blocking, convert_datetime, db::{ activity::insert_activity, @@ -21,6 +22,8 @@ use crate::{ }, naive_now, routes::DbPoolParam, + DbPool, + LemmyError, }; use activitystreams::{ activity::{Accept, Announce, Delete, Remove, Undo}, @@ -35,22 +38,22 @@ use activitystreams::{ }; use activitystreams_ext::Ext3; use activitystreams_new::{activity::Follow, object::Tombstone}; -use actix_web::{body::Body, web::Path, HttpResponse, Result}; -use diesel::PgConnection; -use failure::{Error, _core::fmt::Debug}; +use actix_web::{body::Body, client::Client, web, HttpResponse}; use itertools::Itertools; use serde::{Deserialize, Serialize}; +use std::fmt::Debug; #[derive(Deserialize)] pub struct CommunityQuery { community_name: String, } +#[async_trait::async_trait(?Send)] impl ToApub for Community { type Response = GroupExt; // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network. - fn to_apub(&self, conn: &PgConnection) -> Result { + async fn to_apub(&self, pool: &DbPool) -> Result { let mut group = Group::default(); let oprops: &mut ObjectProperties = group.as_mut(); @@ -58,10 +61,12 @@ impl ToApub for Community { // then the rest of the moderators // TODO Technically the instance admins can mod the community, but lets // ignore that for now - let moderators = CommunityModeratorView::for_community(&conn, self.id)? - .into_iter() - .map(|m| m.user_actor_id) - .collect(); + let id = self.id; + let moderators = blocking(pool, move |conn| { + CommunityModeratorView::for_community(&conn, id) + }) + .await??; + let moderators = moderators.into_iter().map(|m| m.user_actor_id).collect(); oprops .set_context_xsd_any_uri(context())? @@ -92,7 +97,12 @@ impl ToApub for Community { .set_endpoints(endpoint_props)? .set_followers(self.get_followers_url())?; - let group_extension = GroupExtension::new(conn, self.category_id, self.nsfw)?; + let nsfw = self.nsfw; + let category_id = self.category_id; + let group_extension = blocking(pool, move |conn| { + GroupExtension::new(conn, category_id, nsfw) + }) + .await??; Ok(Ext3::new( group, @@ -102,7 +112,7 @@ impl ToApub for Community { )) } - fn to_tombstone(&self) -> Result { + fn to_tombstone(&self) -> Result { create_tombstone( self.deleted, &self.actor_id, @@ -112,6 +122,7 @@ impl ToApub for Community { } } +#[async_trait::async_trait(?Send)] impl ActorType for Community { fn actor_id(&self) -> String { self.actor_id.to_owned() @@ -125,7 +136,12 @@ impl ActorType for Community { } /// As a local community, accept the follow request from a remote user. - fn send_accept_follow(&self, follow: &Follow, conn: &PgConnection) -> Result<(), Error> { + async fn send_accept_follow( + &self, + follow: &Follow, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { let actor_uri = follow.actor.as_single_xsd_any_uri().unwrap().to_string(); let id = format!("{}/accept/{}", self.actor_id, uuid::Uuid::new_v4()); @@ -140,14 +156,20 @@ impl ActorType for Community { .set_object_base_box(BaseBox::from_concrete(follow.clone())?)?; let to = format!("{}/inbox", actor_uri); - insert_activity(&conn, self.creator_id, &accept, true)?; + insert_activity(self.creator_id, accept.clone(), true, pool).await?; - send_activity(&accept, self, vec![to])?; + send_activity(client, &accept, self, vec![to]).await?; Ok(()) } - fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let group = self.to_apub(conn)?; + async fn send_delete( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let group = self.to_apub(pool).await?; + let id = format!("{}/delete/{}", self.actor_id, uuid::Uuid::new_v4()); let mut delete = Delete::default(); @@ -162,17 +184,25 @@ impl ActorType for Community { .set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_object_base_box(BaseBox::from_concrete(group)?)?; - insert_activity(&conn, self.creator_id, &delete, true)?; + insert_activity(self.creator_id, delete.clone(), true, pool).await?; + + let inboxes = self.get_follower_inboxes(pool).await?; // Note: For an accept, since it was automatic, no one pushed a button, // the community was the actor. // But for delete, the creator is the actor, and does the signing - send_activity(&delete, creator, self.get_follower_inboxes(&conn)?)?; + send_activity(client, &delete, creator, inboxes).await?; Ok(()) } - fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let group = self.to_apub(conn)?; + async fn send_undo_delete( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let group = self.to_apub(pool).await?; + let id = format!("{}/delete/{}", self.actor_id, uuid::Uuid::new_v4()); let mut delete = Delete::default(); @@ -203,17 +233,25 @@ impl ActorType for Community { .set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_object_base_box(delete)?; - insert_activity(&conn, self.creator_id, &undo, true)?; + insert_activity(self.creator_id, undo.clone(), true, pool).await?; + + let inboxes = self.get_follower_inboxes(pool).await?; // Note: For an accept, since it was automatic, no one pushed a button, // the community was the actor. // But for delete, the creator is the actor, and does the signing - send_activity(&undo, creator, self.get_follower_inboxes(&conn)?)?; + send_activity(client, &undo, creator, inboxes).await?; Ok(()) } - fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> { - let group = self.to_apub(conn)?; + async fn send_remove( + &self, + mod_: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let group = self.to_apub(pool).await?; + let id = format!("{}/remove/{}", self.actor_id, uuid::Uuid::new_v4()); let mut remove = Remove::default(); @@ -228,17 +266,25 @@ impl ActorType for Community { .set_actor_xsd_any_uri(mod_.actor_id.to_owned())? .set_object_base_box(BaseBox::from_concrete(group)?)?; - insert_activity(&conn, mod_.id, &remove, true)?; + insert_activity(mod_.id, remove.clone(), true, pool).await?; + + let inboxes = self.get_follower_inboxes(pool).await?; // Note: For an accept, since it was automatic, no one pushed a button, // the community was the actor. // But for delete, the creator is the actor, and does the signing - send_activity(&remove, mod_, self.get_follower_inboxes(&conn)?)?; + send_activity(client, &remove, mod_, inboxes).await?; Ok(()) } - fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> { - let group = self.to_apub(conn)?; + async fn send_undo_remove( + &self, + mod_: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let group = self.to_apub(pool).await?; + let id = format!("{}/remove/{}", self.actor_id, uuid::Uuid::new_v4()); let mut remove = Remove::default(); @@ -268,51 +314,69 @@ impl ActorType for Community { .set_actor_xsd_any_uri(mod_.actor_id.to_owned())? .set_object_base_box(remove)?; - insert_activity(&conn, mod_.id, &undo, true)?; + insert_activity(mod_.id, undo.clone(), true, pool).await?; + + let inboxes = self.get_follower_inboxes(pool).await?; // Note: For an accept, since it was automatic, no one pushed a button, // the community was the actor. // But for remove , the creator is the actor, and does the signing - send_activity(&undo, mod_, self.get_follower_inboxes(&conn)?)?; + send_activity(client, &undo, mod_, inboxes).await?; Ok(()) } /// For a given community, returns the inboxes of all followers. - fn get_follower_inboxes(&self, conn: &PgConnection) -> Result, Error> { - Ok( - CommunityFollowerView::for_community(conn, self.id)? - .into_iter() - .map(|c| get_shared_inbox(&c.user_actor_id)) - .filter(|s| !s.is_empty()) - .unique() - .collect(), - ) + async fn get_follower_inboxes(&self, pool: &DbPool) -> Result, LemmyError> { + let id = self.id; + + let inboxes = blocking(pool, move |conn| { + CommunityFollowerView::for_community(conn, id) + }) + .await??; + let inboxes = inboxes + .into_iter() + .map(|c| get_shared_inbox(&c.user_actor_id)) + .filter(|s| !s.is_empty()) + .unique() + .collect(); + + Ok(inboxes) } - fn send_follow(&self, _follow_actor_id: &str, _conn: &PgConnection) -> Result<(), Error> { + async fn send_follow( + &self, + _follow_actor_id: &str, + _client: &Client, + _pool: &DbPool, + ) -> Result<(), LemmyError> { unimplemented!() } - fn send_unfollow(&self, _follow_actor_id: &str, _conn: &PgConnection) -> Result<(), Error> { + async fn send_unfollow( + &self, + _follow_actor_id: &str, + _client: &Client, + _pool: &DbPool, + ) -> Result<(), LemmyError> { unimplemented!() } } +#[async_trait::async_trait(?Send)] impl FromApub for CommunityForm { type ApubType = GroupExt; /// Parse an ActivityPub group received from another instance into a Lemmy community. - fn from_apub(group: &GroupExt, conn: &PgConnection) -> Result { + async fn from_apub(group: &GroupExt, client: &Client, pool: &DbPool) -> Result { let group_extensions: &GroupExtension = &group.ext_one; let oprops = &group.inner.object_props; let aprops = &group.ext_two; let public_key: &PublicKey = &group.ext_three.public_key; let mut creator_and_moderator_uris = oprops.get_many_attributed_to_xsd_any_uris().unwrap(); - let creator = creator_and_moderator_uris - .next() - .map(|c| get_or_fetch_and_upsert_remote_user(&c.to_string(), &conn).unwrap()) - .unwrap(); + let creator_uri = creator_and_moderator_uris.next().unwrap(); + + let creator = get_or_fetch_and_upsert_remote_user(creator_uri.as_str(), client, pool).await?; Ok(CommunityForm { name: oprops.get_name_xsd_string().unwrap().to_string(), @@ -342,14 +406,18 @@ impl FromApub for CommunityForm { /// Return the community json over HTTP. pub async fn get_apub_community_http( - info: Path, + info: web::Path, db: DbPoolParam, -) -> Result, Error> { - let community = Community::read_from_name(&&db.get()?, &info.community_name)?; +) -> Result, LemmyError> { + let community = blocking(&db, move |conn| { + Community::read_from_name(conn, &info.community_name) + }) + .await??; + if !community.deleted { - Ok(create_apub_response( - &community.to_apub(&db.get().unwrap())?, - )) + let apub = community.to_apub(&db).await?; + + Ok(create_apub_response(&apub)) } else { Ok(create_apub_tombstone_response(&community.to_tombstone()?)) } @@ -357,15 +425,19 @@ pub async fn get_apub_community_http( /// Returns an empty followers collection, only populating the size (for privacy). pub async fn get_apub_community_followers( - info: Path, + info: web::Path, db: DbPoolParam, -) -> Result, Error> { - let community = Community::read_from_name(&&db.get()?, &info.community_name)?; +) -> Result, LemmyError> { + let community = blocking(&db, move |conn| { + Community::read_from_name(&conn, &info.community_name) + }) + .await??; - let conn = db.get()?; - - //As we are an object, we validated that the community id was valid - let community_followers = CommunityFollowerView::for_community(&conn, community.id).unwrap(); + let community_id = community.id; + let community_followers = blocking(&db, move |conn| { + CommunityFollowerView::for_community(&conn, community_id) + }) + .await??; let mut collection = UnorderedCollection::default(); let oprops: &mut ObjectProperties = collection.as_mut(); @@ -379,12 +451,13 @@ pub async fn get_apub_community_followers( } impl Community { - pub fn do_announce( + pub async fn do_announce( activity: A, community: &Community, sender: &dyn ActorType, - conn: &PgConnection, - ) -> Result + client: &Client, + pool: &DbPool, + ) -> Result where A: Activity + Base + Serialize + Debug, { @@ -399,15 +472,16 @@ impl Community { .set_actor_xsd_any_uri(community.actor_id.to_owned())? .set_object_base_box(BaseBox::from_concrete(activity)?)?; - insert_activity(&conn, community.creator_id, &announce, true)?; + insert_activity(community.creator_id, announce.clone(), true, pool).await?; // dont send to the instance where the activity originally came from, because that would result // in a database error (same data inserted twice) - let mut to = community.get_follower_inboxes(&conn)?; + let mut to = community.get_follower_inboxes(pool).await?; + // this seems to be the "easiest" stable alternative for remove_item() to.retain(|x| *x != sender.get_shared_inbox_url()); - send_activity(&announce, community, to)?; + send_activity(client, &announce, community, to).await?; Ok(HttpResponse::Ok().finish()) } diff --git a/server/src/apub/community_inbox.rs b/server/src/apub/community_inbox.rs index 975f26877..996e0c251 100644 --- a/server/src/apub/community_inbox.rs +++ b/server/src/apub/community_inbox.rs @@ -4,6 +4,7 @@ use crate::{ fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user}, ActorType, }, + blocking, db::{ activity::insert_activity, community::{Community, CommunityFollower, CommunityFollowerForm}, @@ -11,14 +12,14 @@ use crate::{ Followable, }, routes::{ChatServerParam, DbPoolParam}, + LemmyError, }; use activitystreams::activity::Undo; use activitystreams_new::activity::Follow; -use actix_web::{web, HttpRequest, HttpResponse, Result}; -use diesel::PgConnection; -use failure::{Error, _core::fmt::Debug}; +use actix_web::{client::Client, web, HttpRequest, HttpResponse}; use log::debug; use serde::Deserialize; +use std::fmt::Debug; #[serde(untagged)] #[derive(Deserialize, Debug)] @@ -28,7 +29,7 @@ pub enum CommunityAcceptedObjects { } impl CommunityAcceptedObjects { - fn follow(&self) -> Result { + fn follow(&self) -> Result { match self { CommunityAcceptedObjects::Follow(f) => Ok(f.to_owned()), CommunityAcceptedObjects::Undo(u) => Ok( @@ -49,16 +50,22 @@ pub async fn community_inbox( input: web::Json, path: web::Path, db: DbPoolParam, + client: web::Data, _chat_server: ChatServerParam, -) -> Result { +) -> Result { let input = input.into_inner(); - let conn = db.get()?; - let community = Community::read_from_name(&conn, &path.into_inner())?; + + let path = path.into_inner(); + let community = blocking(&db, move |conn| Community::read_from_name(&conn, &path)).await??; + if !community.local { - return Err(format_err!( - "Received activity is addressed to remote community {}", - &community.actor_id - )); + return Err( + format_err!( + "Received activity is addressed to remote community {}", + &community.actor_id + ) + .into(), + ); } debug!( "Community {} received activity {:?}", @@ -68,28 +75,27 @@ pub async fn community_inbox( let user_uri = follow.actor.as_single_xsd_any_uri().unwrap().to_string(); let community_uri = follow.object.as_single_xsd_any_uri().unwrap().to_string(); - let conn = db.get()?; - - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; - let community = get_or_fetch_and_upsert_remote_community(&community_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, &client, &db).await?; + let community = get_or_fetch_and_upsert_remote_community(&community_uri, &client, &db).await?; verify(&request, &user)?; match input { - CommunityAcceptedObjects::Follow(f) => handle_follow(&f, &user, &community, &conn), - CommunityAcceptedObjects::Undo(u) => handle_undo_follow(&u, &user, &community, &conn), + CommunityAcceptedObjects::Follow(f) => handle_follow(f, user, community, &client, db).await, + CommunityAcceptedObjects::Undo(u) => handle_undo_follow(u, user, community, db).await, } } /// Handle a follow request from a remote user, adding it to the local database and returning an /// Accept activity. -fn handle_follow( - follow: &Follow, - user: &User_, - community: &Community, - conn: &PgConnection, -) -> Result { - insert_activity(&conn, user.id, &follow, false)?; +async fn handle_follow( + follow: Follow, + user: User_, + community: Community, + client: &Client, + db: DbPoolParam, +) -> Result { + insert_activity(user.id, follow.clone(), false, &db).await?; let community_follower_form = CommunityFollowerForm { community_id: community.id, @@ -97,27 +103,34 @@ fn handle_follow( }; // This will fail if they're already a follower, but ignore the error. - CommunityFollower::follow(&conn, &community_follower_form).ok(); + blocking(&db, move |conn| { + CommunityFollower::follow(&conn, &community_follower_form).ok() + }) + .await?; - community.send_accept_follow(&follow, &conn)?; + community.send_accept_follow(&follow, &client, &db).await?; Ok(HttpResponse::Ok().finish()) } -fn handle_undo_follow( - undo: &Undo, - user: &User_, - community: &Community, - conn: &PgConnection, -) -> Result { - insert_activity(&conn, user.id, &undo, false)?; +async fn handle_undo_follow( + undo: Undo, + user: User_, + community: Community, + db: DbPoolParam, +) -> Result { + insert_activity(user.id, undo, false, &db).await?; let community_follower_form = CommunityFollowerForm { community_id: community.id, user_id: user.id, }; - CommunityFollower::unfollow(&conn, &community_follower_form).ok(); + // This will fail if they aren't a follower, but ignore the error. + blocking(&db, move |conn| { + CommunityFollower::unfollow(&conn, &community_follower_form).ok() + }) + .await?; Ok(HttpResponse::Ok().finish()) } diff --git a/server/src/apub/extensions/group_extensions.rs b/server/src/apub/extensions/group_extensions.rs index ece97706c..1c24eef57 100644 --- a/server/src/apub/extensions/group_extensions.rs +++ b/server/src/apub/extensions/group_extensions.rs @@ -1,7 +1,9 @@ -use crate::db::{category::Category, Crud}; +use crate::{ + db::{category::Category, Crud}, + LemmyError, +}; use activitystreams::{ext::Extension, Actor}; use diesel::PgConnection; -use failure::Error; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Serialize)] @@ -24,7 +26,7 @@ impl GroupExtension { conn: &PgConnection, category_id: i32, sensitive: bool, - ) -> Result { + ) -> Result { let category = Category::read(conn, category_id)?; let group_category = GroupCategory { identifier: category_id.to_string(), diff --git a/server/src/apub/extensions/signatures.rs b/server/src/apub/extensions/signatures.rs index 4156f0b3f..af46bc5ee 100644 --- a/server/src/apub/extensions/signatures.rs +++ b/server/src/apub/extensions/signatures.rs @@ -1,9 +1,10 @@ -use crate::apub::ActorType; +use crate::{apub::ActorType, LemmyError}; use activitystreams::ext::Extension; -use actix_web::HttpRequest; -use failure::Error; -use http::request::Builder; -use http_signature_normalization::Config; +use actix_web::{client::ClientRequest, HttpRequest}; +use http_signature_normalization_actix::{ + digest::{DigestClient, SignExt}, + Config, +}; use log::debug; use openssl::{ hash::MessageDigest, @@ -12,7 +13,7 @@ use openssl::{ sign::{Signer, Verifier}, }; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; +use sha2::{Digest, Sha256}; lazy_static! { static ref HTTP_SIG_CONFIG: Config = Config::new(); @@ -24,7 +25,7 @@ pub struct Keypair { } /// Generate the asymmetric keypair for ActivityPub HTTP signatures. -pub fn generate_actor_keypair() -> Result { +pub fn generate_actor_keypair() -> Result { let rsa = Rsa::generate(2048)?; let pkey = PKey::from_rsa(rsa)?; let public_key = pkey.public_key_to_pem()?; @@ -36,56 +37,41 @@ pub fn generate_actor_keypair() -> Result { } /// Signs request headers with the given keypair. -pub fn sign(request: &Builder, actor: &dyn ActorType) -> Result { +pub async fn sign( + request: ClientRequest, + actor: &dyn ActorType, + activity: String, +) -> Result, LemmyError> { let signing_key_id = format!("{}#main-key", actor.actor_id()); + let private_key = actor.private_key(); - let headers = request - .headers_ref() - .unwrap() - .iter() - .map(|h| -> Result<(String, String), Error> { - Ok((h.0.as_str().to_owned(), h.1.to_str()?.to_owned())) - }) - .collect::, Error>>()?; + let digest_client = request + .signature_with_digest( + HTTP_SIG_CONFIG.clone(), + signing_key_id, + Sha256::new(), + activity, + move |signing_string| { + let private_key = PKey::private_key_from_pem(private_key.as_bytes())?; + let mut signer = Signer::new(MessageDigest::sha256(), &private_key).unwrap(); + signer.update(signing_string.as_bytes()).unwrap(); - let signature_header_value = HTTP_SIG_CONFIG - .begin_sign( - request.method_ref().unwrap().as_str(), - request - .uri_ref() - .unwrap() - .path_and_query() - .unwrap() - .as_str(), - headers, - )? - .sign(signing_key_id, |signing_string| { - let private_key = PKey::private_key_from_pem(actor.private_key().as_bytes())?; - let mut signer = Signer::new(MessageDigest::sha256(), &private_key).unwrap(); - signer.update(signing_string.as_bytes()).unwrap(); - Ok(base64::encode(signer.sign_to_vec()?)) as Result<_, Error> - })? - .signature_header(); + Ok(base64::encode(signer.sign_to_vec()?)) as Result<_, LemmyError> + }, + ) + .await?; - Ok(signature_header_value) + Ok(digest_client) } -pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), Error> { - let headers = request - .headers() - .iter() - .map(|h| -> Result<(String, String), Error> { - Ok((h.0.as_str().to_owned(), h.1.to_str()?.to_owned())) - }) - .collect::, Error>>()?; - +pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), LemmyError> { let verified = HTTP_SIG_CONFIG .begin_verify( - request.method().as_str(), - request.uri().path_and_query().unwrap().as_str(), - headers, + request.method(), + request.uri().path_and_query(), + request.headers().clone(), )? - .verify(|signature, signing_string| -> Result { + .verify(|signature, signing_string| -> Result { debug!( "Verifying with key {}, message {}", &actor.public_key(), @@ -101,10 +87,7 @@ pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), Error> debug!("verified signature for {}", &request.uri()); Ok(()) } else { - Err(format_err!( - "Invalid signature on request: {}", - &request.uri() - )) + Err(format_err!("Invalid signature on request: {}", &request.uri()).into()) } } diff --git a/server/src/apub/fetcher.rs b/server/src/apub/fetcher.rs index 7f7a3f971..598903d0c 100644 --- a/server/src/apub/fetcher.rs +++ b/server/src/apub/fetcher.rs @@ -1,15 +1,14 @@ use activitystreams::object::Note; -use actix_web::Result; +use actix_web::client::Client; use diesel::{result::Error::NotFound, PgConnection}; -use failure::{Error, _core::fmt::Debug}; -use isahc::prelude::*; use log::debug; use serde::Deserialize; -use std::time::Duration; +use std::{fmt::Debug, time::Duration}; use url::Url; use crate::{ api::site::SearchResponse, + blocking, db::{ comment::{Comment, CommentForm}, comment_view::CommentView, @@ -23,7 +22,10 @@ use crate::{ SearchType, }, naive_now, + request::{retry, RecvError}, routes::nodeinfo::{NodeInfo, NodeInfoWellKnown}, + DbPool, + LemmyError, }; use crate::{ @@ -43,36 +45,50 @@ use chrono::NaiveDateTime; static ACTOR_REFETCH_INTERVAL_SECONDS: i64 = 24 * 60 * 60; // Fetch nodeinfo metadata from a remote instance. -fn _fetch_node_info(domain: &str) -> Result { +async fn _fetch_node_info(client: &Client, domain: &str) -> Result { let well_known_uri = Url::parse(&format!( "{}://{}/.well-known/nodeinfo", get_apub_protocol_string(), domain ))?; - let well_known = fetch_remote_object::(&well_known_uri)?; - Ok(fetch_remote_object::(&well_known.links.href)?) + + let well_known = fetch_remote_object::(client, &well_known_uri).await?; + let nodeinfo = fetch_remote_object::(client, &well_known.links.href).await?; + + Ok(nodeinfo) } /// Fetch any type of ActivityPub object, handling things like HTTP headers, deserialisation, /// timeouts etc. -pub fn fetch_remote_object(url: &Url) -> Result +pub async fn fetch_remote_object( + client: &Client, + url: &Url, +) -> Result where Response: for<'de> Deserialize<'de>, { if !is_apub_id_valid(&url) { - return Err(format_err!("Activitypub uri invalid or blocked: {}", url)); + return Err(format_err!("Activitypub uri invalid or blocked: {}", url).into()); } - // TODO: this function should return a future + let timeout = Duration::from_secs(60); - let text = Request::get(url.as_str()) - .header("Accept", APUB_JSON_CONTENT_TYPE) - .connect_timeout(timeout) - .timeout(timeout) - .body(())? - .send()? - .text()?; - let res: Response = serde_json::from_str(&text)?; - Ok(res) + + let json = retry(|| { + client + .get(url.as_str()) + .header("Accept", APUB_JSON_CONTENT_TYPE) + .timeout(timeout) + .send() + }) + .await? + .json() + .await + .map_err(|e| { + debug!("Receive error, {}", e); + RecvError(e.to_string()) + })?; + + Ok(json) } /// The types of ActivityPub objects that can be fetched directly by searching for their ID. @@ -92,7 +108,11 @@ pub enum SearchAcceptedObjects { /// http://lemmy_alpha:8540/u/lemmy_alpha, or @lemmy_alpha@lemmy_alpha:8540 /// http://lemmy_alpha:8540/post/3 /// http://lemmy_alpha:8540/comment/2 -pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result { +pub async fn search_by_apub_id( + query: &str, + client: &Client, + pool: &DbPool, +) -> Result { // Parse the shorthand query url let query_url = if query.contains('@') { debug!("{}", query); @@ -107,10 +127,10 @@ pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result>(); (format!("/c/{}", split2[1]), split[1]) } else { - return Err(format_err!("Invalid search query: {}", query)); + return Err(format_err!("Invalid search query: {}", query).into()); } } else { - return Err(format_err!("Invalid search query: {}", query)); + return Err(format_err!("Invalid search query: {}", query).into()); }; let url = format!("{}://{}{}", get_apub_protocol_string(), instance, name); @@ -126,22 +146,41 @@ pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result(&query_url)? { + + let response = match fetch_remote_object::(client, &query_url).await? { SearchAcceptedObjects::Person(p) => { let user_uri = p.inner.object_props.get_id().unwrap().to_string(); - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; - response.users = vec![UserView::read(conn, user.id)?]; + + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; + + response.users = vec![blocking(pool, move |conn| UserView::read(conn, user.id)).await??]; + + response } SearchAcceptedObjects::Group(g) => { let community_uri = g.inner.object_props.get_id().unwrap().to_string(); - let community = get_or_fetch_and_upsert_remote_community(&community_uri, &conn)?; + + let community = + get_or_fetch_and_upsert_remote_community(&community_uri, client, pool).await?; + // TODO Maybe at some point in the future, fetch all the history of a community // fetch_community_outbox(&c, conn)?; - response.communities = vec![CommunityView::read(conn, community.id, None)?]; + response.communities = vec![ + blocking(pool, move |conn| { + CommunityView::read(conn, community.id, None) + }) + .await??, + ]; + + response } SearchAcceptedObjects::Page(p) => { - let p = upsert_post(&PostForm::from_apub(&p, conn)?, conn)?; - response.posts = vec![PostView::read(conn, p.id, None)?]; + let post_form = PostForm::from_apub(&p, client, pool).await?; + + let p = blocking(pool, move |conn| upsert_post(&post_form, conn)).await??; + response.posts = vec![blocking(pool, move |conn| PostView::read(conn, p.id, None)).await??]; + + response } SearchAcceptedObjects::Comment(c) => { let post_url = c @@ -151,41 +190,59 @@ pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result Result { - match User_::read_from_actor_id(&conn, &apub_id) { - Ok(u) => { - // If its older than a day, re-fetch it - if !u.local && should_refetch_actor(u.last_refreshed_at) { - debug!("Fetching and updating from remote user: {}", apub_id); - let person = fetch_remote_object::(&Url::parse(apub_id)?)?; - let mut uf = UserForm::from_apub(&person, &conn)?; - uf.last_refreshed_at = Some(naive_now()); - Ok(User_::update(&conn, u.id, &uf)?) - } else { - Ok(u) - } + client: &Client, + pool: &DbPool, +) -> Result { + let apub_id_owned = apub_id.to_owned(); + let user = blocking(pool, move |conn| { + User_::read_from_actor_id(conn, &apub_id_owned) + }) + .await?; + + match user { + // If its older than a day, re-fetch it + Ok(u) if !u.local && should_refetch_actor(u.last_refreshed_at) => { + debug!("Fetching and updating from remote user: {}", apub_id); + let person = fetch_remote_object::(client, &Url::parse(apub_id)?).await?; + + let mut uf = UserForm::from_apub(&person, client, pool).await?; + uf.last_refreshed_at = Some(naive_now()); + let user = blocking(pool, move |conn| User_::update(conn, u.id, &uf)).await??; + + Ok(user) } + Ok(u) => Ok(u), Err(NotFound {}) => { debug!("Fetching and creating remote user: {}", apub_id); - let person = fetch_remote_object::(&Url::parse(apub_id)?)?; - let uf = UserForm::from_apub(&person, &conn)?; - Ok(User_::create(conn, &uf)?) + let person = fetch_remote_object::(client, &Url::parse(apub_id)?).await?; + + let uf = UserForm::from_apub(&person, client, pool).await?; + let user = blocking(pool, move |conn| User_::create(conn, &uf)).await??; + + Ok(user) } - Err(e) => Err(Error::from(e)), + Err(e) => Err(e.into()), } } @@ -204,27 +261,35 @@ fn should_refetch_actor(last_refreshed: NaiveDateTime) -> bool { } /// Check if a remote community exists, create if not found, if its too old update it.Fetch a community, insert/update it in the database and return the community. -pub fn get_or_fetch_and_upsert_remote_community( +pub async fn get_or_fetch_and_upsert_remote_community( apub_id: &str, - conn: &PgConnection, -) -> Result { - match Community::read_from_actor_id(&conn, &apub_id) { - Ok(c) => { - if !c.local && should_refetch_actor(c.last_refreshed_at) { - debug!("Fetching and updating from remote community: {}", apub_id); - let group = fetch_remote_object::(&Url::parse(apub_id)?)?; - let mut cf = CommunityForm::from_apub(&group, conn)?; - cf.last_refreshed_at = Some(naive_now()); - Ok(Community::update(&conn, c.id, &cf)?) - } else { - Ok(c) - } + client: &Client, + pool: &DbPool, +) -> Result { + let apub_id_owned = apub_id.to_owned(); + let community = blocking(pool, move |conn| { + Community::read_from_actor_id(conn, &apub_id_owned) + }) + .await?; + + match community { + Ok(c) if !c.local && should_refetch_actor(c.last_refreshed_at) => { + debug!("Fetching and updating from remote community: {}", apub_id); + let group = fetch_remote_object::(client, &Url::parse(apub_id)?).await?; + + let mut cf = CommunityForm::from_apub(&group, client, pool).await?; + cf.last_refreshed_at = Some(naive_now()); + let community = blocking(pool, move |conn| Community::update(conn, c.id, &cf)).await??; + + Ok(community) } + Ok(c) => Ok(c), Err(NotFound {}) => { debug!("Fetching and creating remote community: {}", apub_id); - let group = fetch_remote_object::(&Url::parse(apub_id)?)?; - let cf = CommunityForm::from_apub(&group, conn)?; - let community = Community::create(conn, &cf)?; + let group = fetch_remote_object::(client, &Url::parse(apub_id)?).await?; + + let cf = CommunityForm::from_apub(&group, client, pool).await?; + let community = blocking(pool, move |conn| Community::create(conn, &cf)).await??; // Also add the community moderators too let creator_and_moderator_uris = group @@ -232,74 +297,105 @@ pub fn get_or_fetch_and_upsert_remote_community( .object_props .get_many_attributed_to_xsd_any_uris() .unwrap(); - let creator_and_moderators = creator_and_moderator_uris - .map(|c| get_or_fetch_and_upsert_remote_user(&c.to_string(), &conn).unwrap()) - .collect::>(); - for mod_ in creator_and_moderators { - let community_moderator_form = CommunityModeratorForm { - community_id: community.id, - user_id: mod_.id, - }; - CommunityModerator::join(&conn, &community_moderator_form)?; + let mut creator_and_moderators = Vec::new(); + + for uri in creator_and_moderator_uris { + let c_or_m = get_or_fetch_and_upsert_remote_user(uri.as_str(), client, pool).await?; + + creator_and_moderators.push(c_or_m); } + let community_id = community.id; + blocking(pool, move |conn| { + for mod_ in creator_and_moderators { + let community_moderator_form = CommunityModeratorForm { + community_id, + user_id: mod_.id, + }; + + CommunityModerator::join(conn, &community_moderator_form)?; + } + Ok(()) as Result<(), LemmyError> + }) + .await??; + Ok(community) } - Err(e) => Err(Error::from(e)), + Err(e) => Err(e.into()), } } -fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result { +fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result { let existing = Post::read_from_apub_id(conn, &post_form.ap_id); match existing { Err(NotFound {}) => Ok(Post::create(conn, &post_form)?), Ok(p) => Ok(Post::update(conn, p.id, &post_form)?), - Err(e) => Err(Error::from(e)), + Err(e) => Err(e.into()), } } -pub fn get_or_fetch_and_insert_remote_post( +pub async fn get_or_fetch_and_insert_remote_post( post_ap_id: &str, - conn: &PgConnection, -) -> Result { - match Post::read_from_apub_id(conn, post_ap_id) { + client: &Client, + pool: &DbPool, +) -> Result { + let post_ap_id_owned = post_ap_id.to_owned(); + let post = blocking(pool, move |conn| { + Post::read_from_apub_id(conn, &post_ap_id_owned) + }) + .await?; + + match post { Ok(p) => Ok(p), Err(NotFound {}) => { debug!("Fetching and creating remote post: {}", post_ap_id); - let post = fetch_remote_object::(&Url::parse(post_ap_id)?)?; - let post_form = PostForm::from_apub(&post, conn)?; - Ok(Post::create(conn, &post_form)?) + let post = fetch_remote_object::(client, &Url::parse(post_ap_id)?).await?; + let post_form = PostForm::from_apub(&post, client, pool).await?; + + let post = blocking(pool, move |conn| Post::create(conn, &post_form)).await??; + + Ok(post) } - Err(e) => Err(Error::from(e)), + Err(e) => Err(e.into()), } } -fn upsert_comment(comment_form: &CommentForm, conn: &PgConnection) -> Result { +fn upsert_comment(comment_form: &CommentForm, conn: &PgConnection) -> Result { let existing = Comment::read_from_apub_id(conn, &comment_form.ap_id); match existing { Err(NotFound {}) => Ok(Comment::create(conn, &comment_form)?), Ok(p) => Ok(Comment::update(conn, p.id, &comment_form)?), - Err(e) => Err(Error::from(e)), + Err(e) => Err(e.into()), } } -pub fn get_or_fetch_and_insert_remote_comment( +pub async fn get_or_fetch_and_insert_remote_comment( comment_ap_id: &str, - conn: &PgConnection, -) -> Result { - match Comment::read_from_apub_id(conn, comment_ap_id) { + client: &Client, + pool: &DbPool, +) -> Result { + let comment_ap_id_owned = comment_ap_id.to_owned(); + let comment = blocking(pool, move |conn| { + Comment::read_from_apub_id(conn, &comment_ap_id_owned) + }) + .await?; + + match comment { Ok(p) => Ok(p), Err(NotFound {}) => { debug!( "Fetching and creating remote comment and its parents: {}", comment_ap_id ); - let comment = fetch_remote_object::(&Url::parse(comment_ap_id)?)?; - let comment_form = CommentForm::from_apub(&comment, conn)?; - Ok(Comment::create(conn, &comment_form)?) + let comment = fetch_remote_object::(client, &Url::parse(comment_ap_id)?).await?; + let comment_form = CommentForm::from_apub(&comment, client, pool).await?; + + let comment = blocking(pool, move |conn| Comment::create(conn, &comment_form)).await??; + + Ok(comment) } - Err(e) => Err(Error::from(e)), + Err(e) => Err(e.into()), } } @@ -309,7 +405,7 @@ pub fn get_or_fetch_and_insert_remote_comment( // maybe), is community and user actors // and user actors // Fetch all posts in the outbox of the given user, and insert them into the database. -// fn fetch_community_outbox(community: &Community, conn: &PgConnection) -> Result, Error> { +// fn fetch_community_outbox(community: &Community, conn: &PgConnection) -> Result, LemmyError> { // let outbox_url = Url::parse(&community.get_outbox_url())?; // let outbox = fetch_remote_object::(&outbox_url)?; // let items = outbox.collection_props.get_many_items_base_boxes(); @@ -317,11 +413,11 @@ pub fn get_or_fetch_and_insert_remote_comment( // Ok( // items // .unwrap() -// .map(|obox: &BaseBox| -> Result { +// .map(|obox: &BaseBox| -> Result { // let page = obox.clone().to_concrete::()?; // PostForm::from_page(&page, conn) // }) // .map(|pf| upsert_post(&pf?, conn)) -// .collect::, Error>>()?, +// .collect::, LemmyError>>()?, // ) // } diff --git a/server/src/apub/mod.rs b/server/src/apub/mod.rs index 6a2d6cffb..90df87342 100644 --- a/server/src/apub/mod.rs +++ b/server/src/apub/mod.rs @@ -18,7 +18,10 @@ use crate::{ }, convert_datetime, db::user::User_, + request::{retry, RecvError}, routes::webfinger::WebFingerResponse, + DbPool, + LemmyError, MentionData, Settings, }; @@ -28,11 +31,8 @@ use activitystreams::{ }; use activitystreams_ext::{Ext1, Ext2, Ext3}; use activitystreams_new::{activity::Follow, object::Tombstone, prelude::*}; -use actix_web::{body::Body, HttpResponse, Result}; +use actix_web::{body::Body, client::Client, HttpResponse}; use chrono::NaiveDateTime; -use diesel::PgConnection; -use failure::Error; -use isahc::prelude::*; use log::debug; use serde::Serialize; use url::Url; @@ -101,7 +101,9 @@ pub fn get_apub_protocol_string() -> &'static str { // Checks if the ID has a valid format, correct scheme, and is in the allowed instance list. fn is_apub_id_valid(apub_id: &Url) -> bool { + debug!("Checking {}", apub_id); if apub_id.scheme() != get_apub_protocol_string() { + debug!("invalid scheme: {:?}", apub_id.scheme()); return false; } @@ -112,15 +114,27 @@ fn is_apub_id_valid(apub_id: &Url) -> bool { .map(|d| d.to_string()) .collect(); match apub_id.domain() { - Some(d) => allowed_instances.contains(&d.to_owned()), - None => false, + Some(d) => { + let contains = allowed_instances.contains(&d.to_owned()); + + if !contains { + debug!("{} not in {:?}", d, allowed_instances); + } + + contains + } + None => { + debug!("missing domain"); + false + } } } +#[async_trait::async_trait(?Send)] pub trait ToApub { type Response; - fn to_apub(&self, conn: &PgConnection) -> Result; - fn to_tombstone(&self) -> Result; + async fn to_apub(&self, pool: &DbPool) -> Result; + fn to_tombstone(&self) -> Result; } /// Updated is actually the deletion time @@ -129,7 +143,7 @@ fn create_tombstone( object_id: &str, updated: Option, former_type: String, -) -> Result { +) -> Result { if deleted { if let Some(updated) = updated { let mut tombstone = Tombstone::new(); @@ -138,37 +152,85 @@ fn create_tombstone( tombstone.set_deleted(convert_datetime(updated).into()); Ok(tombstone) } else { - Err(format_err!( - "Cant convert to tombstone because updated time was None." - )) + Err(format_err!("Cant convert to tombstone because updated time was None.").into()) } } else { - Err(format_err!( - "Cant convert object to tombstone if it wasnt deleted" - )) + Err(format_err!("Cant convert object to tombstone if it wasnt deleted").into()) } } +#[async_trait::async_trait(?Send)] pub trait FromApub { type ApubType; - fn from_apub(apub: &Self::ApubType, conn: &PgConnection) -> Result + async fn from_apub( + apub: &Self::ApubType, + client: &Client, + pool: &DbPool, + ) -> Result where Self: Sized; } +#[async_trait::async_trait(?Send)] pub trait ApubObjectType { - fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; - fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; - fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; - fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; - fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>; - fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>; + async fn send_create( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; + async fn send_update( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; + async fn send_delete( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; + async fn send_undo_delete( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; + async fn send_remove( + &self, + mod_: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; + async fn send_undo_remove( + &self, + mod_: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; } +#[async_trait::async_trait(?Send)] pub trait ApubLikeableType { - fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; - fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; - fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; + async fn send_like( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; + async fn send_dislike( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; + async fn send_undo_like( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; } pub fn get_shared_inbox(actor_id: &str) -> String { @@ -185,6 +247,7 @@ pub fn get_shared_inbox(actor_id: &str) -> String { ) } +#[async_trait::async_trait(?Send)] pub trait ActorType { fn actor_id(&self) -> String; @@ -194,20 +257,55 @@ pub trait ActorType { // These two have default impls, since currently a community can't follow anything, // and a user can't be followed (yet) #[allow(unused_variables)] - fn send_follow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error>; - fn send_unfollow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error>; + async fn send_follow( + &self, + follow_actor_id: &str, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; + async fn send_unfollow( + &self, + follow_actor_id: &str, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; #[allow(unused_variables)] - fn send_accept_follow(&self, follow: &Follow, conn: &PgConnection) -> Result<(), Error>; + async fn send_accept_follow( + &self, + follow: &Follow, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; - fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; - fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; + async fn send_delete( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; + async fn send_undo_delete( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; - fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>; - fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>; + async fn send_remove( + &self, + mod_: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; + async fn send_undo_remove( + &self, + mod_: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError>; /// For a given community, returns the inboxes of all followers. - fn get_follower_inboxes(&self, conn: &PgConnection) -> Result, Error>; + async fn get_follower_inboxes(&self, pool: &DbPool) -> Result, LemmyError>; // TODO move these to the db rows fn get_inbox_url(&self) -> String { @@ -244,7 +342,10 @@ pub trait ActorType { } } -pub fn fetch_webfinger_url(mention: &MentionData) -> Result { +pub async fn fetch_webfinger_url( + mention: &MentionData, + client: &Client, +) -> Result { let fetch_url = format!( "{}://{}/.well-known/webfinger?resource=acct:{}@{}", get_apub_protocol_string(), @@ -253,8 +354,14 @@ pub fn fetch_webfinger_url(mention: &MentionData) -> Result { mention.domain ); debug!("Fetching webfinger url: {}", &fetch_url); - let text = isahc::get(&fetch_url)?.text()?; - let res: WebFingerResponse = serde_json::from_str(&text)?; + + let mut response = retry(|| client.get(&fetch_url).send()).await?; + + let res: WebFingerResponse = response + .json() + .await + .map_err(|e| RecvError(e.to_string()))?; + let link = res .links .iter() @@ -263,5 +370,5 @@ pub fn fetch_webfinger_url(mention: &MentionData) -> Result { link .href .to_owned() - .ok_or_else(|| format_err!("No href found.")) + .ok_or_else(|| format_err!("No href found.").into()) } diff --git a/server/src/apub/post.rs b/server/src/apub/post.rs index 3f86d34d1..60cb0b557 100644 --- a/server/src/apub/post.rs +++ b/server/src/apub/post.rs @@ -14,6 +14,7 @@ use crate::{ PageExt, ToApub, }, + blocking, convert_datetime, db::{ community::Community, @@ -22,6 +23,8 @@ use crate::{ Crud, }, routes::DbPoolParam, + DbPool, + LemmyError, Settings, }; use activitystreams::{ @@ -32,9 +35,7 @@ use activitystreams::{ }; use activitystreams_ext::Ext1; use activitystreams_new::object::Tombstone; -use actix_web::{body::Body, web::Path, HttpResponse, Result}; -use diesel::PgConnection; -use failure::Error; +use actix_web::{body::Body, client::Client, web, HttpResponse}; use serde::Deserialize; #[derive(Deserialize)] @@ -44,27 +45,33 @@ pub struct PostQuery { /// Return the post json over HTTP. pub async fn get_apub_post( - info: Path, + info: web::Path, db: DbPoolParam, -) -> Result, Error> { +) -> Result, LemmyError> { let id = info.post_id.parse::()?; - let post = Post::read(&&db.get()?, id)?; + let post = blocking(&db, move |conn| Post::read(conn, id)).await??; + if !post.deleted { - Ok(create_apub_response(&post.to_apub(&db.get().unwrap())?)) + Ok(create_apub_response(&post.to_apub(&db).await?)) } else { Ok(create_apub_tombstone_response(&post.to_tombstone()?)) } } +#[async_trait::async_trait(?Send)] impl ToApub for Post { type Response = PageExt; // Turn a Lemmy post into an ActivityPub page that can be sent out over the network. - fn to_apub(&self, conn: &PgConnection) -> Result { + async fn to_apub(&self, pool: &DbPool) -> Result { let mut page = Page::default(); let oprops: &mut ObjectProperties = page.as_mut(); - let creator = User_::read(conn, self.creator_id)?; - let community = Community::read(conn, self.community_id)?; + + let creator_id = self.creator_id; + let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??; + + let community_id = self.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; oprops // Not needed when the Post is embedded in a collection (like for community outbox) @@ -141,7 +148,7 @@ impl ToApub for Post { Ok(Ext1::new(page, ext)) } - fn to_tombstone(&self) -> Result { + fn to_tombstone(&self) -> Result { create_tombstone( self.deleted, &self.ap_id, @@ -151,17 +158,26 @@ impl ToApub for Post { } } +#[async_trait::async_trait(?Send)] impl FromApub for PostForm { type ApubType = PageExt; /// Parse an ActivityPub page received from another instance into a Lemmy post. - fn from_apub(page: &PageExt, conn: &PgConnection) -> Result { + async fn from_apub( + page: &PageExt, + client: &Client, + pool: &DbPool, + ) -> Result { let ext = &page.ext_one; let oprops = &page.inner.object_props; let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string(); - let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, &conn)?; + + let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, client, pool).await?; + let community_actor_id = &oprops.get_to_xsd_any_uri().unwrap().to_string(); - let community = get_or_fetch_and_upsert_remote_community(&community_actor_id, &conn)?; + + let community = + get_or_fetch_and_upsert_remote_community(&community_actor_id, client, pool).await?; let thumbnail_url = match oprops.get_image_any_image() { Some(any_image) => any_image @@ -221,11 +237,20 @@ impl FromApub for PostForm { } } +#[async_trait::async_trait(?Send)] impl ApubObjectType for Post { /// Send out information about a newly created post, to the followers of the community. - fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let page = self.to_apub(conn)?; - let community = Community::read(conn, self.community_id)?; + async fn send_create( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let page = self.to_apub(pool).await?; + + let community_id = self.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4()); let mut create = Create::new(); @@ -241,18 +266,28 @@ impl ApubObjectType for Post { send_activity_to_community( creator, - conn, &community, vec![community.get_shared_inbox_url()], create, - )?; + client, + pool, + ) + .await?; Ok(()) } /// Send out information about an edited post, to the followers of the community. - fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let page = self.to_apub(conn)?; - let community = Community::read(conn, self.community_id)?; + async fn send_update( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let page = self.to_apub(pool).await?; + + let community_id = self.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4()); let mut update = Update::new(); @@ -268,17 +303,27 @@ impl ApubObjectType for Post { send_activity_to_community( creator, - conn, &community, vec![community.get_shared_inbox_url()], update, - )?; + client, + pool, + ) + .await?; Ok(()) } - fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let page = self.to_apub(conn)?; - let community = Community::read(conn, self.community_id)?; + async fn send_delete( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let page = self.to_apub(pool).await?; + + let community_id = self.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4()); let mut delete = Delete::default(); @@ -293,21 +338,29 @@ impl ApubObjectType for Post { .set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_object_base_box(BaseBox::from_concrete(page)?)?; - let community = Community::read(conn, self.community_id)?; - send_activity_to_community( creator, - conn, &community, vec![community.get_shared_inbox_url()], delete, - )?; + client, + pool, + ) + .await?; Ok(()) } - fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let page = self.to_apub(conn)?; - let community = Community::read(conn, self.community_id)?; + async fn send_undo_delete( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let page = self.to_apub(pool).await?; + + let community_id = self.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4()); let mut delete = Delete::default(); @@ -338,20 +391,29 @@ impl ApubObjectType for Post { .set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_object_base_box(delete)?; - let community = Community::read(conn, self.community_id)?; send_activity_to_community( creator, - conn, &community, vec![community.get_shared_inbox_url()], undo, - )?; + client, + pool, + ) + .await?; Ok(()) } - fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> { - let page = self.to_apub(conn)?; - let community = Community::read(conn, self.community_id)?; + async fn send_remove( + &self, + mod_: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let page = self.to_apub(pool).await?; + + let community_id = self.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4()); let mut remove = Remove::default(); @@ -366,20 +428,29 @@ impl ApubObjectType for Post { .set_actor_xsd_any_uri(mod_.actor_id.to_owned())? .set_object_base_box(BaseBox::from_concrete(page)?)?; - let community = Community::read(conn, self.community_id)?; - send_activity_to_community( mod_, - conn, &community, vec![community.get_shared_inbox_url()], remove, - )?; + client, + pool, + ) + .await?; Ok(()) } - fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> { - let page = self.to_apub(conn)?; - let community = Community::read(conn, self.community_id)?; + + async fn send_undo_remove( + &self, + mod_: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let page = self.to_apub(pool).await?; + + let community_id = self.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4()); let mut remove = Remove::default(); @@ -409,22 +480,32 @@ impl ApubObjectType for Post { .set_actor_xsd_any_uri(mod_.actor_id.to_owned())? .set_object_base_box(remove)?; - let community = Community::read(conn, self.community_id)?; send_activity_to_community( mod_, - conn, &community, vec![community.get_shared_inbox_url()], undo, - )?; + client, + pool, + ) + .await?; Ok(()) } } +#[async_trait::async_trait(?Send)] impl ApubLikeableType for Post { - fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let page = self.to_apub(conn)?; - let community = Community::read(conn, self.community_id)?; + async fn send_like( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let page = self.to_apub(pool).await?; + + let community_id = self.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4()); let mut like = Like::new(); @@ -440,17 +521,27 @@ impl ApubLikeableType for Post { send_activity_to_community( &creator, - &conn, &community, vec![community.get_shared_inbox_url()], like, - )?; + client, + pool, + ) + .await?; Ok(()) } - fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let page = self.to_apub(conn)?; - let community = Community::read(conn, self.community_id)?; + async fn send_dislike( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let page = self.to_apub(pool).await?; + + let community_id = self.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4()); let mut dislike = Dislike::new(); @@ -466,17 +557,27 @@ impl ApubLikeableType for Post { send_activity_to_community( &creator, - &conn, &community, vec![community.get_shared_inbox_url()], dislike, - )?; + client, + pool, + ) + .await?; Ok(()) } - fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let page = self.to_apub(conn)?; - let community = Community::read(conn, self.community_id)?; + async fn send_undo_like( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let page = self.to_apub(pool).await?; + + let community_id = self.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4()); let mut like = Like::new(); @@ -508,11 +609,13 @@ impl ApubLikeableType for Post { send_activity_to_community( &creator, - &conn, &community, vec![community.get_shared_inbox_url()], undo, - )?; + client, + pool, + ) + .await?; Ok(()) } } diff --git a/server/src/apub/private_message.rs b/server/src/apub/private_message.rs index a700043ba..ae4c36267 100644 --- a/server/src/apub/private_message.rs +++ b/server/src/apub/private_message.rs @@ -7,6 +7,7 @@ use crate::{ FromApub, ToApub, }, + blocking, convert_datetime, db::{ activity::insert_activity, @@ -14,6 +15,8 @@ use crate::{ user::User_, Crud, }, + DbPool, + LemmyError, }; use activitystreams::{ activity::{Create, Delete, Undo, Update}, @@ -21,18 +24,21 @@ use activitystreams::{ object::{kind::NoteType, properties::ObjectProperties, Note}, }; use activitystreams_new::object::Tombstone; -use actix_web::Result; -use diesel::PgConnection; -use failure::Error; +use actix_web::client::Client; +#[async_trait::async_trait(?Send)] impl ToApub for PrivateMessage { type Response = Note; - fn to_apub(&self, conn: &PgConnection) -> Result { + async fn to_apub(&self, pool: &DbPool) -> Result { let mut private_message = Note::default(); let oprops: &mut ObjectProperties = private_message.as_mut(); - let creator = User_::read(&conn, self.creator_id)?; - let recipient = User_::read(&conn, self.recipient_id)?; + + let creator_id = self.creator_id; + let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??; + + let recipient_id = self.recipient_id; + let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??; oprops .set_context_xsd_any_uri(context())? @@ -49,7 +55,7 @@ impl ToApub for PrivateMessage { Ok(private_message) } - fn to_tombstone(&self) -> Result { + fn to_tombstone(&self) -> Result { create_tombstone( self.deleted, &self.ap_id, @@ -59,16 +65,24 @@ impl ToApub for PrivateMessage { } } +#[async_trait::async_trait(?Send)] impl FromApub for PrivateMessageForm { type ApubType = Note; /// Parse an ActivityPub note received from another instance into a Lemmy Private message - fn from_apub(note: &Note, conn: &PgConnection) -> Result { + async fn from_apub( + note: &Note, + client: &Client, + pool: &DbPool, + ) -> Result { let oprops = ¬e.object_props; let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string(); - let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, &conn)?; + + let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, client, pool).await?; + let recipient_actor_id = &oprops.get_to_xsd_any_uri().unwrap().to_string(); - let recipient = get_or_fetch_and_upsert_remote_user(&recipient_actor_id, &conn)?; + + let recipient = get_or_fetch_and_upsert_remote_user(&recipient_actor_id, client, pool).await?; Ok(PrivateMessageForm { creator_id: creator.id, @@ -91,12 +105,20 @@ impl FromApub for PrivateMessageForm { } } +#[async_trait::async_trait(?Send)] impl ApubObjectType for PrivateMessage { /// Send out information about a newly created private message - fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let note = self.to_apub(conn)?; + async fn send_create( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let note = self.to_apub(pool).await?; let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4()); - let recipient = User_::read(&conn, self.recipient_id)?; + + let recipient_id = self.recipient_id; + let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??; let mut create = Create::new(); create @@ -110,17 +132,24 @@ impl ApubObjectType for PrivateMessage { .set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_object_base_box(note)?; - insert_activity(&conn, creator.id, &create, true)?; + insert_activity(creator.id, create.clone(), true, pool).await?; - send_activity(&create, creator, vec![to])?; + send_activity(client, &create, creator, vec![to]).await?; Ok(()) } /// Send out information about an edited post, to the followers of the community. - fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let note = self.to_apub(conn)?; + async fn send_update( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let note = self.to_apub(pool).await?; let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4()); - let recipient = User_::read(&conn, self.recipient_id)?; + + let recipient_id = self.recipient_id; + let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??; let mut update = Update::new(); update @@ -134,16 +163,23 @@ impl ApubObjectType for PrivateMessage { .set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_object_base_box(note)?; - insert_activity(&conn, creator.id, &update, true)?; + insert_activity(creator.id, update.clone(), true, pool).await?; - send_activity(&update, creator, vec![to])?; + send_activity(client, &update, creator, vec![to]).await?; Ok(()) } - fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let note = self.to_apub(conn)?; + async fn send_delete( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let note = self.to_apub(pool).await?; let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4()); - let recipient = User_::read(&conn, self.recipient_id)?; + + let recipient_id = self.recipient_id; + let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??; let mut delete = Delete::new(); delete @@ -157,16 +193,23 @@ impl ApubObjectType for PrivateMessage { .set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_object_base_box(note)?; - insert_activity(&conn, creator.id, &delete, true)?; + insert_activity(creator.id, delete.clone(), true, pool).await?; - send_activity(&delete, creator, vec![to])?; + send_activity(client, &delete, creator, vec![to]).await?; Ok(()) } - fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let note = self.to_apub(conn)?; + async fn send_undo_delete( + &self, + creator: &User_, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { + let note = self.to_apub(pool).await?; let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4()); - let recipient = User_::read(&conn, self.recipient_id)?; + + let recipient_id = self.recipient_id; + let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??; let mut delete = Delete::new(); delete @@ -195,17 +238,27 @@ impl ApubObjectType for PrivateMessage { .set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_object_base_box(delete)?; - insert_activity(&conn, creator.id, &undo, true)?; + insert_activity(creator.id, undo.clone(), true, pool).await?; - send_activity(&undo, creator, vec![to])?; + send_activity(client, &undo, creator, vec![to]).await?; Ok(()) } - fn send_remove(&self, _mod_: &User_, _conn: &PgConnection) -> Result<(), Error> { + async fn send_remove( + &self, + _mod_: &User_, + _client: &Client, + _pool: &DbPool, + ) -> Result<(), LemmyError> { unimplemented!() } - fn send_undo_remove(&self, _mod_: &User_, _conn: &PgConnection) -> Result<(), Error> { + async fn send_undo_remove( + &self, + _mod_: &User_, + _client: &Client, + _pool: &DbPool, + ) -> Result<(), LemmyError> { unimplemented!() } } diff --git a/server/src/apub/shared_inbox.rs b/server/src/apub/shared_inbox.rs index 1ada6ad1d..667732523 100644 --- a/server/src/apub/shared_inbox.rs +++ b/server/src/apub/shared_inbox.rs @@ -16,6 +16,7 @@ use crate::{ GroupExt, PageExt, }, + blocking, db::{ activity::insert_activity, comment::{Comment, CommentForm, CommentLike, CommentLikeForm}, @@ -34,6 +35,8 @@ use crate::{ server::{SendComment, SendCommunityRoomMessage, SendPost}, UserOperation, }, + DbPool, + LemmyError, }; use activitystreams::{ activity::{Announce, Create, Delete, Dislike, Like, Remove, Undo, Update}, @@ -42,11 +45,10 @@ use activitystreams::{ Base, BaseBox, }; -use actix_web::{web, HttpRequest, HttpResponse, Result}; -use diesel::PgConnection; -use failure::{Error, _core::fmt::Debug}; +use actix_web::{client::Client, web, HttpRequest, HttpResponse}; use log::debug; use serde::{Deserialize, Serialize}; +use std::fmt::Debug; #[serde(untagged)] #[derive(Serialize, Deserialize, Debug)] @@ -112,11 +114,13 @@ impl SharedAcceptedObjects { pub async fn shared_inbox( request: HttpRequest, input: web::Json, - db: DbPoolParam, + client: web::Data, + pool: DbPoolParam, chat_server: ChatServerParam, -) -> Result { +) -> Result { let activity = input.into_inner(); - let conn = &db.get().unwrap(); + let pool = &pool; + let client = &client; let json = serde_json::to_string(&activity)?; debug!("Shared inbox received activity: {}", json); @@ -128,112 +132,120 @@ pub async fn shared_inbox( let to = cc.replace("/followers", ""); // TODO: this is ugly - match get_or_fetch_and_upsert_remote_user(&sender.to_string(), &conn) { - Ok(u) => verify(&request, &u), + match get_or_fetch_and_upsert_remote_user(&sender.to_string(), &client, pool).await { + Ok(u) => verify(&request, &u)?, Err(_) => { - let c = get_or_fetch_and_upsert_remote_community(&sender.to_string(), &conn)?; - verify(&request, &c) + let c = get_or_fetch_and_upsert_remote_community(&sender.to_string(), &client, pool).await?; + verify(&request, &c)?; } - }?; + } match (activity, object.kind()) { (SharedAcceptedObjects::Create(c), Some("Page")) => { - receive_create_post(&c, &conn, chat_server)?; - announce_activity_if_valid::(*c, &to, sender, conn) + receive_create_post((*c).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*c, &to, sender, client, pool).await } (SharedAcceptedObjects::Update(u), Some("Page")) => { - receive_update_post(&u, &conn, chat_server)?; - announce_activity_if_valid::(*u, &to, sender, conn) + receive_update_post((*u).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*u, &to, sender, client, pool).await } (SharedAcceptedObjects::Like(l), Some("Page")) => { - receive_like_post(&l, &conn, chat_server)?; - announce_activity_if_valid::(*l, &to, sender, conn) + receive_like_post((*l).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*l, &to, sender, client, pool).await } (SharedAcceptedObjects::Dislike(d), Some("Page")) => { - receive_dislike_post(&d, &conn, chat_server)?; - announce_activity_if_valid::(*d, &to, sender, conn) + receive_dislike_post((*d).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*d, &to, sender, client, pool).await } (SharedAcceptedObjects::Delete(d), Some("Page")) => { - receive_delete_post(&d, &conn, chat_server)?; - announce_activity_if_valid::(*d, &to, sender, conn) + receive_delete_post((*d).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*d, &to, sender, client, pool).await } (SharedAcceptedObjects::Remove(r), Some("Page")) => { - receive_remove_post(&r, &conn, chat_server)?; - announce_activity_if_valid::(*r, &to, sender, conn) + receive_remove_post((*r).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*r, &to, sender, client, pool).await } (SharedAcceptedObjects::Create(c), Some("Note")) => { - receive_create_comment(&c, &conn, chat_server)?; - announce_activity_if_valid::(*c, &to, sender, conn) + receive_create_comment((*c).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*c, &to, sender, client, pool).await } (SharedAcceptedObjects::Update(u), Some("Note")) => { - receive_update_comment(&u, &conn, chat_server)?; - announce_activity_if_valid::(*u, &to, sender, conn) + receive_update_comment((*u).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*u, &to, sender, client, pool).await } (SharedAcceptedObjects::Like(l), Some("Note")) => { - receive_like_comment(&l, &conn, chat_server)?; - announce_activity_if_valid::(*l, &to, sender, conn) + receive_like_comment((*l).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*l, &to, sender, client, pool).await } (SharedAcceptedObjects::Dislike(d), Some("Note")) => { - receive_dislike_comment(&d, &conn, chat_server)?; - announce_activity_if_valid::(*d, &to, sender, conn) + receive_dislike_comment((*d).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*d, &to, sender, client, pool).await } (SharedAcceptedObjects::Delete(d), Some("Note")) => { - receive_delete_comment(&d, &conn, chat_server)?; - announce_activity_if_valid::(*d, &to, sender, conn) + receive_delete_comment((*d).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*d, &to, sender, client, pool).await } (SharedAcceptedObjects::Remove(r), Some("Note")) => { - receive_remove_comment(&r, &conn, chat_server)?; - announce_activity_if_valid::(*r, &to, sender, conn) + receive_remove_comment((*r).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*r, &to, sender, client, pool).await } (SharedAcceptedObjects::Delete(d), Some("Group")) => { - receive_delete_community(&d, &conn, chat_server)?; - announce_activity_if_valid::(*d, &to, sender, conn) + receive_delete_community((*d).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*d, &to, sender, client, pool).await } (SharedAcceptedObjects::Remove(r), Some("Group")) => { - receive_remove_community(&r, &conn, chat_server)?; - announce_activity_if_valid::(*r, &to, sender, conn) + receive_remove_community((*r).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*r, &to, sender, client, pool).await } (SharedAcceptedObjects::Undo(u), Some("Delete")) => { - receive_undo_delete(&u, &conn, chat_server)?; - announce_activity_if_valid::(*u, &to, sender, conn) + receive_undo_delete((*u).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*u, &to, sender, client, pool).await } (SharedAcceptedObjects::Undo(u), Some("Remove")) => { - receive_undo_remove(&u, &conn, chat_server)?; - announce_activity_if_valid::(*u, &to, sender, conn) + receive_undo_remove((*u).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*u, &to, sender, client, pool).await } (SharedAcceptedObjects::Undo(u), Some("Like")) => { - receive_undo_like(&u, &conn, chat_server)?; - announce_activity_if_valid::(*u, &to, sender, conn) + receive_undo_like((*u).clone(), client, pool, chat_server).await?; + announce_activity_if_valid::(*u, &to, sender, client, pool).await } - (SharedAcceptedObjects::Announce(a), _) => receive_announce(a, &conn, chat_server), + (SharedAcceptedObjects::Announce(a), _) => receive_announce(a, client, pool, chat_server).await, (a, _) => receive_unhandled_activity(a), } } // TODO: should pass in sender as ActorType, but thats a bit tricky in shared_inbox() -fn announce_activity_if_valid( +async fn announce_activity_if_valid( activity: A, community_uri: &str, sender: &str, - conn: &PgConnection, -) -> Result + client: &Client, + pool: &DbPool, +) -> Result where A: Activity + Base + Serialize + Debug, { - let community = Community::read_from_actor_id(conn, &community_uri)?; + let community_uri = community_uri.to_owned(); + let community = blocking(pool, move |conn| { + Community::read_from_actor_id(conn, &community_uri) + }) + .await??; + if community.local { - let sending_user = get_or_fetch_and_upsert_remote_user(&sender.to_string(), &conn)?; - Community::do_announce(activity, &community, &sending_user, conn) + let sending_user = get_or_fetch_and_upsert_remote_user(sender, client, pool).await?; + + Community::do_announce(activity, &community, &sending_user, client, pool).await } else { Ok(HttpResponse::NotFound().finish()) } } -fn receive_announce( +async fn receive_announce( announce: Box, - conn: &PgConnection, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let object = announce .announce_props .get_object_base_box() @@ -245,8 +257,8 @@ fn receive_announce( let create = object.into_concrete::()?; let inner_object = create.create_props.get_object_base_box().unwrap(); match inner_object.kind() { - Some("Page") => receive_create_post(&create, &conn, chat_server), - Some("Note") => receive_create_comment(&create, &conn, chat_server), + Some("Page") => receive_create_post(create, client, pool, chat_server).await, + Some("Note") => receive_create_comment(create, client, pool, chat_server).await, _ => receive_unhandled_activity(announce), } } @@ -254,8 +266,8 @@ fn receive_announce( let update = object.into_concrete::()?; let inner_object = update.update_props.get_object_base_box().unwrap(); match inner_object.kind() { - Some("Page") => receive_update_post(&update, &conn, chat_server), - Some("Note") => receive_update_comment(&update, &conn, chat_server), + Some("Page") => receive_update_post(update, client, pool, chat_server).await, + Some("Note") => receive_update_comment(update, client, pool, chat_server).await, _ => receive_unhandled_activity(announce), } } @@ -263,8 +275,8 @@ fn receive_announce( let like = object.into_concrete::()?; let inner_object = like.like_props.get_object_base_box().unwrap(); match inner_object.kind() { - Some("Page") => receive_like_post(&like, &conn, chat_server), - Some("Note") => receive_like_comment(&like, &conn, chat_server), + Some("Page") => receive_like_post(like, client, pool, chat_server).await, + Some("Note") => receive_like_comment(like, client, pool, chat_server).await, _ => receive_unhandled_activity(announce), } } @@ -272,8 +284,8 @@ fn receive_announce( let dislike = object.into_concrete::()?; let inner_object = dislike.dislike_props.get_object_base_box().unwrap(); match inner_object.kind() { - Some("Page") => receive_dislike_post(&dislike, &conn, chat_server), - Some("Note") => receive_dislike_comment(&dislike, &conn, chat_server), + Some("Page") => receive_dislike_post(dislike, client, pool, chat_server).await, + Some("Note") => receive_dislike_comment(dislike, client, pool, chat_server).await, _ => receive_unhandled_activity(announce), } } @@ -281,8 +293,8 @@ fn receive_announce( let delete = object.into_concrete::()?; let inner_object = delete.delete_props.get_object_base_box().unwrap(); match inner_object.kind() { - Some("Page") => receive_delete_post(&delete, &conn, chat_server), - Some("Note") => receive_delete_comment(&delete, &conn, chat_server), + Some("Page") => receive_delete_post(delete, client, pool, chat_server).await, + Some("Note") => receive_delete_comment(delete, client, pool, chat_server).await, _ => receive_unhandled_activity(announce), } } @@ -290,8 +302,8 @@ fn receive_announce( let remove = object.into_concrete::()?; let inner_object = remove.remove_props.get_object_base_box().unwrap(); match inner_object.kind() { - Some("Page") => receive_remove_post(&remove, &conn, chat_server), - Some("Note") => receive_remove_comment(&remove, &conn, chat_server), + Some("Page") => receive_remove_post(remove, client, pool, chat_server).await, + Some("Note") => receive_remove_comment(remove, client, pool, chat_server).await, _ => receive_unhandled_activity(announce), } } @@ -299,9 +311,9 @@ fn receive_announce( let undo = object.into_concrete::()?; let inner_object = undo.undo_props.get_object_base_box().unwrap(); match inner_object.kind() { - Some("Delete") => receive_undo_delete(&undo, &conn, chat_server), - Some("Remove") => receive_undo_remove(&undo, &conn, chat_server), - Some("Like") => receive_undo_like(&undo, &conn, chat_server), + Some("Delete") => receive_undo_delete(undo, client, pool, chat_server).await, + Some("Remove") => receive_undo_remove(undo, client, pool, chat_server).await, + Some("Like") => receive_undo_like(undo, client, pool, chat_server).await, _ => receive_unhandled_activity(announce), } } @@ -309,7 +321,7 @@ fn receive_announce( } } -fn receive_unhandled_activity(activity: A) -> Result +fn receive_unhandled_activity(activity: A) -> Result where A: Debug, { @@ -317,11 +329,12 @@ where Ok(HttpResponse::NotImplemented().finish()) } -fn receive_create_post( - create: &Create, - conn: &PgConnection, +async fn receive_create_post( + create: Create, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let page = create .create_props .get_object_base_box() @@ -336,15 +349,20 @@ fn receive_create_post( .unwrap() .to_string(); - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &create, false)?; + insert_activity(user.id, create, false, pool).await?; - let post = PostForm::from_apub(&page, &conn)?; - let inserted_post = Post::create(conn, &post)?; + let post = PostForm::from_apub(&page, client, pool).await?; + + let inserted_post = blocking(pool, move |conn| Post::create(conn, &post)).await??; // Refetch the view - let post_view = PostView::read(&conn, inserted_post.id, None)?; + let inserted_post_id = inserted_post.id; + let post_view = blocking(pool, move |conn| { + PostView::read(conn, inserted_post_id, None) + }) + .await??; let res = PostResponse { post: post_view }; @@ -357,11 +375,12 @@ fn receive_create_post( Ok(HttpResponse::Ok().finish()) } -fn receive_create_comment( - create: &Create, - conn: &PgConnection, +async fn receive_create_comment( + create: Create, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let note = create .create_props .get_object_base_box() @@ -376,23 +395,30 @@ fn receive_create_comment( .unwrap() .to_string(); - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &create, false)?; + insert_activity(user.id, create, false, pool).await?; - let comment = CommentForm::from_apub(¬e, &conn)?; - let inserted_comment = Comment::create(conn, &comment)?; - let post = Post::read(&conn, inserted_comment.post_id)?; + let comment = CommentForm::from_apub(¬e, client, pool).await?; + + let inserted_comment = blocking(pool, move |conn| Comment::create(conn, &comment)).await??; + + let post_id = inserted_comment.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; // Note: // Although mentions could be gotten from the post tags (they are included there), or the ccs, // Its much easier to scrape them from the comment body, since the API has to do that // anyway. let mentions = scrape_text_for_mentions(&inserted_comment.content); - let recipient_ids = send_local_notifs(&conn, &mentions, &inserted_comment, &user, &post); + let recipient_ids = + send_local_notifs(mentions, inserted_comment.clone(), user, post, pool).await?; // Refetch the view - let comment_view = CommentView::read(&conn, inserted_comment.id, None)?; + let comment_view = blocking(pool, move |conn| { + CommentView::read(conn, inserted_comment.id, None) + }) + .await??; let res = CommentResponse { comment: comment_view, @@ -408,11 +434,12 @@ fn receive_create_comment( Ok(HttpResponse::Ok().finish()) } -fn receive_update_post( - update: &Update, - conn: &PgConnection, +async fn receive_update_post( + update: Update, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let page = update .update_props .get_object_base_box() @@ -427,16 +454,20 @@ fn receive_update_post( .unwrap() .to_string(); - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &update, false)?; + insert_activity(user.id, update, false, pool).await?; - let post = PostForm::from_apub(&page, conn)?; - let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, &conn)?.id; - Post::update(conn, post_id, &post)?; + let post = PostForm::from_apub(&page, client, pool).await?; + + let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool) + .await? + .id; + + blocking(pool, move |conn| Post::update(conn, post_id, &post)).await??; // Refetch the view - let post_view = PostView::read(&conn, post_id, None)?; + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; let res = PostResponse { post: post_view }; @@ -449,12 +480,12 @@ fn receive_update_post( Ok(HttpResponse::Ok().finish()) } -fn receive_like_post( - like: &Like, - - conn: &PgConnection, +async fn receive_like_post( + like: Like, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let page = like .like_props .get_object_base_box() @@ -465,23 +496,29 @@ fn receive_like_post( let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string(); - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &like, false)?; + insert_activity(user.id, like, false, pool).await?; - let post = PostForm::from_apub(&page, conn)?; - let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, &conn)?.id; + let post = PostForm::from_apub(&page, client, pool).await?; + + let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool) + .await? + .id; let like_form = PostLikeForm { post_id, user_id: user.id, score: 1, }; - PostLike::remove(&conn, &like_form)?; - PostLike::like(&conn, &like_form)?; + blocking(pool, move |conn| { + PostLike::remove(conn, &like_form)?; + PostLike::like(conn, &like_form) + }) + .await??; // Refetch the view - let post_view = PostView::read(&conn, post_id, None)?; + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; let res = PostResponse { post: post_view }; @@ -494,12 +531,12 @@ fn receive_like_post( Ok(HttpResponse::Ok().finish()) } -fn receive_dislike_post( - dislike: &Dislike, - - conn: &PgConnection, +async fn receive_dislike_post( + dislike: Dislike, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let page = dislike .dislike_props .get_object_base_box() @@ -514,23 +551,29 @@ fn receive_dislike_post( .unwrap() .to_string(); - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &dislike, false)?; + insert_activity(user.id, dislike, false, pool).await?; - let post = PostForm::from_apub(&page, conn)?; - let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, &conn)?.id; + let post = PostForm::from_apub(&page, client, pool).await?; + + let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool) + .await? + .id; let like_form = PostLikeForm { post_id, user_id: user.id, score: -1, }; - PostLike::remove(&conn, &like_form)?; - PostLike::like(&conn, &like_form)?; + blocking(pool, move |conn| { + PostLike::remove(conn, &like_form)?; + PostLike::like(conn, &like_form) + }) + .await??; // Refetch the view - let post_view = PostView::read(&conn, post_id, None)?; + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; let res = PostResponse { post: post_view }; @@ -543,12 +586,12 @@ fn receive_dislike_post( Ok(HttpResponse::Ok().finish()) } -fn receive_update_comment( - update: &Update, - - conn: &PgConnection, +async fn receive_update_comment( + update: Update, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let note = update .update_props .get_object_base_box() @@ -563,20 +606,30 @@ fn receive_update_comment( .unwrap() .to_string(); - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &update, false)?; + insert_activity(user.id, update, false, pool).await?; - let comment = CommentForm::from_apub(¬e, &conn)?; - let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, &conn)?.id; - let updated_comment = Comment::update(conn, comment_id, &comment)?; - let post = Post::read(&conn, updated_comment.post_id)?; + let comment = CommentForm::from_apub(¬e, client, pool).await?; + + let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool) + .await? + .id; + + let updated_comment = blocking(pool, move |conn| { + Comment::update(conn, comment_id, &comment) + }) + .await??; + + let post_id = updated_comment.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; let mentions = scrape_text_for_mentions(&updated_comment.content); - let recipient_ids = send_local_notifs(&conn, &mentions, &updated_comment, &user, &post); + let recipient_ids = send_local_notifs(mentions, updated_comment, user, post, pool).await?; // Refetch the view - let comment_view = CommentView::read(&conn, comment_id, None)?; + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; let res = CommentResponse { comment: comment_view, @@ -592,12 +645,12 @@ fn receive_update_comment( Ok(HttpResponse::Ok().finish()) } -fn receive_like_comment( - like: &Like, - - conn: &PgConnection, +async fn receive_like_comment( + like: Like, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let note = like .like_props .get_object_base_box() @@ -608,23 +661,31 @@ fn receive_like_comment( let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string(); - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &like, false)?; + insert_activity(user.id, like, false, pool).await?; + + let comment = CommentForm::from_apub(¬e, client, pool).await?; + + let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool) + .await? + .id; - let comment = CommentForm::from_apub(¬e, &conn)?; - let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, &conn)?.id; let like_form = CommentLikeForm { comment_id, post_id: comment.post_id, user_id: user.id, score: 1, }; - CommentLike::remove(&conn, &like_form)?; - CommentLike::like(&conn, &like_form)?; + blocking(pool, move |conn| { + CommentLike::remove(conn, &like_form)?; + CommentLike::like(conn, &like_form) + }) + .await??; // Refetch the view - let comment_view = CommentView::read(&conn, comment_id, None)?; + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; // TODO get those recipient actor ids from somewhere let recipient_ids = vec![]; @@ -642,12 +703,12 @@ fn receive_like_comment( Ok(HttpResponse::Ok().finish()) } -fn receive_dislike_comment( - dislike: &Dislike, - - conn: &PgConnection, +async fn receive_dislike_comment( + dislike: Dislike, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let note = dislike .dislike_props .get_object_base_box() @@ -662,23 +723,31 @@ fn receive_dislike_comment( .unwrap() .to_string(); - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &dislike, false)?; + insert_activity(user.id, dislike, false, pool).await?; + + let comment = CommentForm::from_apub(¬e, client, pool).await?; + + let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool) + .await? + .id; - let comment = CommentForm::from_apub(¬e, &conn)?; - let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, &conn)?.id; let like_form = CommentLikeForm { comment_id, post_id: comment.post_id, user_id: user.id, score: -1, }; - CommentLike::remove(&conn, &like_form)?; - CommentLike::like(&conn, &like_form)?; + blocking(pool, move |conn| { + CommentLike::remove(conn, &like_form)?; + CommentLike::like(conn, &like_form) + }) + .await??; // Refetch the view - let comment_view = CommentView::read(&conn, comment_id, None)?; + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; // TODO get those recipient actor ids from somewhere let recipient_ids = vec![]; @@ -696,12 +765,12 @@ fn receive_dislike_comment( Ok(HttpResponse::Ok().finish()) } -fn receive_delete_community( - delete: &Delete, - - conn: &PgConnection, +async fn receive_delete_community( + delete: Delete, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let user_uri = delete .delete_props .get_actor_xsd_any_uri() @@ -716,12 +785,18 @@ fn receive_delete_community( .to_owned() .into_concrete::()?; - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &delete, false)?; + insert_activity(user.id, delete, false, pool).await?; - let community_actor_id = CommunityForm::from_apub(&group, &conn)?.actor_id; - let community = Community::read_from_actor_id(conn, &community_actor_id)?; + let community_actor_id = CommunityForm::from_apub(&group, client, pool) + .await? + .actor_id; + + let community = blocking(pool, move |conn| { + Community::read_from_actor_id(conn, &community_actor_id) + }) + .await??; let community_form = CommunityForm { name: community.name.to_owned(), @@ -741,28 +816,38 @@ fn receive_delete_community( last_refreshed_at: None, }; - Community::update(&conn, community.id, &community_form)?; + let community_id = community.id; + blocking(pool, move |conn| { + Community::update(conn, community_id, &community_form) + }) + .await??; + let community_id = community.id; let res = CommunityResponse { - community: CommunityView::read(&conn, community.id, None)?, + community: blocking(pool, move |conn| { + CommunityView::read(conn, community_id, None) + }) + .await??, }; + let community_id = res.community.id; + chat_server.do_send(SendCommunityRoomMessage { op: UserOperation::EditCommunity, response: res, - community_id: community.id, + community_id, my_id: None, }); Ok(HttpResponse::Ok().finish()) } -fn receive_remove_community( - remove: &Remove, - - conn: &PgConnection, +async fn receive_remove_community( + remove: Remove, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let mod_uri = remove .remove_props .get_actor_xsd_any_uri() @@ -777,12 +862,18 @@ fn receive_remove_community( .to_owned() .into_concrete::()?; - let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, &conn)?; + let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, client, pool).await?; - insert_activity(&conn, mod_.id, &remove, false)?; + insert_activity(mod_.id, remove, false, pool).await?; - let community_actor_id = CommunityForm::from_apub(&group, &conn)?.actor_id; - let community = Community::read_from_actor_id(conn, &community_actor_id)?; + let community_actor_id = CommunityForm::from_apub(&group, client, pool) + .await? + .actor_id; + + let community = blocking(pool, move |conn| { + Community::read_from_actor_id(conn, &community_actor_id) + }) + .await??; let community_form = CommunityForm { name: community.name.to_owned(), @@ -802,28 +893,38 @@ fn receive_remove_community( last_refreshed_at: None, }; - Community::update(&conn, community.id, &community_form)?; + let community_id = community.id; + blocking(pool, move |conn| { + Community::update(conn, community_id, &community_form) + }) + .await??; + let community_id = community.id; let res = CommunityResponse { - community: CommunityView::read(&conn, community.id, None)?, + community: blocking(pool, move |conn| { + CommunityView::read(conn, community_id, None) + }) + .await??, }; + let community_id = res.community.id; + chat_server.do_send(SendCommunityRoomMessage { op: UserOperation::EditCommunity, response: res, - community_id: community.id, + community_id, my_id: None, }); Ok(HttpResponse::Ok().finish()) } -fn receive_delete_post( - delete: &Delete, - - conn: &PgConnection, +async fn receive_delete_post( + delete: Delete, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let user_uri = delete .delete_props .get_actor_xsd_any_uri() @@ -838,12 +939,13 @@ fn receive_delete_post( .to_owned() .into_concrete::()?; - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &delete, false)?; + insert_activity(user.id, delete, false, pool).await?; - let post_ap_id = PostForm::from_apub(&page, conn)?.ap_id; - let post = get_or_fetch_and_insert_remote_post(&post_ap_id, &conn)?; + let post_ap_id = PostForm::from_apub(&page, client, pool).await?.ap_id; + + let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?; let post_form = PostForm { name: post.name.to_owned(), @@ -865,10 +967,12 @@ fn receive_delete_post( local: post.local, published: None, }; - Post::update(&conn, post.id, &post_form)?; + let post_id = post.id; + blocking(pool, move |conn| Post::update(conn, post_id, &post_form)).await??; // Refetch the view - let post_view = PostView::read(&conn, post.id, None)?; + let post_id = post.id; + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; let res = PostResponse { post: post_view }; @@ -881,12 +985,12 @@ fn receive_delete_post( Ok(HttpResponse::Ok().finish()) } -fn receive_remove_post( - remove: &Remove, - - conn: &PgConnection, +async fn receive_remove_post( + remove: Remove, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let mod_uri = remove .remove_props .get_actor_xsd_any_uri() @@ -901,12 +1005,13 @@ fn receive_remove_post( .to_owned() .into_concrete::()?; - let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, &conn)?; + let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, client, pool).await?; - insert_activity(&conn, mod_.id, &remove, false)?; + insert_activity(mod_.id, remove, false, pool).await?; - let post_ap_id = PostForm::from_apub(&page, conn)?.ap_id; - let post = get_or_fetch_and_insert_remote_post(&post_ap_id, &conn)?; + let post_ap_id = PostForm::from_apub(&page, client, pool).await?.ap_id; + + let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?; let post_form = PostForm { name: post.name.to_owned(), @@ -928,10 +1033,12 @@ fn receive_remove_post( local: post.local, published: None, }; - Post::update(&conn, post.id, &post_form)?; + let post_id = post.id; + blocking(pool, move |conn| Post::update(conn, post_id, &post_form)).await??; // Refetch the view - let post_view = PostView::read(&conn, post.id, None)?; + let post_id = post.id; + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; let res = PostResponse { post: post_view }; @@ -944,12 +1051,12 @@ fn receive_remove_post( Ok(HttpResponse::Ok().finish()) } -fn receive_delete_comment( - delete: &Delete, - - conn: &PgConnection, +async fn receive_delete_comment( + delete: Delete, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let user_uri = delete .delete_props .get_actor_xsd_any_uri() @@ -964,12 +1071,14 @@ fn receive_delete_comment( .to_owned() .into_concrete::()?; - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &delete, false)?; + insert_activity(user.id, delete, false, pool).await?; + + let comment_ap_id = CommentForm::from_apub(¬e, client, pool).await?.ap_id; + + let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?; - let comment_ap_id = CommentForm::from_apub(¬e, &conn)?.ap_id; - let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, &conn)?; let comment_form = CommentForm { content: comment.content.to_owned(), parent_id: comment.parent_id, @@ -983,10 +1092,16 @@ fn receive_delete_comment( ap_id: comment.ap_id, local: comment.local, }; - Comment::update(&conn, comment.id, &comment_form)?; + let comment_id = comment.id; + blocking(pool, move |conn| { + Comment::update(conn, comment_id, &comment_form) + }) + .await??; // Refetch the view - let comment_view = CommentView::read(&conn, comment.id, None)?; + let comment_id = comment.id; + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; // TODO get those recipient actor ids from somewhere let recipient_ids = vec![]; @@ -1004,12 +1119,12 @@ fn receive_delete_comment( Ok(HttpResponse::Ok().finish()) } -fn receive_remove_comment( - remove: &Remove, - - conn: &PgConnection, +async fn receive_remove_comment( + remove: Remove, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let mod_uri = remove .remove_props .get_actor_xsd_any_uri() @@ -1024,12 +1139,14 @@ fn receive_remove_comment( .to_owned() .into_concrete::()?; - let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, &conn)?; + let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, client, pool).await?; - insert_activity(&conn, mod_.id, &remove, false)?; + insert_activity(mod_.id, remove, false, pool).await?; + + let comment_ap_id = CommentForm::from_apub(¬e, client, pool).await?.ap_id; + + let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?; - let comment_ap_id = CommentForm::from_apub(¬e, &conn)?.ap_id; - let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, &conn)?; let comment_form = CommentForm { content: comment.content.to_owned(), parent_id: comment.parent_id, @@ -1043,10 +1160,16 @@ fn receive_remove_comment( ap_id: comment.ap_id, local: comment.local, }; - Comment::update(&conn, comment.id, &comment_form)?; + let comment_id = comment.id; + blocking(pool, move |conn| { + Comment::update(conn, comment_id, &comment_form) + }) + .await??; // Refetch the view - let comment_view = CommentView::read(&conn, comment.id, None)?; + let comment_id = comment.id; + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; // TODO get those recipient actor ids from somewhere let recipient_ids = vec![]; @@ -1064,12 +1187,12 @@ fn receive_remove_comment( Ok(HttpResponse::Ok().finish()) } -fn receive_undo_delete( - undo: &Undo, - - conn: &PgConnection, +async fn receive_undo_delete( + undo: Undo, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let delete = undo .undo_props .get_object_base_box() @@ -1087,19 +1210,19 @@ fn receive_undo_delete( .unwrap(); match type_ { - "Note" => receive_undo_delete_comment(&delete, &conn, chat_server), - "Page" => receive_undo_delete_post(&delete, &conn, chat_server), - "Group" => receive_undo_delete_community(&delete, &conn, chat_server), - d => Err(format_err!("Undo Delete type {} not supported", d)), + "Note" => receive_undo_delete_comment(delete, client, pool, chat_server).await, + "Page" => receive_undo_delete_post(delete, client, pool, chat_server).await, + "Group" => receive_undo_delete_community(delete, client, pool, chat_server).await, + d => Err(format_err!("Undo Delete type {} not supported", d).into()), } } -fn receive_undo_remove( - undo: &Undo, - - conn: &PgConnection, +async fn receive_undo_remove( + undo: Undo, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let remove = undo .undo_props .get_object_base_box() @@ -1117,19 +1240,19 @@ fn receive_undo_remove( .unwrap(); match type_ { - "Note" => receive_undo_remove_comment(&remove, &conn, chat_server), - "Page" => receive_undo_remove_post(&remove, &conn, chat_server), - "Group" => receive_undo_remove_community(&remove, &conn, chat_server), - d => Err(format_err!("Undo Delete type {} not supported", d)), + "Note" => receive_undo_remove_comment(remove, client, pool, chat_server).await, + "Page" => receive_undo_remove_post(remove, client, pool, chat_server).await, + "Group" => receive_undo_remove_community(remove, client, pool, chat_server).await, + d => Err(format_err!("Undo Delete type {} not supported", d).into()), } } -fn receive_undo_delete_comment( - delete: &Delete, - - conn: &PgConnection, +async fn receive_undo_delete_comment( + delete: Delete, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let user_uri = delete .delete_props .get_actor_xsd_any_uri() @@ -1144,12 +1267,14 @@ fn receive_undo_delete_comment( .to_owned() .into_concrete::()?; - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &delete, false)?; + insert_activity(user.id, delete, false, pool).await?; + + let comment_ap_id = CommentForm::from_apub(¬e, client, pool).await?.ap_id; + + let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?; - let comment_ap_id = CommentForm::from_apub(¬e, &conn)?.ap_id; - let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, &conn)?; let comment_form = CommentForm { content: comment.content.to_owned(), parent_id: comment.parent_id, @@ -1163,10 +1288,16 @@ fn receive_undo_delete_comment( ap_id: comment.ap_id, local: comment.local, }; - Comment::update(&conn, comment.id, &comment_form)?; + let comment_id = comment.id; + blocking(pool, move |conn| { + Comment::update(conn, comment_id, &comment_form) + }) + .await??; // Refetch the view - let comment_view = CommentView::read(&conn, comment.id, None)?; + let comment_id = comment.id; + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; // TODO get those recipient actor ids from somewhere let recipient_ids = vec![]; @@ -1184,12 +1315,12 @@ fn receive_undo_delete_comment( Ok(HttpResponse::Ok().finish()) } -fn receive_undo_remove_comment( - remove: &Remove, - - conn: &PgConnection, +async fn receive_undo_remove_comment( + remove: Remove, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let mod_uri = remove .remove_props .get_actor_xsd_any_uri() @@ -1204,12 +1335,14 @@ fn receive_undo_remove_comment( .to_owned() .into_concrete::()?; - let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, &conn)?; + let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, client, pool).await?; - insert_activity(&conn, mod_.id, &remove, false)?; + insert_activity(mod_.id, remove, false, pool).await?; + + let comment_ap_id = CommentForm::from_apub(¬e, client, pool).await?.ap_id; + + let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?; - let comment_ap_id = CommentForm::from_apub(¬e, &conn)?.ap_id; - let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, &conn)?; let comment_form = CommentForm { content: comment.content.to_owned(), parent_id: comment.parent_id, @@ -1223,10 +1356,16 @@ fn receive_undo_remove_comment( ap_id: comment.ap_id, local: comment.local, }; - Comment::update(&conn, comment.id, &comment_form)?; + let comment_id = comment.id; + blocking(pool, move |conn| { + Comment::update(conn, comment_id, &comment_form) + }) + .await??; // Refetch the view - let comment_view = CommentView::read(&conn, comment.id, None)?; + let comment_id = comment.id; + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; // TODO get those recipient actor ids from somewhere let recipient_ids = vec![]; @@ -1244,12 +1383,12 @@ fn receive_undo_remove_comment( Ok(HttpResponse::Ok().finish()) } -fn receive_undo_delete_post( - delete: &Delete, - - conn: &PgConnection, +async fn receive_undo_delete_post( + delete: Delete, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let user_uri = delete .delete_props .get_actor_xsd_any_uri() @@ -1264,12 +1403,13 @@ fn receive_undo_delete_post( .to_owned() .into_concrete::()?; - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &delete, false)?; + insert_activity(user.id, delete, false, pool).await?; - let post_ap_id = PostForm::from_apub(&page, conn)?.ap_id; - let post = get_or_fetch_and_insert_remote_post(&post_ap_id, &conn)?; + let post_ap_id = PostForm::from_apub(&page, client, pool).await?.ap_id; + + let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?; let post_form = PostForm { name: post.name.to_owned(), @@ -1291,10 +1431,12 @@ fn receive_undo_delete_post( local: post.local, published: None, }; - Post::update(&conn, post.id, &post_form)?; + let post_id = post.id; + blocking(pool, move |conn| Post::update(conn, post_id, &post_form)).await??; // Refetch the view - let post_view = PostView::read(&conn, post.id, None)?; + let post_id = post.id; + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; let res = PostResponse { post: post_view }; @@ -1307,12 +1449,12 @@ fn receive_undo_delete_post( Ok(HttpResponse::Ok().finish()) } -fn receive_undo_remove_post( - remove: &Remove, - - conn: &PgConnection, +async fn receive_undo_remove_post( + remove: Remove, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let mod_uri = remove .remove_props .get_actor_xsd_any_uri() @@ -1327,12 +1469,13 @@ fn receive_undo_remove_post( .to_owned() .into_concrete::()?; - let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, &conn)?; + let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, client, pool).await?; - insert_activity(&conn, mod_.id, &remove, false)?; + insert_activity(mod_.id, remove, false, pool).await?; - let post_ap_id = PostForm::from_apub(&page, conn)?.ap_id; - let post = get_or_fetch_and_insert_remote_post(&post_ap_id, &conn)?; + let post_ap_id = PostForm::from_apub(&page, client, pool).await?.ap_id; + + let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?; let post_form = PostForm { name: post.name.to_owned(), @@ -1354,10 +1497,12 @@ fn receive_undo_remove_post( local: post.local, published: None, }; - Post::update(&conn, post.id, &post_form)?; + let post_id = post.id; + blocking(pool, move |conn| Post::update(conn, post_id, &post_form)).await??; // Refetch the view - let post_view = PostView::read(&conn, post.id, None)?; + let post_id = post.id; + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; let res = PostResponse { post: post_view }; @@ -1370,12 +1515,12 @@ fn receive_undo_remove_post( Ok(HttpResponse::Ok().finish()) } -fn receive_undo_delete_community( - delete: &Delete, - - conn: &PgConnection, +async fn receive_undo_delete_community( + delete: Delete, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let user_uri = delete .delete_props .get_actor_xsd_any_uri() @@ -1390,12 +1535,18 @@ fn receive_undo_delete_community( .to_owned() .into_concrete::()?; - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &delete, false)?; + insert_activity(user.id, delete, false, pool).await?; - let community_actor_id = CommunityForm::from_apub(&group, &conn)?.actor_id; - let community = Community::read_from_actor_id(conn, &community_actor_id)?; + let community_actor_id = CommunityForm::from_apub(&group, client, pool) + .await? + .actor_id; + + let community = blocking(pool, move |conn| { + Community::read_from_actor_id(conn, &community_actor_id) + }) + .await??; let community_form = CommunityForm { name: community.name.to_owned(), @@ -1415,28 +1566,38 @@ fn receive_undo_delete_community( last_refreshed_at: None, }; - Community::update(&conn, community.id, &community_form)?; + let community_id = community.id; + blocking(pool, move |conn| { + Community::update(conn, community_id, &community_form) + }) + .await??; + let community_id = community.id; let res = CommunityResponse { - community: CommunityView::read(&conn, community.id, None)?, + community: blocking(pool, move |conn| { + CommunityView::read(conn, community_id, None) + }) + .await??, }; + let community_id = res.community.id; + chat_server.do_send(SendCommunityRoomMessage { op: UserOperation::EditCommunity, response: res, - community_id: community.id, + community_id, my_id: None, }); Ok(HttpResponse::Ok().finish()) } -fn receive_undo_remove_community( - remove: &Remove, - - conn: &PgConnection, +async fn receive_undo_remove_community( + remove: Remove, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let mod_uri = remove .remove_props .get_actor_xsd_any_uri() @@ -1451,12 +1612,18 @@ fn receive_undo_remove_community( .to_owned() .into_concrete::()?; - let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, &conn)?; + let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, client, pool).await?; - insert_activity(&conn, mod_.id, &remove, false)?; + insert_activity(mod_.id, remove, false, pool).await?; - let community_actor_id = CommunityForm::from_apub(&group, &conn)?.actor_id; - let community = Community::read_from_actor_id(conn, &community_actor_id)?; + let community_actor_id = CommunityForm::from_apub(&group, client, pool) + .await? + .actor_id; + + let community = blocking(pool, move |conn| { + Community::read_from_actor_id(conn, &community_actor_id) + }) + .await??; let community_form = CommunityForm { name: community.name.to_owned(), @@ -1476,28 +1643,38 @@ fn receive_undo_remove_community( last_refreshed_at: None, }; - Community::update(&conn, community.id, &community_form)?; + let community_id = community.id; + blocking(pool, move |conn| { + Community::update(conn, community_id, &community_form) + }) + .await??; + let community_id = community.id; let res = CommunityResponse { - community: CommunityView::read(&conn, community.id, None)?, + community: blocking(pool, move |conn| { + CommunityView::read(conn, community_id, None) + }) + .await??, }; + let community_id = res.community.id; + chat_server.do_send(SendCommunityRoomMessage { op: UserOperation::EditCommunity, response: res, - community_id: community.id, + community_id, my_id: None, }); Ok(HttpResponse::Ok().finish()) } -fn receive_undo_like( - undo: &Undo, - - conn: &PgConnection, +async fn receive_undo_like( + undo: Undo, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let like = undo .undo_props .get_object_base_box() @@ -1515,18 +1692,18 @@ fn receive_undo_like( .unwrap(); match type_ { - "Note" => receive_undo_like_comment(&like, &conn, chat_server), - "Page" => receive_undo_like_post(&like, &conn, chat_server), - d => Err(format_err!("Undo Delete type {} not supported", d)), + "Note" => receive_undo_like_comment(like, client, pool, chat_server).await, + "Page" => receive_undo_like_post(like, client, pool, chat_server).await, + d => Err(format_err!("Undo Delete type {} not supported", d).into()), } } -fn receive_undo_like_comment( - like: &Like, - - conn: &PgConnection, +async fn receive_undo_like_comment( + like: Like, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let note = like .like_props .get_object_base_box() @@ -1537,22 +1714,27 @@ fn receive_undo_like_comment( let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string(); - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &like, false)?; + insert_activity(user.id, like, false, pool).await?; + + let comment = CommentForm::from_apub(¬e, client, pool).await?; + + let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool) + .await? + .id; - let comment = CommentForm::from_apub(¬e, &conn)?; - let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, &conn)?.id; let like_form = CommentLikeForm { comment_id, post_id: comment.post_id, user_id: user.id, score: 0, }; - CommentLike::remove(&conn, &like_form)?; + blocking(pool, move |conn| CommentLike::remove(conn, &like_form)).await??; // Refetch the view - let comment_view = CommentView::read(&conn, comment_id, None)?; + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; // TODO get those recipient actor ids from somewhere let recipient_ids = vec![]; @@ -1570,12 +1752,12 @@ fn receive_undo_like_comment( Ok(HttpResponse::Ok().finish()) } -fn receive_undo_like_post( - like: &Like, - - conn: &PgConnection, +async fn receive_undo_like_post( + like: Like, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let page = like .like_props .get_object_base_box() @@ -1586,22 +1768,25 @@ fn receive_undo_like_post( let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string(); - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; - insert_activity(&conn, user.id, &like, false)?; + insert_activity(user.id, like, false, pool).await?; - let post = PostForm::from_apub(&page, conn)?; - let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, &conn)?.id; + let post = PostForm::from_apub(&page, client, pool).await?; + + let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool) + .await? + .id; let like_form = PostLikeForm { post_id, user_id: user.id, score: 1, }; - PostLike::remove(&conn, &like_form)?; + blocking(pool, move |conn| PostLike::remove(conn, &like_form)).await??; // Refetch the view - let post_view = PostView::read(&conn, post_id, None)?; + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; let res = PostResponse { post: post_view }; diff --git a/server/src/apub/user.rs b/server/src/apub/user.rs index c840cc22d..51339ccf2 100644 --- a/server/src/apub/user.rs +++ b/server/src/apub/user.rs @@ -8,6 +8,7 @@ use crate::{ PersonExt, ToApub, }, + blocking, convert_datetime, db::{ activity::insert_activity, @@ -15,6 +16,8 @@ use crate::{ }, naive_now, routes::DbPoolParam, + DbPool, + LemmyError, }; use activitystreams::{ actor::{properties::ApActorProperties, Person}, @@ -29,9 +32,7 @@ use activitystreams_new::{ object::Tombstone, prelude::*, }; -use actix_web::{body::Body, web::Path, HttpResponse, Result}; -use diesel::PgConnection; -use failure::Error; +use actix_web::{body::Body, client::Client, web, HttpResponse}; use serde::Deserialize; #[derive(Deserialize)] @@ -39,11 +40,12 @@ pub struct UserQuery { user_name: String, } +#[async_trait::async_trait(?Send)] impl ToApub for User_ { type Response = PersonExt; // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network. - fn to_apub(&self, _conn: &PgConnection) -> Result { + async fn to_apub(&self, _pool: &DbPool) -> Result { // TODO go through all these to_string and to_owned() let mut person = Person::default(); let oprops: &mut ObjectProperties = person.as_mut(); @@ -86,11 +88,12 @@ impl ToApub for User_ { Ok(Ext2::new(person, actor_props, self.get_public_key_ext())) } - fn to_tombstone(&self) -> Result { + fn to_tombstone(&self) -> Result { unimplemented!() } } +#[async_trait::async_trait(?Send)] impl ActorType for User_ { fn actor_id(&self) -> String { self.actor_id.to_owned() @@ -105,19 +108,29 @@ impl ActorType for User_ { } /// As a given local user, send out a follow request to a remote community. - fn send_follow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error> { + async fn send_follow( + &self, + follow_actor_id: &str, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4()); let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id); follow.set_context(context()).set_id(id.parse()?); let to = format!("{}/inbox", follow_actor_id); - insert_activity(&conn, self.id, &follow, true)?; + insert_activity(self.id, follow.clone(), true, pool).await?; - send_activity(&follow, self, vec![to])?; + send_activity(client, &follow, self, vec![to]).await?; Ok(()) } - fn send_unfollow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error> { + async fn send_unfollow( + &self, + follow_actor_id: &str, + client: &Client, + pool: &DbPool, + ) -> Result<(), LemmyError> { let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4()); let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id); follow.set_context(context()).set_id(id.parse()?); @@ -130,41 +143,67 @@ impl ActorType for User_ { let mut undo = Undo::new(self.actor_id.parse::()?, follow.into_any_base()?); undo.set_context(context()).set_id(undo_id.parse()?); - insert_activity(&conn, self.id, &undo, true)?; + insert_activity(self.id, undo.clone(), true, pool).await?; - send_activity(&undo, self, vec![to])?; + send_activity(client, &undo, self, vec![to]).await?; Ok(()) } - fn send_delete(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> { + async fn send_delete( + &self, + _creator: &User_, + _client: &Client, + _pool: &DbPool, + ) -> Result<(), LemmyError> { unimplemented!() } - fn send_undo_delete(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> { + async fn send_undo_delete( + &self, + _creator: &User_, + _client: &Client, + _pool: &DbPool, + ) -> Result<(), LemmyError> { unimplemented!() } - fn send_remove(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> { + async fn send_remove( + &self, + _creator: &User_, + _client: &Client, + _pool: &DbPool, + ) -> Result<(), LemmyError> { unimplemented!() } - fn send_undo_remove(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> { + async fn send_undo_remove( + &self, + _creator: &User_, + _client: &Client, + _pool: &DbPool, + ) -> Result<(), LemmyError> { unimplemented!() } - fn send_accept_follow(&self, _follow: &Follow, _conn: &PgConnection) -> Result<(), Error> { + async fn send_accept_follow( + &self, + _follow: &Follow, + _client: &Client, + _pool: &DbPool, + ) -> Result<(), LemmyError> { unimplemented!() } - fn get_follower_inboxes(&self, _conn: &PgConnection) -> Result, Error> { + async fn get_follower_inboxes(&self, _pool: &DbPool) -> Result, LemmyError> { unimplemented!() } } +#[async_trait::async_trait(?Send)] impl FromApub for UserForm { type ApubType = PersonExt; /// Parse an ActivityPub person received from another instance into a Lemmy user. - fn from_apub(person: &PersonExt, _conn: &PgConnection) -> Result { + async fn from_apub(person: &PersonExt, _: &Client, _: &DbPool) -> Result { let oprops = &person.inner.object_props; let aprops = &person.ext_one; let public_key: &PublicKey = &person.ext_two.public_key; @@ -210,10 +249,14 @@ impl FromApub for UserForm { /// Return the user json over HTTP. pub async fn get_apub_user_http( - info: Path, + info: web::Path, db: DbPoolParam, -) -> Result, Error> { - let user = User_::find_by_email_or_username(&&db.get()?, &info.user_name)?; - let u = user.to_apub(&db.get().unwrap())?; +) -> Result, LemmyError> { + let user_name = info.into_inner().user_name; + let user = blocking(&db, move |conn| { + User_::find_by_email_or_username(conn, &user_name) + }) + .await??; + let u = user.to_apub(&db).await?; Ok(create_apub_response(&u)) } diff --git a/server/src/apub/user_inbox.rs b/server/src/apub/user_inbox.rs index f60a2ba9b..c63178e72 100644 --- a/server/src/apub/user_inbox.rs +++ b/server/src/apub/user_inbox.rs @@ -5,6 +5,7 @@ use crate::{ fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user}, FromApub, }, + blocking, db::{ activity::insert_activity, community::{CommunityFollower, CommunityFollowerForm}, @@ -17,16 +18,17 @@ use crate::{ naive_now, routes::{ChatServerParam, DbPoolParam}, websocket::{server::SendUserRoomMessage, UserOperation}, + DbPool, + LemmyError, }; use activitystreams::{ activity::{Accept, Create, Delete, Undo, Update}, object::Note, }; -use actix_web::{web, HttpRequest, HttpResponse, Result}; -use diesel::PgConnection; -use failure::{Error, _core::fmt::Debug}; +use actix_web::{client::Client, web, HttpRequest, HttpResponse}; use log::debug; use serde::Deserialize; +use std::fmt::Debug; #[serde(untagged)] #[derive(Deserialize, Debug)] @@ -43,51 +45,53 @@ pub async fn user_inbox( request: HttpRequest, input: web::Json, path: web::Path, + client: web::Data, db: DbPoolParam, chat_server: ChatServerParam, -) -> Result { +) -> Result { // TODO: would be nice if we could do the signature check here, but we cant access the actor property let input = input.into_inner(); - let conn = &db.get().unwrap(); let username = path.into_inner(); debug!("User {} received activity: {:?}", &username, &input); match input { - UserAcceptedObjects::Accept(a) => receive_accept(&a, &request, &username, &conn), + UserAcceptedObjects::Accept(a) => receive_accept(*a, &request, &username, &client, &db).await, UserAcceptedObjects::Create(c) => { - receive_create_private_message(&c, &request, &conn, chat_server) + receive_create_private_message(*c, &request, &client, &db, chat_server).await } UserAcceptedObjects::Update(u) => { - receive_update_private_message(&u, &request, &conn, chat_server) + receive_update_private_message(*u, &request, &client, &db, chat_server).await } UserAcceptedObjects::Delete(d) => { - receive_delete_private_message(&d, &request, &conn, chat_server) + receive_delete_private_message(*d, &request, &client, &db, chat_server).await } UserAcceptedObjects::Undo(u) => { - receive_undo_delete_private_message(&u, &request, &conn, chat_server) + receive_undo_delete_private_message(*u, &request, &client, &db, chat_server).await } } } /// Handle accepted follows. -fn receive_accept( - accept: &Accept, +async fn receive_accept( + accept: Accept, request: &HttpRequest, username: &str, - conn: &PgConnection, -) -> Result { + client: &Client, + pool: &DbPool, +) -> Result { let community_uri = accept .accept_props .get_actor_xsd_any_uri() .unwrap() .to_string(); - let community = get_or_fetch_and_upsert_remote_community(&community_uri, conn)?; + let community = get_or_fetch_and_upsert_remote_community(&community_uri, client, pool).await?; verify(request, &community)?; - let user = User_::read_from_name(&conn, username)?; + let username = username.to_owned(); + let user = blocking(pool, move |conn| User_::read_from_name(conn, &username)).await??; - insert_activity(&conn, community.creator_id, &accept, false)?; + insert_activity(community.creator_id, accept, false, pool).await?; // Now you need to add this to the community follower let community_follower_form = CommunityFollowerForm { @@ -96,18 +100,22 @@ fn receive_accept( }; // This will fail if they're already a follower - CommunityFollower::follow(&conn, &community_follower_form)?; + blocking(pool, move |conn| { + CommunityFollower::follow(conn, &community_follower_form) + }) + .await??; // TODO: make sure that we actually requested a follow Ok(HttpResponse::Ok().finish()) } -fn receive_create_private_message( - create: &Create, +async fn receive_create_private_message( + create: Create, request: &HttpRequest, - conn: &PgConnection, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let note = create .create_props .get_object_base_box() @@ -122,36 +130,44 @@ fn receive_create_private_message( .unwrap() .to_string(); - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; verify(request, &user)?; - insert_activity(&conn, user.id, &create, false)?; + insert_activity(user.id, create, false, pool).await?; - let private_message = PrivateMessageForm::from_apub(¬e, &conn)?; - let inserted_private_message = PrivateMessage::create(&conn, &private_message)?; + let private_message = PrivateMessageForm::from_apub(¬e, client, pool).await?; - let message = PrivateMessageView::read(&conn, inserted_private_message.id)?; + let inserted_private_message = blocking(pool, move |conn| { + PrivateMessage::create(conn, &private_message) + }) + .await??; - let res = PrivateMessageResponse { - message: message.to_owned(), - }; + let message = blocking(pool, move |conn| { + PrivateMessageView::read(conn, inserted_private_message.id) + }) + .await??; + + let res = PrivateMessageResponse { message }; + + let recipient_id = res.message.recipient_id; chat_server.do_send(SendUserRoomMessage { op: UserOperation::CreatePrivateMessage, response: res, - recipient_id: message.recipient_id, + recipient_id, my_id: None, }); Ok(HttpResponse::Ok().finish()) } -fn receive_update_private_message( - update: &Update, +async fn receive_update_private_message( + update: Update, request: &HttpRequest, - conn: &PgConnection, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let note = update .update_props .get_object_base_box() @@ -166,37 +182,52 @@ fn receive_update_private_message( .unwrap() .to_string(); - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; verify(request, &user)?; - insert_activity(&conn, user.id, &update, false)?; + insert_activity(user.id, update, false, pool).await?; - let private_message = PrivateMessageForm::from_apub(¬e, &conn)?; - let private_message_id = PrivateMessage::read_from_apub_id(&conn, &private_message.ap_id)?.id; - PrivateMessage::update(conn, private_message_id, &private_message)?; + let private_message_form = PrivateMessageForm::from_apub(¬e, client, pool).await?; - let message = PrivateMessageView::read(&conn, private_message_id)?; + let private_message_ap_id = private_message_form.ap_id.clone(); + let private_message = blocking(pool, move |conn| { + PrivateMessage::read_from_apub_id(conn, &private_message_ap_id) + }) + .await??; - let res = PrivateMessageResponse { - message: message.to_owned(), - }; + let private_message_id = private_message.id; + blocking(pool, move |conn| { + PrivateMessage::update(conn, private_message_id, &private_message_form) + }) + .await??; + + let private_message_id = private_message.id; + let message = blocking(pool, move |conn| { + PrivateMessageView::read(conn, private_message_id) + }) + .await??; + + let res = PrivateMessageResponse { message }; + + let recipient_id = res.message.recipient_id; chat_server.do_send(SendUserRoomMessage { op: UserOperation::EditPrivateMessage, response: res, - recipient_id: message.recipient_id, + recipient_id, my_id: None, }); Ok(HttpResponse::Ok().finish()) } -fn receive_delete_private_message( - delete: &Delete, +async fn receive_delete_private_message( + delete: Delete, request: &HttpRequest, - conn: &PgConnection, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let note = delete .delete_props .get_object_base_box() @@ -211,15 +242,21 @@ fn receive_delete_private_message( .unwrap() .to_string(); - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; verify(request, &user)?; - insert_activity(&conn, user.id, &delete, false)?; + insert_activity(user.id, delete, false, pool).await?; + + let private_message_form = PrivateMessageForm::from_apub(¬e, client, pool).await?; + + let private_message_ap_id = private_message_form.ap_id; + let private_message = blocking(pool, move |conn| { + PrivateMessage::read_from_apub_id(conn, &private_message_ap_id) + }) + .await??; - let private_message = PrivateMessageForm::from_apub(¬e, &conn)?; - let private_message_id = PrivateMessage::read_from_apub_id(&conn, &private_message.ap_id)?.id; let private_message_form = PrivateMessageForm { - content: private_message.content, + content: private_message_form.content, recipient_id: private_message.recipient_id, creator_id: private_message.creator_id, deleted: Some(true), @@ -229,30 +266,40 @@ fn receive_delete_private_message( published: None, updated: Some(naive_now()), }; - PrivateMessage::update(conn, private_message_id, &private_message_form)?; - let message = PrivateMessageView::read(&conn, private_message_id)?; + let private_message_id = private_message.id; + blocking(pool, move |conn| { + PrivateMessage::update(conn, private_message_id, &private_message_form) + }) + .await??; - let res = PrivateMessageResponse { - message: message.to_owned(), - }; + let private_message_id = private_message.id; + let message = blocking(pool, move |conn| { + PrivateMessageView::read(&conn, private_message_id) + }) + .await??; + + let res = PrivateMessageResponse { message }; + + let recipient_id = res.message.recipient_id; chat_server.do_send(SendUserRoomMessage { op: UserOperation::EditPrivateMessage, response: res, - recipient_id: message.recipient_id, + recipient_id, my_id: None, }); Ok(HttpResponse::Ok().finish()) } -fn receive_undo_delete_private_message( - undo: &Undo, +async fn receive_undo_delete_private_message( + undo: Undo, request: &HttpRequest, - conn: &PgConnection, + client: &Client, + pool: &DbPool, chat_server: ChatServerParam, -) -> Result { +) -> Result { let delete = undo .undo_props .get_object_base_box() @@ -275,13 +322,19 @@ fn receive_undo_delete_private_message( .unwrap() .to_string(); - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?; verify(request, &user)?; - insert_activity(&conn, user.id, &delete, false)?; + insert_activity(user.id, delete, false, pool).await?; + + let private_message = PrivateMessageForm::from_apub(¬e, client, pool).await?; + + let private_message_ap_id = private_message.ap_id.clone(); + let private_message_id = blocking(pool, move |conn| { + PrivateMessage::read_from_apub_id(conn, &private_message_ap_id).map(|pm| pm.id) + }) + .await??; - let private_message = PrivateMessageForm::from_apub(¬e, &conn)?; - let private_message_id = PrivateMessage::read_from_apub_id(&conn, &private_message.ap_id)?.id; let private_message_form = PrivateMessageForm { content: private_message.content, recipient_id: private_message.recipient_id, @@ -293,18 +346,25 @@ fn receive_undo_delete_private_message( published: None, updated: Some(naive_now()), }; - PrivateMessage::update(conn, private_message_id, &private_message_form)?; - let message = PrivateMessageView::read(&conn, private_message_id)?; + blocking(pool, move |conn| { + PrivateMessage::update(conn, private_message_id, &private_message_form) + }) + .await??; - let res = PrivateMessageResponse { - message: message.to_owned(), - }; + let message = blocking(pool, move |conn| { + PrivateMessageView::read(&conn, private_message_id) + }) + .await??; + + let res = PrivateMessageResponse { message }; + + let recipient_id = res.message.recipient_id; chat_server.do_send(SendUserRoomMessage { op: UserOperation::EditPrivateMessage, response: res, - recipient_id: message.recipient_id, + recipient_id, my_id: None, }); diff --git a/server/src/db/activity.rs b/server/src/db/activity.rs index ccd1e6824..8c2b0c742 100644 --- a/server/src/db/activity.rs +++ b/server/src/db/activity.rs @@ -1,9 +1,9 @@ -use crate::{db::Crud, schema::activity}; +use crate::{blocking, db::Crud, schema::activity, DbPool, LemmyError}; use diesel::{dsl::*, result::Error, *}; -use failure::_core::fmt::Debug; use log::debug; use serde::{Deserialize, Serialize}; use serde_json::Value; +use std::fmt::Debug; #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] #[table_name = "activity"] @@ -55,12 +55,28 @@ impl Crud for Activity { } } -pub fn insert_activity( +pub async fn insert_activity( + user_id: i32, + data: T, + local: bool, + pool: &DbPool, +) -> Result<(), LemmyError> +where + T: Serialize + Debug + Send + 'static, +{ + blocking(pool, move |conn| { + do_insert_activity(conn, user_id, &data, local) + }) + .await??; + Ok(()) +} + +fn do_insert_activity( conn: &PgConnection, user_id: i32, data: &T, local: bool, -) -> Result<(), failure::Error> +) -> Result<(), LemmyError> where T: Serialize + Debug, { diff --git a/server/src/db/code_migrations.rs b/server/src/db/code_migrations.rs index 6f1b656f8..67e0c4dcc 100644 --- a/server/src/db/code_migrations.rs +++ b/server/src/db/code_migrations.rs @@ -10,21 +10,22 @@ use crate::{ apub::{extensions::signatures::generate_actor_keypair, make_apub_endpoint, EndpointType}, db::Crud, naive_now, + LemmyError, }; use diesel::*; -use failure::Error; use log::info; -pub fn run_advanced_migrations(conn: &PgConnection) -> Result<(), Error> { +pub fn run_advanced_migrations(conn: &PgConnection) -> Result<(), LemmyError> { user_updates_2020_04_02(&conn)?; community_updates_2020_04_02(&conn)?; post_updates_2020_04_03(&conn)?; comment_updates_2020_04_03(&conn)?; private_message_updates_2020_05_05(&conn)?; + Ok(()) } -fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> { +fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> { use crate::schema::user_::dsl::*; info!("Running user_updates_2020_04_02"); @@ -75,7 +76,7 @@ fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> { Ok(()) } -fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> { +fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> { use crate::schema::community::dsl::*; info!("Running community_updates_2020_04_02"); @@ -119,7 +120,7 @@ fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> { Ok(()) } -fn post_updates_2020_04_03(conn: &PgConnection) -> Result<(), Error> { +fn post_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> { use crate::schema::post::dsl::*; info!("Running post_updates_2020_04_03"); @@ -143,7 +144,7 @@ fn post_updates_2020_04_03(conn: &PgConnection) -> Result<(), Error> { Ok(()) } -fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), Error> { +fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> { use crate::schema::comment::dsl::*; info!("Running comment_updates_2020_04_03"); @@ -167,7 +168,7 @@ fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), Error> { Ok(()) } -fn private_message_updates_2020_05_05(conn: &PgConnection) -> Result<(), Error> { +fn private_message_updates_2020_05_05(conn: &PgConnection) -> Result<(), LemmyError> { use crate::schema::private_message::dsl::*; info!("Running private_message_updates_2020_05_05"); diff --git a/server/src/db/comment.rs b/server/src/db/comment.rs index 8a2aa8893..7e76770f6 100644 --- a/server/src/db/comment.rs +++ b/server/src/db/comment.rs @@ -12,7 +12,7 @@ use crate::{ // ) // SELECT * FROM MyTree; -#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Deserialize)] +#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Deserialize)] #[belongs_to(Post)] #[table_name = "comment"] pub struct Comment { diff --git a/server/src/db/community.rs b/server/src/db/community.rs index 38ad07fcb..461ba473a 100644 --- a/server/src/db/community.rs +++ b/server/src/db/community.rs @@ -5,7 +5,7 @@ use crate::{ use diesel::{dsl::*, result::Error, *}; use serde::{Deserialize, Serialize}; -#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] +#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] #[table_name = "community"] pub struct Community { pub id: i32, diff --git a/server/src/db/password_reset_request.rs b/server/src/db/password_reset_request.rs index b92d70ed4..4a071f078 100644 --- a/server/src/db/password_reset_request.rs +++ b/server/src/db/password_reset_request.rs @@ -50,8 +50,8 @@ impl Crud for PasswordResetRequest { impl PasswordResetRequest { pub fn create_token(conn: &PgConnection, from_user_id: i32, token: &str) -> Result { let mut hasher = Sha256::new(); - hasher.input(token); - let token_hash: String = PasswordResetRequest::bytes_to_hex(hasher.result().to_vec()); + hasher.update(token); + let token_hash: String = PasswordResetRequest::bytes_to_hex(hasher.finalize().to_vec()); let form = PasswordResetRequestForm { user_id: from_user_id, @@ -62,8 +62,8 @@ impl PasswordResetRequest { } pub fn read_from_token(conn: &PgConnection, token: &str) -> Result { let mut hasher = Sha256::new(); - hasher.input(token); - let token_hash: String = PasswordResetRequest::bytes_to_hex(hasher.result().to_vec()); + hasher.update(token); + let token_hash: String = PasswordResetRequest::bytes_to_hex(hasher.finalize().to_vec()); password_reset_request .filter(token_encrypted.eq(token_hash)) .filter(published.gt(now - 1.days())) diff --git a/server/src/db/user.rs b/server/src/db/user.rs index eaf9d292b..4ca0a0419 100644 --- a/server/src/db/user.rs +++ b/server/src/db/user.rs @@ -10,7 +10,7 @@ use diesel::{dsl::*, result::Error, *}; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation}; use serde::{Deserialize, Serialize}; -#[derive(Queryable, Identifiable, PartialEq, Debug)] +#[derive(Clone, Queryable, Identifiable, PartialEq, Debug)] #[table_name = "user_"] pub struct User_ { pub id: i32, diff --git a/server/src/lib.rs b/server/src/lib.rs index b7c210497..3657b4a25 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -26,20 +26,39 @@ pub extern crate serde_json; pub extern crate sha2; pub extern crate strum; +pub async fn blocking(pool: &DbPool, f: F) -> Result +where + F: FnOnce(&diesel::PgConnection) -> T + Send + 'static, + T: Send + 'static, +{ + let pool = pool.clone(); + let res = actix_web::web::block(move || { + let conn = pool.get()?; + let res = (f)(&conn); + Ok(res) as Result<_, LemmyError> + }) + .await?; + + Ok(res) +} + pub mod api; pub mod apub; pub mod db; pub mod rate_limit; +pub mod request; pub mod routes; pub mod schema; pub mod settings; pub mod version; pub mod websocket; -use crate::settings::Settings; -use actix_web::dev::ConnectionInfo; +use crate::{ + request::{retry, RecvError}, + settings::Settings, +}; +use actix_web::{client::Client, dev::ConnectionInfo}; use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, Utc}; -use isahc::prelude::*; use itertools::Itertools; use lettre::{ smtp::{ @@ -58,12 +77,35 @@ use rand::{distributions::Alphanumeric, thread_rng, Rng}; use regex::{Regex, RegexBuilder}; use serde::Deserialize; +pub type DbPool = diesel::r2d2::Pool>; pub type ConnectionId = usize; pub type PostId = i32; pub type CommunityId = i32; pub type UserId = i32; pub type IPAddr = String; +#[derive(Debug)] +pub struct LemmyError { + inner: failure::Error, +} + +impl std::fmt::Display for LemmyError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + self.inner.fmt(f) + } +} + +impl actix_web::error::ResponseError for LemmyError {} + +impl From for LemmyError +where + T: Into, +{ + fn from(t: T) -> Self { + LemmyError { inner: t.into() } + } +} + pub fn to_datetime_utc(ndt: NaiveDateTime) -> DateTime { DateTime::::from_utc(ndt, Utc) } @@ -85,8 +127,10 @@ pub fn is_email_regex(test: &str) -> bool { EMAIL_REGEX.is_match(test) } -pub fn is_image_content_type(test: &str) -> Result<(), failure::Error> { - if isahc::get(test)? +pub async fn is_image_content_type(client: &Client, test: &str) -> Result<(), LemmyError> { + let response = retry(|| client.get(test).send()).await?; + + if response .headers() .get("Content-Type") .ok_or_else(|| format_err!("No Content-Type header"))? @@ -95,7 +139,7 @@ pub fn is_image_content_type(test: &str) -> Result<(), failure::Error> { { Ok(()) } else { - Err(format_err!("Not an image type.")) + Err(format_err!("Not an image type.").into()) } } @@ -178,10 +222,15 @@ pub struct IframelyResponse { html: Option, } -pub fn fetch_iframely(url: &str) -> Result { +pub async fn fetch_iframely(client: &Client, url: &str) -> Result { let fetch_url = format!("http://iframely/oembed?url={}", url); - let text = isahc::get(&fetch_url)?.text()?; - let res: IframelyResponse = serde_json::from_str(&text)?; + + let mut response = retry(|| client.get(&fetch_url).send()).await?; + + let res: IframelyResponse = response + .json() + .await + .map_err(|e| RecvError(e.to_string()))?; Ok(res) } @@ -197,23 +246,30 @@ pub struct PictrsFile { delete_token: String, } -pub fn fetch_pictrs(image_url: &str) -> Result { - is_image_content_type(image_url)?; +pub async fn fetch_pictrs(client: &Client, image_url: &str) -> Result { + is_image_content_type(client, image_url).await?; let fetch_url = format!( "http://pictrs:8080/image/download?url={}", utf8_percent_encode(image_url, NON_ALPHANUMERIC) // TODO this might not be needed ); - let text = isahc::get(&fetch_url)?.text()?; - let res: PictrsResponse = serde_json::from_str(&text)?; - if res.msg == "ok" { - Ok(res) + + let mut response = retry(|| client.get(&fetch_url).send()).await?; + + let response: PictrsResponse = response + .json() + .await + .map_err(|e| RecvError(e.to_string()))?; + + if response.msg == "ok" { + Ok(response) } else { - Err(format_err!("{}", &res.msg)) + Err(format_err!("{}", &response.msg).into()) } } -fn fetch_iframely_and_pictrs_data( +async fn fetch_iframely_and_pictrs_data( + client: &Client, url: Option, ) -> ( Option, @@ -225,7 +281,7 @@ fn fetch_iframely_and_pictrs_data( Some(url) => { // Fetch iframely data let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) = - match fetch_iframely(url) { + match fetch_iframely(client, url).await { Ok(res) => (res.title, res.description, res.thumbnail_url, res.html), Err(e) => { error!("iframely err: {}", e); @@ -235,7 +291,7 @@ fn fetch_iframely_and_pictrs_data( // Fetch pictrs thumbnail let pictrs_thumbnail = match iframely_thumbnail_url { - Some(iframely_thumbnail_url) => match fetch_pictrs(&iframely_thumbnail_url) { + Some(iframely_thumbnail_url) => match fetch_pictrs(client, &iframely_thumbnail_url).await { Ok(res) => Some(res.files[0].file.to_owned()), Err(e) => { error!("pictrs err: {}", e); @@ -243,7 +299,7 @@ fn fetch_iframely_and_pictrs_data( } }, // Try to generate a small thumbnail if iframely is not supported - None => match fetch_pictrs(&url) { + None => match fetch_pictrs(client, &url).await { Ok(res) => Some(res.files[0].file.to_owned()), Err(e) => { error!("pictrs err: {}", e); @@ -269,7 +325,7 @@ pub fn markdown_to_html(text: &str) -> String { pub fn get_ip(conn_info: &ConnectionInfo) -> String { conn_info - .remote() + .remote_addr() .unwrap_or("127.0.0.1:12345") .split(':') .next() @@ -327,21 +383,25 @@ mod tests { #[test] fn test_mentions_regex() { - let text = "Just read a great blog post by [@tedu@honk.teduangst.com](/u/test). And another by !test_community@fish.teduangst.com . Another [@lemmy@lemmy_alpha:8540](/u/fish)"; + let text = "Just read a great blog post by [@tedu@honk.teduangst.com](/u/test). And another by !test_community@fish.teduangst.com . Another [@lemmy@lemmy-alpha:8540](/u/fish)"; let mentions = scrape_text_for_mentions(text); assert_eq!(mentions[0].name, "tedu".to_string()); assert_eq!(mentions[0].domain, "honk.teduangst.com".to_string()); - assert_eq!(mentions[1].domain, "lemmy_alpha:8540".to_string()); + assert_eq!(mentions[1].domain, "lemmy-alpha:8540".to_string()); } #[test] fn test_image() { - assert!(is_image_content_type("https://1734811051.rsc.cdn77.org/data/images/full/365645/as-virus-kills-navajos-in-their-homes-tribal-women-provide-lifeline.jpg?w=600?w=650").is_ok()); - assert!(is_image_content_type( - "https://twitter.com/BenjaminNorton/status/1259922424272957440?s=20" - ) - .is_err()); + actix_rt::System::new("tset_image").block_on(async move { + let client = actix_web::client::Client::default(); + assert!(is_image_content_type(&client, "https://1734811051.rsc.cdn77.org/data/images/full/365645/as-virus-kills-navajos-in-their-homes-tribal-women-provide-lifeline.jpg?w=600?w=650").await.is_ok()); + assert!(is_image_content_type(&client, + "https://twitter.com/BenjaminNorton/status/1259922424272957440?s=20" + ) + .await.is_err() + ); + }); } #[test] @@ -399,7 +459,7 @@ mod tests { // These helped with testing // #[test] // fn test_iframely() { - // let res = fetch_iframely("https://www.redspark.nu/?p=15341"); + // let res = fetch_iframely(client, "https://www.redspark.nu/?p=15341").await; // assert!(res.is_ok()); // } diff --git a/server/src/main.rs b/server/src/main.rs index 2f53f3ac6..30be711fa 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -4,10 +4,13 @@ extern crate diesel_migrations; #[macro_use] pub extern crate lazy_static; +pub type DbPool = Pool>; + use crate::lemmy_server::actix_web::dev::Service; use actix::prelude::*; use actix_web::{ body::Body, + client::Client, dev::{ServiceRequest, ServiceResponse}, http::{ header::{CACHE_CONTROL, CONTENT_TYPE}, @@ -20,14 +23,16 @@ use diesel::{ PgConnection, }; use lemmy_server::{ + blocking, db::code_migrations::run_advanced_migrations, rate_limit::{rate_limiter::RateLimiter, RateLimit}, routes::{api, federation, feeds, index, nodeinfo, webfinger}, settings::Settings, websocket::server::*, + LemmyError, }; use regex::Regex; -use std::{io, sync::Arc}; +use std::sync::Arc; use tokio::sync::Mutex; lazy_static! { @@ -41,7 +46,7 @@ lazy_static! { embed_migrations!(); #[actix_rt::main] -async fn main() -> io::Result<()> { +async fn main() -> Result<(), LemmyError> { env_logger::init(); let settings = Settings::get(); @@ -53,9 +58,12 @@ async fn main() -> io::Result<()> { .unwrap_or_else(|_| panic!("Error connecting to {}", settings.get_database_url())); // Run the migrations from code - let conn = pool.get().unwrap(); - embedded_migrations::run(&conn).unwrap(); - run_advanced_migrations(&conn).unwrap(); + blocking(&pool, move |conn| { + embedded_migrations::run(conn)?; + run_advanced_migrations(conn)?; + Ok(()) as Result<(), LemmyError> + }) + .await??; // Set up the rate limiter let rate_limiter = RateLimit { @@ -63,7 +71,7 @@ async fn main() -> io::Result<()> { }; // Set up websocket server - let server = ChatServer::startup(pool.clone(), rate_limiter.clone()).start(); + let server = ChatServer::startup(pool.clone(), rate_limiter.clone(), Client::default()).start(); println!( "Starting http server at {}:{}", @@ -79,6 +87,7 @@ async fn main() -> io::Result<()> { .wrap(middleware::Logger::default()) .data(pool.clone()) .data(server.clone()) + .data(Client::default()) // The routes .configure(move |cfg| api::config(cfg, &rate_limiter)) .configure(federation::config) @@ -98,7 +107,9 @@ async fn main() -> io::Result<()> { }) .bind((settings.bind, settings.port))? .run() - .await + .await?; + + Ok(()) } fn add_cache_headers( diff --git a/server/src/rate_limit/mod.rs b/server/src/rate_limit/mod.rs index b4c2dc5db..e49a527e8 100644 --- a/server/src/rate_limit/mod.rs +++ b/server/src/rate_limit/mod.rs @@ -1,5 +1,5 @@ use super::{IPAddr, Settings}; -use crate::{api::APIError, get_ip, settings::RateLimitConfig}; +use crate::{get_ip, settings::RateLimitConfig, LemmyError}; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use futures::future::{ok, Ready}; use rate_limiter::{RateLimitType, RateLimiter}; @@ -15,6 +15,8 @@ pub mod rate_limiter; #[derive(Debug, Clone)] pub struct RateLimit { + // it might be reasonable to use a std::sync::Mutex here, since we don't need to lock this + // across await points pub rate_limiter: Arc>, } @@ -57,17 +59,11 @@ impl RateLimited { fut: impl Future>, ) -> Result where - E: From, + E: From, { - let rate_limit: RateLimitConfig = actix_web::web::block(move || { - // needs to be in a web::block because the RwLock in settings is from stdlib - Ok(Settings::get().rate_limit) as Result<_, failure::Error> - }) - .await - .map_err(|e| match e { - actix_web::error::BlockingError::Error(e) => e, - _ => APIError::err("Operation canceled").into(), - })?; + // Does not need to be blocking because the RwLock in settings never held across await points, + // and the operation here locks only long enough to clone + let rate_limit: RateLimitConfig = Settings::get().rate_limit; // before { @@ -83,6 +79,7 @@ impl RateLimited { false, )?; + drop(limiter); return fut.await; } RateLimitType::Post => { diff --git a/server/src/rate_limit/rate_limiter.rs b/server/src/rate_limit/rate_limiter.rs index b3ac7093c..3e2ea787b 100644 --- a/server/src/rate_limit/rate_limiter.rs +++ b/server/src/rate_limit/rate_limiter.rs @@ -1,6 +1,5 @@ use super::IPAddr; -use crate::api::APIError; -use failure::Error; +use crate::{api::APIError, LemmyError}; use log::debug; use std::{collections::HashMap, time::SystemTime}; use strum::IntoEnumIterator; @@ -61,7 +60,7 @@ impl RateLimiter { rate: i32, per: i32, check_only: bool, - ) -> Result<(), Error> { + ) -> Result<(), LemmyError> { self.insert_ip(ip); if let Some(bucket) = self.buckets.get_mut(&type_) { if let Some(rate_limit) = bucket.get_mut(ip) { diff --git a/server/src/request.rs b/server/src/request.rs new file mode 100644 index 000000000..7d09b60df --- /dev/null +++ b/server/src/request.rs @@ -0,0 +1,51 @@ +use crate::LemmyError; +use std::future::Future; + +#[derive(Clone, Debug, Fail)] +#[fail(display = "Error sending request, {}", _0)] +struct SendError(pub String); + +#[derive(Clone, Debug, Fail)] +#[fail(display = "Error receiving response, {}", _0)] +pub struct RecvError(pub String); + +pub async fn retry(f: F) -> Result +where + F: Fn() -> Fut, + Fut: Future>, +{ + retry_custom(|| async { Ok((f)().await) }).await +} + +pub async fn retry_custom(f: F) -> Result +where + F: Fn() -> Fut, + Fut: Future, LemmyError>>, +{ + let mut response = Err(format_err!("connect timeout").into()); + + for _ in 0u8..3 { + match (f)().await? { + Ok(t) => return Ok(t), + Err(e) => { + if is_connect_timeout(&e) { + response = Err(SendError(e.to_string()).into()); + continue; + } + return Err(SendError(e.to_string()).into()); + } + } + } + + response +} + +fn is_connect_timeout(e: &actix_web::client::SendRequestError) -> bool { + if let actix_web::client::SendRequestError::Connect(e) = e { + if let actix_web::client::ConnectError::Timeout = e { + return true; + } + } + + false +} diff --git a/server/src/routes/api.rs b/server/src/routes/api.rs index 6ee94691d..35e495fa5 100644 --- a/server/src/routes/api.rs +++ b/server/src/routes/api.rs @@ -4,7 +4,7 @@ use crate::{ routes::{ChatServerParam, DbPoolParam}, websocket::WebsocketInfo, }; -use actix_web::{error::ErrorBadRequest, *}; +use actix_web::{client::Client, error::ErrorBadRequest, *}; use serde::Serialize; pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { @@ -150,6 +150,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { async fn perform( data: Request, + client: &Client, db: DbPoolParam, chat_server: ChatServerParam, ) -> Result @@ -162,9 +163,10 @@ where id: None, }; - let oper: Oper = Oper::new(data); + let oper: Oper = Oper::new(data, client.clone()); - let res = web::block(move || oper.perform(db.get_ref().to_owned(), Some(ws_info))) + let res = oper + .perform(&db, Some(ws_info)) .await .map(|json| HttpResponse::Ok().json(json)) .map_err(ErrorBadRequest)?; @@ -173,6 +175,7 @@ where async fn route_get( data: web::Query, + client: web::Data, db: DbPoolParam, chat_server: ChatServerParam, ) -> Result @@ -180,11 +183,12 @@ where Data: Serialize + Send + 'static, Oper: Perform, { - perform::(data.0, db, chat_server).await + perform::(data.0, &client, db, chat_server).await } async fn route_post( data: web::Json, + client: web::Data, db: DbPoolParam, chat_server: ChatServerParam, ) -> Result @@ -192,5 +196,5 @@ where Data: Serialize + Send + 'static, Oper: Perform, { - perform::(data.0, db, chat_server).await + perform::(data.0, &client, db, chat_server).await } diff --git a/server/src/routes/feeds.rs b/server/src/routes/feeds.rs index b76751a67..a1c2ba58f 100644 --- a/server/src/routes/feeds.rs +++ b/server/src/routes/feeds.rs @@ -1,4 +1,5 @@ use crate::{ + blocking, db::{ comment_view::{ReplyQueryBuilder, ReplyView}, community::Community, @@ -12,6 +13,7 @@ use crate::{ markdown_to_html, routes::DbPoolParam, settings::Settings, + LemmyError, }; use actix_web::{error::ErrorBadRequest, *}; use chrono::{DateTime, NaiveDateTime, Utc}; @@ -43,21 +45,20 @@ pub fn config(cfg: &mut web::ServiceConfig) { } async fn get_all_feed(info: web::Query, db: DbPoolParam) -> Result { - let res = web::block(move || { - let conn = db.get()?; - get_feed_all_data(&conn, &get_sort_type(info)?) - }) - .await - .map(|rss| { + let sort_type = get_sort_type(info).map_err(ErrorBadRequest)?; + + let rss = blocking(&db, move |conn| get_feed_all_data(conn, &sort_type)) + .await? + .map_err(ErrorBadRequest)?; + + Ok( HttpResponse::Ok() .content_type("application/rss+xml") - .body(rss) - }) - .map_err(ErrorBadRequest)?; - Ok(res) + .body(rss), + ) } -fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result { +fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result { let site_view = SiteView::read(&conn)?; let posts = PostQueryBuilder::create(&conn) @@ -85,37 +86,34 @@ async fn get_feed( info: web::Query, db: web::Data>>, ) -> Result { - let res = web::block(move || { - let conn = db.get()?; + let sort_type = get_sort_type(info).map_err(ErrorBadRequest)?; - let sort_type = get_sort_type(info)?; + let request_type = match path.0.as_ref() { + "u" => RequestType::User, + "c" => RequestType::Community, + "front" => RequestType::Front, + "inbox" => RequestType::Inbox, + _ => return Err(ErrorBadRequest(LemmyError::from(format_err!("wrong_type")))), + }; - let request_type = match path.0.as_ref() { - "u" => RequestType::User, - "c" => RequestType::Community, - "front" => RequestType::Front, - "inbox" => RequestType::Inbox, - _ => return Err(format_err!("wrong_type")), - }; + let param = path.1.to_owned(); - let param = path.1.to_owned(); - - match request_type { - RequestType::User => get_feed_user(&conn, &sort_type, param), - RequestType::Community => get_feed_community(&conn, &sort_type, param), - RequestType::Front => get_feed_front(&conn, &sort_type, param), - RequestType::Inbox => get_feed_inbox(&conn, param), - } + let builder = blocking(&db, move |conn| match request_type { + RequestType::User => get_feed_user(conn, &sort_type, param), + RequestType::Community => get_feed_community(conn, &sort_type, param), + RequestType::Front => get_feed_front(conn, &sort_type, param), + RequestType::Inbox => get_feed_inbox(conn, param), }) - .await - .map(|builder| builder.build().unwrap().to_string()) - .map(|rss| { + .await? + .map_err(ErrorBadRequest)?; + + let rss = builder.build().map_err(ErrorBadRequest)?.to_string(); + + Ok( HttpResponse::Ok() .content_type("application/rss+xml") - .body(rss) - }) - .map_err(ErrorBadRequest)?; - Ok(res) + .body(rss), + ) } fn get_sort_type(info: web::Query) -> Result { @@ -130,7 +128,7 @@ fn get_feed_user( conn: &PgConnection, sort_type: &SortType, user_name: String, -) -> Result { +) -> Result { let site_view = SiteView::read(&conn)?; let user = User_::find_by_username(&conn, &user_name)?; let user_url = user.get_profile_url(); @@ -156,7 +154,7 @@ fn get_feed_community( conn: &PgConnection, sort_type: &SortType, community_name: String, -) -> Result { +) -> Result { let site_view = SiteView::read(&conn)?; let community = Community::read_from_name(&conn, &community_name)?; @@ -185,7 +183,7 @@ fn get_feed_front( conn: &PgConnection, sort_type: &SortType, jwt: String, -) -> Result { +) -> Result { let site_view = SiteView::read(&conn)?; let user_id = Claims::decode(&jwt)?.claims.id; @@ -210,7 +208,7 @@ fn get_feed_front( Ok(channel_builder) } -fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result { +fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result { let site_view = SiteView::read(&conn)?; let user_id = Claims::decode(&jwt)?.claims.id; diff --git a/server/src/routes/nodeinfo.rs b/server/src/routes/nodeinfo.rs index db206a3e8..ff728fe3e 100644 --- a/server/src/routes/nodeinfo.rs +++ b/server/src/routes/nodeinfo.rs @@ -1,8 +1,10 @@ use crate::{ apub::get_apub_protocol_string, + blocking, db::site_view::SiteView, routes::DbPoolParam, version, + LemmyError, Settings, }; use actix_web::{body::Body, error::ErrorBadRequest, *}; @@ -15,7 +17,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { .route("/.well-known/nodeinfo", web::get().to(node_info_well_known)); } -async fn node_info_well_known() -> Result, failure::Error> { +async fn node_info_well_known() -> Result, LemmyError> { let node_info = NodeInfoWellKnown { links: NodeInfoWellKnownLinks { rel: Url::parse("http://nodeinfo.diaspora.software/ns/schema/2.0")?, @@ -30,38 +32,34 @@ async fn node_info_well_known() -> Result, failure::Error> { } async fn node_info(db: DbPoolParam) -> Result { - let res = web::block(move || { - let conn = db.get()?; - let site_view = match SiteView::read(&conn) { - Ok(site_view) => site_view, - Err(_) => return Err(format_err!("not_found")), - }; - let protocols = if Settings::get().federation.enabled { - vec!["activitypub".to_string()] - } else { - vec![] - }; - Ok(NodeInfo { - version: "2.0".to_string(), - software: NodeInfoSoftware { - name: "lemmy".to_string(), - version: version::VERSION.to_string(), + let site_view = blocking(&db, SiteView::read) + .await? + .map_err(|_| ErrorBadRequest(LemmyError::from(format_err!("not_found"))))?; + + let protocols = if Settings::get().federation.enabled { + vec!["activitypub".to_string()] + } else { + vec![] + }; + + let json = NodeInfo { + version: "2.0".to_string(), + software: NodeInfoSoftware { + name: "lemmy".to_string(), + version: version::VERSION.to_string(), + }, + protocols, + usage: NodeInfoUsage { + users: NodeInfoUsers { + total: site_view.number_of_users, }, - protocols, - usage: NodeInfoUsage { - users: NodeInfoUsers { - total: site_view.number_of_users, - }, - local_posts: site_view.number_of_posts, - local_comments: site_view.number_of_comments, - open_registrations: site_view.open_registration, - }, - }) - }) - .await - .map(|json| HttpResponse::Ok().json(json)) - .map_err(ErrorBadRequest)?; - Ok(res) + local_posts: site_view.number_of_posts, + local_comments: site_view.number_of_comments, + open_registrations: site_view.open_registration, + }, + }; + + Ok(HttpResponse::Ok().json(json)) } #[derive(Serialize, Deserialize, Debug)] diff --git a/server/src/routes/webfinger.rs b/server/src/routes/webfinger.rs index 9fa01a147..af021dd5f 100644 --- a/server/src/routes/webfinger.rs +++ b/server/src/routes/webfinger.rs @@ -1,6 +1,8 @@ use crate::{ + blocking, db::{community::Community, user::User_}, routes::DbPoolParam, + LemmyError, Settings, }; use actix_web::{error::ErrorBadRequest, web::Query, *}; @@ -61,64 +63,58 @@ async fn get_webfinger_response( info: Query, db: DbPoolParam, ) -> Result { - let res = web::block(move || { - let conn = db.get()?; + let community_regex_parsed = WEBFINGER_COMMUNITY_REGEX + .captures(&info.resource) + .map(|c| c.get(1)) + .flatten(); - let community_regex_parsed = WEBFINGER_COMMUNITY_REGEX - .captures(&info.resource) - .map(|c| c.get(1)) - .flatten(); + let user_regex_parsed = WEBFINGER_USER_REGEX + .captures(&info.resource) + .map(|c| c.get(1)) + .flatten(); - let user_regex_parsed = WEBFINGER_USER_REGEX - .captures(&info.resource) - .map(|c| c.get(1)) - .flatten(); + let url = if let Some(community_name) = community_regex_parsed { + let community_name = community_name.as_str().to_owned(); + // Make sure the requested community exists. + blocking(&db, move |conn| { + Community::read_from_name(conn, &community_name) + }) + .await? + .map_err(|_| ErrorBadRequest(LemmyError::from(format_err!("not_found"))))? + .actor_id + } else if let Some(user_name) = user_regex_parsed { + let user_name = user_name.as_str().to_owned(); + // Make sure the requested user exists. + blocking(&db, move |conn| User_::read_from_name(conn, &user_name)) + .await? + .map_err(|_| ErrorBadRequest(LemmyError::from(format_err!("not_found"))))? + .actor_id + } else { + return Err(ErrorBadRequest(LemmyError::from(format_err!("not_found")))); + }; - let url = if let Some(community_name) = community_regex_parsed { - // Make sure the requested community exists. - let community = match Community::read_from_name(&conn, &community_name.as_str()) { - Ok(o) => o, - Err(_) => return Err(format_err!("not_found")), - }; - community.actor_id - } else if let Some(user_name) = user_regex_parsed { - // Make sure the requested user exists. - let user = match User_::read_from_name(&conn, &user_name.as_str()) { - Ok(o) => o, - Err(_) => return Err(format_err!("not_found")), - }; - user.actor_id - } else { - return Err(format_err!("not_found")); - }; + let json = WebFingerResponse { + subject: info.resource.to_owned(), + aliases: vec![url.to_owned()], + links: vec![ + WebFingerLink { + rel: Some("http://webfinger.net/rel/profile-page".to_string()), + type_: Some("text/html".to_string()), + href: Some(url.to_owned()), + template: None, + }, + WebFingerLink { + rel: Some("self".to_string()), + type_: Some("application/activity+json".to_string()), + href: Some(url), + template: None, + }, // TODO: this also needs to return the subscribe link once that's implemented + //{ + // "rel": "http://ostatus.org/schema/1.0/subscribe", + // "template": "https://my_instance.com/authorize_interaction?uri={uri}" + //} + ], + }; - let wf_res = WebFingerResponse { - subject: info.resource.to_owned(), - aliases: vec![url.to_owned()], - links: vec![ - WebFingerLink { - rel: Some("http://webfinger.net/rel/profile-page".to_string()), - type_: Some("text/html".to_string()), - href: Some(url.to_owned()), - template: None, - }, - WebFingerLink { - rel: Some("self".to_string()), - type_: Some("application/activity+json".to_string()), - href: Some(url), - template: None, - }, // TODO: this also needs to return the subscribe link once that's implemented - //{ - // "rel": "http://ostatus.org/schema/1.0/subscribe", - // "template": "https://my_instance.com/authorize_interaction?uri={uri}" - //} - ], - }; - - Ok(wf_res) - }) - .await - .map(|json| HttpResponse::Ok().json(json)) - .map_err(ErrorBadRequest)?; - Ok(res) + Ok(HttpResponse::Ok().json(json)) } diff --git a/server/src/settings.rs b/server/src/settings.rs index b3173457f..12ffaceab 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -1,5 +1,5 @@ +use crate::LemmyError; use config::{Config, ConfigError, Environment, File}; -use failure::Error; use serde::Deserialize; use std::{env, fs, net::IpAddr, sync::RwLock}; @@ -118,11 +118,11 @@ impl Settings { format!("{}/api/v1", self.hostname) } - pub fn read_config_file() -> Result { + pub fn read_config_file() -> Result { Ok(fs::read_to_string(CONFIG_FILE)?) } - pub fn save_config_file(data: &str) -> Result { + pub fn save_config_file(data: &str) -> Result { fs::write(CONFIG_FILE, data)?; // Reload the new settings diff --git a/server/src/websocket/mod.rs b/server/src/websocket/mod.rs index 4eb43e49d..cdaf4f304 100644 --- a/server/src/websocket/mod.rs +++ b/server/src/websocket/mod.rs @@ -6,7 +6,6 @@ use diesel::{ r2d2::{ConnectionManager, Pool}, PgConnection, }; -use failure::Error; use log::{error, info}; use rand::{rngs::ThreadRng, Rng}; use serde::{Deserialize, Serialize}; diff --git a/server/src/websocket/server.rs b/server/src/websocket/server.rs index e4543ea1f..aef0abb8a 100644 --- a/server/src/websocket/server.rs +++ b/server/src/websocket/server.rs @@ -9,10 +9,13 @@ use crate::{ websocket::UserOperation, CommunityId, ConnectionId, + DbPool, IPAddr, + LemmyError, PostId, UserId, }; +use actix_web::client::Client; /// Chat server sends this messages to session #[derive(Message)] @@ -154,12 +157,16 @@ pub struct ChatServer { /// Rate limiting based on rate type and IP addr rate_limiter: RateLimit, + + /// An HTTP Client + client: Client, } impl ChatServer { pub fn startup( pool: Pool>, rate_limiter: RateLimit, + client: Client, ) -> ChatServer { ChatServer { sessions: HashMap::new(), @@ -169,6 +176,7 @@ impl ChatServer { rng: rand::thread_rng(), pool, rate_limiter, + client, } } @@ -236,7 +244,7 @@ impl ChatServer { response: &Response, post_id: PostId, my_id: Option, - ) -> Result<(), Error> + ) -> Result<(), LemmyError> where Response: Serialize, { @@ -260,7 +268,7 @@ impl ChatServer { response: &Response, community_id: CommunityId, my_id: Option, - ) -> Result<(), Error> + ) -> Result<(), LemmyError> where Response: Serialize, { @@ -283,7 +291,7 @@ impl ChatServer { op: &UserOperation, response: &Response, my_id: Option, - ) -> Result<(), Error> + ) -> Result<(), LemmyError> where Response: Serialize, { @@ -305,7 +313,7 @@ impl ChatServer { response: &Response, recipient_id: UserId, my_id: Option, - ) -> Result<(), Error> + ) -> Result<(), LemmyError> where Response: Serialize, { @@ -328,7 +336,7 @@ impl ChatServer { user_operation: &UserOperation, comment: &CommentResponse, my_id: Option, - ) -> Result<(), Error> { + ) -> Result<(), LemmyError> { let mut comment_reply_sent = comment.clone(); comment_reply_sent.comment.my_vote = None; comment_reply_sent.comment.user_id = None; @@ -366,7 +374,7 @@ impl ChatServer { user_operation: &UserOperation, post: &PostResponse, my_id: Option, - ) -> Result<(), Error> { + ) -> Result<(), LemmyError> { let community_id = post.post.community_id; // Don't send my data with it @@ -394,7 +402,7 @@ impl ChatServer { &mut self, msg: StandardMessage, ctx: &mut Context, - ) -> impl Future> { + ) -> impl Future> { let addr = ctx.address(); let pool = self.pool.clone(); let rate_limiter = self.rate_limiter.clone(); @@ -404,6 +412,7 @@ impl ChatServer { None => "blank_ip".to_string(), }; + let client = self.client.clone(); async move { let msg = msg; let json: Value = serde_json::from_str(&msg.msg)?; @@ -414,482 +423,109 @@ impl ChatServer { let user_operation: UserOperation = UserOperation::from_str(&op)?; + let args = Args { + client, + pool, + rate_limiter, + chatserver: addr, + id: msg.id, + ip, + op: user_operation.clone(), + data, + }; + match user_operation { // User ops - UserOperation::Login => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::Register => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::GetUserDetails => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::GetReplies => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::AddAdmin => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::BanUser => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::GetUserMentions => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::EditUserMention => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::MarkAllAsRead => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::DeleteAccount => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::PasswordReset => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::PasswordChange => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } + UserOperation::Login => do_user_operation::(args).await, + UserOperation::Register => do_user_operation::(args).await, + UserOperation::GetUserDetails => do_user_operation::(args).await, + UserOperation::GetReplies => do_user_operation::(args).await, + UserOperation::AddAdmin => do_user_operation::(args).await, + UserOperation::BanUser => do_user_operation::(args).await, + UserOperation::GetUserMentions => do_user_operation::(args).await, + UserOperation::EditUserMention => do_user_operation::(args).await, + UserOperation::MarkAllAsRead => do_user_operation::(args).await, + UserOperation::DeleteAccount => do_user_operation::(args).await, + UserOperation::PasswordReset => do_user_operation::(args).await, + UserOperation::PasswordChange => do_user_operation::(args).await, UserOperation::CreatePrivateMessage => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::EditPrivateMessage => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::GetPrivateMessages => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::UserJoin => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::SaveUserSettings => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await + do_user_operation::(args).await } + UserOperation::EditPrivateMessage => do_user_operation::(args).await, + UserOperation::GetPrivateMessages => do_user_operation::(args).await, + UserOperation::UserJoin => do_user_operation::(args).await, + UserOperation::SaveUserSettings => do_user_operation::(args).await, // Site ops - UserOperation::GetModlog => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::CreateSite => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::EditSite => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::GetSite => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::GetSiteConfig => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::SaveSiteConfig => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::Search => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::TransferCommunity => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::TransferSite => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::ListCategories => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } + UserOperation::GetModlog => do_user_operation::(args).await, + UserOperation::CreateSite => do_user_operation::(args).await, + UserOperation::EditSite => do_user_operation::(args).await, + UserOperation::GetSite => do_user_operation::(args).await, + UserOperation::GetSiteConfig => do_user_operation::(args).await, + UserOperation::SaveSiteConfig => do_user_operation::(args).await, + UserOperation::Search => do_user_operation::(args).await, + UserOperation::TransferCommunity => do_user_operation::(args).await, + UserOperation::TransferSite => do_user_operation::(args).await, + UserOperation::ListCategories => do_user_operation::(args).await, // Community ops - UserOperation::GetCommunity => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::ListCommunities => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::CreateCommunity => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::EditCommunity => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::FollowCommunity => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } + UserOperation::GetCommunity => do_user_operation::(args).await, + UserOperation::ListCommunities => do_user_operation::(args).await, + UserOperation::CreateCommunity => do_user_operation::(args).await, + UserOperation::EditCommunity => do_user_operation::(args).await, + UserOperation::FollowCommunity => do_user_operation::(args).await, UserOperation::GetFollowedCommunities => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::BanFromCommunity => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::AddModToCommunity => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await + do_user_operation::(args).await } + UserOperation::BanFromCommunity => do_user_operation::(args).await, + UserOperation::AddModToCommunity => do_user_operation::(args).await, // Post ops - UserOperation::CreatePost => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::GetPost => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::GetPosts => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::EditPost => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::CreatePostLike => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::SavePost => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } + UserOperation::CreatePost => do_user_operation::(args).await, + UserOperation::GetPost => do_user_operation::(args).await, + UserOperation::GetPosts => do_user_operation::(args).await, + UserOperation::EditPost => do_user_operation::(args).await, + UserOperation::CreatePostLike => do_user_operation::(args).await, + UserOperation::SavePost => do_user_operation::(args).await, // Comment ops - UserOperation::CreateComment => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::EditComment => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::SaveComment => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::GetComments => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::CreateCommentLike => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } + UserOperation::CreateComment => do_user_operation::(args).await, + UserOperation::EditComment => do_user_operation::(args).await, + UserOperation::SaveComment => do_user_operation::(args).await, + UserOperation::GetComments => do_user_operation::(args).await, + UserOperation::CreateCommentLike => do_user_operation::(args).await, } } } } -async fn do_user_operation<'a, Data>( - pool: Pool>, +struct Args<'a> { + client: Client, + pool: DbPool, rate_limiter: RateLimit, chatserver: Addr, id: ConnectionId, ip: IPAddr, op: UserOperation, - data: &str, -) -> Result + data: &'a str, +} + +async fn do_user_operation<'a, 'b, Data>(args: Args<'b>) -> Result where for<'de> Data: Deserialize<'de> + 'a, Oper: Perform, { + let Args { + client, + pool, + rate_limiter, + chatserver, + id, + ip, + op, + data, + } = args; + let ws_info = WebsocketInfo { chatserver, id: Some(id), @@ -898,17 +534,14 @@ where let data = data.to_string(); let op2 = op.clone(); + let client = client.clone(); let fut = async move { - actix_web::web::block(move || { - let parsed_data: Data = serde_json::from_str(&data)?; - let res = Oper::new(parsed_data).perform(pool, Some(ws_info))?; - to_json_string(&op, &res) - }) - .await - .map_err(|e| match e { - actix_web::error::BlockingError::Error(e) => e, - _ => APIError::err("Operation canceled").into(), - }) + let pool = pool.clone(); + let parsed_data: Data = serde_json::from_str(&data)?; + let res = Oper::new(parsed_data, client) + .perform(&pool, Some(ws_info)) + .await?; + to_json_string(&op, &res) }; match op2 { @@ -1109,7 +742,7 @@ struct WebsocketResponse { data: T, } -fn to_json_string(op: &UserOperation, data: &Response) -> Result +fn to_json_string(op: &UserOperation, data: &Response) -> Result where Response: Serialize, { diff --git a/ui/src/api_tests/api.spec.ts b/ui/src/api_tests/api.spec.ts index 7337201c7..41710e11b 100644 --- a/ui/src/api_tests/api.spec.ts +++ b/ui/src/api_tests/api.spec.ts @@ -124,10 +124,10 @@ describe('main', () => { }); describe('follow_accept', () => { - test('/u/lemmy_alpha follows and accepts lemmy_beta/c/main', async () => { - // Make sure lemmy_beta/c/main is cached on lemmy_alpha + test('/u/lemmy_alpha follows and accepts lemmy-beta/c/main', async () => { + // Make sure lemmy-beta/c/main is cached on lemmy_alpha // Use short-hand search url - let searchUrl = `${lemmyAlphaApiUrl}/search?q=!main@lemmy_beta:8550&type_=All&sort=TopAll`; + let searchUrl = `${lemmyAlphaApiUrl}/search?q=!main@lemmy-beta:8550&type_=All&sort=TopAll`; let searchResponse: SearchResponse = await fetch(searchUrl, { method: 'GET', @@ -215,7 +215,7 @@ describe('main', () => { // Also make G follow B // Use short-hand search url - let searchUrlG = `${lemmyGammaApiUrl}/search?q=!main@lemmy_beta:8550&type_=All&sort=TopAll`; + let searchUrlG = `${lemmyGammaApiUrl}/search?q=!main@lemmy-beta:8550&type_=All&sort=TopAll`; let searchResponseG: SearchResponse = await fetch(searchUrlG, { method: 'GET', @@ -449,7 +449,7 @@ describe('main', () => { // Lemmy alpha responds to their own comment, but mentions lemmy beta. // Make sure lemmy beta gets that in their inbox. - let mentionContent = 'A test mention of @lemmy_beta@lemmy_beta:8550'; + let mentionContent = 'A test mention of @lemmy_beta@lemmy-beta:8550'; let mentionCommentForm: CommentForm = { content: mentionContent, post_id: 2, @@ -550,7 +550,7 @@ describe('main', () => { expect(createCommunityRes.community.name).toBe(communityName); // Cache it on lemmy_alpha - let searchUrl = `${lemmyAlphaApiUrl}/search?q=http://lemmy_beta:8550/c/${communityName}&type_=All&sort=TopAll`; + let searchUrl = `${lemmyAlphaApiUrl}/search?q=http://lemmy-beta:8550/c/${communityName}&type_=All&sort=TopAll`; let searchResponse: SearchResponse = await fetch(searchUrl, { method: 'GET', }).then(d => d.json()); @@ -826,7 +826,7 @@ describe('main', () => { expect(createCommunityRes.community.name).toBe(communityName); // Cache it on lemmy_alpha - let searchUrl = `${lemmyAlphaApiUrl}/search?q=http://lemmy_beta:8550/c/${communityName}&type_=All&sort=TopAll`; + let searchUrl = `${lemmyAlphaApiUrl}/search?q=http://lemmy-beta:8550/c/${communityName}&type_=All&sort=TopAll`; let searchResponse: SearchResponse = await fetch(searchUrl, { method: 'GET', }).then(d => d.json()); @@ -1278,7 +1278,7 @@ describe('main', () => { // Create a test comment on Gamma, make sure it gets announced to alpha let commentContent = - 'A jest test federated comment announce, lets mention @lemmy_beta@lemmy_beta:8550'; + 'A jest test federated comment announce, lets mention @lemmy_beta@lemmy-beta:8550'; let commentForm: CommentForm = { content: commentContent, @@ -1417,7 +1417,7 @@ describe('main', () => { expect(createChildCommentRes.comment.content).toBe(childCommentContent); // Follow again, for other tests - let searchUrl = `${lemmyAlphaApiUrl}/search?q=!main@lemmy_beta:8550&type_=All&sort=TopAll`; + let searchUrl = `${lemmyAlphaApiUrl}/search?q=!main@lemmy-beta:8550&type_=All&sort=TopAll`; let searchResponse: SearchResponse = await fetch(searchUrl, { method: 'GET', From 1c0cc78f3f6d191aa384d8702016564625d51269 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 1 Jul 2020 10:45:56 -0400 Subject: [PATCH 10/26] Adding \b before a slur. --- server/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/lib.rs b/server/src/lib.rs index 3d09b25d1..04f376fb4 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -480,7 +480,7 @@ mod tests { lazy_static! { static ref EMAIL_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap(); - static ref SLUR_REGEX: Regex = RegexBuilder::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|n(i|1)g(\b|g?(a|er)?(s|z)?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btr(a|@)nn?(y|ies?)|ladyboy(s?)|\b(b|re|r)tard(ed)?s?)").case_insensitive(true).build().unwrap(); + static ref SLUR_REGEX: Regex = RegexBuilder::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|\bn(i|1)g(\b|g?(a|er)?(s|z)?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btr(a|@)nn?(y|ies?)|ladyboy(s?)|\b(b|re|r)tard(ed)?s?)").case_insensitive(true).build().unwrap(); static ref USERNAME_MATCHES_REGEX: Regex = Regex::new(r"/u/[a-zA-Z][0-9a-zA-Z_]*").unwrap(); // TODO keep this old one, it didn't work with port well tho // static ref WEBFINGER_USER_REGEX: Regex = Regex::new(r"@(?P[\w.]+)@(?P[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)").unwrap(); From 360e7aec424ca07a0329e18549f8dce692495ed8 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 1 Jul 2020 18:50:38 -0400 Subject: [PATCH 11/26] Adding Albanian for weblate. --- ui/src/i18next.ts | 2 ++ ui/src/utils.ts | 4 ++++ ui/translations/sq.json | 1 + 3 files changed, 7 insertions(+) create mode 100644 ui/translations/sq.json diff --git a/ui/src/i18next.ts b/ui/src/i18next.ts index 5fa8f4e88..7a341ceab 100644 --- a/ui/src/i18next.ts +++ b/ui/src/i18next.ts @@ -24,6 +24,7 @@ import { gl } from './translations/gl'; import { tr } from './translations/tr'; import { hu } from './translations/hu'; import { uk } from './translations/uk'; +import { sq } from './translations/sq'; // https://github.com/nimbusec-oss/inferno-i18next/blob/master/tests/T.test.js#L66 const resources = { @@ -51,6 +52,7 @@ const resources = { tr, hu, uk, + sq, }; function format(value: any, format: any, lng: any): any { diff --git a/ui/src/utils.ts b/ui/src/utils.ts index 7bf6fdd1d..48d2095df 100644 --- a/ui/src/utils.ts +++ b/ui/src/utils.ts @@ -21,6 +21,7 @@ import 'moment/locale/gl'; import 'moment/locale/tr'; import 'moment/locale/hu'; import 'moment/locale/uk'; +import 'moment/locale/sq'; import { UserOperation, @@ -83,6 +84,7 @@ export const languages = [ { code: 'fi', name: 'Suomi' }, { code: 'fr', name: 'Français' }, { code: 'sv', name: 'Svenska' }, + { code: 'sq', name: 'Shqip' }, { code: 'tr', name: 'Türkçe' }, { code: 'uk', name: 'українська мова' }, { code: 'ru', name: 'Русский' }, @@ -414,6 +416,8 @@ export function getMomentLanguage(): string { lang = 'hu'; } else if (lang.startsWith('uk')) { lang = 'uk'; + } else if (lang.startsWith('sq')) { + lang = 'sq'; } else { lang = 'en'; } diff --git a/ui/translations/sq.json b/ui/translations/sq.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/ui/translations/sq.json @@ -0,0 +1 @@ +{} From fee0a0c867198df6855ef3ba27b8b3456611c387 Mon Sep 17 00:00:00 2001 From: Tirifto Date: Wed, 1 Jul 2020 22:53:10 +0000 Subject: [PATCH 12/26] Translated using Weblate (Esperanto) Currently translated at 93.9% (234 of 249 strings) Translation: Lemmy/lemmy Translate-URL: http://weblate.yerbamate.dev/projects/lemmy/lemmy/eo/ Translated using Weblate (Esperanto) Currently translated at 76.3% (190 of 249 strings) Translation: Lemmy/lemmy Translate-URL: http://weblate.yerbamate.dev/projects/lemmy/lemmy/eo/ --- ui/translations/eo.json | 231 ++++++++++++++++++++++++++-------------- 1 file changed, 154 insertions(+), 77 deletions(-) diff --git a/ui/translations/eo.json b/ui/translations/eo.json index 5bde84f46..0cbbb7ec4 100644 --- a/ui/translations/eo.json +++ b/ui/translations/eo.json @@ -1,30 +1,31 @@ { - "post": "Poŝti", - "remove_post": "Fortiri Poŝton", - "no_posts": "Ne Poŝtoj.", - "create_a_post": "Verki Poŝton", - "create_post": "Verki Poŝton", - "number_of_posts": "{{count}} Poŝtoj", - "posts": "Poŝtoj", - "related_posts": "Tiuj poŝtoj eble rilatas", - "cross_posts": "Tiuj ligilo ankaŭ estas poŝtinta al:", - "cross_post": "laŭapoŝto", + "post": "Afiŝi", + "remove_post": "Forigi afiŝon", + "no_posts": "Neniuj afiŝoj.", + "create_a_post": "Verki afiŝon", + "create_post": "Verki afiŝon", + "number_of_posts": "{{count}} afiŝo", + "number_of_posts_plural": "{{count}} afiŝoj", + "posts": "Afiŝoj", + "related_posts": "Ĉi tiuj afiŝoj eble rilatas", + "cross_posts": "Tiu ligilo ankaŭ estas afiŝita al:", + "cross_post": "transafiŝo", "comments": "Komentoj", - "number_of_comments": "{{count}} Komento", - "number_of_comments_plural": "{{count}} Komentoj", - "remove_comment": "Fortiri Komentojn", + "number_of_comments": "{{count}} komento", + "number_of_comments_plural": "{{count}} komentoj", + "remove_comment": "Forigi komenton", "communities": "Komunumoj", "users": "Uzantoj", "create_a_community": "Krei komunumon", - "create_community": "Krei Komunumon", - "remove_community": "Forigi Komunumon", - "subscribed_to_communities": "Abonita al <1>komunumoj", - "trending_communities": "Furora <1>komunumoj", + "create_community": "Krei komunumon", + "remove_community": "Forigi komunumon", + "subscribed_to_communities": "Abonanta <1>komunumojn", + "trending_communities": "Furoraj <1>komunumoj", "list_of_communities": "Listo de komunumoj", - "community_reqs": "minusklaj leteroj, substrekoj, kaj ne spacetoj.", + "community_reqs": "minusklaj literoj, substrekoj, kaj neniuj spacetoj.", "edit": "redakti", - "reply": "repliki", - "cancel": "nuligi", + "reply": "respondi", + "cancel": "Nuligi", "unlock": "malŝlosi", "lock": "ŝlosi", "link": "ligi", @@ -37,17 +38,17 @@ "modlog": "Moderlogo", "admin": "administranto", "admins": "administrantoj", - "remove_as_admin": "forigi per administranto", - "appoint_as_admin": "nomumi per administranto", - "remove": "fortiri", + "remove_as_admin": "forigi kiel administranto", + "appoint_as_admin": "nomumi administranto", + "remove": "forigi", "removed": "fortirita", "locked": "ŝlosita", "reason": "Kialo", - "mark_as_read": "marki kiel legita", - "mark_as_unread": "marki kiel nelegita", + "mark_as_read": "marki legita", + "mark_as_unread": "marki nelegita", "delete": "forigi", - "deleted": "forigita", - "restore": "restaŭri", + "deleted": "forigita de la kreinto", + "restore": "revenigi", "ban": "forbari", "ban_from_site": "forbari de retejo", "unban": "malforbari", @@ -55,11 +56,14 @@ "save": "konservi", "unsave": "malkonservi", "create": "krei", - "username": "Uzantnomo", - "email_or_username": "Retadreso aŭ Uzantnomo", - "number_of_users": "{{count}} Uzantoj", - "number_of_subscribers": "{{count}} Abonantoj", - "number_of_points": "{{count}} Voĉdonoj", + "username": "Uzantonomo", + "email_or_username": "Retpoŝtadreso aŭ uzantonomo", + "number_of_users": "{{count}} uzanto", + "number_of_users_plural": "{{count}} uzantoj", + "number_of_subscribers": "{{count}} abonanto", + "number_of_subscribers_plural": "{{count}} abonantoj", + "number_of_points": "{{count}} voĉdono", + "number_of_points_plural": "{{count}} voĉdonoj", "name": "Nomo", "title": "Titolo", "category": "Kategorio", @@ -69,10 +73,10 @@ "unsubscribe": "Malaboni", "subscribe": "Aboni", "subscribed": "Abonita", - "prev": "Antaŭe", - "next": "Poste", - "sidebar": "Flankstango", - "sort_type": "Klasi per kia", + "prev": "Malpluen", + "next": "Pluen", + "sidebar": "Flankobreto", + "sort_type": "Ordigilo", "hot": "Varmaj", "new": "Novaj", "top_day": "Supraj tagaj", @@ -84,46 +88,46 @@ "api": "API", "inbox": "Ricevujo", "inbox_for": "Ricevujo de <1>{{user}}", - "mark_all_as_read": "marki ĉiujn kiel legitaj", + "mark_all_as_read": "marki ĉiujn legitaj", "type": "Tipo", "unread": "Nelegitaj", - "reply_sent": "Repliko sendis", + "reply_sent": "Respondo sendiĝis", "search": "Serĉi", "overview": "Resumo", "view": "Rigardi", - "logout": "Elsaluti", - "login_sign_up": "Ensaluti / Registriĝi", - "login": "Ensaluti", + "logout": "Adiaŭi", + "login_sign_up": "Saluti / Registriĝi", + "login": "Saluti", "sign_up": "Registriĝi", - "notifications_error": "Labortablaj avizoj estas nehavebla en via retumilo. Provu Firefox-on aŭ Chrome-on.", - "unread_messages": "Nelegitaj Mesaĝoj", + "notifications_error": "Labortablaj avizoj estas nehaveblaj en via foliumilo. Provu foliumilojn Firefox aŭ Chrome.", + "unread_messages": "Nelegitaj mesaĝoj", "password": "Pasvorto", - "verify_password": "Konfirmu Vian Pasvorton", - "email": "Retadreso", - "optional": "Fakultativa", + "verify_password": "Konfirmu vian pasvorton", + "email": "Retpoŝtadreso", + "optional": "Malnepra", "expires": "Finiĝos", "url": "URL", "body": "Ĉefparto", - "copy_suggested_title": "kopii la sugestiitan titolon: {{title}}", + "copy_suggested_title": "kopii la proponitan titolon: {{title}}", "community": "Komunumo", - "expand_here": "Ekspansii ĉi tie", + "expand_here": "Etendi ĉi tie", "subscribe_to_communities": "Aboni al iuj <1>komunumoj.", "chat": "Babilo", - "recent_comments": "Freŝaj Komentoj", - "no_results": "Ne rezultoj.", + "recent_comments": "Freŝaj komentoj", + "no_results": "Neniuj rezultoj.", "setup": "Agordi", - "lemmy_instance_setup": "Agordi Instancon de Lemmy", - "setup_admin": "Agordi Retejan Administranton", + "lemmy_instance_setup": "Agordi nodon de Lemmy", + "setup_admin": "Agordi administranton de retejo", "your_site": "via retejo", "modified": "modifita", - "nsfw": "NSFW", - "show_nsfw": "Vidigi NSFW-an enhavon", + "nsfw": "Konsterna", + "show_nsfw": "Montri konsternan enhavon", "sponsors": "Subtenantoj", "sponsors_of_lemmy": "Subtenantoj de Lemmy", - "sponsor_message": "Lemmy estas senpaga, <1>liberkoda programaro. Tio signifas ne reklami, pagigi, aŭ riska kapitalo, ĉiam. Viaj donacoj rekte subtenas plentempan evoluon de la projekto. Dankon al tiuj homoj:", + "sponsor_message": "Lemmy estas senpaga, <1>liberkoda programaro, sen reklamoj, pagigado, aŭ riska kapitalo, ĉiam ajn. Viaj donacoj rekte subtenas plentempan evoluigadon de la projekto. Dankon al tiuj homoj:", "support_on_patreon": "Subteni per Patreon", - "general_sponsors": "Ĝeneralaj Subtenantoj estas tiuj ke donacis inter $10 kaj $39 al Lemmy.", - "crypto": "Crypto", + "general_sponsors": "Ĝeneralaj subtenantoj estas tiuj, kiuj donacis inter $10 kaj $39 al Lemmy.", + "crypto": "Ĉifroteĥnikaro", "bitcoin": "Bitcoin", "ethereum": "Ethereum", "monero": "Monero", @@ -133,45 +137,118 @@ "to": "al", "transfer_community": "transdoni la komunumon", "transfer_site": "transdoni la retejon", - "powered_by": "Konstruis per", - "landing_0": "Lemmy estas <1>ligila agregatilo / Reddit anstataŭo ke intenciĝas funkci en la <2>federacio-universo.<3>ĝi estas mem-gastigebla, havas nuna-ĝisdatigajn komentarojn, kaj estas malgrandega (<4>~80kB). Federacio en la ActivityPub-an reton estas planizita. <5>Estas <6>fruega beta versio, kaj multaj trajtoj estas nune difektaj aŭ mankaj. <7>Sugestias novajn trajtojn aŭ raportas cimojn <8>ĉi tie.<9>Faris per <10>Rust, <11>Actix, <12>Inferno, <13>Typescript.", - "not_logged_in": "Ne estas ensalutinta.", + "powered_by": "Konstruita per", + "landing": "Lemmy estas <1>amasigilo de ligiloj / alternativo de Reddit, intencita funkcii en la <2>federuniverso.<3>ĝi estas mem-gastigebla, havas tuj-ĝisdatigojn de komentaroj, kaj estas malgrandega (<4>~80kB). Federado en la reto de ActivityPub estas planita. <5>Ĉi tio estas <6>tre frua beta-versio, kaj multaj funkcioj estas nune difektaj aŭ mankaj. <7>Proponu novajn funkciojn aŭ raportu erarojn <8>ĉi tie.<9>Konstruita per <10>Rust, <11>Actix, <12>Inferno, <13>Typescript.", + "not_logged_in": "Nesalutinta.", "community_ban": "Vi estas forbarita de la komunumo.", "site_ban": "Vi estas forbarita de la retejo", "couldnt_create_comment": "Ne povis krei la komenton.", "couldnt_like_comment": "Ne povis ŝati la komenton.", - "couldnt_update_comment": "Ne povis ĝisdatigi komenton.", - "couldnt_save_comment": "Ne povis konservi komenton.", + "couldnt_update_comment": "Ne povis ĝisdatigi la komenton.", + "couldnt_save_comment": "Ne povis konservi la komenton.", "no_comment_edit_allowed": "Ne rajtas redakti la komenton.", - "no_post_edit_allowed": "Ne rajtas redakti la poŝton.", + "no_post_edit_allowed": "Ne rajtas redakti la afiŝon.", "no_community_edit_allowed": "Ne rajtas redakti la komunumon.", "couldnt_find_community": "Ne povis trovi la komunumon.", "couldnt_update_community": "Ne povis ĝisdatigi la komunumon.", "community_already_exists": "Komunumo jam ekzistas.", "community_moderator_already_exists": "Komunuma moderanto jam ekzistas.", - "community_follower_already_exists": "Komunuma sekvanto.", - "community_user_already_banned": "Komunuma uzanto jam estas forbarita.", - "couldnt_create_post": "Ne povis krei la poŝton.", - "couldnt_like_post": "Ne povis ŝati la poŝton.", - "couldnt_find_post": "Ne povis trovi la poŝton.", - "couldnt_get_posts": "Ne povis irpreni poŝtojn", - "couldnt_update_post": "Ne povis ĝisdatigi la poŝton", - "couldnt_save_post": "Ne povis konservi la poŝton.", - "no_slurs": "Ne bigotaj vortoj.", + "community_follower_already_exists": "Abonanto de komunumo jam ekzistas.", + "community_user_already_banned": "Uzanto de komunumo jam estas forbarita.", + "couldnt_create_post": "Ne povis krei la afiŝon.", + "couldnt_like_post": "Ne povis ŝati la afiŝon.", + "couldnt_find_post": "Ne povis trovi la afiŝon.", + "couldnt_get_posts": "Ne povis akiri afiŝojn", + "couldnt_update_post": "Ne povis ĝisdatigi la afiŝon", + "couldnt_save_post": "Ne povis konservi la afiŝon.", + "no_slurs": "Neniuj fivortoj.", "not_an_admin": "Ne estas administranto.", "site_already_exists": "Retejo jam ekzistas.", "couldnt_update_site": "Ne povis ĝisdatigi la retejon.", - "couldnt_find_that_username_or_email": "Ne povis trovi tiun uzantnomon aŭ retadreson.", + "couldnt_find_that_username_or_email": "Ne povis trovi tiun uzantonomon aŭ retpoŝtadreson.", "password_incorrect": "Pasvorto malĝustas.", "passwords_dont_match": "Pasvortoj ne samas.", "admin_already_created": "Pardonu, jam estas administranto.", "user_already_exists": "Uzanto jam ekzistas.", "couldnt_update_user": "Ne povis ĝisdatigi la uzanton.", - "system_err_login": "Sistema eraro. Provu elsaluti kaj ensaluti.", + "system_err_login": "Sistema eraro. Provu adiaŭi kaj resaluti.", "send_message": "Sendi mesaĝon", "message": "Mesaĝo", - "number_of_communities": "{{count}} Komunumo", - "number_of_communities_plural": "{{count}} Komunumoj", + "number_of_communities": "{{count}} komunumo", + "number_of_communities_plural": "{{count}} komunumoj", "more": "pli", - "select_a_community": "Elekti komunumon" + "select_a_community": "Elekti komunumon", + "click_to_delete_picture": "Klaku por forigi bildon.", + "cross_posted_to": "transafiŝita al: ", + "invalid_community_name": "Nevalida nomo.", + "picture_deleted": "Bildo foriĝis.", + "create_private_message": "Krei privatan mesaĝon", + "send_secure_message": "Sendi sekuran mesaĝon", + "avatar": "Profilbildo", + "show_avatars": "Montri profilbildojn", + "formatting_help": "helpo pri formatado", + "sorting_help": "helpo pri ordigado", + "sticky": "pingli", + "unsticky": "malpingli", + "stickied": "pinglita", + "delete_account": "Forigi konton", + "delete_account_confirm": "Averto: ĉi tio por ĉiam forigos ĉiujn viajn datumojn. Enigu pasvorton por konfirmi.", + "preview": "Antaŭrigardo", + "upload_image": "alŝuti bildon", + "upload_avatar": "Alŝuti profilbildon", + "banned": "forbarita", + "creator": "kreinto", + "number_online": "{{count}} uzanto enreta", + "number_online_plural": "{{count}} uzantoj enretaj", + "old": "Malnovaj", + "docs": "Dokumentaĵo", + "view_source": "montri fonton", + "show_context": "Montri kuntekston", + "admin_settings": "Agordoj de agministranto", + "site_config": "Agordaro de retejo", + "banned_users": "Forbaritaj uzantoj", + "donate": "Donaci", + "archive_link": "arĥiva ligilo", + "replies": "Respondoj", + "mentions": "Mencioj", + "message_sent": "Mesaĝo sendiĝis", + "post_title_too_long": "Titolo de afiŝo estas tro longa.", + "messages": "Mesaĝoj", + "old_password": "Malnova pasvorto", + "forgot_password": "forgesita pasvorto", + "reset_password_mail_sent": "Retletero sendiĝis por restarigi vian pasvorton.", + "password_change": "Ŝanĝo de pasvorto", + "new_password": "Nova pasvorto", + "no_email_setup": "Ĉi tiu servilo ne agordis ĝuste retpoŝton.", + "matrix_user_id": "Uzanto de Matrix", + "private_message_disclaimer": "Averto: Privataj mesaĝoj en Lemmy ne estas sekuraj. Bonvolu krei konton je <1>Riot.im por sekura mesaĝado.", + "send_notifications_to_email": "Sendi sciigojn al retpoŝtadreso", + "language": "Lingvo", + "browser_default": "Laŭ foliumilo", + "downvotes_disabled": "Neaj voĉdonoj malŝaltiĝis", + "enable_downvotes": "Ŝalti neajn voĉdonojn", + "open_registration": "Ebligi registradon", + "registration_closed": "Registrado malebliĝis", + "enable_nsfw": "Ŝalti konsternajn", + "support_on_open_collective": "Subteni per OpenCollective", + "theme": "Haŭto", + "support_on_liberapay": "Subteni per Liberapay", + "donate_to_lemmy": "Donaci al Lemmy", + "silver_sponsors": "Arĝentaj subtenantoj estas tiuj, kiuj donacis $40 al Lemmy.", + "are_you_sure": "ĉu vi certas?", + "yes": "jes", + "no": "ne", + "logged_in": "Salutinta.", + "site_saved": "Retejo konserviĝis.", + "couldnt_get_comments": "Ne povis akiri la komentojn.", + "email_already_exists": "Retpoŝtadreso jam ekzistas.", + "couldnt_create_private_message": "Ne povis krei privatan mesaĝon.", + "no_private_message_edit_allowed": "Ne rajtas redakti la privatan mesaĝon.", + "couldnt_update_private_message": "Ne povis ĝisdatigi la privatan mesaĝon.", + "time": "Tempo", + "action": "Ago", + "emoji_picker": "Elektilo de bildsignoj", + "block_leaving": "Ĉu vi certe volas foriri?", + "from": "de", + "invalid_username": "Nevalida uzantonomo." } From 52fcda96e141094ceef5c9dbc92d46879328b41f Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 1 Jul 2020 18:54:46 -0400 Subject: [PATCH 13/26] Adding IP to the rate limit request line. (#882) * Adding IP to the rate limit request line. * Adding IP to the rate limit request line. --- server/src/rate_limit/rate_limiter.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/server/src/rate_limit/rate_limiter.rs b/server/src/rate_limit/rate_limiter.rs index 3e2ea787b..20a617c2f 100644 --- a/server/src/rate_limit/rate_limiter.rs +++ b/server/src/rate_limit/rate_limiter.rs @@ -10,7 +10,7 @@ pub struct RateLimitBucket { allowance: f64, } -#[derive(Eq, PartialEq, Hash, Debug, EnumIter, Copy, Clone)] +#[derive(Eq, PartialEq, Hash, Debug, EnumIter, Copy, Clone, AsRefStr)] pub enum RateLimitType { Message, Register, @@ -80,12 +80,21 @@ impl RateLimiter { if rate_limit.allowance < 1.0 { debug!( - "Rate limited IP: {}, time_passed: {}, allowance: {}", - ip, time_passed, rate_limit.allowance + "Rate limited type: {}, IP: {}, time_passed: {}, allowance: {}", + type_.as_ref(), + ip, + time_passed, + rate_limit.allowance ); Err( APIError { - message: format!("Too many requests. {} per {} seconds", rate, per), + message: format!( + "Too many requests. type: {}, IP: {}, {} per {} seconds", + type_.as_ref(), + ip, + rate, + per + ), } .into(), ) From 2e886ce4e62e6788554d165cd7024e4618ea49a5 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 1 Jul 2020 20:38:11 -0400 Subject: [PATCH 14/26] Nother fix for comment box bug. #861 --- ui/src/components/comment-form.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/src/components/comment-form.tsx b/ui/src/components/comment-form.tsx index 72a604b9f..61ee3d77b 100644 --- a/ui/src/components/comment-form.tsx +++ b/ui/src/components/comment-form.tsx @@ -263,7 +263,9 @@ export class CommentForm extends Component { // If its a comment edit, only check that its from your user, and that its a // text edit only - (op == UserOperation.EditComment && data.comment.content) + (data.comment.creator_id == UserService.Instance.user.id && + op == UserOperation.EditComment && + data.comment.content) ) { this.state.previewMode = false; this.state.loading = false; From 8d49dc958efe8f7e07ec8642a1e2287b79a3d888 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 1 Jul 2020 20:39:51 -0400 Subject: [PATCH 15/26] Version v0.7.7 --- ansible/VERSION | 2 +- docker/prod/docker-compose.yml | 2 +- server/src/version.rs | 2 +- ui/src/version.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ansible/VERSION b/ansible/VERSION index 378c127dd..b977a66d9 100644 --- a/ansible/VERSION +++ b/ansible/VERSION @@ -1 +1 @@ -v0.7.6 +v0.7.7 diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index cd364a3cc..b915b7f0c 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -12,7 +12,7 @@ services: restart: always lemmy: - image: dessalines/lemmy:v0.7.6 + image: dessalines/lemmy:v0.7.7 ports: - "127.0.0.1:8536:8536" restart: always diff --git a/server/src/version.rs b/server/src/version.rs index b72c81989..df68fe43b 100644 --- a/server/src/version.rs +++ b/server/src/version.rs @@ -1 +1 @@ -pub const VERSION: &str = "v0.7.6"; +pub const VERSION: &str = "v0.7.7"; diff --git a/ui/src/version.ts b/ui/src/version.ts index 8ad27e7cc..5a734de9e 100644 --- a/ui/src/version.ts +++ b/ui/src/version.ts @@ -1 +1 @@ -export const version: string = 'v0.7.6'; +export const version: string = 'v0.7.7'; From 605ded9c73e5a7ba0c68a4db64fe3b9a8c0e0be1 Mon Sep 17 00:00:00 2001 From: Tirifto Date: Thu, 2 Jul 2020 01:23:43 +0000 Subject: [PATCH 16/26] Translated using Weblate (Esperanto) Currently translated at 95.5% (238 of 249 strings) Translation: Lemmy/lemmy Translate-URL: http://weblate.yerbamate.dev/projects/lemmy/lemmy/eo/ Translated using Weblate (Esperanto) Currently translated at 93.9% (234 of 249 strings) Translation: Lemmy/lemmy Translate-URL: http://weblate.yerbamate.dev/projects/lemmy/lemmy/eo/ Translated using Weblate (Esperanto) Currently translated at 76.3% (190 of 249 strings) Translation: Lemmy/lemmy Translate-URL: http://weblate.yerbamate.dev/projects/lemmy/lemmy/eo/ --- ui/translations/eo.json | 239 +++++++++++++++++++++++++++------------- 1 file changed, 161 insertions(+), 78 deletions(-) diff --git a/ui/translations/eo.json b/ui/translations/eo.json index 5bde84f46..78821bf03 100644 --- a/ui/translations/eo.json +++ b/ui/translations/eo.json @@ -1,33 +1,34 @@ { - "post": "Poŝti", - "remove_post": "Fortiri Poŝton", - "no_posts": "Ne Poŝtoj.", - "create_a_post": "Verki Poŝton", - "create_post": "Verki Poŝton", - "number_of_posts": "{{count}} Poŝtoj", - "posts": "Poŝtoj", - "related_posts": "Tiuj poŝtoj eble rilatas", - "cross_posts": "Tiuj ligilo ankaŭ estas poŝtinta al:", - "cross_post": "laŭapoŝto", + "post": "Afiŝi", + "remove_post": "Forigi afiŝon", + "no_posts": "Neniuj afiŝoj.", + "create_a_post": "Verki afiŝon", + "create_post": "Verki afiŝon", + "number_of_posts": "{{count}} afiŝo", + "number_of_posts_plural": "{{count}} afiŝoj", + "posts": "Afiŝoj", + "related_posts": "Ĉi tiuj afiŝoj eble rilatas", + "cross_posts": "Tiu ligilo ankaŭ estas afiŝita al:", + "cross_post": "transafiŝo", "comments": "Komentoj", - "number_of_comments": "{{count}} Komento", - "number_of_comments_plural": "{{count}} Komentoj", - "remove_comment": "Fortiri Komentojn", + "number_of_comments": "{{count}} komento", + "number_of_comments_plural": "{{count}} komentoj", + "remove_comment": "Forigi komenton", "communities": "Komunumoj", "users": "Uzantoj", "create_a_community": "Krei komunumon", - "create_community": "Krei Komunumon", - "remove_community": "Forigi Komunumon", - "subscribed_to_communities": "Abonita al <1>komunumoj", - "trending_communities": "Furora <1>komunumoj", + "create_community": "Krei komunumon", + "remove_community": "Forigi komunumon", + "subscribed_to_communities": "Abonanta <1>komunumojn", + "trending_communities": "Furoraj <1>komunumoj", "list_of_communities": "Listo de komunumoj", - "community_reqs": "minusklaj leteroj, substrekoj, kaj ne spacetoj.", + "community_reqs": "minusklaj literoj, substrekoj, kaj neniuj spacetoj.", "edit": "redakti", - "reply": "repliki", - "cancel": "nuligi", + "reply": "respondi", + "cancel": "Nuligi", "unlock": "malŝlosi", "lock": "ŝlosi", - "link": "ligi", + "link": "ligilo", "mod": "moderanto", "mods": "moderantoj", "moderates": "Moderigas", @@ -37,17 +38,17 @@ "modlog": "Moderlogo", "admin": "administranto", "admins": "administrantoj", - "remove_as_admin": "forigi per administranto", - "appoint_as_admin": "nomumi per administranto", - "remove": "fortiri", + "remove_as_admin": "forigi kiel administranto", + "appoint_as_admin": "nomumi administranto", + "remove": "forigi", "removed": "fortirita", "locked": "ŝlosita", "reason": "Kialo", - "mark_as_read": "marki kiel legita", - "mark_as_unread": "marki kiel nelegita", + "mark_as_read": "marki legita", + "mark_as_unread": "marki nelegita", "delete": "forigi", - "deleted": "forigita", - "restore": "restaŭri", + "deleted": "forigita de la kreinto", + "restore": "revenigi", "ban": "forbari", "ban_from_site": "forbari de retejo", "unban": "malforbari", @@ -55,11 +56,14 @@ "save": "konservi", "unsave": "malkonservi", "create": "krei", - "username": "Uzantnomo", - "email_or_username": "Retadreso aŭ Uzantnomo", - "number_of_users": "{{count}} Uzantoj", - "number_of_subscribers": "{{count}} Abonantoj", - "number_of_points": "{{count}} Voĉdonoj", + "username": "Uzantonomo", + "email_or_username": "Retpoŝtadreso aŭ uzantonomo", + "number_of_users": "{{count}} uzanto", + "number_of_users_plural": "{{count}} uzantoj", + "number_of_subscribers": "{{count}} abonanto", + "number_of_subscribers_plural": "{{count}} abonantoj", + "number_of_points": "{{count}} voĉdono", + "number_of_points_plural": "{{count}} voĉdonoj", "name": "Nomo", "title": "Titolo", "category": "Kategorio", @@ -69,10 +73,10 @@ "unsubscribe": "Malaboni", "subscribe": "Aboni", "subscribed": "Abonita", - "prev": "Antaŭe", - "next": "Poste", - "sidebar": "Flankstango", - "sort_type": "Klasi per kia", + "prev": "Malpluen", + "next": "Pluen", + "sidebar": "Flankobreto", + "sort_type": "Ordigilo", "hot": "Varmaj", "new": "Novaj", "top_day": "Supraj tagaj", @@ -84,46 +88,46 @@ "api": "API", "inbox": "Ricevujo", "inbox_for": "Ricevujo de <1>{{user}}", - "mark_all_as_read": "marki ĉiujn kiel legitaj", + "mark_all_as_read": "marki ĉiujn legitaj", "type": "Tipo", "unread": "Nelegitaj", - "reply_sent": "Repliko sendis", + "reply_sent": "Respondo sendiĝis", "search": "Serĉi", "overview": "Resumo", "view": "Rigardi", - "logout": "Elsaluti", - "login_sign_up": "Ensaluti / Registriĝi", - "login": "Ensaluti", + "logout": "Adiaŭi", + "login_sign_up": "Saluti / Registriĝi", + "login": "Saluti", "sign_up": "Registriĝi", - "notifications_error": "Labortablaj avizoj estas nehavebla en via retumilo. Provu Firefox-on aŭ Chrome-on.", - "unread_messages": "Nelegitaj Mesaĝoj", + "notifications_error": "Labortablaj avizoj estas nehaveblaj en via foliumilo. Provu foliumilojn Firefox aŭ Chrome.", + "unread_messages": "Nelegitaj mesaĝoj", "password": "Pasvorto", - "verify_password": "Konfirmu Vian Pasvorton", - "email": "Retadreso", - "optional": "Fakultativa", + "verify_password": "Konfirmu vian pasvorton", + "email": "Retpoŝtadreso", + "optional": "Malnepra", "expires": "Finiĝos", "url": "URL", "body": "Ĉefparto", - "copy_suggested_title": "kopii la sugestiitan titolon: {{title}}", + "copy_suggested_title": "kopii la proponitan titolon: {{title}}", "community": "Komunumo", - "expand_here": "Ekspansii ĉi tie", + "expand_here": "Etendi ĉi tie", "subscribe_to_communities": "Aboni al iuj <1>komunumoj.", "chat": "Babilo", - "recent_comments": "Freŝaj Komentoj", - "no_results": "Ne rezultoj.", + "recent_comments": "Freŝaj komentoj", + "no_results": "Neniuj rezultoj.", "setup": "Agordi", - "lemmy_instance_setup": "Agordi Instancon de Lemmy", - "setup_admin": "Agordi Retejan Administranton", + "lemmy_instance_setup": "Agordi nodon de Lemmy", + "setup_admin": "Agordi administranton de retejo", "your_site": "via retejo", "modified": "modifita", - "nsfw": "NSFW", - "show_nsfw": "Vidigi NSFW-an enhavon", + "nsfw": "Konsterna", + "show_nsfw": "Montri konsternan enhavon", "sponsors": "Subtenantoj", "sponsors_of_lemmy": "Subtenantoj de Lemmy", - "sponsor_message": "Lemmy estas senpaga, <1>liberkoda programaro. Tio signifas ne reklami, pagigi, aŭ riska kapitalo, ĉiam. Viaj donacoj rekte subtenas plentempan evoluon de la projekto. Dankon al tiuj homoj:", + "sponsor_message": "Lemmy estas senpaga, <1>liberkoda programaro, sen reklamoj, pagigado, aŭ riska kapitalo, ĉiam ajn. Viaj donacoj rekte subtenas plentempan evoluigadon de la projekto. Dankon al tiuj homoj:", "support_on_patreon": "Subteni per Patreon", - "general_sponsors": "Ĝeneralaj Subtenantoj estas tiuj ke donacis inter $10 kaj $39 al Lemmy.", - "crypto": "Crypto", + "general_sponsors": "Ĝeneralaj subtenantoj estas tiuj, kiuj donacis inter $10 kaj $39 al Lemmy.", + "crypto": "Ĉifroteĥnikaro", "bitcoin": "Bitcoin", "ethereum": "Ethereum", "monero": "Monero", @@ -133,45 +137,124 @@ "to": "al", "transfer_community": "transdoni la komunumon", "transfer_site": "transdoni la retejon", - "powered_by": "Konstruis per", - "landing_0": "Lemmy estas <1>ligila agregatilo / Reddit anstataŭo ke intenciĝas funkci en la <2>federacio-universo.<3>ĝi estas mem-gastigebla, havas nuna-ĝisdatigajn komentarojn, kaj estas malgrandega (<4>~80kB). Federacio en la ActivityPub-an reton estas planizita. <5>Estas <6>fruega beta versio, kaj multaj trajtoj estas nune difektaj aŭ mankaj. <7>Sugestias novajn trajtojn aŭ raportas cimojn <8>ĉi tie.<9>Faris per <10>Rust, <11>Actix, <12>Inferno, <13>Typescript.", - "not_logged_in": "Ne estas ensalutinta.", + "powered_by": "Konstruita per", + "landing": "Lemmy estas <1>amasigilo de ligiloj / alternativo de Reddit, intencita funkcii en la <2>federuniverso.<3>ĝi estas mem-gastigebla, havas tuj-ĝisdatigojn de komentaroj, kaj estas malgrandega (<4>~80kB). Federado en la reto de ActivityPub estas planita. <5>Ĉi tio estas <6>tre frua beta-versio, kaj multaj funkcioj estas nune difektaj aŭ mankaj. <7>Proponu novajn funkciojn aŭ raportu erarojn <8>ĉi tie.<9>Konstruita per <10>Rust, <11>Actix, <12>Inferno, <13>Typescript.", + "not_logged_in": "Nesalutinta.", "community_ban": "Vi estas forbarita de la komunumo.", "site_ban": "Vi estas forbarita de la retejo", "couldnt_create_comment": "Ne povis krei la komenton.", "couldnt_like_comment": "Ne povis ŝati la komenton.", - "couldnt_update_comment": "Ne povis ĝisdatigi komenton.", - "couldnt_save_comment": "Ne povis konservi komenton.", + "couldnt_update_comment": "Ne povis ĝisdatigi la komenton.", + "couldnt_save_comment": "Ne povis konservi la komenton.", "no_comment_edit_allowed": "Ne rajtas redakti la komenton.", - "no_post_edit_allowed": "Ne rajtas redakti la poŝton.", + "no_post_edit_allowed": "Ne rajtas redakti la afiŝon.", "no_community_edit_allowed": "Ne rajtas redakti la komunumon.", "couldnt_find_community": "Ne povis trovi la komunumon.", "couldnt_update_community": "Ne povis ĝisdatigi la komunumon.", "community_already_exists": "Komunumo jam ekzistas.", "community_moderator_already_exists": "Komunuma moderanto jam ekzistas.", - "community_follower_already_exists": "Komunuma sekvanto.", - "community_user_already_banned": "Komunuma uzanto jam estas forbarita.", - "couldnt_create_post": "Ne povis krei la poŝton.", - "couldnt_like_post": "Ne povis ŝati la poŝton.", - "couldnt_find_post": "Ne povis trovi la poŝton.", - "couldnt_get_posts": "Ne povis irpreni poŝtojn", - "couldnt_update_post": "Ne povis ĝisdatigi la poŝton", - "couldnt_save_post": "Ne povis konservi la poŝton.", - "no_slurs": "Ne bigotaj vortoj.", + "community_follower_already_exists": "Abonanto de komunumo jam ekzistas.", + "community_user_already_banned": "Uzanto de komunumo jam estas forbarita.", + "couldnt_create_post": "Ne povis krei la afiŝon.", + "couldnt_like_post": "Ne povis ŝati la afiŝon.", + "couldnt_find_post": "Ne povis trovi la afiŝon.", + "couldnt_get_posts": "Ne povis akiri afiŝojn", + "couldnt_update_post": "Ne povis ĝisdatigi la afiŝon", + "couldnt_save_post": "Ne povis konservi la afiŝon.", + "no_slurs": "Neniuj fivortoj.", "not_an_admin": "Ne estas administranto.", "site_already_exists": "Retejo jam ekzistas.", "couldnt_update_site": "Ne povis ĝisdatigi la retejon.", - "couldnt_find_that_username_or_email": "Ne povis trovi tiun uzantnomon aŭ retadreson.", + "couldnt_find_that_username_or_email": "Ne povis trovi tiun uzantonomon aŭ retpoŝtadreson.", "password_incorrect": "Pasvorto malĝustas.", "passwords_dont_match": "Pasvortoj ne samas.", "admin_already_created": "Pardonu, jam estas administranto.", "user_already_exists": "Uzanto jam ekzistas.", "couldnt_update_user": "Ne povis ĝisdatigi la uzanton.", - "system_err_login": "Sistema eraro. Provu elsaluti kaj ensaluti.", + "system_err_login": "Sistema eraro. Provu adiaŭi kaj resaluti.", "send_message": "Sendi mesaĝon", "message": "Mesaĝo", - "number_of_communities": "{{count}} Komunumo", - "number_of_communities_plural": "{{count}} Komunumoj", + "number_of_communities": "{{count}} komunumo", + "number_of_communities_plural": "{{count}} komunumoj", "more": "pli", - "select_a_community": "Elekti komunumon" + "select_a_community": "Elekti komunumon", + "click_to_delete_picture": "Klaku por forigi bildon.", + "cross_posted_to": "transafiŝita al: ", + "invalid_community_name": "Nevalida nomo.", + "picture_deleted": "Bildo foriĝis.", + "create_private_message": "Krei privatan mesaĝon", + "send_secure_message": "Sendi sekuran mesaĝon", + "avatar": "Profilbildo", + "show_avatars": "Montri profilbildojn", + "formatting_help": "helpo pri formatado", + "sorting_help": "helpo pri ordigado", + "sticky": "pingli", + "unsticky": "malpingli", + "stickied": "pinglita", + "delete_account": "Forigi konton", + "delete_account_confirm": "Averto: ĉi tio por ĉiam forigos ĉiujn viajn datumojn. Enigu pasvorton por konfirmi.", + "preview": "Antaŭrigardo", + "upload_image": "alŝuti bildon", + "upload_avatar": "Alŝuti profilbildon", + "banned": "forbarita", + "creator": "kreinto", + "number_online": "{{count}} uzanto enreta", + "number_online_plural": "{{count}} uzantoj enretaj", + "old": "Malnovaj", + "docs": "Dokumentaĵo", + "view_source": "montri fonton", + "show_context": "Montri kuntekston", + "admin_settings": "Agordoj de agministranto", + "site_config": "Agordaro de retejo", + "banned_users": "Forbaritaj uzantoj", + "donate": "Donaci", + "archive_link": "arĥiva ligilo", + "replies": "Respondoj", + "mentions": "Mencioj", + "message_sent": "Mesaĝo sendiĝis", + "post_title_too_long": "Titolo de afiŝo estas tro longa.", + "messages": "Mesaĝoj", + "old_password": "Malnova pasvorto", + "forgot_password": "forgesita pasvorto", + "reset_password_mail_sent": "Retletero sendiĝis por restarigi vian pasvorton.", + "password_change": "Ŝanĝo de pasvorto", + "new_password": "Nova pasvorto", + "no_email_setup": "Ĉi tiu servilo ne agordis ĝuste retpoŝton.", + "matrix_user_id": "Uzanto de Matrix", + "private_message_disclaimer": "Averto: Privataj mesaĝoj en Lemmy ne estas sekuraj. Bonvolu krei konton je <1>Riot.im por sekura mesaĝado.", + "send_notifications_to_email": "Sendi sciigojn al retpoŝtadreso", + "language": "Lingvo", + "browser_default": "Laŭ foliumilo", + "downvotes_disabled": "Kontraŭvoĉoj malŝaltiĝis", + "enable_downvotes": "Ŝalti kontraŭvoĉojn", + "open_registration": "Ebligi registradon", + "registration_closed": "Registrado malebliĝis", + "enable_nsfw": "Ŝalti konsternajn", + "support_on_open_collective": "Subteni per OpenCollective", + "theme": "Haŭto", + "support_on_liberapay": "Subteni per Liberapay", + "donate_to_lemmy": "Donaci al Lemmy", + "silver_sponsors": "Arĝentaj subtenantoj estas tiuj, kiuj donacis $40 al Lemmy.", + "are_you_sure": "ĉu vi certas?", + "yes": "jes", + "no": "ne", + "logged_in": "Salutinta.", + "site_saved": "Retejo konserviĝis.", + "couldnt_get_comments": "Ne povis akiri la komentojn.", + "email_already_exists": "Retpoŝtadreso jam ekzistas.", + "couldnt_create_private_message": "Ne povis krei privatan mesaĝon.", + "no_private_message_edit_allowed": "Ne rajtas redakti la privatan mesaĝon.", + "couldnt_update_private_message": "Ne povis ĝisdatigi la privatan mesaĝon.", + "time": "Tempo", + "action": "Ago", + "emoji_picker": "Elektilo de bildsignoj", + "block_leaving": "Ĉu vi certe volas foriri?", + "from": "de", + "invalid_username": "Nevalida uzantonomo.", + "upvote": "Porvoĉi", + "number_of_upvotes": "{{count}} porvoĉo", + "number_of_upvotes_plural": "{{count}} porvoĉoj", + "downvote": "Kontraŭvoĉi", + "number_of_downvotes": "{{count}} kontraŭvoĉo", + "number_of_downvotes_plural": "{{count}} kontraŭvoĉoj" } From 96b73cca51fd5d9b6555feff367b2d14db0c62f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ABr=C3=AB=20R=C3=ABr=C3=AB?= Date: Thu, 2 Jul 2020 01:23:43 +0000 Subject: [PATCH 17/26] Translated using Weblate (Albanian) Currently translated at 34.1% (85 of 249 strings) Translation: Lemmy/lemmy Translate-URL: http://weblate.yerbamate.dev/projects/lemmy/lemmy/sq/ --- ui/translations/sq.json | 109 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/ui/translations/sq.json b/ui/translations/sq.json index 0967ef424..0526fa267 100644 --- a/ui/translations/sq.json +++ b/ui/translations/sq.json @@ -1 +1,108 @@ -{} +{ + "remove_post": "Hiqe Postimin", + "no_posts": "Nuk ka Postime.", + "create_a_post": "Krijo një postim", + "create_post": "Krijo Postimin", + "posts": "Postime", + "related_posts": "Këto postime mund të jenë të lidhura", + "cross_posts": "Ky link është postuar edhe te:", + "cross_post": "shumë-postim", + "cross_posted_to": "shumë-postuar në: ", + "comments": "Komentet", + "remove_comment": "Fshije Komentin", + "communities": "Komunitetet", + "users": "Përdoruesit", + "create_a_community": "Krijo një komunitet", + "select_a_community": "Përzgjedh një komunitet", + "create_community": "Krijo komunitetin", + "remove_community": "Fshije Komunitetin", + "subscribed_to_communities": "I abonuar në", + "trending_communities": "Trendi", + "list_of_communities": "Lista e komuniteteve", + "community_reqs": "gërma të vogla, nënvizim, dhe pa hapësira.", + "invalid_community_name": "Emër invalid.", + "create_private_message": "Krijo Mesazh Privat", + "send_secure_message": "Dërgo Mesazh të Sigurtë", + "send_message": "Dërgo Mesazh", + "message": "Mesazh", + "edit": "redakto", + "reply": "përgjigju", + "more": "më shumë", + "cancel": "Anulo", + "preview": "Shiko paraprakisht", + "upload_image": "ngarko imazhin", + "upload_avatar": "Ngarko foton e profilit", + "show_avatars": "Shfaq fotot e profilit", + "show_context": "Shfaq kontekstin", + "formatting_help": "ndihmë me formatimin", + "sorting_help": "ndihmë me radhitjen", + "view_source": "shiko origjinën", + "unlock": "hape", + "lock": "mbyll", + "unsticky": "çngjit", + "link": "link", + "archive_link": "link i arkivuar", + "mod": "moderator", + "mods": "moderatorët", + "settings": "Konfigurimet", + "site_config": "Konfigurimet e faqes", + "remove_as_mod": "Largoje si moderator", + "appoint_as_mod": "emëro si moderator", + "modlog": "Ditari i moderimit", + "admin": "administrator", + "admins": "administratorët", + "appoint_as_admin": "emëro si administrator", + "remove": "fshije", + "removed": "është fshirë nga një moderator", + "locked": "mbyllur", + "stickied": "ngjitur", + "reason": "Arsye", + "mark_as_read": "shëno si të lexuar", + "mark_as_unread": "shëno si të palexuar", + "delete": "fshije", + "delete_account": "Fshije Account-in", + "click_to_delete_picture": "Shtyp për të fshirë imazhin.", + "picture_deleted": "Imazhi është fshirë.", + "restore": "rikthe", + "ban": "", + "ban_from_site": "", + "save": "ruaj", + "unsave": "anulo ruajtjen", + "create": "krijo", + "creator": "krijuesi", + "username": "Emri virtual", + "email_or_username": "Email-i ose Emri virtual", + "number_of_users": "{{count}} Përdorues", + "number_of_users_plural": "{{count}} Përdoruesa", + "number_of_subscribers": "{{count}} i abonuar", + "number_of_subscribers_plural": "{{count}} të abonuar", + "number_of_points": "{{count}} Pikë", + "number_of_points_plural": "{{count}} Pikë", + "number_online": "{{count}} Përdorues Online", + "number_online_plural": "{{count}} Përdoruesa Online", + "name": "Emri", + "title": "Titulli", + "category": "Kategoria", + "subscribers": "Të abonuarit", + "both": "Të dy", + "saved": "E ruajtur", + "subscribe": "Abonohu", + "subscribed": "Jeni abonuar", + "next": "Tjetra", + "post": "postim", + "number_of_posts": "{{count}} Postim", + "number_of_posts_plural": "{{count}} Postime", + "number_of_comments": "{{count}} Koment", + "number_of_comments_plural": "{{count}} Komente", + "number_of_communities": "{{count}} Komunitet", + "number_of_communities_plural": "{{count}} Komunitete", + "avatar": "Fotoja e profilit", + "sticky": "ngjite", + "moderates": "Moderon", + "admin_settings": "Konfigurimet administrative", + "remove_as_admin": "largoje si administrator", + "deleted": "është fshirë nga krijuesi", + "delete_account_confirm": "Paralajmërim: kjo do të fshij të gjitha të dhënat e juaja përgjithmonë. Shtyp fjalëkalimin tënd për ta konfirmuar.", + "unsubscribe": "Çabonohu", + "prev": "E mëparshme" +} From d22152eefd408da84973ef0bffec6ee1d0234bc8 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 1 Jul 2020 21:48:29 -0400 Subject: [PATCH 18/26] Adding a math check for logins (until we get a proper captcha). --- ui/src/components/login.tsx | 49 +++++++++++++++++++++++++++++++++++-- ui/translations/en.json | 3 ++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/ui/src/components/login.tsx b/ui/src/components/login.tsx index ce04d0d4f..978993458 100644 --- a/ui/src/components/login.tsx +++ b/ui/src/components/login.tsx @@ -20,6 +20,11 @@ interface State { loginLoading: boolean; registerLoading: boolean; enable_nsfw: boolean; + mathQuestion: { + a: number; + b: number; + answer: number; + }; } export class Login extends Component { @@ -40,6 +45,11 @@ export class Login extends Component { loginLoading: false, registerLoading: false, enable_nsfw: undefined, + mathQuestion: { + a: Math.floor(Math.random() * 10) + 1, + b: Math.floor(Math.random() * 10) + 1, + answer: undefined, + }, }; constructor(props: any, context: any) { @@ -215,6 +225,23 @@ export class Login extends Component { /> +
+ + +
+ +
+
{this.state.enable_nsfw && (
@@ -235,7 +262,11 @@ export class Login extends Component { )}
-