diff --git a/Cargo.lock b/Cargo.lock index 8213956d..3e76e5b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ checksum = "5e9fedbe571e267d9b93d071bdc4493f944022c6cce717ebb27d352026fc81c4" dependencies = [ "chrono", "mime", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "thiserror", "url", @@ -21,7 +21,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb8e19a0810cc25df3535061a08b7d8f8a734d309ea4411c57a9767e4a2ffa0e" dependencies = [ "activitystreams", - "serde 1.0.117", + "serde 1.0.118", "serde_json", ] @@ -148,7 +148,7 @@ dependencies = [ "pin-project 1.0.2", "rand", "regex", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "serde_urlencoded", "sha-1 0.9.2", @@ -176,7 +176,7 @@ dependencies = [ "http", "log", "regex", - "serde 1.0.117", + "serde 1.0.118", ] [[package]] @@ -320,7 +320,7 @@ dependencies = [ "pin-project 1.0.2", "regex", "rustls", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "serde_urlencoded", "socket2", @@ -399,9 +399,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7" +checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4" [[package]] name = "arrayvec" @@ -466,7 +466,7 @@ dependencies = [ "percent-encoding", "rand", "rustls", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "serde_urlencoded", ] @@ -496,7 +496,7 @@ dependencies = [ "log", "num_cpus", "rand", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "thiserror", "tokio 0.2.23", @@ -515,7 +515,7 @@ dependencies = [ "async-trait", "chrono", "log", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "thiserror", "tokio 0.2.23", @@ -745,7 +745,7 @@ dependencies = [ "libc", "num-integer", "num-traits 0.2.14", - "serde 1.0.117", + "serde 1.0.118", "time 0.1.44", "winapi 0.3.9", ] @@ -759,15 +759,6 @@ dependencies = [ "generic-array 0.14.4", ] -[[package]] -name = "cloudabi" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" -dependencies = [ - "bitflags", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -800,7 +791,7 @@ checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" dependencies = [ "lazy_static", "nom 5.1.2", - "serde 1.0.117", + "serde 1.0.118", "serde-hjson", ] @@ -1686,7 +1677,7 @@ dependencies = [ "base64 0.12.3", "pem", "ring", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "simple_asn1", ] @@ -1745,7 +1736,7 @@ dependencies = [ "openssl", "rand", "reqwest", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "sha2", "strum", @@ -1789,7 +1780,7 @@ dependencies = [ "percent-encoding", "rand", "reqwest", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "sha2", "strum", @@ -1811,7 +1802,7 @@ dependencies = [ "lemmy_utils", "log", "regex", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "sha2", "strum", @@ -1862,7 +1853,7 @@ dependencies = [ "openssl", "reqwest", "rss", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "sha2", "strum", @@ -1880,7 +1871,7 @@ dependencies = [ "lemmy_db", "lemmy_utils", "log", - "serde 1.0.117", + "serde 1.0.118", "serde_json", ] @@ -1903,7 +1894,7 @@ dependencies = [ "rand", "regex", "reqwest", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "thiserror", "url", @@ -1925,7 +1916,7 @@ dependencies = [ "log", "rand", "reqwest", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "strum", "strum_macros", @@ -1950,7 +1941,7 @@ dependencies = [ "r2d2", "rand", "regex", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "uuid", ] @@ -1970,9 +1961,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" +checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" [[package]] name = "linked-hash-map" @@ -2367,12 +2358,11 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" +checksum = "d7c6d9b8427445284a09c55be860a15855ab580a417ccad9da88f5a06787ced0" dependencies = [ - "cfg-if 0.1.10", - "cloudabi", + "cfg-if 1.0.0", "instant", "libc", "redox_syscall", @@ -2723,7 +2713,7 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite 0.2.0", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "serde_urlencoded", "tokio 0.2.23", @@ -2907,9 +2897,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.117" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" +checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" dependencies = [ "serde_derive", ] @@ -2929,9 +2919,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.117" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" +checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" dependencies = [ "proc-macro2", "quote", @@ -2947,7 +2937,7 @@ dependencies = [ "indexmap", "itoa", "ryu", - "serde 1.0.117", + "serde 1.0.118", ] [[package]] @@ -2968,7 +2958,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.117", + "serde 1.0.118", ] [[package]] @@ -3049,9 +3039,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85" +checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" [[package]] name = "socket2" @@ -3108,7 +3098,7 @@ checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ "proc-macro2", "quote", - "serde 1.0.117", + "serde 1.0.118", "serde_derive", "syn", ] @@ -3122,7 +3112,7 @@ dependencies = [ "base-x", "proc-macro2", "quote", - "serde 1.0.117", + "serde 1.0.118", "serde_derive", "serde_json", "sha1", @@ -3161,9 +3151,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.53" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68" +checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44" dependencies = [ "proc-macro2", "quote", @@ -3558,7 +3548,7 @@ dependencies = [ "idna", "matches", "percent-encoding", - "serde 1.0.117", + "serde 1.0.118", ] [[package]] @@ -3568,7 +3558,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" dependencies = [ "rand", - "serde 1.0.117", + "serde 1.0.118", ] [[package]] @@ -3650,7 +3640,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e" dependencies = [ "cfg-if 1.0.0", - "serde 1.0.117", + "serde 1.0.118", "serde_json", "wasm-bindgen-macro", ] diff --git a/Cargo.toml b/Cargo.toml index 42378726..019d0db3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ lemmy_websocket = { path = "./lemmy_websocket" } diesel = "1.4.5" diesel_migrations = "1.4.0" chrono = { version = "0.4.19", features = ["serde"] } -serde = { version = "1.0.117", features = ["derive"] } +serde = { version = "1.0.118", features = ["derive"] } actix = "0.10.0" actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] } actix-files = { version = "0.4.1", default-features = false } @@ -44,7 +44,7 @@ openssl = "0.10.30" http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] } tokio = "0.3.5" sha2 = "0.9.2" -anyhow = "1.0.34" +anyhow = "1.0.35" reqwest = { version = "0.10.9", features = ["json"] } activitystreams = "0.7.0-alpha.8" actix-rt = { version = "1.1.1", default-features = false } diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index e00f5ced..93f6c42d 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -1,23 +1,25 @@ # Summary -- [About](about.md) - - [Features](about_features.md) - - [Goals](about_goals.md) - - [Post and Comment Ranking](about_ranking.md) - - [Guide](about_guide.md) -- [Administration](administration.md) - - [Install with Docker](administration_install_docker.md) - - [Install with Ansible](administration_install_ansible.md) - - [Configuration](administration_configuration.md) - - [Backup and Restore](administration_backup_and_restore.md) - - [Federation](administration_federation.md) -- [Contributing](contributing.md) - - [Docker Development](contributing_docker_development.md) - - [Local Development](contributing_local_development.md) - - [Tests](contributing_tests.md) - - [Federation Development](contributing_federation_development.md) - - [Websocket/HTTP API](contributing_websocket_http_api.md) - - [Federation Overview](contributing_federation_overview.md) - - [ActivityPub API Outline](contributing_apub_api_outline.md) - - [Theming Guide](contributing_theming.md) +- [About](about/about.md) + - [Features](about/features.md) + - [Goals](about/goals.md) + - [Post and Comment Ranking](about/ranking.md) + - [Guide](about/guide.md) +- [Administration](administration/administration.md) + - [Install with Docker](administration/install_docker.md) + - [Install with Ansible](administration/install_ansible.md) + - [Configuration](administration/configuration.md) + - [Backup and Restore](administration/backup_and_restore.md) +- [Federation](federation/federation.md) + - [Federation Overview](federation/overview.md) + - [Administration](federation/administration.md) + - [Resources](federation/resources.md) + - [Lemmy Protocol](federation/lemmy_protocol.md) +- [Contributing](contributing/contributing.md) + - [Docker Development](contributing/docker_development.md) + - [Local Development](contributing/local_development.md) + - [Theming Guide](contributing/theming.md) + - [Tests](contributing/tests.md) + - [Websocket/HTTP API](contributing/websocket_http_api.md) + - [Federation Development](contributing/federation_development.md) - [Lemmy Council](lemmy_council.md) diff --git a/docs/src/about.md b/docs/src/about/about.md similarity index 100% rename from docs/src/about.md rename to docs/src/about/about.md diff --git a/docs/src/about_features.md b/docs/src/about/features.md similarity index 100% rename from docs/src/about_features.md rename to docs/src/about/features.md diff --git a/docs/src/about_goals.md b/docs/src/about/goals.md similarity index 69% rename from docs/src/about_goals.md rename to docs/src/about/goals.md index ea86db07..0ce019fc 100644 --- a/docs/src/about_goals.md +++ b/docs/src/about/goals.md @@ -37,19 +37,3 @@ - [Zurb mentions](https://github.com/zurb/tribute) - [TippyJS](https://github.com/atomiks/tippyjs) - [SQL function indexes](https://sorentwo.com/2013/12/30/let-postgres-do-the-work.html) - -## Activitypub guides - -- https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/ -- https://raw.githubusercontent.com/w3c/activitypub/gh-pages/activitypub-tutorial.txt -- https://github.com/tOkeshu/activitypub-example -- https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/ -- Use the [activitypub crate.](https://docs.rs/activitypub/0.1.4/activitypub/) -- https://docs.rs/activitypub/0.1.4/activitypub/ -- [Activitypub vocab.](https://www.w3.org/TR/activitystreams-vocabulary/) -- [Activitypub main](https://www.w3.org/TR/activitypub/) -- [Federation.md](https://github.com/dariusk/gathio/blob/7fc93dbe9d4d99457a0e85c6c532112f415b7af2/FEDERATION.md) -- [Activitypub implementers guide](https://socialhub.activitypub.rocks/t/draft-guide-for-new-activitypub-implementers/479) -- [Data storage questions](https://socialhub.activitypub.rocks/t/data-storage-questions/579/3) -- [Activitypub as it has been understood](https://flak.tedunangst.com/post/ActivityPub-as-it-has-been-understood) -- [Asonix http signatures in rust](https://git.asonix.dog/Aardwolf/http-signature-normalization) diff --git a/docs/src/about_guide.md b/docs/src/about/guide.md similarity index 96% rename from docs/src/about_guide.md rename to docs/src/about/guide.md index d9f8adb0..59244b1e 100644 --- a/docs/src/about_guide.md +++ b/docs/src/about/guide.md @@ -17,7 +17,7 @@ Hot | Trending sort based on the score, and the post creation time. New | Newest items. Top | The highest scoring items in the given time frame. -For more detail, check the [Post and Comment Ranking details](about_ranking.md). +For more detail, check the [Post and Comment Ranking details](ranking.md). ## Markdown Guide diff --git a/docs/src/about_ranking.md b/docs/src/about/ranking.md similarity index 100% rename from docs/src/about_ranking.md rename to docs/src/about/ranking.md diff --git a/docs/src/administration.md b/docs/src/administration/administration.md similarity index 73% rename from docs/src/administration.md rename to docs/src/administration/administration.md index 690b7159..3211859b 100644 --- a/docs/src/administration.md +++ b/docs/src/administration/administration.md @@ -4,7 +4,7 @@ Information for Lemmy instance admins, and those who want to run a server. ## Install -Lemmy has two primary install methods, [docker](administration_install_docker.md), and [ansible](administration_install_ansible.md). Ansible simplifies deploying to a remote server, while docker is best for local testing. +Lemmy has two primary install methods, [docker](install_docker.md), and [ansible](install_ansible.md). Ansible simplifies deploying to a remote server, while docker is best for local testing. ### Manual install diff --git a/docs/src/administration_backup_and_restore.md b/docs/src/administration/backup_and_restore.md similarity index 100% rename from docs/src/administration_backup_and_restore.md rename to docs/src/administration/backup_and_restore.md diff --git a/docs/src/administration_configuration.md b/docs/src/administration/configuration.md similarity index 100% rename from docs/src/administration_configuration.md rename to docs/src/administration/configuration.md diff --git a/docs/src/administration_install_ansible.md b/docs/src/administration/install_ansible.md similarity index 78% rename from docs/src/administration_install_ansible.md rename to docs/src/administration/install_ansible.md index 849957ad..ea04f53d 100644 --- a/docs/src/administration_install_ansible.md +++ b/docs/src/administration/install_ansible.md @@ -1,6 +1,6 @@ # Ansible Installation -This is the same as the [Docker installation](administration_install_docker.md), except that Ansible handles all of it automatically. It also does some extra things like setting up TLS and email for your Lemmy instance. +This is the same as the [Docker installation](install_docker.md), except that Ansible handles all of it automatically. It also does some extra things like setting up TLS and email for your Lemmy instance. First, you need to [install Ansible on your local computer](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) (e.g. using `sudo apt install ansible`) or the equivalent for you platform. diff --git a/docs/src/administration_install_docker.md b/docs/src/administration/install_docker.md similarity index 91% rename from docs/src/administration_install_docker.md rename to docs/src/administration/install_docker.md index 490fe1c0..4796fe52 100644 --- a/docs/src/administration_install_docker.md +++ b/docs/src/administration/install_docker.md @@ -27,7 +27,7 @@ Open up your `docker-compose.yml`, and make sure `LEMMY_EXTERNAL_HOST` for `lemm If you'd like a different database password, you should also change it in the `docker-compose.yml` **before** your first run. -After this, have a look at the [config file](administration_configuration.md) named `lemmy.hjson`, and adjust it, in particular the hostname, and possibly the db password. Then run: +After this, have a look at the [config file](configuration.md) named `lemmy.hjson`, and adjust it, in particular the hostname, and possibly the db password. Then run: `docker-compose up -d` diff --git a/docs/src/contributing.md b/docs/src/contributing/contributing.md similarity index 100% rename from docs/src/contributing.md rename to docs/src/contributing/contributing.md diff --git a/docs/src/contributing_docker_development.md b/docs/src/contributing/docker_development.md similarity index 91% rename from docs/src/contributing_docker_development.md rename to docs/src/contributing/docker_development.md index 3272efe2..ee035dd2 100644 --- a/docs/src/contributing_docker_development.md +++ b/docs/src/contributing/docker_development.md @@ -29,4 +29,4 @@ To speed up the Docker compile, add the following to `/etc/docker/daemon.json` a ``` If the build is still too slow, you will have to use a -[local build](contributing_local_development.md) instead. +[local build](local_development.md) instead. diff --git a/docs/src/contributing_federation_development.md b/docs/src/contributing/federation_development.md similarity index 90% rename from docs/src/contributing_federation_development.md rename to docs/src/contributing/federation_development.md index 0c6fd9c4..fd625d60 100644 --- a/docs/src/contributing_federation_development.md +++ b/docs/src/contributing/federation_development.md @@ -2,7 +2,7 @@ ## Running locally -Install the dependencies as described in [Docker development](contributing_docker_development.md). Then run the following +Install the dependencies as described in [Docker development](docker_development.md). Then run the following ```bash cd docker/federation @@ -34,8 +34,8 @@ Firefox containers are a good way to test them interacting. Note that federation is currently in alpha. **Only use it for testing**, not on any production server, and be aware that turning on federation may break your instance. -Follow the normal installation instructions, either with [Ansible](administration_install_ansible.md) or -[manually](administration_install_docker.md). Then replace the line `image: dessalines/lemmy:v0.x.x` in +Follow the normal installation instructions, either with [Ansible](../administration/install_ansible.md) or +[manually](../administration/install_docker.md). Then replace the line `image: dessalines/lemmy:v0.x.x` in `/lemmy/docker-compose.yml` with `image: dessalines/lemmy:federation`. Also add the following in `/lemmy/lemmy.hjson`: diff --git a/docs/src/contributing_local_development.md b/docs/src/contributing/local_development.md similarity index 97% rename from docs/src/contributing_local_development.md rename to docs/src/contributing/local_development.md index ccf1f51e..114a5458 100644 --- a/docs/src/contributing_local_development.md +++ b/docs/src/contributing/local_development.md @@ -85,4 +85,4 @@ and go to [localhost:1234](http://localhost:1234). Front end saves should rebuil Note that this setup doesn't include image uploads or link previews (provided by pict-rs and iframely respectively). If you want to test those, you should use the -[Docker development](contributing_docker_development.md). +[Docker development](docker_development.md). diff --git a/docs/src/contributing_tests.md b/docs/src/contributing/tests.md similarity index 51% rename from docs/src/contributing_tests.md rename to docs/src/contributing/tests.md index 494cf5cd..e4cfa65a 100644 --- a/docs/src/contributing_tests.md +++ b/docs/src/contributing/tests.md @@ -2,7 +2,7 @@ #### Rust -After installing [local development dependencies](contributing_local_development.md), run the +After installing [local development dependencies](local_development.md), run the following commands: ```bash @@ -12,7 +12,7 @@ psql -U lemmy -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;" ### Federation -Install the [Docker development dependencies](contributing_docker_development.md), and execute: +Install the [Docker development dependencies](docker_development.md), and execute: ``` cd docker/federation diff --git a/docs/src/contributing_theming.md b/docs/src/contributing/theming.md similarity index 100% rename from docs/src/contributing_theming.md rename to docs/src/contributing/theming.md diff --git a/docs/src/contributing_websocket_http_api.md b/docs/src/contributing/websocket_http_api.md similarity index 100% rename from docs/src/contributing_websocket_http_api.md rename to docs/src/contributing/websocket_http_api.md diff --git a/docs/src/administration_federation.md b/docs/src/federation/administration.md similarity index 96% rename from docs/src/administration_federation.md rename to docs/src/federation/administration.md index c7044ac2..fc24216c 100644 --- a/docs/src/administration_federation.md +++ b/docs/src/federation/administration.md @@ -1,4 +1,4 @@ -# Federation +# Federation Administration Note: ActivityPub federation is still under development. We recommend that you only enable it on test instances for now. @@ -11,6 +11,7 @@ Federation does not start automatically, but needs to be triggered manually thro - `https://lemmy.ml/c/programming` (Community) - `https://lemmy.ml/u/nutomic` (User) - `https://lemmy.ml/post/123` (Post) +- `https://lemmy.ml/comment/321` (Comment) For an overview of how federation in Lemmy works on a technical level, check out our [Federation Overview](contributing_federation_overview.md). diff --git a/docs/src/federation/federation.md b/docs/src/federation/federation.md new file mode 100644 index 00000000..28e304d5 --- /dev/null +++ b/docs/src/federation/federation.md @@ -0,0 +1,14 @@ +# Federation + +Lemmy uses the ActivityPub protocol (a W3C standard) to enable federation between different servers (often called instances). This is very similar to the way email works. For example, if you use gmail.com, then you can't just send mails to other gmail.com users, but also to yahoo.com, yandex.ru and so on. Email uses the SMTP protocol to achieve this, so you can think of ActivityPub as "SMTP for social media". The amount of different actions possible on social media (post, comment, like, share, etc) means that ActivityPub is much more complicated than SMTP. + +As with email, ActivityPub federation happens only between servers. So if you are registered on `enterprise.lemmy.ml`, you only connect to the API of `enterprise.lemmy.ml`, while the server takes care of sending and receiving data from other instances (eg `voyager.lemmy.ml`). The great advantage of this approach is that the average user doesn't have to do anything to use federation. In fact if you are using Lemmy, you are likely already using it. One way to confirm is by going to a community or user profile. If you are on `enterprise.lemmy.ml` and you see a user like `@nutomic@voyager.lemmy.ml`, or a community like `!main@ds9.lemmy.ml`, then those are federated, meaning they use a different instance from yours. + +One way you can take advantage of federation is by opening a different instance, like `ds9.lemmy.ml`, and browsing it. If you see an interesting community, post or user that you want to interact with, just copy its URL and paste it into the search of your own instance. Your instance will connect to the other one (assuming the allowlist/blocklist allows it), and directly display the remote content to you, so that you can follow a community or comment on a post. Here are some examples of working searches: + +- `!main@lemmy.ml` (Community) +- `@nutomic@lemmy.ml` (User) +- `https://lemmy.ml/c/programming` (Community) +- `https://lemmy.ml/u/nutomic` (User) +- `https://lemmy.ml/post/123` (Post) +- `https://lemmy.ml/comment/321` (Comment) \ No newline at end of file diff --git a/docs/src/contributing_apub_api_outline.md b/docs/src/federation/lemmy_protocol.md similarity index 98% rename from docs/src/contributing_apub_api_outline.md rename to docs/src/federation/lemmy_protocol.md index 92cdf9c0..feb2324b 100644 --- a/docs/src/contributing_apub_api_outline.md +++ b/docs/src/federation/lemmy_protocol.md @@ -1,4 +1,6 @@ -# Activitypub API outline +# Lemmy Federation Protocol + +The Lemmy Protocol (or Lemmy Federation Protocol) is a strict subset of the [ActivityPub Protocol](https://www.w3.org/TR/activitypub/). Any deviation from the ActivityPub protocol is a bug in Lemmy or in this documentation (or both). This document is targeted at developers who are familiar with the ActivityPub and ActivityStreams protocols. It gives a detailed outline of the actors, objects and activities used by Lemmy. @@ -438,7 +440,7 @@ Sent to: Community "cc": [ "https://ds9.lemmy.ml/c/main/" ], - "object": ... + "object": "https://enterprise.lemmy.ml/p/123" } ``` @@ -465,7 +467,7 @@ Sent to: Community "cc": [ "https://ds9.lemmy.ml/c/main/" ], - "object": ... + "object": "https://enterprise.lemmy.ml/p/123" } ``` diff --git a/docs/src/contributing_federation_overview.md b/docs/src/federation/overview.md similarity index 99% rename from docs/src/contributing_federation_overview.md rename to docs/src/federation/overview.md index abfac455..949984b8 100644 --- a/docs/src/contributing_federation_overview.md +++ b/docs/src/federation/overview.md @@ -1,4 +1,4 @@ -# Federation +# Federation Overview This document is for anyone who wants to know how Lemmy federation works, without being overly technical. It is meant provide a high-level overview of ActivityPub federation in Lemmy. If you are implementing ActivityPub yourself and want to be compatible with Lemmy, read our [ActivityPub API outline](contributing_apub_api_outline.md). diff --git a/docs/src/federation/resources.md b/docs/src/federation/resources.md new file mode 100644 index 00000000..0f14691c --- /dev/null +++ b/docs/src/federation/resources.md @@ -0,0 +1,22 @@ +# ActivityPub Resources + +## Official Documents + +- [ActivityPub standard](https://www.w3.org/TR/activitypub/) +- [Activitypub vocabulary](https://www.w3.org/TR/activitystreams-vocabulary/) + +## Explanations + +- [ActivityPub - one protocol to rule them all?](https://schub.io/blog/2018/02/01/activitypub-one-protocol-to-rule-them-all.html) +- [A highly opinionated guide to learning about ActivityPub](https://tinysubversions.com/notes/reading-activitypub/) +- [Activitypub implementers guide](https://socialhub.activitypub.rocks/t/draft-guide-for-new-activitypub-implementers/479) +- [Mastodon Blog: How to implement a basic ActivityPub server](https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/) +- [Mastodon Blog: Implementing an ActivityPub inbox](https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/) +- [Data storage questions](https://socialhub.activitypub.rocks/t/data-storage-questions/579) +- [Activitypub as it has been understood](https://flak.tedunangst.com/post/ActivityPub-as-it-has-been-understood) + +## Examples and Libraries + +- [ActivityPub example server](https://github.com/tOkeshu/activitypub-example) +- [ActivityStreams crate](https://docs.rs/activitystreams/) +- [HTTP Signatures crate](https://git.asonix.dog/Aardwolf/http-signature-normalization) \ No newline at end of file diff --git a/lemmy_api/Cargo.toml b/lemmy_api/Cargo.toml index 1a39df41..4d32db2f 100644 --- a/lemmy_api/Cargo.toml +++ b/lemmy_api/Cargo.toml @@ -19,7 +19,7 @@ diesel = "1.4.5" bcrypt = "0.9.0" chrono = { version = "0.4.19", features = ["serde"] } serde_json = { version = "1.0.60", features = ["preserve_order"] } -serde = { version = "1.0.117", features = ["derive"] } +serde = { version = "1.0.118", features = ["derive"] } actix = "0.10.0" actix-web = { version = "3.3.2", default-features = false } actix-rt = { version = "1.1.1", default-features = false } @@ -42,7 +42,7 @@ uuid = { version = "0.8.1", features = ["serde", "v4"] } sha2 = "0.9.2" async-trait = "0.1.42" captcha = "0.0.8" -anyhow = "1.0.34" +anyhow = "1.0.35" thiserror = "1.0.22" background-jobs = "0.8.0" reqwest = { version = "0.10.9", features = ["json"] } diff --git a/lemmy_api/src/community.rs b/lemmy_api/src/community.rs index dcd9be05..fe7748f9 100644 --- a/lemmy_api/src/community.rs +++ b/lemmy_api/src/community.rs @@ -24,6 +24,7 @@ use lemmy_db::{ community_view::{CommunityQueryBuilder, CommunityView}, user_view::UserViewSafe, }, + ApubObject, Bannable, Crud, Followable, @@ -133,7 +134,7 @@ impl Perform for CreateCommunity { let actor_id = make_apub_endpoint(EndpointType::Community, &data.name).to_string(); let actor_id_cloned = actor_id.to_owned(); let community_dupe = blocking(context.pool(), move |conn| { - Community::read_from_actor_id(conn, &actor_id_cloned) + Community::read_from_apub_id(conn, &actor_id_cloned) }) .await?; if community_dupe.is_ok() { diff --git a/lemmy_apub/Cargo.toml b/lemmy_apub/Cargo.toml index 8b78c77c..fd4395c4 100644 --- a/lemmy_apub/Cargo.toml +++ b/lemmy_apub/Cargo.toml @@ -19,7 +19,7 @@ activitystreams-ext = "0.1.0-alpha.2" bcrypt = "0.9.0" chrono = { version = "0.4.19", features = ["serde"] } serde_json = { version = "1.0.60", features = ["preserve_order"] } -serde = { version = "1.0.117", features = ["derive"] } +serde = { version = "1.0.118", features = ["derive"] } actix = "0.10.0" actix-web = { version = "3.3.2", default-features = false } actix-rt = { version = "1.1.1", default-features = false } @@ -42,7 +42,7 @@ itertools = "0.9.0" uuid = { version = "0.8.1", features = ["serde", "v4"] } sha2 = "0.9.2" async-trait = "0.1.42" -anyhow = "1.0.34" +anyhow = "1.0.35" thiserror = "1.0.22" background-jobs = "0.8.0" reqwest = { version = "0.10.9", features = ["json"] } diff --git a/lemmy_apub/src/activities/receive/comment.rs b/lemmy_apub/src/activities/receive/comment.rs index bf8de1eb..ff0fb819 100644 --- a/lemmy_apub/src/activities/receive/comment.rs +++ b/lemmy_apub/src/activities/receive/comment.rs @@ -1,20 +1,13 @@ -use crate::{ - activities::receive::get_actor_as_user, - fetcher::get_or_fetch_and_insert_comment, - ActorType, - FromApub, - NoteExt, -}; +use crate::{activities::receive::get_actor_as_user, objects::FromApub, ActorType, NoteExt}; use activitystreams::{ activity::{ActorAndObjectRefExt, Create, Dislike, Like, Remove, Update}, base::ExtendsExt, }; -use anyhow::{anyhow, Context}; +use anyhow::Context; use lemmy_db::{ - comment::{Comment, CommentForm, CommentLike, CommentLikeForm}, + comment::{Comment, CommentLike, CommentLikeForm}, comment_view::CommentView, post::Post, - Crud, Likeable, }; use lemmy_structs::{blocking, comment::CommentResponse, send_local_notifs}; @@ -30,36 +23,22 @@ pub(crate) async fn receive_create_comment( let note = NoteExt::from_any_base(create.object().to_owned().one().context(location_info!())?)? .context(location_info!())?; - let comment = - CommentForm::from_apub(¬e, context, Some(user.actor_id()?), request_counter).await?; + let comment = Comment::from_apub(¬e, context, user.actor_id()?, request_counter).await?; let post_id = comment.post_id; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; - if post.locked { - return Err(anyhow!("Post is locked").into()); - } - - let inserted_comment = - blocking(context.pool(), move |conn| Comment::upsert(conn, &comment)).await??; // Note: // Although mentions could be gotten from the post tags (they are included there), or the ccs, // Its much easier to scrape them from the comment body, since the API has to do that // anyway. - let mentions = scrape_text_for_mentions(&inserted_comment.content); - let recipient_ids = send_local_notifs( - mentions, - inserted_comment.clone(), - &user, - post, - context.pool(), - true, - ) - .await?; + let mentions = scrape_text_for_mentions(&comment.content); + let recipient_ids = + send_local_notifs(mentions, comment.clone(), &user, post, context.pool(), true).await?; // Refetch the view let comment_view = blocking(context.pool(), move |conn| { - CommentView::read(conn, inserted_comment.id, None) + CommentView::read(conn, comment.id, None) }) .await??; @@ -87,36 +66,19 @@ pub(crate) async fn receive_update_comment( .context(location_info!())?; let user = get_actor_as_user(&update, context, request_counter).await?; - let comment = - CommentForm::from_apub(¬e, context, Some(user.actor_id()?), request_counter).await?; + let comment = Comment::from_apub(¬e, context, user.actor_id()?, request_counter).await?; - let original_comment_id = - get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter) - .await? - .id; - - let updated_comment = blocking(context.pool(), move |conn| { - Comment::update(conn, original_comment_id, &comment) - }) - .await??; - - let post_id = updated_comment.post_id; + let comment_id = comment.id; + let post_id = comment.post_id; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; - let mentions = scrape_text_for_mentions(&updated_comment.content); - let recipient_ids = send_local_notifs( - mentions, - updated_comment, - &user, - post, - context.pool(), - false, - ) - .await?; + let mentions = scrape_text_for_mentions(&comment.content); + let recipient_ids = + send_local_notifs(mentions, comment, &user, post, context.pool(), false).await?; // Refetch the view let comment_view = blocking(context.pool(), move |conn| { - CommentView::read(conn, original_comment_id, None) + CommentView::read(conn, comment_id, None) }) .await??; @@ -137,19 +99,13 @@ pub(crate) async fn receive_update_comment( pub(crate) async fn receive_like_comment( like: Like, + comment: Comment, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let note = NoteExt::from_any_base(like.object().to_owned().one().context(location_info!())?)? - .context(location_info!())?; let user = get_actor_as_user(&like, context, request_counter).await?; - let comment = CommentForm::from_apub(¬e, context, None, request_counter).await?; - - let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter) - .await? - .id; - + let comment_id = comment.id; let like_form = CommentLikeForm { comment_id, post_id: comment.post_id, @@ -188,25 +144,13 @@ pub(crate) async fn receive_like_comment( pub(crate) async fn receive_dislike_comment( dislike: Dislike, + comment: Comment, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let note = NoteExt::from_any_base( - dislike - .object() - .to_owned() - .one() - .context(location_info!())?, - )? - .context(location_info!())?; let user = get_actor_as_user(&dislike, context, request_counter).await?; - let comment = CommentForm::from_apub(¬e, context, None, request_counter).await?; - - let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter) - .await? - .id; - + let comment_id = comment.id; let like_form = CommentLikeForm { comment_id, post_id: comment.post_id, diff --git a/lemmy_apub/src/activities/receive/comment_undo.rs b/lemmy_apub/src/activities/receive/comment_undo.rs index f44604cc..2ee8c6ea 100644 --- a/lemmy_apub/src/activities/receive/comment_undo.rs +++ b/lemmy_apub/src/activities/receive/comment_undo.rs @@ -1,35 +1,23 @@ -use crate::{ - activities::receive::get_actor_as_user, - fetcher::get_or_fetch_and_insert_comment, - FromApub, - NoteExt, -}; -use activitystreams::{activity::*, prelude::*}; -use anyhow::Context; +use crate::activities::receive::get_actor_as_user; +use activitystreams::activity::{Dislike, Like}; use lemmy_db::{ - comment::{Comment, CommentForm, CommentLike}, + comment::{Comment, CommentLike}, comment_view::CommentView, Likeable, }; use lemmy_structs::{blocking, comment::CommentResponse}; -use lemmy_utils::{location_info, LemmyError}; +use lemmy_utils::LemmyError; use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation}; pub(crate) async fn receive_undo_like_comment( like: &Like, + comment: Comment, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { let user = get_actor_as_user(like, context, request_counter).await?; - let note = NoteExt::from_any_base(like.object().to_owned().one().context(location_info!())?)? - .context(location_info!())?; - - let comment = CommentForm::from_apub(¬e, context, None, request_counter).await?; - - let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter) - .await? - .id; + let comment_id = comment.id; let user_id = user.id; blocking(context.pool(), move |conn| { CommentLike::remove(conn, user_id, comment_id) @@ -61,25 +49,13 @@ pub(crate) async fn receive_undo_like_comment( pub(crate) async fn receive_undo_dislike_comment( dislike: &Dislike, + comment: Comment, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { let user = get_actor_as_user(dislike, context, request_counter).await?; - let note = NoteExt::from_any_base( - dislike - .object() - .to_owned() - .one() - .context(location_info!())?, - )? - .context(location_info!())?; - - let comment = CommentForm::from_apub(¬e, context, None, request_counter).await?; - - let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter) - .await? - .id; + let comment_id = comment.id; let user_id = user.id; blocking(context.pool(), move |conn| { CommentLike::remove(conn, user_id, comment_id) diff --git a/lemmy_apub/src/activities/receive/community.rs b/lemmy_apub/src/activities/receive/community.rs index 80c911b1..16b0c4e3 100644 --- a/lemmy_apub/src/activities/receive/community.rs +++ b/lemmy_apub/src/activities/receive/community.rs @@ -4,7 +4,7 @@ use activitystreams::{ base::{AnyBase, ExtendsExt}, }; use anyhow::Context; -use lemmy_db::{community::Community, views::community_view::CommunityView}; +use lemmy_db::{community::Community, views::community_view::CommunityView, ApubObject}; use lemmy_structs::{blocking, community::CommunityResponse}; use lemmy_utils::{location_info, LemmyError}; use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation}; @@ -53,7 +53,7 @@ pub(crate) async fn receive_remove_community( .single_xsd_any_uri() .context(location_info!())?; let community = blocking(context.pool(), move |conn| { - Community::read_from_actor_id(conn, community_uri.as_str()) + Community::read_from_apub_id(conn, community_uri.as_str()) }) .await??; @@ -135,7 +135,7 @@ pub(crate) async fn receive_undo_remove_community( .single_xsd_any_uri() .context(location_info!())?; let community = blocking(context.pool(), move |conn| { - Community::read_from_actor_id(conn, community_uri.as_str()) + Community::read_from_apub_id(conn, community_uri.as_str()) }) .await??; diff --git a/lemmy_apub/src/activities/receive/post.rs b/lemmy_apub/src/activities/receive/post.rs index 80044237..0bbf1eaf 100644 --- a/lemmy_apub/src/activities/receive/post.rs +++ b/lemmy_apub/src/activities/receive/post.rs @@ -1,19 +1,12 @@ -use crate::{ - activities::receive::get_actor_as_user, - fetcher::get_or_fetch_and_insert_post, - ActorType, - FromApub, - PageExt, -}; +use crate::{activities::receive::get_actor_as_user, objects::FromApub, ActorType, PageExt}; use activitystreams::{ activity::{Create, Dislike, Like, Remove, Update}, prelude::*, }; use anyhow::Context; use lemmy_db::{ - post::{Post, PostForm, PostLike, PostLikeForm}, + post::{Post, PostLike, PostLikeForm}, post_view::PostView, - Crud, Likeable, }; use lemmy_structs::{blocking, post::PostResponse}; @@ -29,16 +22,12 @@ pub(crate) async fn receive_create_post( let page = PageExt::from_any_base(create.object().to_owned().one().context(location_info!())?)? .context(location_info!())?; - let post = PostForm::from_apub(&page, context, Some(user.actor_id()?), request_counter).await?; - - // Using an upsert, since likes (which fetch the post), sometimes come in before the create - // resulting in double posts. - let inserted_post = blocking(context.pool(), move |conn| Post::upsert(conn, &post)).await??; + let post = Post::from_apub(&page, context, user.actor_id()?, request_counter).await?; // Refetch the view - let inserted_post_id = inserted_post.id; + let post_id = post.id; let post_view = blocking(context.pool(), move |conn| { - PostView::read(conn, inserted_post_id, None) + PostView::read(conn, post_id, None) }) .await??; @@ -62,20 +51,12 @@ pub(crate) async fn receive_update_post( let page = PageExt::from_any_base(update.object().to_owned().one().context(location_info!())?)? .context(location_info!())?; - let post = PostForm::from_apub(&page, context, Some(user.actor_id()?), request_counter).await?; - - let original_post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter) - .await? - .id; - - blocking(context.pool(), move |conn| { - Post::update(conn, original_post_id, &post) - }) - .await??; + let post = Post::from_apub(&page, context, user.actor_id()?, request_counter).await?; + let post_id = post.id; // Refetch the view let post_view = blocking(context.pool(), move |conn| { - PostView::read(conn, original_post_id, None) + PostView::read(conn, post_id, None) }) .await??; @@ -92,19 +73,13 @@ pub(crate) async fn receive_update_post( pub(crate) async fn receive_like_post( like: Like, + post: Post, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { let user = get_actor_as_user(&like, context, request_counter).await?; - let page = PageExt::from_any_base(like.object().to_owned().one().context(location_info!())?)? - .context(location_info!())?; - - let post = PostForm::from_apub(&page, context, None, request_counter).await?; - - let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter) - .await? - .id; + let post_id = post.id; let like_form = PostLikeForm { post_id, user_id: user.id, @@ -136,25 +111,13 @@ pub(crate) async fn receive_like_post( pub(crate) async fn receive_dislike_post( dislike: Dislike, + post: Post, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { let user = get_actor_as_user(&dislike, context, request_counter).await?; - let page = PageExt::from_any_base( - dislike - .object() - .to_owned() - .one() - .context(location_info!())?, - )? - .context(location_info!())?; - - let post = PostForm::from_apub(&page, context, None, request_counter).await?; - - let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter) - .await? - .id; + let post_id = post.id; let like_form = PostLikeForm { post_id, user_id: user.id, diff --git a/lemmy_apub/src/activities/receive/post_undo.rs b/lemmy_apub/src/activities/receive/post_undo.rs index 99d0ed1d..bcbb7fee 100644 --- a/lemmy_apub/src/activities/receive/post_undo.rs +++ b/lemmy_apub/src/activities/receive/post_undo.rs @@ -1,35 +1,23 @@ -use crate::{ - activities::receive::get_actor_as_user, - fetcher::get_or_fetch_and_insert_post, - FromApub, - PageExt, -}; -use activitystreams::{activity::*, prelude::*}; -use anyhow::Context; +use crate::activities::receive::get_actor_as_user; +use activitystreams::activity::{Dislike, Like}; use lemmy_db::{ - post::{Post, PostForm, PostLike}, + post::{Post, PostLike}, post_view::PostView, Likeable, }; use lemmy_structs::{blocking, post::PostResponse}; -use lemmy_utils::{location_info, LemmyError}; +use lemmy_utils::LemmyError; use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation}; pub(crate) async fn receive_undo_like_post( like: &Like, + post: Post, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { let user = get_actor_as_user(like, context, request_counter).await?; - let page = PageExt::from_any_base(like.object().to_owned().one().context(location_info!())?)? - .context(location_info!())?; - - let post = PostForm::from_apub(&page, context, None, request_counter).await?; - - let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter) - .await? - .id; + let post_id = post.id; let user_id = user.id; blocking(context.pool(), move |conn| { PostLike::remove(conn, user_id, post_id) @@ -55,25 +43,13 @@ pub(crate) async fn receive_undo_like_post( pub(crate) async fn receive_undo_dislike_post( dislike: &Dislike, + post: Post, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { let user = get_actor_as_user(dislike, context, request_counter).await?; - let page = PageExt::from_any_base( - dislike - .object() - .to_owned() - .one() - .context(location_info!())?, - )? - .context(location_info!())?; - - let post = PostForm::from_apub(&page, context, None, request_counter).await?; - - let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter) - .await? - .id; + let post_id = post.id; let user_id = user.id; blocking(context.pool(), move |conn| { PostLike::remove(conn, user_id, post_id) diff --git a/lemmy_apub/src/activities/receive/private_message.rs b/lemmy_apub/src/activities/receive/private_message.rs index 07913226..f05b5237 100644 --- a/lemmy_apub/src/activities/receive/private_message.rs +++ b/lemmy_apub/src/activities/receive/private_message.rs @@ -3,7 +3,7 @@ use crate::{ check_is_apub_id_valid, fetcher::get_or_fetch_and_upsert_user, inbox::get_activity_to_and_cc, - FromApub, + objects::FromApub, NoteExt, }; use activitystreams::{ @@ -13,11 +13,7 @@ use activitystreams::{ public, }; use anyhow::{anyhow, Context}; -use lemmy_db::{ - private_message::{PrivateMessage, PrivateMessageForm}, - private_message_view::PrivateMessageView, - Crud, -}; +use lemmy_db::{private_message::PrivateMessage, private_message_view::PrivateMessageView}; use lemmy_structs::{blocking, user::PrivateMessageResponse}; use lemmy_utils::{location_info, LemmyError}; use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation}; @@ -41,15 +37,10 @@ pub(crate) async fn receive_create_private_message( .context(location_info!())?; let private_message = - PrivateMessageForm::from_apub(¬e, context, Some(expected_domain), request_counter).await?; - - let inserted_private_message = blocking(&context.pool(), move |conn| { - PrivateMessage::create(conn, &private_message) - }) - .await??; + PrivateMessage::from_apub(¬e, context, expected_domain, request_counter).await?; let message = blocking(&context.pool(), move |conn| { - PrivateMessageView::read(conn, inserted_private_message.id) + PrivateMessageView::read(conn, private_message.id) }) .await??; @@ -82,24 +73,8 @@ pub(crate) async fn receive_update_private_message( .to_owned(); let note = NoteExt::from_any_base(object)?.context(location_info!())?; - let private_message_form = - PrivateMessageForm::from_apub(¬e, context, Some(expected_domain), request_counter).await?; - - let private_message_ap_id = private_message_form - .ap_id - .as_ref() - .context(location_info!())? - .clone(); - let private_message = blocking(&context.pool(), move |conn| { - PrivateMessage::read_from_apub_id(conn, &private_message_ap_id) - }) - .await??; - - let private_message_id = private_message.id; - blocking(&context.pool(), move |conn| { - PrivateMessage::update(conn, private_message_id, &private_message_form) - }) - .await??; + let private_message = + PrivateMessage::from_apub(¬e, context, expected_domain, request_counter).await?; let private_message_id = private_message.id; let message = blocking(&context.pool(), move |conn| { diff --git a/lemmy_apub/src/activities/send/comment.rs b/lemmy_apub/src/activities/send/comment.rs index 2aee7b6e..744a1cdd 100644 --- a/lemmy_apub/src/activities/send/comment.rs +++ b/lemmy_apub/src/activities/send/comment.rs @@ -3,10 +3,10 @@ use crate::{ activity_queue::{send_comment_mentions, send_to_community}, extensions::context::lemmy_context, fetcher::get_or_fetch_and_upsert_user, + objects::ToApub, ActorType, ApubLikeableType, ApubObjectType, - ToApub, }; use activitystreams::{ activity::{ @@ -218,8 +218,6 @@ impl ApubObjectType for Comment { #[async_trait::async_trait(?Send)] impl ApubLikeableType for Comment { async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> { - let note = self.to_apub(context.pool()).await?; - let post_id = self.post_id; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; @@ -229,7 +227,7 @@ impl ApubLikeableType for Comment { }) .await??; - let mut like = Like::new(creator.actor_id.to_owned(), note.into_any_base()?); + let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?); like .set_many_contexts(lemmy_context()?) .set_id(generate_activity_id(LikeType::Like)?) @@ -241,8 +239,6 @@ impl ApubLikeableType for Comment { } async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> { - let note = self.to_apub(context.pool()).await?; - let post_id = self.post_id; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; @@ -252,7 +248,7 @@ impl ApubLikeableType for Comment { }) .await??; - let mut dislike = Dislike::new(creator.actor_id.to_owned(), note.into_any_base()?); + let mut dislike = Dislike::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?); dislike .set_many_contexts(lemmy_context()?) .set_id(generate_activity_id(DislikeType::Dislike)?) @@ -268,8 +264,6 @@ impl ApubLikeableType for Comment { creator: &User_, context: &LemmyContext, ) -> Result<(), LemmyError> { - let note = self.to_apub(context.pool()).await?; - let post_id = self.post_id; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; @@ -279,7 +273,7 @@ impl ApubLikeableType for Comment { }) .await??; - let mut like = Like::new(creator.actor_id.to_owned(), note.into_any_base()?); + let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?); like .set_many_contexts(lemmy_context()?) .set_id(generate_activity_id(DislikeType::Dislike)?) diff --git a/lemmy_apub/src/activities/send/post.rs b/lemmy_apub/src/activities/send/post.rs index da78667f..f6eabb04 100644 --- a/lemmy_apub/src/activities/send/post.rs +++ b/lemmy_apub/src/activities/send/post.rs @@ -2,10 +2,10 @@ use crate::{ activities::send::generate_activity_id, activity_queue::send_to_community, extensions::context::lemmy_context, + objects::ToApub, ActorType, ApubLikeableType, ApubObjectType, - ToApub, }; use activitystreams::{ activity::{ @@ -167,15 +167,13 @@ impl ApubObjectType for Post { #[async_trait::async_trait(?Send)] impl ApubLikeableType for Post { async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> { - let page = self.to_apub(context.pool()).await?; - let community_id = self.community_id; let community = blocking(context.pool(), move |conn| { Community::read(conn, community_id) }) .await??; - let mut like = Like::new(creator.actor_id.to_owned(), page.into_any_base()?); + let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?); like .set_many_contexts(lemmy_context()?) .set_id(generate_activity_id(LikeType::Like)?) @@ -187,15 +185,13 @@ impl ApubLikeableType for Post { } async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> { - let page = self.to_apub(context.pool()).await?; - let community_id = self.community_id; let community = blocking(context.pool(), move |conn| { Community::read(conn, community_id) }) .await??; - let mut dislike = Dislike::new(creator.actor_id.to_owned(), page.into_any_base()?); + let mut dislike = Dislike::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?); dislike .set_many_contexts(lemmy_context()?) .set_id(generate_activity_id(DislikeType::Dislike)?) @@ -211,15 +207,13 @@ impl ApubLikeableType for Post { creator: &User_, context: &LemmyContext, ) -> Result<(), LemmyError> { - let page = self.to_apub(context.pool()).await?; - let community_id = self.community_id; let community = blocking(context.pool(), move |conn| { Community::read(conn, community_id) }) .await??; - let mut like = Like::new(creator.actor_id.to_owned(), page.into_any_base()?); + let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?); like .set_many_contexts(lemmy_context()?) .set_id(generate_activity_id(LikeType::Like)?) diff --git a/lemmy_apub/src/activities/send/private_message.rs b/lemmy_apub/src/activities/send/private_message.rs index 23528b5c..e8bc979a 100644 --- a/lemmy_apub/src/activities/send/private_message.rs +++ b/lemmy_apub/src/activities/send/private_message.rs @@ -2,9 +2,9 @@ use crate::{ activities::send::generate_activity_id, activity_queue::send_activity_single_dest, extensions::context::lemmy_context, + objects::ToApub, ActorType, ApubObjectType, - ToApub, }; use activitystreams::{ activity::{ diff --git a/lemmy_apub/src/activities/send/user.rs b/lemmy_apub/src/activities/send/user.rs index a94f241d..8c539c4b 100644 --- a/lemmy_apub/src/activities/send/user.rs +++ b/lemmy_apub/src/activities/send/user.rs @@ -16,6 +16,7 @@ use activitystreams::{ use lemmy_db::{ community::{Community, CommunityFollower, CommunityFollowerForm}, user::User_, + ApubObject, DbPool, Followable, }; @@ -46,7 +47,7 @@ impl ActorType for User_ { ) -> Result<(), LemmyError> { let follow_actor_id = follow_actor_id.to_string(); let community = blocking(context.pool(), move |conn| { - Community::read_from_actor_id(conn, &follow_actor_id) + Community::read_from_apub_id(conn, &follow_actor_id) }) .await??; @@ -77,7 +78,7 @@ impl ActorType for User_ { ) -> Result<(), LemmyError> { let follow_actor_id = follow_actor_id.to_string(); let community = blocking(context.pool(), move |conn| { - Community::read_from_actor_id(conn, &follow_actor_id) + Community::read_from_apub_id(conn, &follow_actor_id) }) .await??; diff --git a/lemmy_apub/src/activity_queue.rs b/lemmy_apub/src/activity_queue.rs index 5e4f113b..46780279 100644 --- a/lemmy_apub/src/activity_queue.rs +++ b/lemmy_apub/src/activity_queue.rs @@ -33,7 +33,7 @@ use url::Url; /// * `activity` the apub activity to be sent /// * `creator` the local actor which created the activity /// * `inbox` the inbox url where the activity should be delivered to -pub async fn send_activity_single_dest( +pub(crate) async fn send_activity_single_dest( activity: T, creator: &dyn ActorType, inbox: Url, @@ -71,7 +71,7 @@ where /// * `community` the sending community /// * `sender_shared_inbox` in case of an announce, this should be the shared inbox of the inner /// activities creator, as receiving a known activity will cause an error -pub async fn send_to_community_followers( +pub(crate) async fn send_to_community_followers( activity: T, community: &Community, context: &LemmyContext, @@ -116,7 +116,7 @@ where /// * `creator` the creator of the activity /// * `community` the destination community /// -pub async fn send_to_community( +pub(crate) async fn send_to_community( activity: T, creator: &User_, community: &Community, @@ -160,7 +160,7 @@ where /// * `creator` user who created the comment /// * `mentions` list of inboxes of users which are mentioned in the comment /// * `activity` either a `Create/Note` or `Update/Note` -pub async fn send_comment_mentions( +pub(crate) async fn send_comment_mentions( creator: &User_, mentions: Vec, activity: T, diff --git a/lemmy_apub/src/extensions/signatures.rs b/lemmy_apub/src/extensions/signatures.rs index 6ff61df4..67fe4b1c 100644 --- a/lemmy_apub/src/extensions/signatures.rs +++ b/lemmy_apub/src/extensions/signatures.rs @@ -65,7 +65,10 @@ pub async fn sign_and_send( } /// Verifies the HTTP signature on an incoming inbox request. -pub fn verify_signature(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), LemmyError> { +pub(crate) fn verify_signature( + request: &HttpRequest, + actor: &dyn ActorType, +) -> Result<(), LemmyError> { let public_key = actor.public_key().context(location_info!())?; let verified = CONFIG2 .begin_verify( diff --git a/lemmy_apub/src/fetcher.rs b/lemmy_apub/src/fetcher.rs index 0eb33cb7..7556580e 100644 --- a/lemmy_apub/src/fetcher.rs +++ b/lemmy_apub/src/fetcher.rs @@ -1,7 +1,7 @@ use crate::{ check_is_apub_id_valid, + objects::FromApub, ActorType, - FromApub, GroupExt, NoteExt, PageExt, @@ -13,15 +13,15 @@ use anyhow::{anyhow, Context}; use chrono::NaiveDateTime; use diesel::result::Error::NotFound; use lemmy_db::{ - comment::{Comment, CommentForm}, + comment::Comment, comment_view::CommentView, - community::{Community, CommunityForm, CommunityModerator, CommunityModeratorForm}, + community::{Community, CommunityModerator, CommunityModeratorForm}, naive_now, - post::{Post, PostForm}, + post::Post, post_view::PostView, - user::{UserForm, User_}, + user::User_, views::{community_view::CommunityView, user_view::UserViewSafe}, - Crud, + ApubObject, Joinable, SearchType, }; @@ -183,22 +183,16 @@ pub async fn search_by_apub_id( response } SearchAcceptedObjects::Page(p) => { - let post_form = PostForm::from_apub(&p, context, Some(query_url), recursion_counter).await?; + let p = Post::from_apub(&p, context, query_url, recursion_counter).await?; - let p = blocking(context.pool(), move |conn| Post::upsert(conn, &post_form)).await??; response.posts = vec![blocking(context.pool(), move |conn| PostView::read(conn, p.id, None)).await??]; response } SearchAcceptedObjects::Comment(c) => { - let comment_form = - CommentForm::from_apub(&c, context, Some(query_url), recursion_counter).await?; + let c = Comment::from_apub(&c, context, query_url, recursion_counter).await?; - let c = blocking(context.pool(), move |conn| { - Comment::upsert(conn, &comment_form) - }) - .await??; response.comments = vec![ blocking(context.pool(), move |conn| { CommentView::read(conn, c.id, None) @@ -242,7 +236,7 @@ pub(crate) async fn get_or_fetch_and_upsert_user( ) -> Result { let apub_id_owned = apub_id.to_owned(); let user = blocking(context.pool(), move |conn| { - User_::read_from_actor_id(conn, apub_id_owned.as_ref()) + User_::read_from_apub_id(conn, apub_id_owned.as_ref()) }) .await?; @@ -257,15 +251,13 @@ pub(crate) async fn get_or_fetch_and_upsert_user( return Ok(u); } - let mut uf = UserForm::from_apub( - &person?, - context, - Some(apub_id.to_owned()), - recursion_counter, - ) - .await?; - uf.last_refreshed_at = Some(naive_now()); - let user = blocking(context.pool(), move |conn| User_::update(conn, u.id, &uf)).await??; + let user = User_::from_apub(&person?, context, apub_id.to_owned(), recursion_counter).await?; + + let user_id = user.id; + blocking(context.pool(), move |conn| { + User_::mark_as_updated(conn, user_id) + }) + .await??; Ok(user) } @@ -275,14 +267,7 @@ pub(crate) async fn get_or_fetch_and_upsert_user( let person = fetch_remote_object::(context.client(), apub_id, recursion_counter).await?; - let uf = UserForm::from_apub( - &person, - context, - Some(apub_id.to_owned()), - recursion_counter, - ) - .await?; - let user = blocking(context.pool(), move |conn| User_::upsert(conn, &uf)).await??; + let user = User_::from_apub(&person, context, apub_id.to_owned(), recursion_counter).await?; Ok(user) } @@ -317,7 +302,7 @@ pub(crate) async fn get_or_fetch_and_upsert_community( ) -> Result { let apub_id_owned = apub_id.to_owned(); let community = blocking(context.pool(), move |conn| { - Community::read_from_actor_id(conn, apub_id_owned.as_str()) + Community::read_from_apub_id(conn, apub_id_owned.as_str()) }) .await?; @@ -353,9 +338,8 @@ async fn fetch_remote_community( } let group = group?; - let cf = - CommunityForm::from_apub(&group, context, Some(apub_id.to_owned()), recursion_counter).await?; - let community = blocking(context.pool(), move |conn| Community::upsert(conn, &cf)).await??; + let community = + Community::from_apub(&group, context, apub_id.to_owned(), recursion_counter).await?; // Also add the community moderators too let attributed_to = group.inner.attributed_to().context(location_info!())?; @@ -405,23 +389,13 @@ async fn fetch_remote_community( } for o in outbox_items { let page = PageExt::from_any_base(o)?.context(location_info!())?; + let page_id = page.id_unchecked().context(location_info!())?; - // The post creator may be from a blocked instance, - // if it errors, then continue - let post = match PostForm::from_apub(&page, context, None, recursion_counter).await { - Ok(post) => post, - Err(_) => continue, - }; - let post_ap_id = post.ap_id.as_ref().context(location_info!())?.clone(); - // Check whether the post already exists in the local db - let existing = blocking(context.pool(), move |conn| { - Post::read_from_apub_id(conn, &post_ap_id) - }) - .await?; - match existing { - Ok(e) => blocking(context.pool(), move |conn| Post::update(conn, e.id, &post)).await??, - Err(_) => blocking(context.pool(), move |conn| Post::upsert(conn, &post)).await??, - }; + // The post creator may be from a blocked instance, if it errors, then skip it + if check_is_apub_id_valid(page_id).is_err() { + continue; + } + Post::from_apub(&page, context, page_id.to_owned(), recursion_counter).await?; // TODO: we need to send a websocket update here } @@ -447,17 +421,9 @@ pub(crate) async fn get_or_fetch_and_insert_post( Ok(p) => Ok(p), Err(NotFound {}) => { debug!("Fetching and creating remote post: {}", post_ap_id); - let post = + let page = fetch_remote_object::(context.client(), post_ap_id, recursion_counter).await?; - let post_form = PostForm::from_apub( - &post, - context, - Some(post_ap_id.to_owned()), - recursion_counter, - ) - .await?; - - let post = blocking(context.pool(), move |conn| Post::upsert(conn, &post_form)).await??; + let post = Post::from_apub(&page, context, post_ap_id.to_owned(), recursion_counter).await?; Ok(post) } @@ -489,25 +455,20 @@ pub(crate) async fn get_or_fetch_and_insert_comment( ); let comment = fetch_remote_object::(context.client(), comment_ap_id, recursion_counter).await?; - let comment_form = CommentForm::from_apub( + let comment = Comment::from_apub( &comment, context, - Some(comment_ap_id.to_owned()), + comment_ap_id.to_owned(), recursion_counter, ) .await?; - let post_id = comment_form.post_id; + let post_id = comment.post_id; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; if post.locked { return Err(anyhow!("Post is locked").into()); } - let comment = blocking(context.pool(), move |conn| { - Comment::upsert(conn, &comment_form) - }) - .await??; - Ok(comment) } Err(e) => Err(e.into()), diff --git a/lemmy_apub/src/http/comment.rs b/lemmy_apub/src/http/comment.rs index 936cd981..bb3d13ac 100644 --- a/lemmy_apub/src/http/comment.rs +++ b/lemmy_apub/src/http/comment.rs @@ -1,6 +1,6 @@ use crate::{ http::{create_apub_response, create_apub_tombstone_response}, - ToApub, + objects::ToApub, }; use actix_web::{body::Body, web, web::Path, HttpResponse}; use diesel::result::Error::NotFound; diff --git a/lemmy_apub/src/http/community.rs b/lemmy_apub/src/http/community.rs index 11322859..d2a18ee0 100644 --- a/lemmy_apub/src/http/community.rs +++ b/lemmy_apub/src/http/community.rs @@ -1,8 +1,8 @@ use crate::{ extensions::context::lemmy_context, http::{create_apub_response, create_apub_tombstone_response}, + objects::ToApub, ActorType, - ToApub, }; use activitystreams::{ base::{AnyBase, BaseExt, ExtendsExt}, diff --git a/lemmy_apub/src/http/post.rs b/lemmy_apub/src/http/post.rs index 7f4bb447..1d25ed95 100644 --- a/lemmy_apub/src/http/post.rs +++ b/lemmy_apub/src/http/post.rs @@ -1,6 +1,6 @@ use crate::{ http::{create_apub_response, create_apub_tombstone_response}, - ToApub, + objects::ToApub, }; use actix_web::{body::Body, web, HttpResponse}; use diesel::result::Error::NotFound; diff --git a/lemmy_apub/src/http/user.rs b/lemmy_apub/src/http/user.rs index 0f2e1a71..1e546d95 100644 --- a/lemmy_apub/src/http/user.rs +++ b/lemmy_apub/src/http/user.rs @@ -1,4 +1,9 @@ -use crate::{extensions::context::lemmy_context, http::create_apub_response, ActorType, ToApub}; +use crate::{ + extensions::context::lemmy_context, + http::create_apub_response, + objects::ToApub, + ActorType, +}; use activitystreams::{ base::BaseExt, collection::{CollectionExt, OrderedCollection}, diff --git a/lemmy_apub/src/inbox/community_inbox.rs b/lemmy_apub/src/inbox/community_inbox.rs index 37ae444e..1c7b32e9 100644 --- a/lemmy_apub/src/inbox/community_inbox.rs +++ b/lemmy_apub/src/inbox/community_inbox.rs @@ -30,6 +30,7 @@ use lemmy_db::{ community::{Community, CommunityFollower, CommunityFollowerForm}, user::User_, views::community_user_ban_view::CommunityUserBanView, + ApubObject, DbPool, Followable, }; @@ -118,7 +119,7 @@ pub(crate) async fn community_receive_message( // unconditionally. let actor_id = actor.actor_id_str(); let user = blocking(&context.pool(), move |conn| { - User_::read_from_actor_id(&conn, &actor_id) + User_::read_from_apub_id(&conn, &actor_id) }) .await??; check_community_or_site_ban(&user, &to_community, context.pool()).await?; @@ -242,7 +243,7 @@ async fn handle_undo_follow( verify_activity_domains_valid(&follow, &user_url, false)?; let user = blocking(&context.pool(), move |conn| { - User_::read_from_actor_id(&conn, user_url.as_str()) + User_::read_from_apub_id(&conn, user_url.as_str()) }) .await??; let community_follower_form = CommunityFollowerForm { @@ -260,7 +261,7 @@ async fn handle_undo_follow( Ok(()) } -async fn check_community_or_site_ban( +pub(crate) async fn check_community_or_site_ban( user: &User_, community: &Community, pool: &DbPool, diff --git a/lemmy_apub/src/inbox/mod.rs b/lemmy_apub/src/inbox/mod.rs index f8dd8bfe..d36a34c9 100644 --- a/lemmy_apub/src/inbox/mod.rs +++ b/lemmy_apub/src/inbox/mod.rs @@ -12,7 +12,7 @@ use activitystreams::{ }; use actix_web::HttpRequest; use anyhow::{anyhow, Context}; -use lemmy_db::{activity::Activity, community::Community, user::User_, DbPool}; +use lemmy_db::{activity::Activity, community::Community, user::User_, ApubObject, DbPool}; use lemmy_structs::blocking; use lemmy_utils::{location_info, settings::Settings, LemmyError}; use lemmy_websocket::LemmyContext; @@ -119,7 +119,7 @@ pub(crate) async fn is_addressed_to_local_user( ) -> Result { for url in to_and_cc { let url = url.to_string(); - let user = blocking(&pool, move |conn| User_::read_from_actor_id(&conn, &url)).await?; + let user = blocking(&pool, move |conn| User_::read_from_apub_id(&conn, &url)).await?; if let Ok(u) = user { if u.local { return Ok(true); @@ -141,7 +141,7 @@ pub(crate) async fn is_addressed_to_community_followers( if url.ends_with("/followers") { let community_url = url.replace("/followers", ""); let community = blocking(&pool, move |conn| { - Community::read_from_actor_id(&conn, &community_url) + Community::read_from_apub_id(&conn, &community_url) }) .await??; if !community.local { diff --git a/lemmy_apub/src/inbox/receive_for_community.rs b/lemmy_apub/src/inbox/receive_for_community.rs index b6dfa1e4..eaad6b1c 100644 --- a/lemmy_apub/src/inbox/receive_for_community.rs +++ b/lemmy_apub/src/inbox/receive_for_community.rs @@ -31,6 +31,7 @@ use crate::{ receive_unhandled_activity, verify_activity_domains_valid, }, + fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, inbox::is_addressed_to_public, }; use activitystreams::{ @@ -40,7 +41,7 @@ use activitystreams::{ }; use anyhow::Context; use diesel::result::Error::NotFound; -use lemmy_db::{comment::Comment, post::Post, site::Site, Crud}; +use lemmy_db::{comment::Comment, post::Post, site::Site, ApubObject, Crud}; use lemmy_structs::blocking; use lemmy_utils::{location_info, LemmyError}; use lemmy_websocket::LemmyContext; @@ -96,10 +97,12 @@ pub(in crate::inbox) async fn receive_like_for_community( verify_activity_domains_valid(&like, &expected_domain, false)?; is_addressed_to_public(&like)?; - match like.object().as_single_kind_str() { - Some("Page") => receive_like_post(like, context, request_counter).await, - Some("Note") => receive_like_comment(like, context, request_counter).await, - _ => receive_unhandled_activity(like), + let object_id = get_like_object_id(&like)?; + match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? { + PostOrComment::Post(post) => receive_like_post(like, post, context, request_counter).await, + PostOrComment::Comment(comment) => { + receive_like_comment(like, comment, context, request_counter).await + } } } @@ -122,10 +125,14 @@ pub(in crate::inbox) async fn receive_dislike_for_community( verify_activity_domains_valid(&dislike, &expected_domain, false)?; is_addressed_to_public(&dislike)?; - match dislike.object().as_single_kind_str() { - Some("Page") => receive_dislike_post(dislike, context, request_counter).await, - Some("Note") => receive_dislike_comment(dislike, context, request_counter).await, - _ => receive_unhandled_activity(dislike), + let object_id = get_like_object_id(&dislike)?; + match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? { + PostOrComment::Post(post) => { + receive_dislike_post(dislike, post, context, request_counter).await + } + PostOrComment::Comment(comment) => { + receive_dislike_comment(dislike, comment, context, request_counter).await + } } } @@ -275,14 +282,14 @@ pub(in crate::inbox) async fn receive_undo_like_for_community( verify_activity_domains_valid(&like, &expected_domain, false)?; is_addressed_to_public(&like)?; - let type_ = like - .object() - .as_single_kind_str() - .context(location_info!())?; - match type_ { - "Note" => receive_undo_like_comment(&like, context, request_counter).await, - "Page" => receive_undo_like_post(&like, context, request_counter).await, - _ => receive_unhandled_activity(like), + let object_id = get_like_object_id(&like)?; + match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? { + PostOrComment::Post(post) => { + receive_undo_like_post(&like, post, context, request_counter).await + } + PostOrComment::Comment(comment) => { + receive_undo_like_comment(&like, comment, context, request_counter).await + } } } @@ -298,14 +305,14 @@ pub(in crate::inbox) async fn receive_undo_dislike_for_community( verify_activity_domains_valid(&dislike, &expected_domain, false)?; is_addressed_to_public(&dislike)?; - let type_ = dislike - .object() - .as_single_kind_str() - .context(location_info!())?; - match type_ { - "Note" => receive_undo_dislike_comment(&dislike, context, request_counter).await, - "Page" => receive_undo_dislike_post(&dislike, context, request_counter).await, - _ => receive_unhandled_activity(dislike), + let object_id = get_like_object_id(&dislike)?; + match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? { + PostOrComment::Post(post) => { + receive_undo_dislike_post(&dislike, post, context, request_counter).await + } + PostOrComment::Comment(comment) => { + receive_undo_dislike_comment(&dislike, comment, context, request_counter).await + } } } @@ -341,3 +348,42 @@ async fn find_post_or_comment_by_id( return Err(NotFound.into()); } + +async fn fetch_post_or_comment_by_id( + apub_id: &Url, + context: &LemmyContext, + request_counter: &mut i32, +) -> Result { + if let Ok(post) = get_or_fetch_and_insert_post(apub_id, context, request_counter).await { + return Ok(PostOrComment::Post(post)); + } + + if let Ok(comment) = get_or_fetch_and_insert_comment(apub_id, context, request_counter).await { + return Ok(PostOrComment::Comment(comment)); + } + + return Err(NotFound.into()); +} + +fn get_like_object_id(like_or_dislike: &Activity) -> Result +where + Activity: ActorAndObjectRefExt, +{ + // TODO: For backwards compatibility with older Lemmy versions where like.object contains a full + // post/comment. This can be removed after some time, using + // `activity.oject().as_single_xsd_any_uri()` instead. + let object = like_or_dislike.object(); + if let Some(xsd_uri) = object.as_single_xsd_any_uri() { + Ok(xsd_uri.to_owned()) + } else { + Ok( + object + .to_owned() + .one() + .context(location_info!())? + .id() + .context(location_info!())? + .to_owned(), + ) + } +} diff --git a/lemmy_apub/src/inbox/shared_inbox.rs b/lemmy_apub/src/inbox/shared_inbox.rs index e9a81ab3..01086229 100644 --- a/lemmy_apub/src/inbox/shared_inbox.rs +++ b/lemmy_apub/src/inbox/shared_inbox.rs @@ -15,7 +15,7 @@ use crate::{ use activitystreams::{activity::ActorAndObject, prelude::*}; use actix_web::{web, HttpRequest, HttpResponse}; use anyhow::Context; -use lemmy_db::{community::Community, DbPool}; +use lemmy_db::{community::Community, ApubObject, DbPool}; use lemmy_structs::blocking; use lemmy_utils::{location_info, LemmyError}; use lemmy_websocket::LemmyContext; @@ -137,10 +137,7 @@ async fn extract_local_community_from_destinations( ) -> Result, LemmyError> { for url in to_and_cc { let url = url.to_string(); - let community = blocking(&pool, move |conn| { - Community::read_from_actor_id(&conn, &url) - }) - .await?; + let community = blocking(&pool, move |conn| Community::read_from_apub_id(&conn, &url)).await?; if let Ok(c) = community { if c.local { return Ok(Some(c)); diff --git a/lemmy_apub/src/inbox/user_inbox.rs b/lemmy_apub/src/inbox/user_inbox.rs index cc1c0661..28f2de60 100644 --- a/lemmy_apub/src/inbox/user_inbox.rs +++ b/lemmy_apub/src/inbox/user_inbox.rs @@ -52,6 +52,7 @@ use lemmy_db::{ community::{Community, CommunityFollower}, private_message::PrivateMessage, user::User_, + ApubObject, Followable, }; use lemmy_structs::blocking; @@ -377,7 +378,7 @@ async fn find_community_or_private_message_by_id( ) -> Result { let ap_id = apub_id.to_string(); let community = blocking(context.pool(), move |conn| { - Community::read_from_actor_id(conn, &ap_id) + Community::read_from_apub_id(conn, &ap_id) }) .await?; if let Ok(c) = community { diff --git a/lemmy_apub/src/lib.rs b/lemmy_apub/src/lib.rs index 2e3f7bfc..9b933b6e 100644 --- a/lemmy_apub/src/lib.rs +++ b/lemmy_apub/src/lib.rs @@ -18,7 +18,7 @@ use activitystreams::{ activity::Follow, actor::{ApActor, Group, Person}, base::AnyBase, - object::{ApObject, Note, Page, Tombstone}, + object::{ApObject, Note, Page}, }; use activitystreams_ext::{Ext1, Ext2}; use anyhow::{anyhow, Context}; @@ -109,33 +109,6 @@ fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> { } } -/// Trait for converting an object or actor into the respective ActivityPub type. -#[async_trait::async_trait(?Send)] -pub trait ToApub { - type ApubType; - async fn to_apub(&self, pool: &DbPool) -> Result; - fn to_tombstone(&self) -> Result; -} - -#[async_trait::async_trait(?Send)] -pub trait FromApub { - type ApubType; - /// Converts an object from ActivityPub type to Lemmy internal type. - /// - /// * `apub` The object to read from - /// * `context` LemmyContext which holds DB pool, HTTP client etc - /// * `expected_domain` If present, ensure that the domains of this and of the apub object ID are - /// identical - async fn from_apub( - apub: &Self::ApubType, - context: &LemmyContext, - expected_domain: Option, - request_counter: &mut i32, - ) -> Result - where - Self: Sized; -} - /// Common functions for ActivityPub objects, which are implemented by most (but not all) objects /// and actors in Lemmy. #[async_trait::async_trait(?Send)] @@ -248,7 +221,7 @@ pub trait ActorType { /// Store a sent or received activity in the database, for logging purposes. These records are not /// persistent. -pub async fn insert_activity( +pub(crate) async fn insert_activity( ap_id: &Url, activity: T, local: bool, diff --git a/lemmy_apub/src/objects/comment.rs b/lemmy_apub/src/objects/comment.rs index 277a55d0..56d75a40 100644 --- a/lemmy_apub/src/objects/comment.rs +++ b/lemmy_apub/src/objects/comment.rs @@ -7,19 +7,22 @@ use crate::{ }, objects::{ check_object_domain, + check_object_for_community_or_site_ban, create_tombstone, + get_object_from_apub, get_source_markdown_value, set_content_and_source, + FromApub, + FromApubToForm, + ToApub, }, - FromApub, NoteExt, - ToApub, }; use activitystreams::{ object::{kind::NoteType, ApObject, Note, Tombstone}, prelude::*, }; -use anyhow::Context; +use anyhow::{anyhow, Context}; use lemmy_db::{ comment::{Comment, CommentForm}, community::Community, @@ -87,16 +90,45 @@ impl ToApub for Comment { } #[async_trait::async_trait(?Send)] -impl FromApub for CommentForm { +impl FromApub for Comment { type ApubType = NoteExt; - /// Converts a `Note` to `CommentForm`. + /// Converts a `Note` to `Comment`. /// /// If the parent community, post and comment(s) are not known locally, these are also fetched. async fn from_apub( note: &NoteExt, context: &LemmyContext, - expected_domain: Option, + expected_domain: Url, + request_counter: &mut i32, + ) -> Result { + check_object_for_community_or_site_ban(note, context, request_counter).await?; + + let comment: Comment = + get_object_from_apub(note, context, expected_domain, request_counter).await?; + + let post_id = comment.post_id; + let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; + if post.locked { + // This is not very efficient because a comment gets inserted just to be deleted right + // afterwards, but it seems to be the easiest way to implement it. + blocking(context.pool(), move |conn| { + Comment::delete(conn, comment.id) + }) + .await??; + return Err(anyhow!("Post is locked").into()); + } else { + Ok(comment) + } + } +} + +#[async_trait::async_trait(?Send)] +impl FromApubToForm for CommentForm { + async fn from_apub( + note: &NoteExt, + context: &LemmyContext, + expected_domain: Url, request_counter: &mut i32, ) -> Result { let creator_actor_id = ¬e diff --git a/lemmy_apub/src/objects/community.rs b/lemmy_apub/src/objects/community.rs index 91638ef0..8cc5b9eb 100644 --- a/lemmy_apub/src/objects/community.rs +++ b/lemmy_apub/src/objects/community.rs @@ -4,13 +4,15 @@ use crate::{ objects::{ check_object_domain, create_tombstone, + get_object_from_apub, get_source_markdown_value, set_content_and_source, + FromApub, + FromApubToForm, + ToApub, }, ActorType, - FromApub, GroupExt, - ToApub, }; use activitystreams::{ actor::{kind::GroupType, ApActor, Endpoints, Group}, @@ -109,14 +111,28 @@ impl ToApub for Community { create_tombstone(self.deleted, &self.actor_id, self.updated, GroupType::Group) } } + #[async_trait::async_trait(?Send)] -impl FromApub for CommunityForm { +impl FromApub for Community { type ApubType = GroupExt; + /// Converts a `Group` to `Community`. async fn from_apub( group: &GroupExt, context: &LemmyContext, - expected_domain: Option, + expected_domain: Url, + request_counter: &mut i32, + ) -> Result { + get_object_from_apub(group, context, expected_domain, request_counter).await + } +} + +#[async_trait::async_trait(?Send)] +impl FromApubToForm for CommunityForm { + async fn from_apub( + group: &GroupExt, + context: &LemmyContext, + expected_domain: Url, request_counter: &mut i32, ) -> Result { let creator_and_moderator_uris = group.inner.attributed_to().context(location_info!())?; diff --git a/lemmy_apub/src/objects/mod.rs b/lemmy_apub/src/objects/mod.rs index a162c165..898c50f3 100644 --- a/lemmy_apub/src/objects/mod.rs +++ b/lemmy_apub/src/objects/mod.rs @@ -1,4 +1,8 @@ -use crate::check_is_apub_id_valid; +use crate::{ + check_is_apub_id_valid, + fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user}, + inbox::community_inbox::check_community_or_site_ban, +}; use activitystreams::{ base::{AsBase, BaseExt, ExtendsExt}, markers::Base, @@ -7,7 +11,10 @@ use activitystreams::{ }; use anyhow::{anyhow, Context}; use chrono::NaiveDateTime; -use lemmy_utils::{location_info, utils::convert_datetime, LemmyError}; +use lemmy_db::{ApubObject, Crud, DbPool}; +use lemmy_structs::blocking; +use lemmy_utils::{location_info, settings::Settings, utils::convert_datetime, LemmyError}; +use lemmy_websocket::LemmyContext; use url::Url; pub(crate) mod comment; @@ -16,6 +23,44 @@ pub(crate) mod post; pub(crate) mod private_message; pub(crate) mod user; +/// Trait for converting an object or actor into the respective ActivityPub type. +#[async_trait::async_trait(?Send)] +pub(crate) trait ToApub { + type ApubType; + async fn to_apub(&self, pool: &DbPool) -> Result; + fn to_tombstone(&self) -> Result; +} + +#[async_trait::async_trait(?Send)] +pub(crate) trait FromApub { + type ApubType; + /// Converts an object from ActivityPub type to Lemmy internal type. + /// + /// * `apub` The object to read from + /// * `context` LemmyContext which holds DB pool, HTTP client etc + /// * `expected_domain` Domain where the object was received from + async fn from_apub( + apub: &Self::ApubType, + context: &LemmyContext, + expected_domain: Url, + request_counter: &mut i32, + ) -> Result + where + Self: Sized; +} + +#[async_trait::async_trait(?Send)] +pub(in crate::objects) trait FromApubToForm { + async fn from_apub( + apub: &ApubType, + context: &LemmyContext, + expected_domain: Url, + request_counter: &mut i32, + ) -> Result + where + Self: Sized; +} + /// Updated is actually the deletion time fn create_tombstone( deleted: bool, @@ -43,17 +88,13 @@ where pub(in crate::objects) fn check_object_domain( apub: &T, - expected_domain: Option, + expected_domain: Url, ) -> Result where T: Base + AsBase, { - let object_id = if let Some(url) = expected_domain { - let domain = url.domain().context(location_info!())?; - apub.id(domain)?.context(location_info!())? - } else { - apub.id_unchecked().context(location_info!())? - }; + let domain = expected_domain.domain().context(location_info!())?; + let object_id = apub.id(domain)?.context(location_info!())?; check_is_apub_id_valid(&object_id)?; Ok(object_id.to_string()) } @@ -127,3 +168,60 @@ pub(in crate::objects) fn check_is_markdown(mime: Option<&Mime>) -> Result<(), L Ok(()) } } + +/// Converts an ActivityPub object (eg `Note`) to a database object (eg `Comment`). If an object +/// with the same ActivityPub ID already exists in the database, it is returned directly. Otherwise +/// the apub object is parsed, inserted and returned. +pub(in crate::objects) async fn get_object_from_apub( + from: &From, + context: &LemmyContext, + expected_domain: Url, + request_counter: &mut i32, +) -> Result +where + From: BaseExt, + To: ApubObject + Crud + Send + 'static, + ToForm: FromApubToForm + Send + 'static, +{ + let object_id = from.id_unchecked().context(location_info!())?.to_owned(); + let domain = object_id.domain().context(location_info!())?; + + // if its a local object, return it directly from the database + if Settings::get().hostname == domain { + let object = blocking(context.pool(), move |conn| { + To::read_from_apub_id(conn, object_id.as_str()) + }) + .await??; + Ok(object) + } + // otherwise parse and insert, assuring that it comes from the right domain + else { + let to_form = ToForm::from_apub(&from, context, expected_domain, request_counter).await?; + + let to = blocking(context.pool(), move |conn| To::upsert(conn, &to_form)).await??; + Ok(to) + } +} + +pub(in crate::objects) async fn check_object_for_community_or_site_ban( + object: &T, + context: &LemmyContext, + request_counter: &mut i32, +) -> Result<(), LemmyError> +where + T: ObjectExt, +{ + let user_id = object + .attributed_to() + .context(location_info!())? + .as_single_xsd_any_uri() + .context(location_info!())?; + let user = get_or_fetch_and_upsert_user(user_id, context, request_counter).await?; + let community_id = object + .to() + .context(location_info!())? + .as_single_xsd_any_uri() + .context(location_info!())?; + let community = get_or_fetch_and_upsert_community(community_id, context, request_counter).await?; + check_community_or_site_ban(&user, &community, context.pool()).await +} diff --git a/lemmy_apub/src/objects/post.rs b/lemmy_apub/src/objects/post.rs index a058d8b7..39b74997 100644 --- a/lemmy_apub/src/objects/post.rs +++ b/lemmy_apub/src/objects/post.rs @@ -3,13 +3,16 @@ use crate::{ fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user}, objects::{ check_object_domain, + check_object_for_community_or_site_ban, create_tombstone, + get_object_from_apub, get_source_markdown_value, set_content_and_source, + FromApub, + FromApubToForm, + ToApub, }, - FromApub, PageExt, - ToApub, }; use activitystreams::{ object::{kind::PageType, ApObject, Image, Page, Tombstone}, @@ -17,7 +20,6 @@ use activitystreams::{ }; use activitystreams_ext::Ext1; use anyhow::Context; -use backtrace::Backtrace; use lemmy_db::{ community::Community, post::{Post, PostForm}, @@ -33,7 +35,6 @@ use lemmy_utils::{ LemmyError, }; use lemmy_websocket::LemmyContext; -use log::error; use url::Url; #[async_trait::async_trait(?Send)] @@ -98,7 +99,7 @@ impl ToApub for Post { } #[async_trait::async_trait(?Send)] -impl FromApub for PostForm { +impl FromApub for Post { type ApubType = PageExt; /// Converts a `PageExt` to `PostForm`. @@ -107,7 +108,20 @@ impl FromApub for PostForm { async fn from_apub( page: &PageExt, context: &LemmyContext, - expected_domain: Option, + expected_domain: Url, + request_counter: &mut i32, + ) -> Result { + check_object_for_community_or_site_ban(page, context, request_counter).await?; + get_object_from_apub(page, context, expected_domain, request_counter).await + } +} + +#[async_trait::async_trait(?Send)] +impl FromApubToForm for PostForm { + async fn from_apub( + page: &PageExt, + context: &LemmyContext, + expected_domain: Url, request_counter: &mut i32, ) -> Result { let ext = &page.ext_one; @@ -132,15 +146,6 @@ impl FromApub for PostForm { let community = get_or_fetch_and_upsert_community(community_actor_id, context, request_counter).await?; - if community.local && creator.local { - let page_id = page.id_unchecked().context(location_info!())?; - let bt = Backtrace::new(); - error!( - "Lemmy is parsing a local post as remote, page id: {}, stack trace: {:?}", - page_id, bt - ); - } - let thumbnail_url = match &page.inner.image() { Some(any_image) => Image::from_any_base( any_image diff --git a/lemmy_apub/src/objects/private_message.rs b/lemmy_apub/src/objects/private_message.rs index 4e9af094..ec8b55e7 100644 --- a/lemmy_apub/src/objects/private_message.rs +++ b/lemmy_apub/src/objects/private_message.rs @@ -5,12 +5,14 @@ use crate::{ objects::{ check_object_domain, create_tombstone, + get_object_from_apub, get_source_markdown_value, set_content_and_source, + FromApub, + FromApubToForm, + ToApub, }, - FromApub, NoteExt, - ToApub, }; use activitystreams::{ object::{kind::NoteType, ApObject, Note, Tombstone}, @@ -63,13 +65,25 @@ impl ToApub for PrivateMessage { } #[async_trait::async_trait(?Send)] -impl FromApub for PrivateMessageForm { +impl FromApub for PrivateMessage { type ApubType = NoteExt; async fn from_apub( note: &NoteExt, context: &LemmyContext, - expected_domain: Option, + expected_domain: Url, + request_counter: &mut i32, + ) -> Result { + get_object_from_apub(note, context, expected_domain, request_counter).await + } +} + +#[async_trait::async_trait(?Send)] +impl FromApubToForm for PrivateMessageForm { + async fn from_apub( + note: &NoteExt, + context: &LemmyContext, + expected_domain: Url, request_counter: &mut i32, ) -> Result { let creator_actor_id = note diff --git a/lemmy_apub/src/objects/user.rs b/lemmy_apub/src/objects/user.rs index ddf33656..18490796 100644 --- a/lemmy_apub/src/objects/user.rs +++ b/lemmy_apub/src/objects/user.rs @@ -1,10 +1,15 @@ use crate::{ extensions::context::lemmy_context, - objects::{check_object_domain, get_source_markdown_value, set_content_and_source}, + objects::{ + check_object_domain, + get_source_markdown_value, + set_content_and_source, + FromApub, + FromApubToForm, + ToApub, + }, ActorType, - FromApub, PersonExt, - ToApub, }; use activitystreams::{ actor::{ApActor, Endpoints, Person}, @@ -16,10 +21,13 @@ use anyhow::Context; use lemmy_db::{ naive_now, user::{UserForm, User_}, + ApubObject, DbPool, }; +use lemmy_structs::blocking; use lemmy_utils::{ location_info, + settings::Settings, utils::{check_slurs, check_slurs_opt, convert_datetime}, LemmyError, }; @@ -81,13 +89,38 @@ impl ToApub for User_ { } #[async_trait::async_trait(?Send)] -impl FromApub for UserForm { +impl FromApub for User_ { type ApubType = PersonExt; + async fn from_apub( + person: &PersonExt, + context: &LemmyContext, + expected_domain: Url, + request_counter: &mut i32, + ) -> Result { + let user_id = person.id_unchecked().context(location_info!())?.to_owned(); + let domain = user_id.domain().context(location_info!())?; + if domain == Settings::get().hostname { + let user = blocking(context.pool(), move |conn| { + User_::read_from_apub_id(conn, user_id.as_str()) + }) + .await??; + Ok(user) + } else { + let user_form = + UserForm::from_apub(person, context, expected_domain, request_counter).await?; + let user = blocking(context.pool(), move |conn| User_::upsert(conn, &user_form)).await??; + Ok(user) + } + } +} + +#[async_trait::async_trait(?Send)] +impl FromApubToForm for UserForm { async fn from_apub( person: &PersonExt, _context: &LemmyContext, - expected_domain: Option, + expected_domain: Url, _request_counter: &mut i32, ) -> Result { let avatar = match person.icon() { diff --git a/lemmy_db/Cargo.toml b/lemmy_db/Cargo.toml index 1d13a88b..11b27fcd 100644 --- a/lemmy_db/Cargo.toml +++ b/lemmy_db/Cargo.toml @@ -11,7 +11,7 @@ path = "src/lib.rs" lemmy_utils = { path = "../lemmy_utils" } diesel = { version = "1.4.5", features = ["postgres","chrono","r2d2","64-column-tables","serde_json"] } chrono = { version = "0.4.19", features = ["serde"] } -serde = { version = "1.0.117", features = ["derive"] } +serde = { version = "1.0.118", features = ["derive"] } serde_json = { version = "1.0.60", features = ["preserve_order"] } strum = "0.20.0" strum_macros = "0.20.1" diff --git a/lemmy_db/src/comment.rs b/lemmy_db/src/comment.rs index c88eb9ad..fb327a30 100644 --- a/lemmy_db/src/comment.rs +++ b/lemmy_db/src/comment.rs @@ -2,6 +2,7 @@ use super::post::Post; use crate::{ naive_now, schema::{comment, comment_like, comment_saved}, + ApubObject, Crud, Likeable, Saveable, @@ -86,6 +87,23 @@ impl Crud for Comment { } } +impl ApubObject for Comment { + fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result { + use crate::schema::comment::dsl::*; + comment.filter(ap_id.eq(object_id)).first::(conn) + } + + fn upsert(conn: &PgConnection, comment_form: &CommentForm) -> Result { + use crate::schema::comment::dsl::*; + insert_into(comment) + .values(comment_form) + .on_conflict(ap_id) + .do_update() + .set(comment_form) + .get_result::(conn) + } +} + impl Comment { pub fn update_ap_id( conn: &PgConnection, @@ -99,11 +117,6 @@ impl Comment { .get_result::(conn) } - pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result { - use crate::schema::comment::dsl::*; - comment.filter(ap_id.eq(object_id)).first::(conn) - } - pub fn permadelete_for_creator( conn: &PgConnection, for_creator_id: i32, @@ -168,16 +181,6 @@ impl Comment { .set((content.eq(new_content), updated.eq(naive_now()))) .get_result::(conn) } - - pub fn upsert(conn: &PgConnection, comment_form: &CommentForm) -> Result { - use crate::schema::comment::dsl::*; - insert_into(comment) - .values(comment_form) - .on_conflict(ap_id) - .do_update() - .set(comment_form) - .get_result::(conn) - } } #[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Clone)] diff --git a/lemmy_db/src/community.rs b/lemmy_db/src/community.rs index 40f04680..eda643ad 100644 --- a/lemmy_db/src/community.rs +++ b/lemmy_db/src/community.rs @@ -1,6 +1,7 @@ use crate::{ naive_now, schema::{community, community_follower, community_moderator, community_user_ban}, + ApubObject, Bannable, Crud, Followable, @@ -149,6 +150,25 @@ impl Crud for Community { } } +impl ApubObject for Community { + fn read_from_apub_id(conn: &PgConnection, for_actor_id: &str) -> Result { + use crate::schema::community::dsl::*; + community + .filter(actor_id.eq(for_actor_id)) + .first::(conn) + } + + fn upsert(conn: &PgConnection, community_form: &CommunityForm) -> Result { + use crate::schema::community::dsl::*; + insert_into(community) + .values(community_form) + .on_conflict(actor_id) + .do_update() + .set(community_form) + .get_result::(conn) + } +} + impl Community { pub fn read_from_name(conn: &PgConnection, community_name: &str) -> Result { use crate::schema::community::dsl::*; @@ -158,13 +178,6 @@ impl Community { .first::(conn) } - pub fn read_from_actor_id(conn: &PgConnection, for_actor_id: &str) -> Result { - use crate::schema::community::dsl::*; - community - .filter(actor_id.eq(for_actor_id)) - .first::(conn) - } - pub fn update_deleted( conn: &PgConnection, community_id: i32, @@ -231,16 +244,6 @@ impl Community { .unwrap_or_default() .contains(&user_id) } - - pub fn upsert(conn: &PgConnection, community_form: &CommunityForm) -> Result { - use crate::schema::community::dsl::*; - insert_into(community) - .values(community_form) - .on_conflict(actor_id) - .do_update() - .set(community_form) - .get_result::(conn) - } } #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] diff --git a/lemmy_db/src/lib.rs b/lemmy_db/src/lib.rs index b5348a6c..0cf1cd61 100644 --- a/lemmy_db/src/lib.rs +++ b/lemmy_db/src/lib.rs @@ -123,6 +123,15 @@ pub trait Reportable { Self: Sized; } +pub trait ApubObject { + fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result + where + Self: Sized; + fn upsert(conn: &PgConnection, user_form: &T) -> Result + where + Self: Sized; +} + pub trait MaybeOptional { fn get_optional(self) -> Option; } diff --git a/lemmy_db/src/post.rs b/lemmy_db/src/post.rs index 787d5e6c..530f475b 100644 --- a/lemmy_db/src/post.rs +++ b/lemmy_db/src/post.rs @@ -1,6 +1,7 @@ use crate::{ naive_now, schema::{post, post_like, post_read, post_saved}, + ApubObject, Crud, Likeable, Readable, @@ -62,6 +63,47 @@ impl PostForm { } } +impl Crud for Post { + fn read(conn: &PgConnection, post_id: i32) -> Result { + use crate::schema::post::dsl::*; + post.find(post_id).first::(conn) + } + + fn delete(conn: &PgConnection, post_id: i32) -> Result { + use crate::schema::post::dsl::*; + diesel::delete(post.find(post_id)).execute(conn) + } + + fn create(conn: &PgConnection, new_post: &PostForm) -> Result { + use crate::schema::post::dsl::*; + insert_into(post).values(new_post).get_result::(conn) + } + + fn update(conn: &PgConnection, post_id: i32, new_post: &PostForm) -> Result { + use crate::schema::post::dsl::*; + diesel::update(post.find(post_id)) + .set(new_post) + .get_result::(conn) + } +} + +impl ApubObject for Post { + fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result { + use crate::schema::post::dsl::*; + post.filter(ap_id.eq(object_id)).first::(conn) + } + + fn upsert(conn: &PgConnection, post_form: &PostForm) -> Result { + use crate::schema::post::dsl::*; + insert_into(post) + .values(post_form) + .on_conflict(ap_id) + .do_update() + .set(post_form) + .get_result::(conn) + } +} + impl Post { pub fn read(conn: &PgConnection, post_id: i32) -> Result { use crate::schema::post::dsl::*; @@ -81,11 +123,6 @@ impl Post { .load::(conn) } - pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result { - use crate::schema::post::dsl::*; - post.filter(ap_id.eq(object_id)).first::(conn) - } - pub fn update_ap_id(conn: &PgConnection, post_id: i32, apub_id: String) -> Result { use crate::schema::post::dsl::*; @@ -177,40 +214,6 @@ impl Post { pub fn is_post_creator(user_id: i32, post_creator_id: i32) -> bool { user_id == post_creator_id } - - pub fn upsert(conn: &PgConnection, post_form: &PostForm) -> Result { - use crate::schema::post::dsl::*; - insert_into(post) - .values(post_form) - .on_conflict(ap_id) - .do_update() - .set(post_form) - .get_result::(conn) - } -} - -impl Crud for Post { - fn read(conn: &PgConnection, post_id: i32) -> Result { - use crate::schema::post::dsl::*; - post.find(post_id).first::(conn) - } - - fn delete(conn: &PgConnection, post_id: i32) -> Result { - use crate::schema::post::dsl::*; - diesel::delete(post.find(post_id)).execute(conn) - } - - fn create(conn: &PgConnection, new_post: &PostForm) -> Result { - use crate::schema::post::dsl::*; - insert_into(post).values(new_post).get_result::(conn) - } - - fn update(conn: &PgConnection, post_id: i32, new_post: &PostForm) -> Result { - use crate::schema::post::dsl::*; - diesel::update(post.find(post_id)) - .set(new_post) - .get_result::(conn) - } } #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] diff --git a/lemmy_db/src/private_message.rs b/lemmy_db/src/private_message.rs index 503a26ab..0e1aef10 100644 --- a/lemmy_db/src/private_message.rs +++ b/lemmy_db/src/private_message.rs @@ -1,4 +1,4 @@ -use crate::{naive_now, schema::private_message, Crud}; +use crate::{naive_now, schema::private_message, ApubObject, Crud}; use diesel::{dsl::*, result::Error, *}; #[derive(Queryable, Identifiable, PartialEq, Debug)] @@ -55,6 +55,28 @@ impl Crud for PrivateMessage { } } +impl ApubObject for PrivateMessage { + fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result + where + Self: Sized, + { + use crate::schema::private_message::dsl::*; + private_message + .filter(ap_id.eq(object_id)) + .first::(conn) + } + + fn upsert(conn: &PgConnection, private_message_form: &PrivateMessageForm) -> Result { + use crate::schema::private_message::dsl::*; + insert_into(private_message) + .values(private_message_form) + .on_conflict(ap_id) + .do_update() + .set(private_message_form) + .get_result::(conn) + } +} + impl PrivateMessage { pub fn update_ap_id( conn: &PgConnection, @@ -68,13 +90,6 @@ impl PrivateMessage { .get_result::(conn) } - pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result { - use crate::schema::private_message::dsl::*; - private_message - .filter(ap_id.eq(object_id)) - .first::(conn) - } - pub fn update_content( conn: &PgConnection, private_message_id: i32, @@ -118,20 +133,6 @@ impl PrivateMessage { .set(read.eq(true)) .get_results::(conn) } - - // TODO use this - pub fn upsert( - conn: &PgConnection, - private_message_form: &PrivateMessageForm, - ) -> Result { - use crate::schema::private_message::dsl::*; - insert_into(private_message) - .values(private_message_form) - .on_conflict(ap_id) - .do_update() - .set(private_message_form) - .get_result::(conn) - } } #[cfg(test)] diff --git a/lemmy_db/src/user.rs b/lemmy_db/src/user.rs index b2cb0e17..41d3ed18 100644 --- a/lemmy_db/src/user.rs +++ b/lemmy_db/src/user.rs @@ -2,6 +2,7 @@ use crate::{ is_email_regex, naive_now, schema::{user_, user_::dsl::*}, + ApubObject, Crud, }; use bcrypt::{hash, DEFAULT_COST}; @@ -151,6 +152,25 @@ impl Crud for User_ { } } +impl ApubObject for User_ { + fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result { + use crate::schema::user_::dsl::*; + user_ + .filter(deleted.eq(false)) + .filter(actor_id.eq(object_id)) + .first::(conn) + } + + fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result { + insert_into(user_) + .values(user_form) + .on_conflict(actor_id) + .do_update() + .set(user_form) + .get_result::(conn) + } +} + impl User_ { pub fn register(conn: &PgConnection, form: &UserForm) -> Result { let mut edited_user = form.clone(); @@ -197,14 +217,6 @@ impl User_ { .get_result::(conn) } - pub fn read_from_actor_id(conn: &PgConnection, object_id: &str) -> Result { - use crate::schema::user_::dsl::*; - user_ - .filter(deleted.eq(false)) - .filter(actor_id.eq(object_id)) - .first::(conn) - } - pub fn find_by_email_or_username( conn: &PgConnection, username_or_email: &str, @@ -241,12 +253,9 @@ impl User_ { ) } - pub fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result { - insert_into(user_) - .values(user_form) - .on_conflict(actor_id) - .do_update() - .set(user_form) + pub fn mark_as_updated(conn: &PgConnection, user_id: i32) -> Result { + diesel::update(user_.find(user_id)) + .set((last_refreshed_at.eq(naive_now()),)) .get_result::(conn) } diff --git a/lemmy_structs/Cargo.toml b/lemmy_structs/Cargo.toml index d11f4a68..e1462306 100644 --- a/lemmy_structs/Cargo.toml +++ b/lemmy_structs/Cargo.toml @@ -11,7 +11,7 @@ path = "src/lib.rs" [dependencies] lemmy_db = { path = "../lemmy_db" } lemmy_utils = { path = "../lemmy_utils" } -serde = { version = "1.0.117", features = ["derive"] } +serde = { version = "1.0.118", features = ["derive"] } log = "0.4.11" diesel = "1.4.5" actix-web = "3.3.2" diff --git a/lemmy_utils/Cargo.toml b/lemmy_utils/Cargo.toml index 2a102110..ae3d246b 100644 --- a/lemmy_utils/Cargo.toml +++ b/lemmy_utils/Cargo.toml @@ -16,7 +16,7 @@ log = "0.4.11" itertools = "0.9.0" rand = "0.7.3" percent-encoding = "2.1.0" -serde = { version = "1.0.117", features = ["derive"] } +serde = { version = "1.0.118", features = ["derive"] } serde_json = { version = "1.0.60", features = ["preserve_order"] } thiserror = "1.0.22" comrak = { version = "0.9.0", default-features = false } @@ -25,5 +25,5 @@ openssl = "0.10.30" url = { version = "2.2.0", features = ["serde"] } actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] } actix-rt = { version = "1.1.1", default-features = false } -anyhow = "1.0.34" +anyhow = "1.0.35" reqwest = { version = "0.10.9", features = ["json"] } diff --git a/lemmy_websocket/Cargo.toml b/lemmy_websocket/Cargo.toml index 46a67129..a7b710bc 100644 --- a/lemmy_websocket/Cargo.toml +++ b/lemmy_websocket/Cargo.toml @@ -16,10 +16,10 @@ lemmy_rate_limit = { path = "../lemmy_rate_limit" } reqwest = { version = "0.10.9", features = ["json"] } log = "0.4.11" rand = "0.7.3" -serde = { version = "1.0.117", features = ["derive"] } +serde = { version = "1.0.118", features = ["derive"] } serde_json = { version = "1.0.60", features = ["preserve_order"] } actix = "0.10.0" -anyhow = "1.0.34" +anyhow = "1.0.35" diesel = "1.4.5" background-jobs = "0.8.0" tokio = "0.3.5"